├── astroplan ├── tests │ ├── __init__.py │ ├── coveragerc │ ├── test_moon.py │ ├── test_utils.py │ ├── test_target.py │ ├── test_periodic.py │ └── test_scheduling.py ├── plots │ ├── tests │ │ ├── __init__.py │ │ ├── baseline_images │ │ │ └── test_image_example.png │ │ └── test_sky.py │ ├── __init__.py │ ├── mplstyles.py │ ├── finder.py │ └── sky.py ├── setup_package.py ├── exceptions.py ├── __init__.py ├── moon.py ├── conftest.py ├── _astropy_init.py ├── target.py ├── periodic.py └── utils.py ├── docs ├── rtd-pip-requirements ├── changelog.rst ├── api.rst ├── _templates │ └── autosummary │ │ ├── base.rst │ │ ├── class.rst │ │ └── module.rst ├── references.txt ├── faq │ ├── index.rst │ ├── contribute.rst │ ├── precision.rst │ ├── terminology.rst │ └── iers.rst ├── tutorials │ ├── index.rst │ ├── periodic.rst │ └── summer_triangle.rst ├── installation.rst ├── index.rst ├── getting_started.rst ├── Makefile ├── make.bat └── conf.py ├── .gitmodules ├── licenses └── README.rst ├── LONG_DESCRIPTION.rst ├── setup.cfg ├── .gitignore ├── CITATION ├── MANIFEST.in ├── appveyor.yml ├── LICENSE.rst ├── CHANGES.rst ├── README.rst ├── setup.py ├── .travis.yml └── ez_setup.py /astroplan/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /astroplan/plots/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/rtd-pip-requirements: -------------------------------------------------------------------------------- 1 | numpy >= 1.6 2 | matplotlib 3 | Cython 4 | astropy >= 1.3 5 | pytz 6 | astroquery 7 | 8 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changelog: 2 | 3 | ********* 4 | Changelog 5 | ********* 6 | 7 | .. include:: ../CHANGES.rst 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "astropy_helpers"] 2 | path = astropy_helpers 3 | url = https://github.com/astropy/astropy-helpers.git 4 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | ************* 4 | Reference/API 5 | ************* 6 | 7 | .. automodapi:: astroplan 8 | 9 | .. automodapi:: astroplan.plots 10 | -------------------------------------------------------------------------------- /astroplan/plots/tests/baseline_images/test_image_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwcraig/astroplan/master/astroplan/plots/tests/baseline_images/test_image_example.png -------------------------------------------------------------------------------- /docs/_templates/autosummary/base.rst: -------------------------------------------------------------------------------- 1 | {% extends "autosummary_core/base.rst" %} 2 | {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #} -------------------------------------------------------------------------------- /docs/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {% extends "autosummary_core/class.rst" %} 2 | {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #} -------------------------------------------------------------------------------- /docs/_templates/autosummary/module.rst: -------------------------------------------------------------------------------- 1 | {% extends "autosummary_core/module.rst" %} 2 | {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #} -------------------------------------------------------------------------------- /licenses/README.rst: -------------------------------------------------------------------------------- 1 | Licenses 2 | ======== 3 | 4 | This directory holds license and credit information for works the astroplan 5 | package is derived from or distributes, and/or datasets. 6 | 7 | License file for the astroplan package itself is placed in the root directory 8 | of this repository. 9 | -------------------------------------------------------------------------------- /astroplan/setup_package.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | 3 | 4 | def get_package_data(): 5 | """Declare astroplan datafiles""" 6 | pdata = dict() 7 | pdata['astroplan.tests'] = ['coveragerc'] 8 | pdata['astroplan.plots.tests'] = ['baseline_images/*.png'] 9 | return pdata 10 | -------------------------------------------------------------------------------- /docs/references.txt: -------------------------------------------------------------------------------- 1 | .. _Astropy: http://astropy.org 2 | .. _Skyfield: http://rhodesmill.org/skyfield/ 3 | .. _PyEphem: http://rhodesmill.org/pyephem/ 4 | .. _Numpy: http://www.numpy.org 5 | .. _Matplotlib: http://www.matplotlib.org 6 | .. _pytz: https://pypi.python.org/pypi/pytz/ 7 | .. _pytest-mpl: https://pypi.python.org/pypi/pytest-mpl 8 | .. _astroquery: https://astroquery.readthedocs.io 9 | -------------------------------------------------------------------------------- /astroplan/plots/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | `astroplan.plots` contains functions for making plots of commonly-used 3 | quantities in observation planning (e.g., airmass vs. time), using `astroplan` 4 | and `Matplotlib`_. 5 | 6 | .. _Matplotlib: http://www.matplotlib.org 7 | """ 8 | 9 | from .time_dependent import * 10 | from .sky import * 11 | from .mplstyles import * 12 | from .finder import * 13 | -------------------------------------------------------------------------------- /docs/faq/index.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | **** 4 | FAQ 5 | **** 6 | 7 | We've assembled some caveats and gotchas in this Frequently Asked 8 | Questions section. If your question isn't addressed here or on the astroplan 9 | `Issues `_ page, post a 10 | new issue. 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | 15 | iers 16 | precision 17 | terminology 18 | contribute -------------------------------------------------------------------------------- /LONG_DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | * Code: https://github.com/astropy/astroplan 2 | * Docs: https://astroplan.readthedocs.io/ 3 | 4 | **astroplan** is an open source (BSD licensed) observation planning package for 5 | astronomers that can help you plan for everything but the clouds. 6 | 7 | It is an in-development `Astropy `__ 8 | `affiliated package `__ that 9 | seeks to make your life as an observational astronomer a little less 10 | infuriating. 11 | 12 | Contributions welcome! 13 | -------------------------------------------------------------------------------- /docs/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | ********* 4 | Tutorials 5 | ********* 6 | 7 | If you'd like to see how `astroplan` is used in the context of real observation 8 | planning examples, this is the page for you! 9 | 10 | Can't find what you're looking for here? Check out our :ref:`api`. 11 | 12 | Is there something you think we should add here? Consider 13 | `posting an issue `_ on 14 | GitHub asking for it... Or better yet, write it yourself, and become a project 15 | contributor! 16 | 17 | We currently have the following tutorials: 18 | 19 | .. toctree:: 20 | :maxdepth: 1 21 | 22 | summer_triangle 23 | plots 24 | periodic 25 | constraints 26 | scheduling -------------------------------------------------------------------------------- /astroplan/plots/tests/test_sky.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | import pytest 5 | 6 | try: 7 | import matplotlib 8 | HAS_MATPLOTLIB = True 9 | except ImportError: 10 | HAS_MATPLOTLIB = False 11 | 12 | 13 | # TODO: replace this with actual plot checks once these 14 | # issues are resolved: 15 | # https://github.com/astropy/astroplan/issues/65 16 | # https://github.com/astropy/astroplan/issues/74 17 | @pytest.mark.skipif('not HAS_MATPLOTLIB') 18 | @pytest.mark.mpl_image_compare 19 | def test_image_example(): 20 | import matplotlib.pyplot as plt 21 | fig = plt.figure() 22 | ax = fig.add_subplot(1, 1, 1) 23 | ax.plot([1, 2, 3]) 24 | return fig 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = docs 3 | build-dir = docs/_build 4 | all_files = 1 5 | 6 | [upload_docs] 7 | upload-dir = docs/_build/html 8 | show-response = 1 9 | 10 | [tool:pytest] 11 | minversion = 3.0 12 | norecursedirs = build docs/_build 13 | doctest_plus = enabled 14 | 15 | [ah_bootstrap] 16 | auto_use = True 17 | 18 | [metadata] 19 | package_name = astroplan 20 | description = Observation planning package for astronomers 21 | long_description = Observation planning package for astronomers 22 | author = Astroplan developers 23 | author_email = astropy-dev@googlegroups.com 24 | license = BSD 25 | url = https://github.com/astropy/astroplan 26 | edit_on_github = False 27 | github_project = astropy/astroplan 28 | 29 | [entry_points] 30 | 31 | [flake8] 32 | exclude = _astropy_init.py,extern 33 | 34 | [pycodestyle] 35 | exclude = _astropy_init.py,extern -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.py[cod] 3 | *.a 4 | *.o 5 | *.so 6 | __pycache__ 7 | 8 | # Ignore .c files by default to avoid including generated code. If you want to 9 | # add a non-generated .c extension, use `git add -f filename.c`. 10 | *.c 11 | 12 | # Other generated files 13 | */version.py 14 | */cython_version.py 15 | htmlcov 16 | .coverage 17 | MANIFEST 18 | .ipynb_checkpoints 19 | 20 | # Sphinx 21 | docs/api 22 | docs/_build 23 | 24 | # Eclipse editor project files 25 | .project 26 | .pydevproject 27 | .settings 28 | 29 | # Pycharm editor project files 30 | .idea 31 | 32 | # Packages/installer info 33 | *.egg 34 | *.egg-info 35 | dist 36 | build 37 | eggs 38 | parts 39 | bin 40 | var 41 | sdist 42 | develop-eggs 43 | .installed.cfg 44 | distribute-*.tar.gz 45 | 46 | # Other 47 | .cache 48 | .tox 49 | .*.sw[op] 50 | *~ 51 | 52 | # Mac OSX 53 | .DS_Store 54 | -------------------------------------------------------------------------------- /astroplan/tests/coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = {packagename} 3 | omit = 4 | {packagename}/_astropy_init* 5 | {packagename}/conftest* 6 | {packagename}/cython_version* 7 | {packagename}/setup_package* 8 | {packagename}/*/setup_package* 9 | {packagename}/*/*/setup_package* 10 | {packagename}/tests/* 11 | {packagename}/*/tests/* 12 | {packagename}/*/*/tests/* 13 | {packagename}/version* 14 | 15 | [report] 16 | exclude_lines = 17 | # Have to re-enable the standard pragma 18 | pragma: no cover 19 | 20 | # Don't complain about packages we have installed 21 | except ImportError 22 | 23 | # Don't complain if tests don't hit assertions 24 | raise AssertionError 25 | raise NotImplementedError 26 | 27 | # Don't complain about script hooks 28 | def main\(.*\): 29 | 30 | # Ignore branches that don't pertain to this version of Python 31 | pragma: py{ignore_python_version} -------------------------------------------------------------------------------- /CITATION: -------------------------------------------------------------------------------- 1 | If you use astroplan in your work, please cite this paper: 2 | 3 | http://adsabs.harvard.edu/abs/2018AJ....155..128M 4 | 5 | The recommended BibTeX entry for the above citation is: 6 | 7 | @ARTICLE{astroplan2018, 8 | author = {{Morris}, B.~M. and {Tollerud}, E. and {Sip{\H o}cz}, B. and 9 | {Deil}, C. and {Douglas}, S.~T. and {Berlanga Medina}, J. and 10 | {Vyhmeister}, K. and {Smith}, T.~R. and {Littlefair}, S. and 11 | {Price-Whelan}, A.~M. and {Gee}, W.~T. and {Jeschke}, E.}, 12 | title = "{astroplan: An Open Source Observation Planning Package in Python}", 13 | journal = {\aj}, 14 | archivePrefix = "arXiv", 15 | eprint = {1712.09631}, 16 | primaryClass = "astro-ph.IM", 17 | keywords = {methods: numerical, methods: observational }, 18 | year = 2018, 19 | month = mar, 20 | volume = 155, 21 | eid = {128}, 22 | pages = {128}, 23 | doi = {10.3847/1538-3881/aaa47e}, 24 | adsurl = {http://adsabs.harvard.edu/abs/2018AJ....155..128M}, 25 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 26 | } 27 | -------------------------------------------------------------------------------- /astroplan/exceptions.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from astropy.utils.exceptions import AstropyWarning 6 | 7 | __all__ = ["TargetAlwaysUpWarning", "TargetNeverUpWarning", 8 | "OldEarthOrientationDataWarning", "PlotWarning", 9 | "PlotBelowHorizonWarning", "AstroplanWarning"] 10 | 11 | 12 | class AstroplanWarning(AstropyWarning): 13 | """Superclass for warnings used by astroplan""" 14 | 15 | 16 | class TargetAlwaysUpWarning(AstroplanWarning): 17 | """Target is circumpolar""" 18 | pass 19 | 20 | 21 | class TargetNeverUpWarning(AstroplanWarning): 22 | """Target never rises above horizon""" 23 | pass 24 | 25 | 26 | class OldEarthOrientationDataWarning(AstroplanWarning): 27 | """Using old Earth rotation data from IERS""" 28 | pass 29 | 30 | 31 | class PlotWarning(AstroplanWarning): 32 | """Warnings dealing with the plotting aspects of astroplan""" 33 | pass 34 | 35 | 36 | class PlotBelowHorizonWarning(PlotWarning): 37 | """Warning for when something is hidden on a plot because it's below the horizon""" 38 | pass 39 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.rst 2 | include README.rst 3 | include CHANGES.rst 4 | 5 | include ez_setup.py 6 | include ah_bootstrap.py 7 | include setup.cfg 8 | 9 | recursive-include *.pyx *.c *.pxd 10 | recursive-include docs * 11 | recursive-include licenses * 12 | recursive-include cextern * 13 | recursive-include scripts * 14 | 15 | prune build 16 | prune docs/_build 17 | prune docs/api 18 | 19 | # the next few stanzas are for astropy_helpers. It's derived from the 20 | # astropy_helpers/MANIFEST.in, but requires additional includes for the actual 21 | # package directory and egg-info. 22 | 23 | include astropy_helpers/README.rst 24 | include astropy_helpers/CHANGES.rst 25 | include astropy_helpers/LICENSE.rst 26 | recursive-include astropy_helpers/licenses * 27 | 28 | include astropy_helpers/ez_setup.py 29 | include astropy_helpers/ah_bootstrap.py 30 | 31 | recursive-include astropy_helpers/astropy_helpers *.py *.pyx *.c *.h 32 | recursive-include astropy_helpers/astropy_helpers.egg-info * 33 | # include the sphinx stuff with "*" because there are css/html/rst/etc. 34 | recursive-include astropy_helpers/astropy_helpers/sphinx * 35 | 36 | prune astropy_helpers/build 37 | prune astropy_helpers/astropy_helpers/tests 38 | 39 | 40 | global-exclude *.pyc *.o 41 | -------------------------------------------------------------------------------- /astroplan/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | """ 3 | astroplan is an open source (BSD licensed) observation planning package for 4 | astronomers that can help you plan for everything but the clouds. 5 | 6 | It is an in-development `Astropy `__ 7 | `affiliated package `__ that 8 | seeks to make your life as an observational astronomer a little less 9 | infuriating. 10 | 11 | * Code: https://github.com/astropy/astroplan 12 | * Docs: https://astroplan.readthedocs.io/ 13 | """ 14 | 15 | # Affiliated packages may add whatever they like to this file, but 16 | # should keep this content at the top. 17 | # ---------------------------------------------------------------------------- 18 | from ._astropy_init import * 19 | # ---------------------------------------------------------------------------- 20 | 21 | # For egg_info test builds to pass, put package imports here. 22 | if not _ASTROPY_SETUP_: 23 | from .utils import * 24 | from .observer import * 25 | from .target import * 26 | from .exceptions import * 27 | from .moon import * 28 | from .constraints import * 29 | from .scheduling import * 30 | from .periodic import * 31 | 32 | get_IERS_A_or_workaround() 33 | -------------------------------------------------------------------------------- /docs/faq/contribute.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _contribute: 3 | 4 | *************************** 5 | Getting Help & Contributing 6 | *************************** 7 | 8 | Getting Help 9 | ============ 10 | 11 | If your questions are not addressed in the docs, you can reach out to us for 12 | help at the 13 | `astropy-dev mailing list `_. 14 | 15 | 16 | Contributing to `astroplan` 17 | =========================== 18 | 19 | Contributions to improve, expand and fix `astroplan` are welcome! As part of the 20 | `Astropy`_ ecosystem, we recommend the following resources for getting started 21 | on your first contributions to astroplan: 22 | 23 | * `How to make a code contribution `_ 24 | 25 | * `Comprehensive Coding Guidelines `_ 26 | 27 | * `Astropy Developer Documentation `_ 28 | 29 | .. note:: 30 | `astroplan` is a very young project and we welcome new features. If you are 31 | interested in contributing, contact us via 32 | `GitHub `_ or the 33 | `astropy-dev mailing list `_ 34 | to check what's already in progress and what still needs to be started. 35 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor.com is a Continuous Integration service to build and run tests under 2 | # Windows 3 | 4 | environment: 5 | 6 | global: 7 | PYTHON: "C:\\conda" 8 | MINICONDA_VERSION: "latest" 9 | CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd" 10 | PYTHON_ARCH: "64" # needs to be set for CMD_IN_ENV to succeed. If a mix 11 | # of 32 bit and 64 bit builds are needed, move this 12 | # to the matrix section. 13 | CONDA_DEPENDENCIES: "pytz matplotlib" 14 | PIP_DEPENDENCIES: "pyephem pytest-mpl" 15 | CONDA_CHANNELS: "astropy" 16 | ASTROPY_USE_SYSTEM_PYTEST: 1 17 | 18 | matrix: 19 | 20 | # Note that we don't support Python 2.6 in astroplan, 21 | # so we test Python 2.7 22 | 23 | - PYTHON_VERSION: "2.7" 24 | NUMPY_VERSION: "stable" 25 | ASTROPY_VERSION: "stable" 26 | - PYTHON_VERSION: "3.6" 27 | NUMPY_VERSION: "stable" 28 | ASTROPY_VERSION: "stable" 29 | 30 | platform: 31 | -x64 32 | 33 | install: 34 | - "git clone git://github.com/astropy/ci-helpers.git" 35 | - "powershell ci-helpers/appveyor/install-miniconda.ps1" 36 | - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 37 | - "activate test" 38 | 39 | # Not a .NET project, we build astroplan in the install step instead 40 | build: false 41 | 42 | test_script: 43 | - "%CMD_IN_ENV% python setup.py test" 44 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017, Astroplan Developers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | * Neither the name of the Astropy Team nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /astroplan/tests/test_moon.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..observer import Observer 6 | from astropy.time import Time 7 | from astropy.coordinates import EarthLocation 8 | import astropy.units as u 9 | from numpy.testing import assert_allclose 10 | 11 | 12 | def test_illumination(): 13 | time = Time(['1990-01-01 00:00:00', '1990-03-01 06:00:00', 14 | '1990-06-01 12:00:00', '1990-11-01 18:00:00']) 15 | location = EarthLocation.from_geodetic(-155*u.deg, 19*u.deg, 0*u.m) 16 | obs = Observer(location) 17 | # Get illumination via time 18 | illumination1 = obs.moon_illumination(time) 19 | 20 | # Run print_pyephem_illumination() for PyEphem's solution 21 | pyephem_illumination = [0.15475513880925418, 0.19484233284757257, 22 | 0.6170840254669668, 0.9780219372563843] 23 | 24 | assert_allclose(illumination1, pyephem_illumination, atol=0.05) 25 | 26 | 27 | def print_pyephem_illumination(): 28 | """ 29 | To run, use: 30 | python -c "from astroplan.tests.test_moon import print_pyephem_illumination as f; f()" 31 | """ 32 | time = Time(['1990-01-01 00:00:00', '1990-03-01 06:00:00', 33 | '1990-06-01 12:00:00', '1990-11-01 18:00:00']) 34 | location = EarthLocation.from_geodetic(-155*u.deg, 19*u.deg, 0*u.m) 35 | 36 | import ephem 37 | moon = ephem.Moon() 38 | pe_obs = ephem.Observer() 39 | pe_obs.lat = location.lat.to_string(u.deg, sep=':') 40 | pe_obs.lon = location.lon.to_string(u.deg, sep=':') 41 | pe_obs.elevation = location.height.to(u.m).value 42 | illuminations = [] 43 | for t in time: 44 | pe_obs.date = t.datetime 45 | moon.compute(pe_obs) 46 | illuminations.append(moon.moon_phase) 47 | print(illuminations) 48 | -------------------------------------------------------------------------------- /astroplan/moon.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | """ 3 | This version of the `moon` module calculates lunar phase angle for a geocentric 4 | """ 5 | 6 | from __future__ import (absolute_import, division, print_function, 7 | unicode_literals) 8 | 9 | # Third-party 10 | import numpy as np 11 | from astropy.coordinates import get_moon, get_sun 12 | 13 | __all__ = ["moon_phase_angle", "moon_illumination"] 14 | 15 | 16 | def moon_phase_angle(time, ephemeris=None): 17 | """ 18 | Calculate lunar orbital phase in radians. 19 | 20 | Parameters 21 | ---------- 22 | time : `~astropy.time.Time` 23 | Time of observation 24 | 25 | ephemeris : str, optional 26 | Ephemeris to use. If not given, use the one set with 27 | `~astropy.coordinates.solar_system_ephemeris` (which is 28 | set to 'builtin' by default). 29 | 30 | Returns 31 | ------- 32 | i : float 33 | Phase angle of the moon [radians] 34 | """ 35 | # TODO: cache these sun/moon SkyCoord objects 36 | 37 | sun = get_sun(time) 38 | moon = get_moon(time, ephemeris=ephemeris) 39 | elongation = sun.separation(moon) 40 | return np.arctan2(sun.distance*np.sin(elongation), 41 | moon.distance - sun.distance*np.cos(elongation)) 42 | 43 | 44 | def moon_illumination(time, ephemeris=None): 45 | """ 46 | Calculate fraction of the moon illuminated. 47 | 48 | Parameters 49 | ---------- 50 | time : `~astropy.time.Time` 51 | Time of observation 52 | 53 | ephemeris : str, optional 54 | Ephemeris to use. If not given, use the one set with 55 | `~astropy.coordinates.solar_system_ephemeris` (which is 56 | set to 'builtin' by default). 57 | 58 | Returns 59 | ------- 60 | k : float 61 | Fraction of moon illuminated 62 | """ 63 | i = moon_phase_angle(time, ephemeris=ephemeris) 64 | k = (1 + np.cos(i))/2.0 65 | return k.value 66 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. include:: references.txt 2 | 3 | .. _installation: 4 | 5 | ************ 6 | Installation 7 | ************ 8 | 9 | Requirements 10 | ============ 11 | 12 | **astroplan** works on Linux, Mac OS X and Windows. 13 | It requires Python 2.7 or 3.5+ (2.6 and 3.2 or earlier are not 14 | supported, 3.3 and 3.4 may work) as well as the following packages: 15 | 16 | * `Numpy`_ (1.10 or later) 17 | * `Astropy`_ (v1.3 or later) 18 | * `pytz`_ 19 | 20 | Optional packages: 21 | 22 | * `Matplotlib`_ 23 | * `astroquery`_ 24 | 25 | First-time Python users may want to consider an all-in-one Python installation 26 | package, such as the `Anaconda Python Distribution 27 | `_ which provides all of the above dependencies. 28 | 29 | Installation 30 | ============ 31 | 32 | You can install the stable version of astroplan from PyPI with:: 33 | 34 | pip install astroplan 35 | 36 | or from anaconda:: 37 | 38 | conda install -c astropy astroplan 39 | 40 | Alternatively, you can install the latest developer version of astroplan by 41 | cloning the git repository:: 42 | 43 | git clone https://github.com/astropy/astroplan 44 | 45 | ...then installing the package with:: 46 | 47 | cd astroplan 48 | python setup.py install 49 | 50 | Testing 51 | ======= 52 | 53 | If you want to check that all the tests are running correctly with your Python 54 | configuration, start up python, and type:: 55 | 56 | import astroplan 57 | astroplan.test() 58 | 59 | If there are no errors, you are good to go! 60 | 61 | .. note:: 62 | If you want to run the tests that access the internet, you'll need to 63 | replace the last line above with ``astroplan.test(remote_data=True)`` and 64 | have an active connection to the internet. Also, if you want the tests 65 | that check plotting to work, you need `Matplotlib`_ and `pytest-mpl`_. 66 | 67 | More 68 | ==== 69 | 70 | astroplan follows `Astropy`_'s guidelines for affiliated packages--installation 71 | and testing for the two are quite similar! Please see Astropy's 72 | `installation page `_ 73 | for more information. 74 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: references.txt 2 | 3 | .. _astroplan: 4 | 5 | ********************************** 6 | Observation Planning (`astroplan`) 7 | ********************************** 8 | 9 | What is astroplan? 10 | ================== 11 | 12 | **astroplan** is an open source Python package to help astronomers plan 13 | observations. 14 | 15 | The goal of astroplan is to make a flexible toolbox for observation planning and 16 | scheduling. When complete, the goal is to be easy for Python beginners and new 17 | observers to to pick up, but powerful enough for observatories preparing nightly 18 | and long-term schedules. 19 | 20 | 21 | Features: 22 | 23 | * Calculate rise/set/meridian transit times, alt/az positions for targets at 24 | observatories anywhere on Earth 25 | * Built-in plotting convenience functions for standard observation planning 26 | plots (airmass, parallactic angle, sky maps). 27 | * Determining observability of sets of targets given an arbitrary set of 28 | constraints (i.e., altitude, airmass, moon separation/illumination, etc.). 29 | * `Astropy`_ powered! 30 | 31 | Links 32 | ===== 33 | 34 | * `Source code `_ 35 | * `Docs `_ 36 | * `Issues `_ 37 | 38 | License: BSD-3 39 | 40 | .. _astroplan_docs: 41 | 42 | General Documentation 43 | ===================== 44 | 45 | .. toctree:: 46 | :maxdepth: 2 47 | 48 | installation 49 | getting_started 50 | tutorials/index 51 | faq/index 52 | api 53 | changelog 54 | 55 | .. _astroplan_authors: 56 | 57 | Authors 58 | ======= 59 | 60 | Maintainers 61 | ----------- 62 | * `Jazmin Berlanga Medina, including contributions from Google Summer of Code 2015 `_ 63 | * `Brett Morris, including contributions from Google Summer of Code 2015 `_ 64 | 65 | Contributors 66 | ------------ 67 | * Christoph Deil 68 | * Stephanie Douglas 69 | * Eric Jeschke 70 | * Adrian Price-Whelan 71 | * Erik Tollerud 72 | * Brigitta Sipocz 73 | * Karl Vyhmeister 74 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | 0.5 (unreleased) 2 | ---------------- 3 | 4 | - ``observability_table`` now accepts scalars as ``time_range`` arguments, and 5 | gives ``'time observable'`` in this case in the resulting table. [#350] 6 | 7 | 0.4 (2017-10-23) 8 | ---------------- 9 | 10 | - Added new ``eclipsing`` module for eclipsing binaries and transiting 11 | exoplanets [#315] 12 | 13 | - Fixes for compatibility with astropy Quantity object updates [#336] 14 | 15 | - Better PEP8 compatibility [#335] 16 | 17 | - Using travis build stages [#330] 18 | 19 | 0.3 (2017-09-02) 20 | ---------------- 21 | 22 | - ``Observer.altaz`` and ``Constraint.__call__`` no longer returns an (MxN) grid 23 | of results when called with M ``target``s and N ``times``. Instead, we attempt 24 | to broadcast the time and target shapes, and an error is raised if this is not 25 | possible. This change breaks backwards compatibility but an optional argument 26 | ``grid_times_targets`` has been added to these methods. If set to True, 27 | the old behaviour is recovered. All ``Observer`` methods for which it is 28 | relevant have this optional argument. 29 | 30 | - Updates for compatibility with astropy v2.0 coordinates implementation 31 | [#311], updates to astropy-helpers [#309], fix pytest version [#312] 32 | 33 | 0.2.1 (2016-04-27) 34 | ------------------ 35 | 36 | - Internal changes to the way calculations are done means that astropy>=1.3 is required [#285] 37 | 38 | - Fixed bug when scheduling block list is empty [#298] 39 | 40 | - Fixed bug in Transitioner object when no transition needed [#295] 41 | 42 | - Update to astropy-helpers 1.3.1 [#294] and compatibility fixes for astropy 1.3 [#283] 43 | 44 | 45 | 0.2 (2016-09-20) 46 | ---------------- 47 | 48 | - Fixed bug arising from changes to distutils.ConfigParser [#177, #187, #191] 49 | 50 | - Removed the sites module from astroplan, since it was ported to astropy [#168] 51 | 52 | - Removed dependence on PyEphem, now using jplephem for the solar system 53 | ephemeris [#167] 54 | 55 | - New API for scheduling observations (still in development) 56 | 57 | - New ``plot_finder_image`` function makes quick finder charts [#115] 58 | 59 | - Updates to astropy helpers and the package template [#177, #180] 60 | -------------------------------------------------------------------------------- /docs/faq/precision.rst: -------------------------------------------------------------------------------- 1 | .. doctest-skip-all 2 | 3 | .. _precision: 4 | 5 | ************************************************************** 6 | Why is my target above/below the horizon at the rise/set time? 7 | ************************************************************** 8 | 9 | Rise/set/meridian transit calculations in `astroplan` are designed to be fast 10 | while achieving a precision comparable to what can be predicted given the 11 | affects of the changing atmosphere. As a result, there may be some 12 | counter-intuitive behavior in `astroplan` methods like 13 | `astroplan.Observer.target_rise_time`, `astroplan.Observer.target_set_time` and 14 | `astroplan.Observer.target_meridian_transit_time`, that can lead to small 15 | changes in the numerical values of these computed timed (of order seconds). 16 | 17 | For example, to calculate the rise time of Sirius, you might write:: 18 | 19 | from astroplan import Observer, FixedTarget 20 | from astropy.time import Time 21 | 22 | # Set up observer, target, and time 23 | keck = Observer.at_site("Keck") 24 | sirius = FixedTarget.from_name("Sirius") 25 | time = Time('2010-05-11 06:00:00') 26 | 27 | # Find rise time of Sirius at Keck nearest to `time` 28 | rise_time = keck.target_rise_time(time, sirius) 29 | 30 | You might expect the altitude of Sirius to be zero degrees at ``rise_time``, 31 | i.e. Sirius will be on the horizon, but this is not the case:: 32 | 33 | >>> altitude_at_rise = keck.altaz(rise_time, sirius).alt 34 | >>> print(altitude_at_rise.to('arcsec')) 35 | 2.70185arcsec 36 | 37 | The altitude that you compute on your machine may be different from the number 38 | above by a small amount – for a detailed explanation on where the difference 39 | arises from, see :doc:`iers`. The rise and set time methods use the following 40 | approximation: 41 | 42 | * A time series of altitudes for the target is computed at times near ``time`` 43 | 44 | * The two times when the target is nearest to the horizon are identified, and a 45 | linear interpolation is done between those times to find the horizon-crossing 46 | 47 | This method has a precision of a few arcseconds, so your targets may be slightly 48 | above or below the horizon at their rise or set times. 49 | -------------------------------------------------------------------------------- /astroplan/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains functions that configure py.test like astropy but with 3 | additions for astroplan. Py.test looks for specially-named functions 4 | (like ``pytest_configure``) and uses those to configure itself. 5 | 6 | Here, we want to keep the behavior of astropy while *adding* more for astroplan. 7 | To do that, in the functions below, we first invoke the functions from astropy, 8 | and then after that do things specific to astroplan. But we also want astropy 9 | functionality for any functions we have *not* overriden, so that's why the 10 | ``import *`` happens at the top. 11 | """ 12 | from astropy.tests.pytest_plugins import * 13 | 14 | # also save a copy of the astropy hooks so we can use them below when 15 | # overriding 16 | from astropy.tests import pytest_plugins as astropy_pytest_plugins 17 | 18 | import warnings 19 | from .utils import _mock_remote_data, _unmock_remote_data 20 | from .exceptions import AstroplanWarning 21 | 22 | import os 23 | 24 | # This is to figure out the affiliated package version, rather than 25 | # using Astropy's 26 | try: 27 | from .version import version 28 | except ImportError: 29 | version = 'dev' 30 | 31 | packagename = os.path.basename(os.path.dirname(__file__)) 32 | TESTED_VERSIONS[packagename] = version 33 | 34 | 35 | # Comment out this line to avoid deprecation warnings being raised as 36 | # exceptions 37 | enable_deprecations_as_exceptions() 38 | 39 | # Define list of packages for which to display version numbers in the test log 40 | try: 41 | PYTEST_HEADER_MODULES['Astropy'] = 'astropy' 42 | PYTEST_HEADER_MODULES['pytz'] = 'pytz' 43 | PYTEST_HEADER_MODULES['pyephem'] = 'ephem' 44 | PYTEST_HEADER_MODULES['matplotlib'] = 'matplotlib' 45 | PYTEST_HEADER_MODULES['pytest-mpl'] = 'pytest_mpl' 46 | del PYTEST_HEADER_MODULES['h5py'] 47 | except KeyError: 48 | pass 49 | 50 | 51 | def pytest_configure(config): 52 | if hasattr(astropy_pytest_plugins, 'pytest_configure'): 53 | # sure ought to be true right now, but always possible it will change in 54 | # future versions of astropy 55 | astropy_pytest_plugins.pytest_configure(config) 56 | 57 | # make sure astroplan warnings always appear so we can test when they show 58 | # up 59 | warnings.simplefilter('always', category=AstroplanWarning) 60 | 61 | # Activate remote data mocking if the `--remote-data` option isn't used: 62 | if (not config.getoption('remote_data') or 63 | config.getvalue('remote_data') == 'none'): 64 | _mock_remote_data() 65 | -------------------------------------------------------------------------------- /astroplan/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import numpy as np 6 | import pytest 7 | 8 | from astropy import units as u 9 | from astropy.time import Time 10 | from astropy.tests.helper import remote_data 11 | from astropy.utils.data import clear_download_cache 12 | from astropy.utils import iers 13 | 14 | from ..utils import (download_IERS_A, IERS_A_in_cache, 15 | get_IERS_A_or_workaround, BACKUP_Time_get_delta_ut1_utc, 16 | stride_array, time_grid_from_range) 17 | 18 | from ..exceptions import OldEarthOrientationDataWarning 19 | 20 | 21 | @remote_data 22 | def test_iers_download(monkeypatch, recwarn): 23 | # the monkeypatch is here to undo the changes that importing astroplan does 24 | # if the IERS A tables already exist 25 | if IERS_A_in_cache(): 26 | clear_download_cache(iers.IERS_A_URL) 27 | 28 | monkeypatch.setattr(iers.IERS, 'iers_table', None) 29 | monkeypatch.setattr(iers.IERS_A, 'iers_table', None) 30 | monkeypatch.setattr(Time, '_get_delta_ut1_utc', BACKUP_Time_get_delta_ut1_utc) 31 | 32 | # now make sure the state is what it should be given the above changes 33 | get_IERS_A_or_workaround() 34 | 35 | # first make sure a future time gives a warning with IERS A missing 36 | nowplusoneyear = Time.now() + 1*u.year 37 | nowplusoneyear.ut1 38 | recwarn.pop(OldEarthOrientationDataWarning) 39 | 40 | download_IERS_A() 41 | 42 | # now test that it actually works post-IERS A download: 43 | nowplusoneyear.ut1 44 | 45 | 46 | arr10 = np.arange(10) 47 | 48 | 49 | def test_stride_array(): 50 | stride10by5 = stride_array(arr10, 5) 51 | assert len(stride10by5) == 6 52 | 53 | 54 | def test_stride_floats(): 55 | arr_float = np.asarray(arr10, float) 56 | stride10by3 = stride_array(arr_float, 3) 57 | 58 | 59 | def test_time_grid_from_range(): 60 | times2 = ['2010-01-01 00:00:00', '2010-01-01 01:00:10'] 61 | times3 = ['2010-01-01 00:00:00', '2010-01-01 01:00:00', 62 | '2010-01-01 03:00:00'] 63 | tgrid = time_grid_from_range(Time(times2)) 64 | assert len(tgrid) == 3 65 | assert np.all(tgrid.iso == Time(['2010-01-01 00:00:00', 66 | '2010-01-01 00:30:00', 67 | '2010-01-01 01:00:00'])) 68 | 69 | with pytest.raises(ValueError): 70 | time_grid_from_range(Time(times3)) 71 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | astroplan 2 | ========= 3 | 4 | Observation planning package for astronomers 5 | 6 | * Code: https://github.com/astropy/astroplan 7 | * Docs: https://astroplan.readthedocs.io/ 8 | * License: BSD-3 9 | 10 | .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat 11 | :target: http://www.astropy.org/ 12 | 13 | .. image:: http://img.shields.io/pypi/v/astroplan.svg?text=version 14 | :target: https://pypi.python.org/pypi/astroplan/ 15 | :alt: Latest release 16 | 17 | .. image:: http://img.shields.io/badge/arXiv-1709.03913-red.svg?style=flat 18 | :target: https://arxiv.org/abs/1712.09631 19 | :alt: arXiv paper 20 | 21 | Status shields 22 | ++++++++++++++ 23 | 24 | (mostly useful for developers) 25 | 26 | .. image:: http://img.shields.io/travis/astropy/astroplan.svg?branch=master 27 | :target: https://travis-ci.org/astropy/astroplan 28 | :alt: Travis Status 29 | 30 | .. image:: https://ci.appveyor.com/api/projects/status/pff1o3vx446pav83/branch/master?svg=true 31 | :target: https://ci.appveyor.com/project/Astropy/astroplan/branch/master 32 | :alt: Appveyor Status 33 | 34 | .. image:: https://img.shields.io/coveralls/astropy/astroplan.svg 35 | :target: https://coveralls.io/r/astropy/astroplan 36 | :alt: Code Coverage 37 | 38 | .. image:: https://readthedocs.org/projects/astroplan/badge/?version=stable 39 | :target: http://astroplan.readthedocs.io/en/stable/ 40 | :alt: Stable Documentation Status 41 | 42 | .. image:: https://readthedocs.org/projects/astroplan/badge/?version=latest 43 | :target: http://astroplan.readthedocs.io/en/latest/ 44 | :alt: Latest Documentation Status 45 | 46 | Attribution 47 | +++++++++++ 48 | 49 | If you use astroplan in your work, please cite `Morris et al. 2018 `_: 50 | 51 | .. code :: 52 | 53 | @ARTICLE{astroplan2018, 54 | author = {{Morris}, B.~M. and {Tollerud}, E. and {Sip{\H o}cz}, B. and 55 | {Deil}, C. and {Douglas}, S.~T. and {Berlanga Medina}, J. and 56 | {Vyhmeister}, K. and {Smith}, T.~R. and {Littlefair}, S. and 57 | {Price-Whelan}, A.~M. and {Gee}, W.~T. and {Jeschke}, E.}, 58 | title = "{astroplan: An Open Source Observation Planning Package in Python}", 59 | journal = {\aj}, 60 | archivePrefix = "arXiv", 61 | eprint = {1712.09631}, 62 | primaryClass = "astro-ph.IM", 63 | keywords = {methods: numerical, methods: observational }, 64 | year = 2018, 65 | month = mar, 66 | volume = 155, 67 | eid = {128}, 68 | pages = {128}, 69 | doi = {10.3847/1538-3881/aaa47e}, 70 | adsurl = {http://adsabs.harvard.edu/abs/2018AJ....155..128M}, 71 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/faq/terminology.rst: -------------------------------------------------------------------------------- 1 | .. _terminology: 2 | 3 | *********** 4 | Terminology 5 | *********** 6 | 7 | Scheduling 8 | ========== 9 | 10 | Scheduling observations is a common task in astronomy, but there is a lack of 11 | formal terminology. Here we define the terminology as it will be used in astroplan 12 | for scheduling tasks. This is for consistency within the code and the documentation. 13 | 14 | The terms used are as follows: 15 | 16 | * **observing block** (**OB**): an observation of a target for an amount of time 17 | and a particular instrument configuration. 18 | * **priority**: number assigned to the **observing block** by the user that 19 | defines its precedence within the set of blocks to be scheduled. Should probably 20 | also be on a 0->1 (0=no good, 1=good) scale, or rescaled within the scheduler 21 | * **rank**: like a **priority**, but defined for a specific set of **OB**s, often set 22 | by an agent other than the observer. (e.g. a proposal is **ranked** by a time 23 | allocation committee (TAC)) 24 | * **constraint**: sets limits and can yield **scores** based on inputs. Currently 25 | only boolean scores are implemented in the code. 26 | * **score**: the value returned by evaluating a **constraint** or **constraints**. Can be 27 | boolean or floats between 0 and 1 inclusive (0=no good, 1=good), with a flag in the 28 | ``Constraint`` call that selects which is used. Can be combined by a **scorekeeper** 29 | into a cumulative score for an **observing block** given a start time. 30 | * **scorekeeper**: assigns a cumulative score to the **OB** based on some function that 31 | would be applied to all the individual **scores** (result should be in (0, 1)) 32 | * **scheduler**: the entity that consumes the results of the **scorekeeper** and the 33 | **observing blocks** and produces a schedule 34 | * **Weights** (“user defined”?): preferences for which constraint's **scores** matter most 35 | (e.g. I care more about getting dark time than getting a low airmass). **weights** can be 36 | floats between 0 and 1 inclusive (0=not important, 1 = important) 37 | 38 | 39 | This is a list of possible schedulers, none are implemented yet. Once implemented they 40 | will have different methods for creating their schedule. Below is a list of ideas for 41 | the scheduler descriptors and the related scheduler would work. Each scheduler will be 42 | able to be called with ``DescriptorScheduler(args)`` using the descriptor defined below. 43 | 44 | * **Sequential**: starts from the beginning of the time range and schedules the **OB** 45 | with the best **score**, that hasn't already been scheduled, for that time. 46 | * **Priority**: starts from the highest **priority** **OB** and schedules it at the time 47 | where it has its highest **score**. Then schedules the next-highest priority without 48 | overlapping blocks -------------------------------------------------------------------------------- /astroplan/plots/mplstyles.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import copy 6 | 7 | from astropy.visualization import astropy_mpl_style 8 | from astropy.utils import minversion 9 | 10 | # This returns False if matplotlib cannot be imported 11 | MATPLOTLIB_GE_1_5 = minversion('matplotlib', '1.5') 12 | 13 | 14 | __all__ = ['light_style_sheet', 'dark_style_sheet', 15 | 'available_style_sheets'] 16 | 17 | available_style_sheets = ["light_style_sheet", "dark_style_sheet"] 18 | """Matplotlib style sheets available within astroplan.""" 19 | 20 | light_style_sheet = copy.deepcopy(astropy_mpl_style) 21 | 22 | # One update specifically for astroplan.plots: raise bottom up from default 0.1 23 | light_style_sheet.update({'figure.subplot.bottom': 0.15}) 24 | 25 | # Define dark style sheet starting from the astropy_mpl_style sheet 26 | dark_style_sheet = { 27 | 28 | # Lines 29 | 'lines.linewidth': 1.7, 30 | 'lines.antialiased': True, 31 | 32 | # Patches 33 | 'patch.linewidth': 1.0, 34 | 'patch.facecolor': 'k', 35 | 'patch.edgecolor': 'k', 36 | 'patch.antialiased': True, 37 | 38 | # images 39 | 'image.cmap': 'gist_heat', 40 | 'image.origin': 'upper', 41 | 42 | # Font 43 | 'font.size': 12.0, 44 | 45 | # Axes 46 | 'axes.facecolor': '#000000', 47 | 'axes.edgecolor': '#F2F2F2', 48 | 'axes.linewidth': 1.0, 49 | 'axes.grid': True, 50 | 'axes.titlesize': 'x-large', 51 | 'axes.labelsize': 'large', 52 | 'axes.labelcolor': 'w', 53 | 'axes.axisbelow': True, 54 | 55 | # Ticks 56 | 'xtick.major.size': 0, 57 | 'xtick.minor.size': 0, 58 | 'xtick.major.pad': 6, 59 | 'xtick.minor.pad': 6, 60 | 'xtick.color': '#F2F2F2', 61 | 'xtick.direction': 'in', 62 | 'ytick.major.size': 0, 63 | 'ytick.minor.size': 0, 64 | 'ytick.major.pad': 6, 65 | 'ytick.minor.pad': 6, 66 | 'ytick.color': '#F2F2F2', 67 | 'ytick.direction': 'in', 68 | 69 | # Legend 70 | 'legend.fancybox': True, 71 | 'legend.loc': 'best', 72 | 73 | # Figure 74 | 'figure.figsize': [8, 6], 75 | 'figure.facecolor': 'k', 76 | 'figure.edgecolor': 'k', 77 | 'figure.subplot.hspace': 0.5, 78 | 'figure.subplot.bottom': 0.15, 79 | 80 | # Saved figures 81 | 'savefig.dpi': 72, 82 | 'savefig.facecolor': 'k', 83 | 'savefig.edgecolor': 'k', 84 | 'savefig.transparent': False, 85 | 86 | # Other 87 | 'grid.color': 'w', 88 | 'text.color': 'w' 89 | } 90 | 91 | color_cycle = ['#00CCFF', # blue 92 | '#FF0000', # red 93 | '#40C000', # green 94 | '#E066E0', # purple 95 | '#CF4457', # pink 96 | '#188487', # turquoise 97 | '#E24A33'] # orange 98 | 99 | if MATPLOTLIB_GE_1_5: 100 | # This is a dependency of matplotlib, so should be present. 101 | from cycler import cycler 102 | dark_style_sheet['axes.prop_cycle'] = cycler('color', color_cycle) 103 | 104 | else: 105 | dark_style_sheet['axes.color_cycle'] = color_cycle 106 | -------------------------------------------------------------------------------- /astroplan/tests/test_target.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import pytest 6 | 7 | # Third-party 8 | import astropy.units as u 9 | from astropy.coordinates import SkyCoord, GCRS, ICRS 10 | from astropy.time import Time 11 | 12 | # Package 13 | from ..target import FixedTarget, get_skycoord 14 | from ..observer import Observer 15 | 16 | 17 | def test_FixedTarget_from_name(): 18 | """ 19 | Check that resolving target names with the `SkyCoord.from_name` constructor 20 | to produce a `FixedTarget` accurately resolves the coordinates of Polaris. 21 | """ 22 | 23 | # Resolve coordinates with SkyCoord.from_name classmethod 24 | polaris_from_name = FixedTarget.from_name('Polaris') 25 | polaris_from_name = FixedTarget.from_name('Polaris', name='Target 1') 26 | # Coordinates grabbed from SIMBAD 27 | polaris_from_SIMBAD = SkyCoord('02h31m49.09456s', '+89d15m50.7923s') 28 | 29 | # Make sure separation is small 30 | assert polaris_from_name.coord.separation(polaris_from_SIMBAD) < 1*u.arcsec 31 | 32 | 33 | def test_FixedTarget_ra_dec(): 34 | """ 35 | Confirm that FixedTarget.ra and FixedTarget.dec are the same as the 36 | right ascension and declination stored in the FixedTarget.coord variable - 37 | which is a SkyCoord 38 | """ 39 | 40 | vega_coords = SkyCoord('18h36m56.33635s', '+38d47m01.2802s') 41 | vega = FixedTarget(vega_coords, name='Vega') 42 | assert vega.coord == vega_coords, 'Store coordinates directly' 43 | assert vega.coord.ra == vega_coords.ra == vega.ra, ('Retrieve RA from ' 44 | 'SkyCoord') 45 | assert vega.coord.dec == vega_coords.dec == vega.dec, ('Retrieve Dec from ' 46 | 'SkyCoord') 47 | 48 | 49 | def test_get_skycoord(): 50 | m31 = SkyCoord(10.6847083*u.deg, 41.26875*u.deg) 51 | m31_with_distance = SkyCoord(10.6847083*u.deg, 41.26875*u.deg, 780*u.kpc) 52 | subaru = Observer.at_site('subaru') 53 | time = Time("2016-01-22 12:00") 54 | pos, vel = subaru.location.get_gcrs_posvel(time) 55 | gcrs_frame = GCRS(obstime=Time("2016-01-22 12:00"), obsgeoloc=pos, obsgeovel=vel) 56 | m31_gcrs = m31.transform_to(gcrs_frame) 57 | m31_gcrs_with_distance = m31_with_distance.transform_to(gcrs_frame) 58 | 59 | coo = get_skycoord(m31) 60 | assert coo.is_equivalent_frame(ICRS()) 61 | with pytest.raises(TypeError) as exc_info: 62 | len(coo) 63 | 64 | coo = get_skycoord([m31]) 65 | assert coo.is_equivalent_frame(ICRS()) 66 | assert len(coo) == 1 67 | 68 | coo = get_skycoord([m31, m31_gcrs]) 69 | assert coo.is_equivalent_frame(ICRS()) 70 | assert len(coo) == 2 71 | 72 | coo = get_skycoord([m31_with_distance, m31_gcrs_with_distance]) 73 | assert coo.is_equivalent_frame(ICRS()) 74 | assert len(coo) == 2 75 | 76 | coo = get_skycoord([m31, m31_gcrs, m31_gcrs_with_distance, m31_with_distance]) 77 | assert coo.is_equivalent_frame(ICRS()) 78 | assert len(coo) == 4 79 | 80 | coo = get_skycoord([m31_gcrs, m31_gcrs_with_distance]) 81 | assert coo.is_equivalent_frame(m31_gcrs.frame) 82 | assert len(coo) == 2 83 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | .. _getting_started: 2 | 3 | *************** 4 | Getting Started 5 | *************** 6 | 7 | General Guidelines 8 | ================== 9 | 10 | `astroplan` is based on `Astropy`_ and was built around the creation of Python 11 | objects that contain all the information needed to perform certain tasks. You, 12 | the user, will create and manipulate these objects to plan your observation. For 13 | instance, an `~astroplan.Target` object contains information associated with 14 | targets, such as right ascension, declination, etc. 15 | 16 | Objects representing celestial bodies like stars (which, if we ignore proper 17 | motion, are fixed on the celestial sphere) are created (or "instantiated") via 18 | an `~astroplan.FixedTarget` object:: 19 | 20 | from astropy.coordinates import SkyCoord 21 | from astroplan import FixedTarget 22 | 23 | coordinates = SkyCoord('19h50m47.6s', '+08d52m12.0s', frame='icrs') 24 | altair = FixedTarget(name='Altair', coord=coordinates) 25 | 26 | Alternatively, for objects known to the CDS name resolver, you can quickly 27 | retrieve their coordinates with `~astroplan.FixedTarget.from_name`:: 28 | 29 | altair = FixedTarget.from_name('Altair') 30 | 31 | Similarly, an `~astroplan.Observer` object contains information about the 32 | observatory, telescope or place where you are observing, such as longitude, 33 | latitude, elevation and other optional parameters. You can initialize an 34 | `~astroplan.Observer` object via the `~astroplan.Observer.at_site` class 35 | method:: 36 | 37 | from astroplan import Observer 38 | observer = Observer.at_site('subaru') 39 | 40 | Or you can specify your own location parameters:: 41 | 42 | import astropy.units as u 43 | from astropy.coordinates import EarthLocation 44 | from pytz import timezone 45 | from astroplan import Observer 46 | 47 | longitude = '-155d28m48.900s' 48 | latitude = '+19d49m42.600s' 49 | elevation = 4163 * u.m 50 | location = EarthLocation.from_geodetic(longitude, latitude, elevation) 51 | 52 | observer = Observer(name='Subaru Telescope', 53 | location=location, 54 | pressure=0.615 * u.bar, 55 | relative_humidity=0.11, 56 | temperature=0 * u.deg_C, 57 | timezone=timezone('US/Hawaii'), 58 | description="Subaru Telescope on Maunakea, Hawaii") 59 | 60 | `astroplan` makes heavy use of certain `Astropy`_ machinery, including the 61 | `~astropy.coordinates` objects and transformations and 62 | `~astropy.units`. Most importantly for basic use of `astroplan` is the 63 | representation of dates/times as `~astropy.time.Time` objects (note that 64 | these are in the UTC timezone by default):: 65 | 66 | from astropy.time import Time 67 | time = Time(['2015-06-16 06:00:00']) 68 | 69 | Since `astroplan` objects are Python objects, manipulating them or accessing 70 | attributes follows Python syntax and conventions. See Python documentation on 71 | `objects `_ 72 | for more information. 73 | 74 | Doing More 75 | ========== 76 | 77 | Now that you know the basics of working with `astroplan`, check out our 78 | :ref:`tutorials` page for high-level examples of using `astroplan`, as well as 79 | the :ref:`api` section for more exhaustive documentation and lower-level usage 80 | examples. 81 | -------------------------------------------------------------------------------- /astroplan/tests/test_periodic.py: -------------------------------------------------------------------------------- 1 | from __future__ import (absolute_import, division, print_function, 2 | unicode_literals) 3 | 4 | import numpy as np 5 | from astropy.time import Time 6 | import astropy.units as u 7 | from numpy.testing import assert_allclose 8 | 9 | from ..periodic import PeriodicEvent, EclipsingSystem 10 | 11 | PRECISION = 0.00001 # days 12 | 13 | 14 | def test_phase(): 15 | epoch = Time('2016-01-01 00:00') 16 | period = 3*u.day 17 | duration = 1*u.hour 18 | pe = PeriodicEvent(epoch=epoch, period=period, duration=duration, 19 | name='test event') 20 | 21 | assert pe.phase(Time('2016-01-02 12:00')) == 0.5 22 | assert pe.phase(Time('2016-01-04 00:00')) == 0.0 23 | 24 | 25 | def test_primary_secondary_eclipse(): 26 | epoch = Time('2016-01-01 00:00') 27 | period = 3*u.day 28 | duration = 1*u.hour 29 | es = EclipsingSystem(primary_eclipse_time=epoch, orbital_period=period, duration=duration, 30 | name='test event') 31 | 32 | ap_primary = es.next_primary_eclipse_time(epoch) 33 | ap_secondary = es.next_secondary_eclipse_time(epoch) 34 | 35 | soln_primary = Time(['2016-01-04 00:00']) 36 | soln_secondary = Time(['2016-01-02 12:00']) 37 | 38 | # Tolerance of 1 second 39 | assert_allclose([ap_primary.jd, ap_secondary.jd], 40 | [soln_primary.jd, soln_secondary.jd], atol=PRECISION) 41 | 42 | 43 | def test_out_of_eclipse(): 44 | epoch = Time('2016-01-01 00:00') 45 | period = 3*u.day 46 | duration = 1*u.hour 47 | es = EclipsingSystem(primary_eclipse_time=epoch, orbital_period=period, duration=duration, 48 | name='test event') 49 | 50 | test_times = Time(['2016-01-01 06:00', '2016-01-02 12:00', 51 | '2016-01-04 00:00', '2016-01-05 00:00']) 52 | 53 | assert np.all(es.out_of_eclipse(test_times) == 54 | np.array([True, False, False, True])) 55 | 56 | 57 | def test_next_eclipse(): 58 | epoch = Time('2016-01-01 00:00') 59 | period = 3*u.day 60 | duration = 1*u.hour 61 | es = EclipsingSystem(primary_eclipse_time=epoch, orbital_period=period, duration=duration, 62 | name='test event') 63 | 64 | test_time = epoch + period + 0.1*u.min 65 | ap_next_primary = es.next_primary_eclipse_time(test_time) 66 | ap_next_secondary = es.next_secondary_eclipse_time(test_time) 67 | 68 | soln_next_primary = Time(['2016-01-07 00:00']) 69 | soln_next_secondary = Time(['2016-01-05 12:00']) 70 | 71 | # Tolerance of 1 second 72 | assert_allclose([ap_next_primary.jd, ap_next_secondary.jd], 73 | [soln_next_primary.jd, soln_next_secondary.jd], 74 | atol=PRECISION) 75 | 76 | 77 | def test_primary_ingress(): 78 | epoch = Time('2016-01-01 00:00') 79 | period = 3*u.day 80 | duration = 1*u.hour 81 | es = EclipsingSystem(primary_eclipse_time=epoch, orbital_period=period, duration=duration, 82 | name='test event') 83 | 84 | test_time = epoch + 0.1*u.min 85 | ap_next_ing_egr_0 = es.next_primary_ingress_egress_time(test_time) 86 | ap_next_ing_egr_1 = es.next_primary_ingress_egress_time(test_time, n_eclipses=3) 87 | 88 | soln_next_ing_egr_0 = Time([['2016-01-03 23:30', '2016-01-04 00:30']]) 89 | soln_next_ing_egr_1 = Time([['2016-01-03 23:30', '2016-01-04 00:30'], 90 | ['2016-01-06 23:30', '2016-01-07 00:30'], 91 | ['2016-01-09 23:30', '2016-01-10 00:30']]) 92 | 93 | # Tolerance of 1 second 94 | assert_allclose(ap_next_ing_egr_0.jd, soln_next_ing_egr_0.jd, 95 | atol=PRECISION) 96 | assert_allclose(ap_next_ing_egr_1.jd, soln_next_ing_egr_1.jd, 97 | atol=PRECISION) 98 | -------------------------------------------------------------------------------- /astroplan/plots/finder.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import numpy as np 6 | 7 | import astropy.units as u 8 | 9 | from astropy.coordinates import SkyCoord 10 | from astropy.wcs import WCS 11 | 12 | __all__ = ['plot_finder_image'] 13 | 14 | 15 | @u.quantity_input(fov_radius=u.deg) 16 | def plot_finder_image(target, survey='DSS', fov_radius=10*u.arcmin, 17 | log=False, ax=None, grid=False, reticle=False, 18 | style_kwargs=None, reticle_style_kwargs=None): 19 | """ 20 | Plot survey image centered on ``target``. 21 | 22 | Survey images are retrieved from NASA Goddard's SkyView service via 23 | ``astroquery.skyview.SkyView``. 24 | 25 | If a `~matplotlib.axes.Axes` object already exists, plots the finder image 26 | on top. Otherwise, creates a new `~matplotlib.axes.Axes` 27 | object with the finder image. 28 | 29 | Parameters 30 | ---------- 31 | target : `~astroplan.FixedTarget`, `~astropy.coordinates.SkyCoord` 32 | Coordinates of celestial object 33 | 34 | survey : string 35 | Name of survey to retrieve image from. For dictionary of 36 | available surveys, use 37 | ``from astroquery.skyview import SkyView; SkyView.list_surveys()``. 38 | Defaults to ``'DSS'``, the Digital Sky Survey. 39 | 40 | fov_radius : `~astropy.units.Quantity` 41 | Radius of field of view of retrieved image. Defaults to 10 arcmin. 42 | 43 | log : bool, optional 44 | Take the natural logarithm of the FITS image if `True`. 45 | False by default. 46 | 47 | ax : `~matplotlib.axes.Axes` or None, optional. 48 | The `~matplotlib.axes.Axes` object to be drawn on. 49 | If None, uses the current `~matplotlib.axes.Axes`. 50 | 51 | grid : bool, optional. 52 | Grid is drawn if `True`. `False` by default. 53 | 54 | reticle : bool, optional 55 | Draw reticle on the center of the FOV if `True`. Default is `False`. 56 | 57 | style_kwargs : dict or `None`, optional. 58 | A dictionary of keywords passed into `~matplotlib.pyplot.imshow` 59 | to set plotting styles. 60 | 61 | reticle_style_kwargs : dict or `None`, optional 62 | A dictionary of keywords passed into `~matplotlib.pyplot.axvline` and 63 | `~matplotlib.pyplot.axhline` to set reticle style. 64 | 65 | Returns 66 | ------- 67 | ax : `~matplotlib.axes.Axes` 68 | Matplotlib axes with survey image centered on ``target`` 69 | 70 | hdu : `~astropy.io.fits.PrimaryHDU` 71 | FITS HDU of the retrieved image 72 | 73 | 74 | Notes 75 | ----- 76 | Dependencies: 77 | In addition to Matplotlib, this function makes use of astroquery. 78 | """ 79 | 80 | import matplotlib.pyplot as plt 81 | from astroquery.skyview import SkyView 82 | 83 | coord = target if not hasattr(target, 'coord') else target.coord 84 | position = coord.icrs 85 | coordinates = 'icrs' 86 | target_name = None if isinstance(target, SkyCoord) else target.name 87 | 88 | hdu = SkyView.get_images(position=position, coordinates=coordinates, 89 | survey=survey, radius=fov_radius, grid=grid)[0][0] 90 | wcs = WCS(hdu.header) 91 | 92 | # Set up axes & plot styles if needed. 93 | if ax is None: 94 | ax = plt.gca(projection=wcs) 95 | if style_kwargs is None: 96 | style_kwargs = {} 97 | style_kwargs = dict(style_kwargs) 98 | style_kwargs.setdefault('cmap', 'Greys') 99 | style_kwargs.setdefault('origin', 'lower') 100 | 101 | if log: 102 | image_data = np.log(hdu.data) 103 | else: 104 | image_data = hdu.data 105 | ax.imshow(image_data, **style_kwargs) 106 | 107 | # Draw reticle 108 | if reticle: 109 | pixel_width = image_data.shape[0] 110 | inner, outer = 0.03, 0.08 111 | 112 | if reticle_style_kwargs is None: 113 | reticle_style_kwargs = {} 114 | reticle_style_kwargs.setdefault('linewidth', 2) 115 | reticle_style_kwargs.setdefault('color', 'm') 116 | 117 | ax.axvline(x=0.5*pixel_width, ymin=0.5+inner, ymax=0.5+outer, 118 | **reticle_style_kwargs) 119 | ax.axvline(x=0.5*pixel_width, ymin=0.5-inner, ymax=0.5-outer, 120 | **reticle_style_kwargs) 121 | ax.axhline(y=0.5*pixel_width, xmin=0.5+inner, xmax=0.5+outer, 122 | **reticle_style_kwargs) 123 | ax.axhline(y=0.5*pixel_width, xmin=0.5-inner, xmax=0.5-outer, 124 | **reticle_style_kwargs) 125 | 126 | # Labels, title, grid 127 | ax.set(xlabel='RA', ylabel='DEC') 128 | if target_name is not None: 129 | ax.set_title(target_name) 130 | ax.grid(grid) 131 | 132 | # Redraw the figure for interactive sessions. 133 | ax.figure.canvas.draw() 134 | return ax, hdu 135 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 3 | 4 | import glob 5 | import os 6 | import sys 7 | 8 | import ah_bootstrap 9 | from setuptools import setup 10 | 11 | #A dirty hack to get around some early import/configurations ambiguities 12 | if sys.version_info[0] >= 3: 13 | import builtins 14 | else: 15 | import __builtin__ as builtins 16 | builtins._ASTROPY_SETUP_ = True 17 | 18 | from astropy_helpers.setup_helpers import (register_commands, get_debug_option, 19 | get_package_info) 20 | from astropy_helpers.git_helpers import get_git_devstr 21 | from astropy_helpers.version_helpers import generate_version_py 22 | 23 | # Get some values from the setup.cfg 24 | try: 25 | from ConfigParser import ConfigParser 26 | except ImportError: 27 | from configparser import ConfigParser 28 | 29 | conf = ConfigParser() 30 | conf.read(['setup.cfg']) 31 | metadata = dict(conf.items('metadata')) 32 | 33 | PACKAGENAME = metadata.get('package_name', 'packagename') 34 | DESCRIPTION = metadata.get('description', 'Astropy affiliated package') 35 | AUTHOR = metadata.get('author', '') 36 | AUTHOR_EMAIL = metadata.get('author_email', '') 37 | LICENSE = metadata.get('license', 'unknown') 38 | URL = metadata.get('url', 'http://astropy.org') 39 | 40 | # Get the long description from the package's docstring 41 | __import__(PACKAGENAME) 42 | package = sys.modules[PACKAGENAME] 43 | LONG_DESCRIPTION = package.__doc__ 44 | 45 | # Store the package name in a built-in variable so it's easy 46 | # to get from other parts of the setup infrastructure 47 | builtins._ASTROPY_PACKAGE_NAME_ = PACKAGENAME 48 | 49 | # VERSION should be PEP386 compatible (http://www.python.org/dev/peps/pep-0386) 50 | VERSION = '0.5.dev' 51 | 52 | # Indicates if this version is a release version 53 | RELEASE = 'dev' not in VERSION 54 | 55 | if not RELEASE: 56 | VERSION += get_git_devstr(False) 57 | 58 | # Populate the dict of setup command overrides; this should be done before 59 | # invoking any other functionality from distutils since it can potentially 60 | # modify distutils' behavior. 61 | cmdclassd = register_commands(PACKAGENAME, VERSION, RELEASE) 62 | 63 | # Freeze build information in version.py 64 | generate_version_py(PACKAGENAME, VERSION, RELEASE, 65 | get_debug_option(PACKAGENAME)) 66 | 67 | # Treat everything in scripts except README.rst as a script to be installed 68 | scripts = [fname for fname in glob.glob(os.path.join('scripts', '*')) 69 | if os.path.basename(fname) != 'README.rst'] 70 | 71 | 72 | # Get configuration information from all of the various subpackages. 73 | # See the docstring for setup_helpers.update_package_files for more 74 | # details. 75 | package_info = get_package_info() 76 | 77 | # Add the project-global data 78 | package_info['package_data'].setdefault(PACKAGENAME, []) 79 | package_info['package_data'][PACKAGENAME].append('data/*') 80 | 81 | # Define entry points for command-line scripts 82 | entry_points = {'console_scripts': []} 83 | 84 | entry_point_list = conf.items('entry_points') 85 | for entry_point in entry_point_list: 86 | entry_points['console_scripts'].append('{0} = {1}'.format(entry_point[0], 87 | entry_point[1])) 88 | 89 | # Include all .c files, recursively, including those generated by 90 | # Cython, since we can not do this in MANIFEST.in with a "dynamic" 91 | # directory name. 92 | c_files = [] 93 | for root, dirs, files in os.walk(PACKAGENAME): 94 | for filename in files: 95 | if filename.endswith('.c'): 96 | c_files.append( 97 | os.path.join( 98 | os.path.relpath(root, PACKAGENAME), filename)) 99 | package_info['package_data'][PACKAGENAME].extend(c_files) 100 | 101 | setup(name=PACKAGENAME, 102 | version=VERSION, 103 | description=DESCRIPTION, 104 | scripts=scripts, 105 | install_requires=['numpy>=1.10', 'astropy>=1.3', 'pytz'], 106 | extras_require=dict( 107 | plotting=['matplotlib>=1.4'], 108 | docs=['sphinx_rtd_theme'] 109 | ), 110 | provides=[PACKAGENAME], 111 | author=AUTHOR, 112 | author_email=AUTHOR_EMAIL, 113 | license=LICENSE, 114 | url=URL, 115 | long_description=LONG_DESCRIPTION, 116 | classifiers=[ 117 | 'Development Status :: 1 - Planning', 118 | 'Intended Audience :: Science/Research', 119 | 'Topic :: Scientific/Engineering :: Astronomy', 120 | 'License :: OSI Approved :: BSD License', 121 | 'Operating System :: OS Independent', 122 | 'Programming Language :: Python :: 2', 123 | 'Programming Language :: Python :: 2.7', 124 | 'Programming Language :: Python :: 3', 125 | 'Programming Language :: Python :: 3.3', 126 | 'Programming Language :: Python :: 3.4', 127 | ], 128 | cmdclass=cmdclassd, 129 | zip_safe=False, 130 | use_2to3=False, 131 | entry_points=entry_points, 132 | **package_info 133 | ) 134 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | #This is needed with git because git doesn't create a dir if it's empty 18 | $(shell [ -d "_static" ] || mkdir -p _static) 19 | 20 | help: 21 | @echo "Please use \`make ' where is one of" 22 | @echo " html to make standalone HTML files" 23 | @echo " dirhtml to make HTML files named index.html in directories" 24 | @echo " singlehtml to make a single large HTML file" 25 | @echo " pickle to make pickle files" 26 | @echo " json to make JSON files" 27 | @echo " htmlhelp to make HTML files and a HTML help project" 28 | @echo " qthelp to make HTML files and a qthelp project" 29 | @echo " devhelp to make HTML files and a Devhelp project" 30 | @echo " epub to make an epub" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " text to make text files" 34 | @echo " man to make manual pages" 35 | @echo " changes to make an overview of all changed/added/deprecated items" 36 | @echo " linkcheck to check all external links for integrity" 37 | 38 | clean: 39 | -rm -rf $(BUILDDIR) 40 | -rm -rf api 41 | -rm -rf generated 42 | 43 | html: 44 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 45 | @echo 46 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 47 | 48 | dirhtml: 49 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 50 | @echo 51 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 52 | 53 | singlehtml: 54 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 55 | @echo 56 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 57 | 58 | pickle: 59 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 60 | @echo 61 | @echo "Build finished; now you can process the pickle files." 62 | 63 | json: 64 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 65 | @echo 66 | @echo "Build finished; now you can process the JSON files." 67 | 68 | htmlhelp: 69 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 70 | @echo 71 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 72 | ".hhp project file in $(BUILDDIR)/htmlhelp." 73 | 74 | qthelp: 75 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 76 | @echo 77 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 78 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 79 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Astropy.qhcp" 80 | @echo "To view the help file:" 81 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Astropy.qhc" 82 | 83 | devhelp: 84 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 85 | @echo 86 | @echo "Build finished." 87 | @echo "To view the help file:" 88 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Astropy" 89 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Astropy" 90 | @echo "# devhelp" 91 | 92 | epub: 93 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 94 | @echo 95 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 96 | 97 | latex: 98 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 99 | @echo 100 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 101 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 102 | "(use \`make latexpdf' here to do that automatically)." 103 | 104 | latexpdf: 105 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 106 | @echo "Running LaTeX files through pdflatex..." 107 | make -C $(BUILDDIR)/latex all-pdf 108 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 109 | 110 | text: 111 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 112 | @echo 113 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 114 | 115 | man: 116 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 117 | @echo 118 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 119 | 120 | changes: 121 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 122 | @echo 123 | @echo "The overview file is in $(BUILDDIR)/changes." 124 | 125 | linkcheck: 126 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 127 | @echo 128 | @echo "Link check complete; look for any errors in the above output " \ 129 | "or in $(BUILDDIR)/linkcheck/output.txt." 130 | 131 | doctest: 132 | @echo "Run 'python setup.py test' in the root directory to run doctests " \ 133 | @echo "in the documentation." 134 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Astropy.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Astropy.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # We set the language to c because python isn't supported on the MacOS X nodes 2 | # on Travis. However, the language ends up being irrelevant anyway, since we 3 | # install Python ourselves using conda. 4 | language: c 5 | 6 | os: 7 | - linux 8 | 9 | stage: Comprehensive tests 10 | 11 | # Setting sudo to false opts in to Travis-CI container-based builds. 12 | sudo: false 13 | 14 | # The apt packages below are needed for sphinx builds, which can no longer 15 | # be installed with sudo apt-get. 16 | addons: 17 | apt: 18 | packages: 19 | - graphviz 20 | - texlive-latex-extra 21 | - dvipng 22 | env: 23 | global: 24 | # Set defaults to avoid repeating in most cases 25 | - PYTHON_VERSION=3.6 26 | - NUMPY_VERSION=stable 27 | - ASTROPY_VERSION=stable 28 | - MAIN_CMD='python setup.py' 29 | - CONDA_DEPENDENCIES='pytz' 30 | - PIP_DEPENDENCIES='' 31 | - SETUP_CMD='test -V' 32 | - CONDA_CHANNELS='astropy' 33 | 34 | 35 | stages: 36 | # Do the initial tests and don't proceed if they fail 37 | - name: Initial tests 38 | # Do the rest of the tests 39 | - name: Comprehensive tests 40 | - name: Cron and master-only tests 41 | if: type IN (push, cron) 42 | 43 | 44 | matrix: 45 | 46 | # Don't wait for allowed failures 47 | fast_finish: true 48 | 49 | include: 50 | 51 | - stage: Initial tests 52 | env: PYTHON_VERSION=2.7 SETUP_CMD='egg_info' 53 | - stage: Initial tests 54 | env: PYTHON_VERSION=3.5 SETUP_CMD='egg_info' 55 | - stage: Initial tests 56 | env: SETUP_CMD='egg_info' 57 | 58 | # Try MacOS X 59 | - os: osx 60 | stage: Cron and master-only tests 61 | env: PYTHON_VERSION=2.7 62 | CONDA_DEPENDENCIES='pytz matplotlib' 63 | PIP_DEPENDENCIES='pytest-mpl' 64 | 65 | # Run one of the docs build during the initial tests 66 | - os: linux 67 | stage: Initial tests 68 | env: SETUP_CMD='build_docs -w' 69 | CONDA_DEPENDENCIES='pytz matplotlib astroquery' 70 | PIP_DEPENDENCIES='pytest-mpl' 71 | 72 | # Try all python versions with the latest numpy 73 | # TODO: add the `--open-files` option back once this issue has been resolved: 74 | # https://github.com/astropy/astroplan/pull/83#issuecomment-136129489 75 | - os: linux 76 | env: PYTHON_VERSION=2.7 77 | 78 | - os: linux 79 | env: PYTHON_VERSION=3.6 80 | 81 | # Now try with all optional dependencies on 2.7 and an appropriate 3.x 82 | # build (with latest numpy). We also note the code coverage on Python 83 | # 2.7. 84 | - os: linux 85 | env: PYTHON_VERSION=2.7 SETUP_CMD='test --remote-data -V --coverage' 86 | CONDA_DEPENDENCIES='pytz matplotlib' 87 | PIP_DEPENDENCIES='pytest-mpl' 88 | - os: linux 89 | stage: Initial tests 90 | env: SETUP_CMD='test --remote-data -V' 91 | CONDA_DEPENDENCIES='pytz matplotlib' 92 | PIP_DEPENDENCIES='pytest-mpl' 93 | 94 | # Try older numpy versions 95 | - os: linux 96 | env: PYTHON_VERSION=3.5 NUMPY_VERSION=1.11 97 | - os: linux 98 | env: PYTHON_VERSION=3.5 NUMPY_VERSION=1.10 99 | 100 | # Try developer version of Numpy 101 | - os: linux 102 | env: PYTHON_VERSION=2.7 NUMPY_VERSION=dev 103 | 104 | # Try pre-release version of Numpy. This only runs if a pre-release 105 | # is available on pypi. 106 | - os: linux 107 | stage: Cron and master-only tests 108 | env: NUMPY_VERSION=prerelease 109 | 110 | # Try developer version of Astropy 111 | - os: linux 112 | env: PYTHON_VERSION=3.6 ASTROPY_VERSION=dev 113 | 114 | # Do a PEP8 test with pycodestyle 115 | - os: linux 116 | env: MAIN_CMD='pycodestyle astroplan --count --max-line-length=100' SETUP_CMD='' 117 | 118 | allow_failures: 119 | - env: PYTHON_VERSION=2.7 NUMPY_VERSION=dev 120 | # Allow them to fail now until the IERSs issues are fixed 121 | - env: PYTHON_VERSION=2.7 SETUP_CMD='test --remote-data -V --coverage' 122 | CONDA_DEPENDENCIES='pytz matplotlib' 123 | PIP_DEPENDENCIES='pytest-mpl' 124 | - env: SETUP_CMD='test --remote-data -V' 125 | CONDA_DEPENDENCIES='pytz matplotlib' 126 | PIP_DEPENDENCIES='pytest-mpl' 127 | 128 | install: 129 | - git clone git://github.com/astropy/ci-helpers.git 130 | - source ci-helpers/travis/setup_conda.sh 131 | 132 | # This is needed to make matplotlib plot testing work 133 | - if [[ $TRAVIS_OS_NAME == 'linux' ]]; then 134 | export DISPLAY=:99.0; 135 | sh -e /etc/init.d/xvfb start; 136 | export QT_API=pyqt; 137 | fi 138 | 139 | script: 140 | # To avoid the OldEarthOrientationDataWarning during docs build 141 | - if [[ $SETUP_CMD == *build_docs* ]]; then python -c "from astroplan import download_IERS_A; download_IERS_A()";fi 142 | - $MAIN_CMD $SETUP_CMD 143 | 144 | after_success: 145 | - if [[ $SETUP_CMD == *--coverage* ]]; then 146 | coveralls --rcfile='astroplan/tests/coveragerc'; 147 | fi 148 | -------------------------------------------------------------------------------- /astroplan/_astropy_init.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | 3 | __all__ = ['__version__', '__githash__', 'test'] 4 | 5 | # this indicates whether or not we are in the package's setup.py 6 | try: 7 | _ASTROPY_SETUP_ 8 | except NameError: 9 | from sys import version_info 10 | if version_info[0] >= 3: 11 | import builtins 12 | else: 13 | import __builtin__ as builtins 14 | builtins._ASTROPY_SETUP_ = False 15 | 16 | try: 17 | from .version import version as __version__ 18 | except ImportError: 19 | __version__ = '' 20 | try: 21 | from .version import githash as __githash__ 22 | except ImportError: 23 | __githash__ = '' 24 | 25 | # set up the test command 26 | def _get_test_runner(): 27 | import os 28 | from astropy.tests.helper import TestRunner 29 | return TestRunner(os.path.dirname(__file__)) 30 | 31 | def test(package=None, test_path=None, args=None, plugins=None, 32 | verbose=False, pastebin=None, remote_data=False, pep8=False, 33 | pdb=False, coverage=False, open_files=False, **kwargs): 34 | """ 35 | Run the tests using `py.test `__. A proper set 36 | of arguments is constructed and passed to `pytest.main`_. 37 | 38 | .. _py.test: http://pytest.org/latest/ 39 | .. _pytest.main: http://pytest.org/latest/builtin.html#pytest.main 40 | 41 | Parameters 42 | ---------- 43 | package : str, optional 44 | The name of a specific package to test, e.g. 'io.fits' or 'utils'. 45 | If nothing is specified all default tests are run. 46 | 47 | test_path : str, optional 48 | Specify location to test by path. May be a single file or 49 | directory. Must be specified absolutely or relative to the 50 | calling directory. 51 | 52 | args : str, optional 53 | Additional arguments to be passed to pytest.main_ in the ``args`` 54 | keyword argument. 55 | 56 | plugins : list, optional 57 | Plugins to be passed to pytest.main_ in the ``plugins`` keyword 58 | argument. 59 | 60 | verbose : bool, optional 61 | Convenience option to turn on verbose output from py.test_. Passing 62 | True is the same as specifying ``'-v'`` in ``args``. 63 | 64 | pastebin : {'failed','all',None}, optional 65 | Convenience option for turning on py.test_ pastebin output. Set to 66 | ``'failed'`` to upload info for failed tests, or ``'all'`` to upload 67 | info for all tests. 68 | 69 | remote_data : bool, optional 70 | Controls whether to run tests marked with @remote_data. These 71 | tests use online data and are not run by default. Set to True to 72 | run these tests. 73 | 74 | pep8 : bool, optional 75 | Turn on PEP8 checking via the `pytest-pep8 plugin 76 | `_ and disable normal 77 | tests. Same as specifying ``'--pep8 -k pep8'`` in ``args``. 78 | 79 | pdb : bool, optional 80 | Turn on PDB post-mortem analysis for failing tests. Same as 81 | specifying ``'--pdb'`` in ``args``. 82 | 83 | coverage : bool, optional 84 | Generate a test coverage report. The result will be placed in 85 | the directory htmlcov. 86 | 87 | open_files : bool, optional 88 | Fail when any tests leave files open. Off by default, because 89 | this adds extra run time to the test suite. Requires the 90 | ``psutil`` package. 91 | 92 | parallel : int, optional 93 | When provided, run the tests in parallel on the specified 94 | number of CPUs. If parallel is negative, it will use the all 95 | the cores on the machine. Requires the 96 | `pytest-xdist `_ plugin 97 | installed. Only available when using Astropy 0.3 or later. 98 | 99 | kwargs 100 | Any additional keywords passed into this function will be passed 101 | on to the astropy test runner. This allows use of test-related 102 | functionality implemented in later versions of astropy without 103 | explicitly updating the package template. 104 | 105 | """ 106 | test_runner = _get_test_runner() 107 | return test_runner.run_tests( 108 | package=package, test_path=test_path, args=args, 109 | plugins=plugins, verbose=verbose, pastebin=pastebin, 110 | remote_data=remote_data, pep8=pep8, pdb=pdb, 111 | coverage=coverage, open_files=open_files, **kwargs) 112 | 113 | if not _ASTROPY_SETUP_: 114 | import os 115 | from warnings import warn 116 | from astropy import config 117 | 118 | # add these here so we only need to cleanup the namespace at the end 119 | config_dir = None 120 | 121 | if not os.environ.get('ASTROPY_SKIP_CONFIG_UPDATE', False): 122 | config_dir = os.path.dirname(__file__) 123 | config_template = os.path.join(config_dir, __package__ + ".cfg") 124 | if os.path.isfile(config_template): 125 | try: 126 | config.configuration.update_default_config( 127 | __package__, config_dir, version=__version__) 128 | except TypeError as orig_error: 129 | try: 130 | config.configuration.update_default_config( 131 | __package__, config_dir) 132 | except config.configuration.ConfigurationDefaultMissingError as e: 133 | wmsg = (e.args[0] + " Cannot install default profile. If you are " 134 | "importing from source, this is expected.") 135 | warn(config.configuration.ConfigurationDefaultMissingWarning(wmsg)) 136 | del e 137 | except: 138 | raise orig_error 139 | -------------------------------------------------------------------------------- /docs/faq/iers.rst: -------------------------------------------------------------------------------- 1 | .. doctest-skip-all 2 | 3 | .. _iers: 4 | 5 | ************************************************** 6 | What are the IERS tables and how do I update them? 7 | ************************************************** 8 | 9 | Contents 10 | ======== 11 | 12 | * :ref:`iers-Background` 13 | * :ref:`iers-What_are_IERS_Bulletins_A_and_B` 14 | * :ref:`iers-How_do_I_get_IERS_Bulletin_A_for_astroplan?` 15 | 16 | .. _iers-Background: 17 | 18 | Background 19 | ========== 20 | 21 | Keeping accurate, consistent time systems is rough. We intuitively want 22 | time to be described by a continuous cyclic clock like the clock on your 23 | wall or the calendar, but the Earth's rate of rotation varies measurably 24 | on human timescales. As a result, the time measured by an atomic clock and 25 | the time that you would infer from measuring how long it has been since the 26 | last solar noon are constantly becoming offset from one another with 27 | stochastic changes in Earth's moment of inertia and slowly acting tidal forces. 28 | 29 | For this reason, there is a time system that keeps track of seconds like an 30 | atomic clock, TAI, a more commonly used one that works like an atomic clock 31 | with a varying offset of some integer number of seconds, UTC, and one that 32 | matches up with the Earth's rotation, UT1. The difference between UT1 and UTC is 33 | constantly changing as the Earth's rotation changes and as leap seconds get 34 | added to UTC to compensate. 35 | 36 | In order to accurately predict the apparent position of a celestial object from 37 | the Earth, for example in altitude and azimuth coordinates, one needs to know 38 | the orientation of the Earth at any point in time, and since the Earth's 39 | rotation does not change in a predictable way, we must rely on observations 40 | of the Earth's orientation as a function of time to accurately calculate 41 | UT1-UTC. 42 | 43 | .. _iers-What_are_IERS_Bulletins_A_and_B: 44 | 45 | What are IERS Bulletins A and B? 46 | ================================ 47 | 48 | The `International Earth Rotation and Reference Systems Service (IERS) 49 | `_ is responsible for measuring the Earth's orientation as 50 | a function of time, and making predictions for the Earth's orientation in the 51 | near future (accounting for scheduled leap seconds). The data products released 52 | by the IERS used by astroplan are the IERS Bulletins A and B. 53 | 54 | * IERS Bulletin B is a table with Earth orientation observations from the last 55 | few decades up through nearly the present time. `Astropy`_ relies on IERS B to 56 | compute UT1-UTC, and by default will raise an error if you try to compute 57 | UT1-UTC for a time that is outside the bounds of the IERS Bulletin B table 58 | (see the `astropy`_ docs on the `UT1/UTC transformation offsets 59 | `_ 60 | for more details), like this:: 61 | 62 | >>> from astropy.time import Time 63 | >>> Time('2040-01-01', scale='utc').ut1 # Convert from UTC to UT1 64 | IndexError: (some) times are outside of range covered by IERS table. 65 | 66 | * IERS Bulletin A encompasses the observations of recent Earth orientation 67 | contained in Bulletin B, while also making extrapolations into the past, 68 | before the Bulletin B tables begin, and into the future, after the Bulletin 69 | B tables end. The future predictions include leap second additions scheduled 70 | to be added. 71 | 72 | Let's plot the UT1-UTC from IERS Bulletins A and B to show the difference using 73 | astropy's IERS machinery: 74 | 75 | .. plot:: 76 | 77 | import matplotlib.pyplot as plt 78 | import numpy as np 79 | import astropy.units as u 80 | from astropy.time import Time 81 | from astropy.extern.six.moves.urllib.error import HTTPError 82 | 83 | # Download and cache the IERS Bulletins A and B using astropy's machinery 84 | # (reminder: astroplan has its own function for this: `download_IERS_A`) 85 | from astropy.utils.iers import (IERS_A, IERS_A_URL, IERS_B, IERS_B_URL, 86 | FROM_IERS_B, IERS_Auto) 87 | from astropy.utils.data import download_file 88 | # Workaround until astropy/astropy#5194 is solved 89 | iers_a = IERS_Auto.open() 90 | iers_b = IERS_B.open(download_file(IERS_B_URL, cache=True)) 91 | 92 | # Get a range of times to plot from 1990-2022 93 | time_range = Time("1990-01-01") + np.arange(0, 30, 0.2)*u.year 94 | 95 | # Calculate the difference between UTC and UT1 at those times, 96 | # allowing times "outside of the table" 97 | DUT1_a, success_a = time_range.get_delta_ut1_utc(return_status=True, 98 | iers_table=iers_a) 99 | DUT1_b, success_b = time_range.get_delta_ut1_utc(return_status=True, 100 | iers_table=iers_b) 101 | 102 | # Compare input times to the times available in the table. For details, see 103 | # https://github.com/astropy/astropy/blob/master/astropy/utils/iers/iers.py#L80 104 | measurements_from_b = (success_b == FROM_IERS_B) 105 | 106 | # Make a plot of the time difference 107 | fig, ax = plt.subplots(figsize=(10,8)) 108 | ax.axhline(0, color='gray', ls='--', lw=2) 109 | 110 | ax.plot_date(time_range.plot_date, 111 | DUT1_a, '-', lw=2, label='IERS Bulletin A + extrapolation') 112 | ax.plot_date(time_range.plot_date[measurements_from_b], 113 | DUT1_b[measurements_from_b], 'r--', lw=2, label='IERS Bulletin B') 114 | ax.set(xlabel='Year', ylabel='UT1-UTC [seconds]') 115 | ax.legend(loc='upper right') 116 | plt.show() 117 | 118 | 119 | .. _iers-How_do_I_get_IERS_Bulletin_A_for_astroplan?: 120 | 121 | How do I get IERS Bulletin A for astroplan? 122 | =========================================== 123 | 124 | Without downloading IERS Bulletin A, astroplan simply approximates UT1-UTC=0 125 | always. This will lead to lower precision position and time calculations 126 | on the order of arcseconds or seconds, and allow you to handle times in the 127 | far future and distant past. 128 | 129 | To download the IERS Bulletin A table for the first time, or to refresh the 130 | cached version that you already have, simply run:: 131 | 132 | from astroplan import download_IERS_A 133 | download_IERS_A() 134 | 135 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 3 | # 4 | # Astropy documentation build configuration file. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this file. 9 | # 10 | # All configuration values have a default. Some values are defined in 11 | # the global Astropy configuration which is loaded here before anything else. 12 | # See astropy.sphinx.conf for which values are set there. 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | # sys.path.insert(0, os.path.abspath('..')) 18 | # IMPORTANT: the above commented section was generated by sphinx-quickstart, but 19 | # is *NOT* appropriate for astropy or Astropy affiliated packages. It is left 20 | # commented out with this explanation to make it clear why this should not be 21 | # done. If the sys.path entry above is added, when the astropy.sphinx.conf 22 | # import occurs, it will import the *source* version of astropy instead of the 23 | # version installed (if invoked as "make html" or directly with sphinx), or the 24 | # version in the build directory (if "python setup.py build_sphinx" is used). 25 | # Thus, any C-extensions that are needed to build the documentation will *not* 26 | # be accessible, and the documentation will not build correctly. 27 | 28 | import datetime 29 | import os 30 | import sys 31 | 32 | try: 33 | import astropy_helpers 34 | except ImportError: 35 | # Building from inside the docs/ directory? 36 | if os.path.basename(os.getcwd()) == 'docs': 37 | a_h_path = os.path.abspath(os.path.join('..', 'astropy_helpers')) 38 | if os.path.isdir(a_h_path): 39 | sys.path.insert(1, a_h_path) 40 | 41 | # Load all of the global Astropy configuration 42 | from astropy_helpers.sphinx.conf import * 43 | 44 | # Get configuration information from setup.cfg 45 | try: 46 | from ConfigParser import ConfigParser 47 | except ImportError: 48 | from configparser import ConfigParser 49 | conf = ConfigParser() 50 | 51 | conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) 52 | setup_cfg = dict(conf.items('metadata')) 53 | 54 | # -- General configuration ---------------------------------------------------- 55 | 56 | # If your documentation needs a minimal Sphinx version, state it here. 57 | #needs_sphinx = '1.2' 58 | 59 | del intersphinx_mapping['h5py'] 60 | intersphinx_mapping['astroquery'] = ('http://astroquery.readthedocs.io/en/latest/', None) 61 | 62 | # To perform a Sphinx version check that needs to be more specific than 63 | # major.minor, call `check_sphinx_version("x.y.z")` here. 64 | # check_sphinx_version("1.2.1") 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | exclude_patterns.append('_templates') 69 | 70 | # This is added to the end of RST files - a good place to put substitutions to 71 | # be used globally. 72 | rst_epilog += """ 73 | """ 74 | 75 | # -- Project information ------------------------------------------------------ 76 | 77 | # This does not *have* to match the package name, but typically does 78 | project = setup_cfg['package_name'] 79 | author = setup_cfg['author'] 80 | copyright = '{0}, {1}'.format( 81 | datetime.datetime.now().year, setup_cfg['author']) 82 | 83 | # The version info for the project you're documenting, acts as replacement for 84 | # |version| and |release|, also used in various other places throughout the 85 | # built documents. 86 | 87 | __import__(setup_cfg['package_name']) 88 | package = sys.modules[setup_cfg['package_name']] 89 | 90 | # The short X.Y version. 91 | version = package.__version__.split('-', 1)[0] 92 | # The full version, including alpha/beta/rc tags. 93 | release = package.__version__ 94 | 95 | 96 | # -- Options for HTML output --------------------------------------------------- 97 | 98 | # A NOTE ON HTML THEMES 99 | # The global astropy configuration uses a custom theme, 'bootstrap-astropy', 100 | # which is installed along with astropy. A different theme can be used or 101 | # the options for this theme can be modified by overriding some of the 102 | # variables set in the global configuration. The variables set in the 103 | # global configuration are listed below, commented out. 104 | 105 | html_theme_options = { 106 | 'logotext1': 'astro', # white, semi-bold 107 | 'logotext2': 'plan', # orange, light 108 | 'logotext3': ':docs' # white, light 109 | } 110 | 111 | # Add any paths that contain custom themes here, relative to this directory. 112 | # To use a different custom theme, add the directory containing the theme. 113 | #html_theme_path = [] 114 | 115 | # The theme to use for HTML and HTML Help pages. See the documentation for 116 | # a list of builtin themes. To override the custom theme, set this to the 117 | # name of a builtin theme or the name of a custom theme in html_theme_path. 118 | # html_theme = None 119 | 120 | # Custom sidebar templates, maps document names to template names. 121 | #html_sidebars = {} 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | #html_favicon = '' 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '' 131 | 132 | # The name for this set of Sphinx documents. If None, it defaults to 133 | # " v documentation". 134 | html_title = '{0} v{1}'.format(project, release) 135 | 136 | # Output file base name for HTML help builder. 137 | htmlhelp_basename = project + 'doc' 138 | 139 | 140 | # -- Options for LaTeX output -------------------------------------------------- 141 | 142 | # Grouping the document tree into LaTeX files. List of tuples 143 | # (source start file, target name, title, author, documentclass [howto/manual]). 144 | latex_documents = [('index', project + '.tex', project + u' Documentation', 145 | author, 'manual')] 146 | 147 | 148 | # -- Options for manual page output -------------------------------------------- 149 | 150 | # One entry per manual page. List of tuples 151 | # (source start file, name, description, authors, manual section). 152 | man_pages = [('index', project.lower(), project + u' Documentation', 153 | [author], 1)] 154 | 155 | 156 | # -- Options for the edit_on_github extension ---------------------------------- 157 | 158 | if eval(setup_cfg.get('edit_on_github')): 159 | extensions += ['astropy_helpers.sphinx.ext.edit_on_github'] 160 | 161 | versionmod = __import__(setup_cfg['package_name'] + '.version') 162 | edit_on_github_project = setup_cfg['github_project'] 163 | if versionmod.version.release: 164 | edit_on_github_branch = "v" + versionmod.version.version 165 | else: 166 | edit_on_github_branch = "master" 167 | 168 | edit_on_github_source_root = "" 169 | edit_on_github_doc_root = "docs" 170 | 171 | # Make appropriate substitutions to mock internet querying methods 172 | # within the tests. 173 | # Currently this is not needed because of the content of the tests, but we leave 174 | # it here in case it's needed again in the future. BUT beware of: 175 | # https://github.com/astropy/astroplan/issues/96 176 | #from astroplan.utils import _mock_remote_data 177 | #_mock_remote_data() 178 | 179 | # Add additional Sphinx extensions: 180 | extensions += ['matplotlib.sphinxext.plot_directive'] 181 | 182 | # -- Resolving issue number to links in changelog ----------------------------- 183 | github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_p\ 184 | roject']) 185 | 186 | 187 | # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- 188 | nitpicky = True 189 | -------------------------------------------------------------------------------- /astroplan/target.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | # Standard library 6 | from abc import ABCMeta 7 | 8 | # Third-party 9 | import astropy.units as u 10 | from astropy.coordinates import (SkyCoord, ICRS, UnitSphericalRepresentation, 11 | SphericalRepresentation) 12 | 13 | __all__ = ["Target", "FixedTarget", "NonFixedTarget"] 14 | 15 | # Docstring code examples include printed SkyCoords, but the format changed 16 | # in astropy 1.3. Thus the doctest needs astropy >=1.3 and this is the 17 | # easiest way to make it work. 18 | 19 | __doctest_requires__ = {'FixedTarget.*': ['astropy.modeling.Hermite1D']} 20 | 21 | 22 | class Target(object): 23 | """ 24 | Abstract base class for target objects. 25 | 26 | This is an abstract base class -- you can't instantiate 27 | examples of this class, but must work with one of its 28 | subclasses such as `~astroplan.target.FixedTarget` or 29 | `~astroplan.target.NonFixedTarget`. 30 | """ 31 | __metaclass__ = ABCMeta 32 | 33 | def __init__(self, name=None, ra=None, dec=None, marker=None): 34 | """ 35 | Defines a single observation target. 36 | 37 | Parameters 38 | ---------- 39 | name : str, optional 40 | 41 | ra : WHAT TYPE IS ra ? 42 | 43 | dec : WHAT TYPE IS dec ? 44 | 45 | marker : str, optional 46 | User-defined markers to differentiate between different types 47 | of targets (e.g., guides, high-priority, etc.). 48 | """ 49 | raise NotImplementedError() 50 | 51 | @property 52 | def ra(self): 53 | """ 54 | Right ascension. 55 | """ 56 | if isinstance(self, FixedTarget): 57 | return self.coord.ra 58 | raise NotImplementedError() 59 | 60 | @property 61 | def dec(self): 62 | """ 63 | Declination. 64 | """ 65 | if isinstance(self, FixedTarget): 66 | return self.coord.dec 67 | raise NotImplementedError() 68 | 69 | 70 | class FixedTarget(Target): 71 | """ 72 | Coordinates and metadata for an object that is "fixed" with respect to the 73 | celestial sphere. 74 | 75 | Examples 76 | -------- 77 | Create a `~astroplan.FixedTarget` object for Sirius: 78 | 79 | >>> from astroplan import FixedTarget 80 | >>> from astropy.coordinates import SkyCoord 81 | >>> import astropy.units as u 82 | >>> sirius_coord = SkyCoord(ra=101.28715533*u.deg, dec=16.71611586*u.deg) 83 | >>> sirius = FixedTarget(coord=sirius_coord, name="Sirius") 84 | 85 | Create an equivalent `~astroplan.FixedTarget` object for Sirius by querying 86 | for the coordinates of Sirius by name: 87 | 88 | >>> from astroplan import FixedTarget 89 | >>> sirius = FixedTarget.from_name("Sirius") 90 | """ 91 | 92 | def __init__(self, coord, name=None, **kwargs): 93 | """ 94 | Parameters 95 | ---------- 96 | coord : `~astropy.coordinates.SkyCoord` 97 | Coordinate of the target 98 | 99 | name : str (optional) 100 | Name of the target, used for plotting and representing the target 101 | as a string 102 | """ 103 | if not (hasattr(coord, 'transform_to') and 104 | hasattr(coord, 'represent_as')): 105 | raise TypeError('`coord` must be a coordinate object.') 106 | 107 | self.name = name 108 | self.coord = coord 109 | 110 | @classmethod 111 | def from_name(cls, query_name, name=None, **kwargs): 112 | """ 113 | Initialize a `FixedTarget` by querying for a name from the CDS name 114 | resolver, using the machinery in 115 | `~astropy.coordinates.SkyCoord.from_name`. 116 | 117 | This 118 | 119 | Parameters 120 | ---------- 121 | query_name : str 122 | Name of the target used to query for coordinates. 123 | 124 | name : string or `None` 125 | Name of the target to use within astroplan. If `None`, query_name 126 | is used as ``name``. 127 | 128 | Examples 129 | -------- 130 | >>> from astroplan import FixedTarget 131 | >>> sirius = FixedTarget.from_name("Sirius") 132 | >>> sirius.coord # doctest: +FLOAT_CMP 133 | 135 | """ 136 | # Allow manual override for name keyword so that the target name can 137 | # be different from the query name, otherwise assume name=queryname. 138 | if name is None: 139 | name = query_name 140 | return cls(SkyCoord.from_name(query_name), name=name, **kwargs) 141 | 142 | def __repr__(self): 143 | """ 144 | String representation of `~astroplan.FixedTarget`. 145 | 146 | Examples 147 | -------- 148 | Show string representation of a `~astroplan.FixedTarget` for Vega: 149 | 150 | >>> from astroplan import FixedTarget 151 | >>> from astropy.coordinates import SkyCoord 152 | >>> vega_coord = SkyCoord(ra='279.23473479d', dec='38.78368896d') 153 | >>> vega = FixedTarget(coord=vega_coord, name="Vega") 154 | >>> print(vega) # doctest: +FLOAT_CMP 155 | 156 | """ 157 | class_name = self.__class__.__name__ 158 | fmt_coord = repr(self.coord).replace('\n ', '')[1:-1] 159 | return '<{} "{}" at {}>'.format(class_name, self.name, fmt_coord) 160 | 161 | @classmethod 162 | def _from_name_mock(cls, query_name, name=None): 163 | """ 164 | Mock method to replace `FixedTarget.from_name` in tests without 165 | internet connection. 166 | """ 167 | # The lowercase method will be run on names, so enter keys in lowercase: 168 | stars = { 169 | "rigel": {"ra": 78.63446707*u.deg, "dec": -8.20163837*u.deg}, 170 | "sirius": {"ra": 101.28715533*u.deg, "dec": -16.71611586*u.deg}, 171 | "vega": {"ra": 279.23473479*u.deg, "dec": 38.78368896*u.deg}, 172 | "aldebaran": {"ra": 68.98016279*u.deg, "dec": 16.50930235*u.deg}, 173 | "polaris": {"ra": 37.95456067*u.deg, "dec": 89.26410897*u.deg}, 174 | "deneb": {"ra": 310.35797975*u.deg, "dec": 45.28033881*u.deg}, 175 | "m13": {"ra": 250.423475*u.deg, "dec": 36.4613194*u.deg}, 176 | "altair": {"ra": 297.6958273*u.deg, "dec": 8.8683212*u.deg}, 177 | "hd 209458": {"ra": 330.79*u.deg, "dec": 18.88*u.deg} 178 | } 179 | 180 | if query_name.lower() in stars: 181 | return cls(coord=SkyCoord(**stars[query_name.lower()]), 182 | name=query_name) 183 | else: 184 | raise ValueError("Target named {} not in mocked FixedTarget " 185 | "method".format(query_name)) 186 | 187 | 188 | class NonFixedTarget(Target): 189 | """ 190 | Placeholder for future function. 191 | """ 192 | 193 | 194 | def get_skycoord(targets): 195 | """ 196 | Return an `~astropy.coordinates.SkyCoord` object. 197 | 198 | When performing calculations it is usually most efficient to have 199 | a single `~astropy.coordinates.SkyCoord` object, rather than a 200 | list of `FixedTarget` or `~astropy.coordinates.SkyCoord` objects. 201 | 202 | This is a convenience routine to do that. 203 | 204 | Parameters 205 | ----------- 206 | targets : list, `~astropy.coordinates.SkyCoord`, `Fixedtarget` 207 | either a single target or a list of targets 208 | 209 | Returns 210 | -------- 211 | coord : `~astropy.coordinates.SkyCoord` 212 | a single SkyCoord object, which may be non-scalar 213 | """ 214 | if not isinstance(targets, list): 215 | return getattr(targets, 'coord', targets) 216 | 217 | # get the SkyCoord object itself 218 | coords = [getattr(target, 'coord', target) for target in targets] 219 | 220 | # are all SkyCoordinate's in equivalent frames? If not, convert to ICRS 221 | convert_to_icrs = not all( 222 | [coord.frame.is_equivalent_frame(coords[0].frame) for coord in coords[1:]]) 223 | 224 | # we also need to be careful about handling mixtures of 225 | # UnitSphericalRepresentations and others 226 | targets_is_unitsphericalrep = [x.data.__class__ is 227 | UnitSphericalRepresentation for x in coords] 228 | 229 | longitudes = [] 230 | latitudes = [] 231 | distances = [] 232 | get_distances = not all(targets_is_unitsphericalrep) 233 | if convert_to_icrs: 234 | # mixture of frames 235 | for coordinate in coords: 236 | icrs_coordinate = coordinate.icrs 237 | longitudes.append(icrs_coordinate.ra) 238 | latitudes.append(icrs_coordinate.dec) 239 | if get_distances: 240 | distances.append(icrs_coordinate.distance) 241 | frame = ICRS() 242 | else: 243 | # all the same frame, get the longitude and latitude names 244 | try: 245 | # from astropy v2.0, keys are classes 246 | lon_name, lat_name = [ 247 | mapping.framename for mapping in 248 | coords[0].frame_specific_representation_info[UnitSphericalRepresentation]] 249 | except BaseException: # whereas prior to that they were strings. 250 | lon_name, lat_name = [mapping.framename for mapping in 251 | coords[0].frame_specific_representation_info['spherical']] 252 | 253 | frame = coords[0].frame 254 | for coordinate in coords: 255 | longitudes.append(getattr(coordinate, lon_name)) 256 | latitudes.append(getattr(coordinate, lat_name)) 257 | if get_distances: 258 | distances.append(coordinate.distance) 259 | 260 | # now let's deal with the fact that we may have a mixture of coords with distances and 261 | # coords with UnitSphericalRepresentations 262 | if all(targets_is_unitsphericalrep): 263 | return SkyCoord(longitudes, latitudes, frame=frame) 264 | elif not any(targets_is_unitsphericalrep): 265 | return SkyCoord(longitudes, latitudes, distances, frame=frame) 266 | else: 267 | """ 268 | We have a mixture of coords with distances and without. 269 | Since we don't know in advance the origin of the frame where further transformation 270 | will take place, it's not safe to drop the distances from those coords with them set. 271 | 272 | Instead, let's assign large distances to those objects with none. 273 | """ 274 | distances = [distance if distance != 1 else 100*u.kpc for distance in distances] 275 | return SkyCoord(longitudes, latitudes, distances, frame=frame) 276 | 277 | 278 | class SpecialObjectFlag(object): 279 | """ 280 | Flag this object as a special non-fixed target, which has a ``get_*`` method 281 | within astropy (like the Sun or Moon) 282 | """ 283 | pass 284 | 285 | 286 | class SunFlag(SpecialObjectFlag): 287 | """ 288 | Flag for a computation with the Sun 289 | """ 290 | approx_sidereal_drift = 5 * u.min 291 | 292 | 293 | class MoonFlag(SpecialObjectFlag): 294 | """ 295 | Flag for a computation with the Moon 296 | """ 297 | approx_sidereal_drift = 60 * u.min 298 | -------------------------------------------------------------------------------- /astroplan/periodic.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | import numpy as np 5 | import astropy.units as u 6 | from astropy.time import Time 7 | 8 | __all__ = ['PeriodicEvent', 'EclipsingSystem'] 9 | 10 | 11 | class PeriodicEvent(object): 12 | """ 13 | A periodic event defined by an epoch and period. 14 | """ 15 | @u.quantity_input(period=u.day, duration=u.day) 16 | def __init__(self, epoch, period, duration=None, name=None): 17 | """ 18 | 19 | Parameters 20 | ---------- 21 | epoch : `~astropy.time.Time` 22 | Time of event 23 | period : `~astropy.units.Quantity` 24 | Period of event 25 | duration : `~astropy.units.Quantity` (optional) 26 | Duration of event 27 | name : str (optional) 28 | Name of target/event 29 | """ 30 | self.epoch = epoch 31 | self.period = period 32 | self.name = name 33 | self.duration = duration 34 | 35 | def phase(self, time): 36 | """ 37 | Phase of periodic event, on interval [0, 1). For example, the phase 38 | could be an orbital phase for an eclipsing binary system. 39 | 40 | Parameters 41 | ---------- 42 | time : `~astropy.time.Time` 43 | Evaluate the phase at this time or times 44 | 45 | Returns 46 | ------- 47 | phase_array : `~numpy.ndarray` 48 | Phase at each ``time``, on range [0, 1) 49 | """ 50 | return ((time - self.epoch).to(u.day).value % 51 | self.period.to(u.day).value) / self.period.to(u.day).value 52 | 53 | 54 | class EclipsingSystem(PeriodicEvent): 55 | """ 56 | Define parameters for an eclipsing system; useful for an eclipsing binary or 57 | transiting exoplanet. 58 | 59 | .. warning:: 60 | There are currently two major caveats in the implementation of 61 | ``EclipsingSystem``. The secondary eclipse time approximation is 62 | only accurate when the orbital eccentricity is small, and the eclipse 63 | times are computed without any barycentric corrections. The current 64 | implementation should only be used forapproximate mid-eclipse times for 65 | low eccentricity orbits, with event durations longer than the 66 | barycentric correction error (<=16 minutes). 67 | """ 68 | @u.quantity_input(period=u.day, duration=u.day) 69 | def __init__(self, primary_eclipse_time, orbital_period, duration=None, 70 | name=None, eccentricity=None, argument_of_periapsis=None): 71 | """ 72 | Parameters 73 | ---------- 74 | primary_eclipse_time : `~astropy.time.Time` 75 | Time of primary eclipse 76 | orbital_period : `~astropy.units.Quantity` 77 | Orbital period of eclipsing system 78 | duration : `~astropy.units.Quantity` (optional) 79 | Duration of eclipse 80 | name : str (optional) 81 | Name of target/event 82 | eccentricity : float (optional) 83 | Orbital eccentricity. Default is `None`, which assumes circular 84 | orbit (e=0). 85 | argument_of_periapsis : float (optional) 86 | Argument of periapsis for the eclipsing system, in radians. 87 | Default is `None`, which assumes pi/2. 88 | """ 89 | self.epoch = primary_eclipse_time 90 | self.period = orbital_period 91 | self.name = name 92 | self.duration = duration 93 | 94 | if eccentricity is None: 95 | eccentricity = 0 96 | self.eccentricity = eccentricity 97 | 98 | if argument_of_periapsis is None: 99 | argument_of_periapsis = np.pi/2 100 | self.argument_of_periapsis = argument_of_periapsis 101 | 102 | def in_primary_eclipse(self, time): 103 | """ 104 | Returns `True` when ``time`` is during a primary eclipse. 105 | 106 | .. warning:: 107 | Barycentric offsets are ignored in the current implementation. 108 | 109 | Parameters 110 | ---------- 111 | time : `~astropy.time.Time` 112 | Time to evaluate 113 | 114 | Returns 115 | ------- 116 | in_eclipse : `~numpy.ndarray` or bool 117 | `True` if ``time`` is during primary eclipse 118 | """ 119 | phases = self.phase(time) 120 | return ((phases < float(self.duration/self.period)/2) | 121 | (phases > 1 - float(self.duration/self.period)/2)) 122 | 123 | def in_secondary_eclipse(self, time): 124 | r""" 125 | Returns `True` when ``time`` is during a secondary eclipse 126 | 127 | If the eccentricity of the eclipsing system is non-zero, then we compute 128 | the secondary eclipse time approximated to first order in eccentricity, 129 | as described in Winn (2010) Equation 33 [1]_: 130 | 131 | The time between the primary eclipse and secondary eclipse :math:`\delta t_c` 132 | is given by :math:`\delta t_c \approx 0.5 \left (\frac{4}{\pi} e \cos{\omega \right)`, 133 | where :math:`e` is the orbital eccentricity and :math:`\omega` is the 134 | angle of periapsis. 135 | 136 | .. warning:: 137 | This approximation for the secondary eclipse time is only accurate 138 | when the orbital eccentricity is small; and barycentric offsets 139 | are ignored in the current implementation. 140 | 141 | Parameters 142 | ---------- 143 | time : `~astropy.time.Time` 144 | Time to evaluate 145 | 146 | Returns 147 | ------- 148 | in_eclipse : `~numpy.ndarray` or bool 149 | `True` if ``time`` is during secondary eclipse 150 | 151 | References 152 | ---------- 153 | .. [1] Winn (2010) https://arxiv.org/abs/1001.2010 154 | """ 155 | if self.eccentricity < 1e-5: 156 | secondary_eclipse_phase = 0.5 157 | else: 158 | secondary_eclipse_phase = 0.5 * (1 + 4/np.pi * self.eccentricity * 159 | np.cos(self.argument_of_periapsis)) 160 | phases = self.phase(time) 161 | return ((phases < secondary_eclipse_phase + float(self.duration/self.period)/2) & 162 | (phases > secondary_eclipse_phase - float(self.duration/self.period)/2)) 163 | 164 | def out_of_eclipse(self, time): 165 | """ 166 | Returns `True` when ``time`` is not during primary or secondary eclipse. 167 | 168 | .. warning:: 169 | Barycentric offsets are ignored in the current implementation. 170 | 171 | Parameters 172 | ---------- 173 | time : `~astropy.time.Time` 174 | Time to evaluate 175 | 176 | Returns 177 | ------- 178 | in_eclipse : `~numpy.ndarray` or bool 179 | `True` if ``time`` is not during primary or secondary eclipse 180 | """ 181 | return np.logical_not(np.logical_or(self.in_primary_eclipse(time), 182 | self.in_secondary_eclipse(time))) 183 | 184 | def next_primary_eclipse_time(self, time, n_eclipses=1): 185 | """ 186 | Time of the next primary eclipse after ``time``. 187 | 188 | .. warning:: 189 | Barycentric offsets are ignored in the current implementation. 190 | 191 | Parameters 192 | ---------- 193 | time : `~astropy.time.Time` 194 | Find the next primary eclipse after ``time`` 195 | n_eclipses : int (optional) 196 | Return the times of eclipse for the next ``n_eclipses`` after 197 | ``time``. Default is 1. 198 | 199 | Returns 200 | ------- 201 | primary_eclipses : `~astropy.time.Time` 202 | Times of the next ``n_eclipses`` primary eclipses after ``time`` 203 | """ 204 | eclipse_times = ((1-self.phase(time)) * self.period + time + 205 | np.arange(n_eclipses) * self.period) 206 | return eclipse_times 207 | 208 | def next_secondary_eclipse_time(self, time, n_eclipses=1): 209 | """ 210 | Time of the next secondary eclipse after ``time``. 211 | 212 | .. warning:: 213 | Barycentric offsets are ignored in the current implementation. 214 | 215 | Parameters 216 | ---------- 217 | time : `~astropy.time.Time` 218 | Find the next secondary eclipse after ``time`` 219 | n_eclipses : int (optional) 220 | Return the times of eclipse for the next ``n_eclipses`` after 221 | ``time``. Default is 1. 222 | 223 | Returns 224 | ------- 225 | secondary_eclipses : `~astropy.time.Time` 226 | Times of the next ``n_eclipses`` secondary eclipses after ``time`` 227 | """ 228 | phase = self.phase(time) 229 | if phase >= 0.5: 230 | next_eclipse_phase = 1.5 231 | else: 232 | next_eclipse_phase = 0.5 233 | eclipse_times = ((next_eclipse_phase - phase) * self.period + time + 234 | np.arange(n_eclipses) * self.period) 235 | return eclipse_times 236 | 237 | def next_primary_ingress_egress_time(self, time, n_eclipses=1): 238 | """ 239 | Calculate the times of ingress and egress for the next ``n_eclipses`` 240 | primary eclipses after ``time`` 241 | 242 | .. warning:: 243 | Barycentric offsets are ignored in the current implementation. 244 | 245 | Parameters 246 | ---------- 247 | time : `~astropy.time.Time` 248 | Find the next primary ingress and egress after ``time`` 249 | n_eclipses : int (optional) 250 | Return the times of eclipse for the next ``n_eclipses`` after 251 | ``time``. Default is 1. 252 | 253 | Returns 254 | ------- 255 | primary_eclipses : `~astropy.time.Time` of shape (``n_eclipses``, 2) 256 | Times of ingress and egress for the next ``n_eclipses`` primary 257 | eclipses after ``time`` 258 | """ 259 | next_mid_eclipses = self.next_primary_eclipse_time(time, n_eclipses=n_eclipses) 260 | next_ingresses = next_mid_eclipses - self.duration/2 261 | next_egresses = next_mid_eclipses + self.duration/2 262 | 263 | ing_egr = np.vstack([next_ingresses.utc.jd, next_egresses.utc.jd]).T 264 | 265 | return Time(ing_egr, format='jd', scale='utc') 266 | 267 | def next_secondary_ingress_egress_time(self, time, n_eclipses=1): 268 | """ 269 | Calculate the times of ingress and egress for the next ``n_eclipses`` 270 | secondary eclipses after ``time`` 271 | 272 | .. warning:: 273 | Barycentric offsets are ignored in the current implementation. 274 | 275 | Parameters 276 | ---------- 277 | time : `~astropy.time.Time` 278 | Find the next secondary ingress and egress after ``time`` 279 | n_eclipses : int (optional) 280 | Return the times of eclipse for the next ``n_eclipses`` after 281 | ``time``. Default is 1. 282 | 283 | Returns 284 | ------- 285 | secondary_eclipses : `~astropy.time.Time` of shape (``n_eclipses``, 2) 286 | Times of ingress and egress for the next ``n_eclipses`` secondary 287 | eclipses after ``time``. 288 | """ 289 | next_mid_eclipses = self.next_secondary_eclipse_time(time, n_eclipses=n_eclipses) 290 | next_ingresses = next_mid_eclipses - self.duration/2 291 | next_egresses = next_mid_eclipses + self.duration/2 292 | 293 | ing_egr = np.vstack([next_ingresses.utc.jd, next_egresses.utc.jd]).T 294 | 295 | return Time(ing_egr, format='jd', scale='utc') 296 | -------------------------------------------------------------------------------- /astroplan/utils.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | # Standard library 6 | import warnings 7 | 8 | # Third-party 9 | import numpy as np 10 | from astropy.utils.data import download_file, clear_download_cache 11 | from astropy.utils import iers 12 | from astropy.time import Time 13 | import astropy.units as u 14 | from astropy.utils.data import _get_download_cache_locs, CacheMissingWarning 15 | from astropy.coordinates import EarthLocation 16 | 17 | # Package 18 | from .exceptions import OldEarthOrientationDataWarning 19 | 20 | __all__ = ["get_IERS_A_or_workaround", "download_IERS_A", 21 | "time_grid_from_range", "_set_mpl_style_sheet", 22 | "stride_array"] 23 | 24 | IERS_A_WARNING = ("For best precision (on the order of arcseconds), you must " 25 | "download an up-to-date IERS Bulletin A table. To do so, run:" 26 | "\n\n" 27 | ">>> from astroplan import download_IERS_A\n" 28 | ">>> download_IERS_A()\n") 29 | 30 | BACKUP_Time_get_delta_ut1_utc = Time._get_delta_ut1_utc 31 | 32 | 33 | def _low_precision_utc_to_ut1(self, jd1, jd2): 34 | """ 35 | When no IERS Bulletin A is available (no internet connection), use low 36 | precision time conversion by assuming UT1-UTC=0 always. 37 | This method mimics `~astropy.coordinates.builtin_frames.utils.get_dut1utc` 38 | """ 39 | try: 40 | if self.mjd*u.day not in iers.IERS_Auto.open()['MJD']: 41 | warnings.warn(IERS_A_WARNING, OldEarthOrientationDataWarning) 42 | return self.delta_ut1_utc 43 | 44 | except (AttributeError, ValueError): 45 | warnings.warn(IERS_A_WARNING, OldEarthOrientationDataWarning) 46 | return np.zeros(self.shape) 47 | 48 | 49 | def get_IERS_A_or_workaround(): 50 | """ 51 | Get the cached IERS Bulletin A table if one exists. If one does not exist, 52 | monkey patch `~astropy.time.Time._get_delta_ut1_utc` so that 53 | `~astropy.time.Time` objects don't raise errors by computing UT1-UTC off 54 | the end of the IERS table. 55 | """ 56 | if IERS_A_in_cache(): 57 | iers.IERS.iers_table = _get_IERS_A_table() 58 | else: 59 | Time._get_delta_ut1_utc = _low_precision_utc_to_ut1 60 | 61 | 62 | def IERS_A_in_cache(): 63 | """ 64 | Check if the IERS Bulletin A table is locally cached. 65 | """ 66 | url_key = iers.IERS_A_URL 67 | # The below code which accesses ``urlmapfn`` is stolen from 68 | # astropy.utils.data.download_file() 69 | try: 70 | dldir, urlmapfn = _get_download_cache_locs() 71 | except (IOError, OSError) as e: 72 | msg = 'Remote data cache could not be accessed due to ' 73 | estr = '' if len(e.args) < 1 else (': ' + str(e)) 74 | warnings.warn(CacheMissingWarning(msg + e.__class__.__name__ + estr)) 75 | return False 76 | with _open_shelve(urlmapfn, True) as url2hash: 77 | # TODO: try to figure out how to test this in the unicode case 78 | if str(url_key) in url2hash: 79 | return True 80 | return False 81 | 82 | 83 | def _get_IERS_A_table(warn_update=14*u.day): 84 | """ 85 | Grab the locally cached copy of the IERS Bulletin A table. Check to see 86 | if it's up to date, and warn the user if it is not. 87 | 88 | This will fail and raise OSError if the file is not in the cache. 89 | """ 90 | if IERS_A_in_cache(): 91 | table = iers.IERS_Auto.open() 92 | # Use polar motion flag to identify last observation before predictions 93 | index_of_last_observation = ''.join(table['PolPMFlag_A']).index('IP') 94 | time_of_last_observation = Time(table['MJD'][index_of_last_observation], 95 | format='mjd') 96 | time_since_last_update = Time.now() - time_of_last_observation 97 | 98 | # If the IERS bulletin is more than `warn_update` days old, warn user 99 | if warn_update < time_since_last_update: 100 | warnmsg = ("Your version of the IERS Bulletin A is {:.1f} days " 101 | "old. ".format(time_since_last_update.to(u.day).value) + 102 | IERS_A_WARNING) 103 | warnings.warn(warnmsg, OldEarthOrientationDataWarning) 104 | return table 105 | else: 106 | raise OSError("No IERS A table has been downloaded.") 107 | 108 | 109 | def download_IERS_A(show_progress=True): 110 | """ 111 | Download and cache the IERS Bulletin A table. 112 | 113 | If one is already cached, download a new one and overwrite the old. Store 114 | table in the astropy cache, and undo the monkey patching done by 115 | `~astroplan.get_IERS_A_or_workaround`. 116 | 117 | Parameters 118 | ---------- 119 | show_progress : bool 120 | `True` shows a progress bar during the download. 121 | """ 122 | if IERS_A_in_cache(): 123 | clear_download_cache(iers.IERS_A_URL) 124 | 125 | local_iers_a_path = download_file(iers.IERS_A_URL, cache=True, 126 | show_progress=show_progress) 127 | # Undo monkey patch set up by get_IERS_A_or_workaround 128 | iers.IERS.iers_table = iers.IERS_A.open(local_iers_a_path) 129 | Time._get_delta_ut1_utc = BACKUP_Time_get_delta_ut1_utc 130 | 131 | 132 | @u.quantity_input(time_resolution=u.hour) 133 | def time_grid_from_range(time_range, time_resolution=0.5*u.hour): 134 | """ 135 | Get linearly-spaced sequence of times. 136 | 137 | Parameters 138 | ---------- 139 | time_range : `~astropy.time.Time` (length = 2) 140 | Lower and upper bounds on time sequence. 141 | 142 | time_resolution : `~astropy.units.quantity` (optional) 143 | Time-grid spacing 144 | 145 | Returns 146 | ------- 147 | times : `~astropy.time.Time` 148 | Linearly-spaced sequence of times 149 | """ 150 | try: 151 | start_time, end_time = time_range 152 | except ValueError: 153 | raise ValueError("time_range should have a length of 2: lower and " 154 | "upper bounds on the time sequence.") 155 | return Time(np.arange(start_time.jd, end_time.jd, 156 | time_resolution.to(u.day).value), format='jd') 157 | 158 | 159 | def _mock_remote_data(): 160 | """ 161 | Apply mocks (i.e. monkey-patches) to avoid the need for internet access 162 | for certain things. 163 | 164 | This is currently called in `astroplan/conftest.py` when the tests are run 165 | and the `--remote-data` option isn't used. 166 | 167 | The way this setup works is that for functionality that usually requires 168 | internet access, but has mocks in place, it is possible to write the test 169 | without adding a `@remote_data` decorator, and `py.test` will do the right 170 | thing when running the tests: 171 | 172 | 1. Access the internet and use the normal code if `--remote-data` is used 173 | 2. Not access the internet and use the mock code if `--remote-data` is not used 174 | 175 | Both of these cases are tested on travis-ci. 176 | """ 177 | from .target import FixedTarget 178 | from astropy.coordinates import EarthLocation 179 | 180 | if not hasattr(FixedTarget, '_real_from_name'): 181 | FixedTarget._real_from_name = FixedTarget.from_name 182 | FixedTarget.from_name = FixedTarget._from_name_mock 183 | 184 | if not hasattr(EarthLocation, '_real_of_site'): 185 | EarthLocation._real_of_site = EarthLocation.of_site 186 | EarthLocation.of_site = EarthLocation_mock.of_site_mock 187 | 188 | # otherwise already mocked 189 | 190 | 191 | def _unmock_remote_data(): 192 | """ 193 | undo _mock_remote_data 194 | currently unused 195 | """ 196 | from .target import FixedTarget 197 | 198 | if hasattr(FixedTarget, '_real_from_name'): 199 | FixedTarget.from_name = FixedTarget._real_from_name 200 | del FixedTarget._real_from_name 201 | 202 | if hasattr(EarthLocation, '_real_of_site'): 203 | EarthLocation.of_site = EarthLocation._real_of_site 204 | del EarthLocation._real_of_site 205 | # otherwise assume it's already correct 206 | 207 | 208 | def _set_mpl_style_sheet(style_sheet): 209 | """ 210 | Import matplotlib, set the style sheet to ``style_sheet`` using 211 | the most backward compatible import pattern. 212 | """ 213 | import matplotlib 214 | matplotlib.rcdefaults() 215 | matplotlib.rcParams.update(style_sheet) 216 | 217 | 218 | def stride_array(arr, window_width): 219 | """ 220 | Computes all possible sequential subarrays of arr with length = window_width 221 | 222 | Parameters 223 | ---------- 224 | arr : array-like (length = n) 225 | Linearly-spaced sequence 226 | 227 | window_width : int 228 | Number of elements in each new sub-array 229 | 230 | Returns 231 | ------- 232 | strided_arr : array (shape = (n-window_width, window_width)) 233 | Linearly-spaced sequence of times 234 | """ 235 | as_strided = np.lib.stride_tricks.as_strided 236 | 237 | new_shape = (len(arr) - window_width + 1, window_width) 238 | 239 | strided_arr = as_strided(arr, new_shape, (arr.strides[0], arr.strides[0])) 240 | 241 | return strided_arr 242 | 243 | 244 | class EarthLocation_mock(EarthLocation): 245 | """ 246 | Mock the EarthLocation class if no remote data for locations commonly 247 | used in the tests. 248 | """ 249 | @classmethod 250 | def of_site_mock(cls, string): 251 | 252 | subaru = EarthLocation.from_geodetic(-155.4761111111111*u.deg, 253 | 19.825555555555564*u.deg, 254 | 4139*u.m) 255 | 256 | lco = EarthLocation.from_geodetic(-70.70166666666665*u.deg, 257 | -29.003333333333327*u.deg, 258 | 2282*u.m) 259 | 260 | aao = EarthLocation.from_geodetic(149.06608611111113*u.deg, 261 | -31.277038888888896*u.deg, 262 | 1164*u.m) 263 | 264 | vbo = EarthLocation.from_geodetic(78.8266*u.deg, 265 | 12.576659999999999*u.deg, 266 | 725*u.m) 267 | 268 | apo = EarthLocation.from_geodetic(-105.82*u.deg, 269 | 32.78*u.deg, 270 | 2798*u.m) 271 | 272 | keck = EarthLocation.from_geodetic(-155.47833333333332*u.deg, 273 | 19.828333333333326*u.deg, 274 | 4160*u.m) 275 | 276 | kpno = EarthLocation.from_geodetic(-111.6*u.deg, 277 | 31.963333333333342*u.deg, 278 | 2120*u.m) 279 | 280 | lapalma = EarthLocation.from_geodetic(-17.879999*u.deg, 281 | 28.758333*u.deg, 282 | 2327*u.m) 283 | 284 | observatories = dict(lco=lco, subaru=subaru, aao=aao, vbo=vbo, apo=apo, 285 | keck=keck, kpno=kpno, lapalma=lapalma) 286 | 287 | return observatories[string.lower()] 288 | 289 | 290 | def _open_shelve(shelffn, withclosing=False): 291 | """ 292 | Opens a shelf file. If ``withclosing`` is True, it will be opened with 293 | closing, allowing use like: 294 | 295 | with _open_shelve('somefile',True) as s: 296 | ... 297 | 298 | This workaround can be removed in favour of using shelve.open() directly 299 | once support for Python <3.4 is dropped. 300 | """ 301 | import shelve 302 | import contextlib 303 | 304 | shelf = shelve.open(shelffn, protocol=2) 305 | 306 | if withclosing: 307 | return contextlib.closing(shelf) 308 | else: 309 | return shelf 310 | -------------------------------------------------------------------------------- /docs/tutorials/periodic.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../references.txt 2 | 3 | .. doctest-skip-all 4 | 5 | .. _periodic_tutorial: 6 | 7 | ****************************************************** 8 | Observing Transiting Exoplanets and Eclipsing Binaries 9 | ****************************************************** 10 | 11 | .. note:: 12 | The ``periodic`` module is new and under development. The API may change in 13 | upcoming versions of astroplan, and pull requests are welcome! 14 | 15 | .. warning:: 16 | 17 | There are currently two major caveats in the implementation of 18 | `~astroplan.EclipsingSystem`. The secondary eclipse time approximation is 19 | only accurate when the orbital eccentricity is small, and the eclipse 20 | times are computed without any barycentric corrections. The current 21 | implementation should only be used for approximate mid-eclipse times for 22 | low eccentricity orbits, with event durations longer than the 23 | barycentric correction error (<=16 minutes). 24 | 25 | Contents 26 | ======== 27 | 28 | * :ref:`periodic-transit_times` 29 | * :ref:`periodic-transit_times_via_astroquery` 30 | * :ref:`periodic-observable_transits` 31 | * :ref:`periodic-phase_constraint` 32 | 33 | .. _periodic-transit_times: 34 | 35 | Transit/Primary and secondary eclipse times 36 | =========================================== 37 | 38 | We can define the properties of an eclipsing system, such as an eclipsing binary 39 | or transiting exoplanet, using the `~astroplan.EclipsingSystem` object. Let's 40 | make an instance for the transiting exoplanet HD 209458 b, which has a period 41 | of 3.52474859 days, mid-transit time of JD=2452826.628514, and transit duration 42 | of 0.1277: 43 | 44 | .. code-block:: python 45 | 46 | >>> from astropy.time import Time 47 | >>> import astropy.units as u 48 | >>> from astroplan import EclipsingSystem 49 | 50 | >>> primary_eclipse_time = Time(2452826.628514, format='jd') 51 | >>> orbital_period = 3.52474859 * u.day 52 | >>> eclipse_duration = 0.1277 * u.day 53 | 54 | >>> hd209458 = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, 55 | ... orbital_period=orbital_period, duration=eclipse_duration, 56 | ... name='HD 209458 b') 57 | 58 | Let's say we're observing on 2016 January 1, 00:00 UTC. We can compute the next 59 | transit and secondary eclipse using the 60 | `~astroplan.EclipsingSystem.next_primary_eclipse_time` and 61 | `~astroplan.EclipsingSystem.next_secondary_eclipse_time` methods, respectively: 62 | 63 | .. code-block:: python 64 | 65 | >>> observing_time = Time('2016-01-01 00:00') 66 | >>> hd209458.next_primary_eclipse_time(observing_time) 67 |