├── .appveyor ├── build.cmd └── install_numpy.py ├── .coveragerc ├── .gitignore ├── .travis.yml ├── .travis ├── before_install.sh ├── install.sh └── upload_coverage.sh ├── CHANGELOG.md ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── data └── constants │ ├── README.txt │ ├── iau.txt │ └── wgs84.txt ├── docs ├── _static │ ├── no_scrollbars.css │ └── style.css ├── _templates │ └── layout.html ├── astrodynamics-docs.py ├── changelog.rst ├── conf.py ├── development │ ├── getting-started.rst │ ├── index.rst │ ├── reviewing-patches.rst │ └── submitting-patches.rst ├── glossary.rst ├── index.rst ├── module.rst ├── modules │ ├── bodies │ │ ├── celestialbody.rst │ │ ├── ellipsoid.rst │ │ └── index.rst │ ├── constants.rst │ └── utils │ │ ├── compat.rst │ │ ├── helper.rst │ │ ├── index.rst │ │ └── web.rst └── spelling_wordlist.txt ├── licenses ├── ASTROPY_LICENSE.txt ├── CRYPTOGRAPHY_LICENSE.txt └── LICENSE.txt ├── setup.py ├── shovel ├── _helpers.py ├── code.py ├── constants.py ├── docs.py └── test.py ├── src └── astrodynamics │ ├── __init__.py │ ├── __main__.py │ ├── bodies │ ├── __init__.py │ ├── celestialbody.py │ └── ellipsoid.py │ ├── compat │ ├── __init__.py │ ├── contextlib.py │ └── math.py │ ├── constants │ ├── __init__.py │ ├── constant.py │ ├── iau.py │ └── wgs84.py │ ├── lowlevel │ ├── __init__.py │ └── ephemerides.py │ └── utils │ ├── __init__.py │ ├── compat.py │ ├── helper.py │ ├── progress.py │ └── web.py ├── tests ├── __init__.py ├── test_bodies.py ├── test_compat.py ├── test_constants.py ├── test_coverage.py ├── test_ephemerides.py ├── test_isclose.py └── test_utils.py └── tox.ini /.appveyor/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: To build extensions for 64 bit Python 3, we need to configure environment 3 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 4 | :: MS Windows SDK for Windows 7 and .NET Framework 4 5 | :: 6 | :: More details at: 7 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows 8 | 9 | IF "%DISTUTILS_USE_SDK%"=="1" ( 10 | ECHO Configuring environment to build with MSVC on a 64bit architecture 11 | ECHO Using Windows SDK 7.1 12 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 13 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release 14 | SET MSSdk=1 15 | REM Need the following to allow tox to see the SDK compiler 16 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB 17 | ) ELSE ( 18 | ECHO Using default MSVC build environment 19 | ) 20 | 21 | CALL %* -------------------------------------------------------------------------------- /.appveyor/install_numpy.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | import pip 6 | import struct 7 | import ssl 8 | import sys 9 | 10 | from six.moves.urllib.request import urlretrieve 11 | 12 | 13 | def download_numpy_wheel(): 14 | base_url = os.getenv('NUMPY_URL') 15 | if base_url is None: 16 | raise ValueError('NUMPY_URL environment variable is missing.') 17 | 18 | version = '1.10.4+mkl' 19 | py = 'cp{0[0]}{0[1]}'.format(sys.version_info) 20 | if py not in {'cp27', 'cp34', 'cp35'}: 21 | print('NumPy wheel not available for {}'.format(py)) 22 | return None 23 | 24 | bits = struct.calcsize('P') * 8 25 | if bits == 32: 26 | arch = 'win32' 27 | elif bits == 64: 28 | arch = 'win_amd64' 29 | else: 30 | raise ValueError("Couldn't determine 32/64 bits.") 31 | 32 | filename = 'numpy-{}-{}-none-{}.whl'.format(version, py, arch) 33 | 34 | directory = 'astrodynamics-numpy-wheels' 35 | os.mkdir(directory) 36 | 37 | filepath = os.path.join(directory, filename) 38 | url = base_url + filename 39 | 40 | # Disable SSL. Shouldn't do this ever. This is just a script. 41 | ssl._create_default_https_context = ssl._create_unverified_context 42 | urlretrieve(url, filepath) 43 | return filepath 44 | 45 | 46 | def install_numpy(): 47 | filepath = download_numpy_wheel() 48 | if filepath: 49 | pip.main(['install', filepath]) 50 | else: 51 | pip.main(['install', 'numpy']) 52 | 53 | 54 | if __name__ == '__main__': 55 | install_numpy() 56 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch=True 3 | source= 4 | astrodynamics 5 | tests/ 6 | 7 | [paths] 8 | source= 9 | src/astrodynamics 10 | .tox/*/lib/python*/site-packages/astrodynamics 11 | C:\projects\venv\Lib\site-packages\astrodynamics 12 | 13 | [report] 14 | exclude_lines= 15 | if __name__ == '__main__': -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | .benchmarks/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | .venv/ 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | cache: 4 | directories: 5 | - $HOME/.cache/pip 6 | # OS X cache not yet enabled: https://github.com/travis-ci/travis-ci/issues/4011 7 | - $HOME/Library/Caches/pip 8 | matrix: 9 | include: 10 | - python: 2.7 11 | env: TOXENV=py27 12 | - python: 3.3 13 | env: TOXENV=py33 14 | - python: 3.4 15 | env: TOXENV=py34 16 | - python: 3.5 17 | env: TOXENV=py35 18 | - python: 2.7 19 | env: TOXENV=py2flake8 20 | - python: 3.4 21 | env: TOXENV=py3flake8 22 | - python: 3.4 23 | env: TOXENV=docs 24 | addons: 25 | apt: 26 | packages: 27 | - libenchant-dev 28 | - graphviz 29 | - language: generic 30 | os: osx 31 | osx_image: xcode7 32 | env: TOXENV=py27 33 | - language: generic 34 | os: osx 35 | osx_image: xcode7 36 | env: TOXENV=py33 37 | - language: generic 38 | os: osx 39 | osx_image: xcode7 40 | env: TOXENV=py34 41 | - language: generic 42 | os: osx 43 | osx_image: xcode7 44 | env: TOXENV=py35 45 | 46 | before_install: 47 | - ./.travis/before_install.sh 48 | 49 | install: 50 | - ./.travis/install.sh 51 | 52 | script: 53 | - source ~/.venv/bin/activate 54 | - tox 55 | 56 | after_success: 57 | - ./.travis/upload_coverage.sh 58 | 59 | after_script: 60 | - cat .tox/$TOXENV/log/$TOXENV-*.log 61 | 62 | notifications: 63 | slack: 64 | secure: lm7XUGVMxiJMMzMHusSO0uFsWVsSATpq9WRLj5++EQrxF5KUQcocob0JL6KT2JvPI6qfxI9iVnVeePM1nmt9Mly52efSKjyiDDQBth5KnLFB/eHqpiXpl/fzaVYIiTR0lEQwYpfU2dPa2xaBjw8YguMNcbQdi4k9DRwj6Ax1wwvJQdglC+zBt+AcGxdsL7CDR/CWU3cvlc9Id+ED5Rm8myHknK6BJARyJTnRD0RtOLT2SCz4E6o/h2XaCxRwyNLwGSNbeIW1ieYtEitw442M0n5qAAIvxXJ9awRUTcAVxs9Bh+nLRdKTSeYqcOyCnwhCHQRG7iO7WIVio0VMrD/55JPXQgKNocTSGKcSskRKT1tnL3nmvJ7BVB8w5ghLMv0wdSVwoY7A56KSZpPP316RFTa8FVTVDxWtlu4ydDMsvtYWKBD2Wwym1CR2+c25Bdee1eIIogmpKEScJzKRquV+pEciu1geqTWuI/bxGOFFVYFRUdQmA1blnFHiFcrA/vqoouJNNVPY+yeyUQafveSy+Thz8Jvj87Ac0ZdLTnCoTwX9iJVTkJ5+JxsxF4BASdS2UXkktKpf2bnpTM86BO7BnFig4ou/OEa0a+i3vD35F/7XIuic+9vczhbccsLqtMewpTXu6kv9RoelB5ntjQx1JxEIvGgkIh5BBaG5gXzc5YE= 65 | 66 | # Blacklist AppVeyor branch used to test changes to config 67 | branches: 68 | except: 69 | - appveyor 70 | -------------------------------------------------------------------------------- /.travis/before_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | if [[ "$(uname -s)" == 'Darwin' ]]; then 7 | brew install findutils 8 | if [ -d "$HOME/Library/Caches/pip" ]; then 9 | gfind $HOME/Library/Caches/pip -type f -name '*.whl' 10 | fi 11 | else 12 | if [ -d "$HOME/.cache/pip" ]; then 13 | find $HOME/.cache/pip -type f -name '*.whl' 14 | fi 15 | fi 16 | -------------------------------------------------------------------------------- /.travis/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | if [[ "$(uname -s)" == 'Darwin' ]]; then 7 | brew update || brew update 8 | brew install pyenv 9 | brew outdated pyenv || brew upgrade pyenv 10 | 11 | if which -s pyenv; then 12 | eval "$(pyenv init -)" 13 | fi 14 | 15 | case "${TOXENV}" in 16 | py27) 17 | curl -O https://bootstrap.pypa.io/get-pip.py 18 | python get-pip.py --user 19 | ;; 20 | py33) 21 | pyenv install 3.3.6 22 | pyenv global 3.3.6 23 | ;; 24 | py34) 25 | pyenv install 3.4.3 26 | pyenv global 3.4.3 27 | ;; 28 | py35) 29 | pyenv install 3.5.0 30 | pyenv global 3.5.0 31 | ;; 32 | esac 33 | pyenv rehash 34 | python -m pip install --user virtualenv 35 | else 36 | pip install virtualenv 37 | fi 38 | 39 | python -m virtualenv ~/.venv 40 | source ~/.venv/bin/activate 41 | pip install tox codecov 42 | -------------------------------------------------------------------------------- /.travis/upload_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | NO_COVERAGE_TOXENVS=(py2flake8 py3flake8 docs) 7 | if ! [[ "${NO_COVERAGE_TOXENVS[*]}" =~ "${TOXENV}" ]]; then 8 | source ~/.venv/bin/activate 9 | codecov --env TRAVIS_OS_NAME TOXENV 10 | fi 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased][unreleased] 4 | N/A 5 | 6 | [unreleased]: https://github.com/python-astrodynamics/astrodynamics/compare/0ef60c1cef3979df819c8f7c0819f1ca052368f6...HEAD 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .coveragerc 2 | include CHANGELOG.md 3 | include tox.ini 4 | recursive-include licenses *.txt 5 | recursive-include data * 6 | recursive-include docs * 7 | prune docs/_build 8 | recursive-include shovel *.py 9 | recursive-include tests *.py -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | astrodynamics 2 | ------------- 3 | 4 | |PyPI Version| |Documentation| |Travis| |AppVeyor| |Coverage| |Python Version| |MIT License| 5 | 6 | Installation 7 | ~~~~~~~~~~~~ 8 | 9 | .. code:: bash 10 | 11 | $ pip install astrodynamics 12 | 13 | Authors 14 | ~~~~~~~ 15 | - Juan Luis Cano 16 | - Helge Eichhorn 17 | - Frazer McLean 18 | 19 | Documentation 20 | ~~~~~~~~~~~~~ 21 | 22 | For in-depth information, `visit the 23 | documentation `__! 24 | 25 | Development 26 | ~~~~~~~~~~~ 27 | 28 | astrodynamics uses `semantic versioning `__ 29 | 30 | .. |Travis| image:: http://img.shields.io/travis/python-astrodynamics/astrodynamics/master.svg?style=flat-square&label=travis 31 | :target: https://travis-ci.org/python-astrodynamics/astrodynamics 32 | .. |AppVeyor| image:: https://img.shields.io/appveyor/ci/pythonastrodynamics/astrodynamics/master.svg?style=flat-square&label=appveyor 33 | :target: https://ci.appveyor.com/project/pythonastrodynamics/astrodynamics 34 | .. |PyPI Version| image:: http://img.shields.io/pypi/v/astrodynamics.svg?style=flat-square 35 | :target: https://pypi.python.org/pypi/astrodynamics/ 36 | .. |Python Version| image:: https://img.shields.io/badge/python-2.7%2C%203-brightgreen.svg?style=flat-square 37 | :target: https://www.python.org/downloads/ 38 | .. |MIT License| image:: http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square 39 | :target: https://raw.githubusercontent.com/python-astrodynamics/astrodynamics/master/LICENSE 40 | .. |Coverage| image:: https://img.shields.io/codecov/c/github/python-astrodynamics/astrodynamics/master.svg?style=flat-square 41 | :target: https://codecov.io/github/python-astrodynamics/astrodynamics?branch=master 42 | .. |Documentation| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat-square 43 | :target: http://astrodynamics.readthedocs.org/en/latest/ 44 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | - C:\Users\appveyor\AppData\Local\pip\Cache\wheels 3 | 4 | environment: 5 | global: 6 | # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the 7 | # /E:ON and /V:ON options are not enabled in the batch script intepreter 8 | # See: http://stackoverflow.com/a/13751649/163740 9 | BUILD: "cmd /E:ON /V:ON /C .\\.appveyor\\build.cmd" 10 | NUMPY_URL: 11 | secure: drCgBIMN3vdVjPRXNyiBAHa4g9VeRmnylFuhd7aUUA7XYhA8FfyJ5bHdXtzkFhgT6GzUSXqp19RcXW6DOX9eow== 12 | 13 | matrix: 14 | # Pre-installed Python versions, which Appveyor may upgrade to 15 | # a later point release. 16 | # See: http://www.appveyor.com/docs/installed-software#python 17 | 18 | - PYTHON: "C:\\Python27" 19 | 20 | - PYTHON: "C:\\Python27-x64" 21 | 22 | - PYTHON: "C:\\Python33" 23 | 24 | - PYTHON: "C:\\Python33-x64" 25 | DISTUTILS_USE_SDK: "1" 26 | 27 | - PYTHON: "C:\\Python34" 28 | 29 | - PYTHON: "C:\\Python34-x64" 30 | DISTUTILS_USE_SDK: "1" 31 | 32 | - PYTHON: "C:\\Python35" 33 | 34 | - PYTHON: "C:\\Python35-x64" 35 | 36 | init: 37 | - set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH% 38 | 39 | install: 40 | - pip install -U virtualenv 41 | - python -m virtualenv C:\projects\venv 42 | - C:\projects\venv\Scripts\activate.bat 43 | - pip install -U codecov coverage setuptools six wheel 44 | - "%BUILD% python .appveyor\\install_numpy.py" 45 | - "%BUILD% pip install .[test]" 46 | 47 | build: off 48 | 49 | test_script: 50 | - coverage run --parallel-mode -m pytest 51 | - coverage combine 52 | - coverage report -m 53 | 54 | after_test: 55 | - codecov --env APPVEYOR PYTHON 56 | - "%BUILD% python setup.py bdist_wheel" 57 | 58 | artifacts: 59 | # Archive the generated packages in the ci.appveyor.com build report. 60 | - path: dist\* 61 | 62 | # Blacklist Travis branch used to test changes to config 63 | branches: 64 | except: 65 | - travis 66 | -------------------------------------------------------------------------------- /data/constants/README.txt: -------------------------------------------------------------------------------- 1 | - Constants should be valid Python 2 | - astropy.units is imported as 'u' 3 | - astrodynamics.constant.Constant is imported as 'Constant' 4 | -------------------------------------------------------------------------------- /data/constants/iau.txt: -------------------------------------------------------------------------------- 1 | CONSTANT_OF_GRAVITATION = Constant( 2 | name='Constant of gravitation', 3 | value=6.67428e-11, 4 | unit='m3 / (kg s2)', 5 | uncertainty=6.7e-15, 6 | reference='IAU 2009/2012 System of Astronomical Constants') 7 | 8 | SOLAR_MASS_PARAMETER = Constant( 9 | name='Solar mass parameter (TCB)', 10 | value=1.32712442099e20, 11 | unit='m3 / s2', 12 | uncertainty=1e10, 13 | reference='IAU 2009/2012 System of Astronomical Constants') 14 | 15 | EARTH_RADIUS_EQUATORIAL = Constant( 16 | name='Equatorial radius of Earth (TT)', 17 | value=6378136.6, 18 | unit='m', 19 | uncertainty=0.1, 20 | reference='IAU 2009/2012 System of Astronomical Constants') 21 | 22 | J2 = Constant( 23 | name='Dynamical form-factor for the Earth', 24 | value=0.0010826359, 25 | unit='', 26 | uncertainty=1e-10, 27 | reference='IAU 2009/2012 System of Astronomical Constants') 28 | 29 | GEOCENTRIC_GRAVITATIONAL_CONSTANT = Constant( 30 | name='Geocentric gravitational constant (TCB)', 31 | value=3.986004418e14, 32 | unit='m3 / s2', 33 | uncertainty=8e5, 34 | reference='IAU 2009/2012 System of Astronomical Constants') 35 | 36 | GEOID_POTENTIAL = Constant( 37 | name='Potential of the geoid', 38 | value=6.26368560e7, 39 | unit='m2 / s2', 40 | uncertainty=0.5, 41 | reference='IAU 2009/2012 System of Astronomical Constants') 42 | 43 | EARTH_ANGULAR_VELOCITY = Constant( 44 | name='Nominal mean angular velocity of the Earth (TT)', 45 | value=7.292115e-5, 46 | unit='rad / s', 47 | uncertainty=0, 48 | reference='IAU 2009/2012 System of Astronomical Constants') 49 | 50 | MASS_RATIO_MOON_TO_EARTH = Constant( 51 | name='Mass ratio: Moon to Earth', 52 | value=1.23000371e-2, 53 | unit='', 54 | uncertainty=4e-10, 55 | reference='IAU 2009/2012 System of Astronomical Constants') 56 | 57 | MASS_RATIO_SUN_TO_MERCURY = Constant( 58 | name='Mass ratio: Sun to Mercury', 59 | value=6.0236e6, 60 | unit='', 61 | uncertainty=3e2, 62 | reference='IAU 2009/2012 System of Astronomical Constants') 63 | 64 | MASS_RATIO_SUN_TO_VENUS = Constant( 65 | name='Mass ratio: Sun to Venus', 66 | value=4.08523719e5, 67 | unit='', 68 | uncertainty=8e-3, 69 | reference='IAU 2009/2012 System of Astronomical Constants') 70 | 71 | MASS_RATIO_SUN_TO_MARS = Constant( 72 | name='Mass ratio: Sun to Mars', 73 | value=3.09870359e6, 74 | unit='', 75 | uncertainty=2e-2, 76 | reference='IAU 2009/2012 System of Astronomical Constants') 77 | 78 | MASS_RATIO_SUN_TO_JUPITER = Constant( 79 | name='Mass ratio: Sun to Jupiter', 80 | value=1.047348644e3, 81 | unit='', 82 | uncertainty=1.7e-5, 83 | reference='IAU 2009/2012 System of Astronomical Constants') 84 | 85 | MASS_RATIO_SUN_TO_SATURN = Constant( 86 | name='Mass ratio: Sun to Saturn', 87 | value=3.4979018e3, 88 | unit='', 89 | uncertainty=1e-4, 90 | reference='IAU 2009/2012 System of Astronomical Constants') 91 | 92 | MASS_RATIO_SUN_TO_URANUS = Constant( 93 | name='Mass ratio: Sun to Uranus', 94 | value=2.290298e4, 95 | unit='', 96 | uncertainty=3e-2, 97 | reference='IAU 2009/2012 System of Astronomical Constants') 98 | 99 | MASS_RATIO_SUN_TO_NEPTUNE = Constant( 100 | name='Mass ratio: Sun to Neptune', 101 | value=1.941226e4, 102 | unit='', 103 | uncertainty=3e-2, 104 | reference='IAU 2009/2012 System of Astronomical Constants') 105 | 106 | MASS_RATIO_SUN_TO_PLUTO = Constant( 107 | name='Mass ratio: Sun to Pluto (134340)', 108 | value=1.36566e8, 109 | unit='', 110 | uncertainty=2.8e4, 111 | reference='IAU 2009/2012 System of Astronomical Constants') 112 | 113 | MERCURY_RADIUS_MEAN = Constant( 114 | name='Mean radius of Mercury', 115 | value=2439.9, 116 | unit='km', 117 | uncertainty=1, 118 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 119 | 120 | MERCURY_RADIUS_EQUATORIAL = Constant( 121 | name='Equatorial radius of Mercury', 122 | value=2439.9, 123 | unit='km', 124 | uncertainty=1, 125 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 126 | 127 | MERCURY_RADIUS_POLAR = Constant( 128 | name='Polar radius of Mercury', 129 | value=2439.9, 130 | unit='km', 131 | uncertainty=1, 132 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 133 | 134 | VENUS_RADIUS_MEAN = Constant( 135 | name='Mean radius of Venus', 136 | value=6051.8, 137 | unit='km', 138 | uncertainty=1, 139 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 140 | 141 | VENUS_RADIUS_EQUATORIAL = Constant( 142 | name='Equatorial radius of Venus', 143 | value=6051.8, 144 | unit='km', 145 | uncertainty=1, 146 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 147 | 148 | VENUS_RADIUS_POLAR = Constant( 149 | name='Polar radius of Venus', 150 | value=6051.8, 151 | unit='km', 152 | uncertainty=1, 153 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 154 | 155 | EARTH_RADIUS_MEAN = Constant( 156 | name='Mean radius of Earth', 157 | value=6371.0084, 158 | unit='km', 159 | uncertainty=0.0001, 160 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 161 | 162 | EARTH_RADIUS_POLAR = Constant( 163 | name='Polar radius of Earth', 164 | value=6356.7519, 165 | unit='km', 166 | uncertainty=0.0001, 167 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 168 | 169 | MARS_RADIUS_MEAN = Constant( 170 | name='Mean radius of Mars', 171 | value=3389.50, 172 | unit='km', 173 | uncertainty=0.2, 174 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 175 | 176 | MARS_RADIUS_EQUATORIAL = Constant( 177 | name='Equatorial radius of Mars', 178 | value=3396.19, 179 | unit='km', 180 | uncertainty=0.1, 181 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 182 | 183 | MARS_RADIUS_POLAR = Constant( 184 | name='Polar radius of Mars', 185 | value=3376.20, 186 | unit='km', 187 | uncertainty=0.1, 188 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 189 | 190 | JUPITER_RADIUS_MEAN = Constant( 191 | name='Mean radius of Jupiter', 192 | value=69911, 193 | unit='km', 194 | uncertainty=6, 195 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 196 | 197 | JUPITER_RADIUS_EQUATORIAL = Constant( 198 | name='Equatorial radius of Jupiter', 199 | value=71492, 200 | unit='km', 201 | uncertainty=4, 202 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 203 | 204 | JUPITER_RADIUS_POLAR = Constant( 205 | name='Polar radius of Jupiter', 206 | value=66854, 207 | unit='km', 208 | uncertainty=10, 209 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 210 | 211 | SATURN_RADIUS_MEAN = Constant( 212 | name='Mean radius of Saturn', 213 | value=58232, 214 | unit='km', 215 | uncertainty=6, 216 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 217 | 218 | SATURN_RADIUS_EQUATORIAL = Constant( 219 | name='Equatorial radius of Saturn', 220 | value=60268, 221 | unit='km', 222 | uncertainty=4, 223 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 224 | 225 | SATURN_RADIUS_POLAR = Constant( 226 | name='Polar radius of Saturn', 227 | value=54364, 228 | unit='km', 229 | uncertainty=10, 230 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 231 | 232 | URANUS_RADIUS_MEAN = Constant( 233 | name='Mean radius of Uranus', 234 | value=25362, 235 | unit='km', 236 | uncertainty=7, 237 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 238 | 239 | URANUS_RADIUS_EQUATORIAL = Constant( 240 | name='Equatorial radius of Uranus', 241 | value=25559, 242 | unit='km', 243 | uncertainty=4, 244 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 245 | 246 | URANUS_RADIUS_POLAR = Constant( 247 | name='Polar radius of Uranus', 248 | value=24973, 249 | unit='km', 250 | uncertainty=20, 251 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 252 | 253 | NEPTUNE_RADIUS_MEAN = Constant( 254 | name='Mean radius of Neptune', 255 | value=24622, 256 | unit='km', 257 | uncertainty=19, 258 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 259 | 260 | NEPTUNE_RADIUS_EQUATORIAL = Constant( 261 | name='Equatorial radius of Neptune', 262 | value=24764, 263 | unit='km', 264 | uncertainty=15, 265 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 266 | 267 | NEPTUNE_RADIUS_POLAR = Constant( 268 | name='Polar radius of Neptune', 269 | value=24341, 270 | unit='km', 271 | uncertainty=30, 272 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 273 | 274 | PLUTO_RADIUS_MEAN = Constant( 275 | name='Mean radius of Pluto (134340)', 276 | value=1195, 277 | unit='km', 278 | uncertainty=5, 279 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 280 | 281 | PLUTO_RADIUS_EQUATORIAL = Constant( 282 | name='Equatorial radius of Pluto (134340)', 283 | value=1195, 284 | unit='km', 285 | uncertainty=5, 286 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 287 | 288 | PLUTO_RADIUS_POLAR = Constant( 289 | name='Polar radius of Pluto (134340)', 290 | value=1195, 291 | unit='km', 292 | uncertainty=5, 293 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 294 | 295 | MOON_RADIUS_MEAN = Constant( 296 | name='Mean radius of Moon', 297 | value=1737.4, 298 | unit='km', 299 | uncertainty=1, 300 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 301 | 302 | MOON_RADIUS_EQUATORIAL = Constant( 303 | name='Equatorial radius of Moon', 304 | value=1737.4, 305 | unit='km', 306 | uncertainty=1, 307 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 308 | 309 | MOON_RADIUS_POLAR = Constant( 310 | name='Polar radius of Moon', 311 | value=1737.4, 312 | unit='km', 313 | uncertainty=1, 314 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 315 | 316 | SUN_RADIUS_EQUATORIAL = Constant( 317 | name='Equatorial radius of Sun', 318 | value=696000, 319 | unit='km', 320 | uncertainty=1, 321 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 322 | 323 | SUN_MASS = SOLAR_MASS_PARAMETER / CONSTANT_OF_GRAVITATION 324 | EARTH_MASS = GEOCENTRIC_GRAVITATIONAL_CONSTANT / CONSTANT_OF_GRAVITATION 325 | MOON_MASS = MASS_RATIO_MOON_TO_EARTH * EARTH_MASS 326 | MERCURY_MASS = SUN_MASS / MASS_RATIO_SUN_TO_MERCURY 327 | VENUS_MASS = SUN_MASS / MASS_RATIO_SUN_TO_VENUS 328 | MARS_MASS = SUN_MASS / MASS_RATIO_SUN_TO_MARS 329 | JUPITER_MASS = SUN_MASS / MASS_RATIO_SUN_TO_JUPITER 330 | SATURN_MASS = SUN_MASS / MASS_RATIO_SUN_TO_SATURN 331 | URANUS_MASS = SUN_MASS / MASS_RATIO_SUN_TO_URANUS 332 | NEPTUNE_MASS = SUN_MASS / MASS_RATIO_SUN_TO_NEPTUNE 333 | PLUTO_MASS = SUN_MASS / MASS_RATIO_SUN_TO_PLUTO 334 | -------------------------------------------------------------------------------- /data/constants/wgs84.txt: -------------------------------------------------------------------------------- 1 | WGS84_EQUATORIAL_RADIUS = Constant( 2 | name='WGS84 semi-major axis', 3 | value=6378137, 4 | unit='m', 5 | uncertainty=0, 6 | reference='World Geodetic System 1984') 7 | 8 | WGS84_FLATTENING = Constant( 9 | name='WGS84 Earth flattening factor', 10 | value=1 / 298.257223563, 11 | unit='', 12 | uncertainty=0, 13 | reference='World Geodetic System 1984') 14 | 15 | WGS84_MU = Constant( 16 | name='WGS84 geocentric gravitational constant', 17 | value=3.986004418e14, 18 | unit='m3 / s2', 19 | uncertainty=0, 20 | reference='World Geodetic System 1984') 21 | 22 | WGS84_ANGULAR_VELOCITY = Constant( 23 | name='WGS84 nominal earth mean angular velocity', 24 | value=7.292115e-5, 25 | unit='rad / s', 26 | uncertainty=0, 27 | reference='World Geodetic System 1984') 28 | -------------------------------------------------------------------------------- /docs/_static/no_scrollbars.css: -------------------------------------------------------------------------------- 1 | /* override table width restrictions */ 2 | .wy-table-responsive table td, .wy-table-responsive table th { 3 | /* !important prevents the common CSS stylesheets from 4 | overriding this as on RTD they are loaded after this stylesheet */ 5 | white-space: normal !important; 6 | } 7 | 8 | .wy-table-responsive { 9 | overflow: visible !important; 10 | } 11 | -------------------------------------------------------------------------------- /docs/_static/style.css: -------------------------------------------------------------------------------- 1 | blockquote { 2 | font-style: italic; 3 | } 4 | 5 | .versionmodified { 6 | font-style: italic; 7 | } 8 | -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {# layout.html #} 2 | {# Import the theme's layout. #} 3 | {% extends "!layout.html" %} 4 | 5 | {% set css_files = ['_static/style.css'] + css_files %} -------------------------------------------------------------------------------- /docs/astrodynamics-docs.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # This file is dual licensed under the terms of the Apache License, Version 3 | # 2.0, and the BSD License. See the LICENSE file in the root of this repository 4 | # for complete details. 5 | # 6 | # This file is adapted from cryptography-docs.py by the The Cryptography 7 | # Developers. 8 | from __future__ import absolute_import, division, print_function 9 | 10 | from docutils import nodes 11 | from sphinx.util.compat import Directive, make_admonition 12 | 13 | IMPORT_MESSAGE = """The contents of this module are placed here for 14 | organisational reasons. They should be imported from :py:mod:`{module}`.""" 15 | 16 | 17 | class ImportFromDirective(Directive): 18 | has_content = True 19 | 20 | def run(self): 21 | message = IMPORT_MESSAGE.format(module=self.content[0]) 22 | 23 | ad = make_admonition( 24 | ImportFrom, 25 | self.name, 26 | [], 27 | self.options, 28 | nodes.paragraph("", message), 29 | self.lineno, 30 | self.content_offset, 31 | self.block_text, 32 | self.state, 33 | self.state_machine 34 | ) 35 | ad[0].line = self.lineno 36 | return ad 37 | 38 | 39 | class ImportFrom(nodes.Admonition, nodes.Element): 40 | pass 41 | 42 | 43 | def html_visit_importfrom_node(self, node): 44 | return self.visit_admonition(node, "note") 45 | 46 | 47 | def latex_visit_importfrom_node(self, node): 48 | return self.visit_admonition(node) 49 | 50 | 51 | def depart_importfrom_node(self, node): 52 | return self.depart_admonition(node) 53 | 54 | 55 | def setup(app): 56 | app.add_node( 57 | ImportFrom, 58 | html=(html_visit_importfrom_node, depart_importfrom_node), 59 | latex=(latex_visit_importfrom_node, depart_importfrom_node), 60 | ) 61 | app.add_directive('importfrom', ImportFromDirective) 62 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | `Unreleased `__ 5 | ------------------------------------------------------------------------------------------------------------------------------ 6 | 7 | N/A 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # astrodynamics documentation build configuration file. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | from __future__ import absolute_import, division, print_function 15 | 16 | from contextlib import suppress 17 | from numbers import Number 18 | import os 19 | import re 20 | import sys 21 | 22 | import sphinx_rtd_theme 23 | 24 | try: 25 | from sphinxcontrib import spelling 26 | except ImportError: 27 | spelling = None 28 | 29 | # If extensions (or modules to document with autodoc) are in another directory, 30 | # add these directories to sys.path here. If the directory is relative to the 31 | # documentation root, use os.path.abspath to make it absolute, like shown here. 32 | sys.path.insert(0, os.path.abspath('.')) 33 | 34 | # -- General configuration ------------------------------------------------ 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | needs_sphinx = '1.3' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.inheritance_diagram', 45 | 'sphinx.ext.intersphinx', 46 | 'sphinx.ext.mathjax', 47 | 'sphinx.ext.napoleon', 48 | 'sphinx.ext.viewcode', 49 | 'astrodynamics-docs', 50 | ] 51 | 52 | if spelling is not None: 53 | extensions.append('sphinxcontrib.spelling') 54 | 55 | # Add any paths that contain templates here, relative to this directory. 56 | templates_path = ['_templates'] 57 | 58 | nitpicky = True 59 | 60 | # The suffix of source filenames. 61 | source_suffix = '.rst' 62 | 63 | # The encoding of source files. 64 | # source_encoding = 'utf-8-sig' 65 | 66 | # The master toctree document. 67 | master_doc = 'index' 68 | 69 | # Parse metadata from __init__.py 70 | INIT_FILE = '../src/astrodynamics/__init__.py' 71 | init_data = open(INIT_FILE).read() 72 | 73 | metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_data)) 74 | 75 | RELEASE = metadata['version'] 76 | LICENSE = metadata['license'] 77 | DESCRIPTION = metadata['description'] 78 | 79 | AUTHOR = metadata['author'] 80 | 81 | ver_parts = RELEASE.split('.') 82 | for i, n in enumerate(ver_parts): 83 | with suppress(ValueError): 84 | ver_parts[i] = int(n) 85 | 86 | VERSION = '.'.join(str(n) for n in ver_parts if isinstance(n, Number)) 87 | 88 | # General information about the project. 89 | project = 'astrodynamics' 90 | copyright = '2015, {}'.format(AUTHOR) 91 | 92 | # The version info for the project you're documenting, acts as replacement for 93 | # |version| and |release|, also used in various other places throughout the 94 | # built documents. 95 | # 96 | # The short X.Y version. 97 | version = VERSION 98 | # The full version, including alpha/beta/rc tags. 99 | release = RELEASE 100 | 101 | # The language for content autogenerated by Sphinx. Refer to documentation 102 | # for a list of supported languages. 103 | # language = None 104 | 105 | # There are two options for replacing |today|: either, you set today to some 106 | # non-false value, then it is used: 107 | # today = '' 108 | # Else, today_fmt is used as the format for a strftime call. 109 | # today_fmt = '%B %d, %Y' 110 | 111 | # List of patterns, relative to source directory, that match files and 112 | # directories to ignore when looking for source files. 113 | exclude_patterns = ['_build'] 114 | 115 | # The reST default role (used for this markup: `text`) to use for all 116 | # documents. 117 | # default_role = None 118 | 119 | # If true, '()' will be appended to :func: etc. cross-reference text. 120 | # add_function_parentheses = True 121 | 122 | # If true, the current module name will be prepended to all description 123 | # unit titles (such as .. function::). 124 | # add_module_names = True 125 | 126 | # If true, sectionauthor and moduleauthor directives will be shown in the 127 | # output. They are ignored by default. 128 | # show_authors = False 129 | 130 | # The name of the Pygments (syntax highlighting) style to use. 131 | pygments_style = 'sphinx' 132 | 133 | # A list of ignored prefixes for module index sorting. 134 | # modindex_common_prefix = [] 135 | 136 | # If true, keep warnings as "system message" paragraphs in the built documents. 137 | # keep_warnings = False 138 | 139 | 140 | # -- Options for HTML output ---------------------------------------------- 141 | 142 | # The theme to use for HTML and HTML Help pages. See the documentation for 143 | # a list of builtin themes. 144 | html_theme = 'sphinx_rtd_theme' 145 | 146 | # Theme options are theme-specific and customize the look and feel of a theme 147 | # further. For a list of options available for each theme, see the 148 | # documentation. 149 | html_theme_options = {} 150 | 151 | # Add any paths that contain custom themes here, relative to this directory. 152 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 153 | 154 | # The name for this set of Sphinx documents. If None, it defaults to 155 | # " v documentation". 156 | # html_title = None 157 | 158 | # A shorter title for the navigation bar. Default is the same as html_title. 159 | # html_short_title = None 160 | 161 | # The name of an image file (relative to this directory) to place at the top 162 | # of the sidebar. 163 | # html_logo = None 164 | 165 | # The name of an image file (within the static path) to use as favicon of the 166 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 167 | # pixels large. 168 | # html_favicon = None 169 | 170 | # Add any paths that contain custom static files (such as style sheets) here, 171 | # relative to this directory. They are copied after the builtin static files, 172 | # so a file named "default.css" will overwrite the builtin "default.css". 173 | html_static_path = ['_static'] 174 | 175 | # Add any extra paths that contain custom files (such as robots.txt or 176 | # .htaccess) here, relative to this directory. These files are copied 177 | # directly to the root of the documentation. 178 | # html_extra_path = [] 179 | 180 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 181 | # using the given strftime format. 182 | # html_last_updated_fmt = '%b %d, %Y' 183 | 184 | # If true, SmartyPants will be used to convert quotes and dashes to 185 | # typographically correct entities. 186 | # html_use_smartypants = True 187 | 188 | # Custom sidebar templates, maps document names to template names. 189 | html_sidebars = {} 190 | 191 | # Additional templates that should be rendered to pages, maps page names to 192 | # template names. 193 | # html_additional_pages = {} 194 | 195 | # If false, no module index is generated. 196 | # html_domain_indices = True 197 | 198 | # If false, no index is generated. 199 | # html_use_index = True 200 | 201 | # If true, the index is split into individual pages for each letter. 202 | # html_split_index = False 203 | 204 | # If true, links to the reST sources are added to the pages. 205 | # html_show_sourcelink = True 206 | 207 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 208 | # html_show_sphinx = True 209 | 210 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 211 | # html_show_copyright = True 212 | 213 | # If true, an OpenSearch description file will be output, and all pages will 214 | # contain a tag referring to it. The value of this option must be the 215 | # base URL from which the finished HTML is served. 216 | # html_use_opensearch = '' 217 | 218 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 219 | # html_file_suffix = None 220 | 221 | # Output file base name for HTML help builder. 222 | htmlhelp_basename = 'astrodynamicsdoc' 223 | 224 | 225 | # -- Options for LaTeX output --------------------------------------------- 226 | 227 | latex_elements = { 228 | # The paper size ('letterpaper' or 'a4paper'). 229 | 'papersize': 'a4paper', 230 | 231 | # The font size ('10pt', '11pt' or '12pt'). 232 | # 'pointsize': '10pt', 233 | 234 | # Additional stuff for the LaTeX preamble. 235 | # 'preamble': '', 236 | } 237 | 238 | # Grouping the document tree into LaTeX files. List of tuples 239 | # (source start file, target name, title, 240 | # author, documentclass [howto, manual, or own class]). 241 | latex_documents = [ 242 | ('index', 'astrodynamics.tex', 'astrodynamics Documentation', AUTHOR, 'manual'), 243 | ] 244 | 245 | # The name of an image file (relative to this directory) to place at the top of 246 | # the title page. 247 | # latex_logo = None 248 | 249 | # For "manual" documents, if this is true, then toplevel headings are parts, 250 | # not chapters. 251 | # latex_use_parts = False 252 | 253 | # If true, show page references after internal links. 254 | # latex_show_pagerefs = False 255 | 256 | # If true, show URL addresses after external links. 257 | # latex_show_urls = False 258 | 259 | # Documents to append as an appendix to all manuals. 260 | # latex_appendices = [] 261 | 262 | # If false, no module index is generated. 263 | # latex_domain_indices = True 264 | 265 | 266 | # -- Options for manual page output --------------------------------------- 267 | 268 | # One entry per manual page. List of tuples 269 | # (source start file, name, description, authors, manual section). 270 | man_pages = [ 271 | ('index', 'astrodynamics', 'astrodynamics Documentation', 272 | [AUTHOR], 1) 273 | ] 274 | 275 | # If true, show URL addresses after external links. 276 | # man_show_urls = False 277 | 278 | 279 | # -- Options for Texinfo output ------------------------------------------- 280 | 281 | # Grouping the document tree into Texinfo files. List of tuples 282 | # (source start file, target name, title, author, 283 | # dir menu entry, description, category) 284 | texinfo_documents = [ 285 | ('index', 'astrodynamics', 'astrodynamics Documentation', AUTHOR, 'astrodynamics', 286 | DESCRIPTION, 'Miscellaneous'), 287 | ] 288 | 289 | # Documents to append as an appendix to all manuals. 290 | # texinfo_appendices = [] 291 | 292 | # If false, no module index is generated. 293 | # texinfo_domain_indices = True 294 | 295 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 296 | # texinfo_show_urls = 'footnote' 297 | 298 | # If true, do not generate a @detailmenu in the "Top" node's menu. 299 | # texinfo_no_detailmenu = False 300 | 301 | # intersphinx 302 | intersphinx_mapping = { 303 | 'python': ('http://docs.python.org/3', None), 304 | 'ipython': ('http://ipython.org/ipython-doc/stable', None), 305 | 'astropy': ('http://docs.astropy.org/en/stable/', None), 306 | 'represent': ('http://represent.readthedocs.io/en/latest/', None), 307 | } 308 | 309 | autodoc_member_order = 'bysource' 310 | 311 | napoleon_numpy_docstring = False 312 | 313 | inheritance_graph_attrs = dict(bgcolor='transparent') 314 | 315 | 316 | # A workaround for the responsive tables always having annoying scrollbars. 317 | # Found this solution here: https://github.com/snide/sphinx_rtd_theme/issues/117 318 | def setup(app): 319 | app.add_stylesheet("no_scrollbars.css") 320 | -------------------------------------------------------------------------------- /docs/development/getting-started.rst: -------------------------------------------------------------------------------- 1 | *************** 2 | Getting started 3 | *************** 4 | 5 | Working on ``astrodynamics`` requires the installation of some 6 | development dependencies. These can be installed in a `virtualenv`_ 7 | using `pip`_ with the ``dev`` extras. You should install 8 | ``astrodynamics`` in ``editable`` mode. 9 | 10 | For example: 11 | 12 | .. code-block:: console 13 | 14 | $ # Create a virtualenv and activate it 15 | $ pip install --editable .[dev] 16 | 17 | You are now ready to run the tests and build the documentation. 18 | 19 | Running tests 20 | ============= 21 | 22 | ``astrodynamics`` unit tests are found in the ``astrodynamics/tests/`` 23 | directory and are designed to be run using `pytest`_. `pytest`_ will discover 24 | the tests automatically, so all you have to do is: 25 | 26 | .. code-block:: console 27 | 28 | $ py.test 29 | ... 30 | 2 passed in 0.78 seconds 31 | 32 | This runs the tests with the default Python interpreter. 33 | 34 | You can also verify that the tests pass on other supported Python interpreters. 35 | For this we use `tox`_, which will automatically create a `virtualenv`_ for 36 | each supported Python version and run the tests. For example: 37 | 38 | .. code-block:: console 39 | 40 | $ tox 41 | ... 42 | py27: commands succeeded 43 | ERROR: py32: InterpreterNotFound: python3.3 44 | ERROR: py33: InterpreterNotFound: python3.4 45 | py35: commands succeeded 46 | 47 | You may not have all the required Python versions installed, in which case you 48 | will see one or more ``InterpreterNotFound`` errors. 49 | 50 | Building documentation 51 | ====================== 52 | 53 | ``astrodynamics`` documentation is stored in the ``docs/`` directory. It is 54 | written in `reStructured Text`_ and rendered using `Sphinx`_. 55 | 56 | Use `shovel`_ to build the documentation. For example: 57 | 58 | .. code-block:: console 59 | 60 | $ shovel docs.gen 61 | ... 62 | 63 | The HTML documentation index can now be found at 64 | ``docs/_build/html/index.html``. 65 | 66 | The documentation can be re-built as-you-edit like so: 67 | 68 | .. code-block:: console 69 | 70 | $ shovel docs.watch 71 | ... 72 | 73 | Adding/modifying constants 74 | ========================== 75 | 76 | The constants package is created from source files stored in 77 | ``data/constants``. Each text file becomes a module in 78 | ``astrodynamics/constants``. Users import all constants directly from 79 | :mod:`astrodynamics.constants`, but the modules are used for organisation. 80 | 81 | After editing the data files, the constants can be updated with the following 82 | commands: 83 | 84 | .. code-block:: console 85 | 86 | $ shovel constants.make_module 87 | $ shovel constants.make_documentation 88 | ... 89 | # Or, to do both: 90 | $ shovel constants.make 91 | ... 92 | 93 | .. _`pip`: https://pypi.python.org/pypi/pip 94 | .. _`pytest`: https://pypi.python.org/pypi/pytest 95 | .. _`reStructured Text`: http://sphinx-doc.org/rest.html 96 | .. _`shovel`: https://github.com/seomoz/shovel#shovel 97 | .. _`sphinx`: https://pypi.python.org/pypi/Sphinx 98 | .. _`tox`: https://pypi.python.org/pypi/tox 99 | .. _`virtualenv`: https://pypi.python.org/pypi/virtualenv 100 | 101 | Import order 102 | ============ 103 | 104 | A consistent import order is used in ``astrodynamics``. The order is as 105 | follows: 106 | 107 | - ``from __future__ import ...`` 108 | - Standard library 109 | - Third party modules 110 | - Current project [#]_ 111 | - Local imports (``from . import ...``, ``from .module import ...``) 112 | 113 | This order, and the formatting of the imports, can be enforced by running the 114 | following commands: 115 | 116 | .. code-block:: console 117 | 118 | $ shovel code.format_imports 119 | ... 120 | 121 | .. [#] Although this order is enforced, within ``astrodynamics/``, use relative 122 | imports rather than absolute imports: 123 | 124 | .. code-block:: python 125 | 126 | # Bad 127 | from astrodynamics.bodies import ReferenceEllipsoid 128 | 129 | # Good 130 | from ..bodies import ReferenceEllipsoid 131 | -------------------------------------------------------------------------------- /docs/development/index.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | .. note:: 5 | 6 | This section of the documentation is based off of PyCA's ``cryptography`` 7 | documentation. It retains the original `license`_. 8 | 9 | ``astrodynamics`` recommends the following guidelines for contributing to the 10 | project. 11 | 12 | Bugs and feature requests can be filed as issues on `GitHub`_. For general 13 | information, please read `contribution-guide.org`_. 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | getting-started 19 | submitting-patches 20 | reviewing-patches 21 | 22 | .. _`GitHub`: https://github.com/python-astrodynamics/astrodynamics 23 | .. _`contribution-guide.org`: http://www.contribution-guide.org/ 24 | .. _`license`: https://raw.githubusercontent.com/python-astrodynamics/astrodynamics/master/licenses/CRYPTOGRAPHY_LICENSE.txt 25 | -------------------------------------------------------------------------------- /docs/development/reviewing-patches.rst: -------------------------------------------------------------------------------- 1 | ***************************** 2 | Reviewing and merging patches 3 | ***************************** 4 | 5 | Everyone is encouraged to review open pull requests. We only ask that you try 6 | and think carefully, ask questions and are `excellent to one another`_. Code 7 | review is our opportunity to share knowledge, design ideas and make friends. 8 | 9 | When reviewing a patch try to keep each of these concepts in mind: 10 | 11 | Architecture 12 | ============ 13 | 14 | * Is the proposed change being made in the correct place? Is it a fix in a 15 | backend when it should be in the primitives? 16 | 17 | Intent 18 | ====== 19 | 20 | * What is the change being proposed? 21 | * Do we want this feature or is the bug they're fixing really a bug? 22 | 23 | Implementation 24 | ============== 25 | 26 | * Does the change do what the author claims? 27 | * Are there sufficient tests? 28 | * Has it been documented? 29 | * Will this change introduce new bugs? 30 | 31 | Grammar and style 32 | ================= 33 | 34 | These are small things that are not caught by the automated style checkers. 35 | 36 | * Does a variable need a better name? 37 | * Should this be a keyword argument? 38 | 39 | .. _`excellent to one another`: https://speakerdeck.com/ohrite/better-code-review 40 | -------------------------------------------------------------------------------- /docs/development/submitting-patches.rst: -------------------------------------------------------------------------------- 1 | ****************** 2 | Submitting patches 3 | ****************** 4 | 5 | * Always make a new branch for your work. 6 | * Patches should be small to facilitate easier review. `Studies have shown`_ 7 | that review quality falls off as patch size grows. Sometimes this will result 8 | in many small PRs to land a single large feature. 9 | * Ideally, larger changes should be discussed in a GitHub issue before 10 | submission. 11 | * New features and significant bug fixes should be documented in the 12 | :doc:`/changelog`. 13 | * You must have legal permission to distribute any code you contribute to 14 | ``astrodynamics``, and it must be available under the MIT License. 15 | 16 | Code 17 | ==== 18 | 19 | When in doubt, refer to :pep:`8` for Python code. You can check if your code 20 | meets our automated requirements by running ``flake8`` against it. If you've 21 | installed the development requirements this will automatically use our 22 | configuration. 23 | 24 | `Write comments as complete sentences.`_ 25 | 26 | Class names which contain acronyms or initialisms should always be 27 | capitalized. A class should be named ``HTTPClient``, not ``HttpClient``. 28 | 29 | Every Python code file must contain the following lines, both of which 30 | are enforced by ``flake8``: 31 | 32 | .. code-block:: python 33 | 34 | # coding: utf-8 35 | from __future__ import absolute_import, division, print_function 36 | 37 | Tests 38 | ===== 39 | 40 | All code changes must be accompanied by unit tests. 41 | 42 | Documentation 43 | ============= 44 | 45 | Docstrings should be written for `Sphinx`_. `Readability counts`_, so 46 | Google-style docstrings as parsed by the Sphinx `Napoleon`_ extension are 47 | encouraged. 48 | 49 | For example:: 50 | 51 | class DirEntry(object): 52 | """The is the template class for the cache objects. 53 | 54 | Args: 55 | path (str): The path of the file to wrap 56 | field_storage (FileStorage): The :class:`FileStorage` instance to wrap 57 | temporary (bool): Whether or not to delete the file when the File 58 | instance is destructed 59 | 60 | Returns: 61 | BufferedFileStorage: A buffered writable file descriptor 62 | """ 63 | 64 | So, specifically: 65 | 66 | * Always use three double quotes. 67 | * Unless the entire docstring fits on a line, place the closing quotes on a 68 | line by themselves. 69 | * No blank line at the end. 70 | * Use Google-style docstrings for the Sphinx `Napoleon`_ extension. 71 | 72 | 73 | .. _`Write comments as complete sentences.`: http://nedbatchelder.com/blog/201401/comments_should_be_sentences.html 74 | .. _`Studies have shown`: https://smartbear.com/smartbear/media/pdfs/wp-cc-11-best-practices-of-peer-code-review.pdf 75 | .. _`sphinx`: https://pypi.python.org/pypi/Sphinx 76 | .. _`readability counts`: https://www.python.org/dev/peps/pep-0020/ 77 | .. _`napoleon`: http://sphinxcontrib-napoleon.readthedocs.org/en/latest/index.html 78 | -------------------------------------------------------------------------------- /docs/glossary.rst: -------------------------------------------------------------------------------- 1 | ******** 2 | Glossary 3 | ******** 4 | 5 | .. glossary:: 6 | :sorted: 7 | 8 | NAIF ID 9 | :term:`NAIF` ID codes map ephemeris objects, reference frames, 10 | and instruments to integers for the :term:`SPICE toolkit`. 11 | 12 | .. seealso:: 13 | 14 | `NAIF Integer ID codes `_ 15 | The NAIF IDS required reading lists all default ID-name mappings 16 | for the SPICE toolkits and a description of functionality of the 17 | corresponding software. 18 | 19 | NAIF 20 | Navigation and Ancillary Information Facility, :term:`NASA` 21 | 22 | NASA 23 | National Aeronautics and Space Administration 24 | 25 | 26 | SPICE 27 | SPICE toolkit 28 | An Observation Geometry System for Planetary Science Missions 29 | 30 | .. seealso:: 31 | 32 | `The SPICE toolkit `_ 33 | NASA's Navigation and Ancillary Information Facility (NAIF) offers 34 | NASA flight projects and NASA funded researchers an observation 35 | geometry information system named "SPICE" to assist scientists in 36 | planning and interpreting scientific observations from space-based 37 | instruments aboard robotic planetary spacecraft. SPICE is also 38 | widely used in engineering tasks associated with these missions. 39 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. astrodynamics documentation master file 2 | 3 | *************************** 4 | astrodynamics documentation 5 | *************************** 6 | 7 | Contents: 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | 12 | module 13 | development/index 14 | changelog 15 | glossary 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /docs/module.rst: -------------------------------------------------------------------------------- 1 | **************** 2 | Module Reference 3 | **************** 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | modules/bodies/index 9 | modules/constants 10 | modules/utils/index 11 | -------------------------------------------------------------------------------- /docs/modules/bodies/celestialbody.rst: -------------------------------------------------------------------------------- 1 | **************** 2 | Celestial Bodies 3 | **************** 4 | 5 | .. currentmodule:: astrodynamics.bodies.celestialbody 6 | .. importfrom:: astrodynamics.bodies 7 | 8 | .. automodule:: astrodynamics.bodies.celestialbody 9 | :members: 10 | :show-inheritance: 11 | 12 | Predefined Celestial Bodies 13 | =========================== 14 | 15 | .. data:: mercury 16 | 17 | .. data:: venus 18 | 19 | .. data:: earth 20 | 21 | A :py:class:`CelestialBody` initialised from the :py:data:`~astrodynamics.bodies.ellipsoid.wgs84` reference ellipsoid. 22 | 23 | .. data:: mars 24 | 25 | .. data:: jupiter 26 | 27 | .. data:: saturn 28 | 29 | .. data:: uranus 30 | 31 | .. data:: neptune 32 | 33 | .. data:: pluto 34 | -------------------------------------------------------------------------------- /docs/modules/bodies/ellipsoid.rst: -------------------------------------------------------------------------------- 1 | ********** 2 | Ellipsoids 3 | ********** 4 | 5 | .. currentmodule:: astrodynamics.bodies.ellipsoid 6 | .. importfrom:: astrodynamics.bodies 7 | 8 | .. automodule:: astrodynamics.bodies.ellipsoid 9 | :members: 10 | :show-inheritance: 11 | 12 | Predefined Reference Ellipsoids 13 | =============================== 14 | 15 | .. data:: wgs84 16 | 17 | A :py:class:`ReferenceEllipsoid` instantiated with the ``WGS84_*`` constants 18 | from :py:mod:`astrodynamics.constants`. 19 | 20 | Inheritance diagram 21 | =================== 22 | 23 | .. inheritance-diagram:: astrodynamics.bodies.ellipsoid 24 | -------------------------------------------------------------------------------- /docs/modules/bodies/index.rst: -------------------------------------------------------------------------------- 1 | ****** 2 | Bodies 3 | ****** 4 | 5 | .. py:module:: astrodynamics.bodies 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | celestialbody 11 | ellipsoid 12 | -------------------------------------------------------------------------------- /docs/modules/constants.rst: -------------------------------------------------------------------------------- 1 | ********* 2 | Constants 3 | ********* 4 | 5 | .. py:module:: astrodynamics.constants 6 | 7 | .. currentmodule:: astrodynamics.constants 8 | 9 | .. autoclass:: Constant 10 | :members: name, uncertainty, reference 11 | 12 | List of constants 13 | ================= 14 | 15 | ================================= =================================================================== ============= 16 | Constant Value Uncertainty 17 | ================================= =================================================================== ============= 18 | CONSTANT_OF_GRAVITATION :math:`6.67428 \times 10^{-11} \; \mathrm{\frac{m^{3}}{kg\,s^{2}}}` 6.7e-15 19 | SOLAR_MASS_PARAMETER :math:`1.3271244 \times 10^{20} \; \mathrm{\frac{m^{3}}{s^{2}}}` 10000000000.0 20 | EARTH_RADIUS_EQUATORIAL :math:`6378136.6 \; \mathrm{m}` 0.1 21 | J2 :math:`0.0010826359 \; \mathrm{}` 1e-10 22 | GEOCENTRIC_GRAVITATIONAL_CONSTANT :math:`3.9860044 \times 10^{14} \; \mathrm{\frac{m^{3}}{s^{2}}}` 800000.0 23 | GEOID_POTENTIAL :math:`62636856 \; \mathrm{\frac{m^{2}}{s^{2}}}` 0.5 24 | EARTH_ANGULAR_VELOCITY :math:`7.292115 \times 10^{-5} \; \mathrm{\frac{rad}{s}}` 0 25 | MASS_RATIO_MOON_TO_EARTH :math:`0.012300037 \; \mathrm{}` 4e-10 26 | MASS_RATIO_SUN_TO_MERCURY :math:`6023600 \; \mathrm{}` 300.0 27 | MASS_RATIO_SUN_TO_VENUS :math:`408523.72 \; \mathrm{}` 0.008 28 | MASS_RATIO_SUN_TO_MARS :math:`3098703.6 \; \mathrm{}` 0.02 29 | MASS_RATIO_SUN_TO_JUPITER :math:`1047.3486 \; \mathrm{}` 1.7e-05 30 | MASS_RATIO_SUN_TO_SATURN :math:`3497.9018 \; \mathrm{}` 0.0001 31 | MASS_RATIO_SUN_TO_URANUS :math:`22902.98 \; \mathrm{}` 0.03 32 | MASS_RATIO_SUN_TO_NEPTUNE :math:`19412.26 \; \mathrm{}` 0.03 33 | MASS_RATIO_SUN_TO_PLUTO :math:`1.36566 \times 10^{8} \; \mathrm{}` 28000.0 34 | MERCURY_RADIUS_MEAN :math:`2439.9 \; \mathrm{km}` 1 35 | MERCURY_RADIUS_EQUATORIAL :math:`2439.9 \; \mathrm{km}` 1 36 | MERCURY_RADIUS_POLAR :math:`2439.9 \; \mathrm{km}` 1 37 | VENUS_RADIUS_MEAN :math:`6051.8 \; \mathrm{km}` 1 38 | VENUS_RADIUS_EQUATORIAL :math:`6051.8 \; \mathrm{km}` 1 39 | VENUS_RADIUS_POLAR :math:`6051.8 \; \mathrm{km}` 1 40 | EARTH_RADIUS_MEAN :math:`6371.0084 \; \mathrm{km}` 0.0001 41 | EARTH_RADIUS_POLAR :math:`6356.7519 \; \mathrm{km}` 0.0001 42 | MARS_RADIUS_MEAN :math:`3389.5 \; \mathrm{km}` 0.2 43 | MARS_RADIUS_EQUATORIAL :math:`3396.19 \; \mathrm{km}` 0.1 44 | MARS_RADIUS_POLAR :math:`3376.2 \; \mathrm{km}` 0.1 45 | JUPITER_RADIUS_MEAN :math:`69911 \; \mathrm{km}` 6 46 | JUPITER_RADIUS_EQUATORIAL :math:`71492 \; \mathrm{km}` 4 47 | JUPITER_RADIUS_POLAR :math:`66854 \; \mathrm{km}` 10 48 | SATURN_RADIUS_MEAN :math:`58232 \; \mathrm{km}` 6 49 | SATURN_RADIUS_EQUATORIAL :math:`60268 \; \mathrm{km}` 4 50 | SATURN_RADIUS_POLAR :math:`54364 \; \mathrm{km}` 10 51 | URANUS_RADIUS_MEAN :math:`25362 \; \mathrm{km}` 7 52 | URANUS_RADIUS_EQUATORIAL :math:`25559 \; \mathrm{km}` 4 53 | URANUS_RADIUS_POLAR :math:`24973 \; \mathrm{km}` 20 54 | NEPTUNE_RADIUS_MEAN :math:`24622 \; \mathrm{km}` 19 55 | NEPTUNE_RADIUS_EQUATORIAL :math:`24764 \; \mathrm{km}` 15 56 | NEPTUNE_RADIUS_POLAR :math:`24341 \; \mathrm{km}` 30 57 | PLUTO_RADIUS_MEAN :math:`1195 \; \mathrm{km}` 5 58 | PLUTO_RADIUS_EQUATORIAL :math:`1195 \; \mathrm{km}` 5 59 | PLUTO_RADIUS_POLAR :math:`1195 \; \mathrm{km}` 5 60 | MOON_RADIUS_MEAN :math:`1737.4 \; \mathrm{km}` 1 61 | MOON_RADIUS_EQUATORIAL :math:`1737.4 \; \mathrm{km}` 1 62 | MOON_RADIUS_POLAR :math:`1737.4 \; \mathrm{km}` 1 63 | SUN_RADIUS_EQUATORIAL :math:`696000 \; \mathrm{km}` 1 64 | SUN_MASS :math:`1.9884159 \times 10^{30} \; \mathrm{kg}` N/A 65 | EARTH_MASS :math:`5.9721864 \times 10^{24} \; \mathrm{kg}` N/A 66 | MOON_MASS :math:`7.3458114 \times 10^{22} \; \mathrm{kg}` N/A 67 | MERCURY_MASS :math:`3.3010423 \times 10^{23} \; \mathrm{kg}` N/A 68 | VENUS_MASS :math:`4.8673205 \times 10^{24} \; \mathrm{kg}` N/A 69 | MARS_MASS :math:`6.4169283 \times 10^{23} \; \mathrm{kg}` N/A 70 | JUPITER_MASS :math:`1.8985234 \times 10^{27} \; \mathrm{kg}` N/A 71 | SATURN_MASS :math:`5.684596 \times 10^{26} \; \mathrm{kg}` N/A 72 | URANUS_MASS :math:`8.6819089 \times 10^{25} \; \mathrm{kg}` N/A 73 | NEPTUNE_MASS :math:`1.0243093 \times 10^{26} \; \mathrm{kg}` N/A 74 | PLUTO_MASS :math:`1.4560109 \times 10^{22} \; \mathrm{kg}` N/A 75 | WGS84_EQUATORIAL_RADIUS :math:`6378137 \; \mathrm{m}` 0 76 | WGS84_FLATTENING :math:`0.0033528107 \; \mathrm{}` 0 77 | WGS84_MU :math:`3.9860044 \times 10^{14} \; \mathrm{\frac{m^{3}}{s^{2}}}` 0 78 | WGS84_ANGULAR_VELOCITY :math:`7.292115 \times 10^{-5} \; \mathrm{\frac{rad}{s}}` 0 79 | ================================= =================================================================== ============= 80 | 81 | References 82 | ========== 83 | 84 | ================================= =============================================== =============================================================== 85 | Constant Full name Reference 86 | ================================= =============================================== =============================================================== 87 | CONSTANT_OF_GRAVITATION Constant of gravitation IAU 2009/2012 System of Astronomical Constants 88 | SOLAR_MASS_PARAMETER Solar mass parameter (TCB) IAU 2009/2012 System of Astronomical Constants 89 | EARTH_RADIUS_EQUATORIAL Equatorial radius of Earth (TT) IAU 2009/2012 System of Astronomical Constants 90 | J2 Dynamical form-factor for the Earth IAU 2009/2012 System of Astronomical Constants 91 | GEOCENTRIC_GRAVITATIONAL_CONSTANT Geocentric gravitational constant (TCB) IAU 2009/2012 System of Astronomical Constants 92 | GEOID_POTENTIAL Potential of the geoid IAU 2009/2012 System of Astronomical Constants 93 | EARTH_ANGULAR_VELOCITY Nominal mean angular velocity of the Earth (TT) IAU 2009/2012 System of Astronomical Constants 94 | MASS_RATIO_MOON_TO_EARTH Mass ratio: Moon to Earth IAU 2009/2012 System of Astronomical Constants 95 | MASS_RATIO_SUN_TO_MERCURY Mass ratio: Sun to Mercury IAU 2009/2012 System of Astronomical Constants 96 | MASS_RATIO_SUN_TO_VENUS Mass ratio: Sun to Venus IAU 2009/2012 System of Astronomical Constants 97 | MASS_RATIO_SUN_TO_MARS Mass ratio: Sun to Mars IAU 2009/2012 System of Astronomical Constants 98 | MASS_RATIO_SUN_TO_JUPITER Mass ratio: Sun to Jupiter IAU 2009/2012 System of Astronomical Constants 99 | MASS_RATIO_SUN_TO_SATURN Mass ratio: Sun to Saturn IAU 2009/2012 System of Astronomical Constants 100 | MASS_RATIO_SUN_TO_URANUS Mass ratio: Sun to Uranus IAU 2009/2012 System of Astronomical Constants 101 | MASS_RATIO_SUN_TO_NEPTUNE Mass ratio: Sun to Neptune IAU 2009/2012 System of Astronomical Constants 102 | MASS_RATIO_SUN_TO_PLUTO Mass ratio: Sun to Pluto (134340) IAU 2009/2012 System of Astronomical Constants 103 | MERCURY_RADIUS_MEAN Mean radius of Mercury IAU WG on Cartographic Coordinates and Rotational Elements 2009 104 | MERCURY_RADIUS_EQUATORIAL Equatorial radius of Mercury IAU WG on Cartographic Coordinates and Rotational Elements 2009 105 | MERCURY_RADIUS_POLAR Polar radius of Mercury IAU WG on Cartographic Coordinates and Rotational Elements 2009 106 | VENUS_RADIUS_MEAN Mean radius of Venus IAU WG on Cartographic Coordinates and Rotational Elements 2009 107 | VENUS_RADIUS_EQUATORIAL Equatorial radius of Venus IAU WG on Cartographic Coordinates and Rotational Elements 2009 108 | VENUS_RADIUS_POLAR Polar radius of Venus IAU WG on Cartographic Coordinates and Rotational Elements 2009 109 | EARTH_RADIUS_MEAN Mean radius of Earth IAU WG on Cartographic Coordinates and Rotational Elements 2009 110 | EARTH_RADIUS_POLAR Polar radius of Earth IAU WG on Cartographic Coordinates and Rotational Elements 2009 111 | MARS_RADIUS_MEAN Mean radius of Mars IAU WG on Cartographic Coordinates and Rotational Elements 2009 112 | MARS_RADIUS_EQUATORIAL Equatorial radius of Mars IAU WG on Cartographic Coordinates and Rotational Elements 2009 113 | MARS_RADIUS_POLAR Polar radius of Mars IAU WG on Cartographic Coordinates and Rotational Elements 2009 114 | JUPITER_RADIUS_MEAN Mean radius of Jupiter IAU WG on Cartographic Coordinates and Rotational Elements 2009 115 | JUPITER_RADIUS_EQUATORIAL Equatorial radius of Jupiter IAU WG on Cartographic Coordinates and Rotational Elements 2009 116 | JUPITER_RADIUS_POLAR Polar radius of Jupiter IAU WG on Cartographic Coordinates and Rotational Elements 2009 117 | SATURN_RADIUS_MEAN Mean radius of Saturn IAU WG on Cartographic Coordinates and Rotational Elements 2009 118 | SATURN_RADIUS_EQUATORIAL Equatorial radius of Saturn IAU WG on Cartographic Coordinates and Rotational Elements 2009 119 | SATURN_RADIUS_POLAR Polar radius of Saturn IAU WG on Cartographic Coordinates and Rotational Elements 2009 120 | URANUS_RADIUS_MEAN Mean radius of Uranus IAU WG on Cartographic Coordinates and Rotational Elements 2009 121 | URANUS_RADIUS_EQUATORIAL Equatorial radius of Uranus IAU WG on Cartographic Coordinates and Rotational Elements 2009 122 | URANUS_RADIUS_POLAR Polar radius of Uranus IAU WG on Cartographic Coordinates and Rotational Elements 2009 123 | NEPTUNE_RADIUS_MEAN Mean radius of Neptune IAU WG on Cartographic Coordinates and Rotational Elements 2009 124 | NEPTUNE_RADIUS_EQUATORIAL Equatorial radius of Neptune IAU WG on Cartographic Coordinates and Rotational Elements 2009 125 | NEPTUNE_RADIUS_POLAR Polar radius of Neptune IAU WG on Cartographic Coordinates and Rotational Elements 2009 126 | PLUTO_RADIUS_MEAN Mean radius of Pluto (134340) IAU WG on Cartographic Coordinates and Rotational Elements 2009 127 | PLUTO_RADIUS_EQUATORIAL Equatorial radius of Pluto (134340) IAU WG on Cartographic Coordinates and Rotational Elements 2009 128 | PLUTO_RADIUS_POLAR Polar radius of Pluto (134340) IAU WG on Cartographic Coordinates and Rotational Elements 2009 129 | MOON_RADIUS_MEAN Mean radius of Moon IAU WG on Cartographic Coordinates and Rotational Elements 2009 130 | MOON_RADIUS_EQUATORIAL Equatorial radius of Moon IAU WG on Cartographic Coordinates and Rotational Elements 2009 131 | MOON_RADIUS_POLAR Polar radius of Moon IAU WG on Cartographic Coordinates and Rotational Elements 2009 132 | SUN_RADIUS_EQUATORIAL Equatorial radius of Sun IAU WG on Cartographic Coordinates and Rotational Elements 2009 133 | WGS84_EQUATORIAL_RADIUS WGS84 semi-major axis World Geodetic System 1984 134 | WGS84_FLATTENING WGS84 Earth flattening factor World Geodetic System 1984 135 | WGS84_MU WGS84 geocentric gravitational constant World Geodetic System 1984 136 | WGS84_ANGULAR_VELOCITY WGS84 nominal earth mean angular velocity World Geodetic System 1984 137 | ================================= =============================================== =============================================================== 138 | 139 | .. _`license`: https://raw.githubusercontent.com/python-astrodynamics/astrodynamics/master/licenses/ASTROPY_LICENSE.txt 140 | -------------------------------------------------------------------------------- /docs/modules/utils/compat.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Compatibility 3 | ************* 4 | 5 | .. currentmodule:: astrodynamics.utils.compat 6 | .. importfrom:: astrodynamics.utils 7 | 8 | .. automodule:: astrodynamics.utils.compat 9 | :members: 10 | :show-inheritance: 11 | 12 | .. data:: PY2 13 | 14 | ``True`` on Python 2 15 | 16 | .. data:: PY3 17 | 18 | ``True`` on Python 3 19 | 20 | .. data:: PY33 21 | 22 | ``True`` on Python 3.3 and above. 23 | 24 | .. data:: WINDOWS 25 | 26 | ``True`` on Windows. 27 | -------------------------------------------------------------------------------- /docs/modules/utils/helper.rst: -------------------------------------------------------------------------------- 1 | ******* 2 | Helpers 3 | ******* 4 | 5 | .. currentmodule:: astrodynamics.utils.helper 6 | .. importfrom:: astrodynamics.utils 7 | 8 | .. automodule:: astrodynamics.utils.helper 9 | :members: 10 | :show-inheritance: 11 | -------------------------------------------------------------------------------- /docs/modules/utils/index.rst: -------------------------------------------------------------------------------- 1 | ********* 2 | Utilities 3 | ********* 4 | 5 | .. py:module:: astrodynamics.utils 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | compat 11 | helper 12 | web 13 | -------------------------------------------------------------------------------- /docs/modules/utils/web.rst: -------------------------------------------------------------------------------- 1 | *** 2 | Web 3 | *** 4 | 5 | .. currentmodule:: astrodynamics.utils.web 6 | .. importfrom:: astrodynamics.utils 7 | 8 | .. automodule:: astrodynamics.utils.web 9 | :members: 10 | :show-inheritance: 11 | 12 | .. data:: SPK_DIR 13 | 14 | `Platform-specific directory`_ based on ``user_data_dir`` from the ``appdirs`` module. 15 | 16 | .. _`Platform-specific directory`: https://github.com/ActiveState/appdirs#some-example-output 17 | -------------------------------------------------------------------------------- /docs/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | backend 2 | docstring 3 | docstrings 4 | ephemeris 5 | geoid 6 | getter 7 | Google 8 | indices 9 | initialised 10 | initialisms 11 | kB 12 | KiB 13 | lagrange 14 | MiB 15 | Moiron 16 | NaN 17 | organisation 18 | organisational 19 | parsable 20 | prepend 21 | SPK 22 | stderr 23 | toolkits 24 | URL 25 | -------------------------------------------------------------------------------- /licenses/ASTROPY_LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2015, Astropy Developers 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | * Neither the name of the Astropy Team nor the names of its contributors may be 14 | used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /licenses/CRYPTOGRAPHY_LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors (PyCA Cryptography developers). 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of PyCA Cryptography nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /licenses/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 python-astrodynamics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import re 5 | import sys 6 | from collections import defaultdict 7 | 8 | from setuptools import setup, find_packages 9 | from setuptools.command.test import test as TestCommand # noqa 10 | 11 | 12 | INIT_FILE = 'src/astrodynamics/__init__.py' 13 | init_data = open(INIT_FILE).read() 14 | 15 | metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_data)) 16 | 17 | VERSION = metadata['version'] 18 | LICENSE = metadata['license'] 19 | DESCRIPTION = metadata['description'] 20 | AUTHOR = metadata['author'] 21 | EMAIL = metadata['email'] 22 | 23 | requires = { 24 | 'appdirs', 25 | 'astropy>=1.0.5', 26 | 'colorama', 27 | 'docopt', 28 | 'jplephem>=2.0', 29 | 'networkx>=1.11', 30 | 'numpy', 31 | 'progress', 32 | 'represent>=1.4.0', 33 | 'requests', 34 | 'six', 35 | } 36 | 37 | 38 | def add_to_extras(extras_require, dest, source): 39 | """Add dependencies from `source` extra to `dest` extra, handling 40 | conditional dependencies. 41 | """ 42 | for key, deps in list(extras_require.items()): 43 | extra, _, condition = key.partition(':') 44 | if extra == source: 45 | if condition: 46 | extras_require[dest + ':' + condition] |= deps 47 | else: 48 | extras_require[dest] |= deps 49 | 50 | 51 | extras_require = defaultdict(set) 52 | 53 | extras_require[':python_version<"3.4"'] = {'pathlib'} 54 | 55 | extras_require['test'] = { 56 | 'pytest>=2.7.3', 57 | 'responses', 58 | } 59 | 60 | extras_require['test:python_version<"3.3"'] = {'mock'} 61 | 62 | extras_require['dev'] = { 63 | 'doc8', 64 | 'flake8', 65 | 'flake8-coding', 66 | 'flake8-future-import', 67 | 'isort', 68 | 'pep8-naming', 69 | 'plumbum>=1.6.0', 70 | 'pyenchant', 71 | 'pytest-cov', 72 | 'shovel', 73 | 'sphinx', 74 | 'sphinx_rtd_theme', 75 | 'sphinxcontrib-spelling', 76 | 'tabulate', 77 | 'tox', 78 | 'twine', 79 | 'watchdog', 80 | } 81 | 82 | add_to_extras(extras_require, 'dev', 'test') 83 | 84 | extras_require = dict(extras_require) 85 | 86 | 87 | class PyTest(TestCommand): 88 | user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] 89 | 90 | def initialize_options(self): 91 | TestCommand.initialize_options(self) 92 | self.pytest_args = [] 93 | 94 | def finalize_options(self): 95 | TestCommand.finalize_options(self) 96 | self.test_args = [] 97 | self.test_suite = True 98 | 99 | def run_tests(self): 100 | # import here, cause outside the eggs aren't loaded 101 | import pytest 102 | errno = pytest.main(self.pytest_args) 103 | sys.exit(errno) 104 | 105 | 106 | setup( 107 | name='astrodynamics', 108 | version=VERSION, 109 | description=DESCRIPTION, 110 | long_description=open('README.rst').read(), 111 | author=AUTHOR, 112 | author_email=EMAIL, 113 | url='https://github.com/python-astrodynamics/astrodynamics', 114 | package_dir={"": "src"}, 115 | packages=find_packages(where='src'), 116 | cmdclass={'test': PyTest}, 117 | classifiers=[ 118 | 'Development Status :: 1 - Planning', 119 | 'License :: OSI Approved :: MIT License', 120 | 'Programming Language :: Python :: 2.7', 121 | 'Programming Language :: Python :: 3', 122 | ], 123 | license=LICENSE, 124 | install_requires=requires, 125 | extras_require=extras_require, 126 | tests_require=extras_require['test']) 127 | -------------------------------------------------------------------------------- /shovel/_helpers.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from plumbum.cmd import git 5 | 6 | 7 | def check_git_unchanged(filename, yes=False): 8 | """Check git to avoid overwriting user changes.""" 9 | if check_staged(filename): 10 | s = 'There are staged changes in {}, overwrite? [y/n] '.format(filename) 11 | if yes or input(s) in ('y', 'yes'): 12 | return 13 | else: 14 | raise RuntimeError('There are staged changes in ' 15 | '{}, aborting.'.format(filename)) 16 | if check_unstaged(filename): 17 | s = 'There are unstaged changes in {}, overwrite? [y/n] '.format(filename) 18 | if yes or input(s) in ('y', 'yes'): 19 | return 20 | else: 21 | raise RuntimeError('There are unstaged changes in ' 22 | '{}, aborting.'.format(filename)) 23 | 24 | 25 | def check_staged(filename=None): 26 | """Check if there are 'changes to be committed' in the index.""" 27 | retcode, _, stdout = git['diff-index', '--quiet', '--cached', 'HEAD', 28 | filename].run(retcode=None) 29 | if retcode == 1: 30 | return True 31 | elif retcode == 0: 32 | return False 33 | else: 34 | raise RuntimeError(stdout) 35 | 36 | 37 | def check_unstaged(filename): 38 | """Check if there are 'changes not staged for commit' in the working 39 | directory. 40 | """ 41 | retcode, _, stdout = git['diff-files', '--quiet', 42 | filename].run(retcode=None) 43 | if retcode == 1: 44 | return True 45 | elif retcode == 0: 46 | return False 47 | else: 48 | raise RuntimeError(stdout) 49 | -------------------------------------------------------------------------------- /shovel/code.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from pathlib import Path 5 | 6 | from isort import SortImports 7 | from shovel import task 8 | 9 | # isort multi_line_output modes 10 | GRID = 0 11 | VERTICAL = 1 12 | HANGING_INDENT = 2 13 | VERTICAL_HANGING_INDENT = 3 14 | HANGING_GRID = 4 15 | HANGING_GRID_GROUPED = 5 16 | 17 | 18 | @task 19 | def format_imports(): 20 | """Sort imports into a consistent style.""" 21 | astrodynamics_dir = Path('astrodynamics') 22 | constants_dir = astrodynamics_dir / 'constants' 23 | for initfile in astrodynamics_dir.glob('**/__init__.py'): 24 | if constants_dir in initfile.parents: 25 | continue 26 | SortImports(str(initfile), 27 | multi_line_output=VERTICAL_HANGING_INDENT, 28 | not_skip=['__init__.py']) 29 | 30 | # Exclude __init__.py 31 | # Exclude generated constants/ python files 32 | for pyfile in astrodynamics_dir.glob('**/*.py'): 33 | if constants_dir in pyfile.parents and pyfile.stem != 'constant': 34 | continue 35 | SortImports(str(pyfile), 36 | multi_line_output=HANGING_GRID, 37 | skip=['__init__.py'], 38 | known_third_party=['six']) 39 | -------------------------------------------------------------------------------- /shovel/constants.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import re 5 | from collections import OrderedDict, namedtuple 6 | from itertools import chain 7 | from pathlib import Path 8 | 9 | from shovel import task 10 | from tabulate import tabulate 11 | 12 | from astrodynamics.constants import Constant 13 | 14 | from _helpers import check_git_unchanged 15 | 16 | ParsedConstant = namedtuple('ParsedConstant', ['name', 'value']) 17 | constant_re = re.compile('^(?P[A-Z0-9_]+) = (?P.+)') 18 | 19 | TEMPLATE = """# coding: utf-8 20 | from __future__ import absolute_import, division, print_function 21 | 22 | from astropy import units as u # noqa 23 | 24 | # Absolute import used here so file can be exec'd 25 | # standalone by documentation helper script. 26 | from astrodynamics.constants import Constant # noqa 27 | 28 | __all__ = ( 29 | {all_string} 30 | ) 31 | 32 | {constant_string}""" 33 | 34 | INIT_TEMPLATE = """# coding: utf-8 35 | from __future__ import absolute_import, division, print_function 36 | 37 | from .constant import Constant 38 | {imports} 39 | 40 | __all__ = ( 41 | 'Constant', 42 | {all_string} 43 | ) 44 | """ 45 | 46 | IMPORT_TEMPLATE = """from .{modulename} import ( 47 | {import_string} 48 | )""" 49 | 50 | 51 | DOC_TEMPLATE = """********* 52 | Constants 53 | ********* 54 | 55 | .. currentmodule:: astrodynamics.constants 56 | 57 | .. autoclass:: Constant 58 | :members: name, uncertainty, reference 59 | 60 | List of constants 61 | ================= 62 | 63 | {value_table} 64 | 65 | References 66 | ========== 67 | 68 | {details_table} 69 | 70 | .. _`license`: https://raw.githubusercontent.com/python-astrodynamics/astrodynamics/master/licenses/ASTROPY_LICENSE.txt 71 | """ # noqa 72 | 73 | 74 | def get_constants_from_data(): 75 | # Mapping of module names to list of constants. 76 | constants = dict() 77 | 78 | sourcedir = Path('data', 'constants') 79 | for constantsfile in sourcedir.glob('*.txt'): 80 | modulename = constantsfile.stem 81 | if modulename == 'README': 82 | continue 83 | constants[modulename] = [] 84 | 85 | with constantsfile.open(encoding='utf-8') as f: 86 | name = value = None 87 | 88 | for line in f: 89 | if line.lstrip().startswith('#'): 90 | continue 91 | elif line.startswith(' '): 92 | # This is a multiline constant definition 93 | assert value is not None 94 | value += line 95 | else: 96 | match = constant_re.match(line) 97 | if match: 98 | # This means new constant is being defined. 99 | 100 | if name is not None: 101 | constants[modulename].append( 102 | ParsedConstant(name=name, value=value)) 103 | name = value = None 104 | 105 | name = match.group('name') 106 | value = match.group('value') + '\n' 107 | 108 | if name is not None: 109 | constants[modulename].append( 110 | ParsedConstant(name=name, value=value)) 111 | 112 | return constants 113 | 114 | 115 | @task 116 | def make_module(yes=False): 117 | """Use the templates defined above to create the astrodynamics.constants 118 | module. 119 | """ 120 | constants = get_constants_from_data() 121 | init_imports = dict() 122 | pythondir = Path('astrodynamics', 'constants') 123 | 124 | for modulename, constants_list in constants.items(): 125 | pythonfile = pythondir / '{}.py'.format(modulename) 126 | if pythonfile.exists(): 127 | check_git_unchanged(str(pythonfile), yes=yes) 128 | 129 | all_lines = (" '{}',".format(c.name) for c in constants_list) 130 | all_string = '\n'.join(all_lines) 131 | 132 | init_import_lines = (' {},'.format(c.name) for c in constants_list) 133 | init_import_string = '\n'.join(init_import_lines) 134 | 135 | init_imports[modulename] = IMPORT_TEMPLATE.format( 136 | modulename=modulename, 137 | import_string=init_import_string) 138 | 139 | line = '{c.name} = {c.value}' 140 | constant_lines = (line.format(c=c) for c in constants_list) 141 | constant_string = '\n'.join(constant_lines) 142 | 143 | with pythonfile.open('w', encoding='utf-8') as f: 144 | f.write(TEMPLATE.format(all_string=all_string, 145 | constant_string=constant_string)) 146 | 147 | initfile = pythondir / '__init__.py' 148 | if initfile.exists(): 149 | check_git_unchanged(str(initfile), yes=yes) 150 | 151 | # Sort init_imports and constants by key 152 | init_imports = OrderedDict(sorted(init_imports.items(), key=lambda t: t[0])) 153 | imports = '\n'.join(init_imports.values()) 154 | 155 | constants = OrderedDict(sorted(constants.items(), key=lambda t: t[0])) 156 | flat_constants = chain.from_iterable(constants.values()) 157 | all_lines = (" '{}',".format(c.name) for c in flat_constants) 158 | all_string = '\n'.join(all_lines) 159 | 160 | with initfile.open('w', encoding='utf-8') as f: 161 | f.write(INIT_TEMPLATE.format(imports=imports, all_string=all_string)) 162 | 163 | 164 | @task 165 | def make_documentation(yes=False): 166 | """Use the templates defined above to create the astrodynamics.constants 167 | documentation. 168 | """ 169 | constants = get_constants_from_data() 170 | docfile = Path('docs', 'modules', 'constants.rst') 171 | 172 | pythondir = Path('astrodynamics', 'constants') 173 | for pythonfile in pythondir.glob('*.py'): 174 | if pythonfile.stem not in constants: 175 | # Skip non-definition files: __init__.py, constant.py 176 | continue 177 | 178 | with pythonfile.open() as f: 179 | exec(f.read()) 180 | 181 | # Sort constants by key 182 | constants = OrderedDict(sorted(constants.items(), key=lambda t: t[0])) 183 | 184 | # Hide individual modules from constants documentation 185 | flat_constants = chain.from_iterable(constants.values()) 186 | 187 | value_table_data = [] 188 | value_table_headers = ['Constant', 'Value', 'Uncertainty'] 189 | 190 | details_table_data = [] 191 | details_table_headers = ['Constant', 'Full name', 'Reference'] 192 | 193 | for constant in flat_constants: 194 | name = constant.name 195 | value = eval(name) 196 | latex = value._repr_latex_()[1:-1] 197 | if isinstance(value, Constant): 198 | value_table_data.append( 199 | [name, 200 | ':math:`{latex}`'.format(latex=latex), 201 | value.uncertainty]) 202 | details_table_data.append([name, value.name, value.reference]) 203 | else: 204 | value_table_data.append( 205 | [name, 206 | ':math:`{latex}`'.format(latex=latex), 207 | 'N/A']) 208 | 209 | if docfile.exists(): 210 | check_git_unchanged(str(docfile), yes=yes) 211 | 212 | with docfile.open('w', encoding='utf-8') as f: 213 | f.write(DOC_TEMPLATE.format( 214 | value_table=tabulate(value_table_data, value_table_headers, 215 | tablefmt='rst'), 216 | details_table=tabulate(details_table_data, details_table_headers, 217 | tablefmt='rst'))) 218 | 219 | 220 | @task 221 | def make(yes=False): 222 | make_module(yes) 223 | make_documentation(yes) 224 | -------------------------------------------------------------------------------- /shovel/docs.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | from shutil import rmtree 6 | 7 | from plumbum import FG 8 | from plumbum.cmd import pandoc, sphinx_build 9 | from shovel import task 10 | from watchdog.observers import Observer 11 | from watchdog.tricks import ShellCommandTrick 12 | from watchdog.watchmedo import observe_with 13 | 14 | from _helpers import check_git_unchanged 15 | 16 | 17 | @task 18 | def watch(): 19 | """Renerate documentation when it changes.""" 20 | 21 | # Start with a clean build 22 | sphinx_build['-b', 'html', '-E', 'docs', 'docs/_build/html'] & FG 23 | 24 | handler = ShellCommandTrick( 25 | shell_command='sphinx-build -b html docs docs/_build/html', 26 | patterns=['*.rst', '*.py', '*.css'], 27 | ignore_patterns=['_build/*'], 28 | ignore_directories=['.tox'], 29 | drop_during_process=True) 30 | observer = Observer() 31 | observe_with(observer, handler, pathnames=['.'], recursive=True) 32 | 33 | 34 | @task 35 | def gen(skipdirhtml=False): 36 | """Generate html and dirhtml output.""" 37 | docs_changelog = 'docs/changelog.rst' 38 | check_git_unchanged(docs_changelog) 39 | pandoc('--from=markdown', '--to=rst', '--output=' + docs_changelog, 'CHANGELOG.md') 40 | if not skipdirhtml: 41 | sphinx_build['-b', 'dirhtml', '-W', '-E', 'docs', 'docs/_build/dirhtml'] & FG 42 | sphinx_build['-b', 'html', '-W', '-E', 'docs', 'docs/_build/html'] & FG 43 | 44 | 45 | @task 46 | def clean(): 47 | """Clean build directory.""" 48 | rmtree('docs/_build') 49 | os.mkdir('docs/_build') 50 | -------------------------------------------------------------------------------- /shovel/test.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import subprocess 5 | from collections import OrderedDict 6 | 7 | from plumbum import local, FG 8 | from shovel import task 9 | 10 | pytest = local['py.test'] 11 | 12 | 13 | @task 14 | def quick(): 15 | failed = OrderedDict.fromkeys( 16 | ['test', 'docs', 'spelling', 'doc8', 'flake8'], False) 17 | 18 | failed['tests'] = bool(subprocess.call(['py.test'])) 19 | failed['docs'] = bool(subprocess.call( 20 | ['sphinx-build', '-W', '-b', 'html', 'docs', 'docs/_build/html'])) 21 | failed['spelling'] = bool(subprocess.call([ 22 | 'sphinx-build', '-W', '-b', 'spelling', 'docs', 'docs/_build/html'])) 23 | failed['doc8'] = bool(subprocess.call(['doc8', 'docs'])) 24 | failed['flake8'] = bool(subprocess.call(['flake8'])) 25 | 26 | print('\nSummary:') 27 | for k, v in failed.items(): 28 | print('{:8s}: {}'.format(k, 'Fail' if v else 'Pass')) 29 | 30 | 31 | @task 32 | def coverage(): 33 | pytest['--cov=astrodynamics', '--cov-report=html', '--cov-config=.coveragerc'] & FG 34 | -------------------------------------------------------------------------------- /src/astrodynamics/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | __version__ = '0.1.0' 5 | __description__ = 'astrodynamics' 6 | 7 | __license__ = 'MIT' 8 | 9 | __author__ = 'The astrodynamics developers' 10 | __email__ = 'developers@python-astrodynamics.org' 11 | -------------------------------------------------------------------------------- /src/astrodynamics/__main__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """astrodynamics 3 | 4 | Usage: 5 | astrodynamics download_spk [options] 6 | 7 | Options: 8 | --download-dir Default: {default} 9 | 10 | Example: 11 | astrodynamics download_spk planets de421 12 | """ 13 | from __future__ import absolute_import, division, print_function 14 | 15 | import sys 16 | 17 | from docopt import docopt 18 | 19 | from .utils import SPK_DIR, SPKDownloadError, download_spk 20 | 21 | 22 | def main(): 23 | args = docopt(__doc__.format(default=SPK_DIR)) 24 | 25 | if args['download_spk']: 26 | category = args[''] 27 | kernel = args[''] 28 | download_dir = args['--download-dir'] 29 | try: 30 | download_spk(category=category, kernel=kernel, download_dir=download_dir) 31 | except SPKDownloadError as e: 32 | sys.exit(e) 33 | 34 | 35 | if __name__ == '__main__': 36 | main() 37 | -------------------------------------------------------------------------------- /src/astrodynamics/bodies/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from .celestialbody import ( 5 | CelestialBody, 6 | earth, 7 | jupiter, 8 | mars, 9 | mercury, 10 | neptune, 11 | pluto, 12 | saturn, 13 | uranus, 14 | venus 15 | ) 16 | from .ellipsoid import Ellipsoid, ReferenceEllipsoid, wgs84 17 | 18 | __all__ = ( 19 | 'CelestialBody', 20 | 'earth', 21 | 'Ellipsoid', 22 | 'jupiter', 23 | 'mars', 24 | 'mercury', 25 | 'neptune', 26 | 'pluto', 27 | 'ReferenceEllipsoid', 28 | 'saturn', 29 | 'uranus', 30 | 'venus', 31 | 'wgs84', 32 | ) 33 | -------------------------------------------------------------------------------- /src/astrodynamics/bodies/celestialbody.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from astropy.units import Quantity 5 | from represent import ReprHelperMixin 6 | 7 | from ..constants import ( 8 | CONSTANT_OF_GRAVITATION, JUPITER_MASS, JUPITER_RADIUS_EQUATORIAL, 9 | JUPITER_RADIUS_POLAR, MARS_MASS, MARS_RADIUS_EQUATORIAL, MARS_RADIUS_POLAR, 10 | MERCURY_MASS, MERCURY_RADIUS_EQUATORIAL, MERCURY_RADIUS_POLAR, 11 | NEPTUNE_MASS, NEPTUNE_RADIUS_EQUATORIAL, NEPTUNE_RADIUS_POLAR, PLUTO_MASS, 12 | PLUTO_RADIUS_EQUATORIAL, PLUTO_RADIUS_POLAR, SATURN_MASS, 13 | SATURN_RADIUS_EQUATORIAL, SATURN_RADIUS_POLAR, URANUS_MASS, 14 | URANUS_RADIUS_EQUATORIAL, URANUS_RADIUS_POLAR, VENUS_MASS, 15 | VENUS_RADIUS_EQUATORIAL, VENUS_RADIUS_POLAR) 16 | from ..utils import read_only_property, verify_unit 17 | from .ellipsoid import Ellipsoid, wgs84 18 | 19 | __all__ = ( 20 | 'CelestialBody', 21 | 'mercury', 22 | 'venus', 23 | 'earth', 24 | 'mars', 25 | 'jupiter', 26 | 'saturn', 27 | 'uranus', 28 | 'neptune', 29 | 'pluto', 30 | ) 31 | 32 | 33 | class CelestialBody(ReprHelperMixin, object): 34 | """Celestial body. 35 | 36 | Parameters: 37 | name: Name of the celestial body. 38 | ellipsoid: Representative ellipsoid. 39 | mu: Standard gravitational parameter [m\ :sup:`3`\ ·s\ :sup:`-2`] 40 | naif_id: :term:`NAIF ID` for body. 41 | 42 | :type ellipsoid: :py:class:`~astrodynamics.bodies.ellipsoid.Ellipsoid` 43 | """ 44 | def __init__(self, name, ellipsoid, mu, naif_id): 45 | self.name = name 46 | self._ellipsoid = ellipsoid 47 | self._mu = verify_unit(mu, 'm3 / s2') 48 | self._naif_id = naif_id 49 | 50 | self._mass = verify_unit(mu / CONSTANT_OF_GRAVITATION, 'kg') 51 | 52 | @classmethod 53 | def from_reference_ellipsoid(cls, name, ellipsoid, naif_id): 54 | """Construct from a 55 | :py:class:`~astrodynamics.bodies.ellipsoid.ReferenceEllipsoid`, which 56 | provides ``mu``. 57 | 58 | Parameters: 59 | name: Name of the celestial body. 60 | ellipsoid: Representative ellipsoid. 61 | naif_id: :term:`NAIF ID` for celestial body. 62 | 63 | :type ellipsoid: :py:class:`~astrodynamics.bodies.ellipsoid.ReferenceEllipsoid` 64 | """ 65 | return cls(name=name, ellipsoid=ellipsoid, mu=ellipsoid.mu, naif_id=naif_id) 66 | 67 | ellipsoid = read_only_property('_ellipsoid') 68 | mu = read_only_property('_mu') 69 | mass = read_only_property('_mass') 70 | 71 | def _repr_helper_(self, r): 72 | r.keyword_from_attr('name') 73 | r.keyword_from_attr('ellipsoid') 74 | # View as Quantity to prevent full Constant repr. 75 | r.keyword_with_value('mu', self.mu.view(Quantity)) 76 | r.keyword_from_attr('naif_id', '_naif_id') 77 | 78 | 79 | G = CONSTANT_OF_GRAVITATION 80 | 81 | mercury = CelestialBody( 82 | name='Mercury', 83 | ellipsoid=Ellipsoid(a=MERCURY_RADIUS_EQUATORIAL, b=MERCURY_RADIUS_POLAR), 84 | mu=G * MERCURY_MASS, naif_id=199) 85 | 86 | venus = CelestialBody( 87 | name='Venus', 88 | ellipsoid=Ellipsoid(a=VENUS_RADIUS_EQUATORIAL, b=VENUS_RADIUS_POLAR), 89 | mu=G * VENUS_MASS, naif_id=299) 90 | 91 | earth = CelestialBody.from_reference_ellipsoid(name='Earth', ellipsoid=wgs84, naif_id=399) 92 | 93 | mars = CelestialBody( 94 | name='Mars', 95 | ellipsoid=Ellipsoid(a=MARS_RADIUS_EQUATORIAL, b=MARS_RADIUS_POLAR), 96 | mu=G * MARS_MASS, naif_id=499) 97 | 98 | jupiter = CelestialBody( 99 | name='Jupiter', 100 | ellipsoid=Ellipsoid(a=JUPITER_RADIUS_EQUATORIAL, b=JUPITER_RADIUS_POLAR), 101 | mu=G * JUPITER_MASS, naif_id=599) 102 | 103 | saturn = CelestialBody( 104 | name='Saturn', 105 | ellipsoid=Ellipsoid(a=SATURN_RADIUS_EQUATORIAL, b=SATURN_RADIUS_POLAR), 106 | mu=G * SATURN_MASS, naif_id=699) 107 | 108 | uranus = CelestialBody( 109 | name='Uranus', 110 | ellipsoid=Ellipsoid(a=URANUS_RADIUS_EQUATORIAL, b=URANUS_RADIUS_POLAR), 111 | mu=G * URANUS_MASS, naif_id=799) 112 | 113 | neptune = CelestialBody( 114 | name='Neptune', 115 | ellipsoid=Ellipsoid(a=NEPTUNE_RADIUS_EQUATORIAL, b=NEPTUNE_RADIUS_POLAR), 116 | mu=G * NEPTUNE_MASS, naif_id=899) 117 | 118 | pluto = CelestialBody( 119 | name='Pluto', 120 | ellipsoid=Ellipsoid(a=PLUTO_RADIUS_EQUATORIAL, b=PLUTO_RADIUS_POLAR), 121 | mu=G * PLUTO_MASS, naif_id=999) 122 | -------------------------------------------------------------------------------- /src/astrodynamics/bodies/ellipsoid.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from astropy.units import Quantity 5 | from represent import ReprHelperMixin 6 | 7 | from ..constants import ( 8 | WGS84_ANGULAR_VELOCITY, WGS84_EQUATORIAL_RADIUS, WGS84_FLATTENING, 9 | WGS84_MU) 10 | from ..utils import read_only_property, verify_unit 11 | 12 | __all__ = ( 13 | 'Ellipsoid', 14 | 'ReferenceEllipsoid', 15 | 'wgs84', 16 | ) 17 | 18 | 19 | class Ellipsoid(ReprHelperMixin, object): 20 | """Ellipsoid 21 | 22 | Parameters: 23 | a: Semi-major axis (equatorial radius) [m] 24 | b: Semi-minor axis (polar radius) [m] 25 | f: Flattening [-] 26 | 27 | Either ``b`` or ``f`` must be specified: the other will be calculated. 28 | """ 29 | def __init__(self, a, b=None, f=None): 30 | if (b is None and f is None) or (b and f): 31 | raise TypeError('Either b or f must be specified, but not both.') 32 | 33 | self._a = verify_unit(a, 'm') 34 | 35 | if b is None: 36 | self._b = verify_unit(a * (1 - f), 'm') 37 | self._f = verify_unit(f, '') 38 | 39 | if f is None: 40 | self._b = verify_unit(b, 'm') 41 | self._f = verify_unit(1 - (b / a), '') 42 | 43 | a = read_only_property('_a', 'Semi-major axis') 44 | b = read_only_property('_b', 'Semi-minor axis') 45 | f = read_only_property('_f', 'Flattening') 46 | 47 | def _repr_helper_(self, r): 48 | # View as Quantity to prevent full Constant repr. 49 | r.keyword_with_value('a', self.a.view(Quantity)) 50 | r.keyword_with_value('f', self.f.view(Quantity)) 51 | 52 | 53 | class ReferenceEllipsoid(Ellipsoid): 54 | """ReferenceEllipsoid 55 | 56 | Parameters: 57 | a: Semi-major axis (equatorial radius) [m] 58 | f: Flattening [-] 59 | mu: Standard gravitational parameter [m\ :sup:`3`\ ·s\ :sup:`-2`] 60 | spin: Spin rate [rad/s] 61 | """ 62 | def __init__(self, a, f, mu, spin): 63 | super(ReferenceEllipsoid, self).__init__(a=a, f=f) 64 | self._mu = verify_unit(mu, 'm3 / s2') 65 | self._spin = verify_unit(spin, 'rad / s') 66 | 67 | mu = read_only_property('_mu', 'Standard gravitational parameter') 68 | spin = read_only_property('_spin', 'Angular velocity') 69 | 70 | def _repr_helper_(self, r): 71 | super(ReferenceEllipsoid, self)._repr_helper_(r) 72 | # View as Quantity to prevent full Constant repr. 73 | r.keyword_with_value('mu', self.mu.view(Quantity)) 74 | r.keyword_with_value('spin', self.spin.view(Quantity)) 75 | 76 | 77 | wgs84 = ReferenceEllipsoid( 78 | a=WGS84_EQUATORIAL_RADIUS, 79 | f=WGS84_FLATTENING, 80 | mu=WGS84_MU, 81 | spin=WGS84_ANGULAR_VELOCITY) 82 | -------------------------------------------------------------------------------- /src/astrodynamics/compat/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /src/astrodynamics/compat/contextlib.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | try: 5 | from contextlib import suppress 6 | except ImportError: 7 | suppress = None 8 | 9 | 10 | class _SuppressExceptions: 11 | """Helper for suppress.""" 12 | def __init__(self, *exceptions): 13 | self._exceptions = exceptions 14 | 15 | def __enter__(self): 16 | pass 17 | 18 | def __exit__(self, exctype, excinst, exctb): 19 | # Unlike isinstance and issubclass, exception handling only 20 | # looks at the concrete type heirarchy (ignoring the instance 21 | # and subclass checking hooks). However, all exceptions are 22 | # also required to be concrete subclasses of BaseException, so 23 | # if there's a discrepancy in behaviour, we currently consider it 24 | # the fault of the strange way the exception has been defined rather 25 | # than the fact that issubclass can be customised while the 26 | # exception checks can't. 27 | # See http://bugs.python.org/issue12029 for more details 28 | return exctype is not None and issubclass(exctype, self._exceptions) 29 | 30 | 31 | # Use a wrapper function since we don't care about supporting inheritance 32 | # and a function gives much cleaner output in help() 33 | def _suppress(*exceptions): 34 | """Context manager to suppress specified exceptions 35 | After the exception is suppressed, execution proceeds with the next 36 | statement following the with statement. 37 | with suppress(FileNotFoundError): 38 | os.remove(somefile) 39 | # Execution still resumes here if the file was already removed 40 | """ 41 | return _SuppressExceptions(*exceptions) 42 | 43 | 44 | suppress = suppress or _suppress 45 | -------------------------------------------------------------------------------- /src/astrodynamics/compat/math.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import math 5 | 6 | try: 7 | from math import isclose 8 | except ImportError: 9 | isclose = None 10 | 11 | 12 | def _isclose(a, b, rel_tol=1e-9, abs_tol=0.0): 13 | """ 14 | Returns True if `a` is close in value to `b`. False otherwise 15 | 16 | :param a: one of the values to be tested 17 | 18 | :param b: the other value to be tested 19 | 20 | :param rel_tol=1e-9: The relative tolerance -- the amount of error 21 | allowed, relative to the absolute value of the 22 | larger input values. 23 | 24 | :param abs_tol=0.0: The minimum absolute tolerance level -- useful 25 | for comparisons to zero. 26 | 27 | NOTES: 28 | 29 | -inf, inf and NaN behave similarly to the IEEE 754 Standard. That 30 | is, NaN is not close to anything, even itself. inf and -inf are 31 | only close to themselves. 32 | 33 | The function can be used with any type that supports comparison, 34 | subtraction and multiplication, including Decimal, Fraction, and 35 | Complex 36 | 37 | Complex values are compared based on their absolute value. 38 | 39 | See PEP-0485 for a detailed description. 40 | 41 | This is the result of much discussion on the python-ideas list 42 | in January, 2015: 43 | 44 | https://mail.python.org/pipermail/python-ideas/2015-January/030947.html 45 | 46 | https://mail.python.org/pipermail/python-ideas/2015-January/031124.html 47 | 48 | https://mail.python.org/pipermail/python-ideas/2015-January/031313.html 49 | 50 | Copyright: Christopher H. Barker 51 | License: Apache License 2.0 http://opensource.org/licenses/apache2.0.php 52 | """ 53 | 54 | if rel_tol < 0.0 or abs_tol < 0.0: 55 | raise ValueError('tolerances must be non-negative') 56 | 57 | if a == b: 58 | # short circuit exact equality -- needed to catch two infinities of 59 | # the same sign. And perhaps speeds things up a bit sometimes. 60 | return True 61 | 62 | # This catches the case of two infinities of opposite sign, or 63 | # one infinity and one finite number. Two infinities of opposite 64 | # sign would otherwise have an infinite relative tolerance. 65 | # Two infinities of the same sign are caught by the equality check 66 | # above. 67 | if math.isinf(a) or math.isinf(b): 68 | return False 69 | 70 | diff = abs(b - a) 71 | 72 | return (((diff <= abs(rel_tol * b)) or 73 | (diff <= abs(rel_tol * a))) or 74 | (diff <= abs_tol)) 75 | 76 | 77 | isclose = isclose or _isclose 78 | -------------------------------------------------------------------------------- /src/astrodynamics/constants/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from .constant import Constant 5 | from .iau import ( 6 | CONSTANT_OF_GRAVITATION, 7 | SOLAR_MASS_PARAMETER, 8 | EARTH_RADIUS_EQUATORIAL, 9 | J2, 10 | GEOCENTRIC_GRAVITATIONAL_CONSTANT, 11 | GEOID_POTENTIAL, 12 | EARTH_ANGULAR_VELOCITY, 13 | MASS_RATIO_MOON_TO_EARTH, 14 | MASS_RATIO_SUN_TO_MERCURY, 15 | MASS_RATIO_SUN_TO_VENUS, 16 | MASS_RATIO_SUN_TO_MARS, 17 | MASS_RATIO_SUN_TO_JUPITER, 18 | MASS_RATIO_SUN_TO_SATURN, 19 | MASS_RATIO_SUN_TO_URANUS, 20 | MASS_RATIO_SUN_TO_NEPTUNE, 21 | MASS_RATIO_SUN_TO_PLUTO, 22 | MERCURY_RADIUS_MEAN, 23 | MERCURY_RADIUS_EQUATORIAL, 24 | MERCURY_RADIUS_POLAR, 25 | VENUS_RADIUS_MEAN, 26 | VENUS_RADIUS_EQUATORIAL, 27 | VENUS_RADIUS_POLAR, 28 | EARTH_RADIUS_MEAN, 29 | EARTH_RADIUS_POLAR, 30 | MARS_RADIUS_MEAN, 31 | MARS_RADIUS_EQUATORIAL, 32 | MARS_RADIUS_POLAR, 33 | JUPITER_RADIUS_MEAN, 34 | JUPITER_RADIUS_EQUATORIAL, 35 | JUPITER_RADIUS_POLAR, 36 | SATURN_RADIUS_MEAN, 37 | SATURN_RADIUS_EQUATORIAL, 38 | SATURN_RADIUS_POLAR, 39 | URANUS_RADIUS_MEAN, 40 | URANUS_RADIUS_EQUATORIAL, 41 | URANUS_RADIUS_POLAR, 42 | NEPTUNE_RADIUS_MEAN, 43 | NEPTUNE_RADIUS_EQUATORIAL, 44 | NEPTUNE_RADIUS_POLAR, 45 | PLUTO_RADIUS_MEAN, 46 | PLUTO_RADIUS_EQUATORIAL, 47 | PLUTO_RADIUS_POLAR, 48 | MOON_RADIUS_MEAN, 49 | MOON_RADIUS_EQUATORIAL, 50 | MOON_RADIUS_POLAR, 51 | SUN_RADIUS_EQUATORIAL, 52 | SUN_MASS, 53 | EARTH_MASS, 54 | MOON_MASS, 55 | MERCURY_MASS, 56 | VENUS_MASS, 57 | MARS_MASS, 58 | JUPITER_MASS, 59 | SATURN_MASS, 60 | URANUS_MASS, 61 | NEPTUNE_MASS, 62 | PLUTO_MASS, 63 | ) 64 | from .wgs84 import ( 65 | WGS84_EQUATORIAL_RADIUS, 66 | WGS84_FLATTENING, 67 | WGS84_MU, 68 | WGS84_ANGULAR_VELOCITY, 69 | ) 70 | 71 | __all__ = ( 72 | 'Constant', 73 | 'CONSTANT_OF_GRAVITATION', 74 | 'SOLAR_MASS_PARAMETER', 75 | 'EARTH_RADIUS_EQUATORIAL', 76 | 'J2', 77 | 'GEOCENTRIC_GRAVITATIONAL_CONSTANT', 78 | 'GEOID_POTENTIAL', 79 | 'EARTH_ANGULAR_VELOCITY', 80 | 'MASS_RATIO_MOON_TO_EARTH', 81 | 'MASS_RATIO_SUN_TO_MERCURY', 82 | 'MASS_RATIO_SUN_TO_VENUS', 83 | 'MASS_RATIO_SUN_TO_MARS', 84 | 'MASS_RATIO_SUN_TO_JUPITER', 85 | 'MASS_RATIO_SUN_TO_SATURN', 86 | 'MASS_RATIO_SUN_TO_URANUS', 87 | 'MASS_RATIO_SUN_TO_NEPTUNE', 88 | 'MASS_RATIO_SUN_TO_PLUTO', 89 | 'MERCURY_RADIUS_MEAN', 90 | 'MERCURY_RADIUS_EQUATORIAL', 91 | 'MERCURY_RADIUS_POLAR', 92 | 'VENUS_RADIUS_MEAN', 93 | 'VENUS_RADIUS_EQUATORIAL', 94 | 'VENUS_RADIUS_POLAR', 95 | 'EARTH_RADIUS_MEAN', 96 | 'EARTH_RADIUS_POLAR', 97 | 'MARS_RADIUS_MEAN', 98 | 'MARS_RADIUS_EQUATORIAL', 99 | 'MARS_RADIUS_POLAR', 100 | 'JUPITER_RADIUS_MEAN', 101 | 'JUPITER_RADIUS_EQUATORIAL', 102 | 'JUPITER_RADIUS_POLAR', 103 | 'SATURN_RADIUS_MEAN', 104 | 'SATURN_RADIUS_EQUATORIAL', 105 | 'SATURN_RADIUS_POLAR', 106 | 'URANUS_RADIUS_MEAN', 107 | 'URANUS_RADIUS_EQUATORIAL', 108 | 'URANUS_RADIUS_POLAR', 109 | 'NEPTUNE_RADIUS_MEAN', 110 | 'NEPTUNE_RADIUS_EQUATORIAL', 111 | 'NEPTUNE_RADIUS_POLAR', 112 | 'PLUTO_RADIUS_MEAN', 113 | 'PLUTO_RADIUS_EQUATORIAL', 114 | 'PLUTO_RADIUS_POLAR', 115 | 'MOON_RADIUS_MEAN', 116 | 'MOON_RADIUS_EQUATORIAL', 117 | 'MOON_RADIUS_POLAR', 118 | 'SUN_RADIUS_EQUATORIAL', 119 | 'SUN_MASS', 120 | 'EARTH_MASS', 121 | 'MOON_MASS', 122 | 'MERCURY_MASS', 123 | 'VENUS_MASS', 124 | 'MARS_MASS', 125 | 'JUPITER_MASS', 126 | 'SATURN_MASS', 127 | 'URANUS_MASS', 128 | 'NEPTUNE_MASS', 129 | 'PLUTO_MASS', 130 | 'WGS84_EQUATORIAL_RADIUS', 131 | 'WGS84_FLATTENING', 132 | 'WGS84_MU', 133 | 'WGS84_ANGULAR_VELOCITY', 134 | ) 135 | -------------------------------------------------------------------------------- /src/astrodynamics/constants/constant.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import numpy as np 5 | from astropy.units import Quantity, Unit 6 | from astropy.utils import lazyproperty 7 | from represent import ReprHelperMixin 8 | 9 | from ..utils import read_only_property 10 | 11 | 12 | class Constant(ReprHelperMixin, Quantity): 13 | """A physical or astronomical constant. 14 | 15 | These objects are quantities that are meant to represent physical 16 | constants. 17 | 18 | Parameters: 19 | name: The full name of the constant. 20 | value: Numerical value of constant 21 | unit (str): Units for given value. Must be parsable by 22 | :py:class:`astropy.units.Unit` 23 | uncertainty: The known uncertainty in this constant's value. 24 | reference: The source used for the value of this constant. 25 | 26 | This class is modified from :py:class:`astropy.constants.Constant`. It 27 | retains the original `license`_. 28 | """ 29 | def __new__(cls, name, value, unit, uncertainty, reference): 30 | # By-pass Quantity initialization, since units may not yet be 31 | # initialized here, and we store the unit in string form. 32 | inst = np.array(value).view(cls) 33 | 34 | inst._name = name 35 | inst._value = value 36 | inst._unit_string = unit 37 | inst._uncertainty = uncertainty 38 | inst._reference = reference 39 | 40 | return inst 41 | 42 | def _repr_helper_(self, r): 43 | r.keyword_from_attr('name') 44 | r.keyword_from_attr('value') 45 | r.keyword_with_value('unit', str(self.unit)) 46 | r.keyword_from_attr('uncertainty') 47 | r.keyword_from_attr('reference') 48 | 49 | def __quantity_subclass__(self, unit): 50 | return super(Constant, self).__quantity_subclass__(unit)[0], False 51 | 52 | def copy(self): 53 | """ 54 | Return a copy of this `Constant` instance. Since they are by 55 | definition immutable, this merely returns another reference to 56 | ``self``. 57 | """ 58 | return self 59 | 60 | __copy__ = copy 61 | 62 | def __deepcopy__(self, memo): 63 | return self.copy() 64 | 65 | name = read_only_property('_name', 'The full name of the constant.') 66 | 67 | uncertainty = read_only_property( 68 | name='_uncertainty', 69 | docstring="The known uncertainty in this constant's value") 70 | 71 | reference = read_only_property( 72 | name='_reference', 73 | docstring='The source used for the value of this constant.') 74 | 75 | @lazyproperty 76 | def _unit(self): 77 | """The unit(s) in which this constant is defined.""" 78 | 79 | return Unit(self._unit_string) 80 | 81 | def __array_finalize__(self, obj): 82 | for attr in ('_name', '_value', '_unit_string', 83 | '_uncertainty', '_reference'): 84 | setattr(self, attr, getattr(obj, attr, None)) 85 | -------------------------------------------------------------------------------- /src/astrodynamics/constants/iau.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from astropy import units as u # flake8: noqa 5 | 6 | # Absolute import used here so file can be exec'd 7 | # standalone by documentation helper script. 8 | from astrodynamics.constants import Constant # flake8: noqa 9 | 10 | __all__ = ( 11 | 'CONSTANT_OF_GRAVITATION', 12 | 'SOLAR_MASS_PARAMETER', 13 | 'EARTH_RADIUS_EQUATORIAL', 14 | 'J2', 15 | 'GEOCENTRIC_GRAVITATIONAL_CONSTANT', 16 | 'GEOID_POTENTIAL', 17 | 'EARTH_ANGULAR_VELOCITY', 18 | 'MASS_RATIO_MOON_TO_EARTH', 19 | 'MASS_RATIO_SUN_TO_MERCURY', 20 | 'MASS_RATIO_SUN_TO_VENUS', 21 | 'MASS_RATIO_SUN_TO_MARS', 22 | 'MASS_RATIO_SUN_TO_JUPITER', 23 | 'MASS_RATIO_SUN_TO_SATURN', 24 | 'MASS_RATIO_SUN_TO_URANUS', 25 | 'MASS_RATIO_SUN_TO_NEPTUNE', 26 | 'MASS_RATIO_SUN_TO_PLUTO', 27 | 'MERCURY_RADIUS_MEAN', 28 | 'MERCURY_RADIUS_EQUATORIAL', 29 | 'MERCURY_RADIUS_POLAR', 30 | 'VENUS_RADIUS_MEAN', 31 | 'VENUS_RADIUS_EQUATORIAL', 32 | 'VENUS_RADIUS_POLAR', 33 | 'EARTH_RADIUS_MEAN', 34 | 'EARTH_RADIUS_POLAR', 35 | 'MARS_RADIUS_MEAN', 36 | 'MARS_RADIUS_EQUATORIAL', 37 | 'MARS_RADIUS_POLAR', 38 | 'JUPITER_RADIUS_MEAN', 39 | 'JUPITER_RADIUS_EQUATORIAL', 40 | 'JUPITER_RADIUS_POLAR', 41 | 'SATURN_RADIUS_MEAN', 42 | 'SATURN_RADIUS_EQUATORIAL', 43 | 'SATURN_RADIUS_POLAR', 44 | 'URANUS_RADIUS_MEAN', 45 | 'URANUS_RADIUS_EQUATORIAL', 46 | 'URANUS_RADIUS_POLAR', 47 | 'NEPTUNE_RADIUS_MEAN', 48 | 'NEPTUNE_RADIUS_EQUATORIAL', 49 | 'NEPTUNE_RADIUS_POLAR', 50 | 'PLUTO_RADIUS_MEAN', 51 | 'PLUTO_RADIUS_EQUATORIAL', 52 | 'PLUTO_RADIUS_POLAR', 53 | 'MOON_RADIUS_MEAN', 54 | 'MOON_RADIUS_EQUATORIAL', 55 | 'MOON_RADIUS_POLAR', 56 | 'SUN_RADIUS_EQUATORIAL', 57 | 'SUN_MASS', 58 | 'EARTH_MASS', 59 | 'MOON_MASS', 60 | 'MERCURY_MASS', 61 | 'VENUS_MASS', 62 | 'MARS_MASS', 63 | 'JUPITER_MASS', 64 | 'SATURN_MASS', 65 | 'URANUS_MASS', 66 | 'NEPTUNE_MASS', 67 | 'PLUTO_MASS', 68 | ) 69 | 70 | CONSTANT_OF_GRAVITATION = Constant( 71 | name='Constant of gravitation', 72 | value=6.67428e-11, 73 | unit='m3 / (kg s2)', 74 | uncertainty=6.7e-15, 75 | reference='IAU 2009/2012 System of Astronomical Constants') 76 | 77 | SOLAR_MASS_PARAMETER = Constant( 78 | name='Solar mass parameter (TCB)', 79 | value=1.32712442099e20, 80 | unit='m3 / s2', 81 | uncertainty=1e10, 82 | reference='IAU 2009/2012 System of Astronomical Constants') 83 | 84 | EARTH_RADIUS_EQUATORIAL = Constant( 85 | name='Equatorial radius of Earth (TT)', 86 | value=6378136.6, 87 | unit='m', 88 | uncertainty=0.1, 89 | reference='IAU 2009/2012 System of Astronomical Constants') 90 | 91 | J2 = Constant( 92 | name='Dynamical form-factor for the Earth', 93 | value=0.0010826359, 94 | unit='', 95 | uncertainty=1e-10, 96 | reference='IAU 2009/2012 System of Astronomical Constants') 97 | 98 | GEOCENTRIC_GRAVITATIONAL_CONSTANT = Constant( 99 | name='Geocentric gravitational constant (TCB)', 100 | value=3.986004418e14, 101 | unit='m3 / s2', 102 | uncertainty=8e5, 103 | reference='IAU 2009/2012 System of Astronomical Constants') 104 | 105 | GEOID_POTENTIAL = Constant( 106 | name='Potential of the geoid', 107 | value=6.26368560e7, 108 | unit='m2 / s2', 109 | uncertainty=0.5, 110 | reference='IAU 2009/2012 System of Astronomical Constants') 111 | 112 | EARTH_ANGULAR_VELOCITY = Constant( 113 | name='Nominal mean angular velocity of the Earth (TT)', 114 | value=7.292115e-5, 115 | unit='rad / s', 116 | uncertainty=0, 117 | reference='IAU 2009/2012 System of Astronomical Constants') 118 | 119 | MASS_RATIO_MOON_TO_EARTH = Constant( 120 | name='Mass ratio: Moon to Earth', 121 | value=1.23000371e-2, 122 | unit='', 123 | uncertainty=4e-10, 124 | reference='IAU 2009/2012 System of Astronomical Constants') 125 | 126 | MASS_RATIO_SUN_TO_MERCURY = Constant( 127 | name='Mass ratio: Sun to Mercury', 128 | value=6.0236e6, 129 | unit='', 130 | uncertainty=3e2, 131 | reference='IAU 2009/2012 System of Astronomical Constants') 132 | 133 | MASS_RATIO_SUN_TO_VENUS = Constant( 134 | name='Mass ratio: Sun to Venus', 135 | value=4.08523719e5, 136 | unit='', 137 | uncertainty=8e-3, 138 | reference='IAU 2009/2012 System of Astronomical Constants') 139 | 140 | MASS_RATIO_SUN_TO_MARS = Constant( 141 | name='Mass ratio: Sun to Mars', 142 | value=3.09870359e6, 143 | unit='', 144 | uncertainty=2e-2, 145 | reference='IAU 2009/2012 System of Astronomical Constants') 146 | 147 | MASS_RATIO_SUN_TO_JUPITER = Constant( 148 | name='Mass ratio: Sun to Jupiter', 149 | value=1.047348644e3, 150 | unit='', 151 | uncertainty=1.7e-5, 152 | reference='IAU 2009/2012 System of Astronomical Constants') 153 | 154 | MASS_RATIO_SUN_TO_SATURN = Constant( 155 | name='Mass ratio: Sun to Saturn', 156 | value=3.4979018e3, 157 | unit='', 158 | uncertainty=1e-4, 159 | reference='IAU 2009/2012 System of Astronomical Constants') 160 | 161 | MASS_RATIO_SUN_TO_URANUS = Constant( 162 | name='Mass ratio: Sun to Uranus', 163 | value=2.290298e4, 164 | unit='', 165 | uncertainty=3e-2, 166 | reference='IAU 2009/2012 System of Astronomical Constants') 167 | 168 | MASS_RATIO_SUN_TO_NEPTUNE = Constant( 169 | name='Mass ratio: Sun to Neptune', 170 | value=1.941226e4, 171 | unit='', 172 | uncertainty=3e-2, 173 | reference='IAU 2009/2012 System of Astronomical Constants') 174 | 175 | MASS_RATIO_SUN_TO_PLUTO = Constant( 176 | name='Mass ratio: Sun to Pluto (134340)', 177 | value=1.36566e8, 178 | unit='', 179 | uncertainty=2.8e4, 180 | reference='IAU 2009/2012 System of Astronomical Constants') 181 | 182 | MERCURY_RADIUS_MEAN = Constant( 183 | name='Mean radius of Mercury', 184 | value=2439.9, 185 | unit='km', 186 | uncertainty=1, 187 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 188 | 189 | MERCURY_RADIUS_EQUATORIAL = Constant( 190 | name='Equatorial radius of Mercury', 191 | value=2439.9, 192 | unit='km', 193 | uncertainty=1, 194 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 195 | 196 | MERCURY_RADIUS_POLAR = Constant( 197 | name='Polar radius of Mercury', 198 | value=2439.9, 199 | unit='km', 200 | uncertainty=1, 201 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 202 | 203 | VENUS_RADIUS_MEAN = Constant( 204 | name='Mean radius of Venus', 205 | value=6051.8, 206 | unit='km', 207 | uncertainty=1, 208 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 209 | 210 | VENUS_RADIUS_EQUATORIAL = Constant( 211 | name='Equatorial radius of Venus', 212 | value=6051.8, 213 | unit='km', 214 | uncertainty=1, 215 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 216 | 217 | VENUS_RADIUS_POLAR = Constant( 218 | name='Polar radius of Venus', 219 | value=6051.8, 220 | unit='km', 221 | uncertainty=1, 222 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 223 | 224 | EARTH_RADIUS_MEAN = Constant( 225 | name='Mean radius of Earth', 226 | value=6371.0084, 227 | unit='km', 228 | uncertainty=0.0001, 229 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 230 | 231 | EARTH_RADIUS_POLAR = Constant( 232 | name='Polar radius of Earth', 233 | value=6356.7519, 234 | unit='km', 235 | uncertainty=0.0001, 236 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 237 | 238 | MARS_RADIUS_MEAN = Constant( 239 | name='Mean radius of Mars', 240 | value=3389.50, 241 | unit='km', 242 | uncertainty=0.2, 243 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 244 | 245 | MARS_RADIUS_EQUATORIAL = Constant( 246 | name='Equatorial radius of Mars', 247 | value=3396.19, 248 | unit='km', 249 | uncertainty=0.1, 250 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 251 | 252 | MARS_RADIUS_POLAR = Constant( 253 | name='Polar radius of Mars', 254 | value=3376.20, 255 | unit='km', 256 | uncertainty=0.1, 257 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 258 | 259 | JUPITER_RADIUS_MEAN = Constant( 260 | name='Mean radius of Jupiter', 261 | value=69911, 262 | unit='km', 263 | uncertainty=6, 264 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 265 | 266 | JUPITER_RADIUS_EQUATORIAL = Constant( 267 | name='Equatorial radius of Jupiter', 268 | value=71492, 269 | unit='km', 270 | uncertainty=4, 271 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 272 | 273 | JUPITER_RADIUS_POLAR = Constant( 274 | name='Polar radius of Jupiter', 275 | value=66854, 276 | unit='km', 277 | uncertainty=10, 278 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 279 | 280 | SATURN_RADIUS_MEAN = Constant( 281 | name='Mean radius of Saturn', 282 | value=58232, 283 | unit='km', 284 | uncertainty=6, 285 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 286 | 287 | SATURN_RADIUS_EQUATORIAL = Constant( 288 | name='Equatorial radius of Saturn', 289 | value=60268, 290 | unit='km', 291 | uncertainty=4, 292 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 293 | 294 | SATURN_RADIUS_POLAR = Constant( 295 | name='Polar radius of Saturn', 296 | value=54364, 297 | unit='km', 298 | uncertainty=10, 299 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 300 | 301 | URANUS_RADIUS_MEAN = Constant( 302 | name='Mean radius of Uranus', 303 | value=25362, 304 | unit='km', 305 | uncertainty=7, 306 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 307 | 308 | URANUS_RADIUS_EQUATORIAL = Constant( 309 | name='Equatorial radius of Uranus', 310 | value=25559, 311 | unit='km', 312 | uncertainty=4, 313 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 314 | 315 | URANUS_RADIUS_POLAR = Constant( 316 | name='Polar radius of Uranus', 317 | value=24973, 318 | unit='km', 319 | uncertainty=20, 320 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 321 | 322 | NEPTUNE_RADIUS_MEAN = Constant( 323 | name='Mean radius of Neptune', 324 | value=24622, 325 | unit='km', 326 | uncertainty=19, 327 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 328 | 329 | NEPTUNE_RADIUS_EQUATORIAL = Constant( 330 | name='Equatorial radius of Neptune', 331 | value=24764, 332 | unit='km', 333 | uncertainty=15, 334 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 335 | 336 | NEPTUNE_RADIUS_POLAR = Constant( 337 | name='Polar radius of Neptune', 338 | value=24341, 339 | unit='km', 340 | uncertainty=30, 341 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 342 | 343 | PLUTO_RADIUS_MEAN = Constant( 344 | name='Mean radius of Pluto (134340)', 345 | value=1195, 346 | unit='km', 347 | uncertainty=5, 348 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 349 | 350 | PLUTO_RADIUS_EQUATORIAL = Constant( 351 | name='Equatorial radius of Pluto (134340)', 352 | value=1195, 353 | unit='km', 354 | uncertainty=5, 355 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 356 | 357 | PLUTO_RADIUS_POLAR = Constant( 358 | name='Polar radius of Pluto (134340)', 359 | value=1195, 360 | unit='km', 361 | uncertainty=5, 362 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 363 | 364 | MOON_RADIUS_MEAN = Constant( 365 | name='Mean radius of Moon', 366 | value=1737.4, 367 | unit='km', 368 | uncertainty=1, 369 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 370 | 371 | MOON_RADIUS_EQUATORIAL = Constant( 372 | name='Equatorial radius of Moon', 373 | value=1737.4, 374 | unit='km', 375 | uncertainty=1, 376 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 377 | 378 | MOON_RADIUS_POLAR = Constant( 379 | name='Polar radius of Moon', 380 | value=1737.4, 381 | unit='km', 382 | uncertainty=1, 383 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 384 | 385 | SUN_RADIUS_EQUATORIAL = Constant( 386 | name='Equatorial radius of Sun', 387 | value=696000, 388 | unit='km', 389 | uncertainty=1, 390 | reference='IAU WG on Cartographic Coordinates and Rotational Elements 2009') 391 | 392 | SUN_MASS = SOLAR_MASS_PARAMETER / CONSTANT_OF_GRAVITATION 393 | 394 | EARTH_MASS = GEOCENTRIC_GRAVITATIONAL_CONSTANT / CONSTANT_OF_GRAVITATION 395 | 396 | MOON_MASS = MASS_RATIO_MOON_TO_EARTH * EARTH_MASS 397 | 398 | MERCURY_MASS = SUN_MASS / MASS_RATIO_SUN_TO_MERCURY 399 | 400 | VENUS_MASS = SUN_MASS / MASS_RATIO_SUN_TO_VENUS 401 | 402 | MARS_MASS = SUN_MASS / MASS_RATIO_SUN_TO_MARS 403 | 404 | JUPITER_MASS = SUN_MASS / MASS_RATIO_SUN_TO_JUPITER 405 | 406 | SATURN_MASS = SUN_MASS / MASS_RATIO_SUN_TO_SATURN 407 | 408 | URANUS_MASS = SUN_MASS / MASS_RATIO_SUN_TO_URANUS 409 | 410 | NEPTUNE_MASS = SUN_MASS / MASS_RATIO_SUN_TO_NEPTUNE 411 | 412 | PLUTO_MASS = SUN_MASS / MASS_RATIO_SUN_TO_PLUTO 413 | -------------------------------------------------------------------------------- /src/astrodynamics/constants/wgs84.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from astropy import units as u # flake8: noqa 5 | 6 | # Absolute import used here so file can be exec'd 7 | # standalone by documentation helper script. 8 | from astrodynamics.constants import Constant # flake8: noqa 9 | 10 | __all__ = ( 11 | 'WGS84_EQUATORIAL_RADIUS', 12 | 'WGS84_FLATTENING', 13 | 'WGS84_MU', 14 | 'WGS84_ANGULAR_VELOCITY', 15 | ) 16 | 17 | WGS84_EQUATORIAL_RADIUS = Constant( 18 | name='WGS84 semi-major axis', 19 | value=6378137, 20 | unit='m', 21 | uncertainty=0, 22 | reference='World Geodetic System 1984') 23 | 24 | WGS84_FLATTENING = Constant( 25 | name='WGS84 Earth flattening factor', 26 | value=1 / 298.257223563, 27 | unit='', 28 | uncertainty=0, 29 | reference='World Geodetic System 1984') 30 | 31 | WGS84_MU = Constant( 32 | name='WGS84 geocentric gravitational constant', 33 | value=3.986004418e14, 34 | unit='m3 / s2', 35 | uncertainty=0, 36 | reference='World Geodetic System 1984') 37 | 38 | WGS84_ANGULAR_VELOCITY = Constant( 39 | name='WGS84 nominal earth mean angular velocity', 40 | value=7.292115e-5, 41 | unit='rad / s', 42 | uncertainty=0, 43 | reference='World Geodetic System 1984') 44 | -------------------------------------------------------------------------------- /src/astrodynamics/lowlevel/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /src/astrodynamics/lowlevel/ephemerides.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """The astrodynamics.lowlevel.ephemerides module 3 | 4 | This module contains a wrapper around the 'jplephem' 5 | library by Brandon Rhodes. 6 | """ 7 | 8 | from __future__ import absolute_import, division, print_function 9 | 10 | import jplephem.spk as spk 11 | import networkx as nx 12 | import numpy as np 13 | 14 | 15 | class JPLEphemeris(object): 16 | def load_kernel(self, spk_file): 17 | self._kernel = spk.SPK.open(spk_file) 18 | self.generate_paths() 19 | 20 | def generate_paths(self): 21 | graph = nx.Graph() 22 | for edge in self.kernel.pairs: 23 | graph.add_edge(*edge) 24 | self.paths = nx.shortest_path(graph) 25 | 26 | @property 27 | def kernel(self): 28 | _kernel = getattr(self, '_kernel', None) 29 | if not _kernel: 30 | raise AttributeError("No SPICE kernel was loaded.") 31 | else: 32 | return self._kernel 33 | 34 | def _compute_segment(self, origin, target, tdb, tdb2): 35 | if (target, origin) in self.kernel.pairs: 36 | origin, target = target, origin 37 | factor = -1 38 | elif (origin, target) in self.kernel.pairs: 39 | factor = 1 40 | segment = self.kernel[origin, target] 41 | r, v = segment.compute_and_differentiate(tdb, tdb2) 42 | return factor * r, factor * v 43 | 44 | def _compute_path(self, path, tdb, tdb2): 45 | if len(path) == 2: 46 | r, v = self._compute_segment(path[0], path[1], tdb, tdb2) 47 | else: 48 | r = np.zeros(3) 49 | v = np.zeros(3) 50 | for origin, target in zip(path, path[1:]): 51 | rs, vs = self._compute_segment(origin, target, tdb, tdb2) 52 | r += rs 53 | v += vs 54 | return r, v 55 | 56 | def rv(self, origin, target, tdb, tdb2=0.0): 57 | if origin not in self.paths or target not in self.paths: 58 | raise ValueError("Unknown pair({}, {}).".format(origin, target)) 59 | path = self.paths[origin][target] 60 | r, v = self._compute_path(path, tdb, tdb2) 61 | return r, v 62 | -------------------------------------------------------------------------------- /src/astrodynamics/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from .compat import PY2, PY3, PY33, WINDOWS 5 | from .helper import ( 6 | format_size, 7 | prefix, 8 | qisclose, 9 | read_only_property, 10 | suppress_file_exists_error, 11 | verify_unit 12 | ) 13 | from .progress import DownloadProgressBar, DownloadProgressSpinner 14 | from .web import ( 15 | SPK_DIR, 16 | SPK_OLD_URL, 17 | SPK_URL, 18 | InvalidCategoryError, 19 | KernelNotFoundError, 20 | SPKDownloadError, 21 | download_file_with_progress, 22 | download_spk 23 | ) 24 | 25 | __all__ = ( 26 | 'download_file_with_progress', 27 | 'download_spk', 28 | 'DownloadProgressBar', 29 | 'DownloadProgressSpinner', 30 | 'format_size', 31 | 'InvalidCategoryError', 32 | 'KernelNotFoundError', 33 | 'prefix', 34 | 'PY2', 35 | 'PY3', 36 | 'PY33', 37 | 'qisclose', 38 | 'read_only_property', 39 | 'SPK_DIR', 40 | 'SPK_OLD_URL', 41 | 'SPK_URL', 42 | 'SPKDownloadError', 43 | 'suppress_file_exists_error', 44 | 'verify_unit', 45 | 'WINDOWS', 46 | ) 47 | -------------------------------------------------------------------------------- /src/astrodynamics/utils/compat.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | import sys 6 | 7 | import six 8 | 9 | PY2 = six.PY2 10 | PY3 = six.PY3 11 | PY33 = sys.version_info >= (3, 3) 12 | 13 | # windows detection, covers cpython and ironpython 14 | WINDOWS = (sys.platform.startswith("win") or 15 | (sys.platform == 'cli' and os.name == 'nt')) 16 | -------------------------------------------------------------------------------- /src/astrodynamics/utils/helper.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import errno 5 | from contextlib import contextmanager 6 | 7 | from astropy import units as u 8 | from astropy.units import Unit, UnitBase 9 | 10 | from ..compat.contextlib import suppress 11 | from ..compat.math import isclose 12 | from .compat import PY33 13 | 14 | __all__ = ( 15 | 'format_size', 16 | 'prefix', 17 | 'qisclose', 18 | 'read_only_property', 19 | 'suppress_file_exists_error', 20 | 'verify_unit', 21 | ) 22 | 23 | _size_suffixes = { 24 | 'decimal': ('kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'), 25 | 'binary': ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'), 26 | 'gnu': "KMGTPEZY", 27 | } 28 | 29 | 30 | def read_only_property(name, docstring=None): 31 | """Return property for accessing attribute with name `name` 32 | 33 | Parameters: 34 | name: Attribute name 35 | docstring: Optional docstring for getter. 36 | 37 | Example: 38 | .. code-block:: python 39 | 40 | class Circle: 41 | def __init__(self, radius): 42 | self._radius = radius 43 | 44 | radius = read_only_property('_radius') 45 | """ 46 | def fget(self): 47 | return getattr(self, name) 48 | 49 | fget.__doc__ = docstring 50 | return property(fget) 51 | 52 | 53 | def verify_unit(quantity, unit): 54 | """Verify unit of passed quantity and return it. 55 | 56 | Parameters: 57 | quantity: :py:class:`~astropy.units.Quantity` to be verified. Bare 58 | numbers are valid if the unit is dimensionless. 59 | unit: Equivalent unit, or string parsable by 60 | :py:class:`astropy.units.Unit` 61 | 62 | Raises: 63 | ValueError: Units are not equivalent. 64 | 65 | Returns: 66 | ``quantity`` unchanged. Bare numbers will be converted to a dimensionless 67 | :py:class:`~astropy.units.Quantity`. 68 | 69 | Example: 70 | .. code-block:: python 71 | 72 | def __init__(self, a): 73 | self.a = verify_unit(a, astropy.units.m) 74 | 75 | """ 76 | if not isinstance(unit, UnitBase): 77 | unit = Unit(unit) 78 | 79 | q = quantity * u.one 80 | if unit.is_equivalent(q.unit): 81 | return q 82 | else: 83 | raise ValueError( 84 | "Unit '{}' not equivalent to quantity '{}'.".format(unit, quantity)) 85 | 86 | 87 | def qisclose(a, b, rel_tol=1e-9, abs_tol=0.0): 88 | """Helper function for using :py:func:`math.isclose` with 89 | :py:class:`~astropy.units.Quantity` objects. 90 | """ 91 | return isclose(a.si.value, b.si.value, rel_tol=rel_tol, abs_tol=abs_tol) 92 | 93 | 94 | def format_size(value, binary=False, gnu=False, format='%.1f'): 95 | """Format a number of bytes like a human readable file size (e.g. 10 kB). By 96 | default, decimal suffixes (kB, MB) are used. Passing binary=true will use 97 | binary suffixes (KiB, MiB) are used and the base will be 2**10 instead of 98 | 10**3. If ``gnu`` is True, the binary argument is ignored and GNU-style 99 | (ls -sh style) prefixes are used (K, M) with the 2**10 definition. 100 | Non-gnu modes are compatible with jinja2's ``filesizeformat`` filter. 101 | 102 | Copyright (c) 2010 Jason Moiron and Contributors. 103 | """ 104 | if gnu: 105 | suffix = _size_suffixes['gnu'] 106 | elif binary: 107 | suffix = _size_suffixes['binary'] 108 | else: 109 | suffix = _size_suffixes['decimal'] 110 | 111 | base = 1024 if (gnu or binary) else 1000 112 | bytes = float(value) 113 | 114 | if bytes == 1 and not gnu: 115 | return '1 Byte' 116 | elif bytes < base and not gnu: 117 | return '%d Bytes' % bytes 118 | elif bytes < base and gnu: 119 | return '%dB' % bytes 120 | 121 | for i, s in enumerate(suffix): 122 | unit = base ** (i + 2) 123 | if bytes < unit and not gnu: 124 | return (format + ' %s') % ((base * bytes / unit), s) 125 | elif bytes < unit and gnu: 126 | return (format + '%s') % ((base * bytes / unit), s) 127 | if gnu: 128 | return (format + '%s') % ((base * bytes / unit), s) 129 | return (format + ' %s') % ((base * bytes / unit), s) 130 | 131 | 132 | @contextmanager 133 | def suppress_file_exists_error(): 134 | """Compatibility function for catching FileExistsError on Python 2""" 135 | if PY33: 136 | with suppress(FileExistsError): # noqa 137 | yield 138 | else: 139 | try: 140 | yield 141 | except OSError as e: 142 | if e.errno != errno.EEXIST: 143 | raise 144 | 145 | 146 | def prefix(prefix, iterable): 147 | """Prepend items from `iterable` with `prefix` string.""" 148 | for x in iterable: 149 | yield '{prefix}{x}'.format(prefix=prefix, x=x) 150 | -------------------------------------------------------------------------------- /src/astrodynamics/utils/progress.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import itertools 5 | import sys 6 | from signal import SIGINT, default_int_handler, signal 7 | 8 | import six 9 | from progress.bar import Bar, IncrementalBar 10 | from progress.helpers import WritelnMixin 11 | from progress.spinner import Spinner 12 | 13 | from .compat import WINDOWS 14 | from .helper import format_size 15 | 16 | try: 17 | import colorama 18 | # Lots of different errors can come from this, including SystemError and 19 | # ImportError. 20 | except Exception: 21 | colorama = None 22 | 23 | __all__ = ('DownloadProgressBar', 'DownloadProgressSpinner') 24 | 25 | 26 | def _select_progress_class(preferred, fallback): 27 | encoding = getattr(preferred.file, "encoding", None) 28 | 29 | # If we don't know what encoding this file is in, then we'll just assume 30 | # that it doesn't support unicode and use the ASCII bar. 31 | if not encoding: 32 | return fallback 33 | 34 | # Collect all of the possible characters we want to use with the preferred 35 | # bar. 36 | characters = [ 37 | getattr(preferred, "empty_fill", six.text_type()), 38 | getattr(preferred, "fill", six.text_type()), 39 | ] 40 | characters += list(getattr(preferred, "phases", [])) 41 | 42 | # Try to decode the characters we're using for the bar using the encoding 43 | # of the given file, if this works then we'll assume that we can use the 44 | # fancier bar and if not we'll fall back to the plaintext bar. 45 | try: 46 | six.text_type().join(characters).encode(encoding) 47 | except UnicodeEncodeError: 48 | return fallback 49 | else: 50 | return preferred 51 | 52 | 53 | _BaseBar = _select_progress_class(IncrementalBar, Bar) 54 | 55 | 56 | class InterruptibleMixin(object): 57 | """ 58 | Helper to ensure that self.finish() gets called on keyboard interrupt. 59 | 60 | This allows downloads to be interrupted without leaving temporary state 61 | (like hidden cursors) behind. 62 | 63 | This class is similar to the progress library's existing SigIntMixin 64 | helper, but as of version 1.2, that helper has the following problems: 65 | 66 | 1. It calls sys.exit(). 67 | 2. It discards the existing SIGINT handler completely. 68 | 3. It leaves its own handler in place even after an uninterrupted finish, 69 | which will have unexpected delayed effects if the user triggers an 70 | unrelated keyboard interrupt some time after a progress-displaying 71 | download has already completed, for example. 72 | """ 73 | 74 | def __init__(self, *args, **kwargs): 75 | """ 76 | Save the original SIGINT handler for later. 77 | """ 78 | super(InterruptibleMixin, self).__init__(*args, **kwargs) 79 | 80 | self.original_handler = signal(SIGINT, self.handle_sigint) 81 | 82 | # If signal() returns None, the previous handler was not installed from 83 | # Python, and we cannot restore it. This probably should not happen, 84 | # but if it does, we must restore something sensible instead, at least. 85 | # The least bad option should be Python's default SIGINT handler, which 86 | # just raises KeyboardInterrupt. 87 | if self.original_handler is None: 88 | self.original_handler = default_int_handler 89 | 90 | def finish(self): 91 | """ 92 | Restore the original SIGINT handler after finishing. 93 | 94 | This should happen regardless of whether the progress display finishes 95 | normally, or gets interrupted. 96 | """ 97 | super(InterruptibleMixin, self).finish() 98 | signal(SIGINT, self.original_handler) 99 | 100 | def handle_sigint(self, signum, frame): 101 | """ 102 | Call self.finish() before delegating to the original SIGINT handler. 103 | 104 | This handler should only be in place while the progress display is 105 | active. 106 | """ 107 | self.finish() 108 | self.original_handler(signum, frame) 109 | 110 | 111 | class DownloadProgressMixin(object): 112 | sma_window = 100 113 | 114 | def __init__(self, *args, **kwargs): 115 | super(DownloadProgressMixin, self).__init__(*args, **kwargs) 116 | self.message = (" " * (0 + 2)) + self.message 117 | 118 | @property 119 | def downloaded(self): 120 | return format_size(self.index) 121 | 122 | @property 123 | def download_speed(self): 124 | # Avoid zero division errors... 125 | if self.avg == 0.0: 126 | return "..." 127 | return format_size(1 / self.avg) + "/s" 128 | 129 | @property 130 | def pretty_eta(self): 131 | if self.eta: 132 | return "eta %s" % self.eta_td 133 | return "" 134 | 135 | def iter(self, it, n=1): 136 | for x in it: 137 | yield x 138 | self.next(n) 139 | self.finish() 140 | 141 | 142 | class WindowsMixin(object): 143 | 144 | def __init__(self, *args, **kwargs): 145 | # The Windows terminal does not support the hide/show cursor ANSI codes 146 | # even with colorama. So we'll ensure that hide_cursor is False on 147 | # Windows. 148 | # This call neds to go before the super() call, so that hide_cursor 149 | # is set in time. The base progress bar class writes the "hide cursor" 150 | # code to the terminal in its init, so if we don't set this soon 151 | # enough, we get a "hide" with no corresponding "show"... 152 | if WINDOWS and self.hide_cursor: 153 | self.hide_cursor = False 154 | 155 | super(WindowsMixin, self).__init__(*args, **kwargs) 156 | 157 | # Check if we are running on Windows and we have the colorama module, 158 | # if we do then wrap our file with it. 159 | if WINDOWS and colorama: 160 | self.file = colorama.AnsiToWin32(self.file) 161 | # The progress code expects to be able to call self.file.isatty() 162 | # but the colorama.AnsiToWin32() object doesn't have that, so we'll 163 | # add it. 164 | self.file.isatty = lambda: self.file.wrapped.isatty() 165 | # The progress code expects to be able to call self.file.flush() 166 | # but the colorama.AnsiToWin32() object doesn't have that, so we'll 167 | # add it. 168 | self.file.flush = lambda: self.file.wrapped.flush() 169 | 170 | 171 | class DownloadProgressBar(WindowsMixin, InterruptibleMixin, 172 | DownloadProgressMixin, _BaseBar): 173 | 174 | file = sys.stdout 175 | message = "%(percent)d%%" 176 | suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" 177 | 178 | 179 | class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin, 180 | DownloadProgressMixin, WritelnMixin, Spinner): 181 | 182 | file = sys.stdout 183 | suffix = "%(downloaded)s %(download_speed)s" 184 | 185 | def next_phase(self): 186 | if not hasattr(self, "_phaser"): 187 | self._phaser = itertools.cycle(self.phases) 188 | return next(self._phaser) 189 | 190 | def update(self): 191 | message = self.message % self 192 | phase = self.next_phase() 193 | suffix = self.suffix % self 194 | line = ''.join([ 195 | message, 196 | " " if message else "", 197 | phase, 198 | " " if suffix else "", 199 | suffix, 200 | ]) 201 | 202 | self.writeln(line) 203 | -------------------------------------------------------------------------------- /src/astrodynamics/utils/web.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from pathlib import Path 5 | 6 | import requests 7 | from appdirs import AppDirs 8 | from requests import HTTPError 9 | from six.moves.urllib.parse import quote 10 | 11 | from .helper import format_size, prefix, suppress_file_exists_error 12 | from .progress import DownloadProgressBar, DownloadProgressSpinner 13 | 14 | SPK_URL = ("http://naif.jpl.nasa.gov/pub/naif/generic_kernels/" 15 | "spk/{category}/{kernel}.bsp") 16 | SPK_OLD_URL = ("http://naif.jpl.nasa.gov/pub/naif/generic_kernels/" 17 | "spk/{category}/a_old_versions/{kernel}.bsp") 18 | 19 | appdirs = AppDirs('astrodynamics') 20 | SPK_DIR = Path(appdirs.user_data_dir, 'spk') 21 | 22 | 23 | class SPKDownloadError(Exception): 24 | """Raised when SPK download fails.""" 25 | 26 | 27 | class KernelNotFoundError(SPKDownloadError): 28 | """Raised when SPK kernel not found on website.""" 29 | 30 | 31 | class InvalidCategoryError(SPKDownloadError): 32 | """Raised for invalid category.""" 33 | 34 | 35 | def download_spk(category, kernel, download_dir=None): 36 | """Download generic kernel SPK files from 37 | http://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/ 38 | 39 | Parameters: 40 | category: asteroids, comets, lagrange_point, planets, satellites, or stations 41 | kernel: Kernel name, e.g. `de430` will download `de430.bsp` 42 | download_dir: Directory to download file to. By default, this is to a 43 | platform-dependent astrodynamics directory: :py:const:`SPK_DIR` 44 | """ 45 | valid_categories = ['asteroids', 'comets', 'lagrange_point', 'planets', 46 | 'satellites', 'stations'] 47 | 48 | if category not in valid_categories: 49 | s = 'Invalid category. Valid categories: {}' 50 | raise InvalidCategoryError(s.format(', '.join(valid_categories))) 51 | 52 | # We only wanted the name, strip the extension. 53 | if kernel.endswith('.bsp'): 54 | kernel = kernel[:-4] 55 | 56 | urls = [ 57 | SPK_URL.format(category=category, kernel=quote(kernel)), 58 | SPK_OLD_URL.format(category=category, kernel=quote(kernel)) 59 | ] 60 | 61 | # Get last path component for filename. 62 | # e.g. 'de423_for_mercury_and_venus/de423' => 'de423.bsp' 63 | # 'de421' => 'de421.bsp' 64 | filename = kernel.split('/')[-1] + '.bsp' 65 | 66 | if download_dir: 67 | download_dir = Path(download_dir) 68 | 69 | filepath = download_dir / filename 70 | else: 71 | with suppress_file_exists_error(): 72 | SPK_DIR.mkdir(parents=True) 73 | 74 | filepath = SPK_DIR / filename 75 | 76 | downloaded = False 77 | 78 | for url in urls: 79 | try: 80 | download_file_with_progress(url, str(filepath)) 81 | except HTTPError as exc: 82 | if exc.response.status_code != 404: 83 | raise 84 | else: 85 | print('Kernel downloaded to', filepath) 86 | downloaded = True 87 | break 88 | 89 | if not downloaded: 90 | s = 'Kernel not found in the following locations:\n{}' 91 | raise KernelNotFoundError(s.format('\n'.join(prefix(' ', urls)))) 92 | 93 | 94 | def download_file_with_progress(url, filepath): 95 | """Download URL to file with progress bar or spinner printed to stderr. 96 | 97 | Parameters: 98 | url (str): URL to download. 99 | filepath (str): File path URL will be saved to. 100 | """ 101 | resp = requests.get(url, stream=True) 102 | resp.raise_for_status() 103 | 104 | try: 105 | total_length = int(resp.headers['content-length']) 106 | except (ValueError, KeyError, TypeError): 107 | total_length = 0 108 | 109 | if total_length: 110 | print('Downloading {} ({})'.format(url, format_size(total_length))) 111 | progress_indicator = DownloadProgressBar(max=total_length).iter 112 | else: 113 | print('Downloading {}'.format(url)) 114 | progress_indicator = DownloadProgressSpinner().iter 115 | 116 | with open(filepath, 'wb') as f: 117 | for chunk in progress_indicator(resp.iter_content(10240), 10240): 118 | f.write(chunk) 119 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | -------------------------------------------------------------------------------- /tests/test_bodies.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import pytest 5 | from astropy import units as u 6 | 7 | from astrodynamics.bodies import ( 8 | CelestialBody, Ellipsoid, ReferenceEllipsoid, wgs84) 9 | from astrodynamics.constants import ( 10 | EARTH_MASS, GEOCENTRIC_GRAVITATIONAL_CONSTANT, WGS84_EQUATORIAL_RADIUS, 11 | WGS84_FLATTENING) 12 | from astrodynamics.utils import qisclose 13 | 14 | 15 | def test_ellipsoid(): 16 | """Test Ellipsoid initialiser and verify computed attributes.""" 17 | wgs84_polar_radius = 6356752.314245 * u.m 18 | 19 | wgs84_ellipsoid1 = Ellipsoid(a=WGS84_EQUATORIAL_RADIUS, f=WGS84_FLATTENING) 20 | wgs84_ellipsoid2 = Ellipsoid(a=WGS84_EQUATORIAL_RADIUS, b=wgs84_polar_radius) 21 | 22 | assert qisclose(wgs84_ellipsoid1.b, wgs84_ellipsoid2.b) 23 | assert qisclose(wgs84_ellipsoid1.f, wgs84_ellipsoid2.f) 24 | 25 | with pytest.raises(TypeError): 26 | Ellipsoid(a=WGS84_EQUATORIAL_RADIUS) 27 | 28 | with pytest.raises(TypeError): 29 | Ellipsoid(a=WGS84_EQUATORIAL_RADIUS, b=wgs84_polar_radius, 30 | f=WGS84_FLATTENING) 31 | 32 | 33 | def test_ellipsoid_repr(): 34 | a = Ellipsoid(a=2 * u.m, b=1 * u.m) 35 | assert repr(a) == 'Ellipsoid(a=, f=)' 36 | 37 | 38 | def test_reference_ellipsoid_repr(): 39 | a = ReferenceEllipsoid( 40 | a=1 * u.m, f=0, mu=1 * u.m ** 3 / u.s ** 2, spin=1 * u.rad / u.s) 41 | s = ('ReferenceEllipsoid(a=, f=, ' 42 | 'mu=, spin=)') 43 | assert repr(a) == s 44 | 45 | 46 | def test_celestial_body(): 47 | "Test CelestialBody initialiser and verify computed attributes" 48 | a = CelestialBody(name='earth', ellipsoid=wgs84, mu=wgs84.mu, naif_id=399) 49 | b = CelestialBody.from_reference_ellipsoid(name='earth', ellipsoid=wgs84, 50 | naif_id=399) 51 | assert a.mu == b.mu == wgs84.mu 52 | 53 | ellipsoid = Ellipsoid(a=1 * u.m, b=1 * u.m) 54 | c = CelestialBody(name='earth', ellipsoid=ellipsoid, 55 | mu=GEOCENTRIC_GRAVITATIONAL_CONSTANT, naif_id=399) 56 | assert qisclose(c.mass, EARTH_MASS) 57 | 58 | 59 | def test_celestial_body_repr(): 60 | ellipsoid = Ellipsoid(a=1 * u.m, b=1 * u.m) 61 | d = CelestialBody(name='d', ellipsoid=ellipsoid, mu=1 * u.m ** 3 / u.s ** 2, 62 | naif_id=1) 63 | 64 | s = ("CelestialBody(name='d', ellipsoid=Ellipsoid(a=, " 65 | "f=), mu=, naif_id=1)") 66 | assert repr(d) == s 67 | -------------------------------------------------------------------------------- /tests/test_compat.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import pytest 5 | 6 | from astrodynamics.compat.contextlib import suppress 7 | 8 | 9 | class TestSuppress: 10 | def test_instance_docs(self): 11 | # Issue 19330: ensure context manager instances have good docstrings 12 | cm_docstring = suppress.__doc__ 13 | obj = suppress() 14 | obj.__doc__ == cm_docstring 15 | 16 | def test_no_result_from_enter(self): 17 | with suppress(ValueError) as enter_result: 18 | assert enter_result is None 19 | 20 | def test_no_exception(self): 21 | with suppress(ValueError): 22 | pow(2, 5) == 32 23 | 24 | def test_exact_exception(self): 25 | with suppress(TypeError): 26 | len(5) 27 | 28 | def test_exception_hierarchy(self): 29 | with suppress(LookupError): 30 | 'Hello'[50] 31 | 32 | def test_other_exception(self): 33 | with pytest.raises(ZeroDivisionError): 34 | with suppress(TypeError): 35 | 1 / 0 36 | 37 | def test_no_args(self): 38 | with pytest.raises(ZeroDivisionError): 39 | with suppress(): 40 | 1 / 0 41 | 42 | def test_multiple_exception_args(self): 43 | with suppress(ZeroDivisionError, TypeError): 44 | 1 / 0 45 | with suppress(ZeroDivisionError, TypeError): 46 | len(5) 47 | 48 | def test_cm_is_reentrant(self): 49 | ignore_exceptions = suppress(Exception) 50 | with ignore_exceptions: 51 | pass 52 | with ignore_exceptions: 53 | len(5) 54 | ignored = False 55 | with ignore_exceptions: 56 | with ignore_exceptions: # Check nested usage 57 | len(5) 58 | ignored = True 59 | 1 / 0 60 | assert ignored 61 | -------------------------------------------------------------------------------- /tests/test_constants.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # These tests are taken from astropy, as with the astrodynamics.constant.Constant 3 | # class. It retains the original license (see licenses/ASTROPY_LICENSE.txt) 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import copy 7 | 8 | import astropy.units as u 9 | from astropy.units import Quantity 10 | 11 | import astrodynamics.constants as const 12 | from astrodynamics.constants import J2, Constant 13 | 14 | 15 | def test_units(): 16 | """Confirm that none of the constants defined in astrodynamics have invalid 17 | units. 18 | """ 19 | for key, val in vars(const).items(): 20 | if isinstance(val, Constant): 21 | # Getting the unit forces the unit parser to run. 22 | assert not isinstance(val.unit, u.UnrecognizedUnit) 23 | 24 | 25 | def test_copy(): 26 | copied = copy.deepcopy(J2) 27 | assert copied == J2 28 | 29 | copied = copy.copy(J2) 30 | assert copied == J2 31 | 32 | 33 | def test_view(): 34 | """Check that Constant and Quantity views can be taken.""" 35 | x = J2 36 | x2 = x.view(Constant) 37 | assert x2 == x 38 | assert x2.value == x.value 39 | # make sure it has the necessary attributes and they're not blank 40 | assert x2.uncertainty 41 | assert x2.name == x.name 42 | assert x2.reference == x.reference 43 | assert x2.unit == x.unit 44 | 45 | q1 = x.view(Quantity) 46 | assert q1 == x 47 | assert q1.value == x.value 48 | assert type(q1) is Quantity 49 | assert not hasattr(q1, 'reference') 50 | 51 | q2 = Quantity(x) 52 | assert q2 == x 53 | assert q2.value == x.value 54 | assert type(q2) is Quantity 55 | assert not hasattr(q2, 'reference') 56 | 57 | x3 = Quantity(x, subok=True) 58 | assert x3 == x 59 | assert x3.value == x.value 60 | # make sure it has the necessary attributes and they're not blank 61 | assert x3.uncertainty 62 | assert x3.name == x.name 63 | assert x3.reference == x.reference 64 | assert x3.unit == x.unit 65 | 66 | x4 = Quantity(x, subok=True, copy=False) 67 | assert x4 is x 68 | 69 | 70 | def test_repr(): 71 | a = Constant('the name', value=1, unit='m2', uncertainty=0.1, reference='me') 72 | s = ("Constant(name='the name', value=1, unit='m2', uncertainty=0.1, " 73 | "reference='me')") 74 | assert repr(a) == s 75 | -------------------------------------------------------------------------------- /tests/test_coverage.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import os 5 | import platform 6 | import sys 7 | 8 | import pytest 9 | from represent import ReprMixin 10 | 11 | 12 | class Machine(ReprMixin, object): 13 | def __init__(self, os, python_version, x64): 14 | self.os = os 15 | self.python_version = python_version 16 | self.x64 = x64 17 | 18 | super(Machine, self).__init__() 19 | 20 | def __eq__(self, other): 21 | return (self.os == other.os and 22 | self.python_version == other.python_version and 23 | self.x64 == other.x64) 24 | 25 | 26 | @pytest.mark.skipif(os.getenv('CI', '').lower() != 'true', 27 | reason="CI test.") 28 | def test_platform_coverage(): 29 | machine = Machine(os=platform.system(), 30 | python_version=sys.version_info[:2], 31 | x64=sys.maxsize > 2 ** 32) 32 | 33 | assert machine.os in ('Darwin', 'Linux', 'Windows') 34 | assert machine.python_version in [ 35 | (2, 7), 36 | (3, 3), 37 | (3, 4), 38 | (3, 5), 39 | ] 40 | 41 | # The charade below is so that in our unified coverage sourced from Travis 42 | # and AppVeyor, we can see that all the combinations of Python version, OS, 43 | # and 32 or 64 bits (Windows only) are tested. 44 | 45 | matches = 0 46 | 47 | testmachine = Machine(os='Darwin', python_version=(2, 7), x64=True) 48 | if machine == testmachine: 49 | matches += 1 50 | 51 | testmachine = Machine(os='Darwin', python_version=(3, 3), x64=True) 52 | if machine == testmachine: 53 | matches += 1 54 | 55 | testmachine = Machine(os='Darwin', python_version=(3, 4), x64=True) 56 | if machine == testmachine: 57 | matches += 1 58 | 59 | testmachine = Machine(os='Darwin', python_version=(3, 5), x64=True) 60 | if machine == testmachine: 61 | matches += 1 62 | 63 | testmachine = Machine(os='Linux', python_version=(2, 7), x64=True) 64 | if machine == testmachine: 65 | matches += 1 66 | 67 | testmachine = Machine(os='Linux', python_version=(3, 3), x64=True) 68 | if machine == testmachine: 69 | matches += 1 70 | 71 | testmachine = Machine(os='Linux', python_version=(3, 4), x64=True) 72 | if machine == testmachine: 73 | matches += 1 74 | 75 | testmachine = Machine(os='Linux', python_version=(3, 5), x64=True) 76 | if machine == testmachine: 77 | matches += 1 78 | 79 | testmachine = Machine(os='Windows', python_version=(2, 7), x64=True) 80 | if machine == testmachine: 81 | matches += 1 82 | 83 | testmachine = Machine(os='Windows', python_version=(2, 7), x64=False) 84 | if machine == testmachine: 85 | matches += 1 86 | 87 | testmachine = Machine(os='Windows', python_version=(3, 3), x64=True) 88 | if machine == testmachine: 89 | matches += 1 90 | 91 | testmachine = Machine(os='Windows', python_version=(3, 3), x64=False) 92 | if machine == testmachine: 93 | matches += 1 94 | 95 | testmachine = Machine(os='Windows', python_version=(3, 4), x64=True) 96 | if machine == testmachine: 97 | matches += 1 98 | 99 | testmachine = Machine(os='Windows', python_version=(3, 4), x64=False) 100 | if machine == testmachine: 101 | matches += 1 102 | 103 | testmachine = Machine(os='Windows', python_version=(3, 5), x64=True) 104 | if machine == testmachine: 105 | matches += 1 106 | 107 | testmachine = Machine(os='Windows', python_version=(3, 5), x64=False) 108 | if machine == testmachine: 109 | matches += 1 110 | 111 | # If the test fails the machine that didn't match will be printed 112 | assert matches == 1, 'No match for {}'.format(machine) 113 | -------------------------------------------------------------------------------- /tests/test_ephemerides.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import numpy as np 5 | import pytest 6 | 7 | import astrodynamics.lowlevel.ephemerides as ephemerides 8 | 9 | 10 | class MockSegment(object): 11 | def __init__(self, a, b): 12 | self.a = a 13 | self.b = b 14 | 15 | def compute_and_differentiate(self, tdb, tdb2): 16 | r = np.empty(3) 17 | v = np.empty(3) 18 | if self.a == 3 and self.b == 399: 19 | r.fill(1.0) 20 | v.fill(1.0) 21 | elif self.a == 3 and self.b == 301: 22 | r.fill(2.0) 23 | v.fill(2.0) 24 | elif self.a == 0 and self.b == 3: 25 | r.fill(3.0) 26 | v.fill(3.0) 27 | elif self.a == 0 and self.b == 4: 28 | r.fill(4.0) 29 | v.fill(4.0) 30 | return r, v 31 | 32 | 33 | class MockKernel(object): 34 | def __init__(self): 35 | self.pairs = [ 36 | (0, 3), 37 | (0, 4), 38 | (3, 301), 39 | (3, 399), 40 | ] 41 | 42 | def __getitem__(self, ind): 43 | return MockSegment(ind[0], ind[1]) 44 | 45 | 46 | @pytest.fixture 47 | def ephemeris(): 48 | eph = ephemerides.JPLEphemeris() 49 | eph._kernel = MockKernel() 50 | eph.generate_paths() 51 | return eph 52 | 53 | 54 | def test_pair_failure(ephemeris): 55 | with pytest.raises(ValueError): 56 | ephemeris.rv(0, 5, 0) 57 | 58 | 59 | def test_ephemeris(ephemeris): 60 | r, v = ephemeris.rv(0, 3, 0) 61 | assert np.all(r == 3.0) 62 | assert np.all(v == 3.0) 63 | r, v = ephemeris.rv(3, 0, 0) 64 | assert np.all(r == -3.0) 65 | assert np.all(v == -3.0) 66 | r, v = ephemeris.rv(0, 4, 0) 67 | assert np.all(r == 4.0) 68 | assert np.all(v == 4.0) 69 | r, v = ephemeris.rv(4, 0, 0) 70 | assert np.all(r == -4.0) 71 | assert np.all(v == -4.0) 72 | r, v = ephemeris.rv(3, 301, 0) 73 | assert np.all(r == 2.0) 74 | assert np.all(v == 2.0) 75 | r, v = ephemeris.rv(301, 3, 0) 76 | assert np.all(r == -2.0) 77 | assert np.all(v == -2.0) 78 | r, v = ephemeris.rv(3, 399, 0) 79 | assert np.all(r == 1.0) 80 | assert np.all(v == 1.0) 81 | r, v = ephemeris.rv(399, 3, 0) 82 | assert np.all(r == -1.0) 83 | assert np.all(v == -1.0) 84 | r, v = ephemeris.rv(0, 301, 0) 85 | assert np.all(r == 5.0) 86 | assert np.all(v == 5.0) 87 | r, v = ephemeris.rv(301, 0, 0) 88 | assert np.all(r == -5.0) 89 | assert np.all(v == -5.0) 90 | r, v = ephemeris.rv(0, 399, 0) 91 | assert np.all(r == 4.0) 92 | assert np.all(v == 4.0) 93 | r, v = ephemeris.rv(399, 0, 0) 94 | assert np.all(r == -4.0) 95 | assert np.all(v == -4.0) 96 | r, v = ephemeris.rv(3, 4, 0) 97 | assert np.all(r == 1.0) 98 | assert np.all(v == 1.0) 99 | r, v = ephemeris.rv(4, 3, 0) 100 | assert np.all(r == -1.0) 101 | assert np.all(v == -1.0) 102 | r, v = ephemeris.rv(301, 4, 0) 103 | assert np.all(r == -1.0) 104 | assert np.all(v == -1.0) 105 | r, v = ephemeris.rv(4, 301, 0) 106 | assert np.all(r == 1.0) 107 | assert np.all(v == 1.0) 108 | r, v = ephemeris.rv(4, 301, 0, 0) 109 | assert np.all(r == 1.0) 110 | assert np.all(v == 1.0) 111 | -------------------------------------------------------------------------------- /tests/test_isclose.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | from decimal import Decimal 5 | from fractions import Fraction 6 | 7 | import pytest 8 | 9 | from astrodynamics.compat.math import _isclose 10 | 11 | # These tests are taken from Python 3.5 stdlib tests for isclose. 12 | # If we're on Python 3.5+, import math.isclose too. 13 | 14 | try: 15 | from math import isclose 16 | except ImportError: 17 | isclose_functions = [_isclose] 18 | else: 19 | isclose_functions = [_isclose, isclose] 20 | 21 | 22 | @pytest.fixture(scope='module', params=isclose_functions) 23 | def isclose_function(request): 24 | return request.param 25 | 26 | 27 | class TestIsclose: 28 | eps = 1E-05 29 | NAN = float('nan') 30 | INF = float('inf') 31 | NINF = float('-inf') 32 | 33 | # identical values must test as close 34 | identical_examples = [(2.0, 2.0), 35 | (0.1e200, 0.1e200), 36 | (1.123e-300, 1.123e-300), 37 | (12345, 12345.0), 38 | (0.0, -0.0), 39 | (345678, 345678)] 40 | 41 | # examples that are close to 1e-8, but not 1e-9 42 | eight_decimal_places_examples = [(1e8, 1e8 + 1), 43 | (-1e-8, -1.000000009e-8), 44 | (1.12345678, 1.12345679)] 45 | 46 | near_zero_examples = [(1e-9, 0.0), 47 | (-1e-9, 0.0), 48 | (-1e-150, 0.0)] 49 | 50 | # these should never be close (following IEEE 754 rules for equality) 51 | not_close_examples = [(NAN, NAN), 52 | (NAN, 1e-100), 53 | (1e-100, NAN), 54 | (INF, NAN), 55 | (NAN, INF), 56 | (INF, NINF), 57 | (INF, 1.0), 58 | (1.0, INF), 59 | (INF, 1e308), 60 | (1e308, INF)] 61 | 62 | zero_tolerance_close_examples = [(1.0, 1.0), 63 | (-3.4, -3.4), 64 | (-1e-300, -1e-300)] 65 | 66 | zero_tolerance_not_close_examples = [(1.0, 1.000000000000001), 67 | (0.99999999999999, 1.0), 68 | (1.0e200, .999999999999999e200)] 69 | 70 | integer_examples = [(100000001, 100000000), 71 | (123456789, 123456788)] 72 | 73 | decimal_examples = [(Decimal('1.00000001'), Decimal('1.0')), 74 | (Decimal('1.00000001e-20'), Decimal('1.0e-20')), 75 | (Decimal('1.00000001e-100'), Decimal('1.0e-100'))] 76 | 77 | # could use some more examples here! 78 | fraction_examples = [(Fraction(1, 100000000) + 1, Fraction(1))] 79 | 80 | def test_negative_tolerances(self, isclose_function): 81 | # ValueError should be raised if either tolerance is less than zero 82 | with pytest.raises(ValueError): 83 | assert isclose_function(1, 1, rel_tol=-1e-100) 84 | with pytest.raises(ValueError): 85 | assert isclose_function(1, 1, rel_tol=1e-100, abs_tol=-1e10) 86 | 87 | @pytest.mark.parametrize('a, b', identical_examples) 88 | def test_identical(self, a, b, isclose_function): 89 | assert isclose_function(a, b, rel_tol=0.0, abs_tol=0.0) 90 | 91 | @pytest.mark.parametrize('a, b', eight_decimal_places_examples) 92 | def test_eight_decimal_places(self, a, b, isclose_function): 93 | assert isclose_function(a, b, rel_tol=1e-8) 94 | assert not isclose_function(a, b, rel_tol=1e-9) 95 | 96 | @pytest.mark.parametrize('a, b', near_zero_examples) 97 | def test_near_zero(self, a, b, isclose_function): 98 | # these should not be close to any rel_tol 99 | assert not isclose_function(a, b, rel_tol=0.9) 100 | # these should be close to abs_tol=1e-8 101 | assert isclose_function(a, b, abs_tol=1e-8) 102 | 103 | def test_identical_infinite(self, isclose_function): 104 | # these are close regardless of tolerance -- i.e. they are equal 105 | assert isclose_function(self.INF, self.INF) 106 | assert isclose_function(self.INF, self.INF, abs_tol=0.0) 107 | assert isclose_function(self.NINF, self.NINF) 108 | assert isclose_function(self.NINF, self.NINF, abs_tol=0.0) 109 | 110 | @pytest.mark.parametrize('a, b', not_close_examples) 111 | def test_inf_ninf_nan(self, a, b, isclose_function): 112 | # use largest reasonable tolerance 113 | assert not isclose_function(a, b, abs_tol=0.999999999999999) 114 | 115 | @pytest.mark.parametrize('a, b', zero_tolerance_close_examples) 116 | def test_zero_tolerance(self, a, b, isclose_function): 117 | assert isclose_function(a, b, rel_tol=0.0) 118 | 119 | @pytest.mark.parametrize('a, b', zero_tolerance_not_close_examples) 120 | def test_zero_tolerance_not_close(self, a, b, isclose_function): 121 | assert not isclose_function(a, b, rel_tol=0.0) 122 | 123 | @pytest.mark.parametrize('a, b', [(9, 10), (10, 9)]) 124 | def test_assymetry(self, a, b, isclose_function): 125 | # test the assymetry example from PEP 485 126 | assert isclose_function(a, b, rel_tol=0.1) 127 | 128 | @pytest.mark.parametrize('a, b', integer_examples) 129 | def test_integers(self, a, b, isclose_function): 130 | assert isclose_function(a, b, rel_tol=1e-8) 131 | assert not isclose_function(a, b, rel_tol=1e-9) 132 | 133 | @pytest.mark.parametrize('a, b', decimal_examples) 134 | def test_decimals(self, a, b, isclose_function): 135 | if isclose_function is _isclose: 136 | pytest.xfail("Python implementation of isclose doesn't work with Decimal") 137 | assert isclose_function(a, b, rel_tol=1e-8) 138 | assert not isclose_function(a, b, rel_tol=1e-9) 139 | 140 | @pytest.mark.parametrize('a, b', fraction_examples) 141 | def test_fractions(self, a, b, isclose_function): 142 | assert isclose_function(a, b, rel_tol=1e-8) 143 | assert not isclose_function(a, b, rel_tol=1e-9) 144 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import absolute_import, division, print_function 3 | 4 | import errno 5 | import io 6 | from pathlib import Path 7 | 8 | import pytest 9 | import requests 10 | import responses 11 | from astropy import units as u 12 | from responses import GET 13 | 14 | from astrodynamics.__main__ import main 15 | from astrodynamics.utils import ( 16 | SPK_DIR, SPK_OLD_URL, SPK_URL, DownloadProgressBar, 17 | DownloadProgressSpinner, InvalidCategoryError, KernelNotFoundError, 18 | SPKDownloadError, download_spk, format_size, suppress_file_exists_error, 19 | verify_unit) 20 | 21 | try: 22 | from unittest.mock import Mock, patch 23 | except ImportError: 24 | from mock import Mock, patch 25 | 26 | 27 | def test_verify_unit(): 28 | # Implicit dimensionless values are allowed, test that Quantity is returned. 29 | assert verify_unit(0, u.one) == 0 * u.one 30 | assert verify_unit(0, '') == 0 * u.one 31 | 32 | # Test failure mode 33 | with pytest.raises(ValueError): 34 | verify_unit(0, u.meter) 35 | with pytest.raises(ValueError): 36 | verify_unit(0, 'm') 37 | 38 | # Quantity should be passed back if unit matches 39 | assert verify_unit(1 * u.meter, u.meter) == 1 * u.meter 40 | assert verify_unit(1 * u.meter, 'm') == 1 * u.meter 41 | 42 | 43 | size_tests = ( 44 | 300, 3000, 3000000, 3000000000, 3000000000000, (300, True), (3000, True), 45 | (3000000, True), (300, False, True), (3000, False, True), 46 | (3000000, False, True), (1024, False, True), (10 ** 26 * 30, False, True), 47 | (10 ** 26 * 30, True), 10 ** 26 * 30, (3141592, False, False, '%.2f'), 48 | (3000, False, True, '%.3f'), (3000000000, False, True, '%.0f'), 49 | (10 ** 26 * 30, True, False, '%.3f'), 1 50 | ) 51 | 52 | size_results = ( 53 | '300 Bytes', '3.0 kB', '3.0 MB', '3.0 GB', '3.0 TB', '300 Bytes', '2.9 KiB', 54 | '2.9 MiB', '300B', '2.9K', '2.9M', '1.0K', '2481.5Y', '2481.5 YiB', 55 | '3000.0 YB', '3.14 MB', '2.930K', '3G', '2481.542 YiB', '1 Byte' 56 | ) 57 | 58 | 59 | @pytest.mark.parametrize('args, string', zip(size_tests, size_results)) 60 | def test_format_size(args, string): 61 | if not isinstance(args, tuple): 62 | args = args, 63 | assert format_size(*args) == string 64 | 65 | 66 | spk_data = [ 67 | {'category': 'planets', 'kernel': 'de432s', 'old': False}, 68 | {'category': 'planets', 'kernel': 'de410mini', 'old': True}, 69 | ] 70 | 71 | 72 | def test_suppress_file_exists_error(tmpdir): 73 | with suppress_file_exists_error(): 74 | Path(str(tmpdir)).mkdir() 75 | 76 | with pytest.raises(OSError): 77 | with suppress_file_exists_error(): 78 | raise OSError(errno.ENOENT, 'File not found') 79 | 80 | with suppress_file_exists_error(): 81 | raise OSError(errno.EEXIST, 'File exists.') 82 | 83 | 84 | @pytest.yield_fixture(scope='class', params=[False, True]) 85 | def mock_spk_url(request): 86 | # Mock requests so we don't hit the server 87 | with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: 88 | for data in spk_data: 89 | body = 'fake data for ' + data['kernel'] 90 | 91 | # If the file is in the 'old' directory, mock a 404 response in the 92 | # top level directory. 93 | if data['old']: 94 | rsps.add(GET, SPK_URL.format(**data), status=404) 95 | url = SPK_OLD_URL 96 | else: 97 | 98 | url = SPK_URL 99 | if request.param: 100 | rsps.add(GET, url.format(**data), body=body) 101 | else: 102 | headers = {'Content-Length': str(len(body))} 103 | rsps.add(GET, url.format(**data), body=body, adding_headers=headers) 104 | 105 | rsps.add(GET, SPK_URL.format(category='planets', kernel='tatooine'), 106 | status=404) 107 | rsps.add(GET, SPK_OLD_URL.format(category='planets', kernel='tatooine'), 108 | status=404) 109 | 110 | yield rsps 111 | 112 | 113 | @pytest.mark.usefixtures('mock_spk_url') 114 | class TestDownloadSPK: 115 | def test_download(self, tmpdir, capsys, mock_spk_url): 116 | rsps = mock_spk_url 117 | # Mock progress file attribute to prevent output to stderr (I had 118 | # trouble capturing) 119 | patch_bar = patch.object(DownloadProgressBar, 'file', io.StringIO()) 120 | patch_spinner = patch.object(DownloadProgressSpinner, 'file', io.StringIO()) 121 | with patch_bar, patch_spinner: 122 | for data in spk_data: 123 | download_spk(category=data['category'], kernel=data['kernel'], 124 | download_dir=str(tmpdir)) 125 | 126 | file = tmpdir.join(data['kernel'] + '.bsp') 127 | assert file.check() 128 | with file.open('r') as f: 129 | assert f.read() == 'fake data for ' + data['kernel'] 130 | 131 | stdout, _ = capsys.readouterr() 132 | lines = stdout.split('\n') 133 | assert lines[0].startswith('Downloading') 134 | 135 | headers = rsps.calls[-1].response.headers 136 | if 'content-length' in headers: 137 | length = headers['content-length'] 138 | assert lines[0].endswith('{}.bsp ({})'.format( 139 | data['kernel'], format_size(length))) 140 | else: 141 | assert lines[0].endswith(data['kernel'] + '.bsp') 142 | 143 | assert lines[1].endswith(data['kernel'] + '.bsp') 144 | capsys.readouterr() 145 | 146 | def test_download_failed(self, tmpdir): 147 | with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: 148 | rsps.add( 149 | GET, SPK_URL.format(category='planets', kernel='jakku'), 150 | status=500) 151 | rsps.add( 152 | GET, SPK_OLD_URL.format(category='planets', kernel='jakku'), 153 | status=500) 154 | 155 | with pytest.raises(requests.HTTPError): 156 | download_spk(category='planets', kernel='jakku', 157 | download_dir=str(tmpdir)) 158 | 159 | def test_kernel_with_extension(self, tmpdir, capsys): 160 | """Test that kernel can still be downloaded if it has its extension.""" 161 | with patch('astrodynamics.utils.web.download_file_with_progress', 162 | autospec=True) as mock_download_file_with_progress: 163 | 164 | download_spk(category='planets', kernel='de432s.bsp', 165 | download_dir=str(tmpdir)) 166 | 167 | url = SPK_URL.format(category='planets', kernel='de432s') 168 | filepath = tmpdir.join('de432s.bsp') 169 | 170 | mock_download_file_with_progress.assert_called_once_with( 171 | url, str(filepath)) 172 | 173 | mock_download_file_with_progress.reset_mock() 174 | assert not mock_download_file_with_progress.called 175 | capsys.readouterr() 176 | 177 | def test_invalid_category(self, tmpdir): 178 | with pytest.raises(InvalidCategoryError): 179 | download_spk(category='nonsense', kernel=spk_data[0]['kernel'], 180 | download_dir=str(tmpdir)) 181 | 182 | def test_kernel_not_found(self, tmpdir, capsys): 183 | with pytest.raises(KernelNotFoundError): 184 | download_spk(category='planets', kernel='tatooine', 185 | download_dir=str(tmpdir)) 186 | 187 | def test_default_dir(self, capsys): 188 | with patch('astrodynamics.utils.web.download_file_with_progress', 189 | autospec=True) as mock: 190 | url = SPK_URL.format(category='planets', kernel='de432s') 191 | download_spk('planets', 'de432s') 192 | mock.assert_called_once_with(url, str(SPK_DIR / 'de432s.bsp')) 193 | capsys.readouterr() 194 | 195 | def test_main(self): 196 | patch_download_spk = patch( 197 | 'astrodynamics.__main__.download_spk', autospec=True) 198 | with patch_download_spk as mock_download_spk: 199 | with patch('sys.argv', ['__main__.py', 'download_spk', 'planets', 200 | 'de432s', '--download-dir=spam']): 201 | main() 202 | mock_download_spk.assert_called_with( 203 | category='planets', kernel='de432s', download_dir='spam') 204 | with patch('sys.argv', ['__main__.py', 'download_spk', 'planets', 'de432s']): 205 | main() 206 | mock_download_spk.assert_called_with(category='planets', kernel='de432s', 207 | download_dir=None) 208 | 209 | mock_download_spk = Mock(side_effect=SPKDownloadError) 210 | with patch('astrodynamics.__main__.download_spk', mock_download_spk): 211 | with patch('sys.argv', ['__main__.py', 'download_spk', 'planets', 'eggs']): 212 | with pytest.raises(SystemExit): 213 | main() 214 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py27,py33,py34,py35,py2flake8,py3flake8,docs 3 | 4 | [testenv] 5 | deps= 6 | coverage 7 | wheel>=0.25.0 8 | .[test] 9 | commands= 10 | # We use parallel mode and then combine here so that coverage.py will take 11 | # the paths like .tox/py34/lib/python3.4/site-packages/astrodynamics/__init__.py 12 | # and collapse them into astrodynamics/__init__.py. 13 | coverage run --parallel-mode -m pytest {posargs} 14 | coverage combine 15 | coverage report -m 16 | 17 | [testenv:py2flake8] 18 | basepython=python2 19 | deps= 20 | flake8 21 | flake8-coding 22 | flake8-future-import 23 | pep8-naming 24 | commands= 25 | flake8 . 26 | 27 | [testenv:py3flake8] 28 | basepython=python3 29 | deps= 30 | flake8 31 | flake8-coding 32 | flake8-future-import 33 | pep8-naming 34 | commands= 35 | flake8 . 36 | 37 | [testenv:docs] 38 | basepython=python3 39 | deps= 40 | doc8 41 | pyenchant 42 | sphinx 43 | sphinx_rtd_theme 44 | sphinxcontrib-spelling 45 | commands= 46 | sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html 47 | sphinx-build -W -b spelling docs docs/_build/html 48 | doc8 docs/ 49 | 50 | [flake8] 51 | # FI50: Require 'from __future__ import division' 52 | # FI51: Require 'from __future__ import absolute_import' 53 | # FI53: Require 'from __future__ import print_function' 54 | # FI14,FI54: Make 'from __future__ import unicode_literals' optional 55 | # FI15: Forbid 'from __future__ import generator_stop' 56 | ignore=FI50,FI51,FI53,FI14,FI54,FI15 57 | accept-encodings=utf-8 58 | # Should aim for 80, but don't warn until 90. 59 | max-line-length=90 60 | min-version=2.7 61 | require-code=True 62 | 63 | [doc8] 64 | ignore-path=docs/_build/,docs/modules/constants.rst 65 | 66 | [pytest] 67 | addopts=-r s 68 | --------------------------------------------------------------------------------