diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f20b4205..21f497ba 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.0.0 +current_version = 2.4.0 commit = True tag = True @@ -7,5 +7,5 @@ tag = True [bumpversion:file:docs/conf.py] -[bumpversion:file:src/aacgmv2/__init__.py] +[bumpversion:file:aacgmv2/__init__.py] diff --git a/.codeclimate.yml b/.codeclimate.yml index b3025c95..7a814397 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -3,6 +3,6 @@ languages: Python: true exclude_paths: - "ci/" - - "tests/" + - "aacgmv2/tests/" - "docs/" - "setup.py" \ No newline at end of file diff --git a/.cookiecutterrc b/.cookiecutterrc index 9e897cbb..b4f1dc90 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -14,14 +14,14 @@ default_context: command_line_interface: 'no' coveralls: 'yes' distribution_name: 'aacgmv2' - email: 'cmeeren@gmail.com' - full_name: 'Christer van der Meeren' - github_username: 'cmeeren' + email: 'agb073000@utdallas.edu' + full_name: 'Angeline G. Burrell' + github_username: 'aburrell' landscape: 'yes' package_name: 'aacgmv2' project_name: 'AACGM-v2 Python library' project_short_description: '"A Python wrapper for AACGM-v2 magnetic coordinates"' - release_date: '2015-06-10' + release_date: '2018-03-12' repo_name: 'aacgmv2' requiresio: 'yes' scrutinizer: 'yes' @@ -29,6 +29,6 @@ default_context: test_matrix_configurator: 'yes' test_runner: 'pytest' travis: 'yes' - version: '0.1.0' - website: 'https://github.com/cmeeren/aacgmv2' - year: '2015' + version: '2.0.1' + website: 'https://github.com/aburrell/aacgmv2' + year: '2018' diff --git a/.coveragerc b/.coveragerc index 772866d2..8a55c3fe 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,9 +1,13 @@ [paths] -source = src +source = + aacgmv2 + c_aacgmv2 [run] branch = True -source = src +source = + aacgmv2 + c_aacgmv2 parallel = true [report] diff --git a/.gitignore b/.gitignore index 9e8fc169..939de61b 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ pip-log.txt .coverage .tox .coverage.* +.pytest_cache nosetests.xml coverage.xml htmlcov diff --git a/.landscape.yaml b/.landscape.yaml index 3129377d..e8c75d79 100644 --- a/.landscape.yaml +++ b/.landscape.yaml @@ -3,5 +3,5 @@ doc-warnings: yes ignore-paths: - docs - ci - - tests + - aacgmv2/tests - setup.py \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 1fbd6d93..76753986 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,54 +1,26 @@ language: python -python: '3.5' +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" sudo: false env: global: LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - matrix: - - TOXENV=check - - TOXENV=2.7-np1.10,extension-coveralls,coveralls - - TOXENV=2.7-np1.10-nocover - - TOXENV=2.7-np1.8,extension-coveralls,coveralls - - TOXENV=2.7-np1.8-nocover - - TOXENV=2.7-np1.9,extension-coveralls,coveralls - - TOXENV=2.7-np1.9-nocover - - TOXENV=3.3-np1.10,extension-coveralls,coveralls - - TOXENV=3.3-np1.10-nocover - - TOXENV=3.3-np1.8,extension-coveralls,coveralls - - TOXENV=3.3-np1.8-nocover - - TOXENV=3.3-np1.9,extension-coveralls,coveralls - - TOXENV=3.3-np1.9-nocover - - TOXENV=3.4-np1.10,extension-coveralls,coveralls - - TOXENV=3.4-np1.10-nocover - - TOXENV=3.4-np1.8,extension-coveralls,coveralls - - TOXENV=3.4-np1.8-nocover - - TOXENV=3.4-np1.9,extension-coveralls,coveralls - - TOXENV=3.4-np1.9-nocover - - TOXENV=3.5-np1.10,extension-coveralls,coveralls - - TOXENV=3.5-np1.10-nocover - - TOXENV=3.5-np1.8,extension-coveralls,coveralls - - TOXENV=3.5-np1.8-nocover - - TOXENV=3.5-np1.9,extension-coveralls,coveralls - - TOXENV=3.5-np1.9-nocover before_install: - python --version - uname -a - lsb_release -a install: - - pip install tox - - virtualenv --version - - easy_install --version - - pip --version - - tox --version + - pip install coveralls + - "python setup.py install" + - pip install tox-travis script: - - tox -v -after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat + - tox + - coverage run --source aacgmv2 -m py.test +after_sucess: coveralls notifications: email: on_success: never - on_failure: always -matrix: - allow_failures: - - env: "TOXENV=check" + on_failure: always \ No newline at end of file diff --git a/AUTHORS.rst b/AUTHORS.rst index ae2f6f32..b5c919ad 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -2,5 +2,6 @@ Authors ======= -* Christer van der Meeren - https://github.com/cmeeren +* Angeline G. Burrell - https://github.com/aburrell +* Christer van der Meeren * Karl M. Laundal diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c2528abe..6029d0e1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ Changelog ========= +2.4.0 (2017-03-21) +----------------------------------------- + +* Update to use AACGM-v2.4, which includes changes to the inverse MLT and + dipole tilt functions and some minor bug fixes +* Updated file structure +* Updated methods, retaining old methods in deprecated module +* Added testing for python 3.6 +* Updated dependencies, removing support for python 3.3 +* Tested on Mac OSX +* Updated comments to include units for input and output + 2.0.0 (2016-11-03) ----------------------------------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index b16b8cb1..a3a231db 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -2,18 +2,21 @@ Contributing ============ -Bug reports, feature suggestions and other contributions are greatly appreciated! While I can't promise to implement everything, I will always try to respond in a timely manner. +Bug reports, feature suggestions and other contributions are greatly +appreciated! While I can't promise to implement everything, I will always try +to respond in a timely manner. Short version ============= -* Submit bug reports and feature requests at `GitHub `_ +* Submit bug reports and feature requests at `GitHub `_ * Make pull requests to the ``develop`` branch Bug reports =========== -When `reporting a bug `_ please include: +When `reporting a bug `_ please +include: * Your operating system name and version * Any details about your local setup that might be helpful in troubleshooting @@ -22,20 +25,22 @@ When `reporting a bug `_ please inclu Feature requests and feedback ============================= -The best way to send feedback is to file an issue at `GitHub `_. +The best way to send feedback is to file an issue at +`GitHub `_. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. -* Remember that this is a volunteer-driven project, and that code contributions are welcome :) +* Remember that this is a volunteer-driven project, and that code contributions + are welcome :) Development =========== To set up `aacgmv2` for local development: -1. `Fork aacgmv2 on GitHub `_. +1. `Fork aacgmv2 on GitHub `_. 2. Clone your fork locally:: git clone git@github.com:your_name_here/aacgmv2.git @@ -44,9 +49,16 @@ To set up `aacgmv2` for local development: git checkout -b name-of-your-bugfix-or-feature - Now you can make your changes locally. Add tests for bugs and new features in ``tests/test_py_aacgmv2.py`` (for the wrapper), ``test_c_aacgmv2.py`` (for the C extension), or ``tests/test_cmd_aacgmv2.py`` (for the command-line interface). The tests are run with ``py.test`` and can be written as normal functions (starting with ``test_``) containing a standard ``assert`` statement for testing output. + Now you can make your changes locally. Add tests for bugs and new features + in ``tests/test_py_aacgmv2.py`` (for the wrapper), ``test_c_aacgmv2.py`` + (for the C extension), or ``tests/test_cmd_aacgmv2.py`` (for the + command-line interface). ``tests/test_dep_aacgmv2.py`` includes tests for + deprecated functions. The tests are run with ``py.test`` and can be + written as normal functions (starting with ``test_``) containing a standard + ``assert`` statement for testing output, or use the numpy testing suite. -4. When you're done making changes, run all the checks, doc builder and spell checker with `tox `_ [1]_:: +4. When you're done making changes, run all the checks, doc builder and spell + checker with `tox `_ [1]_:: tox @@ -56,12 +68,14 @@ To set up `aacgmv2` for local development: git commit -m "Brief description of your changes" git push origin name-of-your-bugfix-or-feature -6. Submit a pull request through the GitHub website. Pull requests should be made to the ``develop`` branch. +6. Submit a pull request through the GitHub website. Pull requests should be + made to the ``develop`` branch. Pull Request Guidelines ----------------------- -If you need some code review or feedback while you're developing the code, just make a pull request. +If you need some code review or feedback while you're developing the code, just +make a pull request. For merging, you should: @@ -70,9 +84,10 @@ For merging, you should: 3. Add a note to ``CHANGELOG.rst`` about the changes 4. Add yourself to ``AUTHORS.rst`` -.. [1] If you don't have all the necessary Python versions available locally or have trouble - building NumPy in all the testing environments, you can rely on Travis and - AppVeyor - they will run the tests for each change you add in the pull request. +.. [1] If you don't have all the necessary Python versions available locally or + have trouble building all the testing environments, you can rely on + Travis and AppVeyor - they will run the tests for each change you add in + the pull request. Tips ---- diff --git a/LICENSE-AstAlg.txt b/LICENSE-AstAlg.txt new file mode 100644 index 00000000..c0efb746 --- /dev/null +++ b/LICENSE-AstAlg.txt @@ -0,0 +1,28 @@ +Copyright and License Information + + This source file is part of a library of files implementing + portions of the algorithms given in the book _Astronomical + Algorithms_ by Jean Meeus. + + Software Copyright (C) 2006, U.S. Government + Author: Kile B. Baker + National Science Foundation + 4201 Wilson Blvd, + Arlington, VA 22230 + email: kbaker@nsf.gov + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + diff --git a/MANIFEST.in b/MANIFEST.in index 05582f8b..e7e51b8c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,10 @@ graft docs -graft examples -graft src graft ci -graft tests +graft aacgmv2/tests +graft c_aacgmv2 + +recursive-include aacgmv2 *.asc +recursive-include aacgmv2 *.txt include .bumpversion.cfg include .coveragerc @@ -14,8 +16,9 @@ include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE +include LICENSE-AstAlg.txt include README.rst include tox.ini .travis.yml appveyor.yml .codeclimate.yml .landscape.yaml -global-exclude *.py[cod] __pycache__ *.so *.dylib +global-exclude *.py[cod] __pycache__ *.so *.dylib *.dSYM .pytest_cache diff --git a/README.rst b/README.rst index 573e73da..8dbc4b77 100644 --- a/README.rst +++ b/README.rst @@ -5,48 +5,53 @@ Overview |docs| |version| This is a Python wrapper for the `AACGM-v2 C library -`_, which allows converting between geographic and magnetic coordinates. The currently included version of the C library is 2.2. The wrapper is provided "as is" in the hopes that it will be useful to the space science community, and will not automatically be updated when new versions of the C library is released. MLT calculations are included in the wrapper (not part of the C library, please see the documentation for implementation details). The package is free software (MIT license). +`_, which allows +converting between geographic and magnetic coordinates. The currently included +version of the C library is 2.4. The package is free software +(MIT license). Quick start =========== -Install (requires NumPy):: +Install (requires NumPy and logging):: pip install aacgmv2 Convert between AACGM and geographic coordinates:: - >>> from aacgmv2 import convert - >>> from datetime import date + >>> import aacgmv2 + >>> import datetime as dt + >>> import numpy as np + >>> np.set_printoptions(formatter={'float_kind': lambda x:'{:.4f}'.format(x)}) >>> # geo to AACGM, single numbers - >>> mlat, mlon = convert(60, 15, 300, date(2013, 11, 3)) - >>> mlat - array(57.47207691280528) - >>> mlon - array(93.62138045643167) + >>> dtime = dt.datetime(2013, 11, 3) + >>> np.array(aacgmv2.get_aacgm_coord(60, 15, 300, dtime)) + array([57.4698, 93.6300, 1.4822]) >>> # AACGM to geo, mix arrays/numbers - >>> glat, glon = convert([90, -90], 0, 0, date(2013, 11, 3), a2g=True) - >>> glat - array([ 82.96656071, -74.33854592]) - >>> glon - array([ -84.66516034, 125.84014944]) + >>> aacgmv2.convert_latlon_arr([90, -90], 0, 0, dtime, code="A2G") + (array([82.9666, -74.3385]), array([-84.6652, 125.8401]), array([14.1244, 12.8771])) Convert between AACGM and MLT:: - >>> from aacgmv2 import convert_mlt - >>> from datetime import datetime + >>> import aacgmv2 + >>> import datetime as dt + >>> import numpy as np + >>> np.set_printoptions(formatter={'float_kind': lambda x:'{:.4f}'.format(x)}) >>> # MLT to AACGM - >>> mlon = convert_mlt([0, 12], datetime(2013, 11, 3, 18, 0), m2a=True) - >>> mlon - array([ 159.08967974, 339.08967974]) + >>> dtime = dt.datetime(2013, 11, 3, 0, 0, 0) + >>> aacgmv2.convert_mlt([1.4822189, 12], dtime, m2a=True) + array([93.6300, -108.6033]) -If you don't know or use Python, you can also use the command line. See details in the full documentation. +If you don't know or use Python, you can also use the command line. See details +in the full documentation. Documentation ============= https://aacgmv2.readthedocs.org/ +http://superdarn.thayer.dartmouth.edu/aacgm.html + Badges ====== @@ -57,7 +62,6 @@ Badges - |docs| * - tests - | |travis| |appveyor| |requires| - | |coveralls| |codecov| | |landscape| |codeclimate| | |scrutinizer| |codacy| * - package @@ -68,36 +72,36 @@ Badges :target: https://readthedocs.org/projects/aacgmv2 :alt: Documentation Status -.. |travis| image:: https://travis-ci.org/cmeeren/aacgmv2.svg?branch=master +.. |travis| image:: https://travis-ci.org/aburrell/aacgmv2.svg?branch=master :alt: Travis-CI Build Status - :target: https://travis-ci.org/cmeeren/aacgmv2 + :target: https://travis-ci.org/aburrell/aacgmv2 -.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/cmeeren/aacgmv2?branch=master&svg=true +.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/aburrell/aacgmv2?branch=master&svg=true :alt: AppVeyor Build Status - :target: https://ci.appveyor.com/project/cmeeren/aacgmv2 + :target: https://ci.appveyor.com/project/aburrell/aacgmv2 -.. |requires| image:: https://requires.io/github/cmeeren/aacgmv2/requirements.svg?branch=master +.. |requires| image:: https://requires.io/github/aburrell/aacgmv2/requirements.svg?branch=master :alt: Requirements Status - :target: https://requires.io/github/cmeeren/aacgmv2/requirements/?branch=master + :target: https://requires.io/github/aburrell/aacgmv2/requirements/?branch=master -.. |coveralls| image:: https://coveralls.io/repos/cmeeren/aacgmv2/badge.svg?branch=master&service=github +.. |coveralls| image:: https://coveralls.io/repos/aburrell/aacgmv2/badge.svg?branch=master&service=github :alt: Coverage Status - :target: https://coveralls.io/github/cmeeren/aacgmv2 + :target: https://coveralls.io/github/aburrell/aacgmv2 -.. |codecov| image:: https://codecov.io/github/cmeeren/aacgmv2/coverage.svg?branch=master +.. |codecov| image:: https://codecov.io/github/aburrell/aacgmv2/coverage.svg?branch=master :alt: Coverage Status - :target: https://codecov.io/github/cmeeren/aacgmv2 + :target: https://codecov.io/github/aburrell/aacgmv2 -.. |landscape| image:: https://landscape.io/github/cmeeren/aacgmv2/master/landscape.svg?style=flat - :target: https://landscape.io/github/cmeeren/aacgmv2/master +.. |landscape| image:: https://landscape.io/github/aburrell/aacgmv2/master/landscape.svg?style=flat + :target: https://landscape.io/github/aburrell/aacgmv2/master :alt: Code Quality Status -.. |codacy| image:: https://img.shields.io/codacy/af7fdf6be28841f283dfdbc1c01fa82a.svg?style=flat - :target: https://www.codacy.com/app/cmeeren/aacgmv2 - :alt: Codacy Code Quality Status +.. |codacy| image:: https://api.codacy.com/project/badge/Grade/b64ee44194f148f5bdb0f00c7cf16ab8 + :target: https://www.codacy.com/app/aburrell/aacgmv2?utm_source=github.com&utm_medium=referral&utm_content=aburrell/aacgmv2&utm_campaign=Badge_Grade + :alt: Codacy Code Quality Status -.. |codeclimate| image:: https://codeclimate.com/github/cmeeren/aacgmv2/badges/gpa.svg - :target: https://codeclimate.com/github/cmeeren/aacgmv2 +.. |codeclimate| image:: https://codeclimate.com/github/aburrell/aacgmv2/badges/gpa.svg + :target: https://codeclimate.com/github/aburrell/aacgmv2 :alt: CodeClimate Quality Status .. |version| image:: https://img.shields.io/pypi/v/aacgmv2.svg?style=flat :alt: PyPI Package latest release @@ -119,6 +123,6 @@ Badges :alt: Supported implementations :target: https://pypi.python.org/pypi/aacgmv2 -.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/cmeeren/aacgmv2/master.svg?style=flat +.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/aburrell/aacgmv2/master.svg?style=flat :alt: Scrutinizer Status - :target: https://scrutinizer-ci.com/g/cmeeren/aacgmv2/ + :target: https://scrutinizer-ci.com/g/aburrell/aacgmv2/ diff --git a/aacgmv2/__init__.py b/aacgmv2/__init__.py new file mode 100644 index 00000000..319a37cf --- /dev/null +++ b/aacgmv2/__init__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +"""aacgmv2 + +Modules +--------------------------------------------------------------------------- +_aacgmv2 : Contains functions and variables from c code +deprecated : Contains deprecated functions from previous versions +wrapper : Contains current python functions +--------------------------------------------------------------------------- + +Parameters +--------------------------------------------------------------------------- +AACGM_V2_DAT_PREFIX +IGRF_12_COEFFS +_aacgmv2.G2A +_aacgmv2.A2G +_aacgmv2.TRACE +_aacgmv2.ALLOWTRACE +_aacgmv2.BADIDEA +_aacgmv2.GEOCENTRIC +--------------------------------------------------------------------------- + +Functions +--------------------------------------------------------------------------- +convert_latlon_arr +convert_str_to_bit +convert_bool_to_bit +get_aacgm_coord +get_aacgm_coord_arr +convert +convert_mlt +wrapper.set_coeff_path +deprecated.subsol +_aacgmv2.convert +_aacgmv2.set_datetime +_aacgmv2.mlt_convert +_aacgmv2.mlt_convert_yrsec +_aacgmv2.inv_mlt_convert +_aacgmv2.inv_mlt_convert_yrsec +--------------------------------------------------------------------------- +""" +import os.path as _path +import logbook as logging +__version__ = "2.4.0" + +# path and filename prefix for the IGRF coefficients +AACGM_V2_DAT_PREFIX = _path.join(_path.realpath(_path.dirname(__file__)), + 'aacgm_coeffs', 'aacgm_coeffs-12-') +IGRF_12_COEFFS = _path.join(_path.realpath(_path.dirname(__file__)), + 'igrf12coeffs.txt') + +# Imports +#--------------------------------------------------------------------- + +try: + from aacgmv2.wrapper import (convert_latlon, convert_mlt, get_aacgm_coord) + from aacgmv2.wrapper import (convert_latlon_arr, get_aacgm_coord_arr) + from aacgmv2.wrapper import (convert_bool_to_bit, convert_str_to_bit) +except Exception as err: + logging.exception(__file__ + ' -> aacgmv2: ' + str(err)) + +try: + from aacgmv2 import (deprecated) + from aacgmv2.deprecated import (convert) +except Exception as err: + logging.exception(__file__ + ' -> aacgmv2: ' + str(err)) + +try: + from aacgmv2 import (_aacgmv2) +except Exception as err: + logging.exception(__file__ + ' -> aacgmv2: ' + str(err)) diff --git a/aacgmv2/__main__.py b/aacgmv2/__main__.py new file mode 100644 index 00000000..6fd39a7e --- /dev/null +++ b/aacgmv2/__main__.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + + +"""Executed when aacgmv2 is invoked with python -m aacgmv2""" + +from __future__ import division, print_function, absolute_import + +import sys +import argparse +import datetime as dt +import numpy as np +import aacgmv2 + +try: + # Python 3 + STDIN = sys.stdin.buffer + STDOUT = sys.stdout.buffer +except AttributeError: + # Python 2 + STDIN = sys.stdin + STDOUT = sys.stdout + + +def main(): + """Entry point for the script""" + + desc = 'Converts between geographical coordinates, AACGM-v2, and MLT' + parser = argparse.ArgumentParser(description=desc) + + desc = 'for help, run %(prog)s SUBCOMMAND -h' + subparsers = parser.add_subparsers(title='Subcommands', prog='aacgmv2', + dest='subcommand', description=desc) + subparsers.required = True + + desc = 'convert to/from geomagnetic coordinates. Input file must have lines' + desc += 'of the form "LAT LON ALT".' + parser_convert = subparsers.add_parser('convert', help=(desc)) + + desc = 'convert between magnetic local time (MLT) and AACGM-v2 longitude. ' + desc += 'Input file must have a single number on each line.' + parser_convert_mlt = subparsers.add_parser('convert_mlt', help=(desc)) + + desc = 'input file (stdin if none specified)' + for pp in [parser_convert, parser_convert_mlt]: + pp.add_argument('-i', '--input', dest='file_in', metavar='FILE_IN', + type=argparse.FileType('r'), default=STDIN, help=desc) + pp.add_argument('-o', '--output', dest='file_out', metavar='FILE_OUT', + type=argparse.FileType('wb'), default=STDOUT, + help='output file (stdout if none specified)') + + desc = 'date for magnetic field model (1900-2020, default: today)' + parser_convert.add_argument('-d', '--date', dest='date', metavar='YYYYMMDD', + help=desc) + + desc = 'invert - convert AACGM to geographic instead of geographic to AACGM' + parser_convert.add_argument('-v', '--a2g', dest='a2g', action='store_true', + default=False, help=desc) + + desc = 'use field-line tracing instead of coefficients' + parser_convert.add_argument('-t', '--trace', dest='trace', + action='store_true', default=False, help=desc) + + desc = 'automatically use field-line tracing above 2000 km' + parser_convert.add_argument('-a', '--allowtrace', dest='allowtrace', + action='store_true', default=False, help=desc) + + desc = 'allow use of coefficients above 2000 km (bad idea!)' + parser_convert.add_argument('-b', '--badidea', dest='badidea', + action='store_true', default=False, help=desc) + + desc = 'assume inputs are geocentric with Earth radius 6371.2 km' + parser_convert.add_argument('-g', '--geocentric', dest='geocentric', + action='store_true', default=False, help=desc) + + parser_convert_mlt.add_argument('datetime', metavar='YYYYMMDDHHMMSS', + help='date and time for conversion') + + desc = 'invert - convert MLT to AACGM longitude instead of AACGM longitude' + desc += ' to MLT' + parser_convert_mlt.add_argument('-v', '--m2a', dest='m2a', + action='store_true', default=False, + help=desc) + + args = parser.parse_args() + array = np.loadtxt(args.file_in, ndmin=2) + + if args.subcommand == 'convert': + date = dt.date.today() if args.date is None else \ + dt.datetime.strptime(args.date, '%Y%m%d') + code = aacgmv2.convert_bool_to_bit(a2g=args.a2g, trace=args.trace, + allowtrace=args.allowtrace, + badidea=args.badidea, + geocentric=args.geocentric) + lats, lons, alts = aacgmv2.convert_latlon_arr(array[:, 0], array[:, 1], + array[:, 2], dtime=date, + code=code) + np.savetxt(args.file_out, np.column_stack((lats, lons, alts)), + fmt='%.8f') + elif args.subcommand == 'convert_mlt': + dtime = dt.datetime.strptime(args.datetime, '%Y%m%d%H%M%S') + out = aacgmv2.convert_mlt(array[:, 0], dtime, m2a=args.m2a) + np.savetxt(args.file_out, out, fmt='%.8f') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1900.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1900.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1900.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1900.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1905.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1905.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1905.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1905.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1910.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1910.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1910.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1910.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1915.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1915.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1915.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1915.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1920.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1920.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1920.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1920.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1925.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1925.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1925.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1925.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1930.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1930.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1930.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1930.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1935.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1935.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1935.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1935.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1940.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1940.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1940.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1940.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1945.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1945.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1945.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1945.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1950.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1950.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1950.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1950.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1955.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1955.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1955.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1955.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1960.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1960.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1960.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1960.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1965.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1965.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1965.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1965.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1970.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1970.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1970.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1970.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1975.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1975.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1975.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1975.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1980.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1980.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1980.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1980.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1985.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1985.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1985.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1985.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1990.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1990.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1990.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1990.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1995.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1995.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1995.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-1995.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2000.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2000.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2000.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2000.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2005.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2005.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2005.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2005.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2010.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2010.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2010.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2010.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2015.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2015.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2015.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2015.asc diff --git a/src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2020.asc b/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2020.asc similarity index 100% rename from src/aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2020.asc rename to aacgmv2/aacgm_coeffs/aacgm_coeffs-12-2020.asc diff --git a/aacgmv2/aacgmv2module.c b/aacgmv2/aacgmv2module.c new file mode 100644 index 00000000..7a68c901 --- /dev/null +++ b/aacgmv2/aacgmv2module.c @@ -0,0 +1,357 @@ +#include + +/***************************************************************************** + * Author: Angeline G. Burrell, UTDallas, April 2017 + * + * Comments: python wrapper to AACGM functions based on test_aacgm.c + * Based on wrapper built by C. Meeren + * Originally written for DaViTpy and adapted to update AACGMV2 + * + * References: Shepherd, S. G. (2014), Altitude‐adjusted corrected geomagnetic + * coordinates: Definition and functional approximations, Journal + * of Geophysical Research: Space Physics, 119, p 7501-7521, + * doi:10.1002/2014JA020264 + *****************************************************************************/ + +#include + +#include "aacgmlib_v2.h" +#include "mlt_v2.h" + +PyObject *module; + +static PyObject *aacgm_v2_setdatetime(PyObject *self, PyObject *args) +{ + int year, month, day, hour, minute, second, err; + + char *root; + + /* Parse the input as a tupple */ + if(!PyArg_ParseTuple(args, "iiiiiis", &year, &month, &day, &hour, &minute, + &second, &root)) + return(NULL); + + /* Call the AACGM routine */ + err = AACGM_v2_SetDateTime(year, month, day, hour, minute, second, root); + + if(err < 0) + { + PyErr_Format(PyExc_RuntimeError, + "AACGM_v2_SetDateTime returned error code %d", err); + return(NULL); + } + + Py_RETURN_NONE; +} + +static PyObject *aacgm_v2_convert(PyObject *self, PyObject *args) +{ + int code, err; + + double in_lat, in_lon, in_h, out_lat, out_lon, out_r; + + char *igrf_file; + + /* Parse the input as a tupple */ + if(!PyArg_ParseTuple(args, "dddis", &in_lat, &in_lon, &in_h, &code, + &igrf_file)) + return(NULL); + + /* Call the AACGM routine */ + err = AACGM_v2_Convert(in_lat, in_lon, in_h, &out_lat, &out_lon, &out_r, + code, igrf_file); + + if(err < 0) + { + PyErr_Format(PyExc_RuntimeError, + "AACGM_v2_Convert returned error code %d", err); + return(NULL); + } + + return Py_BuildValue("ddd", out_lat, out_lon, out_r); +} + +static PyObject *mltconvert_v2(PyObject *self, PyObject *args) +{ + int yr, mo, dy, hr, mt, sc; + + double mlon, mlt; + + char *igrf_file, *root; + + /* Parse the input as a tupple */ + if(!PyArg_ParseTuple(args, "iiiiiidss", &yr, &mo, &dy, &hr, &mt, &sc, &mlon, + &root, &igrf_file)) + return(NULL); + + /* Call the AACGM routine */ + mlt = MLTConvertYMDHMS_v2(yr, mo, dy, hr, mt, sc, mlon, root, igrf_file); + + return Py_BuildValue("d", mlt); +} + +static PyObject *mltconvert_yrsec_v2(PyObject *self, PyObject *args) +{ + int yr, yr_sec; + + double mlon, mlt; + + char *igrf_file, *root; + + /* Parse the input as a tupple */ + if(!PyArg_ParseTuple(args, "iidss", &yr, &yr_sec, &mlon, &root, &igrf_file)) + return(NULL); + + /* Call the AACGM routine */ + mlt = MLTConvertYrsec_v2(yr, yr_sec, mlon, root, igrf_file); + + return Py_BuildValue("d", mlt); +} + +static PyObject *inv_mltconvert_v2(PyObject *self, PyObject *args) +{ + int yr, mo, dy, hr, mt, sc; + + double mlon, mlt; + + char *root, *igrf_file; + + /* Parse the input as a tupple */ + if(!PyArg_ParseTuple(args, "iiiiiidss", &yr, &mo, &dy, &hr, &mt, &sc, &mlt, + &root, &igrf_file)) + return(NULL); + + /* Call the AACGM routine */ + mlon = inv_MLTConvertYMDHMS_v2(yr, mo, dy, hr, mt, sc, mlt, root, igrf_file); + + return Py_BuildValue("d", mlon); +} + +static PyObject *inv_mltconvert_yrsec_v2(PyObject *self, PyObject *args) +{ + int yr, yr_sec; + + double mlon, mlt; + + char *root, *igrf_file; + + /* Parse the input as a tupple */ + if(!PyArg_ParseTuple(args, "iidss", &yr, &yr_sec, &mlt, &root, &igrf_file)) + return(NULL); + + /* Call the AACGM routine */ + mlon = inv_MLTConvertYrsec_v2(yr, yr_sec, mlt, root, igrf_file); + + return Py_BuildValue("d", mlon); +} + +static PyMethodDef aacgm_v2_methods[] = { + { "set_datetime", aacgm_v2_setdatetime, METH_VARARGS, + "set_datetime(year, month, day, hour, minute, second, root)\n\ +\n\ +Sets the date and time for the IGRF magnetic field.\n\ +\n\ +Parameters \n\ +-------------\n\ +year : (int)\n\ + Four digit year starting from 1900, ending 2020\n\ +month : (int)\n\ + Month of year ranging from 1-12\n\ +day : (int)\n\ + Day of month (1-31)\n\ +hour : (int)\n\ + Hour of day (0-23)\n\ +minute : (int)\n\ + Minute of hour (0-59)\n\ +second : (int)\n\ + Seconds of minute (0-59)\n\ +root : (str)\n\ + AACGM coefficient filename root\n\ + (e.g. ~/aacgmv2/aacgmv2/aacgm_coeff/aacgm_coeffs-12-)\n\ +\n\ +Returns\n\ +-------------\n\ +Void\n" }, + { "convert", aacgm_v2_convert, METH_VARARGS, + "convert(in_lat, in_lon, height, code, igrf_file)\n\ +\n\ +Converts between geographic/dedic and magnetic coordinates.\n\ +\n\ +Parameters\n\ +-------------\n\ +in_lat : (float)\n\ + Input latitude in degrees N (code specifies type of latitude)\n\ +in_lon : (float)\n\ + Input longitude in degrees E (code specifies type of longitude)\n\ +height : (float)\n\ + Altitude above the surface of the earth in km\n\ +code : (int) \n\ + Bitwise code for passing options into converter (default=0)\n\ + 0 - G2A - geographic (geodetic) to AACGM-v2 \n\ + 1 - A2G - AACGM-v2 to geographic (geodetic) \n\ + 2 - TRACE - use field-line tracing, not coefficients\n\ + 4 - ALLOWTRACE - use trace only above 2000 km\n\ + 8 - BADIDEA - use coefficients above 2000 km\n\ + 16 - GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2\n\ +igrf_file : (str)\n\ + Full filename of IGRF coefficient file\n\ +\n\ +Returns \n\ +-------\n\ +out_lat : (float)\n\ + Output latitude in degrees\n\ +out_lon : (float)\n\ + Output longitude in degrees\n\ +out_r : (float)\n\ + Geocentric radial distance in Re\n", }, + {"mlt_convert", mltconvert_v2, METH_VARARGS, + "mlt_convert(yr, mo, dy, hr, mt, sc, mlon, root, igrf_file)\n\ +\n\ +Converts from universal time to magnetic local time.\n\ +\n\ +Parameters\n\ +-------------\n\ +yr : (int)\n\ + 4 digit integer year (1900-2020)\n\ +mo : (int)\n\ + Month of year (1-12)\n\ +dy : (int)\n\ + Day of month (1-31)\n\ +hr : (int)\n\ + hours of day (0-23)\n\ +mt : (int)\n\ + Minutes of hour (0-59)\n\ +sc : (int)\n\ + Seconds of minute (0-59)\n\ +mlon : (float)\n\ + Magnetic longitude\n\ +root : (str)\n\ + Root of the AACGM coefficient files\n\ + (e.g. ~/aacgmv2/aacgmv2/aacgm_coeff/aacgm_coeffs-12-)\n\ +igrf_file : (str)\n\ + Full filename of IGRF coefficient file\n\ +\n\ +Returns \n\ +-------\n\ +mlt : (float)\n\ + Magnetic local time (hours)\n" }, + + {"mlt_convert_yrsec", mltconvert_yrsec_v2, METH_VARARGS, + "mlt_convert_yrsec(yr, yr_sec, mlon, root, igrf_file)\n\ +\n\ +Converts from universal time to magnetic local time.\n\ +\n\ +Parameters\n\ +-------------\n\ +yr : (int)\n\ + 4 digit integer year (1900-2020)\n\ +yr_sec : (int)\n\ + Seconds of year (0-31622400)\n\ +mlon : (float)\n\ + Magnetic longitude\n\ +root : (str)\n\ + Root of the AACGM coefficient files\n\ + (e.g. ~/aacgmv2/aacgmv2/aacgm_coeff/aacgm_coeffs-12-)\n\ +igrf_file : (str)\n\ + Full filename of IGRF coefficient file\n\ +\n\ +Returns \n\ +-------\n\ +mlt : (float)\n\ + Magnetic local time (hours)\n" }, + {"inv_mlt_convert", inv_mltconvert_v2, METH_VARARGS, + "inv_mlt_convert(yr, mo, dy, hr, mt, sc, mlt, root, igrf_file)\n\ +\n\ +Converts from universal time and magnetic local time to magnetic longitude.\n\ +\n\ +Parameters\n\ +-------------\n\ +yr : (int)\n\ + 4 digit integer year (1900-2020)\n\ +mo : (int)\n\ + Month of year (1-12)\n\ +dy : (int)\n\ + Day of month (1-31)\n\ +hr : (int)\n\ + hours of day (0-23)\n\ +mt : (int)\n\ + Minutes of hour (0-59)\n\ +sc : (int)\n\ + Seconds of minute (0-59)\n\ +mlt : (float)\n\ + Magnetic local time\n\ +root : (str)\n\ + Root of the AACGM coefficient files\n\ + (e.g. ~/aacgmv2/aacgmv2/aacgm_coeff/aacgm_coeffs-12-)\n\ +igrf_file : (str)\n\ + Full filename of IGRF coefficient file\n\ +\n\ +Returns \n\ +-------\n\ +mlon : (float)\n\ + Magnetic longitude (degrees)\n" }, + + {"inv_mlt_convert_yrsec", inv_mltconvert_yrsec_v2, METH_VARARGS, + "inv_mlt_convert_yrsec(yr, yr_sec, mlt, root, igrf_file)\n\ +\n\ +Converts from universal time and magnetic local time to magnetic longitude.\n\ +\n\ +Parameters\n\ +-------------\n\ +yr : (int)\n\ + 4 digit integer year (1900-2020)\n\ +yr_sec : (int)\n\ + Seconds of year (0-31622400)\n\ +mlt : (float)\n\ + Magnetic local time\n\ +root : (str)\n\ + Root of the AACGM coefficient files\n\ + (e.g. ~/aacgmv2/aacgmv2/aacgm_coeff/aacgm_coeffs-12-)\n\ +igrf_file : (str)\n\ + Full filename of IGRF coefficient file\n\ +\n\ +Returns \n\ +-------\n\ +mlon : (float)\n\ + Magnetic longitude (degrees)\n" }, + { NULL, NULL, 0, NULL } +}; + +/* Different versions of python require different constant declarations */ + +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef aacgmv2module = { + PyModuleDef_HEAD_INIT, + "_aacgmv2", /* name of module */ + "Interface to the AACGM-v2 C library.", /* module documentation */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + aacgm_v2_methods + }; + + PyMODINIT_FUNC PyInit__aacgmv2(void) + { + module = PyModule_Create(&aacgmv2module); + PyModule_AddIntConstant(module, "G2A", G2A); + PyModule_AddIntConstant(module, "A2G", A2G); + PyModule_AddIntConstant(module, "TRACE", TRACE); + PyModule_AddIntConstant(module, "ALLOWTRACE", ALLOWTRACE); + PyModule_AddIntConstant(module, "BADIDEA", BADIDEA); + PyModule_AddIntConstant(module, "GEOCENTRIC", GEOCENTRIC); + return module; + } + +#else + + PyMODINIT_FUNC init_aacgmv2(void) + { + module = Py_InitModule("_aacgmv2", aacgm_v2_methods); + PyModule_AddIntConstant(module, "G2A", G2A); + PyModule_AddIntConstant(module, "A2G", A2G); + PyModule_AddIntConstant(module, "TRACE", TRACE); + PyModule_AddIntConstant(module, "ALLOWTRACE", ALLOWTRACE); + PyModule_AddIntConstant(module, "BADIDEA", BADIDEA); + PyModule_AddIntConstant(module, "GEOCENTRIC", GEOCENTRIC); + } + +#endif diff --git a/aacgmv2/deprecated.py b/aacgmv2/deprecated.py new file mode 100644 index 00000000..6157d7d4 --- /dev/null +++ b/aacgmv2/deprecated.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +"""Pythonic wrappers for AACGM-V2 C functions that were depricated in the +change from version 2.0.0 to version 2.0.2 + +Functions +------------------------------------------------------------------------------- +convert : Converts array location +subsol : finds subsolar geocentric longitude and latitude +gc2gd_lat : Convert between geocentric and geodetic coordinates +igrf_dipole_axis : Get Cartesian unit vector pointing at the IGRF north dipole +------------------------------------------------------------------------------ + +References +------------------------------------------------------------------------------- +Laundal, K. M. and A. D. Richmond (2016), Magnetic Coordinate Systems, Space + Sci. Rev., doi:10.1007/s11214-016-0275-y. +------------------------------------------------------------------------------- +""" + +from __future__ import division, absolute_import, unicode_literals +import numpy as np +import logbook as logging + +def convert(lat, lon, alt, date=None, a2g=False, trace=False, allowtrace=False, + badidea=False, geocentric=False): + """Converts between geomagnetic coordinates and AACGM coordinates + + Parameters + ------------ + lat : (float) + Input latitude in degrees N (code specifies type of latitude) + lon : (float) + Input longitude in degrees E (code specifies type of longitude) + alt : (float) + Altitude above the surface of the earth in km + date : (datetime) + Datetime for magnetic field + a2g : (bool) + True for AACGM-v2 to geographic (geodetic), False otherwise + (default=False) + trace : (bool) + If True, use field-line tracing, not coefficients (default=False) + allowtrace : (bool) + If True, use trace only above 2000 km (default=False) + badidea : (bool) + If True, use coefficients above 2000 km (default=False) + geocentric : (bool) + True for geodetic, False for geocentric w/RE=6371.2 (default=False) + + Returns + ------- + lat_out : (float) + Output latitude in degrees N + lon_out : (float) + Output longitude in degrees E + """ + import aacgmv2 + + if(np.array(alt).max() > 2000 and not trace and not allowtrace and + badidea): + estr = 'coefficients are not valid for altitudes above 2000 km. You' + estr += ' must either use field-line tracing (trace=True ' + estr += 'or allowtrace=True) or indicate you know this is a bad idea' + logging.error(estr) + raise ValueError + + # construct a code from the boolian flags + bit_code = aacgmv2.convert_bool_to_bit(a2g=a2g, trace=trace, + allowtrace=allowtrace, + badidea=badidea, + geocentric=geocentric) + + # convert location + lat_out, lon_out, _ = aacgmv2.convert_latlon_arr(lat, lon, alt, date, + code=bit_code) + + return lat_out, lon_out + +def subsol(year, doy, utime): + """Finds subsolar geocentric longitude and latitude. + + Parameters + ------------ + year : (int) + Calendar year between 1601 and 2100 + doy : (int) + Day of year between 1-365/366 + utime : (float) + Seconds since midnight on the specified day + + Returns + --------- + sbsllon : (float) + Subsolar longitude in degrees E for the given date/time + sbsllat : (float) + Subsolar latitude in degrees N for the given date/time + + Notes + -------- + Based on formulas in Astronomical Almanac for the year 1996, p. C24. + (U.S. Government Printing Office, 1994). Usable for years 1601-2100, + inclusive. According to the Almanac, results are good to at least 0.01 + degree latitude and 0.025 degrees longitude between years 1950 and 2050. + Accuracy for other years has not been tested. Every day is assumed to have + exactly 86400 seconds; thus leap seconds that sometimes occur on December + 31 are ignored (their effect is below the accuracy threshold of the + algorithm). + After Fortran code by A. D. Richmond, NCAR. Translated from IDL + by K. Laundal. + """ + yr2 = year - 2000 + + if year >= 2101: + logging.error('subsol invalid after 2100. Input year is:', year) + + nleap = np.floor((year - 1601) / 4) + nleap = nleap - 99 + if year <= 1900: + if year <= 1600: + print('subsol.py: subsol invalid before 1601. Input year is:', year) + ncent = np.floor((year - 1601) / 100) + ncent = 3 - ncent + nleap = nleap + ncent + + l_0 = -79.549 + (-0.238699 * (yr2 - 4 * nleap) + 3.08514e-2 * nleap) + g_0 = -2.472 + (-0.2558905 * (yr2 - 4 * nleap) - 3.79617e-2 * nleap) + + # Days (including fraction) since 12 UT on January 1 of IYR2: + dfrac = (utime / 86400 - 1.5) + doy + + # Mean longitude of Sun: + l_sun = l_0 + 0.9856474 * dfrac + + # Mean anomaly: + grad = np.radians(g_0 + 0.9856003 * dfrac) + + # Ecliptic longitude: + lmrad = np.radians(l_sun + 1.915 * np.sin(grad) + 0.020 * np.sin(2 * grad)) + sinlm = np.sin(lmrad) + + # Days (including fraction) since 12 UT on January 1 of 2000: + epoch_day = dfrac + 365.0 * yr2 + nleap + + # Obliquity of ecliptic: + epsrad = np.radians(23.439 - 4.0e-7 * epoch_day) + + # Right ascension: + alpha = np.degrees(np.arctan2(np.cos(epsrad) * sinlm, np.cos(lmrad))) + + # Declination, which is the subsolar latitude: + sbsllat = np.degrees(np.arcsin(np.sin(epsrad) * sinlm)) + + # Equation of time (degrees): + etdeg = l_sun - alpha + etdeg = etdeg - 360.0 * np.round(etdeg / 360.0) + + # Apparent time (degrees): + aptime = utime / 240.0 + etdeg # Earth rotates one degree every 240 s. + + # Subsolar longitude: + sbsllon = 180.0 - aptime + sbsllon = sbsllon - 360.0 * np.round(sbsllon / 360.0) + + return sbsllon, sbsllat + +def gc2gd_lat(gc_lat): + """Convert geocentric latitude to geodetic latitude using WGS84. + + Parameters + ----------- + gc_lat : (array_like or float) + Geocentric latitude in degrees N + + Returns + --------- + gd_lat : (same as input) + Geodetic latitude in degrees N + """ + wgs84_e2 = 0.006694379990141317 - 1.0 + return np.rad2deg(-np.arctan(np.tan(np.deg2rad(gc_lat)) / wgs84_e2)) + +def igrf_dipole_axis(date): + """Get Cartesian unit vector pointing at dipole pole in the north, + according to IGRF + + Parameters + ------------- + date : (dt.datetime) + Date and time + + Returns + ---------- + m_0: (np.ndarray) + Cartesian 3 element unit vector pointing at dipole pole in the north + (geocentric coords) + + Notes + ---------- + IGRF coefficients are read from the igrf12coeffs.txt file. It should also + work after IGRF updates. The dipole coefficients are interpolated to the + date, or extrapolated if date > latest IGRF model + """ + import datetime as dt + import aacgmv2 + + # get time in years, as float: + year = date.year + doy = date.timetuple().tm_yday + year_days = int(dt.date(date.year, 12, 31).strftime("%j")) + year = year + doy / year_days + + # read the IGRF coefficients + with open(aacgmv2.IGRF_12_COEFFS, 'r') as f_igrf: + lines = f_igrf.readlines() + + years = lines[3].split()[3:][:-1] + years = np.array(years, dtype=float) # time array + + g10 = lines[4].split()[3:] + g11 = lines[5].split()[3:] + h11 = lines[6].split()[3:] + + # secular variation coefficients (for extrapolation) + g10sv = np.float32(g10[-1]) + g11sv = np.float32(g11[-1]) + h11sv = np.float32(h11[-1]) + + # model coefficients: + g10 = np.array(g10[:-1], dtype=float) + g11 = np.array(g11[:-1], dtype=float) + h11 = np.array(h11[:-1], dtype=float) + + # get the gauss coefficient at given time: + if year <= years[-1]: + # regular interpolation + g10 = np.interp(year, years, g10) + g11 = np.interp(year, years, g11) + h11 = np.interp(year, years, h11) + else: + # extrapolation + dyear = year - years[-1] + g10 = g10[-1] + g10sv * dyear + g11 = g11[-1] + g11sv * dyear + h11 = h11[-1] + h11sv * dyear + + # calculate pole position + B_0 = np.sqrt(g10**2 + g11**2 + h11**2) + + # Calculate output + m_0 = -np.array([g11, h11, g10]) / B_0 + + return m_0 diff --git a/src/aacgmv2/igrf12coeffs.txt b/aacgmv2/igrf12coeffs.txt similarity index 100% rename from src/aacgmv2/igrf12coeffs.txt rename to aacgmv2/igrf12coeffs.txt diff --git a/aacgmv2/tests/test_c_aacgmv2.py b/aacgmv2/tests/test_c_aacgmv2.py new file mode 100644 index 00000000..3e28b36e --- /dev/null +++ b/aacgmv2/tests/test_c_aacgmv2.py @@ -0,0 +1,342 @@ +# -*- coding: utf-8 -*- +from __future__ import division, absolute_import, unicode_literals + +import numpy as np +import pytest +import aacgmv2 + +class TestCAACGMV2: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.date_args = [(2014, 3, 22, 3, 11, 0, aacgmv2.AACGM_V2_DAT_PREFIX), + (2018, 1, 1, 0, 0, 0, aacgmv2.AACGM_V2_DAT_PREFIX)] + self.long_date = [2014, 3, 22, 3, 11, 0] + self.mlat = None + self.mlon = None + self.rshell = None + self.mlt = None + self.lat_in = [45.5, 60] + self.lon_in = [-23.5, 0] + self.alt_in = [1135, 300] + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.date_args, self.long_date, self.mlat, self.mlon, self.mlt + del self.lat_in, self.lon_in, self.alt_in + + def test_module_structure(self): + """Test module structure""" + assert aacgmv2 + assert aacgmv2._aacgmv2 + assert aacgmv2._aacgmv2.set_datetime + assert aacgmv2._aacgmv2.convert + assert aacgmv2._aacgmv2.inv_mlt_convert + assert aacgmv2._aacgmv2.inv_mlt_convert_yrsec + assert aacgmv2._aacgmv2.mlt_convert + assert aacgmv2._aacgmv2.mlt_convert_yrsec + + def test_constants(self): + """Test module constants""" + ans1 = aacgmv2._aacgmv2.G2A == 0 + ans2 = aacgmv2._aacgmv2.A2G == 1 + ans3 = aacgmv2._aacgmv2.TRACE == 2 + ans4 = aacgmv2._aacgmv2.ALLOWTRACE == 4 + ans5 = aacgmv2._aacgmv2.BADIDEA == 8 + ans6 = aacgmv2._aacgmv2.GEOCENTRIC == 16 + + assert ans1 & ans2 & ans3 & ans4 & ans5 & ans6 + del ans1, ans2, ans3, ans4, ans5, ans6 + + def test_set_datetime(self): + """Test set_datetime""" + for darg in self.date_args: + arg1 = aacgmv2._aacgmv2.set_datetime(*darg) is None + assert arg1 + + def test_fail_set_datetime(self): + """Test unsuccessful set_datetime""" + with pytest.raises(RuntimeError): + aacgmv2._aacgmv2.set_datetime(1013, 1, 1, 0, 0, 0, + aacgmv2.AACGM_V2_DAT_PREFIX) + + def test_convert_G2A_coeff(self): + """Test convert from geographic to magnetic coordinates""" + lat_comp = [48.1896, 58.1633] + lon_comp = [57.7635, 81.0719] + r_comp = [1.1775, 1.0457] + + for i,darg in enumerate(self.date_args): + aacgmv2._aacgmv2.set_datetime(*darg) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[i], + self.lon_in[i], + self.alt_in[i], + aacgmv2._aacgmv2.G2A, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, lat_comp[i], decimal=4) + np.testing.assert_almost_equal(self.mlon, lon_comp[i], decimal=4) + np.testing.assert_almost_equal(self.rshell, r_comp[i], decimal=4) + + del lat_comp, lon_comp, r_comp + + def test_convert_A2G_coeff(self): + """Test convert from magnetic to geodetic coordinates""" + lat_comp = [30.7534, 50.3910] + lon_comp = [-94.1806, -77.7919] + r_comp = [1133.6241, 305.7138] + + for i,darg in enumerate(self.date_args): + aacgmv2._aacgmv2.set_datetime(*darg) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[i], + self.lon_in[i], + self.alt_in[i], + aacgmv2._aacgmv2.A2G, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, lat_comp[i], decimal=4) + np.testing.assert_almost_equal(self.mlon, lon_comp[i], decimal=4) + np.testing.assert_almost_equal(self.rshell, r_comp[i], decimal=4) + + del lat_comp, lon_comp, r_comp + + def test_convert_G2A_TRACE(self): + """Test convert from geodetic to magnetic coordinates using trace""" + code = aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.TRACE + trace_lat = [48.1948, 58.1633] + trace_lon = [57.7588, 81.0756] + trace_r = [1.1775, 1.0457] + + for i,dargs in enumerate(self.date_args): + aacgmv2._aacgmv2.set_datetime(*dargs) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[i], + self.lon_in[i], + self.alt_in[i], code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, trace_lat[i], decimal=4) + np.testing.assert_almost_equal(self.mlon, trace_lon[i], decimal=4) + np.testing.assert_almost_equal(self.rshell, trace_r[i], decimal=4) + + del code, trace_lat, trace_lon, trace_r + + def test_convert_A2G_TRACE(self): + """Test convert from magnetic to geodetic coordinates using trace""" + code = aacgmv2._aacgmv2.A2G + aacgmv2._aacgmv2.TRACE + trace_lat = [30.7644, 50.3958] + trace_lon = [-94.1809, -77.8019] + trace_r = [1133.6277, 305.7156] + + for i,dargs in enumerate(self.date_args): + aacgmv2._aacgmv2.set_datetime(*dargs) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[i], + self.lon_in[i], + self.alt_in[i], code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, trace_lat[i], decimal=4) + np.testing.assert_almost_equal(self.mlon, trace_lon[i], decimal=4) + np.testing.assert_almost_equal(self.rshell, trace_r[i], decimal=4) + + del code, trace_lat, trace_lon, trace_r + + def test_convert_high_denied(self): + """Test for failure when converting to high altitude geodetic to + magnetic coordinates""" + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + with pytest.raises(RuntimeError): + aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], 5500, + aacgmv2._aacgmv2.G2A, + aacgmv2.IGRF_12_COEFFS) + + def test_convert_high_TRACE(self): + """Test convert from high altitude geodetic to magnetic coordinates + using trace""" + code = aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.TRACE + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + 5500, code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 59.9748, decimal=4) + np.testing.assert_almost_equal(self.mlon, 57.7425, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1.8626, decimal=4) + + del code + + def test_convert_high_ALLOWTRACE(self): + """Test convert from high altitude geodetic to magnetic coordinates + by allowing IGRF tracing""" + code = aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.ALLOWTRACE + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + 5500, code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 59.9748, decimal=4) + np.testing.assert_almost_equal(self.mlon, 57.7425, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1.8626, decimal=4) + + del code + + def test_convert_high_BADIDEA(self): + """Test convert from high altitude geodetic to magnetic coordinates + using coefficients""" + code = aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.BADIDEA + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + 5500, code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 58.7154, decimal=4) + np.testing.assert_almost_equal(self.mlon, 56.5830, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1.8626, decimal=4) + + del code + + def test_convert_GEOCENTRIC_G2A_coeff(self): + """Test convert from geographic to magnetic coordinates""" + code = aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.GEOCENTRIC + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + self.alt_in[0], code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 48.3779, decimal=4) + np.testing.assert_almost_equal(self.mlon, 57.7974, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1.1781, decimal=4) + + del code + + def test_convert_GEOCENTRIC_A2G_coeff(self): + """Test convert from magnetic to geocentric coordinates""" + code = aacgmv2._aacgmv2.A2G + aacgmv2._aacgmv2.GEOCENTRIC + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + self.alt_in[0], code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 30.6101, decimal=4) + np.testing.assert_almost_equal(self.mlon, -94.1806, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1135.0000, decimal=4) + + del code + + def test_convert_GEOCENTRIC_G2A_TRACE(self): + """Test convert from geographic to magnetic coordinates using trace""" + code = aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.TRACE + \ + aacgmv2._aacgmv2.GEOCENTRIC + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + self.alt_in[0], code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 48.3830, decimal=4) + np.testing.assert_almost_equal(self.mlon, 57.7926, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1.1781, decimal=4) + + del code + + def test_convert_GEOCENTRIC_A2G_TRACE(self): + """Test convert from magnetic to geographic coordinates using trace""" + code = aacgmv2._aacgmv2.A2G + aacgmv2._aacgmv2.TRACE + \ + aacgmv2._aacgmv2.GEOCENTRIC + aacgmv2._aacgmv2.set_datetime(*self.date_args[0]) + (self.mlat, self.mlon, + self.rshell) = aacgmv2._aacgmv2.convert(self.lat_in[0], self.lon_in[0], + self.alt_in[0], code, + aacgmv2.IGRF_12_COEFFS) + np.testing.assert_almost_equal(self.mlat, 30.6211, decimal=4) + np.testing.assert_almost_equal(self.mlon, -94.1809, decimal=4) + np.testing.assert_almost_equal(self.rshell, 1135.0000, decimal=4) + + del code + + def test_forbidden(self): + """Test convert failure""" + with pytest.raises(RuntimeError): + mloc = aacgmv2._aacgmv2.convert(7, 0, 0, aacgmv2._aacgmv2.G2A, + aacgmv2.IGRF_12_COEFFS) + + def test_inv_mlt_convert(self): + """Test MLT inversion""" + mlt_args = list(self.long_date) + mlt_args.extend([12.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS]) + self.mlon = aacgmv2._aacgmv2.inv_mlt_convert(*mlt_args) + np.testing.assert_almost_equal(self.mlon, -153.5931, decimal=4) + + mlt_args[-3] = 25.0 + self.mlon = aacgmv2._aacgmv2.inv_mlt_convert(*mlt_args) + np.testing.assert_almost_equal(self.mlon, 41.4069, decimal=4) + + mlt_args[-3] = -1.0 + self.mlon = aacgmv2._aacgmv2.inv_mlt_convert(*mlt_args) + np.testing.assert_almost_equal(self.mlon, 11.4069, decimal=4) + + del mlt_args + + def test_inv_mlt_convert_yrsec(self): + """Test MLT inversion with year and seconds of year""" + import datetime as dt + dtime = dt.datetime(*self.long_date) + soy = (int(dtime.strftime("%j"))-1) * 86400 + dtime.hour * 3600 + \ + dtime.minute * 60 + dtime.second + + mlt_args_1 = [dtime.year, soy, 12.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS] + mlt_args_2 = [dtime.year, soy, 25.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS] + mlt_args_3 = [dtime.year, soy, -1.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS] + + mlon_1 = aacgmv2._aacgmv2.inv_mlt_convert_yrsec(*mlt_args_1) + mlon_2 = aacgmv2._aacgmv2.inv_mlt_convert_yrsec(*mlt_args_2) + mlon_3 = aacgmv2._aacgmv2.inv_mlt_convert_yrsec(*mlt_args_3) + + np.testing.assert_almost_equal(mlon_1, -153.5931, decimal=4) + np.testing.assert_almost_equal(mlon_2, 41.4069, decimal=4) + np.testing.assert_almost_equal(mlon_3, 11.4069, decimal=4) + + del dtime, soy, mlt_args_1, mlt_args_2, mlt_args_3, mlon_1, mlon_2 + del mlon_3 + + def test_mlt_convert(self): + """Test MLT calculation""" + mlt_args = list(self.long_date) + mlt_args.extend([270.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS]) + self.mlt = aacgmv2._aacgmv2.mlt_convert(*mlt_args) + np.testing.assert_almost_equal(self.mlt, 16.2395, decimal=4) + + mlt_args[-3] = 80.0 + self.mlt = aacgmv2._aacgmv2.mlt_convert(*mlt_args) + np.testing.assert_almost_equal(self.mlt, 3.5729, decimal=4) + + mlt_args[-3] = -90.0 + self.mlt = aacgmv2._aacgmv2.mlt_convert(*mlt_args) + np.testing.assert_almost_equal(self.mlt, 16.2395, decimal=4) + + del mlt_args + + def test_mlt_convert_yrsec(self): + """Test MLT calculation using year and seconds of year""" + import datetime as dt + dtime = dt.datetime(*self.long_date) + soy = (int(dtime.strftime("%j"))-1) * 86400 + dtime.hour * 3600 + \ + dtime.minute * 60 + dtime.second + mlt_args_1 = [dtime.year, soy, 270.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS] + mlt_args_2 = [dtime.year, soy, 80.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS] + mlt_args_3 = [dtime.year, soy, -90.0, aacgmv2.AACGM_V2_DAT_PREFIX, + aacgmv2.IGRF_12_COEFFS] + + mlt_1 = aacgmv2._aacgmv2.mlt_convert_yrsec(*mlt_args_1) + mlt_2 = aacgmv2._aacgmv2.mlt_convert_yrsec(*mlt_args_2) + mlt_3 = aacgmv2._aacgmv2.mlt_convert_yrsec(*mlt_args_3) + + np.testing.assert_almost_equal(mlt_1, 16.2395, decimal=4) + np.testing.assert_almost_equal(mlt_2, 3.5729, decimal=4) + np.testing.assert_equal(mlt_1, mlt_3) + + del dtime, soy, mlt_args_1, mlt_args_2, mlt_args_3, mlt_1, mlt_2, mlt_3 diff --git a/aacgmv2/tests/test_cmd_aacgmv2.py b/aacgmv2/tests/test_cmd_aacgmv2.py new file mode 100644 index 00000000..a60fbb38 --- /dev/null +++ b/aacgmv2/tests/test_cmd_aacgmv2.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +from __future__ import division, absolute_import, unicode_literals + +import subprocess +import numpy as np + +class testCmdAACGMV2: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.output = "tests/test_data/output.txt" + self.convert = "tests/test_data/test_convert.txt" + self.single = 'tests/test_data/test_convert_single_line.txt' + self.mlt = 'tests/test_data/test_convert_mlt.txt' + self.mlt_single = 'tests/test_data/test_convert_mlt_single_line.txt' + + def teardown(self): + """Runs after every method to clean up previous testing""" + import os + + if os.path.isfile(self.output): + os.remove(self.output) + + del self.output, self.convert, self.single, self.mlt, self.mlt_single + + def test_module_invocation(self): + p = subprocess.Popen(['python', '-m', 'aacgmv2', 'convert', '-i', + 'tests/test_convert.txt', '-d', '20150224', + '-o', self.output]) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [[57.4761, 93.5572], + [58.5332, 93.9607], + [59.5852, 94.3897]], rtol=1e-4) + + def test_convert_g2a(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.convert, '-d', + '20150224', '-o', self.output]) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [[57.4761, 93.5572], + [58.5332, 93.9607], + [59.5852, 94.3897]], rtol=1e-4) + + def test_convert_a2g(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.convert, + '-d', '20150224', '-o', self.output, '-v']) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [[51.6547, -66.6601], + [52.6725, -66.7555], + [53.6914, -66.8552]], rtol=1e-4) + + def test_convert_trace_g2a(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.convert, '-d', + '20150224', '-o', self.output, '-t']) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [[57.4736, 93.5676], + [58.5305, 93.9716], + [59.5825, 94.4009]], rtol=1e-4) + + def test_convert_trace_a2g(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.convert, '-d', + '20150224', '-o', self.output, '-t', '-v']) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [[51.6454, -66.6444], + [52.6671, -66.7432], + [53.6899, -66.8469]], rtol=1e-4) + + def test_convert_geocentric(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.convert, '-d', + '20150224', '-o', self.output, '-g']) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [[57.6697, 93.6319], + [58.7223, 94.0385], + [59.7695, 94.4708]], rtol=1e-4) + + def test_convert_today(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.convert]) + p.communicate() + p.wait() + + def test_convert_single_line(self): + p = subprocess.Popen(['aacgmv2', 'convert', '-i', self.single, '-d', + '20150224', '-o', self.output]) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [57.4761, 93.5572], rtol=1e-4) + + def test_convert_stdin_stdout(self): + p = subprocess.Popen('echo 60 15 300 | aacgmv2 convert -d 20150224', + shell=True, stdout=subprocess.PIPE) + stdout, _ = p.communicate() + p.wait() + assert b'57.47612194 93.55719875' in stdout + + def test_convert_mlt_a2m(self): + p = subprocess.Popen(['aacgmv2', 'convert_mlt', '-i', self.mlt, + '20150224140015', '-o', self.output]) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [9.056476, 9.78981, 10.523143], + rtol=1e-6) + + def test_convert_mlt_m2a(self): + p = subprocess.Popen(['aacgmv2', 'convert_mlt', '-i', self.mlt, + '20150224140015', '-o', self.output, '-v']) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, [240.152854, 45.152854, 210.152854], + rtol=1e-6) + + def test_convert_mlt_single_line(self): + p = subprocess.Popen(['aacgmv2', 'convert_mlt', '-i', self.mlt_single, + '20150224140015', '-o', self.output]) + p.communicate() + p.wait() + data = np.loadtxt(self.output) + np.testing.assert_allclose(data, 9.0564764, rtol=1e-6) + + def test_convert_mlt_stdin_stdout(self): + p = subprocess.Popen('echo 12 | aacgmv2 convert_mlt -v 20150224140015', + shell=True, stdout=subprocess.PIPE) + stdout, _ = p.communicate() + p.wait() + assert b'45.15285362' in stdout + + def test_convert_mlt_stdin_stdout_order(self): + p = subprocess.Popen('echo 12 | aacgmv2 convert_mlt 20150224140015 -v', + shell=True, stdout=subprocess.PIPE) + stdout, _ = p.communicate() + p.wait() + assert b'45.15285362' in stdout diff --git a/tests/test_convert.txt b/aacgmv2/tests/test_data/test_convert.txt similarity index 100% rename from tests/test_convert.txt rename to aacgmv2/tests/test_data/test_convert.txt diff --git a/tests/test_convert_mlt.txt b/aacgmv2/tests/test_data/test_convert_mlt.txt similarity index 100% rename from tests/test_convert_mlt.txt rename to aacgmv2/tests/test_data/test_convert_mlt.txt diff --git a/tests/test_convert_mlt_single_line.txt b/aacgmv2/tests/test_data/test_convert_mlt_single_line.txt similarity index 100% rename from tests/test_convert_mlt_single_line.txt rename to aacgmv2/tests/test_data/test_convert_mlt_single_line.txt diff --git a/tests/test_convert_single_line.txt b/aacgmv2/tests/test_data/test_convert_single_line.txt similarity index 100% rename from tests/test_convert_single_line.txt rename to aacgmv2/tests/test_data/test_convert_single_line.txt diff --git a/aacgmv2/tests/test_dep_aacgmv2.py b/aacgmv2/tests/test_dep_aacgmv2.py new file mode 100644 index 00000000..aba41198 --- /dev/null +++ b/aacgmv2/tests/test_dep_aacgmv2.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +from __future__ import division, absolute_import, unicode_literals + +import datetime as dt +import numpy as np +import pytest +import aacgmv2 + +class TestDepAACGMV2: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0) + self.ddate = dt.date(2015, 1, 1) + self.lat = None + self.lon = None + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.dtime, self.ddate, self.lat, self.lon + + def test_module_structure(self): + """Test module structure for deprecated routines""" + assert aacgmv2 + assert aacgmv2.convert + assert aacgmv2.deprecated.subsol + assert aacgmv2.deprecated + assert aacgmv2.deprecated.gc2gd_lat + assert aacgmv2.deprecated.igrf_dipole_axis + + def test_convert_single_val(self): + """Test conversion for a single value""" + self.lat, self.lon = aacgmv2.convert(60, 0, 300, self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (1,) + np.testing.assert_allclose(self.lat, [58.2258], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685], rtol=1e-4) + + def test_convert_list(self): + """Test conversion for list input""" + self.lat, self.lon = aacgmv2.convert([60], [0], [300], self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (1,) + np.testing.assert_allclose(self.lat, [58.2258], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685], rtol=1e-4) + + self.lat, self.lon = aacgmv2.convert([60, 61], [0, 0], [300, 300], + self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (2,) + np.testing.assert_allclose(self.lat, [58.2258, 59.3186], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685, 81.6140], rtol=1e-4) + + def test_convert_arr_single(self): + """Test conversion for array input with one element""" + self.lat, self.lon = aacgmv2.convert(np.array([60]), np.array([0]), + np.array([300]), self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (1,) + np.testing.assert_allclose(self.lat, [58.2258], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685], rtol=1e-4) + + def test_convert_arr(self): + """Test conversion for array input""" + self.lat, self.lon = aacgmv2.convert(np.array([60, 61]), + np.array([0, 0]), + np.array([300, 300]), self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (2,) + np.testing.assert_allclose(self.lat, [58.2258, 59.3186], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685, 81.6140], rtol=1e-4) + + def test_convert_list_mix(self): + """Test conversion for a list and floats""" + self.lat, self.lon = aacgmv2.convert([60, 61], 0, 300, self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (2,) + np.testing.assert_allclose(self.lat, [58.2258, 59.3186], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685, 81.6140], rtol=1e-4) + + def test_convert_arr_mix(self): + """Test conversion for an array and floats""" + self.lat, self.lon = aacgmv2.convert(np.array([60, 61]), 0, 300, + self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (2,) + np.testing.assert_allclose(self.lat, [58.2258, 59.3186], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685, 81.6140], rtol=1e-4) + + def test_convert_mult_array_mix(self): + """Test conversion for a multi-dim array and floats""" + self.lat, self.lon = aacgmv2.convert(np.array([[60, 61, 62], + [63, 64, 65]]), 0, + 300, self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (2, 3) + np.testing.assert_allclose(self.lat, [[58.2258, 59.3186, 60.4040], + [61.4820, 62.5528, 63.6164]], + rtol=1e-4) + np.testing.assert_allclose(self.lon, [[81.1685, 81.6140, 82.0872], + [82.5909, 83.1286, 83.7039]], + rtol=1e-4) + + def test_convert_unequal_arra(self): + """Test conversion for unequal sized arrays""" + self.lat, self.lon = aacgmv2.convert(np.array([[60, 61, 62], + [63, 64, 65]]), + np.array([0]), np.array([300]), + self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (2, 3) + np.testing.assert_allclose(self.lat, [[58.2258, 59.3186, 60.4040], + [61.4820, 62.5528, 63.6164]], + rtol=1e-4) + np.testing.assert_allclose(self.lon, [[81.1685, 81.6140, 82.0872], + [82.5909, 83.1286, 83.7039]], + rtol=1e-4) + + def test_convert_badidea_failure(self): + """Test conversion failure for BADIDEA""" + with pytest.raises(ValueError): + self.lat, self.lon = aacgmv2.convert([60], [0], [3000], self.dtime, + badidea=True) + + def test_convert_location_failure(self): + """Test conversion with a bad location""" + self.lat, self.lon = aacgmv2.convert([0], [0], [0], self.dtime) + assert isinstance(self.lat, np.ndarray) + assert isinstance(self.lon, np.ndarray) + assert self.lat.shape == self.lon.shape and self.lat.shape == (1,) + assert np.all([np.isnan(self.lat), np.isnan(self.lon)]) + + def test_convert_time_failure(self): + """Test conversion with a bad time""" + with pytest.raises(AssertionError): + self.lat, self.lon = aacgmv2.convert([60], [0], [300], None) + + def test_convert_datetime_date(self): + """Test conversion with date and datetime input""" + self.lat, self.lon = aacgmv2.convert([60], [0], [300], self.ddate) + np.testing.assert_allclose(self.lat, [58.2258], rtol=1e-4) + np.testing.assert_allclose(self.lon, [81.1685], rtol=1e-4) + + def test_warning_below_ground_convert(self): + """ Test that a warning is issued if altitude is below zero""" + import logbook + lwarn = u"conversion not intended for altitudes < 0 km" + + with logbook.TestHandler() as handler: + self.lat, self.lon = aacgmv2.convert([60], [0], [-1], self.dtime) + assert handler.has_warning(lwarn) + + handler.close() + + def test_convert_maxalt_failure(self): + """For an array, test failure for an altitude too high for + coefficients""" + self.lat, self.lon = aacgmv2.convert([60], [0], [2001], self.dtime) + assert np.all([np.isnan(self.lat), np.isnan(self.lon)]) + + def test_convert_lat_failure(self): + """Test error return for co-latitudes above 90 for an array""" + with pytest.raises(AssertionError): + aacgmv2.convert([91, 60, -91], 0, 300, self.dtime) + + def test_subsol(self): + """Test the subsolar calculation""" + doy = int(self.dtime.strftime("%j")) + ut = self.dtime.hour * 3600.0 + self.dtime.minute * 60.0 + \ + self.dtime.second + self.lon, self.lat = aacgmv2.deprecated.subsol(self.dtime.year, doy, ut) + + np.testing.assert_almost_equal(self.lon, -179.2004, decimal=4) + np.testing.assert_almost_equal(self.lat, -23.0431, decimal=4) + + def test_gc2gd_lat(self): + """Test the geocentric to geodetic conversion""" + self.lat = aacgmv2.deprecated.gc2gd_lat(45.0) + + np.testing.assert_almost_equal(self.lat, 45.1924, decimal=4) + + def test_gc2gd_lat_list(self): + """Test the geocentric to geodetic conversion""" + self.lat = [45.0, -45.0] + self.lat = aacgmv2.deprecated.gc2gd_lat(self.lat) + + np.testing.assert_allclose(self.lat, [45.1924, -45.1924], rtol=1.0e-4) + + def test_gc2gd_lat_arr(self): + """Test the geocentric to geodetic conversion""" + self.lat = np.array([45.0, -45.0]) + self.lat = aacgmv2.deprecated.gc2gd_lat(self.lat) + + np.testing.assert_allclose(self.lat, [45.1924, -45.1924], rtol=1.0e-4) + + def test_igrf_dipole_axis(self): + """Test the IGRF dipole axis calculation""" + m = aacgmv2.deprecated.igrf_dipole_axis(self.dtime) + + np.testing.assert_allclose(m, [0.050253, -0.160608, 0.985738], + rtol=1.0e-4) diff --git a/aacgmv2/tests/test_py_aacgmv2.py b/aacgmv2/tests/test_py_aacgmv2.py new file mode 100644 index 00000000..0f1a5b0d --- /dev/null +++ b/aacgmv2/tests/test_py_aacgmv2.py @@ -0,0 +1,958 @@ +# -*- coding: utf-8 -*- +from __future__ import division, absolute_import, unicode_literals + +import datetime as dt +import numpy as np +import pytest +import aacgmv2 + +class TestPyAACGMV2: + + def test_module_structure(self): + """Test module structure""" + assert aacgmv2 + assert aacgmv2.convert_bool_to_bit + assert aacgmv2.convert_str_to_bit + assert aacgmv2.convert_mlt + assert aacgmv2.convert_latlon + assert aacgmv2.convert_latlon_arr + assert aacgmv2.get_aacgm_coord + assert aacgmv2.get_aacgm_coord_arr + assert aacgmv2.wrapper + assert aacgmv2.wrapper.set_coeff_path + + def test_module_parameters(self): + """Test module constants""" + from os import path + + path1 = path.join("aacgmv2", "aacgmv2", "aacgm_coeffs", + "aacgm_coeffs-12-") + assert aacgmv2.AACGM_V2_DAT_PREFIX.find(path1) >= 0 + + path2 = path.join("aacgmv2", "aacgmv2", "igrf12coeffs.txt") + assert aacgmv2.IGRF_12_COEFFS.find(path2) >= 0 + + del path1, path2 + +class TestConvertLatLon: + + def setup(self): + """Runs before every method to create a clean testing setup""" + self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0) + self.ddate = dt.date(2015, 1, 1) + self.lat_out = None + self.lon_out = None + self.r_out = None + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.lat_out, self.lon_out, self.r_out, self.dtime, self.ddate + + def test_convert_latlon(self): + """Test single value latlon conversion""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(60, 0, 300, self.dtime) + np.testing.assert_almost_equal(self.lat_out, 58.2258, decimal=4) + np.testing.assert_almost_equal(self.lon_out, 81.1685, decimal=4) + np.testing.assert_almost_equal(self.r_out, 1.0457, decimal=4) + + def test_convert_latlon_badidea_failure(self): + """Test single value latlon conversion with a bad flag""" + code = "G2A | BADIDEA" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(60, 0, 3000, self.dtime, code) + assert (np.isnan(self.lat_out) & np.isnan(self.lon_out) & + np.isnan(self.r_out)) + del code + + def test_convert_latlon_location_failure(self): + """Test single value latlon conversion with a bad location""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(0, 0, 0, self.dtime) + assert (np.isnan(self.lat_out) & np.isnan(self.lon_out) & + np.isnan(self.r_out)) + + def test_convert_latlon_time_failure(self): + """Test single value latlon conversion with a bad datetime""" + with pytest.raises(AssertionError): + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(60, 0, 300, None) + + def test_convert_latlon_datetime_date(self): + """Test single latlon conversion with date and datetime input""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(60, 0, 300, self.ddate) + lat_2, lon_2, r_2 = aacgmv2.convert_latlon(60, 0, 300, self.dtime) + + assert self.lat_out == lat_2 + assert self.lon_out == lon_2 + assert self.r_out == r_2 + + del lat_2, lon_2, r_2 + + def test_warning_below_ground_convert_latlon(self): + """ Test that a warning is issued if altitude is below zero""" + import logbook + lwarn = u"conversion not intended for altitudes < 0 km" + + with logbook.TestHandler() as handler: + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(60, 0, -1, self.dtime) + assert handler.has_warning(lwarn) + + handler.close() + + def test_convert_latlon_maxalt_failure(self): + """For a single value, test failure for an altitude too high for + coefficients""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon(60, 0, 2001, self.dtime) + assert (np.isnan(self.lat_out) & np.isnan(self.lon_out) & + np.isnan(self.r_out)) + + def test_convert_latlon_lat_failure(self): + """Test error return for co-latitudes above 90 for a single value""" + with pytest.raises(AssertionError): + aacgmv2.convert_latlon(91, 0, 300, self.dtime) + + with pytest.raises(AssertionError): + aacgmv2.convert_latlon(-91, 0, 300, self.dtime) + +class TestConvertLatLonArr: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0) + self.ddate = dt.date(2015, 1, 1) + self.lat_out = None + self.lon_out = None + self.r_out = None + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.lat_out, self.lon_out, self.r_out, self.dtime, self.ddate + + def test_convert_latlon_arr_single_val(self): + """Test array latlon conversion for a single value""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr(60, 0, 300, self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (1,)) + + np.testing.assert_allclose(self.lat_out, [58.2257709], rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959], rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346], rtol=1e-4) + + def test_convert_latlon_arr_list_single(self): + """Test array latlon conversion for list input of single values""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60], [0], [300], self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (1,)) + + np.testing.assert_allclose(self.lat_out, [58.2257709], rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959], rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346], rtol=1e-4) + + def test_convert_latlon_arr_list(self): + """Test array latlon conversion for list input""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60, 61], [0, 0], [300, 300], + self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (2,)) + + np.testing.assert_allclose(self.lat_out, [58.22577090, 59.31860933], + rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959, 81.61398933], + rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346, 1.04561304], + rtol=1e-4) + + def test_convert_latlon_arr_arr_single(self): + """Test array latlon conversion for array input of shape (1,)""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr(np.array([60]), np.array([0]), + np.array([300]), self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (1,)) + + np.testing.assert_allclose(self.lat_out, [58.2257709], rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959], rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346], rtol=1e-4) + + def test_convert_latlon_arr_arr(self): + """Test array latlon conversion for array input""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr(np.array([60, 61]), + np.array([0, 0]), + np.array([300, 300]), + self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (2,)) + + np.testing.assert_allclose(self.lat_out, [58.22577090, 59.31860933], + rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959, 81.61398933], + rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346, 1.04561304], + rtol=1e-4) + + def test_convert_latlon_arr_list_mix(self): + """Test array latlon conversion for mixed types with list""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60, 61], 0, 300, self.dtime) + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (2,)) + + np.testing.assert_allclose(self.lat_out, [58.22577090, 59.31860933], + rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959, 81.61398933], + rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346, 1.04561304], + rtol=1e-4) + + def test_convert_latlon_arr_arr_mix(self): + """Test array latlon conversion for mixed type with an array""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr(np.array([60, 61]), 0, + 300, self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (2,)) + + np.testing.assert_allclose(self.lat_out, [58.22577090, 59.31860933], + rtol=1e-4) + np.testing.assert_allclose(self.lon_out, [81.16846959, 81.61398933], + rtol=1e-4) + np.testing.assert_allclose(self.r_out, [1.04566346, 1.04561304], + rtol=1e-4) + + def test_convert_latlon_arr_mult_arr_mix(self): + """Test array latlon conversion for mix type with multi-dim array""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr(np.array([[60, 61, 62], + [63, 64, 65]]), + 0, 300, self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (2, 3)) + + np.testing.assert_allclose(self.lat_out, + [[58.2257709, 59.3186093, 60.4039740], + [61.4819893, 62.5527635, 63.6163840]], + rtol=1e-4) + np.testing.assert_allclose(self.lon_out, + [[81.1684696, 81.6139893, 82.0871880], + [82.5909499, 83.1285895, 83.7039272]], + rtol=1e-4) + np.testing.assert_allclose(self.r_out, + [[1.04566346, 1.04561304, 1.04556369], + [1.04551548, 1.04546847, 1.04542272]], + rtol=1e-4) + + def test_convert_latlon_arr_mult_arr_unequal(self): + """Test array latlon conversion for unequal sized multi-dim array""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr(np.array([[60, 61, 62], + [63, 64, 65]]), + np.array([0]), + np.array([300]), self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (2, 3)) + + np.testing.assert_allclose(self.lat_out, + [[58.2257709, 59.3186093, 60.4039740], + [61.4819893, 62.5527635, 63.6163840]], + rtol=1e-4) + np.testing.assert_allclose(self.lon_out, + [[81.1684696, 81.6139893, 82.0871880], + [82.5909499, 83.1285895, 83.7039272]], + rtol=1e-4) + np.testing.assert_allclose(self.r_out, + [[1.04566346, 1.04561304, 1.04556369], + [1.04551548, 1.04546847, 1.04542272]], + rtol=1e-4) + + def test_convert_latlon_arr_badidea_failure(self): + """Test array latlon conversion failure for BADIDEA""" + code = "G2A | BADIDEA" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60], [0], [3000], + self.dtime, code) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (1,)) + assert np.all([np.isnan(self.lat_out), np.isnan(self.lon_out), + np.isnan(self.r_out)]) + + def test_convert_latlon_arr_location_failure(self): + """Test array latlon conversion with a bad location""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([0], [0], [0], self.dtime) + + assert isinstance(self.lat_out, np.ndarray) + assert isinstance(self.lon_out, np.ndarray) + assert isinstance(self.r_out, np.ndarray) + assert (self.r_out.shape == self.lon_out.shape and + self.lat_out.shape == self.r_out.shape and + self.r_out.shape == (1,)) + assert np.all([np.isnan(self.lat_out), np.isnan(self.lon_out), + np.isnan(self.r_out)]) + + def test_convert_latlon_arr_time_failure(self): + """Test array latlon conversion with a bad time""" + with pytest.raises(AssertionError): + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60], [0], [300], None) + + def test_convert_latlon_arr_datetime_date(self): + """Test array latlon conversion with date and datetime input""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60], [0], [300], self.ddate) + lat_2, lon_2, r_2 = aacgmv2.convert_latlon_arr([60], [0], [300], + self.dtime) + assert self.lat_out == lat_2 + assert self.lon_out == lon_2 + assert self.r_out == r_2 + + del lat_2, lon_2, r_2 + + def test_warning_below_ground_convert_latlon_arr(self): + """ Test that a warning is issued if altitude is below zero""" + import logbook + lwarn = u"conversion not intended for altitudes < 0 km" + + with logbook.TestHandler() as handler: + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60], [0], [-1], + self.dtime) + assert handler.has_warning(lwarn) + + handler.close() + + def test_convert_latlon_arr_maxalt_failure(self): + """For an array, test failure for an altitude too high for + coefficients""" + (self.lat_out, self.lon_out, + self.r_out) = aacgmv2.convert_latlon_arr([60], [0], [2001], self.dtime) + assert np.all([np.isnan(self.lat_out), np.isnan(self.lon_out), + np.isnan(self.r_out)]) + + def test_convert_latlon_arr_lat_failure(self): + """Test error return for co-latitudes above 90 for an array""" + with pytest.raises(AssertionError): + aacgmv2.convert_latlon_arr([91, 60, -91], 0, 300, self.dtime) + +class TestGetAACGMCoord: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0) + self.ddate = dt.date(2015, 1, 1) + self.mlat_out = None + self.mlon_out = None + self.mlt_out = None + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.mlat_out, self.mlon_out, self.mlt_out, self.dtime, self.ddate + + def test_get_aacgm_coord(self): + """Test single value AACGMV2 calculation, defaults to TRACE""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(60, 0, 300, self.dtime) + + np.testing.assert_almost_equal(self.mlat_out, 58.2247, decimal=4) + np.testing.assert_almost_equal(self.mlon_out, 81.1761, decimal=4) + np.testing.assert_almost_equal(self.mlt_out, 0.1889, decimal=4) + + def test_get_aacgm_coord_badidea_failure(self): + """Test single value AACGMV2 calculation with a bad flag""" + method = "BADIDEA" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(60, 0, 3000, self.dtime, + method=method) + + assert (np.isnan(self.mlat_out) & np.isnan(self.mlon_out) & + np.isnan(self.mlt_out)) + del method + + def test_get_aacgm_coord_location_failure(self): + """Test single value AACGMV2 calculation with a bad location""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(0, 0, 0, self.dtime) + assert (np.isnan(self.mlat_out) & np.isnan(self.mlon_out) & + np.isnan(self.mlt_out)) + + def test_get_aacgm_coord_time_failure(self): + """Test single value AACGMV2 calculation with a bad datetime""" + with pytest.raises(AssertionError): + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(60, 0, 300, None) + + def test_get_aacgm_coord_datetime_date(self): + """Test single AACGMV2 calculation with date and datetime input""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(60, 0, 300, self.ddate) + mlat_2, mlon_2, mlt_2 = aacgmv2.get_aacgm_coord(60, 0, 300, self.dtime) + + np.testing.assert_almost_equal(self.mlat_out, mlat_2, decimal=6) + np.testing.assert_almost_equal(self.mlon_out, mlon_2, decimal=6) + np.testing.assert_almost_equal(self.mlt_out, mlt_2, decimal=6) + + del mlat_2, mlon_2, mlt_2 + + def test_warning_below_ground_get_aacgm_coord(self): + """ Test that a warning is issued if altitude is below zero""" + import logbook + lwarn = u"conversion not intended for altitudes < 0 km" + + with logbook.TestHandler() as handler: + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(60, 0, -1, self.dtime) + assert handler.has_warning(lwarn) + + handler.close() + + def test_get_aacgm_coord_maxalt_failure(self): + """For a single value, test failure for an altitude too high for + coefficients""" + method = "" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(60, 0, 2001, self.dtime, + method=method) + assert (np.isnan(self.mlat_out) & np.isnan(self.mlon_out) & + np.isnan(self.mlt_out)) + + def test_get_aacgm_coord_mlat_failure(self): + """Test error return for co-latitudes above 90 for a single value""" + import logbook + lerr = u"unrealistic latitude" + + with logbook.TestHandler() as hhigh: + with pytest.raises(AssertionError): + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(91, 0, 300, self.dtime) + assert hhigh.has_error(lerr) + + with logbook.TestHandler() as hlow: + with pytest.raises(AssertionError): + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord(-91, 0, 300, + self.dtime) + assert hlow.has_error(lerr) + + hhigh.close() + hlow.close() + +class TestGetAACGMCoordArr: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0) + self.ddate = dt.date(2015, 1, 1) + self.mlat_out = None + self.mlon_out = None + self.mlt_out = None + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.mlat_out, self.mlon_out, self.mlt_out, self.dtime, self.ddate + + def test_get_aacgm_coord_arr_single_val(self): + """Test array AACGMV2 calculation for a single value""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr(60, 0, 300, self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (1,)) + + np.testing.assert_allclose(self.mlat_out, [58.22474610], rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, [81.17611033], rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, [0.18891995], rtol=1e-4) + + def test_get_aacgm_coord_arr_list_single(self): + """Test array AACGMV2 calculation for list input of single values""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60], [0], [300], + self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (1,)) + + np.testing.assert_allclose(self.mlat_out, [58.22474610], rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, [81.17611033], rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, [0.18891995], rtol=1e-4) + + def test_get_aacgm_coord_arr_list(self): + """Test array AACGMV2 calculation for list input""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60, 61], [0, 0], + [300, 300], self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (2,)) + + np.testing.assert_allclose(self.mlat_out, + [58.22474610, 59.31648007], rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, + [81.17611033, 81.62281360], rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, + [0.18891995, 0.21870017], rtol=1e-4) + + def test_get_aacgm_coord_arr_arr_single(self): + """Test array AACGMV2 calculation for array with a single value""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr(np.array([60]), + np.array([0]), + np.array([300]), + self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (1,)) + + np.testing.assert_almost_equal(self.mlat_out, 58.2247, decimal=4) + np.testing.assert_almost_equal(self.mlon_out, 81.1761, decimal=4) + np.testing.assert_almost_equal(self.mlt_out, 0.1889, decimal=4) + + def test_get_aacgm_coord_arr_arr(self): + """Test array AACGMV2 calculation for an array""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr(np.array([60, 61]), + np.array([0, 0]), + np.array([300, 300]), + self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (2,)) + + np.testing.assert_allclose(self.mlat_out, + [58.22474610, 59.31648007], rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, + [81.17611033, 81.62281360], rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, + [0.18891995, 0.21870017], rtol=1e-4) + + def test_get_aacgm_coord_arr_list_mix(self): + """Test array AACGMV2 calculation for a list and floats""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60, 61], 0, 300, + self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (2,)) + + np.testing.assert_allclose(self.mlat_out, + [58.22474610, 59.31648007], rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, + [81.17611033, 81.62281360], rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, + [0.18891995, 0.21870017], rtol=1e-4) + + def test_get_aacgm_coord_arr_arr_mix(self): + """Test array AACGMV2 calculation for an array and floats""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr(np.array([60, 61]), 0, + 300, self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (2,)) + + np.testing.assert_allclose(self.mlat_out, + [58.22474610, 59.31648007], rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, + [81.17611033, 81.62281360], rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, + [0.18891995, 0.21870017], rtol=1e-4) + + def test_get_aacgm_coord_arr_mult_arr_mix(self): + """Test array AACGMV2 calculation for a multi-dim array and + floats""" + mlat_in = np.array([[60, 61, 62], [63, 64, 65]]) + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr(mlat_in, 0, 300, + self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (2, 3)) + + np.testing.assert_allclose(self.mlat_out, + [[58.2247461, 59.3164801, 60.4008651], + [61.4780560, 62.5481858, 63.6113609]], + rtol=1e-4) + np.testing.assert_allclose(self.mlon_out, + [[81.1761103, 81.6228136, 82.0969646], + [82.6013918, 83.1393547, 83.7146224]], + rtol=1e-4) + np.testing.assert_allclose(self.mlt_out, + [[0.18891995, 0.21870017, 0.25031024], + [0.28393872, 0.31980291, 0.35815409]], + rtol=1e-4) + del mlat_in + + def test_get_aacgm_coord_arr_arr_unequal(self): + """Test array AACGMV2 calculation for unequal arrays""" + mlat_in = np.array([[60, 61, 62], [63, 64, 65]]) + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr(mlat_in, np.array([0]), + np.array([300]), + self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (2, 3)) + + np.testing.assert_allclose(self.mlat_out, + [[58.2247, 59.3165, 60.4009], + [61.4781, 62.5482, 63.6114]], rtol=1e-3) + np.testing.assert_allclose(self.mlon_out, + [[81.1761, 81.6228, 82.0970], + [82.6014, 83.1394, 83.7146]], rtol=1e-3) + np.testing.assert_allclose(self.mlt_out, + [[0.1889, 0.2187, 0.2503], + [0.2839, 0.3198, 0.3582]], rtol=1e-3) + del mlat_in + + def test_get_aacgm_coord_arr_badidea_failure(self): + """Test array AACGMV2 calculation failure for BADIDEA""" + method = "BADIDEA" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60], [0], [3000], + self.dtime, method=method) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (1,)) + assert np.all([np.isnan(self.mlat_out), np.isnan(self.mlon_out), + np.isnan(self.mlt_out)]) + del method + + def test_get_aacgm_coord_arr_location_failure(self): + """Test array AACGMV2 calculation with a bad location""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([0], [0], [0], self.dtime) + + assert isinstance(self.mlat_out, np.ndarray) + assert isinstance(self.mlon_out, np.ndarray) + assert isinstance(self.mlt_out, np.ndarray) + assert (self.mlt_out.shape == self.mlon_out.shape and + self.mlat_out.shape == self.mlt_out.shape and + self.mlt_out.shape == (1,)) + assert np.all([np.isnan(self.mlat_out), np.isnan(self.mlon_out), + np.isnan(self.mlt_out)]) + + def test_get_aacgm_coord_arr_time_failure(self): + """Test array AACGMV2 calculation with a bad time""" + with pytest.raises(AssertionError): + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60], [0], [300], + None) + + def test_get_aacgm_coord_arr_datetime_date(self): + """Test array AACGMV2 calculation with date and datetime input""" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60], [0], [300], + self.ddate) + mlat_2, mlon_2, mlt_2 = aacgmv2.get_aacgm_coord_arr([60], [0], [300], + self.dtime) + + np.testing.assert_almost_equal(self.mlat_out, mlat_2, decimal=6) + np.testing.assert_almost_equal(self.mlon_out, mlon_2, decimal=6) + np.testing.assert_almost_equal(self.mlt_out, mlt_2, decimal=6) + + del mlat_2, mlon_2, mlt_2 + + def test_warning_below_ground_get_aacgm_coord_arr(self): + """ Test that a warning is issued if altitude is below zero""" + import logbook + lwarn = u"conversion not intended for altitudes < 0 km" + + with logbook.TestHandler() as handler: + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60], [0], [-1], + self.dtime) + assert handler.has_warning(lwarn) + + handler.close() + + def test_get_aacgm_coord_arr_maxalt_failure(self): + """For an array, test failure for an altitude too high for + coefficients""" + method = "" + (self.mlat_out, self.mlon_out, + self.mlt_out) = aacgmv2.get_aacgm_coord_arr([60], [0], [2001], + self.dtime, method=method) + assert np.all([np.isnan(self.mlat_out), np.isnan(self.mlon_out), + np.isnan(self.mlt_out)]) + del method + + def test_get_aacgm_coord_arr_mlat_failure(self): + """Test error return for co-latitudes above 90 for an array""" + import logbook + lerr = u"unrealistic latitude" + + with logbook.TestHandler() as handler: + with pytest.raises(AssertionError): + aacgmv2.get_aacgm_coord_arr([91, 60, -91], 0, 300, + self.dtime) + assert handler.has_error(lerr) + + handler.close() + +class TestConvertCode: + def test_convert_str_to_bit_g2a(self): + """Test conversion from string code to bit G2A""" + assert aacgmv2.convert_str_to_bit("G2A") == aacgmv2._aacgmv2.G2A + + def test_convert_str_to_bit_a2g(self): + """Test conversion from string code to bit A2G""" + assert aacgmv2.convert_str_to_bit("A2G") == aacgmv2._aacgmv2.A2G + + def test_convert_str_to_bit_trace(self): + """Test conversion from string code to bit TRACE""" + assert aacgmv2.convert_str_to_bit("TRACE") == aacgmv2._aacgmv2.TRACE + + def test_convert_str_to_bit_allowtrace(self): + """Test conversion from string code to bit ALLOWTRACE""" + assert aacgmv2.convert_str_to_bit("ALLOWTRACE") == \ + aacgmv2._aacgmv2.ALLOWTRACE + + def test_convert_str_to_bit_badidea(self): + """Test conversion from string code to bit BADIDEA""" + assert aacgmv2.convert_str_to_bit("BADIDEA") == \ + aacgmv2._aacgmv2.BADIDEA + + def test_convert_str_to_bit_geocentric(self): + """Test conversion from string code to bit GEOCENTRIC""" + assert aacgmv2.convert_str_to_bit("GEOCENTRIC") == \ + aacgmv2._aacgmv2.GEOCENTRIC + + def test_convert_str_to_bit_lowercase(self): + """Test conversion from string code to bit for a lowercase code""" + assert aacgmv2.convert_str_to_bit("g2a") == aacgmv2._aacgmv2.G2A + + def test_convert_str_to_bit_spaces(self): + """Test conversion from string code to bit for a code with spaces""" + assert aacgmv2.convert_str_to_bit("G2A | trace") == \ + aacgmv2._aacgmv2.G2A + aacgmv2._aacgmv2.TRACE + + def test_convert_str_to_bit_invalid(self): + """Test conversion from string code to bit for an invalid code""" + assert aacgmv2.convert_str_to_bit("ggoogg|") == aacgmv2._aacgmv2.G2A + + def test_convert_bool_to_bit_g2a(self): + """Test conversion from string code to bit G2A""" + assert aacgmv2.convert_bool_to_bit() == aacgmv2._aacgmv2.G2A + + def test_convert_bool_to_bit_a2g(self): + """Test conversion from string code to bit A2G""" + assert aacgmv2.convert_bool_to_bit(a2g=True) == aacgmv2._aacgmv2.A2G + + def test_convert_bool_to_bit_trace(self): + """Test conversion from string code to bit TRACE""" + assert aacgmv2.convert_bool_to_bit(trace=True) == aacgmv2._aacgmv2.TRACE + + def test_convert_bool_to_bit_allowtrace(self): + """Test conversion from string code to bit ALLOWTRACE""" + assert aacgmv2.convert_bool_to_bit(allowtrace=True) == \ + aacgmv2._aacgmv2.ALLOWTRACE + + def test_convert_bool_to_bit_badidea(self): + """Test conversion from string code to bit BADIDEA""" + assert aacgmv2.convert_bool_to_bit(badidea=True) == \ + aacgmv2._aacgmv2.BADIDEA + + def test_convert_bool_to_bit_geocentric(self): + """Test conversion from string code to bit GEOCENTRIC""" + assert aacgmv2.convert_bool_to_bit(geocentric=True) == \ + aacgmv2._aacgmv2.GEOCENTRIC + +class TestMLTConvert: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.dtime = dt.datetime(2015, 1, 1, 0, 0, 0) + self.ddate = dt.date(2015, 1, 1) + self.mlon_out = None + self.mlt_out = None + self.mlon_list = [270.0, 80.0, -95.0] + self.mlt_list = [12.0, 25.0, -1.0] + self.mlon_comp = [-101.657689, 93.34231102, 63.34231102] + self.mlt_comp = [12.77717927, 0.1105126, 12.44384593] + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.mlon_out, self.mlt_out, self.mlt_list, self.mlon_list + del self.mlon_comp, self.mlt_comp + + def test_inv_convert_mlt_single(self): + """Test MLT inversion for a single value""" + for i,mlt in enumerate(self.mlt_list): + self.mlon_out = aacgmv2.convert_mlt(mlt, self.dtime, m2a=True) + np.testing.assert_almost_equal(self.mlon_out, self.mlon_comp[i], + decimal=4) + + def test_inv_convert_mlt_list(self): + """Test MLT inversion for a list""" + self.mlon_out = aacgmv2.convert_mlt(self.mlt_list, self.dtime, m2a=True) + np.testing.assert_allclose(self.mlon_out, self.mlon_comp, rtol=1.0e-4) + + def test_inv_convert_mlt_arr(self): + """Test MLT inversion for an array""" + self.mlon_out = aacgmv2.convert_mlt(np.array(self.mlt_list), self.dtime, + m2a=True) + + np.testing.assert_allclose(self.mlon_out, self.mlon_comp, rtol=1.0e-4) + + def test_inv_convert_mlt_wrapping(self): + """Test MLT wrapping""" + self.mlon_out = aacgmv2.convert_mlt(np.array([1, 25, -1, 23]), + self.dtime, m2a=True) + + np.testing.assert_almost_equal(self.mlon_out[0], self.mlon_out[1], + decimal=6) + np.testing.assert_almost_equal(self.mlon_out[2], self.mlon_out[3], + decimal=6) + + def test_mlt_convert_mlon_wrapping(self): + """Test mlon wrapping""" + self.mlt_out = aacgmv2.convert_mlt(np.array([270, -90, 1, 361]), + self.dtime, m2a=False) + + np.testing.assert_almost_equal(self.mlt_out[0], self.mlt_out[1], + decimal=6) + np.testing.assert_almost_equal(self.mlt_out[2], self.mlt_out[3], + decimal=6) + + def test_mlt_convert_single(self): + """Test MLT calculation for a single value""" + for i,mlon in enumerate(self.mlon_list): + self.mlt_out = aacgmv2.convert_mlt(mlon, self.dtime, m2a=False) + np.testing.assert_almost_equal(self.mlt_out, self.mlt_comp[i], + decimal=4) + + def test_mlt_convert_list(self): + """Test MLT calculation for a list""" + self.mlt_out = aacgmv2.convert_mlt(self.mlon_list, self.dtime, + m2a=False) + np.testing.assert_allclose(self.mlt_out, self.mlt_comp, rtol=1.0e-4) + + def test_mlt_convert_arr(self): + """Test MLT calculation for an array""" + self.mlt_out = aacgmv2.convert_mlt(np.array(self.mlon_list), + self.dtime, m2a=False) + np.testing.assert_allclose(self.mlt_out, self.mlt_comp, rtol=1.0e-4) + +class TestCoeffPath: + def setup(self): + """Runs before every method to create a clean testing setup""" + self.igrf_out = None + self.aacgm_out = None + + def teardown(self): + """Runs after every method to clean up previous testing""" + del self.igrf_out, self.aacgm_out + + def test_set_coeff_path_default(self): + """Test the coefficient path setting using defaults""" + self.igrf_out, self.coeff_out = aacgmv2.wrapper.set_coeff_path() + + assert self.igrf_out == aacgmv2.IGRF_12_COEFFS + assert self.coeff_out == aacgmv2.AACGM_V2_DAT_PREFIX + + def test_set_coeff_path_different(self): + """Test the coefficient path setting""" + self.igrf_out, self.coeff_out = aacgmv2.wrapper.set_coeff_path("hi", + "bye") + + assert self.igrf_out == "hi" + assert self.coeff_out == "bye" + + def test_set_coeff_path_mix(self): + """Test the coefficient path setting using a mix of input""" + (self.igrf_out, + self.coeff_out) = aacgmv2.wrapper.set_coeff_path(coeff_prefix="hi") + + assert self.igrf_out == aacgmv2.IGRF_12_COEFFS + assert self.coeff_out == "hi" diff --git a/aacgmv2/wrapper.py b/aacgmv2/wrapper.py new file mode 100644 index 00000000..9ed22714 --- /dev/null +++ b/aacgmv2/wrapper.py @@ -0,0 +1,556 @@ +# -*- coding: utf-8 -*- +"""Pythonic wrappers for AACGM-V2 C functions. + +Functions +-------------- +set_coeff_path : Set the coefficient paths using default or supplied values +convert_latlon : Converts scalar location +convert_latlon_arr : Converts array location +get_aacgm_coord : Get scalar magnetic lat, lon, mlt from geographic location +get_aacgm_coord_arr : Get array magnetic lat, lon, mlt from geographic location +convert_str_to_bit : Convert human readible AACGM flag to bits +convert_bool_to_bit : Convert boolian flags to bits +convert_mlt : Get array mlt +-------------- +""" + +from __future__ import division, absolute_import, unicode_literals +import datetime as dt +import numpy as np +import logbook as logging + +def set_coeff_path(igrf_file=None, coeff_prefix=None): + """This routine sets the two path variables. + + Parameters + ----------- + igrf_file : (str or NoneType) + Full filename of IGRF coefficient file or None to use + aacgmv2.IGRF_12_COEFFS. (default=None) + coeff_prefix : (str or NoneType) + Location and file prefix for aacgm coefficient files or None to use + aacgmv2.AACGM_V2_DAT_PREFIX. (default=None) + + Returns + --------- + igrf_file : (str) + Full filename of IGRF coefficient file + coeff_prefix : (str) + Location and file prefix for aacgm coefficient files + """ + import aacgmv2 + + # Define coefficient file prefix if not supplied + if coeff_prefix is None: + coeff_prefix = aacgmv2.AACGM_V2_DAT_PREFIX + + # Define IGRF file if not supplied + if igrf_file is None: + igrf_file = aacgmv2.IGRF_12_COEFFS + + return igrf_file, coeff_prefix + +def convert_latlon(in_lat, in_lon, height, dtime, code="G2A", igrf_file=None, + coeff_prefix=None): + """Converts between geomagnetic coordinates and AACGM coordinates + + Parameters + ------------ + in_lat : (float) + Input latitude in degrees N (code specifies type of latitude) + in_lon : (float) + Input longitude in degrees E (code specifies type of longitude) + height : (float) + Altitude above the surface of the earth in km + dtime : (datetime) + Datetime for magnetic field + code : (str or int) + Bit code or string denoting which type(s) of conversion to perform + G2A - geographic (geodetic) to AACGM-v2 + A2G - AACGM-v2 to geographic (geodetic) + TRACE - use field-line tracing, not coefficients + ALLOWTRACE - use trace only above 2000 km + BADIDEA - use coefficients above 2000 km + GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 + (default is "G2A") + igrf_file : (str or NoneType) + Full filename of IGRF coefficient file or None to use + aacgmv2.IGRF_12_COEFFS. (default=None) + coeff_prefix : (str or NoneType) + Location and file prefix for aacgm coefficient files or None to use + aacgmv2.AACGM_V2_DAT_PREFIX. (default=None) + + Returns + ------- + out_lat : (float) + Output latitude in degrees N + out_lon : (float) + Output longitude in degrees E + out_r : (float) + Geocentric radial distance (R_Earth) or altitude above the surface of + the Earth (km) + """ + import aacgmv2._aacgmv2 as c_aacgmv2 + + # Define coefficient file prefix if not supplied + igrf_file, coeff_prefix = set_coeff_path(igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + + # Test time + if isinstance(dtime, dt.date): + dtime = dt.datetime.combine(dtime, dt.time(0)) + + assert isinstance(dtime, dt.datetime), \ + logging.error('time must be specified as datetime object') + + # Test height + if height < 0: + logging.warn('conversion not intended for altitudes < 0 km') + + # Initialise output + lat_out = np.nan + lon_out = np.nan + r_out = np.nan + + # Test code + try: + code = code.upper() + + if(height > 2000 and code.find("TRACE") < 0 and + code.find("ALLOWTRACE") < 0 and code.find("BADIDEA")): + estr = 'coefficients are not valid for altitudes above 2000 km. You' + estr += ' must either use field-line tracing (trace=True ' + estr += 'or allowtrace=True) or indicate you know this ' + estr += 'is a bad idea' + logging.error(estr) + return lat_out, lon_out, r_out + + # make flag + bit_code = convert_str_to_bit(code) + except AttributeError: + bit_code = code + + assert isinstance(bit_code, int), \ + logging.error("unknown code {:}".format(bit_code)) + + # Test latitude range + if abs(in_lat) > 90.0: + assert abs(in_lat) <= 90.1, logging.error('unrealistic latitude') + in_lat = np.sign(in_lat) * 90.0 + + # Constrain longitudes between -180 and 180 + in_lon = ((in_lon + 180.0) % 360.0) - 180.0 + + # Set current date and time + c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, + dtime.minute, dtime.second, coeff_prefix) + + # convert location + try: + lat_out, lon_out, r_out = c_aacgmv2.convert(in_lat, in_lon, height, + bit_code, igrf_file) + except: + pass + + return lat_out, lon_out, r_out + +def convert_latlon_arr(in_lat, in_lon, height, dtime, code="G2A", + igrf_file=None, coeff_prefix=None): + """Converts between geomagnetic coordinates and AACGM coordinates. At least + one of in_lat, in_lon, and height must be a list or array + + Parameters + ------------ + in_lat : (np.ndarray or list or float) + Input latitude in degrees N (code specifies type of latitude) + in_lon : (np.ndarray or list or float) + Input longitude in degrees E (code specifies type of longitude) + height : (np.ndarray or list or float) + Altitude above the surface of the earth in km + dtime : (datetime) + Single datetime object for magnetic field + code : (int or str) + Bit code or string denoting which type(s) of conversion to perform + G2A - geographic (geodetic) to AACGM-v2 + A2G - AACGM-v2 to geographic (geodetic) + TRACE - use field-line tracing, not coefficients + ALLOWTRACE - use trace only above 2000 km + BADIDEA - use coefficients above 2000 km + GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 + (default = "G2A") + igrf_file : (str or NoneType) + Full filename of IGRF coefficient file or None to use + aacgmv2.IGRF_12_COEFFS. (default=None) + coeff_prefix : (str or NoneType) + Location and file prefix for aacgm coefficient files or None to use + aacgmv2.AACGM_V2_DAT_PREFIX. (default=None) + + Returns + ------- + out_lat : (np.ndarray) + Output latitudes in degrees N + out_lon : (np.ndarray) + Output longitudes in degrees E + out_r : (np.ndarray) + Geocentric radial distance (R_Earth) or altitude above the surface of + the Earth (km) + """ + import aacgmv2._aacgmv2 as c_aacgmv2 + + # If a list was entered instead of a numpy array, recast it here + if isinstance(in_lat, list): + in_lat = np.array(in_lat) + + if isinstance(in_lon, list): + in_lon = np.array(in_lon) + + if isinstance(height, list): + height = np.array(height) + + # If one or two of these elements is a float or int, create an array + test_array = np.array([hasattr(in_lat, "shape"), hasattr(in_lon, "shape"), + hasattr(height, "shape")]) + if not test_array.all(): + if test_array.any(): + arr_shape = in_lat.shape if test_array.argmax() == 0 else \ + (in_lon.shape if test_array.argmax() == 1 else + height.shape) + if not test_array[0]: + in_lat = np.ones(shape=arr_shape, dtype=float) * in_lat + if not test_array[1]: + in_lon = np.ones(shape=arr_shape, dtype=float) * in_lon + if not test_array[2]: + height = np.ones(shape=arr_shape, dtype=float) * height + else: + logging.info("for a single location, consider using convert_latlon") + in_lat = np.array([in_lat]) + in_lon = np.array([in_lon]) + height = np.array([height]) + + # Ensure that lat, lon, and height are the same length or if the lengths + # differ that the different ones contain only a single value + if not (in_lat.shape == in_lon.shape and in_lat.shape == height.shape): + ulen = np.unique([in_lat.shape, in_lon.shape, height.shape]) + if ulen.min() != (1,): + logging.error("mismatched input arrays") + return None, None, None + + # Define coefficient file prefix if not supplied + igrf_file, coeff_prefix = set_coeff_path(igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + + # Test time + if isinstance(dtime, dt.date): + dtime = dt.datetime.combine(dtime, dt.time(0)) + + assert isinstance(dtime, dt.datetime), \ + logging.error('time must be specified as datetime object') + + # Test height + if np.min(height) < 0: + logging.warn('conversion not intended for altitudes < 0 km') + + # Initialise output + lat_out = np.empty(shape=in_lat.shape, dtype=float) * np.nan + lon_out = np.empty(shape=in_lon.shape, dtype=float) * np.nan + r_out = np.empty(shape=height.shape, dtype=float) * np.nan + + # Test code + try: + code = code.upper() + + if(np.nanmax(height) > 2000 and code.find("TRACE") < 0 and + code.find("ALLOWTRACE") < 0 and code.find("BADIDEA")): + estr = 'coefficients are not valid for altitudes above 2000 km. You' + estr += ' must either use field-line tracing (trace=True ' + estr += 'or allowtrace=True) or indicate you know this ' + estr += 'is a bad idea' + logging.error(estr) + return lat_out, lon_out, r_out + + # make flag + bit_code = convert_str_to_bit(code) + except AttributeError: + bit_code = code + + assert isinstance(bit_code, int), \ + logging.error("unknown code {:}".format(bit_code)) + + # Test latitude range + if np.abs(in_lat).max() > 90.0: + assert np.abs(in_lat).max() <= 90.1, \ + logging.error('unrealistic latitude') + in_lat = np.clip(in_lat, -90.0, 90.0) + + # Constrain longitudes between -180 and 180 + in_lon = ((in_lon + 180.0) % 360.0) - 180.0 + + # Set current date and time + c_aacgmv2.set_datetime(dtime.year, dtime.month, dtime.day, dtime.hour, + dtime.minute, dtime.second, coeff_prefix) + + # Vectorise the AACGM code + convert_vectorised = np.vectorize(c_aacgmv2.convert) + + # convert + try: + lat_out, lon_out, r_out = convert_vectorised(in_lat, in_lon, height, + bit_code, igrf_file) + except: + pass + + return lat_out, lon_out, r_out + +def get_aacgm_coord(glat, glon, height, dtime, method="TRACE", + igrf_file=None, coeff_prefix=None): + """Get AACGM latitude, longitude, and magnetic local time + + Parameters + ------------ + glat : (float) + Geodetic latitude in degrees N + glon : (float) + Geodetic longitude in degrees E + height : (float) + Altitude above the surface of the earth in km + dtime : (datetime) + Date and time to calculate magnetic location + method : (str) + String denoting which type(s) of conversion to perform + TRACE - use field-line tracing, not coefficients + ALLOWTRACE - use trace only above 2000 km + BADIDEA - use coefficients above 2000 km + GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 + (default = "TRACE") + igrf_file : (str or NoneType) + Full filename of IGRF coefficient file or None to use + aacgmv2.IGRF_12_COEFFS. (default=None) + coeff_prefix : (str or NoneType) + Location and file prefix for aacgm coefficient files or None to use + aacgmv2.AACGM_V2_DAT_PREFIX. (default=None) + + Returns + ------- + mlat : (float) + magnetic latitude in degrees N + mlon : (float) + magnetic longitude in degrees E + mlt : (float) + magnetic local time in hours + """ + # Define coefficient file prefix if not supplied + igrf_file, coeff_prefix = set_coeff_path(igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + + # Initialize code + code = "G2A|{:s}".format(method) + + # Get magnetic lat and lon. + mlat, mlon, _ = convert_latlon(glat, glon, height, dtime, code=code, + igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + # Get magnetic local time + if np.isnan(mlon): + mlt = np.nan + else: + mlt = convert_mlt(mlon, dtime, m2a=False, coeff_prefix=coeff_prefix, + igrf_file=igrf_file) + + return mlat, mlon, mlt + + +def get_aacgm_coord_arr(glat, glon, height, dtime, method="TRACE", + igrf_file=None, coeff_prefix=None): + """Get AACGM latitude, longitude, and magnetic local time + + Parameters + ------------ + glat : (np.array or list) + Geodetic latitude in degrees N + glon : (np.array or list) + Geodetic longitude in degrees E + height : (np.array or list) + Altitude above the surface of the earth in km + dtime : (datetime) + Date and time to calculate magnetic location + method : (str) + String denoting which type(s) of conversion to perform + TRACE - use field-line tracing, not coefficients + ALLOWTRACE - use trace only above 2000 km + BADIDEA - use coefficients above 2000 km + GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 + (default = "TRACE") + igrf_file : (str or NoneType) + Full filename of IGRF coefficient file or None to use + aacgmv2.IGRF_12_COEFFS. (default=None) + coeff_prefix : (str or NoneType) + Location and file prefix for aacgm coefficient files or None to use + aacgmv2.AACGM_V2_DAT_PREFIX. (default=None) + + Returns + ------- + mlat : (float) + magnetic latitude in degrees N + mlon : (float) + magnetic longitude in degrees E + mlt : (float) + magnetic local time in hours + """ + # Define coefficient file prefix if not supplied + igrf_file, coeff_prefix = set_coeff_path(igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + + # Initialize code + code = "G2A|{:s}".format(method) + + # Get magnetic lat and lon. + mlat, mlon, _ = convert_latlon_arr(glat, glon, height, dtime, code=code, + igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + + if np.all(np.isnan(mlon)): + mlt = np.empty(shape=mlat.shape, dtype=float) * np.nan + else: + # Get magnetic local time + mlt = convert_mlt(mlon, dtime, m2a=False, coeff_prefix=coeff_prefix, + igrf_file=igrf_file) + + return mlat, mlon, mlt + +def convert_str_to_bit(code): + """convert string code specification to bit code specification + + Parameters + ------------ + code : (str) + Bitwise code for passing options into converter (default=0) + G2A - geographic (geodetic) to AACGM-v2 + A2G - AACGM-v2 to geographic (geodetic) + TRACE - use field-line tracing, not coefficients + ALLOWTRACE - use trace only above 2000 km + BADIDEA - use coefficients above 2000 km + GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 + + Returns + -------- + bit_code : (int) + code specification in bits + + Notes + -------- + Multiple codes should be seperated by pipes '|'. Invalid parts of the code + are ignored and no code defaults to 'G2A'. + """ + import aacgmv2._aacgmv2 as c_aacgmv2 + + convert_code = {"G2A": c_aacgmv2.G2A, "A2G": c_aacgmv2.A2G, + "TRACE": c_aacgmv2.TRACE, "BADIDEA": c_aacgmv2.BADIDEA, + "GEOCENTRIC": c_aacgmv2.GEOCENTRIC, + "ALLOWTRACE": c_aacgmv2.ALLOWTRACE} + + # Force upper case, remove any spaces, and split along pipes + codes = code.upper().replace(" ", "").split("|") + + # Add the valid parts of the code, invalid elements are ignored + bit_code = sum([convert_code[k] for k in codes if k in convert_code.keys()]) + + return bit_code + +def convert_bool_to_bit(a2g=False, trace=False, allowtrace=False, + badidea=False, geocentric=False): + """convert boolian flags to bit code specification + + Parameters + ---------- + a2g : (bool) + True for AACGM-v2 to geographic (geodetic), False otherwise + (default=False) + trace : (bool) + If True, use field-line tracing, not coefficients (default=False) + allowtrace : (bool) + If True, use trace only above 2000 km (default=False) + badidea : (bool) + If True, use coefficients above 2000 km (default=False) + geocentric : (bool) + True for geodetic, False for geocentric w/RE=6371.2 (default=False) + + Returns + -------- + bit_code : (int) + code specification in bits + """ + import aacgmv2._aacgmv2 as c_aacgmv2 + + bit_code = c_aacgmv2.A2G if a2g else c_aacgmv2.G2A + + if trace: + bit_code += c_aacgmv2.TRACE + if allowtrace: + bit_code += c_aacgmv2.ALLOWTRACE + if badidea: + bit_code += c_aacgmv2.BADIDEA + if geocentric: + bit_code += c_aacgmv2.GEOCENTRIC + + return bit_code + +def convert_mlt(arr, dtime, m2a=False, coeff_prefix=None, igrf_file=None): + """Converts between magnetic local time (MLT) and AACGM-v2 longitude + + Parameters + ------------ + arr : (array_line or float) + Magnetic longitudes (degrees E) or MLTs (hours) to convert + dtime : (datetime.datetime) + Date and time for MLT conversion in Universal Time (UT). + m2a : (bool) + Convert MLT to AACGM-v2 longitude (True) or magnetic longitude to MLT + (False). (default=False) + coeff_prefix : (str or NoneType) + Location and file prefix for aacgm coefficient files or None to use + aacgmv2.AACGM_V2_DAT_PREFIX. (default=None) + igrf_file : (str or NoneType) + Full filename of IGRF coefficient file or None to use + aacgmv2.IGRF_12_COEFFS. (default=None) + + Returns + -------- + out : (np.ndarray) + Converted coordinates/MLT in degrees E or hours (as appropriate) + + Notes + ------- + This routine previously based on Laundal et al. 2016, but now uses the + improved calculation available in AACGM-V2.4. + """ + import aacgmv2._aacgmv2 as c_aacgmv2 + + # Define coefficient file prefix if not supplied + igrf_file, coeff_prefix = set_coeff_path(igrf_file=igrf_file, + coeff_prefix=coeff_prefix) + + # Test time + if isinstance(dtime, dt.date): + dtime = dt.datetime.combine(dtime, dt.time(0)) + + assert isinstance(dtime, dt.datetime), \ + logging.error('time must be specified as datetime object') + + # Calculate desired location, C routines set date and time + if m2a: + # Get the magnetic longitude + inv_vectorised = np.vectorize(c_aacgmv2.inv_mlt_convert) + out = inv_vectorised(dtime.year, dtime.month, dtime.day, dtime.hour, + dtime.minute, dtime.second, arr, coeff_prefix, + igrf_file) + else: + # Get magnetic local time + mlt_vectorised = np.vectorize(c_aacgmv2.mlt_convert) + out = mlt_vectorised(dtime.year, dtime.month, dtime.day, dtime.hour, + dtime.minute, dtime.second, arr, coeff_prefix, + igrf_file) + + if hasattr(out, "shape") and out.shape == (): + out = float(out) + + return out diff --git a/appveyor.yml b/appveyor.yml index e67cc470..3aeac6da 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,18 +16,6 @@ environment: PYTHON_HOME: C:\python27-x64 PYTHON_VERSION: '2.7' PYTHON_ARCH: '64' - - TOXENV: '3.3-buildonly-nocover' - TOXPYTHON: C:\python33\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '32' - - TOXENV: '3.3-buildonly-nocover' - TOXPYTHON: C:\python33-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33-x64 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '64' - TOXENV: '3.4-buildonly-nocover' TOXPYTHON: C:\python34\python.exe WINDOWS_SDK_VERSION: v7.1 @@ -52,147 +40,67 @@ environment: PYTHON_HOME: C:\python35-x64 PYTHON_VERSION: '3.5' PYTHON_ARCH: '64' - - TOXENV: check - PYTHON_HOME: C:\Python27 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '32' - - TOXENV: '2.7-np1.10-nocover' - TOXPYTHON: C:\python27\python.exe - WINDOWS_SDK_VERSION: v7.0 - PYTHON_HOME: C:\python27 - PYTHON_VERSION: '2.7' + - TOXENV: '3.6-buildonly-nocover' + TOXPYTHON: C:\python36\python.exe + WINDOWS_SDK_VERSION: v7.1 + PYTHON_HOME: C:\python36 + PYTHON_VERSION: '3.6' PYTHON_ARCH: '32' - - TOXENV: '2.7-np1.10-nocover' - TOXPYTHON: C:\python27-x64\python.exe - WINDOWS_SDK_VERSION: v7.0 - PYTHON_HOME: C:\python27-x64 - PYTHON_VERSION: '2.7' + - TOXENV: '3.6-buildonly-nocover' + TOXPYTHON: C:\python36-x64\python.exe + WINDOWS_SDK_VERSION: v7.1 + PYTHON_HOME: C:\python36-x64 + PYTHON_VERSION: '3.6' PYTHON_ARCH: '64' - - TOXENV: '2.7-np1.8-nocover' - TOXPYTHON: C:\python27\python.exe - WINDOWS_SDK_VERSION: v7.0 - PYTHON_HOME: C:\python27 + - TOXENV: check + PYTHON_HOME: C:\Python27 PYTHON_VERSION: '2.7' PYTHON_ARCH: '32' - - TOXENV: '2.7-np1.8-nocover' - TOXPYTHON: C:\python27-x64\python.exe - WINDOWS_SDK_VERSION: v7.0 - PYTHON_HOME: C:\python27-x64 - PYTHON_VERSION: '2.7' - PYTHON_ARCH: '64' - - TOXENV: '2.7-np1.9-nocover' + - TOXENV: '2.7-nocover' TOXPYTHON: C:\python27\python.exe WINDOWS_SDK_VERSION: v7.0 PYTHON_HOME: C:\python27 PYTHON_VERSION: '2.7' PYTHON_ARCH: '32' - - TOXENV: '2.7-np1.9-nocover' + - TOXENV: '2.7-nocover' TOXPYTHON: C:\python27-x64\python.exe WINDOWS_SDK_VERSION: v7.0 PYTHON_HOME: C:\python27-x64 PYTHON_VERSION: '2.7' PYTHON_ARCH: '64' - - TOXENV: '3.3-np1.10-nocover' - TOXPYTHON: C:\python33\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '32' - - TOXENV: '3.3-np1.10-nocover' - TOXPYTHON: C:\python33-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33-x64 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '64' - - TOXENV: '3.3-np1.8-nocover' - TOXPYTHON: C:\python33\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '32' - - TOXENV: '3.3-np1.8-nocover' - TOXPYTHON: C:\python33-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33-x64 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '64' - - TOXENV: '3.3-np1.9-nocover' - TOXPYTHON: C:\python33\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '32' - - TOXENV: '3.3-np1.9-nocover' - TOXPYTHON: C:\python33-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33-x64 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '64' - - TOXENV: '3.4-np1.10-nocover' - TOXPYTHON: C:\python34\python.exe - WINDOWS_SDK_VERSION: v7.0 - PYTHON_HOME: C:\python34 - PYTHON_VERSION: '3.4' - PYTHON_ARCH: '32' - - TOXENV: '3.4-np1.10-nocover' - TOXPYTHON: C:\python34-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python34-x64 - PYTHON_VERSION: '3.4' - PYTHON_ARCH: '64' - - TOXENV: '3.4-np1.8-nocover' - TOXPYTHON: C:\python34\python.exe - WINDOWS_SDK_VERSION: v7.0 - PYTHON_HOME: C:\python34 - PYTHON_VERSION: '3.4' - PYTHON_ARCH: '32' - - TOXENV: '3.4-np1.8-nocover' - TOXPYTHON: C:\python34-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python34-x64 - PYTHON_VERSION: '3.4' - PYTHON_ARCH: '64' - - TOXENV: '3.4-np1.9-nocover' + - TOXENV: '3.4-nocover' TOXPYTHON: C:\python34\python.exe WINDOWS_SDK_VERSION: v7.0 PYTHON_HOME: C:\python34 PYTHON_VERSION: '3.4' PYTHON_ARCH: '32' - - TOXENV: '3.4-np1.9-nocover' + - TOXENV: '3.4-nocover' TOXPYTHON: C:\python34-x64\python.exe WINDOWS_SDK_VERSION: v7.1 PYTHON_HOME: C:\python34-x64 PYTHON_VERSION: '3.4' PYTHON_ARCH: '64' - - TOXENV: '3.5-np1.10-nocover' + - TOXENV: '3.5-nocover' TOXPYTHON: C:\python35\python.exe PYTHON_HOME: C:\python35 PYTHON_VERSION: '3.5' PYTHON_ARCH: '32' - - TOXENV: '3.5-np1.10-nocover' + - TOXENV: '3.5-nocover' TOXPYTHON: C:\python35-x64\python.exe PYTHON_HOME: C:\python35-x64 PYTHON_VERSION: '3.5' PYTHON_ARCH: '64' - - TOXENV: '3.5-np1.8-nocover' - TOXPYTHON: C:\python35\python.exe - PYTHON_HOME: C:\python35 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '32' - - TOXENV: '3.5-np1.8-nocover' - TOXPYTHON: C:\python35-x64\python.exe - PYTHON_HOME: C:\python35-x64 - PYTHON_VERSION: '3.5' - PYTHON_ARCH: '64' - - TOXENV: '3.5-np1.9-nocover' - TOXPYTHON: C:\python35\python.exe - PYTHON_HOME: C:\python35 - PYTHON_VERSION: '3.5' + - TOXENV: '3.6-nocover' + TOXPYTHON: C:\python36\python.exe + WINDOWS_SDK_VERSION: v7.0 + PYTHON_HOME: C:\python36 + PYTHON_VERSION: '3.6' PYTHON_ARCH: '32' - - TOXENV: '3.5-np1.9-nocover' - TOXPYTHON: C:\python35-x64\python.exe - PYTHON_HOME: C:\python35-x64 - PYTHON_VERSION: '3.5' + - TOXENV: '3.6-nocover' + TOXPYTHON: C:\python36-x64\python.exe + WINDOWS_SDK_VERSION: v7.1 + PYTHON_HOME: C:\python36-x64 + PYTHON_VERSION: '3.6' PYTHON_ARCH: '64' init: - ps: echo $env:TOXENV @@ -223,20 +131,12 @@ artifacts: matrix: allow_failures: - TOXENV: 'check' - - TOXENV: '3.3-np1.8-nocover' - - TOXENV: '3.3-np1.8,codecov' - - TOXENV: '3.4-np1.8-nocover' - - TOXENV: '3.4-np1.8,codecov' - - TOXENV: '3.5-np1.8-nocover' - - TOXENV: '3.5-np1.8,codecov' - - TOXENV: '3.5-np1.9-nocover' - - TOXENV: '3.5-np1.9,codecov' - - TOXENV: '3.5-np1.10-nocover' - - TOXENV: '3.5-np1.10,codecov' + - TOXENV: '3.6-nocover' + - TOXENV: '3.6,codecov' - TOXENV: '2.7-buildonly-nocover' - - TOXENV: '3.3-buildonly-nocover' - TOXENV: '3.4-buildonly-nocover' - TOXENV: '3.5-buildonly-nocover' + - TOXENV: '3.6-buildonly-nocover' ### To enable remote debugging uncomment this: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/c_aacgmv2/README.txt b/c_aacgmv2/README.txt new file mode 100644 index 00000000..b7e79b65 --- /dev/null +++ b/c_aacgmv2/README.txt @@ -0,0 +1,193 @@ +AACGM-v2 Software +v2.4 20170601 + +C Instructions: + +1. Download the coefficients and put them in a convenient directory + +2. Set the environment variable AACGM_v2_DAT_PREFIX to the directory that + you are storing the coefficients in AND include the prefix of the + coefficient files, i.e., aacgm_coeffs-12- + + e.g.: + + AACGM_v2_DAT_PREFIX=/mnt/thayerfs/shepherd/AACGM/idl/coeffs/aacgm_coeffs-12- + + Note that if you used the old AACGM software from JHU/APL you should have + a similar variable already set. + + AGB: I altered the routines to allow the file root to be entered as input + +3. Untar the contents of the .tar file into a directory + +4. Setup IGRF by putting the IGRF coefficients (igrf12coeffs.txt) somewhere + or leaving them in the current directory and setting the environment + variable IGRF_COEFFS to the fully qualified path, i.e., + + IGRF_COEFFS=/directory_you_put_IGRF_coefs_in/igrf12coeffs.txt + + AGB: I altered the routines to allow the filename to be entered as input + +5. Build the test program by running: + + gcc -o test_aacgm test_aacgm.c aacgmlib_v2.c igrflib.c genmag.c astalglib.c + mlt_v2.c time.c -lm + +6. Run the test program by running: + + test_aacgm + + AGB: or by running: + + test_aacgm aagcm_coeff_fileroot igrf_filename + + Both commands will yeild the same result + + + The output should look something like: + +================================================================================ + +AACGM-v2 Test Program + +================================================================================ + +TEST: no date/time (this will return an error.) + +************************************************************************** +* AACGM-v2 ERROR: No Date/Time Set * +* * +* You must specifiy the date and time in order to use AACGM coordinates, * +* which depend on the internal (IGRF) magnetic field. Before calling * +* AACGM_v2_Convert() you must set the date and time to the integer values* +* using the function: * +* * +* AACGM_v2_SetDateTime(year,month,day,hour,minute,second); * +* * +* or to the current computer time in UT using the function: * +* * +* AACGM_v2_SetNow(); * +* * +* subsequent calls to AACGM_v2_Convert() will use the last date and time * +* that was set, so update to the actual date and time that is desired. * +************************************************************************** + +TEST: Setting time to : 20140322 0311:00 + +TEST: geographic to AACGM-v2 + GLAT GLON HEIGHT MLAT MLON R + 45.500000 -23.500000 1135.000000 48.189618 57.763454 1.177533 + +TEST: AACGM-v2 to geographic + MLAT MLON HEIGHT GLAT GLON HEIGHT + 48.189618 57.763454 1131.097495 45.440775 -23.472757 1134.977896 + +Do the same thing but use field-line tracing + +TEST: geographic to AACGM-v2 (TRACE) + GLAT GLON HEIGHT MLAT MLON R + 45.500000 -23.500000 1135.000000 48.194757 57.758831 1.177533 + +TEST: AACGM-v2 to geographic (TRACE) + MLAT MLON HEIGHT GLAT GLON HEIGHT + 48.194757 57.758831 1131.097495 45.500000 -23.500000 1135.000000 + +-------------------------------------------------------------------------------- + +Testing MLT +-------------------------------------------------------------------------------- + + GLAT GLON HEIGHT MLAT MLON MLT +TRACE 37.000000 -88.000000 300.000000 48.840368 -17.006090 1.977745 +COEFF 37.000000 -88.000000 300.000000 48.844355 -16.999464 1.978187 + + +Array: + 45.0000 0.0000 150.0000 40.2841 76.6676 8.2227 + 45.0000 1.0000 150.0000 40.2447 77.4899 8.2775 + 45.0000 2.0000 150.0000 40.2108 78.3157 8.3325 + 45.0000 3.0000 150.0000 40.1822 79.1452 8.3878 + 45.0000 4.0000 150.0000 40.1587 79.9785 8.4434 + 45.0000 5.0000 150.0000 40.1400 80.8157 8.4992 + 45.0000 6.0000 150.0000 40.1261 81.6569 8.5553 + 45.0000 7.0000 150.0000 40.1165 82.5020 8.6116 + 45.0000 8.0000 150.0000 40.1111 83.3513 8.6682 + 45.0000 9.0000 150.0000 40.1097 84.2048 8.7251 + 45.0000 10.0000 150.0000 40.1121 85.0624 8.7823 + 45.0000 11.0000 150.0000 40.1179 85.9243 8.8398 + 45.0000 12.0000 150.0000 40.1271 86.7904 8.8975 + 45.0000 13.0000 150.0000 40.1394 87.6608 8.9555 + 45.0000 14.0000 150.0000 40.1546 88.5354 9.0138 + 45.0000 15.0000 150.0000 40.1725 89.4143 9.0724 + 45.0000 16.0000 150.0000 40.1930 90.2976 9.1313 + 45.0000 17.0000 150.0000 40.2157 91.1850 9.1905 + 45.0000 18.0000 150.0000 40.2405 92.0768 9.2499 + 45.0000 19.0000 150.0000 40.2673 92.9728 9.3097 + + +IMPORTANT NOTES: + +1. Magnetic local time (MLT) functions have been restored: + + double MLTConvertYMDHMS_v2(int yr,int mo,int dy,int hr,int mt,int sc, + double mlon); + double MLTConvertYrsec_v2(int yr,int yrsec, double mlon); + double MLTConvertEpoch_v2(double epoch, double mlon); + + + Note that AACGM-v2 longitude is much less sensitive to altitude; maximum + difference of <1 degree (5 min in MLT) over the range 0-2000 km. For this + reason there is no height passed directly into the MLT routines. The value + of AACGM-v2 longitude does change with altitude and variations of MLT with + altitude above a given geographic location do exist. + +2. The function AACGM_v2_Convert is a direct replacement for the function + AACGMConvert that is used in much of the SD software. This is your + starting point, but you can modify the test program as you like. + +3. New user-space functions have been added that allow users to set the + date and time. The functions are: + + AACGM_v2_SetDateTime(int year, int month, int day, int hour, + int minute, int second); + + AACGM_v2_SetNow(); + + The latter will use the current computer date and time in UT. + + Note that setting the time frequently triggers an interpolation in time and + in altitude, which will slow the calculations. Testing should be done to + determine what the correspondence between changes in time and AACGM lat/lon + are. + +4. You must set the date and time at least once or the code will not run. + +5. A new user-space function has been added that allow users to see what + date and time are being used. The function is: + + AACGM_v2_GetDateTime(int *year, int *month, int *day, int *hour, + int *minute, int *second, int *doy); + + +This package include the following files: + +AACGM C software: + +README.txt ; this file +release_notes.txt ; details of changes to v2.4 +aacgmlib_v2.c ; AACGM-v2 functions +aacgmlib_v2.h ; AACGM-v2 header file +genmag.c ; general purpose functions +genmag.h ; general purpose header file +igrflib.c ; internal IGRF functions +igrflib.h ; internal IGRF header file +time.c ; internal date/time functions +time.h ; internal date/time header file +astalg.c ; Astronomical algorithms functions +astalg.h ; Astronomical algorithms header file +mlt_v2.c ; MLT-v2 functions +mlt_v2.h ; MLT-v2 header file +igrf12coeffs.txt ; IGRF12 coefficients +test_aacgm.c ; testing and example program +LICENSE-AstAlg.txt ; license file for Astro algrorithms + diff --git a/c_aacgmv2/include/aacgmlib_v2.h b/c_aacgmv2/include/aacgmlib_v2.h new file mode 100644 index 00000000..6a51f466 --- /dev/null +++ b/c_aacgmv2/include/aacgmlib_v2.h @@ -0,0 +1,71 @@ +#ifndef _AACGMLIB_ + +#define _AACGMLIB_ + +/***************************************************************************** + * defines and structures + *****************************************************************************/ + +#define RE 6371.2 /* Earth Radius */ +#define MAXALT 2000 /* maximum altitude in km */ +#define NCOORD 3 /* xyz */ +#define POLYORD 5 /* quartic polynomial fit in altitude */ +#define NFLAG 2 /* 0: geo to AACGM, 1: AACGM to geo */ +#define SHORDER 10 /* order of Spherical Harmonic expansion */ +#define AACGM_KMAX ((SHORDER+1)*(SHORDER+1)) /* number of SH coefficients */ + +/* options for AACGM-v2 coordinate determination */ +#define G2A 0 /* convert geographic (geodetic) to AACGM-v2 coords */ +#define A2G 1 /* convert AACGM-v2 to geographic (geodetic) coords */ +#define TRACE 2 /* use field-line tracing to compute coordinates */ +#define ALLOWTRACE 4 /* if height is >2000 km use tracing, else use coefs */ +#define BADIDEA 8 /* use coefficients above 2000 km; Terrible idea!! */ +#define GEOCENTRIC 16 /* assume inputs are geocentric with sphere RE */ +#ifndef M_PI + #define M_PI 3.14159265358979323846 /* define M_PI if not already */ +#endif + +/* added for MSC compatibility */ +#ifdef _MSC_VER + #ifndef NAN + #include + #define INFINITY (DBL_MAX+DBL_MAX) + #define NAN (INFINITY-INFINITY) + #endif +#endif + +/***************************************************************************** + * function prototypes + *****************************************************************************/ + +/* private functions */ +int AACGM_v2_Rylm(double colat, double lon, int order, double *ylmval); +void AACGM_v2_Alt2CGM(double r_height_in, double r_lat_alt, double *r_lat_adj); +int AACGM_v2_CGM2Alt(double r_height_in, double r_lat_in, double *r_lat_adj); +double AACGM_v2_Sgn(double a, double b); +int convert_geo_coord_v2(double lat_in, double lon_in, double height_in, + double *lat_out, double *lon_out, int flag, int order, + char *igrf_filename); +int AACGM_v2_LoadCoefFP(FILE *fp, int code); +int AACGM_v2_LoadCoef(char *fname, int code); +int AACGM_v2_LoadCoefs(int year, char *root); +int AACGM_v2_TimeInterp(char *root); +void AACGM_v2_errmsg(int ecode); +int AACGM_v2_Trace(double lat_in, double lon_in, double alt, + double *lat_out, double *lon_out, char *igrf_filename); +int AACGM_v2_Trace_inv(double lat_in, double lon_in, double alt, + double *lat_out, double *lon_out, char *igrf_filename); + + +/* public functions */ +int AACGM_v2_Convert(double in_lat, double in_lon, double height, + double *out_lat, double *out_lon, double *r, int code, + char *igrf_filename); +int AACGM_v2_SetDateTime(int year, int month, int day, + int hour, int minute, int second, char *root); +int AACGM_v2_GetDateTime(int *year, int *month, int *day, + int *hour, int *minute, int *second, int *dayno); +int AACGM_v2_SetNow(char *root); + +#endif + diff --git a/c_aacgmv2/include/astalg.h b/c_aacgmv2/include/astalg.h new file mode 100644 index 00000000..bc7f54af --- /dev/null +++ b/c_aacgmv2/include/astalg.h @@ -0,0 +1,99 @@ +/* astalg.h + ======== + Author: Kile Baker +*/ + +/* + Copyright and License Information + + This source file is part of a library of files implementing + portions of the algorithms given in the book _Astronomical + Algorithms_ by Jean Meeus. + + Software Copyright (C) 2006, U.S. Government + Author: Kile B. Baker + National Science Foundation + 4201 Wilson Blvd, + Arlington, VA 22230 + email: kbaker@nsf.gov + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + + + +*/ + + + +/* ----------------- REFERENCE ------------------------ + +The software contained herein is derived from algorithms published +in the book _Astronomical Algorithms_, Second Edition, by Jean Meeus, +publisher: Willman-Bell, Inc., Richmond, Virginia, 1998 (corrections +dated 2005). + +The book will be referred to as "Meeus" for short. + + +*/ + +#ifndef _AstAlg_H +#define _AstAlg_H + +#define J2000 (2451545.0) +/* The reference time for all the algorithms in Meeus. The reference + date is January 1, 2000 at UT (actually TD) noon (i.e. day 1.5 of 2000). + + The zero time for Julian Day is noon on the beginning of the year in + -4712. +*/ +/* I need the value of PI, so include the math.h definitions */ + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define AstAlg_DTOR (M_PI/180.0) + +/* we need a floating point version of the % binary operator */ + +#define dmod(a,b) ((double) ((((long) a) % ((long) b)) + (a - (long) a))) + +double AstAlg_apparent_obliquity(double jd); +double AstAlg_apparent_solar_longitude(double jd); +double AstAlg_dday(int day, int hour, int minute, int second); +double AstAlg_equation_of_time(double jd); +double AstAlg_geometric_solar_longitude(double jd); +double AstAlg_jde(int year, int month, double day); +void AstAlg_jde2calendar(double jd, + int *year, + int *month, + int *day, + int *hour, + int *minute, + int *second); +double AstAlg_lunar_ascending_node(double jd); +double AstAlg_mean_lunar_longitude(double jd); +double AstAlg_mean_obliquity(double jd); +double AstAlg_mean_solar_anomaly( double jd ); +double AstAlg_mean_solar_longitude( double jd ); +void AstAlg_nutation_corr(double jd, double *slong_corr, double *obliq_corr); +double AstAlg_solar_declination(double jd); +double AstAlg_solar_right_ascension(double jd); + +#endif diff --git a/src/c_aacgm_v2/genmag.h b/c_aacgmv2/include/genmag.h similarity index 100% rename from src/c_aacgm_v2/genmag.h rename to c_aacgmv2/include/genmag.h diff --git a/src/c_aacgm_v2/igrflib.h b/c_aacgmv2/include/igrflib.h similarity index 69% rename from src/c_aacgm_v2/igrflib.h rename to c_aacgmv2/include/igrflib.h index 4308d88b..091a758a 100644 --- a/src/c_aacgm_v2/igrflib.h +++ b/c_aacgmv2/include/igrflib.h @@ -1,16 +1,23 @@ +#ifndef _IGRFLIB_ +#define _IGRFLIB_ #define IGRF_FIRST_EPOCH 1900 #define IGRF_LAST_EPOCH 2015 -#define IGRF_FILE "igrf12coeffs.txt" /* current IGRF model */ +/* SGS using IGRF_COEFFS environment variable */ +/*#define IGRF_FILE "igrf12coeffs.txt"*/ /* current IGRF model */ #ifndef RE #define RE 6371.2 /* Earth Radius */ #endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* define M_PI if not already */ +#endif + #define MAXSTR 200 /* maximum string length */ #define MAXNYR 100 /* maximum number of epochs */ #define IGRF_ORDER 13 /* maximum order of SH expansion */ -#define IGRF_MAXK ((IGRF_ORDER+1)*(IGRF_ORDER+1)) /* # of SH coefficients */ +#define IGRF_MAXK ((IGRF_ORDER+1)*(IGRF_ORDER+1)) /* # of SH coefficients */ #define DTOR (M_PI/180.) #define MIN(a,b) ((a) < (b) ? (a) : (b)) @@ -18,17 +25,20 @@ /* function prototypes */ /* private functions */ -int igrf_loadcoeffs(char *filename); +int IGRF_loadcoeffs(char *filename); +int IGRF_interpolate_coefs(void); void pause(void); void IGRF_msg_notime(void); /* public functions */ int IGRF_compute(const double rtp[], double brtp[]); -int IGRF_SetNow(void); +int IGRF_SetNow(char *filename); int IGRF_GetDateTime(int *year, int *month, int *day, - int *hour, int *minute, int *second, int *dayno); + int *hour, int *minute, int *second, int *dayno); int IGRF_SetDateTime(int year, int month, int day, - int hour, int minute, int second); + int hour, int minute, int second, char *filename); +double IGRF_Tilt(int yr, int mo, int dy, int hr, int mt, int sc, + char *filename); /* some geopack functionality */ int geo2mag(const double xyzg[], double xyzm[]); @@ -45,3 +55,5 @@ int geoc2geod(double lat, double lon, double r, double llh[]); int AACGM_v2_Newval(double xyz[], int idir, double ds, double k[]); int AACGM_v2_RK45(double xyz[], int idir, double *ds, double eps, int code); +#endif + diff --git a/c_aacgmv2/include/mlt_v2.h b/c_aacgmv2/include/mlt_v2.h new file mode 100644 index 00000000..c0c86290 --- /dev/null +++ b/c_aacgmv2/include/mlt_v2.h @@ -0,0 +1,23 @@ + +#ifndef _MLT_v2_H +#define _MLT_v2_H + +double MLTConvert_v2(int yr, int mo, int dy, int hr, int mt ,int sc, + double mlon, char *root, char *igrf_filename); +double inv_MLTConvert_v2(int yr, int mo, int dy, int hr, int mt ,int sc, + double mlt, char *root, char *igrf_filename); +double MLTConvertYMDHMS_v2(int yr,int mo,int dy,int hr,int mt,int sc, + double mlon, char *root, char *igrf_filename); +double inv_MLTConvertYMDHMS_v2(int yr,int mo,int dy,int hr,int mt,int sc, + double mlt, char *root, char *igrf_filename); +double MLTConvertYrsec_v2(int yr,int yrsec, double mlon, char *root, + char *igrf_filename); +double inv_MLTConvertYrsec_v2(int yr, int yrsec, double mlt, char *root, + char *igrf_filename); +double MLTConvertEpoch_v2(double epoch, double mlon, char *root, + char *igrf_filename); +double inv_MLTConvertEpoch_v2(double epoch, double mlt, char *root, + char *igrf_filename); + +#endif + diff --git a/c_aacgmv2/include/rtime.h b/c_aacgmv2/include/rtime.h new file mode 100644 index 00000000..2614dd1a --- /dev/null +++ b/c_aacgmv2/include/rtime.h @@ -0,0 +1,27 @@ +/* rtime.h + +====== + Author: R.J.Barnes +*/ + +/* + (c) 2010 JHU/APL & Others - Please Consult + LICENSE.superdarn-rst.3.2-beta-4-g32f7302.txt for more information. +*/ + +#ifndef _RTIME_H +#define _RTIME_H + +void TimeYrsecToYMDHMS(int time,int yr, + int *mo,int *dy, int *hr,int *mn,int *sc); +int TimeYMDHMSToYrsec(int yr, int mo, int dy, int hr, int mn, int sc); +double TimeYMDHMSToEpoch(int yr,int mo,int dy,int hr,int mn,double sc); +void TimeEpochToYMDHMS(double tme, + int *yr,int *mo,int *dy,int *hr,int *mn, double *sc); +double TimeYMDHMSToJulian(int yr,int mo,int dy,int hr,int mt,double sc); +int TimeJulianToYMDHMS(double jd, + int *yr,int *mo, int *dy,int *hr,int *mt,double *sc); +void TimeReadClock(int *yr, int *mo, int *dy, int *hr, int *mn, int *sc, + int *usc); + +#endif + diff --git a/c_aacgmv2/src/aacgmlib_v2.c b/c_aacgmv2/src/aacgmlib_v2.c new file mode 100644 index 00000000..a797b8ff --- /dev/null +++ b/c_aacgmv2/src/aacgmlib_v2.c @@ -0,0 +1,1529 @@ +/*----------------------------------------------------------------------------- +; AACGM library +; +; a collection of C routines intended to fully exploit the functionality of +; the AACGM coordinates [Shepherd, 2014] including use of the AACGM +; coefficients and field line tracing +; +; 20140611 SGS v0.0 Modification to existing AACGM C and IDL software, but +; includes additional features: linear interpolation and +; fieldline tracing. +; 20140702 SGS v0.1 Made operational, moved globals to library, fixed bug +; in malloc. +; 20140703 SGS v0.2 Added AACGM_GetDateTime() function to provide capability +; of getting date and time used in calculations (Jeff). +; Changed behavior to abort (with message) if not data/time +; is set. +; Variable fact changed to double from unsigned long which +; is not big enought on some 32-bit systems. +; Switched from NAN to HUGE_VAL for undefined result +; 20140918 SGS v1.0 change function names to _v2 for wider distribution +; 20150810 SGS v1.1 added code to default to geodetic coordinates for inverse +; transformation. This code was left out in the C version. +; 20170308 SGS v1.2 Added static to global variables in order to work with RST +; library. +; +; Functions: +; +; AACGM_v2_Rylm +; AACGM_v2_Alt2CGM - not used +; AACGM_v2_CGM2Alt +; AACGM_v2_Sgn +; convert_geo_coord_v2 +; AACGM_v2_LoadCoefFP +; AACGM_v2_LoadCoef +; AACGM_v2_LoadCoefs +; AACGM_v2_Convert +; AACGM_v2_SetDateTime +; AACGM_v2_GetDateTime +; AACGM_v2_SetNow +; AACGM_v2_errmsg +; + +; AACGM_v2_Dayno +; AACGM_v2_Trace +; AACGM_v2_Trace_inv +; +;------------------------------------------------------------------------------ +*/ + + +#include +#include +#include +#include +#include +#include "aacgmlib_v2.h" +#include "igrflib.h" +#include "genmag.h" + +#define DEBUG 0 + +/* put these in the library header file when you figure out how to do so... */ +static struct { + int year; + int month; + int day; + int hour; + int minute; + int second; + int dayno; + int daysinyear; +} aacgm_date = {-1,-1,-1,-1,-1,-1,-1,-1}; + +static int myear = 0; /* model year: 5-year epoch */ +static double fyear = 0.; /* floating point year */ + +static int myear_old = -1; +static double fyear_old = -1.; + +static double height_old[2] = {-1,-1}; + +static struct { + double coef[AACGM_KMAX][NCOORD][POLYORD][NFLAG]; /* interpolated coefs */ + double coefs[AACGM_KMAX][NCOORD][POLYORD][NFLAG][2]; /* bracketing coefs */ +} sph_harm_model; + +/* SGS added for MSC compatibility */ +#ifndef complex +struct complex { + double x; + double y; +}; +#endif + + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_Rylm +; +; PURPOSE: +; Computes an array of real spherical harmonic function values +; Y_lm(phi,theta) for a given colatitiude (phi) and longitude (theta) +; for all the values up to l = order, which is typically 10. The +; values are stored in a 1D array of dimension (order+1)^2. The +; indexing scheme used is: +; +; l 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 ... +; m 0 -1 0 1 -2 -1 0 1 2 -3 -2 -1 0 1 2 3 -4 -3 -2 -1 0 ... +;C & IDL j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... +;FORTRAN j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ... +; +; CALLING SEQUENCE: +; AACGM_v2_Rylm, colat,lon,order, ylmval +; +; Input Arguments: +; colat - The colatitude of the point for which the spherical +; harmonic Y_lm is to be calculated +; +; lon - The longitude of the point for which the spherical +; harmonic Y_lm is to be calculated +; +; order - The order of the spherical harmonic function expansion. +; The total number of terms computed will be (order+1)^2 +; +; Output Argument: +; ylmval - 1D array of spherical harmonic functions at the point +; (colat,lon) +; +; HISTORY: +; +; Revision 1.1 94/10/12 15:24:21 15:24:21 baker (Kile Baker S1G) +; Initial revision +; +; subsequent revisions, porting to C and IDL by baker, wing and barnes. +; +; NOTES by SGS: +; +; It is likely that the original version was taken from FORTRAN and used array +; indexing that begins with 1. Indexing is somewhat more natural using the +; zeros-based indexing of C/IDL. Indices have thus been changed from the +; original version. +; +; It appears that the original version used unnormalized spherical harmonic +; functions. I suspect this might be better, but realized it too late. The +; coefficients I derived are for orthonormal spherical harmonic functions +; which then require the same for evaluation. I believe that the original +; authors used orthogonal spherical harmonic functions which eliminate the +; need for computing the normalization factors. I suspect this is just fine, +; but have not tested it. +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_Rylm(double colat, double lon, int order, double *ylmval) +{ + struct complex z1, z2; + struct complex q_fac, q_val; + int k, l, m, kk; + int ia,ib,ic,id; + double cos_theta, sin_theta; + double cos_lon, sin_lon; + double l2, tl, fac; + double ca, cb, d1; + double *ffff; + /* long unsigned *fact; not big enough for 32-bit machines */ + double *fact; + + cos_theta = cos(colat); + sin_theta = sin(colat); + + cos_lon = cos(lon); + sin_lon = sin(lon); + + d1 = -sin_theta; + z2.x = cos_lon; + z2.y = sin_lon; + + z1.x = d1 * z2.x; + z1.y = d1 * z2.y; + q_fac = z1; + + /* + * Generate Zonal Harmonics (P_l^(m=0) for l = 1,order) using recursion + * relation (6.8.7), p. 252, Numerical Recipes in C, 2nd. ed., Press. W. + * et al. Cambridge University Press, 1992) for case where m = 0. + * + * l Pl = cos(theta) (2l-1) Pl-1 - (l-1) Pl-2 (6.8.7) + * + * where Pl = P_l^(m=0) are the associated Legendre polynomials + * + */ + + ylmval[0] = 1; /* l = 0, m = 0 */ + ylmval[2] = cos_theta; /* l = 1, m = 0 */ + + for (l=2; l<=order; l++) { + /* indices for previous two values: k = l * (l+1) + m with m=0 */ + ia = (l-2)*(l-1); + ib = (l-1)*l; + ic = l * (l+1); + + ylmval[ic] = (cos_theta * (2*l-1) * ylmval[ib] - (l-1)*ylmval[ia])/l; + } + + /* + * Generate P_l^l for l = 1 to (order+1)^2 using algorithm based upon (6.8.8) + * in Press et al., but incorporate longitude dependence, i.e., sin/cos (phi) + * + * Pll = (-1)^l (2l-1)!! (sin^2(theta))^(l/2) + * + * where Plm = P_l^m are the associated Legendre polynomials + * + */ + + q_val = q_fac; + ylmval[3] = q_val.x; /* l = 1, m = +1 */ + ylmval[1] = -q_val.y; /* l = 1, m = -1 */ + for (l=2; l<=order; l++) { + d1 = l*2 - 1.; + z2.x = d1 * q_fac.x; + z2.y = d1 * q_fac.y; + z1.x = z2.x * q_val.x - z2.y * q_val.y; + z1.y = z2.x * q_val.y + z2.y * q_val.x; + q_val = z1; + + /* indices for previous two values: k = l * (l+1) + m */ + ia = l*(l+2); /* m = +l */ + ib = l*l; /* m = -l */ + + ylmval[ia] = q_val.x; + ylmval[ib] = -q_val.y; + } + + /* + * Generate P_l,l-1 to P_(order+1)^2,l-1 using algorithm based upon (6.8.9) + * in Press et al., but incorporate longitude dependence, i.e., sin/cos (phi) + * + * Pl,l-1 = cos(theta) (2l-1) Pl-1,l-1 + * + */ + + for (l=2; l<=order; l++) { + + l2 = l*l; + tl = 2*l; + /* indices for Pl,l-1; Pl-1,l-1; Pl,-(l-1); Pl-1,-(l-1) */ + ia = l2 - 1; + ib = l2 - tl + 1; + ic = l2 + tl - 1; + id = l2 + 1; + + fac = tl - 1; + ylmval[ic] = fac * cos_theta * ylmval[ia]; /* Pl,l-1 */ + ylmval[id] = fac * cos_theta * ylmval[ib]; /* Pl,-(l-1) */ + } + + /* + * Generate remaining P_l+2,m to P_(order+1)^2,m for each m = 1 to order-2 + * using algorithm based upon (6.8.7) in Press et al., but incorporate + * longitude dependence, i.e., sin/cos (phi). + * + * for each m value 1 to order-2 we have P_mm and P_m+1,m so we can compute + * P_m+2,m; P_m+3,m; etc. + * + */ + + for (m=1; m<=order-2; m++) { + for (l=m+2; l<=order; l++) { + ca = ((double) (2*l-1))/(l-m); + cb = ((double) (l+m-1))/(l-m); + + l2 = l*l; + ic = l2 + l + m; + ib = l2 - l + m; + ia = l2 - l - l - l + 2 + m; + /* positive m */ + ylmval[ic] = ca * cos_theta * ylmval[ib] - cb * ylmval[ia]; + + ic -= (m+m); + ib -= (m+m); + ia -= (m+m); + /* negative m */ + ylmval[ic] = ca * cos_theta * ylmval[ib] - cb * ylmval[ia]; + } + } + + /* + * Normalization added here (SGS) + * + * Note that this is NOT the standard spherical harmonic normalization factors + * + * The recursive algorithms above treat positive and negative values of m in + * the same manner. In order to use these algorithms the normalization must + * also be modified to reflect the symmetry. + * + * Output values have been checked against those obtained using the internal + * IDL legendre() function to obtain the various associated legendre + * polynomials. + * + * As stated above, I think that this normalization may be unnecessary. The + * important thing is that the various spherical harmonics are orthogonal, + * rather than orthonormal. + * + */ + + /* determine array of factorials */ + /*fact = (long unsigned *)malloc(sizeof(long unsigned)*(2*order+2));*/ + fact = (double *)malloc(sizeof(double)*(2*order+2)); + if (fact == NULL) return (-1); + fact[0] = fact[1] = 1; + for (k=2; k <= 2*order+1; k++) fact[k] = k*fact[k-1]; + + ffff = (double *)malloc(sizeof(double)*(order+1)*(order+1)); + if (ffff == NULL) return (-1); + + /* determine normalization factors */ + for (l=0; l<=order; l++) { + for (m=0; m<=l; m++) { + k = l * (l+1) + m; /* 1D index for l,m */ + ffff[k] = sqrt((2*l+1)/(4*M_PI) * fact[l-m]/fact[l+m]); + ylmval[k] *= ffff[k]; + } + for (m=-l; m<0; m++) { + k = l * (l+1) + m; /* 1D index for l,m */ + kk = l * (l+1) - m; + ylmval[k] *= ffff[kk] * ((-m % 2) ? -1 : 1); + } + } + + #if DEBUG > 1 + for (k=0; k<2*order+1; k++) + /*printf("%03d %lu\n", k, fact[k]);*/ + printf("%03d %lf\n", k, fact[k]); + #endif + + free(fact); + free(ffff); + + #if DEBUG > 10 + for (l=0; l<=order; l++) { + for (m=0; m<=l; m++) { + k = l * (l+1) + m; /* 1D index for l,m */ + printf("%03d %lf\n", k, ylmval[k]); + } + } + #endif + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_Alt2CGM +; +; PURPOSE: +; Transformation from so-called 'at-altitude' coordinates to AACGM. +; The purpose of this function is to scale the latitudes in such a +; way so that there is no gap. The problem is that for non-zero +; altitudes (h) are range of latitudes near the equator lie on dipole +; field lines that near reach the altitude h, and are therefore not +; accessible. This is the inverse transformation. +; +; cos (lat_aacgm) = sqrt( Re/(Re + h) ) cos (lat_at-alt) +; +; +; CALLING SEQUENCE: +; AACGM_v2_Alt2CGM,r_height_in,r_lat_alt,r_lat_adj +; +; Input Arguments: +; r_height_in - The altitude (h) +; r_lat_alt - The 'at-altitude' latitude +; +; Output Arguments: +; r_lat_adj - The corrected latitude, i.e., AACGM latitude +; +; HISTORY: +; +; This function is unchanged from its original version (Baker ?) +; +;+----------------------------------------------------------------------------- +*/ + +void AACGM_v2_Alt2CGM(double r_height_in, double r_lat_alt, double *r_lat_adj) +{ + double eps =1e-9; + double unim =0.9999999; + + double r1; + double r0, ra; + + #if DEBUG > 0 + printf("AACGM_v2_Alt2CGM\n"); + #endif + + /* Computing 2nd power */ + r1 = cos(r_lat_alt*DTOR); + ra = r1 * r1; + if (ra < eps) ra = eps; + + r0 = (r_height_in/RE + 1.) / ra; + if (r0 < unim) r0 = unim; + + r1 = acos(sqrt(1/r0)); + *r_lat_adj = AACGM_v2_Sgn(r1, r_lat_alt)/DTOR; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_CGM2Alt +; +; PURPOSE: +; Transformation from AACGM to so-called 'at-altitude' coordinates. +; The purpose of this function is to scale the latitudes in such a +; way so that there is no gap. The problem is that for non-zero +; altitudes (h) are range of latitudes near the equator lie on dipole +; field lines that near reach the altitude h, and are therefore not +; accessible. This mapping closes the gap. +; +; cos (lat_at-alt) = sqrt( (Re + h)/Re ) cos (lat_aacgm) +; +; +; CALLING SEQUENCE: +; AACGM_v2_CGM2Alt,r_height_in,r_lat_in,r_lat_adj, error +; +; Input Arguments: +; r_height_in - The altitude (h) +; r_lat_in - The AACGM latitude +; +; Output Arguments: +; r_lat_adj - The 'at-altitude' latitude +; error - variable is set if latitude is below the value that +; is mapped to the origin +; +; HISTORY: +; +; This function is unchanged from its original version (Baker ?) +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_CGM2Alt(double r_height_in, double r_lat_in, double *r_lat_adj) +{ + double unim=1; + double r1; + double ra; + int error=0; + + #if DEBUG > 0 + printf("AACGM_v2_CGM2Alt\n"); + #endif + + /* convert from AACGM to at-altitude coordinates */ + r1 = cos(r_lat_in*DTOR); + ra = (r_height_in/RE + 1)*(r1*r1); + if (ra > unim) { + ra = unim; + error = 1; + } + + r1 = acos(sqrt(ra)); + *r_lat_adj = AACGM_v2_Sgn(r1,r_lat_in)/DTOR; + + return (error); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_Sgn +; +; PURPOSE: +; return the signed quantity of a variable where the magnitude is given +; by the first argument and the sign is given by the second argument. +; +; CALLING SEQUENCE: +; AACGM_v2_Sgn, a, b +; +; Input Arguments: +; a - magnitude +; b - sign +; +; Return Value: +; signed quantity +; +;+----------------------------------------------------------------------------- +*/ + +double AACGM_v2_Sgn(double a, double b) +{ + double x=0; + x = (a >= 0) ? a : -a; + return (b >= 0) ? x: -x; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; convert_geo_coord_v2 +; +; PURPOSE: +; Second-level function used to determine the lat/lon of the input +; coordinates. +; +; CALLING SEQUENCE: +; err = convert_geo_coord_v2(in_lat,in_lon,height, out_lat,out_lon, +; code,order, igrf_filename); +; +; Input Arguments: +; in_lat - latitude in degrees +; in_lon - longitude in degrees +; height - height above Earth in km +; code - bitwise code for passing options into converter +; G2A - geographic (geodetic) to AACGM-v2 +; A2G - AACGM-v2 to geographic (geodetic) +; TRACE - use field-line tracing, not coefficients +; ALLOWTRACE - use trace only above 2000 km +; BADIDEA - use coefficients above 2000 km +; order - integer order of spherical harmonic expansion +; +; Output Arguments: +; out_lat - pointer to output latitude in degrees +; out_lon - pointer to output longitude in degrees +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int convert_geo_coord_v2(double lat_in, double lon_in, double height_in, + double *lat_out, double *lon_out, int code, int order, + char *igrf_filename) { + +/* int i,j,k,l,m,f,a,t,flag;*/ + int i,j,k,l,m,flag; + int i_err64, err; + +/* extern int rylm(); */ + double ylmval[AACGM_KMAX]; + double colat_temp; + double lon_output; + + double lat_adj=0; +/* double lat_alt=0; */ + double colat_input; + + double alt_var_cu=0, lon_temp=0, alt_var_sq=0, alt_var_qu=0; + double colat_output=0, r=0, x=0, y=0, z=0; + double ztmp, fac; + double alt_var=0; + double lon_input=0; + + static double cint[AACGM_KMAX][NCOORD][NFLAG]; + + #if DEBUG > 0 + printf("convert_geo_coord_v2\n"); + #endif + + /* no date/time set so use current time */ + if (aacgm_date.year < 0) { /* AACGM_v2_SetNow();*/ + AACGM_v2_errmsg(0); + return -128; + } + + /* TRACE */ /* call tracing functions here and return */ + if ((code & TRACE) || (height_in > MAXALT && (code & ALLOWTRACE))) { + if (A2G & code) { /* AACGM-v2 to geographic */ + err = AACGM_v2_Trace_inv(lat_in,lon_in,height_in, lat_out,lon_out, + igrf_filename); + + /* v2.3 moved to AACGM_v2_Convert + if ((code & GEOCENTRIC) == 0) { + geoc2geod(*lat_out,*lon_out,(RE+height_in)/RE, llh); + *lat_out = llh[0]; + } */ + } else { + err = AACGM_v2_Trace(lat_in,lon_in,height_in, lat_out,lon_out, + igrf_filename); + } + + return (err); + } + + /* determine the altitude dependence of the coefficients */ + flag = (A2G & code); /* 0 for G2A; 1 for A2G */ + if (height_in != height_old[flag]) { + alt_var = height_in/(double)MAXALT; + alt_var_sq = alt_var * alt_var; + alt_var_cu = alt_var * alt_var_sq; + alt_var_qu = alt_var * alt_var_cu; + + #if DEBUG > 1 + printf("alt_var = %lf\n", alt_var); + printf("alt_var_qu = %lf\n", alt_var_qu); + #endif + + #if DEBUG > 0 + printf("** HEIGHT INTERPOLATION **\n"); + #endif + + for (i=0; i 10 + printf("%35.30lf %35.30lf\n", cint[j][i][flag], + sph_harm_model.coef[j][i][0][flag]); + #endif + + } + } + + height_old[flag] = height_in; + } + #if DEBUG > 1 + printf("cint[0][0][%d] = %lf\n", flag, cint[0][0][flag]); + printf("cint[%d][0][%d] = %lf\n", + AACGM_KMAX-1, flag, cint[AACGM_KMAX-1][0][flag]); + printf("cint[%d][%d][%d] = %lf\n", AACGM_KMAX-1, NCOORD-1, flag, + cint[AACGM_KMAX-1][NCOORD-1][flag]); + #endif + + x = y = z = 0; + + lon_input = lon_in*DTOR; + + if (flag == 0) { + colat_input = (90.-lat_in)*DTOR; + } else { + /* use intermediate "at-altitude" coordinates for inverse trans. */ + i_err64 = AACGM_v2_CGM2Alt(height_in, lat_in, &lat_adj); + + if (i_err64 != 0) return -64; + colat_input= (90. - lat_adj)*DTOR; + } + + /* Compute the values of the spherical harmonic functions. + * NOTE: this function was adapted to use orthonormal SH functions */ + AACGM_v2_Rylm(colat_input, lon_input, order, ylmval); + + for (l=0; l<=order; l++) { + for (m=-l; m<=l; m++) { + k = l * (l+1) + m; /* SGS: changes indexing */ + + x += cint[k][0][flag]*ylmval[k]; + y += cint[k][1][flag]*ylmval[k]; + z += cint[k][2][flag]*ylmval[k]; + } + } + + /* COMMENT: SGS + * + * This answers one of my questions about how the coordinates for AACGM are + * guaranteed to be on the unit sphere. Here they compute xyz indpendently + * using the SH coefficients for each coordinate. They reject anything that + * is +/- .1 Re from the surface of the Earth. They then scale each xyz + * coordinate by the computed radial distance. This is a TERRIBLE way to do + * things... but necessary for the inverse transformation. + */ + + /* SGS - new method that ensures position is on unit sphere and results in a + * much better fit. Uses z coordinate only for sign, i.e., hemisphere. + */ + if (flag == 0) { + fac = x*x + y*y; + if (fac > 1.) { + /* we are in the forbidden region and the solution is undefined */ + *lat_out = HUGE_VAL; + *lon_out = HUGE_VAL; + + return -64; + } + + ztmp = sqrt(1. - fac); + z = (z < 0) ? -ztmp : ztmp; + + colat_temp = acos(z); + } else { + /* SGS - for inverse the old normalization produces lower overall errors...*/ + r = sqrt(x*x + y*y + z*z); + if ((r< 0.9) || (r > 1.1)) return -32; + + z /= r; + x /= r; + y /= r; + + if (z > 1.) colat_temp = 0; + else if (z < -1.) colat_temp = M_PI; + else colat_temp = acos(z); + } + + if ((fabs(x) < 1e-8) && (fabs(y) < 1e-8)) lon_temp = 0; + else lon_temp = atan2(y,x); + + lon_output = lon_temp; + + /* SGS - not used for forward transformation */ + /* + if (flag == 0) { + lat_alt =90 - colat_temp*180/PI; + altitude_to_cgm(height_in, lat_alt,&lat_adj); + colat_output = (90. - lat_adj) * PI/180; + } else colat_output = colat_temp; + */ + colat_output = colat_temp; + + *lat_out = (double) (90. - colat_output/DTOR); + *lon_out = (double) (lon_output/DTOR); + + /* v2.3 moved to AACGM_v2_Convert + if ((code & GEOCENTRIC) == 0 && (code & A2G)) { + geoc2geod(*lat_out,*lon_out,(RE+height_in)/RE, llh); + *lat_out = llh[0]; + } */ + + return 0; +} + + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_LoadCoefFP +; +; PURPOSE: +; Load a set of spherical harmonic coefficients. +; +; CALLING SEQUENCE: +; err = AACGM_v2_LoadCoefFP(fp, code); +; +; Input Arguments: +; fp - FILE pointer to open coefficient file +; code - 0 for 1st set; 1 for 2nd set +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_LoadCoefFP(FILE *fp, int code) +{ + /* char tmp[64]; */ + double tmp; + int f,l,a,t; + + #if DEBUG > 0 + printf("AACGM_v2_LoadCoefFP\n"); + #endif + + if (fp==NULL) { + #if DEBUG > 0 + printf("NULL file pointer in AACGM_v2_LoadCoefFP\n"); + #endif + return -1; + } + + for (f=0;f 0 + printf("FILE error, aborting\n"); + #endif + fclose(fp); + return -1; + } + + sph_harm_model.coefs[t][a][l][f][code] = tmp; + } + } + } + } + + #if DEBUG > 10 + for (f=0;f 0 + printf("loading %s\n", fname); + #endif + + fp = fopen(fname,"r"); + if (fp==NULL) { + #if DEBUG > 0 + printf("NULL file pointer in AACGM_v2_LoadCoef\n"); + #endif + return -1; + } + + err = AACGM_v2_LoadCoefFP(fp, code); + if (err != 0) { + #if DEBUG > 0 + printf("error in AACGM_v2_LoadCoefFP\n"); + #endif + return -2; + } + fclose(fp); + + return 0; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_LoadCoefs +; +; PURPOSE: +; Load two sets of spherical harmonic coefficients. +; +; CALLING SEQUENCE: +; err = AACGM_v2_LoadCoefs(myear, root); +; +; Input Arguments: +; myear - 5-year epoch year prior to desired time; bracketing +; set is +5 years. +; root - directory and file root for AACGM coefficient files +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_LoadCoefs(int year, char *root) +{ + char fname[256]; + char yrstr[5]; + int ret=0; + + #if DEBUG > 0 + printf("AACGM_v2_LoadCoefs\n"); + #endif + /* default location of coefficient files */ + if(strlen(root) == 0 && getenv("AACGM_v2_DAT_PREFIX") != (char *)(NULL)) + strcpy(root,getenv("AACGM_v2_DAT_PREFIX")); + + if (strlen(root)==0) { + AACGM_v2_errmsg(2); + return -1; + } + + if (year <= 0) return -1; + sprintf(yrstr,"%4.4d",year); + + strcpy(fname,root); + strcat(fname,yrstr); + strcat(fname,".asc"); + #if DEBUG > 0 + printf("AACGM_v2_LoadCoefs: %s\n", fname); + #endif + ret = AACGM_v2_LoadCoef(fname,G2A); /* forward coefficients */ + if (ret != 0) return ret; + + sprintf(yrstr,"%4.4d",year+5); + strcpy(fname,root); + strcat(fname,yrstr); + strcat(fname,".asc"); + #if DEBUG > 0 + printf("AACGM_v2_LoadCoefs: %s\n", fname); + #endif + ret += AACGM_v2_LoadCoef(fname,A2G); /* inverse coefficients */ + + myear_old = year; + + return ret; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_Convert +; +; PURPOSE: +; Main function called by many SD plotting routines that are written +; in C. +; +; CALLING SEQUENCE: +; err = AACGM_v2_Convert(in_lat, in_lon, height, +; out_lat, out_lon, r, code, igrf_filename); +; +; Input Arguments: +; int_lat - double precision input latitude in degrees +; int_lon - double precision input longitude in degrees +; height - altitude in km +; code - bitwise code for passing options into converter +; G2A - geographic (geodetic) to AACGM-v2 +; A2G - AACGM-v2 to geographic (geodetic) +; TRACE - use field-line tracing, not coefficients +; ALLOWTRACE - use trace only above 2000 km +; BADIDEA - use coefficients above 2000 km +; GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 +; +; Output Arguments: +; out_lat - output latitude in degrees +; out_lon - output longitude in degrees +; r - geocentric radial distance in Re +; +; Return Value: +; error code +; +; +; NOTES: +; +; All AACGM-v2 conversions are done in geocentric coordinates using a +; value of 6371.2 km for the Earth radius. +; +; For G2A conversion inputs are geographic latitude, longitude and +; height (glat,glon,height), specified as either geocentric or +; geodetic (default). For geodetic inputs a conversion to geocentric +; coordinates is performed, which changes the values of +; glat,glon,height. The output is AACGM-v2 latitude, longitude and +; the geocentric radius (mlat,mlon,r) using the geocentric height +; in units of RE. +; +; For A2G conversion inputs are AACGM-v2 latitude, longitude and the +; geocentric height (mlat,mlon,height). The latter can be obtained +; from the r output of the G2A conversion. The output is geographic +; latitude, longitude and height (glat,glon,height). If the +; gedodetic option is desired (default) a conversion of the outputs +; is performed, which changes the values of glat,glon,height. +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_Convert(double in_lat, double in_lon, double height, + double *out_lat, double *out_lon, double *r, int code, + char *igrf_filename) +{ + int err; + int order=10; /* pass in so a lower order would be allowed? */ + double rtp[3]; + double llh[3]; + + #if DEBUG > 0 + printf("AACGM_v2_Convert\n"); + #endif + + /* height < 0 km */ + if (height < 0) { + fprintf(stderr, "WARNING: coordinate transformations are not intended " + "for altitudes < 0 km: %lf\n", height); + /* return -2; */ + } + + /* height > 2000 km not allowed for coefficients */ + if (height > MAXALT && !(code & (TRACE|ALLOWTRACE|BADIDEA))) { + fprintf(stderr, "ERROR: coefficients are not valid for altitudes " + "above %d km: %lf.\n", MAXALT, height); + fprintf(stderr, " You must either use field-line tracing " + "(TRACE or ALLOWTRACE) or\n" + " indicate that you know this is a very bad idea " + "(BADIDEA)\n\n"); + return -4; + } + + /* latitude out of bounds */ + if (fabs(in_lat) > 90.) { + fprintf(stderr, "ERROR: latitude must be in the range -90 to +90 degrees: " + "%lf\n", in_lat); + return -8; + } + + /* longitude out of bounds */ +/* SGS v2.3 removing requirement that longitude be -180 to 180. Does not seems + * to matter and is inconsistent with IDL version: -180 to 180. + + if ((in_lon < -180) || (in_lon > 180)) { + fprintf(stderr, "ERROR: longitude must be in the range -180 to 180 " + "degrees: %lf\n", in_lon); + return -16; + } + */ + + /* if forward calculation (G2A) and input coordinates are given in geodetic + coordinates (default) then must first convert to geocentric coordinates */ + if ((code & GEOCENTRIC) == 0 && (code & A2G) == 0) { + geod2geoc(in_lat,in_lon,height, rtp); + + /* modify lat/lon/alt to geocentric values */ + in_lat = 90. - rtp[1]/DTOR; + in_lon = rtp[2]/DTOR; + height = (rtp[0]-1.)*RE; + } + + /* all inputs are geocentric */ + err = convert_geo_coord_v2(in_lat,in_lon,height, out_lat,out_lon, code,order, + igrf_filename); + /* all outputs are geocentric */ + + if ((code & A2G) == 0) { /* forward: G2A */ + *r = (height + RE)/RE; /* geocentric radial distance in RE */ + } else { /* inverse: A2G */ + if ((code & GEOCENTRIC) == 0) { /* geodetic outputs */ + geoc2geod(*out_lat,*out_lon,(RE+height)/RE, llh); + *out_lat = llh[0]; + height = llh[2]; + } + *r = height; /* height in km */ + } + + if (err !=0) return -1; + return 0; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_SetDateTime +; +; PURPOSE: +; Function to set date and time. MUST be called at least once BEFORE +; any calls to AACGM_v2_Convert. +; +; CALLING SEQUENCE: +; err = AACGM_v2_SetDateTime(year, month, day, hour, minute, second, root) +; +; Input Arguments: +; year - year [1900-2020) +; month - month of year [01-12] +; day - day of month [01-31] +; hour - hour of day [00-24] +; minute - minute of hour [00-60] +; second - second of minute [00-60] +; root - directory and file root of AACGM coefficient file +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_SetDateTime(int year, int month, int day, + int hour, int minute, int second, char *root) +{ + int err, doy, ndays; + double fyear; + + doy = dayno(year,month,day,&ndays); + fyear = year + ((doy-1) + (hour + (minute + second/60.)/60.)/24.) / ndays; + + if ((fyear < IGRF_FIRST_EPOCH) || (fyear >= IGRF_LAST_EPOCH + 5.)) { + AACGM_v2_errmsg(1); + return (-1); + } + + aacgm_date.year = year; + aacgm_date.month = month; + aacgm_date.day = day; + aacgm_date.hour = hour; + aacgm_date.minute = minute; + aacgm_date.second = second; + aacgm_date.dayno = doy; + aacgm_date.daysinyear = ndays; + + #if DEBUG > 0 + printf("AACGM_v2_SetDateTime\n"); + printf("%03d: %04d%02d%02d %02d%02d:%02d\n", + aacgm_date.dayno, aacgm_date.year, aacgm_date.month, aacgm_date.day, + aacgm_date.hour, aacgm_date.minute, aacgm_date.second); + #endif + + err = AACGM_v2_TimeInterp(root); + + return err; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_GetDateTime +; +; PURPOSE: +; Function to get date and time. +; +; CALLING SEQUENCE: +; err = AACGM_v2_GetDateTime(year, month, day, hour, minute, second, dayno); +; +; Output Arguments (integer pointers): +; year - year [1965-2014] +; month - month of year [01-12] +; day - day of month [01-31] +; hour - hour of day [00-24] +; minute - minute of hour [00-60] +; second - second of minute [00-60] +; dayno - day of year [01-366] +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_GetDateTime(int *year, int *month, int *day, + int *hour, int *minute, int *second, int *dayno) +{ + *year = aacgm_date.year; + *month = aacgm_date.month; + *day = aacgm_date.day; + *hour = aacgm_date.hour; + *minute = aacgm_date.minute; + *second = aacgm_date.second; + *dayno = aacgm_date.dayno; + + return 0; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_SetNow +; +; PURPOSE: +; Function to set date and time to current computer time in UT. +; +; CALLING SEQUENCE: +; err = AACGM_v2_SetNow(root); +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_SetNow(char *root) +{ + /* current time */ + int err, doy,ndays; + double fyear; + time_t now; + struct tm *tm_now; + + time(&now); + tm_now = gmtime(&now); /* right now in UT */ + + doy = dayno(1900 + tm_now->tm_year,tm_now->tm_mon,tm_now->tm_mday,&ndays); + fyear = 1900 + tm_now->tm_year + ((doy-1) + (tm_now->tm_hour + + (tm_now->tm_min + tm_now->tm_sec/60.)/60.)/24.) / ndays; + + if ((fyear < IGRF_FIRST_EPOCH) || (fyear >= IGRF_LAST_EPOCH + 5.)) { + fprintf(stderr, "\nDate range for AACGM-v2 is [%4d - %4d)\n\n", + IGRF_FIRST_EPOCH, IGRF_LAST_EPOCH + 5); + fprintf(stderr, "%04d%02d%02d %02d%02d:%02d\n", tm_now->tm_year, + tm_now->tm_mon, tm_now->tm_mday, tm_now->tm_hour, + tm_now->tm_min, tm_now->tm_sec); + return (-1); + } + + aacgm_date.year = (*tm_now).tm_year + 1900; + aacgm_date.month = (*tm_now).tm_mon + 1; + aacgm_date.day = (*tm_now).tm_mday; + aacgm_date.hour = (*tm_now).tm_hour; + aacgm_date.minute = (*tm_now).tm_min; + aacgm_date.second = (*tm_now).tm_sec; + aacgm_date.dayno = (*tm_now).tm_yday + 1; + aacgm_date.daysinyear = ndays; + + #if DEBUG > 0 + printf("AACGM_v2_SetNow\n"); + printf("%03d: %04d%02d%02d %02d%02d:%02d\n", + aacgm_date.dayno, aacgm_date.year, aacgm_date.month, aacgm_date.day, + aacgm_date.hour, aacgm_date.minute, aacgm_date.second); + #endif + + err = AACGM_v2_TimeInterp(root); + + return err; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_errmsg +; +; PURPOSE: +; Display error message because no date and time have been set. +; +; CALLING SEQUENCE: +; AACGM_v2_errmsg(code); +; +;+----------------------------------------------------------------------------- +*/ + +void AACGM_v2_errmsg(int ecode) +{ + + fprintf(stderr, "\n" + "**************************************************************************" + "\n"); + + switch (ecode) { + case 0: /* no Date/Time set */ + fprintf(stderr, + "* AACGM-v2 ERROR: No Date/Time Set *\n" + "* *\n" + "* You must specifiy the date and time in order to use AACGM coordinates, *\n" + "* which depend on the internal (IGRF) magnetic field. Before calling *\n" + "* AACGM_v2_Convert() you must set the date and time to the integer values*\n" + "* using the function: *\n" + "* *\n" + "* AACGM_v2_SetDateTime(year,month,day,hour,minute,second); *\n" + "* *\n" + "* or to the current computer time in UT using the function: *\n" + "* *\n" + "* AACGM_v2_SetNow(root); *\n" + "* *\n" + "* subsequent calls to AACGM_v2_Convert() will use the last date and time *\n" + "* that was set, so update to the actual date and time that is desired. *" + "\n"); + break; + + case 1: /* Date/Time out of bounds */ + fprintf(stderr, + "* AACGM-v2 ERROR: Date out of bounds *\n" + "* *\n" + "* The current date range for AACGM-v2 coordinates is [1990-2020), which *\n" + "* corresponds to the date range for the IGRF12 model, including the *\n" + "* 5-year secular variation. *" + "\n"); + break; + + case 2: /* COEF Path not set */ + fprintf(stderr, + "* AACGM-v2 ERROR: AACGM_v2_DAT_PREFIX path not set *\n" + "* *\n" + "* You must set the environment variable AACGM_v2_DAT_PREFIX to the *\n" + "\n"); + break; + } + fprintf(stderr, + "**************************************************************************" + "\n\n"); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_TimeInterp +; +; PURPOSE: +; Interpolate coefficients between adjacent 5-year epochs +; +; CALLING SEQUENCE: +; err = AACGM_v2_TimeInterp(root); +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_TimeInterp(char *root) +{ + int myear,f,l,a,t,err; + double fyear; + + /* myear is the epoch model year */ + myear = aacgm_date.year/5*5; + if (myear != myear_old) { /* load the new coefficients, if needed */ + err = AACGM_v2_LoadCoefs(myear, root); + if (err != 0) return err; + fyear_old = -1; /* force time interpolation */ + height_old[0] = -1.; /* force height interpolation */ + height_old[1] = -1.; + } + + /* fyear is the floating point time */ + fyear = aacgm_date.year + ((aacgm_date.dayno-1) + (aacgm_date.hour + + (aacgm_date.minute + aacgm_date.second/60.)/60.)/24.)/ + aacgm_date.daysinyear; + + /* time interpolation right here */ + if (fyear != fyear_old) { + #if DEBUG > 0 + printf("** TIME INTERPOLATION **\n"); + #endif + + for (f=0;f 0.) ? -1 : 1; /* N or S hemisphere */ + + dsRE = dsRE0; + + /* + ; trace to magnetic equator + ; + ; Note that there is the possibility that the magnetic equator lies + ; at an altitude above the surface of the Earth but below the starting + ; altitude. I am not certain of the definition of CGM, but these + ; fieldlines map to very different locations than the solutions that + ; lie above the starting altitude. I am considering the solution for + ; this set of fieldlines as undefined; just like those that lie below + ; the surface of the Earth. + */ + while (idir*xyzm[2] < 0.) { + + for (kk=0;kk<3;kk++) xyzp[kk] = xyzg[kk]; /* save as previous */ + + AACGM_v2_RK45(xyzg, idir, &dsRE, eps, 1); /* set to 0 for RK4: /noadapt) */ + + /* convert to magnetic Dipole coordinates */ + geo2mag(xyzg, xyzm); + + k++; + } + niter = k; + + if (niter > 1) { + /* now bisect stepsize (fixed) to land on magnetic equator w/in 1 m */ + for (k=0;k<3;k++) xyzc[k] = xyzp[k]; + kk = 0L; + while (dsRE > 1e-3/RE) { + dsRE *= .5; + for (k=0;k<3;k++) xyzp[k] = xyzc[k]; + AACGM_v2_RK45(xyzc, idir, &dsRE, eps, 0); /* using RK4 */ + geo2mag(xyzc,xyzm); + + /* Is it possible that resetting here causes a doubling of the tol? */ + if (idir * xyzm[2] > 0) + for (k=0;k<3;k++) xyzc[k] = xyzp[k]; + + kk++; + } + niter += kk; + } + + /* 'trace' back to reference surface along Dipole field lines */ + Lshell = sqrt(xyzc[0]*xyzc[0] + xyzc[1]*xyzc[1] + xyzc[2]*xyzc[2]); + if (Lshell < (RE+alt)/RE) { /* magnetic equator is below ... */ + *lat_out = NAN; + *lon_out = NAN; + + err = -1; + } else { + geo2mag(xyzc, xyzm); /* geographic to magnetic */ + car2sph(xyzm, rtp); + + *lat_out = -idir*acos(sqrt(1./Lshell))/DTOR; + *lon_out = rtp[2]/DTOR; + if (*lon_out > 180) *lon_out -= 360.; + + err = 0; + } + + return (err); +} + +int AACGM_v2_Trace_inv(double lat_in, double lon_in, double alt, + double *lat_out, double *lon_out, char *igrf_filename) +{ + int err, kk, idir; + unsigned long k,niter; + double ds, dsRE, dsRE0, eps, Lshell; + double rtp[3],xyzg[3],xyzm[3],xyzc[3],xyzp[3]; + + /* set date for IGRF model */ + IGRF_SetDateTime(aacgm_date.year, aacgm_date.month, aacgm_date.day, + aacgm_date.hour, aacgm_date.minute, aacgm_date.second, + igrf_filename); + + /* Q: these could eventually be command-line options */ + ds = 1.; + dsRE = ds/RE; + dsRE0 = dsRE; + eps = 1.e-4/RE; + + /* Q: Test this */ + /* poles map to infinity */ + if (fabs(fabs(lat_in) - 90.) < 1e-6) + lat_in += (lat_in > 0) ? -1e-6 : 1e-6; + + Lshell = 1./(cos(lat_in*DTOR)*cos(lat_in*DTOR)); + if (Lshell <(RE+alt)/RE) { /* solution does not exist; the starting + * position at the magnetic equator is below + * the altitude of interest */ + *lat_out = NAN; + *lon_out = NAN; + err = -1; + } else { + /* magnetic Cartesian coordinates of fieldline trace starting point */ + xyzm[0] = Lshell*cos(lon_in*DTOR); + xyzm[1] = Lshell*sin(lon_in*DTOR); + xyzm[2] = 0.; + + /* geographic Cartesian coordinates of starting point */ + mag2geo(xyzm, xyzg); + + /* geographic spherical coordinates of starting point */ + car2sph(xyzg,rtp); + + k = 0L; + + /* direction of trace is determined by the starting hemisphere? */ + idir = (lat_in > 0) ? 1 : -1; + + dsRE = dsRE0; + + /* trace back to altitude above Earth */ + while (rtp[0] > (RE + alt)/RE) { + for (kk=0;kk<3;kk++) xyzp[kk] = xyzg[kk]; /* save as previous */ + + AACGM_v2_RK45(xyzg, idir, &dsRE, eps, 1); /* set to 0 for RK4: /noadapt)*/ + + car2sph(xyzg, rtp); + + k++; + } + niter = k; + + if (niter > 1) { + /* now bisect stepsize (fixed) to land on magnetic equator w/in 1 m */ + for (k=0;k<3;k++) xyzc[k] = xyzp[k]; + kk = 0L; + while (dsRE > 1e-3/RE) { + dsRE *= .5; + for (k=0;k<3;k++) xyzp[k] = xyzc[k]; + AACGM_v2_RK45(xyzc, idir, &dsRE, eps, 0); /* using RK4 */ + + car2sph(xyzc, rtp); + + if (rtp[0] < (RE + alt)/RE) + for (k=0;k<3;k++) xyzc[k] = xyzp[k]; + + kk++; + } + niter += kk; + } + + *lat_out = 90. - rtp[1]/DTOR; + *lon_out = rtp[2]/DTOR; + if (*lon_out > 180) *lon_out -= 360.; + err = 0; + } + + return (err); +} + diff --git a/c_aacgmv2/src/astalglib.c b/c_aacgmv2/src/astalglib.c new file mode 100644 index 00000000..fab747ff --- /dev/null +++ b/c_aacgmv2/src/astalglib.c @@ -0,0 +1,728 @@ +/* astalglib.c + * + * collection of Astronomical Algorithms see LICENSE.txt + * + * minor reformatting by SGS for including in AACGM-v2 software + * + */ + +/* ----------------- REFERENCE ------------------------ + +The software contained herein is derived from algorithms published +in the book _Astronomical Algorithms_, Second Edition, by Jean Meeus, +publisher: Willman-Bell, Inc., Richmond, Virginia, 1998 (corrections +dated 2005). + +The book will be referred to as "Meeus" for short. + +*/ + +#include +#include +#include "astalg.h" + +/* AstAlg_apparent_obliquity + ========================= + Author: Kile Baker +*/ + +/* This function calculates the apparent obliquity (angle between + the earth's spin axis and the ecliptic). It uses the + mean obliquity and the effect of the lunar position. + + Calling sequence: + eps = AstAlg_apparent_obliquity(double julian_day) + + The value is returned in degrees. +*/ + +double AstAlg_apparent_obliquity(double jd) { + + static double last_jd, last_eps; + + /* if we've already calculated the value just return it */ + + if (jd == last_jd) return (last_eps); + + last_jd = jd; + last_eps = AstAlg_mean_obliquity(jd) + + 0.00256 * cos(AstAlg_DTOR * AstAlg_lunar_ascending_node(jd)); + + return (last_eps); +} + +/* AstAlg_apparent_solar_longitude + =============================== + Author: Kile Baker +*/ + +/* This function calculates the apparent solar longitude for a given + Julian Day using the geometric solar longitude along with + the effect of the position of the moon. + + Calling Sequence: + asl = AstAlg_apparent_solar_longitude(double jd) + + The returned value is in degrees. +*/ + +double AstAlg_apparent_solar_longitude(double jd) { + + static double last_jd, last_asl; + + /* if we've already calculated the value simply return it */ + + if (jd == last_jd) return (last_asl); + + last_jd = jd; + last_asl = AstAlg_geometric_solar_longitude(jd) - 0.00569 - + 0.00478*sin(AstAlg_DTOR * AstAlg_lunar_ascending_node(jd)); + + return (last_asl); +} + +/* AstAlg_dday + ============= + Author: Kile Baker +*/ + +/* This routine is a fairly trivial one. In the AstAlg software + it is commonly necessary to know the decimal day (i.e. day of month + and fraction thereof. This routine simply calculates the decimal + day as a double precision number from the day, hour, minute, and second. + + No attempt is made to check the validity of the arguments so you can + create a garbage result if you give it nonsense values like setting + the day to 1000 or the hour to 50, etc. +*/ + +double AstAlg_dday(int day, int hour, int minute, int second) { + +/* SGS Changed */ +/* Kile, this is WRONG! Nathanial found this bug */ +/* return (double) (day + hour/24.0 + minute/60.0 + second/3600.0);*/ + return ((double) (day + (hour + minute/60.0 + second/3600.0)/24.)); +} + +/* AstAlg_equation_of_time + ========================= + Author: Kile Baker +*/ + +/* This function returns the equation of time for a given + Julian date. + + The returned value is the difference between the apparent time and the + mean solar time. The correction is returned in minutes and is always + between + and - 20. + + A positive value menas that the true sun crosses the observer's meridian + before the mean sun (i.e. mean time is lagging). + + Calling sequence: + + eqt = AstAlg_equation_of_time(double jd) + + This routine calls AstAlg_mean_solar_longitude, + AstAlg_solar_right_ascension, AstAlg_mean_obliquity, + and AstAlg_nutation_corr. +*/ + +double AstAlg_equation_of_time(double jd) { + + static double last_jd, last_eqt; + + double eqt; + double dpsi, deps, sml, sra, obliq; + + /* if we've already calculated the value, simply return it */ + + if (jd == last_jd) return (last_eqt); + + /* first get all the separate pieces */ + sml = AstAlg_mean_solar_longitude(jd); + sra = AstAlg_solar_right_ascension(jd); + obliq = AstAlg_mean_obliquity(jd); + AstAlg_nutation_corr(jd, &dpsi, &deps); + + /* now put it all together */ + + eqt = sml - 0.0057183 - sra + dpsi*cos(AstAlg_DTOR*(obliq + deps)); + + eqt = dmod(eqt,360.0); + + /* now convert from degrees to minutes */ + eqt = 4.0 * eqt; + + if (eqt > 20.0) eqt = eqt - 24.0*60.0; /*wrap back 24 hours*/ + if (eqt < -20.0) eqt = 24.0*60.0 + eqt; /*wrap forward 24 hours */ + + last_jd = jd; + last_eqt = eqt; + + return (eqt); +} + +/* AstAlg_geometric_solar_longitude + ================================ + Author: Kile Baker +*/ + +/* This function calculates the geometric_solar_longitude. + + Calling sequence: + gsl = AstAlg_geometric_solar_longitude(julian_day) + + The returned value is in degrees from 0 to 360 + + The function requires the mean solar longitude and the + mean solar anomaly. + +*/ + +double AstAlg_geometric_solar_longitude(double jd) { + + static double last_jd, last_gsl; + + double sml, sma, tau, gc; + + /* if we've already calculated the value for this Julian Day just + return it */ + + if (last_jd == jd) return (last_gsl); + + /* compute the delta-tau in centuries from the reference time J2000 */ + tau = (jd - J2000)/36525.0; + + sml = AstAlg_mean_solar_longitude(jd); + + /* we need the mean solar anomaly in radians because it's used in some + sine functions */ + sma = AstAlg_DTOR * AstAlg_mean_solar_anomaly(jd); + + gc = (1.914602 - 0.004817*tau - 0.000014*(tau*tau)) * sin(sma) + + (0.019993 - 0.000101*tau) * sin(2.0*sma) + + 0.000289 * sin(3.0*sma); + + sml += gc; +/* sml = sml + gc;*/ + + /* make sure the value is between 0 and 360 degrees */ + sml = dmod(sml, 360.0); + + if (sml < 0.0) sml += 360.0; + + last_jd = jd; + last_gsl = sml; + + return (sml); +} + +/* AstAlg_jde2calendar + =================== + Author: Kile Baker +*/ + +/* This routine converts Julian Day to calendar year, month, day, + hour, minute and second */ + +void AstAlg_jde2calendar(double jd, + int *year, + int *month, + int *day, + int *hour, + int *minute, + int *second) { + + long a,b,c,d,e, z; + long alpha; + double f; + double dday, resid; + +/* this is a rather complicated calculation and uses some clever tricks + that I (Kile Baker) don't really understand. It all comes out of + Meeus (chapter 7) and it all seems to work. */ + + jd += 0.5; + +/* z is the integer part of jd+.5 and f is the remaining fraction of a day */ + + z = (long) jd; + f = jd - z; + + if (z < 2299161) a = z; + else { + alpha = (long) ((z - 1867216.25)/36524.25); + a = z + 1 + alpha - (long)(alpha/4); + } + + b = a + 1524; + c = (long) ((b - 122.1)/365.25); + d = (long) (365.25 * c); + e = (long) ((b - d)/30.6001); +/* NOTE: Meeus states emphatically that the constant 30.6001 must be used + to avoid calculating dates such as Feb. 0 instead of Jan. 31. */ + +/* the value of e is basically giving us the month */ + + if (e < 14) *month = e-1; + else *month = e-13; + +/* the value of c is basically giving us the year */ + + if (*month > 2) *year = c - 4716; + else *year = c - 4715; + +/* now calclulate the decimal day */ + + dday = b - d - (double) ((long) (30.6001 * e)) + f; + +/* extract the integer part of the day and get the left over residual + in hours */ + + *day = (int) dday; + resid = (dday - *day) * 24.0; + +/* extract the integer part of the hours and get the left over minutes */ + + *hour = (int) resid; + resid = (resid - *hour)*60.0; + +/* extract the integer part of the minutes and get the left over seconds */ + *minute = (int) resid; + resid = (resid - *minute)*60.0; + +/* round of the value of second to the nearest second */ + + *second = (int)(resid + 0.5); +} + +/* AstAlg_jde + ========== + Author: Kile Baker +*/ + +/* This routine returns the Julian Day as a double precision value + + Calling sequence: + + jd = AstAlg_jde( int year, int month, double day) + +*/ + +/* NOTE: Meeus uses Terrestrial Dynamic (TD) time, sometimes referred to + as Ephemeris Time. The 'E' in the name of this function (jde) is + a reminder that the time is Julian Day in Ephemeris time. + + All of the rest of the algorithms provided in this set of software + calculate their values with reference to Jan 1, 2000 at noon TD. + This reference time is referred to as J2000 and is a defined constant + in AstAlg.h. + + Since all the calculations involve differences between the JDE value + and J2000, the difference between TD at UT can safely be ignored. + The only difference comes from any leaap seconds that have been applied + between the reference time and the time of interest. Those differences + are sufficiently small (a few seconds) so that we ignore them. + + If you want to have higher accuracy you probably need to calculate the + decimal day in terms of TD rather than UT. Meeus has details of how + to do this. +*/ + +double AstAlg_jde(int year, int month, double day) { + +/* NOTE: the value of 'year' must be the full 4-digit year */ + + int a; + double b; + +/* if the month is January or February, treat it as belonging + to the previous year. This makes the leap year correction + easier to handle */ + + if (month <= 2) { + year--; + month += 12; + } + +/* the next few lines perform the leap year correction. It deals + with the centuries correctly */ + + a = (int) (year/100); + b = (double) (2 - a + a/4); + +/* ideally, the constant 30.6001 could simply be 30.6, but adding the + additional .0001 to it ensures that truncation error doesn't result + in an incorrect calculation */ + + return ((double) ( (long) (365.25*(year + 4716)) + + (double) ( (long) (30.6001 * (month + 1)))) + day + b - 1524.5); +} + +/* AstAlg_lunar_ascending_node + =========================== + Author: Kile Baker +*/ + +/* This function calculates the location of the moon's ascending node. + This value affects the nutation of the Earth's spin axis. + + Calling Sequence: + asc_node = AstAlg_lunar_ascending_node(double jd) + + where jd is the Julian Day +*/ + +double AstAlg_lunar_ascending_node(double jd) { + + static double last_jd, last_ascn; + double tau, omega; + +/* if we've already calculated the value just return it */ + + if (jd == last_jd) return (last_ascn); + +/* calculate the delta-time in centuries with respect to + the reference time J2000 */ + + tau = (jd - J2000)/36525.0; + +/* omega = 125 - 1934 * tau + .002*tau^2 + t^3/4.5e5 */ + + omega = (((tau/4.50e5 + 2.0708e-3)*tau - 1.934136261e3)*tau) + 125.04452; + +/* now make sure omega is between 0 and 360 */ + + omega = dmod(omega, 360.0); + + if (omega < 0.0) omega += 360.0; + + last_jd = jd; + last_ascn = omega; + + return (omega); +} + +/* AstAlg_mean_lunar_longitude + =========================== + Author: Kile Baker +*/ + +/* This function calculates the mean lunar longitude for a given + Julian Day. + + Calling Sequence: + + lunlong = AstAlg_mean_lunar_longitude(double jd) + +*/ + +double AstAlg_mean_lunar_longitude(double jd) { + + static double last_jd, last_llong; + double tau, llong; + +/* simply return the value if we've already calculated it */ + + if (jd == last_jd) return (last_llong); + +/* calculate the detla-time from the reference time J2000 in centuries */ + + tau = (jd - J2000)/36525.0; + + llong = 218.3165 + 481267.8813 * tau; + +/* make sure the resulting value is between 0 and 360 */ + + llong = dmod(llong, 360.0); + + if (llong < 0) llong += 360.0; + + last_jd = jd; + last_llong = llong; + + return (llong); +} + +/* AstAlg_apparent_solar_longitude + =============================== + Author: Kile Baker +*/ + +/* This function calcules the mean obliquity of the earth. That is + the inclination of the rotation axis relative to the ecliptic plane. + + Calling Sequence: + + e0 = AstAlg_mean_obliquity(julian_date) + + The returned value is in degrees. + +*/ + +double AstAlg_mean_obliquity(double jd) { + + static double last_jd, last_e0; + double tau; + const double coefs[] = {23.439291111111, -0.0130041666667, -1.638888889e-7, + 5.036111111e-7}; + +/* if we've already calculated this value for this date simply return it */ + + if (jd == last_jd) return (last_e0); + + /* the coefficients in Meeus are given in degrees, minutes and seconds + so I have to convert them to double precision degrees first. + Although this could be done before compilation it would make + it hard to check the code with the book. I therefore do + the calculation here but only the first time the routine is called. + */ + +/* if (coeffs[3] != 0.001813/3600.0) { + coeffs[3] = 0.001813/3600.0; + coeffs[2] = -0.00059/3600.0; + coeffs[1] = -46.8150/3600.0; + coeffs[0] = 23.0 + 26.0/60.0 + 21.448/3600.0; + } +*/ + + tau = (jd - J2000)/36525.0; + +/* Now calculate the value of e0 */ + last_e0 = ((((coefs[3]*tau) + coefs[2]) * tau) + coefs[1]) * tau + coefs[0]; + last_jd = jd; + + return (last_e0); +} + +/* AstAlg_apparent_solar_anomaly + ============================= + Author: Kile Baker +*/ + +/* This function calculates the mean solar anomaly for a given Julian day +(see function AstAlg_jde.c). + + Calling Sequence: + slong = AstAlg_mean_solar_anomaly(double jd); + + The returned value is in degrees and ranges from 0 to 360 + +*/ + +double AstAlg_mean_solar_anomaly( double jd ) { + +/* last calculated value for this function is saved. This is done + to avoid recalculating the value multiple times, since it is used + in several other functions in the AstAlg library */ + + static double last_jd, last_sma; + + double tau, sma; + + if (jd == last_jd) return (last_sma); + +/* calculate the difference between the requested Julian Day and + the reference value, J2000, in centuries */ + + tau = (jd - J2000)/36525.0; + sma = 357.5291130 + 35999.05029 * tau - 0.0001537 * (tau*tau); + +/* make sure the value is between 0 and 360 */ + + sma = dmod(sma, 360); + + if (sma < 0.0) sma += 360.0; + + last_jd = jd; + last_sma = sma; + + return (sma); +} + +/* AstAlg_mean_solar_longitude + =========================== + Author: Kile Baker +*/ + +/* This function calculates the mean solar longitude for a given Julian day (see function AstAlg_jde.c). + + Calling Sequence: + slong = AstAlg_mean_solar_longitude(double jd); + + The returned value is in degrees and ranges from 0 to 360 + +*/ + +double AstAlg_mean_solar_longitude( double jd ) { + +/* the mean solar longitude gets called from several other functions + and we don't want to have to recalculate the value every time, so + the last calculated value is saved as a static value */ + + static double last_jd, last_sl; + + static const double coefs[] = {280.4664567, 360007.6982779, + 0.03032028, 2.00276381406e-5, + -6.53594771242e-5, -0.50e-6 }; + double tau, sl; + int i; + + if (jd == last_jd) return (last_sl); + +/* calculate the difference between the requested Julian day and the + reference Julian day, J2000, in terms of milennia */ + + tau = (jd - J2000)/365250.0; + +/* now calculate the solar longitude using the delta-time tau and the + coefficients */ + + sl = 0.0; + for (i=5; i>=0; i--) { + sl = tau * sl + coefs[i]; + } + +/* make sure the result is between 0 and 360 degrees */ + + sl = dmod(sl, 360); + + if (sl < 0.0) sl += 360.0; + + last_jd = jd; + last_sl = sl; + + return (sl); +} + +/* AstAlg_nutation_corr + ==================== + Author: Kile Baker +*/ + +/* This routine calculates the corrections to the solar longitude and + obliquity that arize due to the nutation of the earth's spin. + + Calling Sequence: + + void AstAlg_nutation_corr(double jd, double *slong_corr, double *obliq_corr) + +*/ + +void AstAlg_nutation_corr(double jd, double *slong_corr, double *obliq_corr) +{ + double slong, lunlong, omega; + static double last_jd, last_slcorr, last_oblcorr; + +/* just return the values if they've already been calculated */ + + if (jd == last_jd) { + *slong_corr = last_slcorr; + *obliq_corr = last_oblcorr; + return; /* SGS added */ + } + +/* get the mean solar longitude and mean lunar longitude in radians */ + + slong = AstAlg_DTOR * AstAlg_mean_solar_longitude(jd); + lunlong = AstAlg_DTOR * AstAlg_mean_lunar_longitude(jd); + omega = AstAlg_DTOR * AstAlg_lunar_ascending_node(jd); + +/* the next line computes the correction to the solar longitude in arcsecs */ + + *slong_corr = -17.20 * sin(omega) - 1.32 * sin(2.0*slong) - + 0.23 * sin(2.0*lunlong) + 0.21 * sin(2.0*omega); + +/* convert from arcsec to degrees */ + *slong_corr = *slong_corr/3600.0; + +/* Next we calculate the correction to the obliquity in arcsecs */ + + *obliq_corr = 9.20 * cos(omega) + 0.57 * cos(2.0*slong) + + 0.10 * cos(2.0*lunlong) - 0.09 * cos(2.0*omega); + + *obliq_corr = *obliq_corr/3600.0; + +/* save the calculated values in case their needed again */ + last_jd = jd; + last_slcorr = *slong_corr; + last_oblcorr = *obliq_corr; +} + +/* AstAlg_apparent_solar_declination + ================================= + Author: Kile Baker +*/ + +/* This function returns the apparent declination of the sun for a given + Julian Day. It uses the apparent_obliquity and the apparent solar + longitude. + + Calling sequence: + + sdec = AstAlg_solar_declination(double jd) + + The value is returned in degrees. +*/ + +double AstAlg_solar_declination(double jd) { + + static double last_jd, last_sdec; + double sindec; + +/* if we've already calculated the value simply return it */ + + if (jd == last_jd) return (last_sdec); + + sindec = sin(AstAlg_DTOR * AstAlg_apparent_obliquity(jd)) * + sin(AstAlg_DTOR * AstAlg_apparent_solar_longitude(jd)); + + last_jd = jd; + last_sdec = asin(sindec)/AstAlg_DTOR; + + return (last_sdec); +} + +/* AstAlg_solar_right_ascension + ============================ + Author: Kile Baker +*/ + +/* This function returns the right ascension of the sun for a given + Julian date. + + Calling sequence: + ra = AstAlg_solar_right_ascension(double jd) + + The value is returned in DEGREES, not in the more traditional + hours. Convert to hour angle by dividing degrees by 15. + + The function calls the functions AstAlg_apparent_solar_longitude + and AstAlg_apparent_obliquity + +*/ + +double AstAlg_solar_right_ascension(double jd) { + + static double last_jd, last_ra; + double eps, slong, alpha; + +/* if we've already calculated the value, simply return it. */ + + if (jd == last_jd) return (last_ra); + + /* get the solar longitude in radians */ + slong = AstAlg_DTOR * AstAlg_apparent_solar_longitude(jd); + + /* get the obliquity in radians */ + eps = AstAlg_DTOR * AstAlg_apparent_obliquity(jd); + + alpha = atan2(cos(eps)*sin(slong),cos(slong)); + + last_ra = alpha/AstAlg_DTOR; + last_jd = jd; + + return (last_ra); +} + diff --git a/src/c_aacgm_v2/genmag.c b/c_aacgmv2/src/genmag.c similarity index 74% rename from src/c_aacgm_v2/genmag.c rename to c_aacgmv2/src/genmag.c index eeba0d5a..957e1cd7 100644 --- a/src/c_aacgm_v2/genmag.c +++ b/c_aacgmv2/src/genmag.c @@ -26,19 +26,19 @@ int dayno(int year, int month, int day, int *diy) { - int k,tot; - int ndays[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + int k,tot; + int ndays[] = {31,28,31,30,31,30,31,31,30,31,30,31}; - *diy = 365; - if(((year%4==0)&&(year%100!=0))||(year%400==0)) { - ndays[1]++; - *diy = 366; - } + *diy = 365; + if(((year%4==0)&&(year%100!=0))||(year%400==0)) { + ndays[1]++; + *diy = 366; + } - tot = 0; - for (k=0; k +#include +#include +#include +#include +#include "igrflib.h" +#include "genmag.h" +#include "astalg.h" + +/*#define DEBUG 1*/ +/* TO DO: should these stuff go in igrflib.h? */ + +static struct { + int year; + int month; + int day; + int hour; + int minute; + int second; + int dayno; + int daysinyear; +} igrf_date = {-1,-1,-1,-1,-1,-1,-1,-1}; + +static struct { + double ctcl; + double ctsl; + double stcl; + double stsl; + double ct0; + double st0; + double cl0; + double sl0; +} geopack = {0.,0.,0.,0.,0.,0.,0.,0.}; + +static double IGRF_coef_set[MAXNYR][IGRF_MAXK]; /* all the coefficients */ +static double IGRF_svs[IGRF_MAXK]; /* secular variations */ +static double IGRF_coefs[IGRF_MAXK]; /* interpolated coefficients */ +static int nmx; /* order of expansion */ + +/*----------------------------------------------------------------------------- +; for debugging +;+----------------------------------------------------------------------------- +*/ +void pause(void) +{ + char ch; + + fprintf(stdout, "(Hit Enter to coninue..."); + scanf("%c", &ch); + fprintf(stdout, "\n"); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_loadcoeffs +; +; PURPOSE: +; Load the entire set of spherical harmonic coefficients from the given +; file. +; +; Read the in the coefficients. Note that I am using the same ordering as +; is used in the AACGM code. That is, +; +; l 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 ... +; m 0 -1 0 1 -2 -1 0 1 2 -3 -2 -1 0 1 2 3 -4 -3 -2 -1 0 ... +; +; C & IDL index: k = l * (l+1) + m +; +; k 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... +; +; CALLING SEQUENCE: +; err = IGRF_loadcoeffs(); +; +; Input Arguments: +; filename - name of file which contains IGRF coefficients; default +; is current IGRF model: igrf12coeffs.txt +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int IGRF_loadcoeffs(char *filename) +{ + int k,l,m,n, ll,mm; + int fac, len; + int iyear, nyear; + int dgrf[MAXNYR]; + int epoch[MAXNYR]; + char jnk; +/* char header[2][MAXSTR];*/ + char line[MAXSTR]; + double fyear; + double coef, sv; + double Slm[IGRF_MAXK], fctrl[2*IGRF_ORDER+1], dfc[2*IGRF_ORDER]; + FILE *fp; + + #if DEBUG > 0 + printf("IGRF_loadceoffs\n"); + #endif + + /* file containing the IGRF coefficients */ + if(strlen(filename) == 0 && getenv("IGRF_COEFFS") != (char *)(NULL)) + filename = getenv("IGRF_COEFFS"); + + if(strlen(filename) == 0) + { + printf("\n"); + printf("*************************************************************\n"); + printf("* You MUST set the environment variable IGRF_COEFFS \n"); + printf("*************************************************************\n"); + return (-99); + } +/* strcpy(filename,getenv("IGRF_COEFFS")); */ + + #if DEBUG > 1 + printf("Schmidt quasi-normalization factors\n"); + printf("===================================\n\n"); + #endif + + /* factorial */ + fctrl[0] = fctrl[1] = 1.; + for (k=2; k<= 2*IGRF_ORDER; k++) + fctrl[k] = k*fctrl[k-1]; + + /*for(k=0; k<=2*IGRF_ORDER; k++) printf("%lf\n", fctrl[k]); */ + + /* double factorial */ + dfc[1] = 1; + for (k=3; k<2*IGRF_ORDER; k+=2) + dfc[k] = dfc[k-2]*k; + + for (l=0; l<=IGRF_ORDER; l++) { + for (m=0; m<=l; m++) { + k = l * (l+1) + m; /* 1D index for l,m */ + n = l * (l+1) - m; /* 1D index for l,m */ + + fac = (m) ? 2 : 1; + /* Davis 2004; Wertz 1978 recursion + Slm[k] = Slm[n] = sqrt(fac*fctrl[l-m]/fctrl[l+m])*dfc[2*l-1]/fctrl[l-m]; + */ + /* Winch 2004 */ + Slm[k] = Slm[n] = sqrt(fac*fctrl[l-m]/fctrl[l+m]); + + #if DEBUG > 1 + printf("$ %2d %2d %2d %e %e %e\n", l, m, k, fctrl[l-m],fctrl[l+m],Slm[k]); + printf("$ %2d %2d %2d %e %e %e\n", l,-m, n, fctrl[l-m],fctrl[l+m],Slm[n]); + #endif + } + } + + /* get the coefficients */ + fp = fopen(filename, "r"); + if (fp == NULL) { + fprintf(stderr, "File not found: %s\n", filename); + return (-1); + } + + /* read first two header lines */ + for (k=0; k<2; k++) { + jnk = ' '; + m = 0; + while (jnk != '\n') { + fscanf(fp, "%c", &jnk); +/* header[k][m] = (jnk == '\n') ? (char)0 : jnk;*/ + m++; + } + } + + /* get next line */ + jnk = ' '; + m = 0; + while (jnk != '\n') { + fscanf(fp, "%c", &jnk); + line[m] = (jnk == '\n') ? (char)0 : jnk; + m++; + } + len = m; + #if DEBUG > 1 + fprintf(stderr, "%s\n", line); + #endif + + /* count how many D/IGRF years */ + nyear = 0; + for (m=0; m MAXNYR) { + fprintf(stderr, "Too many years in file: %d\n", nyear); + return (-2); + } + #if DEBUG > 1 + fprintf(stderr, "%d years\n", nyear); + #endif + + iyear = 0; + for (m=0; m 1 + for (m=0; m 1 + fprintf(stderr, "%8.2lf\n", fyear); + #endif + } + + #if DEBUG > 1 + for (m=0; m 1 + fprintf(stderr, "%d %d %d %d %f\n", k, l, n, 0, IGRF_coef_set[n][k]); + #endif + } + fscanf(fp, "%lf", &sv); /* secular variation */ + IGRF_svs[k] = sv * Slm[k]; /* NORMALIZE */ + fscanf(fp, "%c", &jnk); /* */ + + for (m=1; m<=l; m++) { + k = l * (l+1) + m; /* 1D index for l,m */ + fscanf(fp, "%c", &jnk); /* g or h */ + fscanf(fp, "%d %d", &ll, &mm); /* l amd m */ + + for (n=0; n 1 + fprintf(stderr, "%d %d %d %d %f\n", k, l, n, m, IGRF_coef_set[n][k]); + #endif + } + fscanf(fp, "%lf", &sv); /* secular variation */ + IGRF_svs[k] = sv * Slm[k]; /* NORMALIZE */ + fscanf(fp, "%c", &jnk); /* */ + + k = l * (l+1) - m; /* 1D index for l,m */ + fscanf(fp, "%c", &jnk); /* g or h */ + fscanf(fp, "%d %d", &ll, &mm); /* l amd m */ + for (n=0; n 1 + fprintf(stderr, "%d %d %d %d %f\n", k, l, n, -m, IGRF_coef_set[n][k]); + #endif + } + fscanf(fp, "%lf", &sv); /* secular variation */ + IGRF_svs[k] = sv * Slm[k]; /* NORMALIZE */ + + /* note, some files end each line with while others are */ + fscanf(fp, "%c", &jnk); /* */ + if (jnk == 13) fscanf(fp, "%c", &jnk); /* */ + } + + #if DEBUG > 2 + pause(); + #endif + } + fclose(fp); + + #if DEBUG > 1 + for (n=0; n 1 + fprintf(stderr, "%d\n", (2000-1900)/5); + /* print coefficients in order */ + for (l=0; l<=IGRF_ORDER; l++) { + for (m=-l; m<=l; m++) { + k = l * (l+1) + m; + fprintf(stderr, "%2d %3d %3d: %e\n", l,m,k, + IGRF_coef_set[(1980-1900)/5][k]); + } + } + pause(); + #endif + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_Plm +; +; PURPOSE: +; Internal function to compute array of Gaussian Normalized Associated +; Legendre functions and the corresponding derivatives. +; +; CALLING SEQUENCE: +; err = IGRF_Plm(theta, order, plmval, dplmval); +; +; Input Arguments: +; theta - co-latitude in radians +; order - order of expansion, should NOT exceed IGRF_ORDER +; +; Output Arguments: +; plmval - pointer to array for storage of values +; dplmval - pointer to array for storage of derivative values +; +; Return Value: +; error code +; +; Notes: I am using array indexing similar to that used for m=-l to l, +; but here m=0 to l, so the arrays are too big and there are no +; values stored in locations for m<0. Probably should fix that... +; +; values are stored in a 1D array of dimension (order+1)^2. The +; indexing scheme used is: +; +; g h g g h h g g g h h h g g g g h h h h h ... +; l 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 ... +; m 0 -1 0 1 -2 -1 0 1 2 -3 -2 -1 0 1 2 3 -4 -3 -2 -1 0 ... +;C & IDL j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... +;FORTRAN j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ... +; +;+----------------------------------------------------------------------------- +*/ + +int IGRF_Plm(double theta, int order, double *plmval, double *dplmval) { + int l,m,k,n,p; + double a,b; /* factors */ + double st,ct; + + if (order > IGRF_ORDER) return (-1); + + st = sin(theta); + ct = cos(theta); + + plmval[0] = 1.; /* 0,0 */ + dplmval[0] = 0.; /* 0,0 */ + /* compute values of P^{l,l} and dP^{l,l}/dtheta */ + for (l=1; l<=order; l++) { + k = l * (l+1) + l; /* l = m */ + n = (l-1) * l + l-1; /* l-1 = m-l, i.e., previous l=m */ + /* Davis 2004; Wertz 1978 recursion + plmval[k] = plmval[n]*st; + dplmval[k] = dplmval[n]*st + plmval[n]*ct; + */ + /* numerical recipies in C */ + /* a = 1-2*l;*/ /* reverse order to remove Condon-Shortley phase */ + a = 2*l-1; + plmval[k] = a*plmval[n]*st; + dplmval[k] = a*(dplmval[n]*st + plmval[n]*ct); + + #if DEBUG > 1 + printf("%2d %3d %e %e\n", l, k, plmval[k], dplmval[k]); + #endif + } + + plmval[2] = ct; /* 1,0 */ + dplmval[2] = -st; /* 1,0 */ + /* compute values of P^{l,m} and dP^{l,m}/dtheta */ + for (l=2; l<=order; l++) { + for (m=0; m 1 + printf("%2d %2d %3d %e %e\n", l, m, k, plmval[k], dplmval[k]); + #endif + } + } + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_compute +; +; PURPOSE: +; User function to compute IGRF magnetic field at lat/lon and distance. +; +; CALLING SEQUENCE: +; err = IGRF_compute(r, theta, phi, Br, Btheta, Bphi); +; +; Input Arguments: +; r - geocentric distance in km +; theta - co-latitude in radians +; phi - longitude in radians +; +; Output Arguments: +; Br - pointer to field in radial direction +; Btheta - pointer to field in co-latitude direction +; Bphi - pointer to field in longtitude direction +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int IGRF_compute(const double rtp[], double brtp[]) { + + int k,l,m,n; +/* double brr,btt,bpp; */ + double tbrtp[3], st, theta; + double aor, afac; + double dplmval[IGRF_MAXK], plmval[IGRF_MAXK]; + double cosm_arr[IGRF_ORDER+1], sinm_arr[IGRF_ORDER+1]; + + #if DEBUG > 0 + printf("IGRF_compute\n"); + #endif + + /* no date/time set so bail */ + if (igrf_date.year < 0) { + IGRF_msg_notime(); + return -128; + } + + /* Must avoid singularit at the poles (dividing by sin(theta) later) */ + theta = rtp[1]; + st = sin(theta); + if (fabs(st) < 1e-15) theta += (st < 0.) ? 1e-15 : -1e-15; + + /* Compute the values of the Legendre Polynomials, and derivatives */ + IGRF_Plm(theta,nmx,plmval,dplmval); + +/* aor = RE/r;*/ /* a/r, where RE = a */ +/* aor = RE/rtp[0];*/ /* a/r, where RE = a */ + aor = 1./rtp[0]; /* r is in units of RE to be consistent with geopack, */ + /* we want RE/r */ + + /*printf("aor = %lf\n", aor);*/ + afac = aor*aor; + + /* array of trig functions in phi for faster computation */ + for (k=0; k<=IGRF_ORDER; k++) { + cosm_arr[k] = cos(k*rtp[2]); + sinm_arr[k] = sin(k*rtp[2]); + } + + for (k=0;k<3;k++) brtp[k] = 0; + + for (l=1; l<=nmx; l++) { /* no l = 0 term in IGRF */ + for (k=0;k<3;k++) tbrtp[k] = 0; + for (m=0; m<=l; m++) { + k = l*(l+1) + m; /* g */ + n = l*(l+1) - m; /* h */ + + tbrtp[0] += (IGRF_coefs[k]*cosm_arr[m] + IGRF_coefs[n]*sinm_arr[m]) * + plmval[k]; + tbrtp[1] += (IGRF_coefs[k]*cosm_arr[m] + IGRF_coefs[n]*sinm_arr[m]) * + dplmval[k]; + tbrtp[2] += (-IGRF_coefs[k]*sinm_arr[m] + IGRF_coefs[n]*cosm_arr[m]) * + m*plmval[k]; + +/* printf("%2d %2d %e %e %e\n", l,m, IGRF_coefs[k],IGRF_coefs[n], plmval[k]); +// printf("[]: %e %e %e\n", tbrtp[0], tbrtp[1], tbrtp[2]); +// printf(" %2d: brr=%lf, coef[k]=%lf, coef[n]=%lf, plmval[k]=%lf\n", +// m,brr,IGRF_coefs[k],IGRF_coefs[n],plmval[k]); +// printf(" %2d: brr=%lf, cosm=%lf, sinm=%lf\n", m,brr,cosm_arr[m],sinm_arr[m]);*/ + } +/* printf("%2d brr = %lf\n", l,brr);*/ + afac *= aor; + +/* *br += afac*(l+1)*brr; +// *btheta -= afac*btt; +// *bphi -= afac*bpp; */ + brtp[0] += afac*(l+1)*tbrtp[0]; + brtp[1] -= afac*tbrtp[1]; + brtp[2] -= afac*tbrtp[2]; + } + +/* *bphi /= sin(theta); +//printf("*** %e %e\n", brtp[2], sin(rtp[1])); +//printf("*** %e %e %e\n", brtp[0], brtp[1], brtp[2]); */ + brtp[2] /= sin(theta); +/* if (sin(rtp[1]) > 1.e-19) { *//* from geopack ... */ +/* brtp[2] /= sin(rtp[1]); +// } else { +// if (cos(rtp[1]) < 0.) brtp[2] = -brtp[2]; +// } +//printf("*** %e %e %e\n", brtp[0], brtp[1], brtp[2]); */ + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_interpolate_coefs +; +; PURPOSE: +; Function to compute interpolated coefficients. +; +; CALLING SEQUENCE: +; err = IGRF_interpolate_coefs(); +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ +int IGRF_interpolate_coefs(void) { + + int i,k,l,m, myear; + double fyear; + double g10,g11,h11,sq,sqq,sqr; + + #if DEBUG > 0 + printf("** TIME INTERPOLATION **\n"); + #endif + + /* fyear is the floating point time */ + fyear = igrf_date.year + ((igrf_date.dayno-1) + (igrf_date.hour + + (igrf_date.minute + igrf_date.second/60.)/60.)/24.)/ + igrf_date.daysinyear; + + /* NOTE: FORTRAN code allows 10-year extrapolation beyond last epoch. + * Here we are limiting to only 5 */ + if (fyear < IGRF_FIRST_EPOCH || fyear > IGRF_LAST_EPOCH + 5) { + /* reset date */ + igrf_date.year = igrf_date.month = igrf_date.day = -1; + igrf_date.hour = igrf_date.minute = igrf_date.second = -1; + igrf_date.dayno = igrf_date.daysinyear = -1; + + fprintf(stdout, "Date range for current IGRF model is: %4d to %4d\n\n", + IGRF_FIRST_EPOCH, IGRF_LAST_EPOCH+5); + return (-3); + } + + myear = igrf_date.year/5*5; /* epoch year */ + nmx = (igrf_date.year < 1995) ? 10 : 13; /* order of expansion */ + i = (myear - IGRF_FIRST_EPOCH)/5; /* index of first set of coefs */ + + if (fyear < IGRF_LAST_EPOCH) { + /* interpolate bounding coefficients */ + for (l=1; l<=nmx; l++) { /* no l = 0 term in IGRF */ + for (m=-l; m<=l; m++) { + k = l * (l+1) + m; /* SGS: changes indexing */ + IGRF_coefs[k] = IGRF_coef_set[i][k] + (fyear-myear)* + (IGRF_coef_set[i+1][k]-IGRF_coef_set[i][k])/5; + } + } + } else { + /* use secular varation */ + for (l=1; l<=nmx; l++) { /* no l = 0 term in IGRF */ + for (m=-l; m<=l; m++) { + k = l * (l+1) + m; /* SGS: changes indexing */ + IGRF_coefs[k] = IGRF_coef_set[i][k] + (fyear-myear)*IGRF_svs[k]; + } + } + } + + /* compute the components of the unit vector EzMag in geographic coordinates: + * sin(theta0)*cos(lambda0), sin(theta0)*sin(lambda0) + */ + +/* C & IDL index: k = l * (l+1) + m */ + g10 = -IGRF_coefs[2]; /* 1*2+0 = 2 */ + g11 = IGRF_coefs[3]; /* 1*2+1 = 3 */ + h11 = IGRF_coefs[1]; /* 1*2-1 = 1 */ + + sq = g11*g11 + h11*h11; + + sqq = sqrt(sq); + sqr = sqrt(g10*g10 + sq); + + geopack.sl0 = -h11/sqq; + geopack.cl0 = -g11/sqq; + geopack.st0 = sqq/sqr; + geopack.ct0 = g10/sqr; + + geopack.stcl = geopack.st0*geopack.cl0; + geopack.stsl = geopack.st0*geopack.sl0; + geopack.ctsl = geopack.ct0*geopack.sl0; + geopack.ctcl = geopack.ct0*geopack.cl0; + + #if DEBUG > 0 + printf("sl0 = %lf\n", geopack.sl0); + printf("cl0 = %lf\n", geopack.cl0); + printf("st0 = %lf\n", geopack.st0); + printf("ct0 = %lf\n", geopack.ct0); + printf("stcl = %lf\n", geopack.stcl); + printf("stsl = %lf\n", geopack.stsl); + printf("ctsl = %lf\n", geopack.ctsl); + printf("ctcl = %lf\n", geopack.ctcl); + #endif + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_SetDateTime +; +; PURPOSE: +; Function to set date and time. MUST be called at least once BEFORE +; any calls to IGRF functions. +; +; CALLING SEQUENCE: +; err = IGRF_SetDateTime(year, month, day, hour, minute, second, filename) +; +; Input Arguments: +; year - year [1965-2014] +; month - month of year [01-12] +; day - day of month [01-31] +; hour - hour of day [00-24] +; minute - minute of hour [00-60] +; second - second of minute [00-60] +; filename - file with IGRF coefficients +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int IGRF_SetDateTime(int year, int month, int day, + int hour, int minute, int second, char *filename) +{ + int err = 0; + + /* load coefficients if not already loaded */ + if (igrf_date.year < 0) + err = IGRF_loadcoeffs(filename); + + if (err) return (err); + + if (igrf_date.year != year || igrf_date.month != month || + igrf_date.day != day || igrf_date.hour != hour || + igrf_date.minute != minute || igrf_date.second != second) { + + igrf_date.year = year; + igrf_date.month = month; + igrf_date.day = day; + igrf_date.hour = hour; + igrf_date.minute = minute; + igrf_date.second = second; + igrf_date.dayno = dayno(year,month,day,&(igrf_date.daysinyear)); + + #if DEBUG > 0 + printf("IGRF_SetDateTime\n"); + printf("%03d: %04d%02d%02d %02d%02d:%02d\n", + igrf_date.dayno, igrf_date.year, igrf_date.month, igrf_date.day, + igrf_date.hour, igrf_date.minute, igrf_date.second); + #endif + + err = IGRF_interpolate_coefs(); + } + + return (err); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_GetDateTime +; +; PURPOSE: +; Function to get date and time. +; +; CALLING SEQUENCE: +; err = IGRF_GetDateTime(year, month, day, hour, minute, second, dayno); +; +; Output Arguments (integer pointers): +; year - year [1965-2014] +; month - month of year [01-12] +; day - day of month [01-31] +; hour - hour of day [00-24] +; minute - minute of hour [00-60] +; second - second of minute [00-60] +; dayno - day of year [01-366] +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int IGRF_GetDateTime(int *year, int *month, int *day, + int *hour, int *minute, int *second, int *dayno) +{ + *year = igrf_date.year; + *month = igrf_date.month; + *day = igrf_date.day; + *hour = igrf_date.hour; + *minute = igrf_date.minute; + *second = igrf_date.second; + *dayno = igrf_date.dayno; + + return 0; +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_SetNow +; +; PURPOSE: +; Function to set date and time to current computer time in UT. +; +; CALLING SEQUENCE: +; err = IGRF_SetNow(filename); +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int IGRF_SetNow(char *filename) +{ + /* current time */ + int err = 0; + int dyno; + time_t now; + struct tm *tm_now; + + /* load coefficients if not already loaded */ + if (igrf_date.year < 0) + err = IGRF_loadcoeffs(filename); + + if (err) return (err); + + now = time(NULL); + tm_now = gmtime(&now); /* right now in UT */ + + igrf_date.year = (*tm_now).tm_year + 1900; + igrf_date.month = (*tm_now).tm_mon + 1; + igrf_date.day = (*tm_now).tm_mday; + igrf_date.hour = (*tm_now).tm_hour; + igrf_date.minute = (*tm_now).tm_min; + igrf_date.second = (*tm_now).tm_sec; + igrf_date.dayno = (*tm_now).tm_yday + 1; + dyno = dayno(igrf_date.year,0,0,&(igrf_date.daysinyear)); + + #if DEBUG > 0 + printf("IGRF_SetNow\n"); + printf("%03d: %04d%02d%02d %02d%02d:%02d\n", + igrf_date.dayno, igrf_date.year, igrf_date.month, igrf_date.day, + igrf_date.hour, igrf_date.minute, igrf_date.second); + #endif + + fprintf(stderr, "\nIGRF: No date/time specified, using current time: "); + fprintf(stderr, "%04d%02d%02d %02d%02d:%02d\n\n", + igrf_date.year, igrf_date.month, igrf_date.day, + igrf_date.hour, igrf_date.minute, igrf_date.second); + + err = IGRF_interpolate_coefs(); + + return (err); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_Tilt +; +; PURPOSE: +; Function to return dipole tilt angle for the given UT time. +; +; CALLING SEQUENCE: +; tilt = IGRF_Tilt(year,month,day, hour,minute,second); +; +; Return Value: +; dipole tilt angle in degrees +; +;+----------------------------------------------------------------------------- +*/ + +double IGRF_Tilt(int yr, int mo, int dy, int hr, int mt, int sc, char *filename) +{ + double sps,s1,s2,s3,q; + double d1,d2,d3; + double dd,jd,dec,sras; + double dyn; + int diy; + double gst,cgst,sgst; + double fday,dj,d__1; + double rad = 57.295779513; + double dtwopi = 360.; + + IGRF_SetDateTime(yr,mo,dy,hr,mt,sc, filename); + + dd = AstAlg_dday(dy,hr,mt,sc); + jd = AstAlg_jde(yr,mo,dd); + dec = AstAlg_solar_declination(jd)*DTOR; + sras = AstAlg_solar_right_ascension(jd)*DTOR; + + s1 = cos(sras) * cos(dec); + s2 = sin(sras) * cos(dec); + s3 = sin(dec); + + dyn = dayno(yr,mo,dy, &diy); + + /* need Greenwich Mean Sidereal Time */ + /* SGS: seems like this should be somewhere in astalg.c, but can't find it */ + fday = ((double)hr*3600. + mt*60.+sc)/86400.; + dj = ((double)yr - 1900.)*365 + ((double)yr - 1901)/4. + dyn - .5 + fday; + d__1 = dj*0.9856473354 + 279.690983 + fday*360. + 180.; + /* SGS: double modulus */ + q = d__1/dtwopi; + q = (q >= 0) ? floor(q) : -floor(-q); + gst = (d__1 - dtwopi*q)/rad; + + sgst = sin(gst); + cgst = cos(gst); + + d1 = geopack.stcl * cgst - geopack.stsl * sgst; + d2 = geopack.stcl * sgst + geopack.stsl * cgst; + d3 = geopack.ct0; + + sps = d1*s1 + d2*s2 + d3*s3; + + return (asin(sps)/DTOR); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; IGRF_msg_notime +; +; PURPOSE: +; Display error message because no date and time have been set. +; +; CALLING SEQUENCE: +; IGRF_msg_notime(); +; +;+----------------------------------------------------------------------------- +*/ + +void IGRF_msg_notime(void) { +fprintf(stderr, +"\n" +"***************************************************************************\n" +"* IGRF ERROR: No Date/Time Set *\n" +"* *\n" +"* You must specifiy the date and time in order to use IGRF models. Before *\n" +"* calling IGRF functions you must set the date and time to the integer *\n" +"* using the function: *\n" +"* *\n" +"* IGRF_SetDateTime(year,month,day,hour,minute,second,filename); *\n" +"* *\n" +"* or to the current computer time in UT using the function: *\n" +"* *\n" +"* IGRF_SetNow(filename); *\n" +"* *\n" +"* subsequent calls to IGRF functions will use the last date and time *\n" +"* that was set, so update to the actual date and time that is desired. *\n" +"***************************************************************************" +"\n\n"); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; sph2car +; +; PURPOSE: +; Converts spherical coordinates into Cartesian coordinates. +; +; CALLING SEQUENCE: +; err = sph2car(r,theta,phi, x,y,z); +; +; Input Arguments: +; r - geocentric distance [RE, where RE=6371.2 km] +; theta - co-latitude [radians] +; phi - longitude [radians] +; +; Output Arguments (pointers to type double): +; x - Cartesian components +; y +; z +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ +int sph2car(const double rtp[], double xyz[]) { + double sq; + + sq = rtp[0]*sin(rtp[1]); + xyz[0] = sq*cos(rtp[2]); + xyz[1] = sq*sin(rtp[2]); + xyz[2] = rtp[0]*cos(rtp[1]); +/* + sq = r*sin(theta); + *x = sq*cos(phi); + *y = sq*sin(phi); + *z = r *cos(theta); +*/ + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; car2sph +; +; PURPOSE: +; Converts Cartesian coordinates into spherical coordinates. +; +; CALLING SEQUENCE: +; err = car2sph(x,y,z, r,theta,phi); +; +; Input Arguments +; x - Cartesian components [RE] +; y +; z +; +; Output Arguments: +; r - geocentric distance [RE] +; theta - co-latitude [radians] +; phi - longitude [radians] +; +; Return Value: +; error code +; +; Note: at the poles (x=0 and y=0) it is assumed that phi=0 +; +;+----------------------------------------------------------------------------- +*/ + +int car2sph(const double xyz[], double rtp[]) { + double sq; + + sq = xyz[0]*xyz[0] + xyz[1]*xyz[1]; + rtp[0] = sqrt(sq + xyz[2]*xyz[2]); + + if (sq == 0.) { + rtp[2] = 0.; + rtp[1] = (xyz[2] < 0) ? M_PI : 0.; + } else { + sq = sqrt(sq); + rtp[2] = atan2(xyz[1],xyz[0]); + rtp[1] = atan2(sq,xyz[2]); + if (rtp[2] < 0) rtp[2] += 2*M_PI; + } +/* + sq = x*x + y*y; + *r = sqrt(sq + z*z); + + if (sq == 0.) { + *phi = 0.; + *theta = (z < 0) ? M_PI : 0.; + } else { + sq = sqrt(sq); + *phi = atan2(y,x); + *theta = atan2(sq,z); + if (*phi < 0) *phi += 2*M_PI; + } +*/ + + return (0); + } + +/*----------------------------------------------------------------------------- +; +; NAME: +; bspcar +; +; PURPOSE: +; Converts spherical field components to Cartesian components. +; +; CALLING SEQUENCE: +; err = bspcar(theta,phi, br,btheta,bphi, bx,by,bz); +; +; Input Arguments +; theta - colatitude of point [radians] +; phi - longitude of point [radians] +; br - radial component [nT]; radially positive +; btheta - colatitude component [nT]; southward positive +; bphi - longitude component [nT]; eastward positive +; +; Output Arguments: +; bx - Cartesian components [RE] +; by +; bz +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int bspcar(double theta,double phi, const double brtp[], double bxyz[]) { + double st,ct,sp,cp,be; + + st = sin(theta); + ct = cos(theta); + sp = sin(phi); + cp = cos(phi); + be = brtp[0]*st + brtp[1]*ct; + + bxyz[0] = be*cp - brtp[2]*sp; + bxyz[1] = be*sp + brtp[2]*cp; + bxyz[2] = brtp[0]*ct - brtp[1]*st; +/* + st = sin(theta); + ct = cos(theta); + sp = sin(phi); + cp = cos(phi); + be = br*st + btheta*ct; + + *bx = be*cp - bphi*sp; + *by = be*sp + bphi*cp; + *bz = br*ct - btheta*st; +*/ + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; bcarsp +; +; PURPOSE: +; Converts Cartesian field components into spherical components. +; +; CALLING SEQUENCE: +; err = bcarsp(theta,phi, bx,by,bz, br,btheta,bphi); +; +; Input Arguments +; x,y,z - Cartesian components of point +; bx,by,bz - Cartesian field components [nT] +; +; Output Arguments: +; br - spherical field components [nT] +; btheta +; bphi +; +; Return Value: +; error code +; +; Note: at the poles (theta=0 or pi) it is assumed that phi=0 and therefore +; btheta=bx and bphi=by +; +;+----------------------------------------------------------------------------- +*/ + +int bcarsp(const double xyz[], const double bxyz[], double brtp[]) { + double r,rho,rho2,cp,sp,ct,st; + + rho2 = xyz[0]*xyz[0] + xyz[1]*xyz[1]; + r = sqrt(rho2 + xyz[2]*xyz[2]); + rho = sqrt(rho2); + + if (rho == 0.) { + cp = 1.; + sp = 0.; + } else { + cp = xyz[0]/rho; + sp = xyz[1]/rho; + } + + ct = xyz[2]/r; + st = rho/r; + + brtp[0] = (xyz[0]*bxyz[0] + xyz[1]*bxyz[1] + xyz[2]*bxyz[2])/r; + brtp[1] = (bxyz[0]*cp + bxyz[1]*sp)*ct - bxyz[1]*st; + brtp[2] = bxyz[1]*cp - bxyz[0]*sp; +/* + rho2 = x*x+y*y; + r = sqrt(rho2 + z*z); + rho = sqrt(rho2); + + if (rho == 0.) { + cp = 1.; + sp = 0.; + } else { + cp = x/rho; + sp = y/rho; + } + + ct = z/r; + st = rho/r; + + *br = (x*bx + y*by + z*bz)/r; + *btheta = (bx*cp + by*sp)*ct - bz*st; + *bphi = by*cp - bx*sp; +*/ + + return (0); +} + + +int geo2mag(const double xyzg[], double xyzm[]) { + + xyzm[0] = xyzg[0]*geopack.ctcl + xyzg[1]*geopack.ctsl - xyzg[2]*geopack.st0; + xyzm[1] = xyzg[1]*geopack.cl0 - xyzg[0]*geopack.sl0; + xyzm[2] = xyzg[0]*geopack.stcl + xyzg[1]*geopack.stsl + xyzg[2]*geopack.ct0; +/* + *xm = xg*geopack.ctcl + yg*geopack.ctsl - zg*geopack.st0; + *ym = yg*geopack.cl0 - xg*geopack.sl0; + *zm = xg*geopack.stcl + yg*geopack.stsl + zg*geopack.ct0; +*/ + + return (0); +} + +int mag2geo(const double xyzm[], double xyzg[]) { + + xyzg[0] = xyzm[0]*geopack.ctcl - xyzm[1]*geopack.sl0 + xyzm[2]*geopack.stcl; + xyzg[1] = xyzm[0]*geopack.ctsl + xyzm[1]*geopack.cl0 + xyzm[2]*geopack.stsl; + xyzg[2] = xyzm[2]*geopack.ct0 - xyzm[0]*geopack.st0; +/* + *xg = xm*geopack.ctcl - ym*geopack.sl0 + zm*geopack.stcl; + *yg = xm*geopack.ctsl + ym*geopack.cl0 + zm*geopack.stsl; + *zg = zm*geopack.ct0 - xm*geopack.st0; +*/ + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; geod2geoc +; +; PURPOSE: +; Convert from geodetic coordinates (as specified by WGS84) to +; geocentric coordinates using algorithm from IGRF Fortran code. +; +; CALLING SEQUENCE: +; err = geod2geoc(lat,lon,alt, rtp); +; +; Input Arguments: +; lat,lon - geodetic latitude and longitude [degrees N and E] +; alt - distance above sea level [km] +; +; Output Argument: +; rtp[3] - geocentric coordinates: radial distance from center +; of Earth [RE], angle from north pole [radians], +; azimuthal angle [radians] +; +; Return Value: +; error code +; +;+----------------------------------------------------------------------------- +*/ + +int geod2geoc(double lat, double lon, double alt, double rtp[]) { + + double a,b,f,a2,b2,st,ct,one,two,three,rho,cd,sd; + double r,theta; + + a = 6378.1370; /* semi-major axis */ + f = 1./298.257223563; /* flattening */ + b = a*(1. -f); /* semi-minor axis */ + a2 = a*a; + b2 = b*b; + theta = (90. -lat)*DTOR; /* colatitude in radians */ + st = sin(theta); + ct = cos(theta); + one = a2*st*st; + two = b2*ct*ct; + three = one + two; + rho = sqrt(three); /* [km] */ + r = sqrt(alt*(alt+2*rho) + (a2*one + b2*two)/three); /* [km] */ + cd = (alt+rho)/r; + sd = (a2-b2)/rho *ct*st/r; + + rtp[0] = r/RE; /* units of RE */ + rtp[1] = acos(ct*cd - st*sd); + rtp[2] = lon*DTOR; + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; plh2xyz +; +; PURPOSE: +; Convert from geodetic coordinates (as specified by WGS84) to geocentric +; coordinates (RE = 6371.2 km) using an alternate method from wikipedia. +; +; CALLING SEQUENCE: +; [r,theta,phi] = geod2geoc(lat,lon,alt, rtp) +; +; Input Arguments: +; lat,lon - geodetic latitude and longitude [degrees N and E] +; alt - distance above sea level [km] +; +; Output Argument: +; rtp[3] - r: radial distance from center of Earth [RE], +; theta: angle from north pole [radians], +; phi: azimuthal angle [radians] +; Return Value: +; err - error code +; +;+----------------------------------------------------------------------------- +*/ + +int plh2xyz(double lat, double lon, double alt, double rtp[]) +{ + double a,b,f,ee,st,ct,sp,cp,N,Nac,x,y,z,r,t; + + a = 6378.1370; /* semi-major axis */ + f = 1./298.257223563; /* flattening */ + b = a*(1. -f); /* semi-minor axis */ + ee = (2. - f) * f; + + st = sin(lat*DTOR); + ct = cos(lat*DTOR); + sp = sin(lon*DTOR); + cp = cos(lon*DTOR); + + N = a / sqrt(1. - ee*st*st); + Nac = (N + alt) * ct; + + x = Nac * cp; + y = Nac * sp; + z = (N*(1. - ee)+alt) * st; + + r = sqrt(Nac*Nac + z*z); + t = acos(z/r); + + rtp[0] = r/RE; /* units of RE */ + rtp[1] = t; + rtp[2] = lon*DTOR; + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; geoc2geod +; +; PURPOSE: +; Convert from geocentric coordinates (RE = 6371.2 km) to geodetic +; coordinates (as specified by WGS84) using algorithm from wikipedia. +; +; CALLING SEQUENCE: +; [lat,lon,h] = geoc2geod(lat,lon,r, llh) +; +; Input Arguments: +; lat,lon - geocentric latitude and longitude [degrees N and E] +; r - radial distance from center of Earth [RE] +; +; Output Argument: +; llh[3] - geodetic latitude and longitude using WGS84 [radians], +; distance above sea level [km] +; Return Value: +; err - error code +; +;+----------------------------------------------------------------------------- +*/ + +int geoc2geod(double lat, double lon, double r, double llh[]) +{ + double a,f,b,ee,e4,aa, theta,phi, st,ct,sp,cp, x,y,z; + double k0i,pp,zeta,rho,s,rho3,t,u,v,w,kappa; + + a = 6378.1370; /* semi-major axis */ + f = 1./298.257223563; /* flattening */ + b = a*(1. -f); /* semi-minor axis */ + ee = (2. - f) * f; + e4 = ee*ee; + aa = a*a; + + theta = (90. - lat)*DTOR; + phi = lon * DTOR; + + st = sin(theta); + ct = cos(theta); + sp = sin(phi); + cp = cos(phi); + + x = r*RE * st * cp; + y = r*RE * st * sp; + z = r*RE * ct; + + k0i = 1. - ee; + pp = x*x + y*y; + zeta = k0i*z*z/aa; + rho = (pp/aa + zeta - e4)/6.; + s = e4*zeta*pp/(4.*aa); + rho3 = rho*rho*rho; + t = pow(rho3 + s + sqrt(s*(s+2*rho3)), 1./3.); + u = rho + t + rho*rho/t; + v = sqrt(u*u + e4*zeta); + w = ee*(u + v - zeta)/(2.*v); + kappa = 1. + ee*(sqrt(u+v+w*w) + w)/(u + v); + + llh[0] = atan2(z*kappa,sqrt(pp))/DTOR; + llh[1] = lon; + llh[2] = sqrt(pp + z*z*kappa*kappa)/ee * (1./kappa - k0i); + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_Newval +; +; PURPOSE: +; Advance position along magnetic field line by one step, i.e., +; numerical field-line tracing. +; +; CALLING SEQUENCE: +; k = AACGM_v2_Newval(xyz, dir, ds) +; +; Input Arguments: +; xyz - Cartesian position +; dir - direction along field-line to trace +; ds - stepsize to take +; +; Return value: +; k[3] - rate functions evaluated +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_Newval(double xyz[], int idir, double ds, double k[]) { + int j; + double rtp[3], brtp[3], bxyz[3]; + double bmag; + + car2sph(xyz, rtp); /* convert to spherical coords */ + IGRF_compute(rtp, brtp); /* compute the IGRF field here */ + bspcar(rtp[1],rtp[2], brtp, bxyz); /* convert field to Cartesian */ + + bmag = sqrt(bxyz[0]*bxyz[0] + bxyz[1]*bxyz[1] + bxyz[2]*bxyz[2]); + for (j=0; j<3; j++) k[j] = ds*idir*bxyz[j]/bmag; + + return (0); +} + +/*----------------------------------------------------------------------------- +; +; NAME: +; AACGM_v2_RK45 +; +; PURPOSE: +; Advance position along magnetic field line by one step, i.e., +; numerical field-line tracing using either a fixed stepsize RK4 method +; or a Runge-Kutta-Fehlberg adaptive stepsize ODE solver. +; +; CALLING SEQUENCE: +; AACGM_v2_RK45, xyz, dir, ds, eps, fixed=fixed, max_ds=max_ds, RRds=RRds +; +; Input Arguments: +; xyz - Cartesian position +; dir - direction along field-line to trace +; ds - stepsize to take +; +; Keywords: +; fixed - set this keyword to do RK4 method with stepsize ds +; max_ds - maximum stepsize that is allowed, in units of RE +; RRds - set to use a maximum stepsize that is proportional +; to cube of the distance from the origin. +; +; Return Value: +; err - error code +; +; NOTES: +; +; position variables (x,y,z) are modified directly +; +; HISTORY: +; +; Revision 1.0 150122 SGS initial version +; +;+----------------------------------------------------------------------------- +*/ + +int AACGM_v2_RK45(double xyz[], int idir, double *ds, double eps, int code) { + int k; + double bmag,rr,delt; + double k1[3],k2[3],k3[3],k4[3],k5[3],k6[3], w1[3],w2[3]; + double rtp[3], brtp[3], bxyz[3]; + double xyztmp[3]; + +/*function test_aacgm_rk45, x,y,z, idir, ds, eps, noadapt=noadapt, $ + * max_ds=max_ds, RRds=RRds*/ + +/* + ; if noadapt is set then just do straight RK4 and ds is spatial step size + ; in kilometers + ; default is to do adapative step size where eps is error in km + ; set max_ds to the maximum step size (in RE) to prevent too large step +*/ + /* convert position to spherical coords */ + car2sph(xyz, rtp); + + /* compute IGRF field in spherical coords */ + IGRF_compute(rtp, brtp); + + /* convert field from spherical coords to Cartesian */ + bspcar(rtp[1],rtp[2], brtp, bxyz); + + /* magnitude of field to normalize vector */ + bmag = sqrt(bxyz[0]*bxyz[0] + bxyz[1]*bxyz[1] + bxyz[2]*bxyz[2]); + + if (code == 0) { /* no adaptive stepping */ + /**************\ + * RK4 Method * + \**************/ + for (k=0;k<3;k++) k1[k] = (*ds)*idir*bxyz[k]/bmag; + for (k=0;k<3;k++) xyztmp[k] = xyz[k] + .5*k1[k]; + AACGM_v2_Newval(xyztmp,idir,*ds, k2); + for (k=0;k<3;k++) xyztmp[k] = xyz[k] + .5*k2[k]; + AACGM_v2_Newval(xyztmp,idir,*ds, k3); + for (k=0;k<3;k++) xyztmp[k] = xyz[k] + k3[k]; + AACGM_v2_Newval(xyztmp,idir,*ds, k4); + + for (k=0; k<3; k++) + xyz[k] += (k1[k] + k2[k]+k2[k] + k3[k]+k3[k] + k4[k])/6.; + } else { + + /************************\ + * Adaptive RK45 method * + \************************/ + rr = eps+1; /* just to get into the loop */ + while (rr > eps) { + for (k=0;k<3;k++) k1[k] = (*ds)*idir*bxyz[k]/bmag; + for (k=0;k<3;k++) xyztmp[k] = xyz[k] + k1[k]/4.; + AACGM_v2_Newval(xyztmp,idir,*ds, k2); + for (k=0;k<3;k++) xyztmp[k] = xyz[k] + (3.*k1[k] + 9.*k2[k])/32.; + AACGM_v2_Newval(xyztmp,idir,*ds, k3); + for (k=0;k<3;k++) xyztmp[k] = xyz[k] + (1932.*k1[k] - 7200.*k2[k] + + 7296.*k3[k])/2197.; + AACGM_v2_Newval(xyztmp,idir,*ds, k4); + for (k=0;k<3;k++) + xyztmp[k] = xyz[k] + 439.*k1[k]/216. - 8.*k2[k] + + 3680.*k3[k]/513. - 845.*k4[k]/4104.; + AACGM_v2_Newval(xyztmp,idir,*ds, k5); + for (k=0;k<3;k++) + xyztmp[k] = xyz[k] - 8.*k1[k]/27. + 2.*k2[k] - 3544.*k3[k]/2565. + + 1859.*k4[k]/4104. - 11.*k5[k]/40.; + AACGM_v2_Newval(xyztmp,idir,*ds, k6); + + rr = 0.; + for (k=0;k<3;k++) { + w1[k] = xyz[k] + 25.*k1[k]/216. + 1408.*k3[k]/2565. + + 2197.*k4[k]/4104. - k5[k]/5.; + w2[k] = xyz[k] + 16.*k1[k]/135. + 6656.*k3[k]/12825. + + 28561.*k4[k]/56430. - 9.*k5[k]/50. + + 2.*k6[k]/55.; + rr += (w1[k]-w2[k])*(w1[k]-w2[k]); + } + rr = sqrt(rr)/(*ds); + + if (fabs(rr) > 1e-16) { + delt = 0.84 *pow(eps/rr,0.25); /* this formula sucks because I have + no it where it came from. + Obviously it involves factors in + the LTEs of the two methods, but + I cannot find them written down + anywhere. */ + /*newds = ds * delt; + //ds = newds;*/ + *ds *= delt; + + /* maximum stepsize is fixed to max_ds in units of Re */ + /*if keyword_set(max_ds) then ds = min([max_ds,ds])*/ + /* maximum stepsize is r^2 * 1km, where r is in units of Re */ + /*if keyword_set(RRds) then ds = min([50*r*r*r/RE, ds])*/ + *ds = MIN(50*rtp[0]*rtp[0]*rtp[0]/RE, *ds); + } /* otherwise leave the stepsize alone */ + } + + /* we use the RK4 solution */ + for (k=0;k<3;k++) xyz[k] = w1[k]; + /* + ; I would assume that using the higher order RK5 method is better, but + ; there is the suggestion that using the RK4 solution guarantees accuracy + ; while the RK5 does not. Apparently some texts are now suggesting using + ; the RK5 solution... + for (k=0;k<3;k++) xyz[k] = w2[k]; + */ + } + + return (0); +} + diff --git a/c_aacgmv2/src/mlt_v2.c b/c_aacgmv2/src/mlt_v2.c new file mode 100644 index 00000000..9f3da9c5 --- /dev/null +++ b/c_aacgmv2/src/mlt_v2.c @@ -0,0 +1,316 @@ + +#include +#include +#include +#include "aacgmlib_v2.h" +#include "rtime.h" +#include "astalg.h" + +#ifndef NAN +#define NAN sqrt(-1) +#endif + +/* Macro infinite can't be defined, limit to NAN */ +#if defined(_WIN32) || defined(_WIN64) +#define isfinite(x) 1 ? x != NAN : 0 +#endif + +/*----------------------------------------------------------------------------- + MLT functions for use with AACGM-v2 +; +; C user functions intended to compute the magnetic local time at a given +; time and location given by AACGM-v2 coordinates. +; +; Code based on legacy routines authored by Kile Baker, Simon Wing and Robin +; Barnes. +; +; Significant changes include: +; +; - discontinued use of common block used for interpolation between two values +; separated by 10 minutes. +; +; - MLT is based on the relative AACGM-v2 longitudes of the desired location to +; that of a reference location. The reference location is taken to be the +; subsolar point at 700 km altitude. The reference AACGM-v2 longitude is +; computed using AACGM-v2 coefficients, which act to interpolate through +; regions where AACGM coordinates are undefined, thereby providing a +; mechanism for defining the reference longitude in a consistent manner. +; Differences with values determined at 0 km altitude (where defined) are +; typically <1 minute and always <5 minutes (in MLT). + +; 20170601 SGS v1.2 MLTConvert_v2 now calls AACGM_v2_SetDateTime() if the +; AACGM-v2 date/time is not currently set OR if the +; date/time passed into one of the public functions differs +; from the AACGM-v2 date/time by more than 30 days. In each +; case the AACGM-v2 coefficients are loaded and interpolated +; which could impact other calls to AACGM_v2_Convert() if +; the date/time is not reset. +; 20180314 AGB v1.2a Changed inv_MLTConvert_v2 to call AACGM_v2_SetDateTime() if +; the AACGM-v2 date/time is not currently set OR if the +; date/time passed into one of the public functions differs +; from the AACGM-v2 date/time by more than 30 days. This way +; it has the same behaviour as MLTConvert_v2 + +; Changes + + Combined MLTAst and MLTAst1 + + Removed *mlson variable since it is only used for doing linear + interpolation between two periods separated by 10 minutes. + +; +; Public Functions: +; ----------------- +; +; mlt = MLTConvertYMDHMS_v2(yr,mo,dy,hr,mt,sc, mlon, igrf_filename); +; mlon = inv_MLTConvertYMDHMS_v2(yr,mo,dy,hr,mt,sc, mlt, igrf_filename); +; +; mlt = MLTConvertEpoch_v2(epoch, mlon, igrf_filename); +; mlon = inv_MLTConvertEpoch_v2(epoch, mlt, igrf_filename); +; +; mlt = MLTConvertYrsec_v2(yr,yrsec, mlon, igrf_filename); +; mlon = inv_MLTConvertYrsec_v2(yr,yrsec, mlt, igrf_filename); +; +; Private Functions: +; ------------------ +; +; mlt = MLTConvert_v2(yr,mo,dy,hr,mt,sc, mlon, root, igrf_filename); +; mlon = inv_MLTConvert_v2(yr,mo,dy,hr,mt,sc, mlt, root, igrf_filename); +; +; +*/ + +double mlon_ref = -1; + +struct { + int yr; + int mo; + int dy; + int hr; + int mt; + int sc; +} mlt_date = {-1,-1,-1,-1,-1,-1}; + +/* + * Accepts scalars but only recomputes mlon_ref if time has changed from last + * call so computation is fast(er) for multiple calls at same date/time + * + * No options here. Use IDL version for development + * + */ +double MLTConvert_v2(int yr, int mo, int dy, int hr, int mt ,int sc, + double mlon, char *root, char *igrf_filename) +{ + int err; + int ayr,amo,ady,ahr,amt,asc,adyn; + double dd,jd,eqt,dec,ut,at; + double slon,mlat,r; + double hgt,aacgm_mlt; + double ajd; + + err = 0; + AACGM_v2_GetDateTime(&ayr, &amo, &ady, &ahr, &amt, &asc, &adyn); + if (ayr < 0) { + /* AACGM date/time not set so set it to the date/time passed in */ + err = AACGM_v2_SetDateTime(yr,mo,dy,hr,mt,sc, root); + if (err != 0) return (err); + } else { + /* If date/time passed into function differs from AACGM data/time by more + * than 30 days, recompute the AACGM-v2 coefficients */ + ajd = TimeYMDHMSToJulian(ayr,amo,ady,ahr,amt,asc); + jd = TimeYMDHMSToJulian(yr,mo,dy,hr,mt,sc); + if (fabs(jd-ajd) > 30.0) { + err = AACGM_v2_SetDateTime(yr,mo,dy,hr,mt,sc, root); + } + if (err != 0) return (err); + } + +/* check for bad input, which can come from undefined region, and return NAN */ + if (!isfinite(mlon)) { + return (NAN); + } + + hgt = 700.0; /* AACGM-v2 coefficients are defined everywhere above this + * altitude. */ + + if (mlt_date.yr != yr || mlt_date.mo != mo || mlt_date.dy != dy || + mlt_date.hr != hr || mlt_date.mt != mt || mlt_date.sc != sc) { + /* date/time has changed so recompute */ + mlt_date.yr = yr; + mlt_date.mo = mo; + mlt_date.dy = dy; + mlt_date.hr = hr; + mlt_date.mt = mt; + mlt_date.sc = sc; + + /* compute corrected time */ + dd = AstAlg_dday(dy,hr,mt,sc); + jd = AstAlg_jde(yr,mo,dd); + eqt = AstAlg_equation_of_time(jd); + dec = AstAlg_solar_declination(jd); + ut = hr*3600. + mt*60. + sc; + at = ut + eqt*60.; + + /* comparision with IDL version is exact + printf("\n\n"); + printf("dd = %20.12lf\n", dd); + printf("jd = %20.12lf\n", jd); + printf("eqt = %20.12lf\n", eqt); + printf("dec = %20.12lf\n", dec); + printf("\n\n"); + */ + + /* compute reference longitude */ + slon = (43200.-at)*15./3600.; /* subsolar point */ + + /* compute AACGM-v2 coordinates of reference point */ + err = AACGM_v2_Convert(dec, slon, hgt, &mlat, &mlon_ref, &r, G2A, + igrf_filename); + + /* check for error: this should NOT happen... */ + if (err != 0) { + err = -99; + return (NAN); + } + } +/*printf("** %lf\n", mlon_ref);*/ + + aacgm_mlt = 12.0 + (mlon - mlon_ref)/15.0; /* MLT based on subsolar point */ + + /* comparision with IDL version is exact */ + + while (aacgm_mlt > 24.0) aacgm_mlt -= 24.0; + while (aacgm_mlt < 0.0) aacgm_mlt += 24.0; + + return (aacgm_mlt); +} + +/* inverse function: MLT to AACGM-v2 magnetic longitude */ +double inv_MLTConvert_v2(int yr, int mo, int dy, int hr, int mt ,int sc, + double mlt, char *root, char *igrf_filename) +{ + int err; + int ayr,amo,ady,ahr,amt,asc,adyn; + double dd,jd,eqt,dec,ut,at,ajd; + double slon,mlat,r; + double hgt,aacgm_mlon; + + err = 0; + AACGM_v2_GetDateTime(&ayr, &amo, &ady, &ahr, &amt, &asc, &adyn); + if (ayr < 0) { + /* AACGM date/time not set so set it to the date/time passed in */ + err = AACGM_v2_SetDateTime(yr,mo,dy,hr,mt,sc,root); + if (err != 0) return (err); + } else { + /* If date/time passed into function differs from AACGM data/time by more + * than 30 days, recompute the AACGM-v2 coefficients */ + ajd = TimeYMDHMSToJulian(ayr,amo,ady,ahr,amt,asc); + jd = TimeYMDHMSToJulian(yr,mo,dy,hr,mt,sc); + if (fabs(jd-ajd) > 30.0) { + err = AACGM_v2_SetDateTime(yr,mo,dy,hr,mt,sc, root); + } + if (err != 0) return (err); + } + +/* check for bad input, which should not happen for MLT, and return NAN */ + if (!isfinite(mlt)) { + return (NAN); + } + + hgt = 700.; /* AACGM-v2 coefficients are defined everywhere above this + * altitude. */ + + if (mlt_date.yr != yr || mlt_date.mo != mo || mlt_date.dy != dy || + mlt_date.hr != hr || mlt_date.mt != mt || mlt_date.sc != sc) { + /* date/time has changed so recompute */ + mlt_date.yr = yr; + mlt_date.mo = mo; + mlt_date.dy = dy; + mlt_date.hr = hr; + mlt_date.mt = mt; + mlt_date.sc = sc; + + /* compute corrected time */ + dd = AstAlg_dday(dy,hr,mt,sc); + jd = AstAlg_jde(yr,mo,dd); + eqt = AstAlg_equation_of_time(jd); + dec = AstAlg_solar_declination(jd); + ut = hr*3600. + mt*60. + sc; + at = ut + eqt*60.; + + /* compute reference longitude */ + slon = (43200.-at)*15./3600.; /* subsolar point */ + + /* compute AACGM-v2 coordinates of reference point */ + err = AACGM_v2_Convert(dec, slon, hgt, &mlat, &mlon_ref, &r, G2A, + igrf_filename); + + /* check for error: this should NOT happen... */ + if (err != 0) { + err = -99; + return (NAN); + } + } + + aacgm_mlon = (mlt - 12.)*15. + mlon_ref; /* mlon based on subsolar point */ + + while (aacgm_mlon > 180.) aacgm_mlon -= 360.; + while (aacgm_mlon < -180.) aacgm_mlon += 360.; + + return (aacgm_mlon); +} + +double MLTConvertYMDHMS_v2(int yr, int mo, int dy, int hr, int mt, int sc, + double mlon, char *root, char *igrf_filename) +{ + return (MLTConvert_v2(yr,mo,dy,hr,mt,sc,mlon,root,igrf_filename)); +} + +double inv_MLTConvertYMDHMS_v2(int yr, int mo, int dy, int hr, int mt, int sc, + double mlt, char *root, char *igrf_filename) +{ + return (inv_MLTConvert_v2(yr,mo,dy,hr,mt,sc,mlt,root,igrf_filename)); +} + +double MLTConvertYrsec_v2(int yr,int yr_sec, double mlon, char *root, + char *igrf_filename) +{ + int mo,dy,hr,mt,sc; + + TimeYrsecToYMDHMS(yr_sec,yr,&mo,&dy,&hr,&mt,&sc); + + return (MLTConvert_v2(yr,mo,dy,hr,mt,sc,mlon,root,igrf_filename)); +} + +double inv_MLTConvertYrsec_v2(int yr, int yr_sec, double mlt, char *root, + char *igrf_filename) +{ + int mo,dy,hr,mt,sc; + + TimeYrsecToYMDHMS(yr_sec,yr,&mo,&dy,&hr,&mt,&sc); + + return (inv_MLTConvert_v2(yr,mo,dy,hr,mt,sc,mlt,root,igrf_filename)); +} + +double MLTConvertEpoch_v2(double epoch, double mlon, char *root, + char *igrf_filename) +{ + int yr,mo,dy,hr,mt; + double sc; + + TimeEpochToYMDHMS(epoch,&yr,&mo,&dy,&hr,&mt,&sc); + + return (MLTConvert_v2(yr,mo,dy,hr,mt,(int)sc, mlon, root, igrf_filename)); +} + +double inv_MLTConvertEpoch_v2(double epoch, double mlt, char *root, + char *igrf_filename) +{ + int yr,mo,dy,hr,mt; + double sc; + + TimeEpochToYMDHMS(epoch,&yr,&mo,&dy,&hr,&mt,&sc); + + return (inv_MLTConvert_v2(yr,mo,dy,hr,mt,(int)sc, mlt, root, igrf_filename)); +} + diff --git a/c_aacgmv2/src/rtime.c b/c_aacgmv2/src/rtime.c new file mode 100644 index 00000000..6ea3918d --- /dev/null +++ b/c_aacgmv2/src/rtime.c @@ -0,0 +1,264 @@ +/* rtime.c + ======= + Author: R.J.Barnes + A.G.Burrell +*/ + +/* + (c) 2010 JHU/APL & Others - Please Consult + LICENSE.superdarn-rst.3.2-beta-4-g32f7302.txt for more information. + Also LICENSE +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "rtime.h" + +#define DAY_SEC 86400 + +/* For windows, define setenv and unsetenv */ +#if defined(_WIN32) || defined(_WIN64) +int setenv(const char *name, const char *value, int overwrite) +{ + int setsize; + char envset[1000], *testenv; + + if(!overwrite) + { + testenv = getenv(name); + setsize = strlen(testenv); + if(setsize > 0) return -1; + } + + sprintf(envset, "%s=%s", name, value); + return putenv(envset); +} + +int unsetenv(const char *name) +{ + extern char **environ; + char **ep, **sp; + size_t len; + + if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) + { + errno = EINVAL; + return -1; + } + + len = strlen(name); + + for (ep = environ; *ep != NULL; ) + { + if (strncmp(*ep, name, len) == 0 && (*ep)[len] == '=') + { + /* Remove found entry by shifting all successive entries */ + /* back one element */ + for (sp = ep; *sp != NULL; sp++) + *sp = *(sp + 1); + + /* Continue around the loop to further instances of 'name' */ + + } + else ep++; + } + + return 0; +} +#endif + +int TimeYMDHMSToYrsec(int yr,int mo,int dy,int hr,int mn,int sc) { + + time_t clock; + struct tm tm; + char *tz; + + memset(&tm,0,sizeof(struct tm)); + tm.tm_year=yr-1900; + tm.tm_mon=0; + tm.tm_mday=1; + tm.tm_hour=0; + tm.tm_min=0; + tm.tm_sec=0; + + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + clock=mktime(&tm); + + memset(&tm,0,sizeof(struct tm)); + tm.tm_year=yr-1900; + tm.tm_mon=mo-1; + tm.tm_mday=dy; + tm.tm_hour=hr; + tm.tm_min=mn; + tm.tm_sec=sc; + clock=mktime(&tm)-clock; + + + if (tz) setenv("TZ", tz, 1); + else unsetenv("TZ"); + tzset(); + + return (int) clock; +} + +void TimeYrsecToYMDHMS(int yrsec,int yr,int *mo,int *dy,int *hr,int *mn, + int *sc) { + + + time_t clock; + struct tm tmyr; + struct tm *tm; + char *tz; + + memset(&tmyr,0,sizeof(struct tm)); + tmyr.tm_year=yr-1900; + tmyr.tm_mon=0; + tmyr.tm_mday=1; + tmyr.tm_hour=0; + tmyr.tm_min=0; + tmyr.tm_sec=0; + + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + + clock=mktime(&tmyr); + if (tz) setenv("TZ", tz, 1); + else unsetenv("TZ"); + tzset(); + + clock=clock+yrsec; + tm=gmtime(&clock); + + *mo=tm->tm_mon+1; + *dy=tm->tm_mday; + *hr=tm->tm_hour; + *mn=tm->tm_min; + *sc=tm->tm_sec; +} + + +double TimeYMDHMSToEpoch(int yr,int mo,int dy,int hr,int mn,double sc) { + + time_t clock; + struct tm tm; + char *tz; + + memset(&tm,0,sizeof(struct tm)); + tm.tm_year=yr-1900; + tm.tm_mon=mo-1; + tm.tm_mday=dy; + tm.tm_hour=hr; + tm.tm_min=mn; + tm.tm_sec=floor(sc); + + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + clock=mktime(&tm); + if (tz) setenv("TZ", tz, 1); + else unsetenv("TZ"); + tzset(); + + return clock+(sc-floor(sc)); +} + +void TimeEpochToYMDHMS(double tme,int *yr,int *mo,int *dy,int *hr,int *mn, + double *sc) { + time_t clock; + struct tm *tm; + + clock=floor(tme); + tm=gmtime(&clock); + + *yr=tm->tm_year+1900; + *mo=tm->tm_mon+1; + *dy=tm->tm_mday; + *hr=tm->tm_hour; + *mn=tm->tm_min; + *sc=tm->tm_sec+(tme-floor(tme)); +} + +double TimeYMDHMSToJulian(int yr,int mo,int dy,int hr,int mt,double sc) { + + int A,B,i; + double jdoy; + double dfrac; + yr=yr-1; + i=yr/100; + A=i; + i=A/4; + B=2-A+i; + i=365.25*yr; + i+=30.6001*14; + jdoy=i+1720994.5+B; + + + dfrac=1+TimeYMDHMSToYrsec(yr+1,mo,dy,hr,mt,sc)/DAY_SEC; + + return jdoy+dfrac; + +} + + +int TimeJulianToYMDHMS(double jd,int *yr,int *mo, + int *dy,int *hr,int *mt,double *sc) { + + int Z,month; + int hour,minute; + + double A,B,C,D,E,F,alpha,day,year,factor,second; + + factor=0.5/DAY_SEC/1000; + F=(jd+0.5)-floor(jd+0.5); + if ((F+factor)>=1.0) { + jd=jd+factor; + F=0.0; + } + + Z=floor(jd+0.5); + + if (Z<2299161) A=Z; + else { + alpha=floor((Z-1867216.25)/36524.25); + A=Z+1+alpha-floor(alpha/4); + } + + B=A+1524; + C=floor((B-122.1)/365.25); + D=floor(365.25*C); + E=floor((B-D)/30.6001); + day=B-D-floor(30.6001*E)+F; + + if (E<13.5) month=floor(E-0.5); + else month=floor(E-12.5); + if (month>2.5) year=C-4716; + else year=C-4715; + + + + *yr=(int) year; + *mo=month; + *dy=(int) floor(day); + + /* okay now use the residual of the day to work out the time */ + + A=(day-floor(day))*DAY_SEC; + + hour=(int) (A/3600.0); + minute=(int) ((A-hour*3600)/60); + second=A-hour*3600-minute*60; + + *hr=hour; + *mt=minute; + *sc=second; + return 0; +} + diff --git a/c_aacgmv2/src/test_aacgm.c b/c_aacgmv2/src/test_aacgm.c new file mode 100644 index 00000000..443be9bc --- /dev/null +++ b/c_aacgmv2/src/test_aacgm.c @@ -0,0 +1,265 @@ +#include +#include +#include "aacgmlib_v2.h" +#include "mlt_v2.h" + +#define CR printf("\n") +#define DEBUG 1 + +void next(void); +void line(char ch, int n); + +int main(int argc, char *argv[]) +{ + double lat,lon,hgt; + double h, mlt_c, mlt_t; + double rtp[3]; + double mlat,mlon,r; + int k, err, npts; + int yr, mo, dy, hr, mt, sc; + + /* Allow input of AACGM and IGRF database files instead of env variables */ + char root[1000], igrf_filename[1000]; + + if(argc > 1) + { + memcpy(root, argv[1], strlen(argv[1])+1); + root[strlen(argv[1])] = '\0'; + } + else root[0] = '\0'; + + if(argc > 2) + { + memcpy(igrf_filename, argv[2], strlen(argv[2])+1); + igrf_filename[strlen(argv[2])] = '\0'; + } + else igrf_filename[0] = '\0'; + /* End of reading optional input */ + + line('=',80); + printf("\nAACGM-v2 Test Program\n\n"); + line('=',80); + CR; + + /* compute AACGM-v2 lat/lon with no time specified */ + printf("TEST: no date/time (this will return an error.)\n"); + lat = 45.5; + lon = -23.5; + hgt = 1135.; + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A, igrf_filename); + if (err == 0) + { + printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); + printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); + printf("\n\n"); + } + + #if (DEBUG > 1) + next(); + + printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d (will fail)\n", + 1850,1,22,0,0,0); + AACGM_v2_SetDateTime(1850, 1, 22, 0,0,0); /* this should fail */ + printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", + 1900,1,22,0,0,0); + AACGM_v2_SetDateTime(1900, 1, 22, 0,0,0); /* this is valid */ + printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d (will fail)\n", 2020,1,22,0,0,0); + AACGM_v2_SetDateTime(2020, 1, 22, 0,0,0); /* this shoudl fail */ + printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", + 2019,1,22,0,0,0); + AACGM_v2_SetDateTime(2019, 1, 22, 0,0,0); /* this is valid */ + next(); + #endif + + yr = 2014; + mo = 3; + dy = 22; + hr = 3; + mt = 11; + sc = 0; + printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", + yr, mo, dy, hr, mt, sc); + CR; + + /* set date and time */ + AACGM_v2_SetDateTime(yr, mo, dy, hr, mt, sc, root); + + lat = 45.5; + lon = -23.5; + hgt = 1135.; + + printf("TEST: geographic to AACGM-v2\n"); + /* compute AACGM lat/lon */ + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A, igrf_filename); + + printf(" GLAT GLON HEIGHT MLAT MLON R\n"); + printf(" %lf %lf %lf %lf %lf %lf", lat,lon,hgt, mlat,mlon,r); + printf("\n\n"); + #if (DEBUG > 1) + next(); + #endif + + printf("TEST: AACGM-v2 to geographic\n"); + /* do the inverse: A2G */ + hgt = (r-1.)*RE; + err = AACGM_v2_Convert(mlat,mlon,hgt, &lat,&lon, &h, A2G, igrf_filename); + + printf(" MLAT MLON HEIGHT GLAT GLON "); + printf("HEIGHT\n %lf %lf %lf %lf %lf %lf", mlat,mlon,hgt, lat,lon,h); + printf("\n\n"); + #if (DEBUG > 1) + next(); + #endif + + /* same thing but using field-line tracing */ + lat = 45.5; + lon = -23.5; + hgt = 1135.; + + printf("Do the same thing but use field-line tracing\n\n"); + printf("TEST: geographic to AACGM-v2 (TRACE)\n"); + /* compute AACGM lat/lon */ + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE,igrf_filename); + + printf(" GLAT GLON HEIGHT MLAT MLON R\n"); + printf(" %lf %lf %lf %lf %lf %lf", lat,lon,hgt, mlat,mlon,r); + printf("\n\n"); + #if (DEBUG > 1) + next(); + #endif + + printf("TEST: AACGM-v2 to geographic (TRACE)\n"); + /* do the inverse: A2G */ + hgt = (r-1.)*RE; + err = AACGM_v2_Convert(mlat,mlon,hgt, &lat,&lon, &h, A2G|TRACE,igrf_filename); + + printf(" MLAT MLON HEIGHT GLAT GLON HEIGHT\n"); + printf(" %lf %lf %lf %lf %lf %lf", mlat,mlon,hgt, lat,lon,h); + printf("\n\n"); + #if (DEBUG > 1) + next(); + #endif + + /* compare tracing to coefficients */ + lat = 45.5; + lon = -23.5; + hgt = 150.; + + /* set date and time */ + AACGM_v2_SetDateTime(2018,1,1,0,0,0,root); + + #if (DEBUG >1) + printf("TEST: geographic to AACGM-v2; coefficients\n"); + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A, igrf_filename); + printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); + printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); + next(); + #endif + + #if (DEBUG > 1) + printf("TEST: geographic to AACGM-v2; tracing\n"); + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE,igrf_filename); + printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); + printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); + next(); + #endif + + #if (DEBUG > 1) + printf("TEST: geographic to AACGM-v2; too high\n"); + hgt = 2500; + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A, igrf_filename); + printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); + printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); + next(); + #endif + + #if (DEBUG > 1) + printf("TEST: geographic to AACGM-v2; trace high\n"); + hgt = 7500; + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE,igrf_filename); + printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); + printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); + next(); + #endif + + #if (DEBUG > 1) + printf("TEST: geographic to AACGM-v2; coefficient high\n"); + hgt = 7500; + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|BADIDEA, + igrf_filename); + printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); + printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); + next(); + #endif + + line('-',80); + CR; + printf("Testing MLT\n"); + line('-',80); + CR; + + lat = 77.; + lat = 37.; + lon = -88.; + hgt = 300.; + + yr = 2003; + mo = 5; + dy = 17; + hr = 7; + mt = 53; + sc = 16; + + /* compute AACGM lat/lon */ + AACGM_v2_SetDateTime(yr, mo, dy, hr, mt, sc, root); + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE,igrf_filename); + mlt_t = MLTConvertYMDHMS_v2(yr,mo,dy,hr,mt,sc,mlon,root,igrf_filename); + printf(" GLAT GLON HEIGHT MLAT MLON MLT\n"); + printf("TRACE %lf %lf %lf %lf %lf %lf", lat,lon,hgt, mlat,mlon,mlt_t); + printf("\n"); + + err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A, igrf_filename); + mlt_c = MLTConvertYMDHMS_v2(yr,mo,dy,hr,mt,sc,mlon,root,igrf_filename); + printf("COEFF %lf %lf %lf %lf %lf %lf", lat,lon,hgt, mlat,mlon,mlt_c); + printf("\n\n"); + + npts = 20; + printf("\n"); + printf("Array:\n"); + for (k=0; k 1000000: - progress[1] = progress[0] - print("Downloaded {:,}/{:,} ...".format(progress[1], total)) + progress_total = count * size + if progress_total - progress > 1000000: + progress = progress_total + print("Downloaded {:,}/{:,} ...".format(progress, total)) dest, _ = urlretrieve(url, path, reporthook=report) return dest def install_python(version, arch, home): - print("Installing Python", version, "for", arch, "bit architecture to", home) + print("Installing Python", version, "for", arch, "bit architecture to", + home) if exists(home): return @@ -112,6 +119,8 @@ def install_packages(home, *packages): if __name__ == "__main__": - install_python(environ['PYTHON_VERSION'], environ['PYTHON_ARCH'], environ['PYTHON_HOME']) + install_python(environ['PYTHON_VERSION'], environ['PYTHON_ARCH'], + environ['PYTHON_HOME']) install_pip(environ['PYTHON_HOME']) - install_packages(environ['PYTHON_HOME'], "setuptools>=18.0.1", "wheel", "tox", "virtualenv>=13.1.0") + install_packages(environ['PYTHON_HOME'], "setuptools>=18.0.1", "wheel", + "tox", "virtualenv>=13.1.0") diff --git a/ci/appveyor-download.py b/ci/appveyor-download.py index fa5e80d1..e8bc4634 100644 --- a/ci/appveyor-download.py +++ b/ci/appveyor-download.py @@ -16,8 +16,9 @@ def make_auth_headers(): """Make the authentication headers needed to use the Appveyor API.""" if not os.path.exists(".appveyor.token"): raise RuntimeError( - "Please create a file named `.appveyor.token` in the current directory. " - "You can get the token from https://ci.appveyor.com/api-token" + "Please create a file named `.appveyor.token` in the current " + " directory. You can get the token from " + "https://ci.appveyor.com/api-token" ) with open(".appveyor.token") as f: token = f.read().strip() @@ -35,7 +36,8 @@ def make_url(url, **kwargs): def get_project_build(account_project): """Get the details of the latest Appveyor build.""" - url = make_url("/projects/{account_project}", account_project=account_project) + url = make_url("/projects/{account_project}", + account_project=account_project) response = requests.get(url, headers=make_auth_headers()) return response.json() @@ -47,7 +49,8 @@ def download_latest_artifacts(account_project): print("Build {0[build][version]}, {1} jobs: {0[build][message]}".format(build, len(jobs))) for job in jobs: name = job['name'].partition(':')[2].split(',')[0].strip() - print(" {0}: {1[status]}, {1[artifactsCount]} artifacts".format(name, job)) + print(" {0}: {1[status]}, {1[artifactsCount]} artifacts".format(name, + job)) url = make_url("/buildjobs/{jobid}/artifacts", jobid=job['jobId']) response = requests.get(url, headers=make_auth_headers()) @@ -96,10 +99,10 @@ def unpack_zipfile(filename): ensure_dirs(name) z.extract(name) -parser = argparse.ArgumentParser(description='Download artifacts from AppVeyor.') -parser.add_argument('name', - metavar='ID', - help='Project ID in AppVeyor. Example: ionelmc/python-nameless') +desc = 'Download artifacts from AppVeyor.' +parser = argparse.ArgumentParser(description=desc) +desc = 'Project ID in AppVeyor. Example: ionelmc/python-nameless' +parser.add_argument('name', metavar='ID', help=desc) if __name__ == "__main__": # import logging diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 466bb8fe..aec07822 100644 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -4,61 +4,62 @@ import os import sys -from os.path import exists -from os.path import join -from os.path import dirname -from os.path import abspath - if __name__ == "__main__": - base_path = dirname(dirname(abspath(__file__))) + base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) print("Project path: {0}".format(base_path)) - env_path = join(base_path, ".tox", "bootstrap") + env_path = os.path.join(base_path, ".tox", "bootstrap") if sys.platform == "win32": - bin_path = join(env_path, "Scripts") + bin_path = os.path.join(env_path, "Scripts") else: - bin_path = join(env_path, "bin") - if not exists(env_path): + bin_path = os.path.join(env_path, "bin") + if not os.path.exists(env_path): import subprocess print("Making bootstrap env in: {0} ...".format(env_path)) try: subprocess.check_call(["virtualenv", env_path]) except Exception: - subprocess.check_call([sys.executable, "-m", "virtualenv", env_path]) + subprocess.check_call([sys.executable, "-m", "virtualenv", + env_path]) print("Installing `jinja2` and `matrix` into bootstrap environment ...") - subprocess.check_call([join(bin_path, "pip"), "install", "jinja2", "matrix"]) - activate = join(bin_path, "activate_this.py") - exec(compile(open(activate, "rb").read(), activate, "exec"), dict(__file__=activate)) + subprocess.check_call([os.path.join(bin_path, "pip"), "install", + "jinja2", "matrix"]) + activate = os.path.join(bin_path, "activate_this.py") + exec(compile(open(activate, "rb").read(), activate, "exec"), + dict(__file__=activate)) import jinja2 import matrix jinja = jinja2.Environment( - loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")), + loader=jinja2.FileSystemLoader(os.path.join(base_path, "ci", + "templates")), trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True ) - tox_environments = {} - for (alias, conf) in matrix.from_file(join(base_path, "setup.cfg")).items(): + tox_envs = {} + for (alias, conf) in matrix.from_file(os.path.join(base_path, + "setup.cfg")).items(): python = conf["python_versions"] - deps = conf["dependencies"] + #deps = conf["dependencies"] if "coverage_flags" in conf: - cover = {"false": False, "true": True}[conf["coverage_flags"].lower()] + cover = {"false": False, + "true": True}[conf["coverage_flags"].lower()] if "environment_variables" in conf: env_vars = conf["environment_variables"] - tox_environments[alias] = { + tox_envs[alias] = { "python": "python" + python if "py" not in python else python, - "deps": deps.split(), + #"deps": deps.split(), } if "coverage_flags" in conf: - tox_environments[alias].update(cover=cover) + tox_envs[alias].update(cover=cover) if "environment_variables" in conf: - tox_environments[alias].update(env_vars=env_vars.split()) + tox_envs[alias].update(env_vars=env_vars.split()) - for name in os.listdir(join("ci", "templates")): - with open(join(base_path, name), "w") as fh: - fh.write(jinja.get_template(name).render(tox_environments=tox_environments)) + for name in os.listdir(os.path.join("ci", "templates")): + with open(os.path.join(base_path, name), "w") as fh: + fh.write(jinja.get_template(name).render(tox_environments=tox_envs)) print("Wrote {}".format(name)) print("DONE.") diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml index 71bab06f..76753986 100644 --- a/ci/templates/.travis.yml +++ b/ci/templates/.travis.yml @@ -1,34 +1,26 @@ language: python -python: '3.5' +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" sudo: false env: global: LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - matrix: - - TOXENV=check -{% for env, config in tox_environments|dictsort %} - - TOXENV={{ env }}{% if config.cover %},extension-coveralls,coveralls,codecov{% endif %} - -{% endfor %} before_install: - python --version - uname -a - lsb_release -a install: - - pip install tox - - virtualenv --version - - easy_install --version - - pip --version - - tox --version + - pip install coveralls + - "python setup.py install" + - pip install tox-travis script: - - tox -v -after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat + - tox + - coverage run --source aacgmv2 -m py.test +after_sucess: coveralls notifications: email: on_success: never - on_failure: always -matrix: - allow_failures: - - env: "TOXENV=check" + on_failure: always \ No newline at end of file diff --git a/ci/templates/appveyor.yml b/ci/templates/appveyor.yml index d5e097ae..9af1d4ef 100644 --- a/ci/templates/appveyor.yml +++ b/ci/templates/appveyor.yml @@ -16,18 +16,6 @@ environment: PYTHON_HOME: C:\python27-x64 PYTHON_VERSION: '2.7' PYTHON_ARCH: '64' - - TOXENV: '3.3-buildonly-nocover' - TOXPYTHON: C:\python33\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '32' - - TOXENV: '3.3-buildonly-nocover' - TOXPYTHON: C:\python33-x64\python.exe - WINDOWS_SDK_VERSION: v7.1 - PYTHON_HOME: C:\python33-x64 - PYTHON_VERSION: '3.3' - PYTHON_ARCH: '64' - TOXENV: '3.4-buildonly-nocover' TOXPYTHON: C:\python34\python.exe WINDOWS_SDK_VERSION: v7.1 @@ -52,11 +40,23 @@ environment: PYTHON_HOME: C:\python35-x64 PYTHON_VERSION: '3.5' PYTHON_ARCH: '64' + - TOXENV: '3.6-buildonly-nocover' + TOXPYTHON: C:\python36\python.exe + WINDOWS_SDK_VERSION: v7.1 + PYTHON_HOME: C:\python36 + PYTHON_VERSION: '3.6' + PYTHON_ARCH: '32' + - TOXENV: '3.6-buildonly-nocover' + TOXPYTHON: C:\python36-x64\python.exe + WINDOWS_SDK_VERSION: v7.1 + PYTHON_HOME: C:\python36-x64 + PYTHON_VERSION: '3.6' + PYTHON_ARCH: '64' - TOXENV: check PYTHON_HOME: C:\Python27 PYTHON_VERSION: '2.7' PYTHON_ARCH: '32' -{% for env, config in tox_environments|dictsort %}{% if config.python in ('python2.6', 'python2.7', 'python3.3', 'python3.4', 'python3.5') and not config.cover %} +{% for env, config in tox_environments|dictsort %}{% if config.python in ('python2.6', 'python2.7', 'python3.4', 'python3.5', 'python3.6') and not config.cover %} - TOXENV: '{{ env }}{% if config.cover %},codecov{% endif %}' TOXPYTHON: C:\{{ config.python.replace('.', '') }}\python.exe {%- if config.python != 'python3.5' %} @@ -107,20 +107,12 @@ artifacts: matrix: allow_failures: - TOXENV: 'check' - - TOXENV: '3.3-np1.8-nocover' - - TOXENV: '3.3-np1.8,codecov' - - TOXENV: '3.4-np1.8-nocover' - - TOXENV: '3.4-np1.8,codecov' - - TOXENV: '3.5-np1.8-nocover' - - TOXENV: '3.5-np1.8,codecov' - - TOXENV: '3.5-np1.9-nocover' - - TOXENV: '3.5-np1.9,codecov' - - TOXENV: '3.5-np1.10-nocover' - - TOXENV: '3.5-np1.10,codecov' + - TOXENV: '3.6-nocover' + - TOXENV: '3.6,codecov' - TOXENV: '2.7-buildonly-nocover' - - TOXENV: '3.3-buildonly-nocover' - TOXENV: '3.4-buildonly-nocover' - TOXENV: '3.5-buildonly-nocover' + - TOXENV: '3.6-buildonly-nocover' ### To enable remote debugging uncomment this: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/ci/templates/tox.ini b/ci/templates/tox.ini index a5906ae3..06e96a34 100644 --- a/ci/templates/tox.ini +++ b/ci/templates/tox.ini @@ -16,7 +16,8 @@ passenv = * deps = pytest - pytest-capturelog + logbook + numpy commands = python setup.py clean --all build_ext --force --inplace {posargs:py.test -vv --ignore=src --doctest-glob='*.rst'} @@ -87,7 +88,6 @@ commands = coverage xml --ignore-errors codecov [] - [testenv:extension-coveralls] deps = cpp-coveralls @@ -148,12 +148,6 @@ deps = skip_install = true commands = -[testenv:3.3-buildonly-nocover] -basepython = {env:TOXPYTHON:python3.3} -deps = -skip_install = true -commands = - [testenv:3.4-buildonly-nocover] basepython = {env:TOXPYTHON:python3.4} deps = @@ -166,4 +160,10 @@ deps = skip_install = true commands = +[testenv:3.6-buildonly-nocover] +basepython = {env:TOXPYTHON:python3.6} +deps = +skip_install = true +commands = + diff --git a/docs/conf.py b/docs/conf.py index 4e33f6dc..55711c52 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,7 +3,6 @@ import os - extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', @@ -23,13 +22,14 @@ master_doc = 'index' project = u'AACGM-v2 Python library' year = u'2015' -author = u'Christer van der Meeren' +author = u'Angeline G. Burrell, Christer van der Meeren' copyright = '{0}, {1}'.format(year, author) -version = release = u'2.0.0' +version = release = u'2.4.0' # on_rtd is whether we are on readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -if not on_rtd: # only import and set the theme if we're building docs locally +# only import and set the theme if we're building docs locally +if not on_rtd: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/docs/installation.rst b/docs/installation.rst index acda0c34..c8fb34f5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -2,16 +2,22 @@ Installation ============ -This package requires NumPy, which you can install alone or as a part of SciPy. `Some Python distributions `_ come with NumPy/SciPy pre-installed. For Python distributions without NumPy/SciPy, Windows/Mac users should install `pre-compiled binaries of NumPy/SciPy `_, and Linux users may have NumPy/SciPy available in `their repositories `_. +This package requires NumPy, which you can install alone or as a part of SciPy. +`Some Python distributions `_ come with NumPy/SciPy pre-installed. For Python distributions +without NumPy/SciPy, Windows/Mac users should install +`pre-compiled binaries of NumPy/SciPy `_, and Linux users may have NumPy/SciPy +available in `their repositories `_. -When you have NumPy, install this package at the command line using ``pip`` [1]_:: +When you have NumPy, install this package at the command line using +``pip`` [1]_:: pip install aacgmv2 The package has been tested with the following setups (others might work, too): -* Windows (32/64 bit) and Linux (64 bit) -* Python 2.7, 3.3, 3.4 and 3.5 -* NumPy 1.8, 1.9, 1.10 +* Mac (64 bit), Windows (32/64 bit), and Linux (64 bit) +* Python 2.7, 3.4, 3.5, and 3.6 -.. [1] pip is included with Python 2 from v2.7.9 and Python 3 from v3.4. If you don't have pip, `get it here `_. \ No newline at end of file +.. [1] pip is included with Python 2 from v2.7.9 and Python 3 from v3.4. If you + don't have pip, + `get it here `_. diff --git a/docs/reference/_aacgmv2.rst b/docs/reference/_aacgmv2.rst index 46bfe215..67f53871 100644 --- a/docs/reference/_aacgmv2.rst +++ b/docs/reference/_aacgmv2.rst @@ -1,7 +1,7 @@ aacgmv2._aacgmv2 ================ -This submodule contains the interface to the AACGM-v2 C library. For the user-friendly wrapper, see :func:`aacgmv2.convert`. +This submodule contains the interface to the AACGM-v2 C library. For the user-friendly wrapper, see the functions in :py:module:`aacgmv2.wrapper`. .. automodule:: aacgmv2._aacgmv2 :members: diff --git a/docs/reference/aacgmv2.rst b/docs/reference/aacgmv2.rst index 26998c85..5bf1daaf 100644 --- a/docs/reference/aacgmv2.rst +++ b/docs/reference/aacgmv2.rst @@ -5,3 +5,10 @@ These functions are available when you ``import aacgmv2``. .. automodule:: aacgmv2 :members: + +.. automodule:: aacgmv2.wrapper + :members: + +.. automodule:: aacgmv2.deprecated + :members: + diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index ff526ee9..7249ee6d 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -3,9 +3,14 @@ Command-line interface .. highlight:: none -When you install this package you will get a command called ``aacgmv2``. It has two subcommands, ``convert`` and ``convert_mlt``, which correspond to the functions :py:func:`aacgmv2.convert` and :py:func:`aacgmv2.convert_mlt`. See the documentation for these functions for a more thorough explanation of arguments and behaviour. - -You can get help on the two commands by running ``aacgmv2 convert -h`` and ``aacgmv2 convert_mlt -h``. +When you install this package you will get a command called ``aacgmv2``. It has +two subcommands, ``convert`` and ``convert_mlt``, which correspond to the +functions :py:func:`aacgmv2.convert_latlon_arr` and +:py:func:`aacgmv2.convert_mlt`. See the documentation for these functions for a +more thorough explanation of arguments and behaviour. + +You can get help on the two commands by running ``aacgmv2 convert -h`` and +``aacgmv2 convert_mlt -h``. convert ------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 8edfbc77..51b6ed5d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ sphinx sphinxcontrib-napoleon numpydoc +sphinx_rtd_theme -e . diff --git a/docs/usage.rst b/docs/usage.rst index 28b78f7d..1841f011 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -7,36 +7,36 @@ Python library For full documentation of the functions, see :doc:`Reference → aacgmv2 `. - >>> from aacgmv2 import convert - >>> from datetime import date - >>> # geo to AACGM, single numbers - >>> mlat, mlon = convert(60, 15, 300, date(2013, 11, 3)) - >>> mlat - array(57.47207691280528) - >>> mlon - array(93.62138045643167) - >>> # AACGM to geo, mix arrays/numbers - >>> glat, glon = convert([90, -90], 0, 0, date(2013, 11, 3), a2g=True) - >>> glat - array([ 82.96656071, -74.33854592]) - >>> glon - array([ -84.66516034, 125.84014944]) - - - + >>> import aacgmv2 + >>> import datetime as dt + >>> import numpy as np + >>> np.set_printoptions(formatter={'float_kind': lambda x:'{:.4f}'.format(x)}) + >>> # geo to AACGM, single numbers + >>> dtime = dt.datetime(2013, 11, 3) + >>> np.array(aacgmv2.get_aacgm_coord(60, 15, 300, dtime)) + array([57.4698, 93.6300, 1.4822]) + >>> # AACGM to geo, mix arrays/numbers + >>> aacgmv2.convert_latlon_arr([90, -90], 0, 0, dtime, code="A2G") + (array([82.9666, -74.3385]), array([-84.6652, 125.8401]), array([14.1244, 12.8771])) Command-line interface ====================== .. highlight:: none -The Python package also installs a command called ``aacgmv2`` with two sub-commands, ``aacgmv2 convert`` and ``aacgmv2 convert_mlt``. The command-line interface allows you to make use of the Python library even if you don't know or use Python. See :doc:`Reference → Command-line interface ` for a list of arguments to the commands. Below are some simple usage examples. +The Python package also installs a command called ``aacgmv2`` with several +sub-commands that allow conversion between geographic/geodetic and AACGM-v2 +magnetic coordinates (mlat, mlon, and mlt). The command-line interface allows +you to make use of the Python library even if you don't know or use Python. See +:doc:`Reference → Command-line interface ` for a list of +arguments to the commands. Below are some simple usage examples. Convert geographical/magnetic coordinates ----------------------------------------- -Produce a file called e.g. ``input.txt`` with the input latitudes, longitudes and altitudes on each row separated by whitespace:: +Produce a file called e.g. ``input.txt`` with the input latitudes, longitudes +and altitudes on each row separated by whitespace:: # lat lon alt # comment lines like these are ignored @@ -44,34 +44,41 @@ Produce a file called e.g. ``input.txt`` with the input latitudes, longitudes an 61 15 300 62 15 300 -To convert this to AACGM-v2 for the date 2015-02-24, run the command ``aacgmv2 convert -i input.txt -o output.txt -d 20150224``. The output file will look like this:: +To convert this to AACGM-v2 for the date 2015-02-24, run the command +``python -m aacgmv2 convert -i input.txt -o output.txt -d 20150224``. The +output file will look like this:: - 57.47612194 93.55719875 - 58.53323704 93.96069212 - 59.58522105 94.38968625 + 57.47612194 93.55719875 1.04566346 + 58.53323704 93.96069212 1.04561304 + 59.58522105 94.38968625 1.04556369 Alternatively, you can skip the files and just use command-line piping:: - $ echo 60 15 300 | aacgmv2 convert -d 20150224 - 57.47612194 93.55719875 + $ echo 60 15 300 | python -m aacgmv2 convert -d 20150224 + 57.47612194 93.55719875 1.04566346 Convert MLT ----------- -This works in much the same way as ``convert``. The file should only contain a single column of numbers (MLTs or magnetic longitudes, depending on which way you're converting):: +This works in much the same way as ``convert``. The file should only contain a +single column of numbers (MLTs or magnetic longitudes, depending on which way +you're converting):: 1 12 23 -To convert these MLTs to magnetic longitudes at 2015-02-24 14:00:15, run e.g. ``aacgmv2 convert_mlt 20150224140015 -i input.txt -o output.txt -v`` (note that the date/time is a required parameter). The output file will then look like this:: +To convert these MLTs to magnetic longitudes at 2015-02-24 14:00:15, run e.g. +``aacgmv2 convert_mlt 20150224140015 -i input.txt -o output.txt -v`` (note that +the date/time is a required parameter). The output file will then look like +this:: - 240.13651777 - 45.13651777 - 210.13651777 + -120.34354125 + 44.65645875 + -150.34354125 Like with ``convert``, you can use stdin/stdout instead of input/output files:: - $ echo 12 | aacgmv2 convert_mlt 20150224140015 -v - 45.13651777 + $ echo 12 | python -m aacgmv2 convert_mlt 20150224140015 -v + 44.65645875 diff --git a/setup.cfg b/setup.cfg index 609b8d13..7789f1ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ release = register clean --all sdist max-line-length = 140 exclude = tests/*,*/migrations/*,*/south_migrations/* -[pytest] +[tool:pytest] norecursedirs = .git .tox @@ -60,19 +60,11 @@ multi_line_output=0 python_versions = 2.7 - 3.3 3.4 3.5 + 3.6 -dependencies = - np1.10: numpy>=1.10,<1.11 - np1.9: numpy>=1.9,<1.10 - np1.8: numpy>=1.8,<1.9 -# 1.4: Django==1.4.16 !python_versions[3.*] -# 1.5: Django==1.5.11 -# 1.6: Django==1.6.8 -# 1.7: Django==1.7.1 !python_versions[2.6] -# Deps commented above are provided as examples. That's what you would use in a Django project. +#dependencies = coverage_flags = : true diff --git a/setup.py b/setup.py index 50d220bc..c7111cd2 100644 --- a/setup.py +++ b/setup.py @@ -1,28 +1,20 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from __future__ import absolute_import, print_function +#-*- encoding: utf-8 -*- +from __future__ import absolute_import import io import os import re from glob import glob -from os.path import basename -from os.path import dirname -from os.path import join -from os.path import relpath -from os.path import splitext +from os import path from setuptools import find_packages from setuptools import setup from distutils.core import Extension - -def read(*names, **kwargs): - return io.open( - join(dirname(__file__), *names), - encoding=kwargs.get('encoding', 'utf8') - ).read() - +def read(fname, **kwargs): + return io.open(path.join(path.dirname(__file__), fname), + encoding=kwargs.get('encoding', 'utf8')).read() # enable code coverage for C code # We can't use CFLAGS=-coverage in tox.ini, since that may mess with @@ -34,25 +26,30 @@ def read(*names, **kwargs): setup( name='aacgmv2', - version='2.0.0', + version='2.4.0', license='MIT', description='A Python wrapper for AACGM-v2 magnetic coordinates', - long_description='%s\n%s' % (read('README.rst'), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))), - author='Christer van der Meeren', - author_email='cmeeren@gmail.com', - url='https://github.com/cmeeren/aacgmv2', - packages=find_packages('src'), - package_dir={'': 'src'}, - py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], + long_description='%s\n%s' % (read('README.rst'), + re.sub(':[a-z]+:`~?(.*?)`', + r'``\1``', read('CHANGELOG.rst'))), + author='Angeline G. Burrell, Christer van der Meeren', + author_email='agb073000@utdallas.edu', + url='https://github.com/aburrell/aacgmv2', + packages=find_packages('aacgmv2'), + package_dir={'': 'aacgmv2'}, + py_modules=[path.splitext(path.basename(psrc))[0] + for psrc in glob('aacgmv2/*.py')], package_data={'aacgmv2': ['aacgm_coeffs/*.asc', 'igrf12coeffs.txt']}, zip_safe=False, classifiers=[ - # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers + # complete classifier list: + # http://pypi.python.org/pypi?%3Aaction=list_classifiers 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: MIT License', 'Operating System :: Unix', 'Operating System :: POSIX', + 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', @@ -60,6 +57,7 @@ def read(*names, **kwargs): 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Utilities', @@ -77,11 +75,20 @@ def read(*names, **kwargs): ], install_requires=[ 'numpy', + 'logbook', ], + extras_require={'test':['pytest'], + }, ext_modules=[ - Extension('aacgmv2._aacgmv2', - sources=['src/aacgmv2/aacgmv2module.c', 'src/c_aacgm_v2/aacgmlib_v2.c', 'src/c_aacgm_v2/genmag.c', 'src/c_aacgm_v2/igrflib.c'], - include_dirs=['src/c_aacgm_v2']) + Extension('_aacgmv2', + sources=['aacgmv2/aacgmv2module.c', + 'c_aacgmv2/src/aacgmlib_v2.c', + 'c_aacgmv2/src/astalglib.c', + 'c_aacgmv2/src/genmag.c', + 'c_aacgmv2/src/igrflib.c', + 'c_aacgmv2/src/mlt_v2.c', + 'c_aacgmv2/src/rtime.c'], + include_dirs=['c_aacgmv2/include']) ], entry_points={ 'console_scripts': [ diff --git a/src/aacgmv2/__init__.py b/src/aacgmv2/__init__.py deleted file mode 100644 index 80b4fb81..00000000 --- a/src/aacgmv2/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -import os as _os - -__version__ = "2.0.0" - -# path and filename prefix for the IGRF coefficients -AACGM_v2_DAT_PREFIX = _os.path.join(_os.path.realpath(_os.path.dirname(__file__)), 'aacgm_coeffs', 'aacgm_coeffs-12-') -IGRF_12_COEFFS = _os.path.join(_os.path.realpath(_os.path.dirname(__file__)), 'igrf12coeffs.txt') - - -def set_coeff_path(): - '''Sets the environment variables ``AACGM_v2_DAT_PREFIX`` and - ``IGRF_12_COEFFS`` (for the current process). These are required for the - C library to function correctly. This function is automatically called - when importing aacgmv2. You may need to call this manually if you use - multithreading or spawn child processes (untested). - ''' - _os.environ['AACGM_v2_DAT_PREFIX'] = AACGM_v2_DAT_PREFIX - _os.environ['IGRF_12_COEFFS'] = IGRF_12_COEFFS - -set_coeff_path() - - -# NOTE: it is important that we import _aacgmv2 AFTER setting the -# environment variables above, otherwise it doesn't seem to inherit them -from aacgmv2 import _aacgmv2 # noqa: E402 - -from aacgmv2.wrapper import convert, convert_mlt, subsol # noqa: E402 - -__all__ = ['_aacgmv2', 'convert', 'convert_mlt', 'subsol', 'set_coeff_path', 'AACGM_v2_DAT_PREFIX', 'IGRF_12_COEFFS'] diff --git a/src/aacgmv2/__main__.py b/src/aacgmv2/__main__.py deleted file mode 100644 index 633265b5..00000000 --- a/src/aacgmv2/__main__.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- - - -'''Executed when aacgmv2 is invoked with python -m aacgmv2''' - -from __future__ import division, print_function, absolute_import - -import sys -import argparse -import datetime as dt - -import numpy as np - -import aacgmv2 - -try: - # Python 3 - STDIN = sys.stdin.buffer - STDOUT = sys.stdout.buffer -except AttributeError: - # Python 2 - STDIN = sys.stdin - STDOUT = sys.stdout - - -def main(): - '''Entry point for the script''' - - parser = argparse.ArgumentParser(description='Converts between geographical coordinates, AACGM-v2, and MLT') - - subparsers = parser.add_subparsers(title='Subcommands', prog='aacgmv2', dest='subcommand', - description='for help, run %(prog)s SUBCOMMAND -h') - subparsers.required = True - parser_convert = subparsers.add_parser('convert', help=('convert to/from geomagnetic coordinates. Input file must ' - 'have lines of the form "LAT LON ALT".')) - parser_convert_mlt = subparsers.add_parser('convert_mlt', help=('convert between magnetic local time (MLT) and ' - 'AACGM-v2 longitude. Input file must have a single ' - 'number on each line.')) - - for p in [parser_convert, parser_convert_mlt]: - p.add_argument('-i', '--input', dest='file_in', metavar='FILE_IN', type=argparse.FileType('r'), - default=STDIN, help='input file (stdin if none specified)') - p.add_argument('-o', '--output', dest='file_out', metavar='FILE_OUT', type=argparse.FileType('wb'), - default=STDOUT, help='output file (stdout if none specified)') - - parser_convert.add_argument('-d', '--date', dest='date', metavar='YYYYMMDD', - help='date for magnetic field model (1900-2020, default: today)') - parser_convert.add_argument('-v', '--a2g', dest='a2g', action='store_true', default=False, - help='invert - convert AACGM to geographic instead of geographic to AACGM') - parser_convert.add_argument('-t', '--trace', dest='trace', action='store_true', default=False, - help='use field-line tracing instead of coefficients') - parser_convert.add_argument('-a', '--allowtrace', dest='allowtrace', action='store_true', default=False, - help='automatically use field-line tracing above 2000 km') - parser_convert.add_argument('-b', '--badidea', dest='badidea', action='store_true', default=False, - help='allow use of coefficients above 2000 km (bad idea!)') - parser_convert.add_argument('-g', '--geocentric', dest='geocentric', action='store_true', default=False, - help='assume inputs are geocentric with Earth radius 6371.2 km') - - parser_convert_mlt.add_argument('datetime', metavar='YYYYMMDDHHMMSS', help='date and time for conversion') - parser_convert_mlt.add_argument('-v', '--m2a', dest='m2a', action='store_true', default=False, - help='invert - convert MLT to AACGM longitude instead of AACGM longitude to MLT') - - args = parser.parse_args() - - array = np.loadtxt(args.file_in, ndmin=2) - - if args.subcommand == 'convert': - date = dt.date.today() if args.date is None else dt.datetime.strptime(args.date, '%Y%m%d') - lats, lons = aacgmv2.convert(array[:, 0], array[:, 1], array[:, 2], date=date, a2g=args.a2g, trace=args.trace, - allowtrace=args.allowtrace, badidea=args.badidea, geocentric=args.geocentric) - np.savetxt(args.file_out, np.column_stack((lats, lons)), fmt='%.8f') - elif args.subcommand == 'convert_mlt': - datetime = dt.datetime.strptime(args.datetime, '%Y%m%d%H%M%S') - out = aacgmv2.convert_mlt(array, datetime, m2a=args.m2a) - np.savetxt(args.file_out, out, fmt='%.8f') - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/aacgmv2/aacgmv2module.c b/src/aacgmv2/aacgmv2module.c deleted file mode 100644 index 25e0894f..00000000 --- a/src/aacgmv2/aacgmv2module.c +++ /dev/null @@ -1,155 +0,0 @@ -#include "Python.h" -#include -#include "aacgmlib_v2.h" - -PyObject *module; - - -static PyObject * -aacgmv2_setDateTime(PyObject *self, PyObject *args) -{ - int year, month, day, hour, minute, second; - int err; - - if (!PyArg_ParseTuple(args, "iiiiii", &year, &month, &day, &hour, &minute, &second)) { - return NULL; - } - - err = AACGM_v2_SetDateTime(year, month, day, hour, minute, second); - - if (err < 0) { - PyErr_Format(PyExc_RuntimeError, "AACGM_v2_SetDateTime returned error code %d", err); - return NULL; - } - - Py_RETURN_NONE; -} - - -static PyObject * -aacgmv2_aacgmConvert(PyObject *self, PyObject *args) -{ - double in_lat, in_lon, height; - double out_lat, out_lon, r; - int code; - int err; - - if (!PyArg_ParseTuple(args, "dddi", &in_lat, &in_lon, &height, &code)) { - return NULL; - } - - err = AACGM_v2_Convert(in_lat, in_lon, height, &out_lat, &out_lon, &r, code); - if (err < 0) { - PyErr_Format(PyExc_RuntimeError, "AACGM_v2_Convert returned error code %d", err); - return NULL; - } - - return Py_BuildValue("ddd", out_lat, out_lon, r); -} - - -static PyMethodDef aacgmv2Methods[] = { - { "setDateTime" , aacgmv2_setDateTime , METH_VARARGS, "setDateTime(year, month, day, hour, minute, second)\n\ -\n\ -Sets the date and time for the IGRF magnetic field. \n\ -\n\ -Parameters\n\ -==========\n\ -year : int [1900, 2020)\n\ - ..\n\ -month : int [1, 12]\n\ - ..\n\ -day : int [1, 31]\n\ - ..\n\ -hour : int [0, 24]\n\ - ..\n\ -minute : int [0, 60]\n\ - ..\n\ -second : int [0, 60]\n\ - .." }, - { "aacgmConvert", aacgmv2_aacgmConvert, METH_VARARGS, "aacgmConvert(in_lat, in_lon, height, code) -> out_lat, out_lon, r\n\ -\n\ -Converts between geographic and magnetic coordinates.\n\ -\n\ -Parameters\n\ -----------\n\ -in_lat : float [-90, 90]\n\ - Input latitude \n\ -in_lon : float [-180, 180]\n\ - Input longitude\n\ -height : float\n\ - Input altitude\n\ -code : int\n\ - Bitwise code for passing options into converter. The codes and their names (defined in this module) are given in the table below.\n\ -\n\ -Returns\n\ -=======\n\ -out_lat : float\n\ - Converted latitude\n\ -out_lon : float\n\ - Converted longitude\n\ -r : float\n\ - Not used, always 1.0\n\ -\n\ -Notes\n\ -=====\n\ -The bitwise codes are:\n\ -\n\ -====== ============ =============\n\ - Code Name Description\n\ -====== ============ =============\n\ - 0 G2A Convert geographic to AACGM-v2.\n\ - 1 A2G Convert AACGM-v2 to geographic.\n\ - 2 TRACE Use field-line tracing instead of coefficients. More precise, but significantly slower.\n\ - 4 ALLOWTRACE Automatically use field-line tracing above 2000 km. If not set, cause exception to be thrown for these altitudes unless TRACE or BADIDEA is set.\n\ - 8 BADIDEA Allow use of coefficients above 2000 km (bad idea!)\n\ - 16 GEOCENTRIC Assume inputs are geocentric with Earth radius 6371.2 km.\n\ -====== ============ =============\n\ - \n\ -For example, to convert from AACGM-v2 to geographpic using field-line tracing, use either of the following:\n\ -\n\ - >>> aacgmConvert(in_lat, in_lon, height, A2G | TRACE)\n\ - >>> aacgmConvert(in_lat, in_lon, height, 1 | 2)\n\ - >>> aacgmConvert(in_lat, in_lon, height, 3)" }, - { NULL, NULL, 0, NULL } -}; - - -#if PY_MAJOR_VERSION >= 3 - - static struct PyModuleDef aacgmv2module = { - PyModuleDef_HEAD_INIT, - "_aacgmv2", /* name of module */ - "This module contains the interface to the AACGM-v2 C library.", /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module, - or -1 if the module keeps state in global variables. */ - aacgmv2Methods - }; - - - PyMODINIT_FUNC PyInit__aacgmv2(void) - { - module = PyModule_Create(&aacgmv2module); - PyModule_AddIntConstant(module, "G2A", G2A); - PyModule_AddIntConstant(module, "A2G", A2G); - PyModule_AddIntConstant(module, "TRACE", TRACE); - PyModule_AddIntConstant(module, "ALLOWTRACE", ALLOWTRACE); - PyModule_AddIntConstant(module, "BADIDEA", BADIDEA); - PyModule_AddIntConstant(module, "GEOCENTRIC", GEOCENTRIC); - return module; - } - -#else - - PyMODINIT_FUNC init_aacgmv2(void) - { - module = Py_InitModule("_aacgmv2", aacgmv2Methods); - PyModule_AddIntConstant(module, "G2A", G2A); - PyModule_AddIntConstant(module, "A2G", A2G); - PyModule_AddIntConstant(module, "TRACE", TRACE); - PyModule_AddIntConstant(module, "ALLOWTRACE", ALLOWTRACE); - PyModule_AddIntConstant(module, "BADIDEA", BADIDEA); - PyModule_AddIntConstant(module, "GEOCENTRIC", GEOCENTRIC); - } - -#endif diff --git a/src/aacgmv2/wrapper.py b/src/aacgmv2/wrapper.py deleted file mode 100644 index e854c0cb..00000000 --- a/src/aacgmv2/wrapper.py +++ /dev/null @@ -1,400 +0,0 @@ -# -*- coding: utf-8 -*- -'''This module provides a user-friendly pythonic wrapper for the low-level C interface functions.''' - -from __future__ import division, print_function, absolute_import, unicode_literals - -import datetime as dt -import calendar -import warnings -import os as _os - -import numpy as np - -from aacgmv2._aacgmv2 import A2G, TRACE, BADIDEA, ALLOWTRACE, GEOCENTRIC, setDateTime, aacgmConvert - -IGRF_12_COEFFS = _os.path.join(_os.path.realpath(_os.path.dirname(__file__)), 'igrf12coeffs.txt') - -aacgmConvert_vectorized = np.vectorize(aacgmConvert) - - -def convert(lat, lon, alt, date=None, a2g=False, trace=False, allowtrace=False, badidea=False, geocentric=False): - '''Converts to/from geomagnetic coordinates. - - This is a user-friendly pythonic wrapper for the low-level C interface - functions available in :mod:`aacgmv2._aacgmv2`. - - Parameters - ========== - lat,lon,alt : array_like - Input latitude(s), longitude(s) and altitude(s). They must be - `broadcastable to the same shape `_. - date : :class:`datetime.date`/:class:`datetime.datetime`, optional - The date/time to use for the magnetic field model, default ``None`` (uses - current time). Must be between 1900 and 2020. - a2g : bool, optional - Convert from AACGM-v2 to geographic coordinates, default ``False`` - (converts geographic to AACGM-v2). - trace : bool, optional - Use field-line tracing, default ``False`` (uses coefficients). Tracing - is more precise and needed at altitudes > 2000 km, but significantly - slower. - allowtrace : bool, optional - Automatically use field-line tracing above 2000 km, default ``False`` - (raises an exception for these altitudes unless ``trace=True`` or - ``badidea=True``). - badidea : bool, optional - Allow use of coefficients above 2000 km (bad idea!) - geocentric : bool, optional - Assume inputs are geocentric with Earth radius 6371.2 km. - - Returns - ======= - - lat_out : ``numpy.ndarray`` - Converted latitude - lon_out : ``numpy.ndarray`` - Converted longitude - - Raises - ====== - - ValueError - if max(alt) > 2000 and neither of `trace`, `allowtrace`, or `badidea` is ``True`` - ValueError - if latitude is outside the range -90 to +90 degrees - RuntimeError - if there was a problem in the C extension - - Notes - ===== - - This function exclusively relies on the `AACGM-v2 C library - `_. Specifically, - it calls the functions :func:`_aacgmv2.setDateTime` and - :func:`_aacgmv2.aacgmConvert`, which are simple interfaces to the - C library functions :func:`AACGM_v2_SetDateTime` and - :func:`AACGM_v2_Convert`. Details of the techniques used to derive the - AACGM-v2 coefficients are described by Shepherd, 2014 [1]_. - - .. [1] Shepherd, S. G. (2014), Altitude-adjusted corrected geomagnetic - coordinates: Definition and functional approximations, - J. Geophys. Res. Space Physics, 119, 7501--7521, - doi:`10.1002/2014JA020264 `_. - - ''' - - # check values - if np.min(alt) < 0: - warnings.warn('Coordinate transformations are not intended for altitudes < 0 km', UserWarning) - - if np.max(alt) > 2000 and not (trace or allowtrace or badidea): - raise ValueError('Coefficients are not valid for altitudes above 2000 km. You must either use field-line ' - 'tracing (trace=True or allowtrace=True) or indicate you know this is a bad idea ' - '(badidea=True)') - - # check if latitudes are > 90.1 (to allow some room for rounding errors, which will be clipped) - if np.max(np.abs(lat)) > 90.1: - raise ValueError('Latitude must be in the range -90 to +90 degrees') - np.clip(lat, -90, 90) - - # constrain longitudes between -180 and 180 - lon = ((np.asarray(lon) + 180) % 360) - 180 - - # set to current date if none is given - if date is None: - date = dt.datetime.now() - - # add time info if only date is given - if isinstance(date, dt.date): - date = dt.datetime.combine(date, dt.time(0)) - - # set current date and time - setDateTime(date.year, date.month, date.day, date.hour, date.minute, date.second) - - # make flag - flag = A2G*a2g + TRACE*trace + ALLOWTRACE*allowtrace + BADIDEA*badidea + GEOCENTRIC*geocentric - - # convert - lat_out, lon_out, _ = aacgmConvert_vectorized(lat, lon, alt, flag) - - return lat_out, lon_out - - -def convert_mlt(arr, datetime, m2a=False): - '''Converts between magnetic local time (MLT) and AACGM-v2 longitude. - - .. note:: This function is not related to the AACGM-v2 C library, but is provided as - a convenience in the hopes that it might be useful for some purposes. - - Parameters - ========== - arr : array_like or float - Magnetic longitudes or MLTs to convert. - datetime : :class:`datetime.datetime` - Date and time for MLT conversion in Universal Time (UT). - m2a : bool - Convert MLT to AACGM-v2 longitude (default is ``False``, which implies - conversion from AACGM-v2 longitude to MLT). - - Returns - ======= - out : numpy.ndarray - Converted coordinates/MLT - - Notes - ===== - - The MLT conversion is not part of the AACGM-v2 C library and is instead based - on Laundal et al., 2016 [1]_. A brief summary of the method is provided below. - - MLT is defined as - - MLT = (magnetic longitude - magnetic noon meridian longitude) / 15 + 12 - - where the magnetic noon meridian longitude is the centered dipole longitude - of the subsolar point. - - There are two important reasons for using centered dipole instead of AACGM for - this calculation. One reason is that the AACGM longitude of the subsolar point - is often undefined (being at low latitudes). More importantly, if the subsolar - point close to ground was used, the MLT at polar latitudes would be affected - by non-dipole features at low latitudes, such as the South Atlantic Anomaly. - This is not desirable; since the Sun-Earth interaction takes place at polar - field lines, it is these field lines the MLT should describe. - - In calculating the centered dipole longitude of the subsolar point, we use - the first three IGRF Gauss coefficients, using linear interpolation between - the model updates every five years. - - Both input and output MLON are taken modulo 360 to ensure they are between - 0 and 360 degrees. Similarly, input/output MLT are taken modulo 24. - For implementation of the subsolar point calculation, see :func:`subsol`. - - .. [1] Laundal, K. M. and A. D. Richmond (2016), Magnetic Coordinate Systems, - Space Sci. Rev., doi:`10.1007/s11214-016-0275-y `_. - - ''' - d2r = np.pi/180 - - # find subsolar point - yr = datetime.year - doy = datetime.timetuple().tm_yday - ssm = datetime.hour*3600 + datetime.minute*60 + datetime.second - subsol_lon, subsol_lat = subsol(yr, doy, ssm) - - # unit vector pointing at subsolar point: - s = np.array([np.cos(subsol_lat * d2r) * np.cos(subsol_lon * d2r), - np.cos(subsol_lat * d2r) * np.sin(subsol_lon * d2r), - np.sin(subsol_lat * d2r)]) - - # convert subsolar coordinates to centered dipole coordinates - z = igrf_dipole_axis(datetime) # Cartesian axis pointing at Northern dipole pole - y = np.cross(np.array([0, 0, 1]), z) - y = y/np.linalg.norm(y) - x = np.cross(y, z) - R = np.vstack((x, y, z)) - s_cd = R.dot(s) - - # centered dipole longitude of subsolar point: - mlon_subsol = np.arctan2(s_cd[1], s_cd[0])/d2r - - # convert the input array - if m2a: # MLT to AACGM - mlt = np.asarray(arr) % 24 - mlon = (15*(mlt - 12) + mlon_subsol) % 360 - return mlon - else: # AACGM to MLT - mlon = np.asarray(arr) % 360 - mlt = ((mlon - mlon_subsol)/15 + 12) % 24 - return mlt - - -def subsol(year, doy, ut): - '''Finds subsolar geocentric longitude and latitude. - - Helper function for :func:`convert_mlt`. - - Parameters - ========== - year : int [1601, 2100] - Calendar year - doy : int [1, 365/366] - Day of year - ut : float - Seconds since midnight on the specified day - - Returns - ======= - sbsllon : float - Subsolar longitude for the given date/time - sbsllat : float - Subsolar latitude for the given date/time - - Notes - ===== - - Based on formulas in Astronomical Almanac for the year 1996, p. C24. - (U.S. Government Printing Office, 1994). Usable for years 1601-2100, - inclusive. According to the Almanac, results are good to at least 0.01 - degree latitude and 0.025 degrees longitude between years 1950 and 2050. - Accuracy for other years has not been tested. Every day is assumed to have - exactly 86400 seconds; thus leap seconds that sometimes occur on December - 31 are ignored (their effect is below the accuracy threshold of the - algorithm). - - After Fortran code by A. D. Richmond, NCAR. Translated from IDL - by K. Laundal. - - ''' - - from numpy import sin, cos, pi, arctan2, arcsin - - yr = year - 2000 - - if year >= 2101: - print('subsol.py: subsol invalid after 2100. Input year is:', year) - - nleap = np.floor((year-1601)/4) - nleap = nleap - 99 - if year <= 1900: - if year <= 1600: - print('subsol.py: subsol invalid before 1601. Input year is:', year) - ncent = np.floor((year-1601)/100) - ncent = 3 - ncent - nleap = nleap + ncent - - l0 = -79.549 + (-0.238699*(yr-4*nleap) + 3.08514e-2*nleap) - - g0 = -2.472 + (-0.2558905*(yr-4*nleap) - 3.79617e-2*nleap) - - # Days (including fraction) since 12 UT on January 1 of IYR: - df = (ut/86400 - 1.5) + doy - - # Addition to Mean longitude of Sun since January 1 of IYR: - lf = 0.9856474*df - - # Addition to Mean anomaly since January 1 of IYR: - gf = 0.9856003*df - - # Mean longitude of Sun: - l = l0 + lf - - # Mean anomaly: - g = g0 + gf - grad = g*pi/180 - - # Ecliptic longitude: - lmbda = l + 1.915*sin(grad) + 0.020*sin(2*grad) - lmrad = lmbda*pi/180 - sinlm = sin(lmrad) - - # Days (including fraction) since 12 UT on January 1 of 2000: - n = df + 365*yr + nleap - - # Obliquity of ecliptic: - epsilon = 23.439 - 4e-7*n - epsrad = epsilon*pi/180 - - # Right ascension: - alpha = arctan2(cos(epsrad)*sinlm, cos(lmrad)) * 180/pi - - # Declination: - delta = arcsin(sin(epsrad)*sinlm) * 180/pi - - # Subsolar latitude: - sbsllat = delta - - # Equation of time (degrees): - etdeg = l - alpha - nrot = round(etdeg/360) - etdeg = etdeg - 360*nrot - - # Apparent time (degrees): - aptime = ut/240 + etdeg # Earth rotates one degree every 240 s. - - # Subsolar longitude: - sbsllon = 180 - aptime - nrot = round(sbsllon/360) - sbsllon = sbsllon - 360*nrot - - return sbsllon, sbsllat - - -def gc2gd_lat(gc_lat): - '''Convert geocentric latitude to geodetic latitude using WGS84. - - Parameters - ========== - gc_lat : array_like or float - Geocentric latitude - - Returns - ======= - gd_lat : same as input - Geodetic latitude - - ''' - WGS84_e2 = 0.006694379990141317 - return np.rad2deg(-np.arctan(np.tan(np.deg2rad(gc_lat))/(WGS84_e2 - 1))) - - -def igrf_dipole_axis(date): - '''Get Cartesian unit vector pointing at dipole pole in the north, according to IGRF - - Parameters - ========== - date : :class:`datetime.datetime` - Date and time - - Returns - ======= - m: numpy.ndarray - Cartesian 3 element vector pointing at dipole pole in the north (geocentric coords) - - Notes - ===== - IGRF coefficients are read from the igrf12coeffs.txt file. It should also work after IGRF updates. - The dipole coefficients are interpolated to the date, or extrapolated if date > latest IGRF model - ''' - - # get time in years, as float: - year = date.year - doy = date.timetuple().tm_yday - year = year + doy/(365 + calendar.isleap(year)) - - # read the IGRF coefficients - with open(IGRF_12_COEFFS, 'r') as f: - lines = f.readlines() - - years = lines[3].split()[3:][:-1] - years = np.array(years, dtype=float) # time array - - g10 = lines[4].split()[3:] - g11 = lines[5].split()[3:] - h11 = lines[6].split()[3:] - - # secular variation coefficients (for extrapolation) - g10sv = np.float32(g10[-1]) - g11sv = np.float32(g11[-1]) - h11sv = np.float32(h11[-1]) - - # model coefficients: - g10 = np.array(g10[:-1], dtype=float) - g11 = np.array(g11[:-1], dtype=float) - h11 = np.array(h11[:-1], dtype=float) - - # get the gauss coefficient at given time: - if year <= years[-1]: # regular interpolation - g10 = np.interp(year, years, g10) - g11 = np.interp(year, years, g11) - h11 = np.interp(year, years, h11) - else: # extrapolation - dt = year - years[-1] - g10 = g10[-1] + g10sv * dt - g11 = g11[-1] + g11sv * dt - h11 = h11[-1] + h11sv * dt - - # calculate pole position - B0 = np.sqrt(g10**2 + g11**2 + h11**2) - - return -np.array([g11, h11, g10])/B0 diff --git a/src/c_aacgm_v2/README.txt b/src/c_aacgm_v2/README.txt deleted file mode 100644 index ef5816c0..00000000 --- a/src/c_aacgm_v2/README.txt +++ /dev/null @@ -1,106 +0,0 @@ - -AACGM v2 Software C - -Instructions: - -1. Download the coefficients and put them in a convenient directory - -2. Set the environment variable AACGM_v2_DAT_PREFIX to the directory that - you are storing the coefficients in AND include the prefix of the - coefficient files, i.e., aacgm_coeffs-12- - - e.g.: - - AACGM_v2_DAT_PREFIX=/mnt/thayerfs/shepherd/AACGM/idl/coeffs/aacgm_coeffs-12- - - Note that if you used the old AACGM software from JHU/APL you should have - a similar variable already set. - -3. Untar the contents of the .tar file into a directory - -4. Build the test program by running: - - gcc -o test_aacgm test_aacgm.c aacgmlib_v2.c igrflib.c genmag.c -lm -static - - Note that on older systems you might need to remove the -static flag - -5. Run the test program by running: - - test_aacgm - - The output should look something like: - - -*************************************************************************** -* AACGM v2 ERROR: No Date/Time Set * -* * -* You must specifiy the date and time in order to use AACGM coordinates, * -* which depend on the internal (IGRF) magnetic field. Before calling * -* AACGM_v2_Convert() you must set the date and time to the integer values * -* using the function: * -* * -* AACGM_v2_SetDateTime(year,month,day,hour,minute,second); * -* * -* or to the current computer time in UT using the function: * -* * -* AACGM_v2_SetNow(); * -* * -* subsequent calls to AACGM_v2_Convert() will use the last date and time * -* that was set, so update to the actual date and time that is desired. * -*************************************************************************** - - -Setting time to : 20140322 0311:00 -lat = 45.500000, lon = -23.500000, height = 1135.000000 -mlat = 48.377539, mlon = 57.822458, r = 1.000000 - - -lat = 45.500000, lon = -23.500000, height = 1135.000000 -mlat = 49.425800, mlon = 58.259686, r = 1.000000 - - -lat = 65.500000, lon = 93.500000, height = 1135.000000 -mlat = 62.251076, mlon = 166.990581, r = 1.000000 - - -lat = 65.500000, lon = 93.500000, height = 0.000000 -mlat = 60.799240, mlon = 166.518084, r = 1.000000 - - -lat = 75.500000, lon = 73.500000, height = 0.000000 -mlat = 70.420669, mlon = 150.743259, r = 1.000000 - - -lat = 75.500000, lon = 73.500000, height = 0.000000 -mlat = 70.726381, mlon = 150.672892, r = 1.000000 - - -IMPORTANT NOTES: - -1. The function AACGM_v2_Convert is a direct replacement for the function - AACGMConvert that is used in much of the SD software. This is your - starting point, but you can modify the test program as you like. - -2. New user-space functions have been added that allow users to set the - date and time. The functions are: - - AACGM_v2_SetDateTime(int year, int month, int day, int hour, - int minute, int second); - - AACGM_v2_SetNow(); - - The latter will use the current computer date and time in UT. - - Note that setting the time frequently triggers an interpolation in time and - in altitude, which will slow the calculations. Testing should be done to - determine what the correspondence between changes in time and AACGM lat/lon - are. - -3. You must set the date and time at least once or the code will not run. - -4. A new user-space function has been added that allow users to see what - date and time are being used. The function is: - - AACGM_v2_GetDateTime(int *year, int *month, int *day, int *hour, - int *minute, int *second, int *doy); - diff --git a/src/c_aacgm_v2/aacgmlib_v2.c b/src/c_aacgm_v2/aacgmlib_v2.c deleted file mode 100644 index e0289aa2..00000000 --- a/src/c_aacgm_v2/aacgmlib_v2.c +++ /dev/null @@ -1,1480 +0,0 @@ -/*----------------------------------------------------------------------------- -; AACGM library -; -; a collection of C routines intended to fully exploit the functionality of -; the AACGM coordinates [Shepherd, 2014] including use of the AACGM -; coefficients and field line tracing -; -; 20140611 SGS v0.0 Modification to existing AACGM C and IDL software, but -; includes additional features: linear interpolation and -; fieldline tracing. -; 20140702 SGS v0.1 Made operational, moved globals to library, fixed bug -; in malloc. -; 20140703 SGS v0.2 Added AACGM_GetDateTime() function to provide capability -; of getting date and time used in calculations (Jeff). -; Changed behavior to abort (with message) if not data/time -; is set. -; Variable fact changed to double from unsigned long which -; is not big enought on some 32-bit systems. -; Switched from NAN to HUGE_VAL for undefined result -; 20140918 SGS v1.0 change function names to _v2 for wider distribution -; -; Functions: -; -; AACGM_v2_Rylm -; AACGM_v2_Alt2CGM - not used -; AACGM_v2_CGM2Alt -; AACGM_v2_Sgn -; convert_geo_coord -; AACGM_v2_LoadCoefFP -; AACGM_v2_LoadCoef -; AACGM_v2_LoadCoefs -; AACGM_v2_Convert -; AACGM_v2_SetDateTime -; AACGM_v2_GetDateTime -; AACGM_v2_SetNow -; AACGM_v2_errmsg -; - -; AACGM_v2_Dayno -; AACGM_v2_Trace -; AACGM_v2_Trace_inv -; -;------------------------------------------------------------------------------ -*/ - - -#include -#include -#include -#include -#include -#include "aacgmlib_v2.h" -#include "igrflib.h" -#include "genmag.h" - -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - -#ifdef _MSC_VER - #include - #define INFINITY (DBL_MAX+DBL_MAX) - #define NAN (INFINITY-INFINITY) -#endif - -#define DEBUG 0 - -/* put these in the library header file when you figure out how to do so... */ -struct { - int year; - int month; - int day; - int hour; - int minute; - int second; - int dayno; - int daysinyear; -} aacgm_date = {-1,-1,-1,-1,-1,-1,-1}; - -int myear = 0; /* model year: 5-year epoch */ -double fyear = 0.; /* floating point year */ - -int myear_old = -1; -double fyear_old = -1.; - -double height_old[2] = {-1,-1}; - -struct { - double coef[AACGM_KMAX][NCOORD][POLYORD][NFLAG]; /* interpolated coefs */ - double coefs[AACGM_KMAX][NCOORD][POLYORD][NFLAG][2]; /* bracketing coefs */ -} sph_harm_model; - -#ifndef complex - struct complex { - double x; - double y; - }; -#endif - - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_Rylm -; -; PURPOSE: -; Computes an array of real spherical harmonic function values -; Y_lm(phi,theta) for a given colatitiude (phi) and longitude (theta) -; for all the values up to l = order, which is typically 10. The -; values are stored in a 1D array of dimension (order+1)^2. The -; indexing scheme used is: -; -; l 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 ... -; m 0 -1 0 1 -2 -1 0 1 2 -3 -2 -1 0 1 2 3 -4 -3 -2 -1 0 ... -;C & IDL j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... -;FORTRAN j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ... -; -; CALLING SEQUENCE: -; AACGM_v2_Rylm, colat,lon,order, ylmval -; -; Input Arguments: -; colat - The colatitude of the point for which the spherical -; harmonic Y_lm is to be calculated -; -; lon - The longitude of the point for which the spherical -; harmonic Y_lm is to be calculated -; -; order - The order of the spherical harmonic function expansion. -; The total number of terms computed will be (order+1)^2 -; -; Output Argument: -; ylmval - 1D array of spherical harmonic functions at the point -; (colat,lon) -; -; HISTORY: -; -; Revision 1.1 94/10/12 15:24:21 15:24:21 baker (Kile Baker S1G) -; Initial revision -; -; subsequent revisions, porting to C and IDL by baker, wing and barnes. -; -; NOTES by SGS: -; -; It is likely that the original version was taken from FORTRAN and used array -; indexing that begins with 1. Indexing is somewhat more natural using the -; zeros-based indexing of C/IDL. Indices have thus been changed from the -; original version. -; -; It appears that the original version used unnormalized spherical harmonic -; functions. I suspect this might be better, but realized it too late. The -; coefficients I derived are for orthonormal spherical harmonic functions -; which then require the same for evaluation. I believe that the original -; authors used orthogonal spherical harmonic functions which eliminate the -; need for computing the normalization factors. I suspect this is just fine, -; but have not tested it. -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_Rylm(double colat, double lon, int order, double *ylmval) -{ - struct complex z1, z2; - struct complex q_fac, q_val; - int k, l, m, kk; - int ia,ib,ic,id; - double cos_theta, sin_theta; - double cos_lon, sin_lon; - double l2, tl, fac; - double ca, cb, d1; - double *ffff; - /* long unsigned *fact; not big enough for 32-bit machines */ - double *fact; - - cos_theta = cos(colat); - sin_theta = sin(colat); - - cos_lon = cos(lon); - sin_lon = sin(lon); - - d1 = -sin_theta; - z2.x = cos_lon; - z2.y = sin_lon; - - z1.x = d1 * z2.x; - z1.y = d1 * z2.y; - q_fac = z1; - - /* - * Generate Zonal Harmonics (P_l^(m=0) for l = 1,order) using recursion - * relation (6.8.7), p. 252, Numerical Recipes in C, 2nd. ed., Press. W. - * et al. Cambridge University Press, 1992) for case where m = 0. - * - * l Pl = cos(theta) (2l-1) Pl-1 - (l-1) Pl-2 (6.8.7) - * - * where Pl = P_l^(m=0) are the associated Legendre polynomials - * - */ - - ylmval[0] = 1; /* l = 0, m = 0 */ - ylmval[2] = cos_theta; /* l = 1, m = 0 */ - - for (l=2; l<=order; l++) { - /* indices for previous two values: k = l * (l+1) + m with m=0 */ - ia = (l-2)*(l-1); - ib = (l-1)*l; - ic = l * (l+1); - - ylmval[ic] = (cos_theta * (2*l-1) * ylmval[ib] - (l-1)*ylmval[ia])/l; - } - - /* - * Generate P_l^l for l = 1 to (order+1)^2 using algorithm based upon (6.8.8) - * in Press et al., but incorporate longitude dependence, i.e., sin/cos (phi) - * - * Pll = (-1)^l (2l-1)!! (sin^2(theta))^(l/2) - * - * where Plm = P_l^m are the associated Legendre polynomials - * - */ - - q_val = q_fac; - ylmval[3] = q_val.x; /* l = 1, m = +1 */ - ylmval[1] = -q_val.y; /* l = 1, m = -1 */ - for (l=2; l<=order; l++) { - d1 = l*2 - 1.; - z2.x = d1 * q_fac.x; - z2.y = d1 * q_fac.y; - z1.x = z2.x * q_val.x - z2.y * q_val.y; - z1.y = z2.x * q_val.y + z2.y * q_val.x; - q_val = z1; - - /* indices for previous two values: k = l * (l+1) + m */ - ia = l*(l+2); /* m = +l */ - ib = l*l; /* m = -l */ - - ylmval[ia] = q_val.x; - ylmval[ib] = -q_val.y; - } - - /* - * Generate P_l,l-1 to P_(order+1)^2,l-1 using algorithm based upon (6.8.9) - * in Press et al., but incorporate longitude dependence, i.e., sin/cos (phi) - * - * Pl,l-1 = cos(theta) (2l-1) Pl-1,l-1 - * - */ - - for (l=2; l<=order; l++) { - - l2 = l*l; - tl = 2*l; - /* indices for Pl,l-1; Pl-1,l-1; Pl,-(l-1); Pl-1,-(l-1) */ - ia = l2 - 1; - ib = l2 - tl + 1; - ic = l2 + tl - 1; - id = l2 + 1; - - fac = tl - 1; - ylmval[ic] = fac * cos_theta * ylmval[ia]; /* Pl,l-1 */ - ylmval[id] = fac * cos_theta * ylmval[ib]; /* Pl,-(l-1) */ - } - - /* - * Generate remaining P_l+2,m to P_(order+1)^2,m for each m = 1 to order-2 - * using algorithm based upon (6.8.7) in Press et al., but incorporate - * longitude dependence, i.e., sin/cos (phi). - * - * for each m value 1 to order-2 we have P_mm and P_m+1,m so we can compute - * P_m+2,m; P_m+3,m; etc. - * - */ - - for (m=1; m<=order-2; m++) { - for (l=m+2; l<=order; l++) { - ca = ((double) (2*l-1))/(l-m); - cb = ((double) (l+m-1))/(l-m); - - l2 = l*l; - ic = l2 + l + m; - ib = l2 - l + m; - ia = l2 - l - l - l + 2 + m; - /* positive m */ - ylmval[ic] = ca * cos_theta * ylmval[ib] - cb * ylmval[ia]; - - ic -= (m+m); - ib -= (m+m); - ia -= (m+m); - /* negative m */ - ylmval[ic] = ca * cos_theta * ylmval[ib] - cb * ylmval[ia]; - } - } - - /* - * Normalization added here (SGS) - * - * Note that this is NOT the standard spherical harmonic normalization factors - * - * The recursive algorithms above treat positive and negative values of m in - * the same manner. In order to use these algorithms the normalization must - * also be modified to reflect the symmetry. - * - * Output values have been checked against those obtained using the internal - * IDL legendre() function to obtain the various associated legendre - * polynomials. - * - * As stated above, I think that this normalization may be unnecessary. The - * important thing is that the various spherical harmonics are orthogonal, - * rather than orthonormal. - * - */ - - /* determine array of factorials */ - /*fact = (long unsigned *)malloc(sizeof(long unsigned)*(2*order+2));*/ - fact = (double *)malloc(sizeof(double)*(2*order+2)); - if (fact == NULL) return (-1); - fact[0] = fact[1] = 1; - for (k=2; k <= 2*order+1; k++) fact[k] = k*fact[k-1]; - - ffff = (double *)malloc(sizeof(double)*(order+1)*(order+1)); - if (ffff == NULL) return (-1); - - /* determine normalization factors */ - for (l=0; l<=order; l++) { - for (m=0; m<=l; m++) { - k = l * (l+1) + m; /* 1D index for l,m */ - ffff[k] = sqrt((2*l+1)/(4*M_PI) * fact[l-m]/fact[l+m]); - ylmval[k] *= ffff[k]; - } - for (m=-l; m<0; m++) { - k = l * (l+1) + m; /* 1D index for l,m */ - kk = l * (l+1) - m; - ylmval[k] *= ffff[kk] * ((-m % 2) ? -1 : 1); - } - } - - #if DEBUG > 1 - for (k=0; k<2*order+1; k++) - /*printf("%03d %lu\n", k, fact[k]);*/ - printf("%03d %lf\n", k, fact[k]); - #endif - - free(fact); - free(ffff); - - #if DEBUG > 10 - for (l=0; l<=order; l++) { - for (m=0; m<=l; m++) { - k = l * (l+1) + m; /* 1D index for l,m */ - printf("%03d %lf\n", k, ylmval[k]); - } - } - #endif - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_Alt2CGM -; -; PURPOSE: -; Transformation from so-called 'at-altitude' coordinates to AACGM. -; The purpose of this function is to scale the latitudes in such a -; way so that there is no gap. The problem is that for non-zero -; altitudes (h) are range of latitudes near the equator lie on dipole -; field lines that near reach the altitude h, and are therefore not -; accessible. This is the inverse transformation. -; -; cos (lat_aacgm) = sqrt( Re/(Re + h) ) cos (lat_at-alt) -; -; -; CALLING SEQUENCE: -; AACGM_v2_Alt2CGM,r_height_in,r_lat_alt,r_lat_adj -; -; Input Arguments: -; r_height_in - The altitude (h) -; r_lat_alt - The 'at-altitude' latitude -; -; Output Arguments: -; r_lat_adj - The corrected latitude, i.e., AACGM latitude -; -; HISTORY: -; -; This function is unchanged from its original version (Baker ?) -; -;+----------------------------------------------------------------------------- -*/ - -void AACGM_v2_Alt2CGM(double r_height_in, double r_lat_alt, double *r_lat_adj) -{ - double eps =1e-9; - double unim =0.9999999; - - double r1; - double r0, ra; - - #if DEBUG > 0 - printf("AACGM_v2_Alt2CGM\n"); - #endif - - /* Computing 2nd power */ - r1 = cos(r_lat_alt*M_PI/180.); - ra = r1 * r1; - if (ra < eps) ra = eps; - - r0 = (r_height_in/RE + 1.) / ra; - if (r0 < unim) r0 = unim; - - r1 = acos(sqrt(1/r0)); - *r_lat_adj = AACGM_v2_Sgn(r1, r_lat_alt)*180/M_PI; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_CGM2Alt -; -; PURPOSE: -; Transformation from AACGM to so-called 'at-altitude' coordinates. -; The purpose of this function is to scale the latitudes in such a -; way so that there is no gap. The problem is that for non-zero -; altitudes (h) are range of latitudes near the equator lie on dipole -; field lines that near reach the altitude h, and are therefore not -; accessible. This mapping closes the gap. -; -; cos (lat_at-alt) = sqrt( (Re + h)/Re ) cos (lat_aacgm) -; -; -; CALLING SEQUENCE: -; AACGM_v2_CGM2Alt,r_height_in,r_lat_in,r_lat_adj, error -; -; Input Arguments: -; r_height_in - The altitude (h) -; r_lat_in - The AACGM latitude -; -; Output Arguments: -; r_lat_adj - The 'at-altitude' latitude -; error - variable is set if latitude is below the value that -; is mapped to the origin -; -; HISTORY: -; -; This function is unchanged from its original version (Baker ?) -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_CGM2Alt(double r_height_in, double r_lat_in, double *r_lat_adj) -{ - double unim=1; - double r1; - double ra; - int error=0; - - #if DEBUG > 0 - printf("AACGM_v2_CGM2Alt\n"); - #endif - - /* convert from AACGM to at-altitude coordinates */ - r1 = cos(r_lat_in*M_PI/180.); - ra = (r_height_in/RE + 1)*(r1*r1); - if (ra > unim) { - ra = unim; - error = 1; - } - - r1 = acos(sqrt(ra)); - *r_lat_adj = AACGM_v2_Sgn(r1,r_lat_in)*180/M_PI; - - return (error); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_Sgn -; -; PURPOSE: -; return the signed quantity of a variable where the magnitude is given -; by the first argument and the sign is given by the second argument. -; -; CALLING SEQUENCE: -; AACGM_v2_Sgn, a, b -; -; Input Arguments: -; a - magnitude -; b - sign -; -; Return Value: -; signed quantity -; -;+----------------------------------------------------------------------------- -*/ - -double AACGM_v2_Sgn(double a, double b) -{ - double x=0; - x = (a >= 0) ? a : -a; - return (b >= 0) ? x: -x; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; convert_geo_coord -; -; PURPOSE: -; Second-level function used to determine the lat/lon of the input -; coordinates. -; -; CALLING SEQUENCE: -; err = convert_geo_coord(in_lat,in_lon,height, out_lat,out_lon, -; code,order); -; -; Input Arguments: -; in_lat - latitude in degrees -; in_lon - longitude in degrees -; height - height above Earth in km -; code - bitwise code for passing options into converter -; G2A - geographic (geodetic) to AACGM-v2 -; A2G - AACGM-v2 to geographic (geodetic) -; TRACE - use field-line tracing, not coefficients -; ALLOWTRACE - use trace only above 2000 km -; BADIDEA - use coefficients above 2000 km -; order - integer order of spherical harmonic expansion -; -; Output Arguments: -; out_lat - pointer to output latitude in degrees -; out_lon - pointer to output longitude in degrees -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int convert_geo_coord(double lat_in, double lon_in, double height_in, - double *lat_out, double *lon_out, int code, int order) { - - int i,j,k,l,m,f,a,t,flag; - int i_err64, err; - -// extern int rylm(); - double ylmval[AACGM_KMAX]; - double colat_temp; - double lon_output; - - double lat_adj=0; - double lat_alt=0; - double colat_input; - - double alt_var_cu=0, lon_temp=0, alt_var_sq=0, alt_var_qu=0; - double colat_output=0, r=0, x=0, y=0, z=0; - double ztmp, fac; - double alt_var=0; - double lon_input=0; - double llh[3]; - - static double cint[AACGM_KMAX][NCOORD][NFLAG]; - - #if DEBUG > 0 - printf("convert_geo_coord\n"); - #endif - - /* no date/time set so use current time */ - if (aacgm_date.year < 0) { /* AACGM_v2_SetNow();*/ - AACGM_v2_errmsg(0); - return -128; - } - - /* TRACE */ /* call tracing functions here and return */ - if ((code & TRACE) || (height_in > MAXALT && (code & ALLOWTRACE))) { - if (A2G & code) { /* AACGM-v2 to geographic */ - err = AACGM_v2_Trace_inv(lat_in,lon_in,height_in, lat_out,lon_out); - if ((code & GEOCENTRIC) == 0) { - geoc2geod(*lat_out,*lon_out,(RE+height_in)/RE, llh); - *lat_out = llh[0]; - } - } else { - err = AACGM_v2_Trace(lat_in,lon_in,height_in, lat_out,lon_out); - } - - return (err); - } - - /* determine the altitude dependence of the coefficients */ - flag = (A2G & code); /* 0 for G2A; 1 for A2G */ - if (height_in != height_old[flag]) { - alt_var = height_in/(double)MAXALT; - alt_var_sq = alt_var * alt_var; - alt_var_cu = alt_var * alt_var_sq; - alt_var_qu = alt_var * alt_var_cu; - - #if DEBUG > 1 - printf("alt_var = %lf\n", alt_var); - printf("alt_var_qu = %lf\n", alt_var_qu); - #endif - - #if DEBUG > 0 - printf("** HEIGHT INTERPOLATION **\n"); - #endif - - for (i=0; i 10 - printf("%lf %lf\n", cint[j][i][flag], - sph_harm_model.coef[j][i][0][flag]); - #endif - - } - } - height_old[flag] = height_in; - } - #if DEBUG > 1 - printf("cint[0][0][%d] = %lf\n", flag, cint[0][0][flag]); - printf("cint[%d][0][%d] = %lf\n", - AACGM_KMAX-1, flag, cint[AACGM_KMAX-1][0][flag]); - printf("cint[%d][%d][%d] = %lf\n", AACGM_KMAX-1, NCOORD-1, flag, - cint[AACGM_KMAX-1][NCOORD-1][flag]); - #endif - - x = y = z = 0; - - lon_input = lon_in*M_PI/180.0; - - if (flag == 0) colat_input = (90.-lat_in)*M_PI/180.0; - else { - /* use intermediate "at-altitude" coordinates for inverse trans. */ - i_err64 = AACGM_v2_CGM2Alt(height_in, lat_in, &lat_adj); - - if (i_err64 != 0) return -64; - colat_input= (90. - lat_adj)*M_PI/180; - } - - /* Compute the values of the spherical harmonic functions. - * NOTE: this function was adapted to use orthonormal SH functions */ - AACGM_v2_Rylm(colat_input, lon_input, order, ylmval); - - for (l=0; l<=order; l++) { - for (m=-l; m<=l; m++) { - k = l * (l+1) + m; /* SGS: changes indexing */ - - x += cint[k][0][flag]*ylmval[k]; - y += cint[k][1][flag]*ylmval[k]; - z += cint[k][2][flag]*ylmval[k]; - } - } - - /* COMMENT: SGS - * - * This answers one of my questions about how the coordinates for AACGM are - * guaranteed to be on the unit sphere. Here they compute xyz indpendently - * using the SH coefficients for each coordinate. They reject anything that - * is +/- .1 Re from the surface of the Earth. They then scale each xyz - * coordinate by the computed radial distance. This is a TERRIBLE way to do - * things... but necessary for the inverse transformation. - */ - - /* SGS - new method that ensures position is on unit sphere and results in a - * much better fit. Uses z coordinate only for sign, i.e., hemisphere. - */ - if (flag == 0) { - fac = x*x + y*y; - if (fac > 1.) { - /* we are in the forbidden region and the solution is undefined */ - *lat_out = NAN; - *lon_out = NAN; - - return 0; /* return -64; */ - } - - ztmp = sqrt(1. - fac); - z = (z < 0) ? -ztmp : ztmp; - - colat_temp = acos(z); - } else { - /* SGS - for inverse the old normalization produces lower overall errors...*/ - r = sqrt(x*x + y*y + z*z); - if ((r< 0.9) || (r > 1.1)) return -32; - - z /= r; - x /= r; - y /= r; - - if (z > 1.) colat_temp = 0; - else if (z < -1.) colat_temp = M_PI; - else colat_temp = acos(z); - } - - if ((fabs(x) < 1e-8) && (fabs(y) < 1e-8)) lon_temp = 0; - else lon_temp = atan2(y,x); - - lon_output = lon_temp; - - /* SGS - not used for forward transformation */ - /* - if (flag == 0) { - lat_alt =90 - colat_temp*180/PI; - altitude_to_cgm(height_in, lat_alt,&lat_adj); - colat_output = (90. - lat_adj) * PI/180; - } else colat_output = colat_temp; - */ - colat_output = colat_temp; - - *lat_out = (double) (90 - colat_output*180/M_PI); - *lon_out = (double) (lon_output*180/M_PI); - - if ((code & GEOCENTRIC) == 0 && (code & A2G)) { - geoc2geod(*lat_out,*lon_out,(RE+height_in)/RE, llh); - *lat_out = llh[0]; - } - - return 0; -} - - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_LoadCoefFP -; -; PURPOSE: -; Load a set of spherical harmonic coefficients. -; -; CALLING SEQUENCE: -; err = AACGM_v2_LoadCoefFP(fp, code); -; -; Input Arguments: -; fp - FILE pointer to open coefficient file -; code - 0 for 1st set; 1 for 2nd set -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_LoadCoefFP(FILE *fp, int code) -{ - /* char tmp[64]; */ - double tmp; - int f,l,a,t,i; - - #if DEBUG > 0 - printf("AACGM_v2_LoadCoefFP\n"); - #endif - - if (fp==NULL) { - #if DEBUG > 0 - printf("NULL file pointer in AACGM_v2_LoadCoefFP\n"); - #endif - return -1; - } - - for (f=0;f 0 - printf("FILE error, aborting\n"); - #endif - fclose(fp); - return -1; - } - - sph_harm_model.coefs[t][a][l][f][code] = tmp; - } - } - } - } - - #if DEBUG > 10 - for (f=0;f 0 - printf("loading %s\n", fname); - #endif - - fp = fopen(fname,"r"); - if (fp==NULL) { - #if DEBUG > 0 - printf("NULL file pointer in AACGM_v2_LoadCoef\n"); - #endif - return -1; - } - - err = AACGM_v2_LoadCoefFP(fp, code); - if (err != 0) { - #if DEBUG > 0 - printf("error in AACGM_v2_LoadCoefFP\n"); - #endif - return -2; - } - fclose(fp); - - return 0; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_LoadCoefs -; -; PURPOSE: -; Load two sets of spherical harmonic coefficients. -; -; CALLING SEQUENCE: -; err = AACGM_v2_LoadCoefs(myear); -; -; Input Arguments: -; myear - 5-year epoch year prior to desired time; bracketing -; set is +5 years. -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_LoadCoefs(int year) -{ - char fname[256]; - char root[256]; - char yrstr[5]; - int ret=0; - - #if DEBUG > 0 - printf("AACGM_v2_LoadCoefs\n"); - #endif - /* default location of coefficient files */ - strcpy(root,getenv("AACGM_v2_DAT_PREFIX")); - if (strlen(root)==0) return -1; - - if (year <= 0) return -1; -// if (year==0) year=DEFAULT_YEAR; - sprintf(yrstr,"%4.4d",year); -// strcpy(fname,getenv("AACGM_DAT_PREFIX")); - - strcpy(fname,root); - strcat(fname,yrstr); - strcat(fname,".asc"); - #if DEBUG > 0 - printf("AACGM_v2_LoadCoefs: %s\n", fname); - #endif - ret = AACGM_v2_LoadCoef(fname,G2A); /* forward coefficients */ - if (ret != 0) return ret; - - sprintf(yrstr,"%4.4d",year+5); - strcpy(fname,root); - strcat(fname,yrstr); - strcat(fname,".asc"); - #if DEBUG > 0 - printf("AACGM_v2_LoadCoefs: %s\n", fname); - #endif - ret += AACGM_v2_LoadCoef(fname,A2G); /* inverse coefficients */ - - myear_old = year; - - return ret; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_Convert -; -; PURPOSE: -; Main function called by many SD plotting routines that are written -; in C. -; -; CALLING SEQUENCE: -; err = AACGM_v2_Convert(in_lat, in_lon, height, -; out_lat, out_lon, r, code); -; -; Input Arguments: -; int_lat - double precision input latitude in degrees -; int_lon - double precision input longitude in degrees -; height - altitude in km -; code - bitwise code for passing options into converter -; G2A - geographic (geodetic) to AACGM-v2 -; A2G - AACGM-v2 to geographic (geodetic) -; TRACE - use field-line tracing, not coefficients -; ALLOWTRACE - use trace only above 2000 km -; BADIDEA - use coefficients above 2000 km -; GEOCENTRIC - assume inputs are geocentric w/ RE=6371.2 -; -; Output Arguments: -; out_lat - double precision output latitude in degrees -; out_lon - double precision output longitude in degrees -; r - double radial distance in Re (always = 1.0) -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_Convert(double in_lat, double in_lon, double height, - double *out_lat, double *out_lon, double *r, int code) -{ - int err; - int order=10; /* pass in so a lower order would be allowed? */ - double rtp[3]; - - #if DEBUG > 0 - printf("AACGM_v2_Convert\n"); - #endif - - /* height < 0 km */ - if (height < 0) { - fprintf(stderr, "WARNING: coordinate transformations are not intended " - "for altitudes < 0 km: %lf\n", height); - /* return -2; */ - } - - /* height > 2000 km not allowed for coefficients */ - if (height > MAXALT && !(code & (TRACE|ALLOWTRACE|BADIDEA))) { - fprintf(stderr, "ERROR: coefficients are not valid for altitudes " - "above %d km: %lf.\n", MAXALT, height); - fprintf(stderr, " You must either use field-line tracing " - "(TRACE or ALLOWTRACE) or\n" - " indicate that you know this is a very bad idea " - "(BADIDEA)\n\n"); - return -4; - } - - /* latitude out of bounds */ - if (fabs(in_lat) > 90.) { - fprintf(stderr, "ERROR: latitude must be in the range -90 to +90 degrees: " - "%lf\n", in_lat); - return -8; - } - -// if (in_lon < 0) in_lon += 360.0; -// if (in_lon > 180.0) in_lon -= 360.0; - /* longitude out of bounds */ - if ((in_lon < -180) || (in_lon > 180)) { - fprintf(stderr, "ERROR: longitude must be in the range -180 to 180 " - "degrees: %lf\n", in_lon); - return -16; - } - -//printf("%d %d %d %d\n", -// code, GEOCENTRIC, code & GEOCENTRIC, (code & GEOCENTRIC)==0); - if ((code & GEOCENTRIC) == 0 && (code & A2G) == 0) { -//printf("GEODETIC\n"); - /* coordinates are given in geodetic coordinates and must be converted */ - geod2geoc(in_lat, in_lon, height, rtp); -//printf("lat: %f %f\n", in_lat, 90.d - rtp[1]/DTOR); -//printf("lon: %f %f\n", in_lon, rtp[2]/DTOR); -//printf("alt: %f %f\n", height, (rtp[0]-1.d)*RE); -//printf("\n"); - - /* modify lat/lon/alt to geocentric values */ - in_lat = 90. - rtp[1]/DTOR; - in_lon = rtp[2]/DTOR; - height = (rtp[0]-1.)*RE; - } - - err = convert_geo_coord(in_lat,in_lon,height, out_lat,out_lon, code,order); - *r = 1.0; - - if (err !=0) return -1; - return 0; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_SetDateTime -; -; PURPOSE: -; Function to set date and time. MUST be called at least once BEFORE -; any calls to AACGM_v2_Convert. -; -; CALLING SEQUENCE: -; err = AACGM_v2_SetDateTime(year, month, day, hour, minute, second); -; -; Input Arguments: -; year - year [1900-2020) -; month - month of year [01-12] -; day - day of month [01-31] -; hour - hour of day [00-24] -; minute - minute of hour [00-60] -; second - second of minute [00-60] -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_SetDateTime(int year, int month, int day, - int hour, int minute, int second) -{ - int err, doy, ndays; - double fyear; - - doy = dayno(year,month,day,&ndays); - fyear = year + ((doy-1) + (hour + (minute + second/60.)/60.)/24.) / ndays; - - if ((fyear < IGRF_FIRST_EPOCH) || (fyear >= IGRF_LAST_EPOCH + 5.)) { - AACGM_v2_errmsg(1); - return (-1); - } - - aacgm_date.year = year; - aacgm_date.month = month; - aacgm_date.day = day; - aacgm_date.hour = hour; - aacgm_date.minute = minute; - aacgm_date.second = second; - aacgm_date.dayno = doy; - aacgm_date.daysinyear = ndays; - - #if DEBUG > 0 - printf("AACGM_v2_SetDateTime\n"); - printf("%03d: %04d%02d%02d %02d%02d:%02d\n", - aacgm_date.dayno, aacgm_date.year, aacgm_date.month, aacgm_date.day, - aacgm_date.hour, aacgm_date.minute, aacgm_date.second); - #endif - - err = AACGM_v2_TimeInterp(); - - return err; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_GetDateTime -; -; PURPOSE: -; Function to get date and time. -; -; CALLING SEQUENCE: -; err = AACGM_v2_GetDateTime(year, month, day, hour, minute, second, dayno); -; -; Output Arguments (integer pointers): -; year - year [1965-2014] -; month - month of year [01-12] -; day - day of month [01-31] -; hour - hour of day [00-24] -; minute - minute of hour [00-60] -; second - second of minute [00-60] -; dayno - day of year [01-366] -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_GetDateTime(int *year, int *month, int *day, - int *hour, int *minute, int *second, int *dayno) -{ - *year = aacgm_date.year; - *month = aacgm_date.month; - *day = aacgm_date.day; - *hour = aacgm_date.hour; - *minute = aacgm_date.minute; - *second = aacgm_date.second; - *dayno = aacgm_date.dayno; - - return 0; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_SetNow -; -; PURPOSE: -; Function to set date and time to current computer time in UT. -; -; CALLING SEQUENCE: -; err = AACGM_v2_SetNow(); -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_SetNow(void) -{ - /* current time */ - int err, doy,ndays; - double fyear; - time_t now; - struct tm *tm_now; - - now = time(NULL); - tm_now = gmtime(&now); /* right now in UT */ - - doy = dayno(tm_now->tm_year,tm_now->tm_mon,tm_now->tm_mday,&ndays); - fyear = tm_now->tm_year + ((doy-1) + (tm_now->tm_hour + (tm_now->tm_min + - tm_now->tm_sec/60.)/60.)/24.) / ndays; - - if ((fyear < IGRF_FIRST_EPOCH) || (fyear >= IGRF_LAST_EPOCH + 5.)) { - fprintf(stderr, "\nDate range for AACGM-v2 is [%4d - %4d)\n\n", - IGRF_FIRST_EPOCH, IGRF_LAST_EPOCH + 5); - return (-1); - } - - aacgm_date.year = (*tm_now).tm_year + 1900; - aacgm_date.month = (*tm_now).tm_mon + 1; - aacgm_date.day = (*tm_now).tm_mday; - aacgm_date.hour = (*tm_now).tm_hour; - aacgm_date.minute = (*tm_now).tm_min; - aacgm_date.second = (*tm_now).tm_sec; - aacgm_date.dayno = (*tm_now).tm_yday + 1; - aacgm_date.daysinyear = ndays; - - #if DEBUG > 0 - printf("AACGM_v2_SetNow\n"); - printf("%03d: %04d%02d%02d %02d%02d:%02d\n", - aacgm_date.dayno, aacgm_date.year, aacgm_date.month, aacgm_date.day, - aacgm_date.hour, aacgm_date.minute, aacgm_date.second); - #endif - - err = AACGM_v2_TimeInterp(); - - return err; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_errmsg -; -; PURPOSE: -; Display error message because no date and time have been set. -; -; CALLING SEQUENCE: -; AACGM_v2_errmsg(code); -; -;+----------------------------------------------------------------------------- -*/ - -void AACGM_v2_errmsg(int ecode) -{ - char estr[100]; - - fprintf(stderr, "\n" - "**************************************************************************" - "\n"); - - switch (ecode) { - case 0: /* no Date/Time set */ - fprintf(stderr, - "* AACGM-v2 ERROR: No Date/Time Set *\n" - "* *\n" - "* You must specifiy the date and time in order to use AACGM coordinates, *\n" - "* which depend on the internal (IGRF) magnetic field. Before calling *\n" - "* AACGM_v2_Convert() you must set the date and time to the integer values*\n" - "* using the function: *\n" - "* *\n" - "* AACGM_v2_SetDateTime(year,month,day,hour,minute,second); *\n" - "* *\n" - "* or to the current computer time in UT using the function: *\n" - "* *\n" - "* AACGM_v2_SetNow(); *\n" - "* *\n" - "* subsequent calls to AACGM_v2_Convert() will use the last date and time *\n" - "* that was set, so update to the actual date and time that is desired. *" - "\n"); - break; - - case 1: /* Date/Time out of bounds */ - fprintf(stderr, - "* AACGM-v2 ERROR: Date out of bounds *\n" - "* *\n" - "* The current date range for AACGM-v2 coordinates is [1990-2020), which *\n" - "* corresponds to the date range for the IGRF12 model, including the *\n" - "* 5-year secular variation. *" - "\n"); - break; - } - fprintf(stderr, - "**************************************************************************" - "\n\n"); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_TimeInterp -; -; PURPOSE: -; Interpolate coefficients between adjacent 5-year epochs -; -; CALLING SEQUENCE: -; err = AACGM_v2_TimeInterp(); -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_TimeInterp(void) -{ - int myear,f,l,a,t,err; - double fyear; - - /* myear is the epoch model year */ - myear = aacgm_date.year/5*5; - if (myear != myear_old) { /* load the new coefficients, if needed */ - err = AACGM_v2_LoadCoefs(myear); - if (err != 0) return err; - fyear_old = -1; /* force time interpolation */ - height_old[0] = -1.; /* force height interpolation */ - height_old[1] = -1.; - } - - /* fyear is the floating point time */ - fyear = aacgm_date.year + ((aacgm_date.dayno-1) + (aacgm_date.hour + - (aacgm_date.minute + aacgm_date.second/60.)/60.)/24.)/ - aacgm_date.daysinyear; - - /* time interpolation right here */ - if (fyear != fyear_old) { - #if DEBUG > 0 - printf("** TIME INTERPOLATION **\n"); - #endif - - for (f=0;f 0.) ? -1 : 1; /* N or S hemisphere */ - - dsRE = dsRE0; - - /* - ; trace to magnetic equator - ; - ; Note that there is the possibility that the magnetic equator lies - ; at an altitude above the surface of the Earth but below the starting - ; altitude. I am not certain of the definition of CGM, but these - ; fieldlines map to very different locations than the solutions that - ; lie above the starting altitude. I am considering the solution for - ; this set of fieldlines as undefined; just like those that lie below - ; the surface of the Earth. - */ - while (idir*xyzm[2] < 0.) { - - for (kk=0;kk<3;kk++) xyzp[kk] = xyzg[kk]; /* save as previous */ - - AACGM_v2_RK45(xyzg, idir, &dsRE, eps, 1); // set to 0 for RK4: /noadapt) - - /* convert to magnetic Dipole coordinates */ - geo2mag(xyzg, xyzm); - - k++; - } - niter = k; - - if (niter > 1) { - /* now bisect stepsize (fixed) to land on magnetic equator w/in 1 m */ - for (k=0;k<3;k++) xyzc[k] = xyzp[k]; - kk = 0L; - while (dsRE > 1e-3/RE) { - dsRE *= .5; - for (k=0;k<3;k++) xyzp[k] = xyzc[k]; - AACGM_v2_RK45(xyzc, idir, &dsRE, eps, 0); /* using RK4 */ - geo2mag(xyzc,xyzm); - - /* Is it possible that resetting here causes a doubling of the tol? */ - if (idir * xyzm[2] > 0) - for (k=0;k<3;k++) xyzc[k] = xyzp[k]; - - kk++; - } - niter += kk; - } - - /* 'trace' back to reference surface along Dipole field lines */ - Lshell = sqrt(xyzc[0]*xyzc[0] + xyzc[1]*xyzc[1] + xyzc[2]*xyzc[2]); - if (Lshell < (RE+alt)/RE) { /* magnetic equator is below ... */ - *lat_out = NAN; - *lon_out = NAN; - - err = -1; - } else { - geo2mag(xyzc, xyzm); /* geographic to magnetic */ - car2sph(xyzm, rtp); - - *lat_out = -idir*acos(sqrt(1./Lshell))/DTOR; - *lon_out = rtp[2]/DTOR; - if (*lon_out > 180) *lon_out -= 360.; - - err = 0; - } - - return (err); -} - -int AACGM_v2_Trace_inv(double lat_in, double lon_in, double alt, - double *lat_out, double *lon_out) -{ - int err, kk, idir; - unsigned long k,niter; - double ds, dsRE, dsRE0, eps, Lshell; - double rtp[3],xyzg[3],xyzm[3],xyzc[3],xyzp[3]; - - // Q: will this load coefficients each time??? - /* set date for IGRF model */ - IGRF_SetDateTime(aacgm_date.year, aacgm_date.month, aacgm_date.day, - aacgm_date.hour, aacgm_date.minute, aacgm_date.second); - - // Q: these could eventually be command-line options - ds = 1.; - dsRE = ds/RE; - dsRE0 = dsRE; - eps = 1.e-4/RE; - - // Q: Test this - /* poles map to infinity */ - if (fabs(fabs(lat_in) - 90.) < 1e-6) - lat_in += (lat_in > 0) ? -1e-6 : 1e-6; - - Lshell = 1./(cos(lat_in*DTOR)*cos(lat_in*DTOR)); - if (Lshell <(RE+alt)/RE) { /* solution does not exist; the starting - * position at the magnetic equator is below - * the altitude of interest */ - *lat_out = NAN; - *lon_out = NAN; - err = -1; - } else { - /* magnetic Cartesian coordinates of fieldline trace starting point */ - xyzm[0] = Lshell*cos(lon_in*DTOR); - xyzm[1] = Lshell*sin(lon_in*DTOR); - xyzm[2] = 0.; - - /* geographic Cartesian coordinates of starting point */ - mag2geo(xyzm, xyzg); - - /* geographic spherical coordinates of starting point */ - car2sph(xyzg,rtp); - - k = 0L; - - /* direction of trace is determined by the starting hemisphere? */ - idir = (lat_in > 0) ? 1 : -1; - - dsRE = dsRE0; - - /* trace back to altitude above Earth */ - while (rtp[0] > (RE + alt)/RE) { - for (kk=0;kk<3;kk++) xyzp[kk] = xyzg[kk]; /* save as previous */ - - AACGM_v2_RK45(xyzg, idir, &dsRE, eps, 1); // set to 0 for RK4: /noadapt) - - car2sph(xyzg, rtp); - - k++; - } - niter = k; - - if (niter > 1) { - /* now bisect stepsize (fixed) to land on magnetic equator w/in 1 m */ - for (k=0;k<3;k++) xyzc[k] = xyzp[k]; - kk = 0L; - while (dsRE > 1e-3/RE) { - dsRE *= .5; - for (k=0;k<3;k++) xyzp[k] = xyzc[k]; - AACGM_v2_RK45(xyzc, idir, &dsRE, eps, 0); /* using RK4 */ - - car2sph(xyzc, rtp); - - if (rtp[0] < (RE + alt)/RE) - for (k=0;k<3;k++) xyzc[k] = xyzp[k]; - - kk++; - } - niter += kk; - } - - *lat_out = 90. - rtp[1]/DTOR; - *lon_out = rtp[2]/DTOR; - if (*lon_out > 180) *lon_out -= 360.; - err = 0; - } - - return (0); -} - diff --git a/src/c_aacgm_v2/aacgmlib_v2.h b/src/c_aacgm_v2/aacgmlib_v2.h deleted file mode 100644 index 989169c5..00000000 --- a/src/c_aacgm_v2/aacgmlib_v2.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef _AACGMLIB_ - -#define _AACGMLIB_ - -/***************************************************************************** - * defines and structures - *****************************************************************************/ - -#define RE 6371.2 /* Earth Radius */ -#define MAXALT 2000 /* maximum altitude in km */ -#define NCOORD 3 /* xyz */ -#define POLYORD 5 /* quartic polynomial fit in altitude */ -#define NFLAG 2 /* 0: geo to AACGM, 1: AACGM to geo */ -#define SHORDER 10 /* order of Spherical Harmonic expansion */ -#define AACGM_KMAX ((SHORDER+1)*(SHORDER+1)) /* number of SH coefficients */ - -/* options for AACGM-v2 coordinate determination */ -#define G2A 0 /* convert geographic (geodetic) to AACGM-v2 coords */ -#define A2G 1 /* convert AACGM-v2 to geographic (geodetic) coords */ -#define TRACE 2 /* use field-line tracing to compute coordinates */ -#define ALLOWTRACE 4 /* if height is >2000 km use tracing, else use coefs */ -#define BADIDEA 8 /* use coefficients above 2000 km; Terrible idea!! */ -#define GEOCENTRIC 16 /* assume inputs are geocentric with sphere RE */ - -/***************************************************************************** - * function prototypes - *****************************************************************************/ - -/* private functions */ -int AACGM_v2_Rylm(double colat, double lon, int order, double *ylmval); -void AACGM_v2_Alt2CGM(double r_height_in, double r_lat_alt, double *r_lat_adj); -int AACGM_v2_CGM2Alt(double r_height_in, double r_lat_in, double *r_lat_adj); -double AACGM_v2_Sgn(double a, double b); -int convert_geo_coord(double lat_in, double lon_in, double height_in, - double *lat_out, double *lon_out, int flag, int order); -int AACGM_v2_LoadCoefFP(FILE *fp, int code); -int AACGM_v2_LoadCoef(char *fname, int code); -int AACGM_v2_LoadCoefs(int year); -int AACGM_v2_TimeInterp(void); -void AACGM_v2_errmsg(int ecode); -int AACGM_v2_Trace(double lat_in, double lon_in, double alt, - double *lat_out, double *lon_out); -int AACGM_v2_Trace_inv(double lat_in, double lon_in, double alt, - double *lat_out, double *lon_out); - - -/* public functions */ -int AACGM_v2_Convert(double in_lat, double in_lon, double height, - double *out_lat, double *out_lon, double *r, int code); -int AACGM_v2_SetDateTime(int year, int month, int day, - int hour, int minute, int second); -int AACGM_v2_GetDateTime(int *year, int *month, int *day, - int *hour, int *minute, int *second, int *dayno); -int AACGM_v2_SetNow(void); - -#endif - diff --git a/src/c_aacgm_v2/igrf12coeffs.txt b/src/c_aacgm_v2/igrf12coeffs.txt deleted file mode 100644 index 92304705..00000000 --- a/src/c_aacgm_v2/igrf12coeffs.txt +++ /dev/null @@ -1,199 +0,0 @@ -# 12th Generation International Geomagnetic Reference Field Schmidt semi-normalised spherical harmonic coefficients, degree n=1,13 -# in units nanoTesla for IGRF and definitive DGRF main-field models (degree n=1,8 nanoTesla/year for secular variation (SV)) -c/s deg ord IGRF IGRF IGRF IGRF IGRF IGRF IGRF IGRF IGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF DGRF IGRF SV -g/h n m 1900.0 1905.0 1910.0 1915.0 1920.0 1925.0 1930.0 1935.0 1940.0 1945.0 1950.0 1955.0 1960.0 1965.0 1970.0 1975.0 1980.0 1985.0 1990.0 1995.0 2000.0 2005.0 2010.0 2015.0 2015-20 -g 1 0 -31543 -31464 -31354 -31212 -31060 -30926 -30805 -30715 -30654 -30594 -30554 -30500 -30421 -30334 -30220 -30100 -29992 -29873 -29775 -29692 -29619.4 -29554.63 -29496.57 -29442.0 10.3 -g 1 1 -2298 -2298 -2297 -2306 -2317 -2318 -2316 -2306 -2292 -2285 -2250 -2215 -2169 -2119 -2068 -2013 -1956 -1905 -1848 -1784 -1728.2 -1669.05 -1586.42 -1501.0 18.1 -h 1 1 5922 5909 5898 5875 5845 5817 5808 5812 5821 5810 5815 5820 5791 5776 5737 5675 5604 5500 5406 5306 5186.1 5077.99 4944.26 4797.1 -26.6 -g 2 0 -677 -728 -769 -802 -839 -893 -951 -1018 -1106 -1244 -1341 -1440 -1555 -1662 -1781 -1902 -1997 -2072 -2131 -2200 -2267.7 -2337.24 -2396.06 -2445.1 -8.7 -g 2 1 2905 2928 2948 2956 2959 2969 2980 2984 2981 2990 2998 3003 3002 2997 3000 3010 3027 3044 3059 3070 3068.4 3047.69 3026.34 3012.9 -3.3 -h 2 1 -1061 -1086 -1128 -1191 -1259 -1334 -1424 -1520 -1614 -1702 -1810 -1898 -1967 -2016 -2047 -2067 -2129 -2197 -2279 -2366 -2481.6 -2594.50 -2708.54 -2845.6 -27.4 -g 2 2 924 1041 1176 1309 1407 1471 1517 1550 1566 1578 1576 1581 1590 1594 1611 1632 1663 1687 1686 1681 1670.9 1657.76 1668.17 1676.7 2.1 -h 2 2 1121 1065 1000 917 823 728 644 586 528 477 381 291 206 114 25 -68 -200 -306 -373 -413 -458.0 -515.43 -575.73 -641.9 -14.1 -g 3 0 1022 1037 1058 1084 1111 1140 1172 1206 1240 1282 1297 1302 1302 1297 1287 1276 1281 1296 1314 1335 1339.6 1336.30 1339.85 1350.7 3.4 -g 3 1 -1469 -1494 -1524 -1559 -1600 -1645 -1692 -1740 -1790 -1834 -1889 -1944 -1992 -2038 -2091 -2144 -2180 -2208 -2239 -2267 -2288.0 -2305.83 -2326.54 -2352.3 -5.5 -h 3 1 -330 -357 -389 -421 -445 -462 -480 -494 -499 -499 -476 -462 -414 -404 -366 -333 -336 -310 -284 -262 -227.6 -198.86 -160.40 -115.3 8.2 -g 3 2 1256 1239 1223 1212 1205 1202 1205 1215 1232 1255 1274 1288 1289 1292 1278 1260 1251 1247 1248 1249 1252.1 1246.39 1232.10 1225.6 -0.7 -h 3 2 3 34 62 84 103 119 133 146 163 186 206 216 224 240 251 262 271 284 293 302 293.4 269.72 251.75 244.9 -0.4 -g 3 3 572 635 705 778 839 881 907 918 916 913 896 882 878 856 838 830 833 829 802 759 714.5 672.51 633.73 582.0 -10.1 -h 3 3 523 480 425 360 293 229 166 101 43 -11 -46 -83 -130 -165 -196 -223 -252 -297 -352 -427 -491.1 -524.72 -537.03 -538.4 1.8 -g 4 0 876 880 884 887 889 891 896 903 914 944 954 958 957 957 952 946 938 936 939 940 932.3 920.55 912.66 907.6 -0.7 -g 4 1 628 643 660 678 695 711 727 744 762 776 792 796 800 804 800 791 782 780 780 780 786.8 797.96 808.97 813.7 0.2 -h 4 1 195 203 211 218 220 216 205 188 169 144 136 133 135 148 167 191 212 232 247 262 272.6 282.07 286.48 283.3 -1.3 -g 4 2 660 653 644 631 616 601 584 565 550 544 528 510 504 479 461 438 398 361 325 290 250.0 210.65 166.58 120.4 -9.1 -h 4 2 -69 -77 -90 -109 -134 -163 -195 -226 -252 -276 -278 -274 -278 -269 -266 -265 -257 -249 -240 -236 -231.9 -225.23 -211.03 -188.7 5.3 -g 4 3 -361 -380 -400 -416 -424 -426 -422 -415 -405 -421 -408 -397 -394 -390 -395 -405 -419 -424 -423 -418 -403.0 -379.86 -356.83 -334.9 4.1 -h 4 3 -210 -201 -189 -173 -153 -130 -109 -90 -72 -55 -37 -23 3 13 26 39 53 69 84 97 119.8 145.15 164.46 180.9 2.9 -g 4 4 134 146 160 178 199 217 234 249 265 304 303 290 269 252 234 216 199 170 141 122 111.3 100.00 89.40 70.4 -4.3 -h 4 4 -75 -65 -55 -51 -57 -70 -90 -114 -141 -178 -210 -230 -255 -269 -279 -288 -297 -297 -299 -306 -303.8 -305.36 -309.72 -329.5 -5.2 -g 5 0 -184 -192 -201 -211 -221 -230 -237 -241 -241 -253 -240 -229 -222 -219 -216 -218 -218 -214 -214 -214 -218.8 -227.00 -230.87 -232.6 -0.2 -g 5 1 328 328 327 327 326 326 327 329 334 346 349 360 362 358 359 356 357 355 353 352 351.4 354.41 357.29 360.1 0.5 -h 5 1 -210 -193 -172 -148 -122 -96 -72 -51 -33 -12 3 15 16 19 26 31 46 47 46 46 43.8 42.72 44.58 47.3 0.6 -g 5 2 264 259 253 245 236 226 218 211 208 194 211 230 242 254 262 264 261 253 245 235 222.3 208.95 200.26 192.4 -1.3 -h 5 2 53 56 57 58 58 58 60 64 71 95 103 110 125 128 139 148 150 150 154 165 171.9 180.25 189.01 197.0 1.7 -g 5 3 5 -1 -9 -16 -23 -28 -32 -33 -33 -20 -20 -23 -26 -31 -42 -59 -74 -93 -109 -118 -130.4 -136.54 -141.05 -140.9 -0.1 -h 5 3 -33 -32 -33 -34 -38 -44 -53 -64 -75 -67 -87 -98 -117 -126 -139 -152 -151 -154 -153 -143 -133.1 -123.45 -118.06 -119.3 -1.2 -g 5 4 -86 -93 -102 -111 -119 -125 -131 -136 -141 -142 -147 -152 -156 -157 -160 -159 -162 -164 -165 -166 -168.6 -168.05 -163.17 -157.5 1.4 -h 5 4 -124 -125 -126 -126 -125 -122 -118 -115 -113 -119 -122 -121 -114 -97 -91 -83 -78 -75 -69 -55 -39.3 -19.57 -0.01 16.0 3.4 -g 5 5 -16 -26 -38 -51 -62 -69 -74 -76 -76 -82 -76 -69 -63 -62 -56 -49 -48 -46 -36 -17 -12.9 -13.55 -8.03 4.1 3.9 -h 5 5 3 11 21 32 43 51 58 64 69 82 80 78 81 81 83 88 92 95 97 107 106.3 103.85 101.04 100.2 0.0 -g 6 0 63 62 62 61 61 61 60 59 57 59 54 47 46 45 43 45 48 53 61 68 72.3 73.60 72.78 70.0 -0.3 -g 6 1 61 60 58 57 55 54 53 53 54 57 57 57 58 61 64 66 66 65 65 67 68.2 69.56 68.69 67.7 -0.1 -h 6 1 -9 -7 -5 -2 0 3 4 4 4 6 -1 -9 -10 -11 -12 -13 -15 -16 -16 -17 -17.4 -20.33 -20.90 -20.8 0.0 -g 6 2 -11 -11 -11 -10 -10 -9 -9 -8 -7 6 4 3 1 8 15 28 42 51 59 68 74.2 76.74 75.92 72.7 -0.7 -h 6 2 83 86 89 93 96 99 102 104 105 100 99 96 99 100 100 99 93 88 82 72 63.7 54.75 44.18 33.2 -2.1 -g 6 3 -217 -221 -224 -228 -233 -238 -242 -246 -249 -246 -247 -247 -237 -228 -212 -198 -192 -185 -178 -170 -160.9 -151.34 -141.40 -129.9 2.1 -h 6 3 2 4 5 8 11 14 19 25 33 16 33 48 60 68 72 75 71 69 69 67 65.1 63.63 61.54 58.9 -0.7 -g 6 4 -58 -57 -54 -51 -46 -40 -32 -25 -18 -25 -16 -8 -1 4 2 1 4 4 3 -1 -5.9 -14.58 -22.83 -28.9 -1.2 -h 6 4 -35 -32 -29 -26 -22 -18 -16 -15 -15 -9 -12 -16 -20 -32 -37 -41 -43 -48 -52 -58 -61.2 -63.53 -66.26 -66.7 0.2 -g 6 5 59 57 54 49 44 39 32 25 18 21 12 7 -2 1 3 6 14 16 18 19 16.9 14.58 13.10 13.2 0.3 -h 6 5 36 32 28 23 18 13 8 4 0 -16 -12 -12 -11 -8 -6 -4 -2 -1 1 1 0.7 0.24 3.02 7.3 0.9 -g 6 6 -90 -92 -95 -98 -101 -103 -104 -106 -107 -104 -105 -107 -113 -111 -112 -111 -108 -102 -96 -93 -90.4 -86.36 -78.09 -70.9 1.6 -h 6 6 -69 -67 -65 -62 -57 -52 -46 -40 -33 -39 -30 -24 -17 -7 1 11 17 21 24 36 43.8 50.94 55.40 62.6 1.0 -g 7 0 70 70 71 72 73 73 74 74 74 70 65 65 67 75 72 71 72 74 77 77 79.0 79.88 80.44 81.6 0.3 -g 7 1 -55 -54 -54 -54 -54 -54 -54 -53 -53 -40 -55 -56 -56 -57 -57 -56 -59 -62 -64 -72 -74.0 -74.46 -75.00 -76.1 -0.2 -h 7 1 -45 -46 -47 -48 -49 -50 -51 -52 -52 -45 -35 -50 -55 -61 -70 -77 -82 -83 -80 -69 -64.6 -61.14 -57.80 -54.1 0.8 -g 7 2 0 0 1 2 2 3 4 4 4 0 2 2 5 4 1 1 2 3 2 1 0.0 -1.65 -4.55 -6.8 -0.5 -h 7 2 -13 -14 -14 -14 -14 -14 -15 -17 -18 -18 -17 -24 -28 -27 -27 -26 -27 -27 -26 -25 -24.2 -22.57 -21.20 -19.5 0.4 -g 7 3 34 33 32 31 29 27 25 23 20 0 1 10 15 13 14 16 21 24 26 28 33.3 38.73 45.24 51.8 1.3 -h 7 3 -10 -11 -12 -12 -13 -14 -14 -14 -14 2 0 -4 -6 -2 -4 -5 -5 -2 0 4 6.2 6.82 6.54 5.7 -0.2 -g 7 4 -41 -41 -40 -38 -37 -35 -34 -33 -31 -29 -40 -32 -32 -26 -22 -14 -12 -6 -1 5 9.1 12.30 14.00 15.0 0.1 -h 7 4 -1 0 1 2 4 5 6 7 7 6 10 8 7 6 8 10 16 20 21 24 24.0 25.35 24.96 24.4 -0.3 -g 7 5 -21 -20 -19 -18 -16 -14 -12 -11 -9 -10 -7 -11 -7 -6 -2 0 1 4 5 4 6.9 9.37 10.46 9.4 -0.6 -h 7 5 28 28 28 28 28 29 29 29 29 28 36 28 23 26 23 22 18 17 17 17 14.8 10.93 7.03 3.4 -0.6 -g 7 6 18 18 18 19 19 19 18 18 17 15 5 9 17 13 13 12 11 10 9 8 7.3 5.42 1.64 -2.8 -0.8 -h 7 6 -12 -12 -13 -15 -16 -17 -18 -19 -20 -17 -18 -20 -18 -23 -23 -23 -23 -23 -23 -24 -25.4 -26.32 -27.61 -27.4 0.1 -g 7 7 6 6 6 6 6 6 6 6 5 29 19 18 8 1 -2 -5 -2 0 0 -2 -1.2 1.94 4.92 6.8 0.2 -h 7 7 -22 -22 -22 -22 -22 -21 -20 -19 -19 -22 -16 -18 -17 -12 -11 -12 -10 -7 -4 -6 -5.8 -4.64 -3.28 -2.2 -0.2 -g 8 0 11 11 11 11 11 11 11 11 11 13 22 11 15 13 14 14 18 21 23 25 24.4 24.80 24.41 24.2 0.2 -g 8 1 8 8 8 8 7 7 7 7 7 7 15 9 6 5 6 6 6 6 5 6 6.6 7.62 8.21 8.8 0.0 -h 8 1 8 8 8 8 8 8 8 8 8 12 5 10 11 7 7 6 7 8 10 11 11.9 11.20 10.84 10.1 -0.3 -g 8 2 -4 -4 -4 -4 -3 -3 -3 -3 -3 -8 -4 -6 -4 -4 -2 -1 0 0 -1 -6 -9.2 -11.73 -14.50 -16.9 -0.6 -h 8 2 -14 -15 -15 -15 -15 -15 -15 -15 -14 -21 -22 -15 -14 -12 -15 -16 -18 -19 -19 -21 -21.5 -20.88 -20.03 -18.3 0.3 -g 8 3 -9 -9 -9 -9 -9 -9 -9 -9 -10 -5 -1 -14 -11 -14 -13 -12 -11 -11 -10 -9 -7.9 -6.88 -5.59 -3.2 0.5 -h 8 3 7 7 6 6 6 6 5 5 5 -12 0 5 7 9 6 4 4 5 6 8 8.5 9.83 11.83 13.3 0.1 -g 8 4 1 1 1 2 2 2 2 1 1 9 11 6 2 0 -3 -8 -7 -9 -12 -14 -16.6 -18.11 -19.34 -20.6 -0.2 -h 8 4 -13 -13 -13 -13 -14 -14 -14 -15 -15 -7 -21 -23 -18 -16 -17 -19 -22 -23 -22 -23 -21.5 -19.71 -17.41 -14.6 0.5 -g 8 5 2 2 2 3 4 4 5 6 6 7 15 10 10 8 5 4 4 4 3 9 9.1 10.17 11.61 13.4 0.4 -h 8 5 5 5 5 5 5 5 5 5 5 2 -8 3 4 4 6 6 9 11 12 15 15.5 16.22 16.71 16.2 -0.2 -g 8 6 -9 -8 -8 -8 -7 -7 -6 -6 -5 -10 -13 -7 -5 -1 0 0 3 4 4 6 7.0 9.36 10.85 11.7 0.1 -h 8 6 16 16 16 16 17 17 18 18 19 18 17 23 23 24 21 18 16 14 12 11 8.9 7.61 6.96 5.7 -0.3 -g 8 7 5 5 5 6 6 7 8 8 9 7 5 6 10 11 11 10 6 4 2 -5 -7.9 -11.25 -14.05 -15.9 -0.4 -h 8 7 -5 -5 -5 -5 -5 -5 -5 -5 -5 3 -4 -4 1 -3 -6 -10 -13 -15 -16 -16 -14.9 -12.76 -10.74 -9.1 0.3 -g 8 8 8 8 8 8 8 8 8 7 7 2 -1 9 8 4 3 1 -1 -4 -6 -7 -7.0 -4.87 -3.54 -2.0 0.3 -h 8 8 -18 -18 -18 -18 -19 -19 -19 -19 -19 -11 -17 -13 -20 -17 -16 -17 -15 -11 -10 -4 -2.1 -0.06 1.64 2.1 0.0 -g 9 0 8 8 8 8 8 8 8 8 8 5 3 4 4 8 8 7 5 5 4 4 5.0 5.58 5.50 5.4 0.0 -g 9 1 10 10 10 10 10 10 10 10 10 -21 -7 9 6 10 10 10 10 10 9 9 9.4 9.76 9.45 8.8 0.0 -h 9 1 -20 -20 -20 -20 -20 -20 -20 -20 -21 -27 -24 -11 -18 -22 -21 -21 -21 -21 -20 -20 -19.7 -20.11 -20.54 -21.6 0.0 -g 9 2 1 1 1 1 1 1 1 1 1 1 -1 -4 0 2 2 2 1 1 1 3 3.0 3.58 3.45 3.1 0.0 -h 9 2 14 14 14 14 14 14 14 15 15 17 19 12 12 15 16 16 16 15 15 15 13.4 12.69 11.51 10.8 0.0 -g 9 3 -11 -11 -11 -11 -11 -11 -12 -12 -12 -11 -25 -5 -9 -13 -12 -12 -12 -12 -12 -10 -8.4 -6.94 -5.27 -3.3 0.0 -h 9 3 5 5 5 5 5 5 5 5 5 29 12 7 2 7 6 7 9 9 11 12 12.5 12.67 12.75 11.8 0.0 -g 9 4 12 12 12 12 12 12 12 11 11 3 10 2 1 10 10 10 9 9 9 8 6.3 5.01 3.13 0.7 0.0 -h 9 4 -3 -3 -3 -3 -3 -3 -3 -3 -3 -9 2 6 0 -4 -4 -4 -5 -6 -7 -6 -6.2 -6.72 -7.14 -6.8 0.0 -g 9 5 1 1 1 1 1 1 1 1 1 16 5 4 4 -1 -1 -1 -3 -3 -4 -8 -8.9 -10.76 -12.38 -13.3 0.0 -h 9 5 -2 -2 -2 -2 -2 -2 -2 -3 -3 4 2 -2 -3 -5 -5 -5 -6 -6 -7 -8 -8.4 -8.16 -7.42 -6.9 0.0 -g 9 6 -2 -2 -2 -2 -2 -2 -2 -2 -2 -3 -5 1 -1 -1 0 -1 -1 -1 -2 -1 -1.5 -1.25 -0.76 -0.1 0.0 -h 9 6 8 8 8 8 9 9 9 9 9 9 8 10 9 10 10 10 9 9 9 8 8.4 8.10 7.97 7.8 0.0 -g 9 7 2 2 2 2 2 2 3 3 3 -4 -2 2 -2 5 3 4 7 7 7 10 9.3 8.76 8.43 8.7 0.0 -h 9 7 10 10 10 10 10 10 10 11 11 6 8 7 8 10 11 11 10 9 8 5 3.8 2.92 2.14 1.0 0.0 -g 9 8 -1 0 0 0 0 0 0 0 1 -3 3 2 3 1 1 1 2 1 1 -2 -4.3 -6.66 -8.42 -9.1 0.0 -h 9 8 -2 -2 -2 -2 -2 -2 -2 -2 -2 1 -11 -6 0 -4 -2 -3 -6 -7 -7 -8 -8.2 -7.73 -6.08 -4.0 0.0 -g 9 9 -1 -1 -1 -1 -1 -1 -2 -2 -2 -4 8 5 -1 -2 -1 -2 -5 -5 -6 -8 -8.2 -9.22 -10.08 -10.5 0.0 -h 9 9 2 2 2 2 2 2 2 2 2 8 -7 5 5 1 1 1 2 2 2 3 4.8 6.01 7.01 8.4 0.0 -g 10 0 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -8 -3 1 -2 -3 -3 -4 -4 -3 -3 -2.6 -2.17 -1.94 -1.9 0.0 -g 10 1 -4 -4 -4 -4 -4 -4 -4 -4 -4 11 4 -5 -3 -3 -3 -3 -4 -4 -4 -6 -6.0 -6.12 -6.24 -6.3 0.0 -h 10 1 2 2 2 2 2 2 2 2 2 5 13 -4 4 2 1 1 1 1 2 1 1.7 2.19 2.73 3.2 0.0 -g 10 2 2 2 2 2 2 2 2 2 2 1 -1 -1 4 2 2 2 2 3 2 2 1.7 1.42 0.89 0.1 0.0 -h 10 2 1 1 1 1 1 1 1 1 1 1 -2 0 1 1 1 1 0 0 1 0 0.0 0.10 -0.10 -0.4 0.0 -g 10 3 -5 -5 -5 -5 -5 -5 -5 -5 -5 2 13 2 0 -5 -5 -5 -5 -5 -5 -4 -3.1 -2.35 -1.07 0.5 0.0 -h 10 3 2 2 2 2 2 2 2 2 2 -20 -10 -8 0 2 3 3 3 3 3 4 4.0 4.46 4.71 4.6 0.0 -g 10 4 -2 -2 -2 -2 -2 -2 -2 -2 -2 -5 -4 -3 -1 -2 -1 -2 -2 -2 -2 -1 -0.5 -0.15 -0.16 -0.5 0.0 -h 10 4 6 6 6 6 6 6 6 6 6 -1 2 -2 2 6 4 4 6 6 6 5 4.9 4.76 4.44 4.4 0.0 -g 10 5 6 6 6 6 6 6 6 6 6 -1 4 7 4 4 6 5 5 5 4 4 3.7 3.06 2.45 1.8 0.0 -h 10 5 -4 -4 -4 -4 -4 -4 -4 -4 -4 -6 -3 -4 -5 -4 -4 -4 -4 -4 -4 -5 -5.9 -6.58 -7.22 -7.9 0.0 -g 10 6 4 4 4 4 4 4 4 4 4 8 12 4 6 4 4 4 3 3 3 2 1.0 0.29 -0.33 -0.7 0.0 -h 10 6 0 0 0 0 0 0 0 0 0 6 6 1 1 0 0 -1 0 0 0 -1 -1.2 -1.01 -0.96 -0.6 0.0 -g 10 7 0 0 0 0 0 0 0 0 0 -1 3 -2 1 0 1 1 1 1 1 2 2.0 2.06 2.13 2.1 0.0 -h 10 7 -2 -2 -2 -2 -2 -2 -2 -1 -1 -4 -3 -3 -1 -2 -1 -1 -1 -1 -2 -2 -2.9 -3.47 -3.95 -4.2 0.0 -g 10 8 2 2 2 1 1 1 1 2 2 -3 2 6 -1 2 0 0 2 2 3 5 4.2 3.77 3.09 2.4 0.0 -h 10 8 4 4 4 4 4 4 4 4 4 -2 6 7 6 3 3 3 4 4 3 1 0.2 -0.86 -1.99 -2.8 0.0 -g 10 9 2 2 2 2 3 3 3 3 3 5 10 -2 2 2 3 3 3 3 3 1 0.3 -0.21 -1.03 -1.8 0.0 -h 10 9 0 0 0 0 0 0 0 0 0 0 11 -1 0 0 1 1 0 0 -1 -2 -2.2 -2.31 -1.97 -1.2 0.0 -g 10 10 0 0 0 0 0 0 0 0 0 -2 3 0 0 0 -1 -1 0 0 0 0 -1.1 -2.09 -2.80 -3.6 0.0 -h 10 10 -6 -6 -6 -6 -6 -6 -6 -6 -6 -2 8 -3 -7 -6 -4 -5 -6 -6 -6 -7 -7.4 -7.93 -8.31 -8.7 0.0 -g 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.7 2.95 3.05 3.1 0.0 -g 11 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.7 -1.60 -1.48 -1.5 0.0 -h 11 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.26 0.13 -0.1 0.0 -g 11 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.9 -1.88 -2.03 -2.3 0.0 -h 11 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.3 1.44 1.67 2.0 0.0 -g 11 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.5 1.44 1.65 2.0 0.0 -h 11 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.77 -0.66 -0.7 0.0 -g 11 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 -0.31 -0.51 -0.8 0.0 -h 11 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.6 -2.27 -1.76 -1.1 0.0 -g 11 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.29 0.54 0.6 0.0 -h 11 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0.90 0.85 0.8 0.0 -g 11 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.7 -0.79 -0.79 -0.7 0.0 -h 11 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.7 -0.58 -0.39 -0.2 0.0 -g 11 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.53 0.37 0.2 0.0 -h 11 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.8 -2.69 -2.51 -2.2 0.0 -g 11 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.7 1.80 1.79 1.7 0.0 -h 11 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -1.08 -1.27 -1.4 0.0 -g 11 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.16 0.12 -0.2 0.0 -h 11 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.2 -1.58 -2.11 -2.5 0.0 -g 11 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.2 0.96 0.75 0.4 0.0 -h 11 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.9 -1.90 -1.94 -2.0 0.0 -g 11 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4.0 3.99 3.75 3.5 0.0 -h 11 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -1.39 -1.86 -2.4 0.0 -g 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.2 -2.15 -2.12 -1.9 0.0 -g 12 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.3 -0.29 -0.21 -0.2 0.0 -h 12 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.55 -0.87 -1.1 0.0 -g 12 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0.21 0.30 0.4 0.0 -h 12 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.23 0.27 0.4 0.0 -g 12 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0.89 1.04 1.2 0.0 -h 12 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.5 2.38 2.13 1.9 0.0 -g 12 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.38 -0.63 -0.8 0.0 -h 12 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2.6 -2.63 -2.49 -2.2 0.0 -g 12 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.9 0.96 0.95 0.9 0.0 -h 12 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.61 0.49 0.3 0.0 -g 12 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.5 -0.30 -0.11 0.1 0.0 -h 12 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.40 0.59 0.7 0.0 -g 12 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.46 0.52 0.5 0.0 -h 12 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0 0.01 0.00 -0.1 0.0 -g 12 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.3 -0.35 -0.39 -0.3 0.0 -h 12 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0 0.02 0.13 0.3 0.0 -g 12 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.36 -0.37 -0.4 0.0 -h 12 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.28 0.27 0.2 0.0 -g 12 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 0.08 0.21 0.2 0.0 -h 12 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.87 -0.86 -0.9 0.0 -g 12 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.49 -0.77 -0.9 0.0 -h 12 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.34 -0.23 -0.1 0.0 -g 12 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.08 0.04 0.0 0.0 -h 12 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0.88 0.87 0.7 0.0 -g 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.16 -0.09 0.0 0.0 -g 13 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.88 -0.89 -0.9 0.0 -h 13 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.76 -0.87 -0.9 0.0 -g 13 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.30 0.31 0.4 0.0 -h 13 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0.33 0.30 0.4 0.0 -g 13 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0.28 0.42 0.5 0.0 -h 13 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.8 1.72 1.66 1.6 0.0 -g 13 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.43 -0.45 -0.5 0.0 -h 13 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.54 -0.59 -0.5 0.0 -g 13 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.3 1.18 1.08 1.0 0.0 -h 13 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.0 -1.07 -1.14 -1.2 0.0 -g 13 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.37 -0.31 -0.2 0.0 -h 13 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 -0.04 -0.07 -0.1 0.0 -g 13 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.75 0.78 0.8 0.0 -h 13 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0.63 0.54 0.4 0.0 -g 13 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.4 -0.26 -0.18 -0.1 0.0 -h 13 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.21 0.10 -0.1 0.0 -g 13 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.35 0.38 0.3 0.0 -h 13 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.6 0.53 0.49 0.4 0.0 -g 13 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.1 -0.05 0.02 0.1 0.0 -h 13 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.38 0.44 0.5 0.0 -g 13 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.4 0.41 0.42 0.5 0.0 -h 13 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2 -0.22 -0.25 -0.3 0.0 -g 13 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0 -0.10 -0.26 -0.4 0.0 -h 13 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.5 -0.57 -0.53 -0.4 0.0 -g 13 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 -0.18 -0.26 -0.3 0.0 -h 13 13 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.9 -0.82 -0.79 -0.8 0.0 diff --git a/src/c_aacgm_v2/igrflib.c b/src/c_aacgm_v2/igrflib.c deleted file mode 100644 index c0f3b560..00000000 --- a/src/c_aacgm_v2/igrflib.c +++ /dev/null @@ -1,1478 +0,0 @@ -#include -#include -#include -#include -#include -#include "igrflib.h" -#include "genmag.h" - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -//#define DEBUG 1 -/* TO DO: should these stuff go in igrflib.h? */ - -struct { - int year; - int month; - int day; - int hour; - int minute; - int second; - int dayno; - int daysinyear; -} igrf_date = {-1,-1,-1,-1,-1,-1,-1}; - -struct { - double ctcl; - double ctsl; - double stcl; - double stsl; - double ct0; - double st0; - double cl0; - double sl0; -} geopack = {0.,0.,0.,0.,0.,0.,0.,0.}; - -double IGRF_coef_set[MAXNYR][IGRF_MAXK]; /* all the coefficients */ -double IGRF_svs[IGRF_MAXK]; /* secular variations */ -double IGRF_coefs[IGRF_MAXK]; /* interpolated coefficients */ -int nmx; /* order of expansion */ - -/*----------------------------------------------------------------------------- -; for debugging -;+----------------------------------------------------------------------------- -*/ -void pause(void) -{ - char ch; - - fprintf(stdout, "(Hit Enter to coninue..."); - scanf("%c", &ch); - fprintf(stdout, "\n"); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_loadcoeffs -; -; PURPOSE: -; Load the entire set of spherical harmonic coefficients from the given -; file. -; -; Read the in the coefficients. Note that I am using the same ordering as -; is used in the AACGM code. That is, -; -; l 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 ... -; m 0 -1 0 1 -2 -1 0 1 2 -3 -2 -1 0 1 2 3 -4 -3 -2 -1 0 ... -; -; C & IDL index: k = l * (l+1) + m -; -; k 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... -; -; CALLING SEQUENCE: -; err = IGRF_loadcoeffs(filename); -; -; Input Arguments: -; filename - name of file which contains IGRF coefficients; default -; is current IGRF model: igrf12coeffs.txt -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int IGRF_loadcoeffs(char *filename) -{ - int i,j,k,l,m,n, ll,mm; - int fac, len; - int iyear, nyear; - int dgrf[MAXNYR], epoch[MAXNYR]; - char jnk; - char header[2][MAXSTR], line[MAXSTR]; - double fyear; - double coef, sv; - double Slm[IGRF_MAXK], fctrl[2*IGRF_ORDER+1], dfc[2*IGRF_ORDER]; - FILE *fp; - - #if DEBUG > 0 - printf("IGRF_loadceoffs\n"); - #endif - - #if DEBUG > 0 - printf("Schmidt quasi-normalization factors\n"); - printf("===================================\n\n"); - #endif - - /* factorial */ - fctrl[0] = fctrl[1] = 1.; - for (k=2; k<= 2*IGRF_ORDER; k++) - fctrl[k] = k*fctrl[k-1]; - - //for(k=0; k<=2*IGRF_ORDER; k++) printf("%lf\n", fctrl[k]); - - /* double factorial */ - dfc[1] = 1; - for (k=3; k<2*IGRF_ORDER; k+=2) - dfc[k] = dfc[k-2]*k; - - for (l=0; l<=IGRF_ORDER; l++) { - for (m=0; m<=l; m++) { - k = l * (l+1) + m; /* 1D index for l,m */ - n = l * (l+1) - m; /* 1D index for l,m */ - - fac = (m) ? 2 : 1; - /* Davis 2004; Wertz 1978 recursion - Slm[k] = Slm[n] = sqrt(fac*fctrl[l-m]/fctrl[l+m])*dfc[2*l-1]/fctrl[l-m]; - */ - /* Winch 2004 */ - Slm[k] = Slm[n] = sqrt(fac*fctrl[l-m]/fctrl[l+m]); - - #if DEBUG > 0 - printf("$ %2d %2d %2d %e %e %e\n", l, m, k, fctrl[l-m],fctrl[l+m],Slm[k]); - printf("$ %2d %2d %2d %e %e %e\n", l,-m, n, fctrl[l-m],fctrl[l+m],Slm[n]); - #endif - } - } - - /* get the coefficients */ - fp = fopen(filename, "r"); - if (fp == NULL) { - fprintf(stderr, "File not found: %s\n", filename); - return (-1); - } - - /* read first two header lines */ - for (k=0; k<2; k++) { - jnk = ' '; - m = 0; - while (jnk != '\n') { - fscanf(fp, "%c", &jnk); - header[k][m] = (jnk == '\n') ? (char)0 : jnk; - m++; - } - } - - /* get next line */ - jnk = ' '; - m = 0; - while (jnk != '\n') { - fscanf(fp, "%c", &jnk); - line[m] = (jnk == '\n') ? (char)0 : jnk; - m++; - } - len = m; - #if DEBUG > 0 - fprintf(stderr, "%s\n", line); - #endif - - /* count how many D/IGRF years */ - nyear = 0; - for (m=0; m MAXNYR) { - fprintf(stderr, "Too many years in file: %d\n", nyear); - return (-2); - } - #if DEBUG > 0 - fprintf(stderr, "%d years\n", nyear); - #endif - - iyear = 0; - for (m=0; m 0 - for (m=0; m 0 - fprintf(stderr, "%8.2lf\n", fyear); - #endif - } - - #if DEBUG > 0 - for (m=0; m 0 - fprintf(stderr, "%d %d %d %d %f\n", k, l, n, 0, IGRF_coef_set[n][k]); - #endif - } - fscanf(fp, "%lf", &sv); /* secular variation */ - IGRF_svs[k] = sv * Slm[k]; /* NORMALIZE */ - fscanf(fp, "%c", &jnk); /* */ - - for (m=1; m<=l; m++) { - k = l * (l+1) + m; /* 1D index for l,m */ - fscanf(fp, "%c", &jnk); /* g or h */ - fscanf(fp, "%d %d", &ll, &mm); /* l amd m */ - - for (n=0; n 0 - fprintf(stderr, "%d %d %d %d %f\n", k, l, n, m, IGRF_coef_set[n][k]); - #endif - } - fscanf(fp, "%lf", &sv); /* secular variation */ - IGRF_svs[k] = sv * Slm[k]; /* NORMALIZE */ - fscanf(fp, "%c", &jnk); /* */ - - k = l * (l+1) - m; /* 1D index for l,m */ - fscanf(fp, "%c", &jnk); /* g or h */ - fscanf(fp, "%d %d", &ll, &mm); /* l amd m */ - for (n=0; n 0 - fprintf(stderr, "%d %d %d %d %f\n", k, l, n, -m, IGRF_coef_set[n][k]); - #endif - } - fscanf(fp, "%lf", &sv); /* secular variation */ - IGRF_svs[k] = sv * Slm[k]; /* NORMALIZE */ - - /* note, some files end each line with while others are */ - fscanf(fp, "%c", &jnk); /* */ - if (jnk == 13) fscanf(fp, "%c", &jnk); /* */ - } - - #if DEBUG > 0 - pause(); - #endif - } - fclose(fp); - - #if DEBUG > 0 - for (n=0; n 0 - fprintf(stderr, "%d\n", (2000-1900)/5); - /* print coefficients in order */ - for (l=0; l<=IGRF_ORDER; l++) { - for (m=-l; m<=l; m++) { - k = l * (l+1) + m; - fprintf(stderr, "%2d %3d %3d: %e\n", l,m,k, - IGRF_coef_set[(1980-1900)/5][k]); - } - } - pause(); - #endif - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_Plm -; -; PURPOSE: -; Internal function to compute array of Gaussian Normalized Associated -; Legendre functions and the corresponding derivatives. -; -; CALLING SEQUENCE: -; err = IGRF_Plm(theta, order, plmval, dplmval); -; -; Input Arguments: -; theta - co-latitude in radians -; order - order of expansion, should NOT exceed IGRF_ORDER -; -; Output Arguments: -; plmval - pointer to array for storage of values -; dplmval - pointer to array for storage of derivative values -; -; Return Value: -; error code -; -; Notes: I am using array indexing similar to that used for m=-l to l, -; but here m=0 to l, so the arrays are too big and there are no -; values stored in locations for m<0. Probably should fix that... -; -; values are stored in a 1D array of dimension (order+1)^2. The -; indexing scheme used is: -; -; g h g g h h g g g h h h g g g g h h h h h ... -; l 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 ... -; m 0 -1 0 1 -2 -1 0 1 2 -3 -2 -1 0 1 2 3 -4 -3 -2 -1 0 ... -;C & IDL j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... -;FORTRAN j 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ... -; -;+----------------------------------------------------------------------------- -*/ - -int IGRF_Plm(double theta, int order, double *plmval, double *dplmval) { - int l,m,k,n,p; - double a,b; /* factors */ - double st,ct,kfac; - - if (order > IGRF_ORDER) return (-1); - - st = sin(theta); - ct = cos(theta); - - plmval[0] = 1.; /* 0,0 */ - dplmval[0] = 0.; /* 0,0 */ - /* compute values of P^{l,l} and dP^{l,l}/dtheta */ - for (l=1; l<=order; l++) { - k = l * (l+1) + l; /* l = m */ - n = (l-1) * l + l-1; /* l-1 = m-l, i.e., previous l=m */ - /* Davis 2004; Wertz 1978 recursion - plmval[k] = plmval[n]*st; - dplmval[k] = dplmval[n]*st + plmval[n]*ct; - */ - /* numerical recipies in C */ -// a = 1-2*l; /* reverse order to remove Condon-Shortley phase */ - a = 2*l-1; - plmval[k] = a*plmval[n]*st; - dplmval[k] = a*(dplmval[n]*st + plmval[n]*ct); - - #if DEBUG > 0 - printf("%2d %3d %e %e\n", l, k, plmval[k], dplmval[k]); - #endif - } - - plmval[2] = ct; /* 1,0 */ - dplmval[2] = -st; /* 1,0 */ - /* compute values of P^{l,m} and dP^{l,m}/dtheta */ - for (l=2; l<=order; l++) { - for (m=0; m 0 - printf("%2d %2d %3d %e %e\n", l, m, k, plmval[k], dplmval[k]); - #endif - } - } - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_compute -; -; PURPOSE: -; User function to compute IGRF magnetic field at lat/lon and distance. -; -; CALLING SEQUENCE: -; err = IGRF_compute(r, theta, phi, Br, Btheta, Bphi); -; -; Input Arguments: -; r - geocentric distance in km -; theta - co-latitude in radians -; phi - longitude in radians -; -; Output Arguments: -; Br - pointer to field in radial direction -; Btheta - pointer to field in co-latitude direction -; Bphi - pointer to field in longtitude direction -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int IGRF_compute(const double rtp[], double brtp[]) { - - int k,l,m,n; -// double brr,btt,bpp; - double tbrtp[3], st, theta; - double aor, afac; - double dplmval[IGRF_MAXK], plmval[IGRF_MAXK]; - double cosm_arr[IGRF_ORDER+1], sinm_arr[IGRF_ORDER+1]; - - #if DEBUG > 0 - printf("IGRF_compute\n"); - #endif - - /* no date/time set so bail */ - if (igrf_date.year < 0) { - IGRF_msg_notime(); - return -128; - } - - /* Must avoid singularit at the poles (dividing by sin(theta) later) */ - theta = rtp[1]; - st = sin(theta); - if (fabs(st) < 1e-15) theta += (st < 0.) ? 1e-15 : -1e-15; - - /* Compute the values of the Legendre Polynomials, and derivatives */ - IGRF_Plm(theta,nmx,plmval,dplmval); - -// aor = RE/r; /* a/r, where RE = a */ -// aor = RE/rtp[0]; /* a/r, where RE = a */ - aor = 1./rtp[0]; /* r is in units of RE to be consistent with geopack, */ - /* we want RE/r */ - - //printf("aor = %lf\n", aor); - afac = aor*aor; - - /* array of trig functions in phi for faster computation */ - for (k=0; k<=IGRF_ORDER; k++) { -// cosm_arr[k] = cos(k*phi); -// sinm_arr[k] = sin(k*phi); - cosm_arr[k] = cos(k*rtp[2]); - sinm_arr[k] = sin(k*rtp[2]); - } - -// *br = *btheta = *bphi = 0.; - for (k=0;k<3;k++) brtp[k] = 0; - - for (l=1; l<=nmx; l++) { /* no l = 0 term in IGRF */ -// brr = btt = bpp = 0.; - for (k=0;k<3;k++) tbrtp[k] = 0; -// printf("l = %d, afac = %lf\n", l, afac); - for (m=0; m<=l; m++) { - k = l*(l+1) + m; /* g */ - n = l*(l+1) - m; /* h */ - - tbrtp[0] += (IGRF_coefs[k]*cosm_arr[m] + IGRF_coefs[n]*sinm_arr[m]) * - plmval[k]; - tbrtp[1] += (IGRF_coefs[k]*cosm_arr[m] + IGRF_coefs[n]*sinm_arr[m]) * - dplmval[k]; - tbrtp[2] += (-IGRF_coefs[k]*sinm_arr[m] + IGRF_coefs[n]*cosm_arr[m]) * - m*plmval[k]; - -// printf("%2d %2d %e %e %e\n", l,m, IGRF_coefs[k],IGRF_coefs[n], plmval[k]); -// printf("[]: %e %e %e\n", tbrtp[0], tbrtp[1], tbrtp[2]); -// printf(" %2d: brr=%lf, coef[k]=%lf, coef[n]=%lf, plmval[k]=%lf\n", -// m,brr,IGRF_coefs[k],IGRF_coefs[n],plmval[k]); -// printf(" %2d: brr=%lf, cosm=%lf, sinm=%lf\n", m,brr,cosm_arr[m],sinm_arr[m]); - } -// printf("%2d brr = %lf\n", l,brr); - afac *= aor; - -// *br += afac*(l+1)*brr; -// *btheta -= afac*btt; -// *bphi -= afac*bpp; - brtp[0] += afac*(l+1)*tbrtp[0]; - brtp[1] -= afac*tbrtp[1]; - brtp[2] -= afac*tbrtp[2]; - } - -// *bphi /= sin(theta); -//printf("*** %e %e\n", brtp[2], sin(rtp[1])); -//printf("*** %e %e %e\n", brtp[0], brtp[1], brtp[2]); - brtp[2] /= sin(theta); -// if (sin(rtp[1]) > 1.e-19) { /* from geopack ... */ -// brtp[2] /= sin(rtp[1]); -// } else { -// if (cos(rtp[1]) < 0.) brtp[2] = -brtp[2]; -// } -//printf("*** %e %e %e\n", brtp[0], brtp[1], brtp[2]); - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_interpolate_coefs -; -; PURPOSE: -; Function to compute interpolated coefficients. -; -; CALLING SEQUENCE: -; err = IGRF_interpolate_coefs(); -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ -int IGRF_interpolate_coefs(void) { - - int i,k,l,m, myear; - double fyear; - double g10,g11,h11,sq,sqq,sqr; - - #if DEBUG > 0 - printf("** TIME INTERPOLATION **\n"); - #endif - - /* fyear is the floating point time */ - fyear = igrf_date.year + ((igrf_date.dayno-1) + (igrf_date.hour + - (igrf_date.minute + igrf_date.second/60.)/60.)/24.)/ - igrf_date.daysinyear; - - /* NOTE: FORTRAN code allows 10-year extrapolation beyond last epoch. - * Here we are limiting to only 5 */ - if (fyear < IGRF_FIRST_EPOCH || fyear > IGRF_LAST_EPOCH + 5) { - /* reset date */ - igrf_date.year = igrf_date.month = igrf_date.day = -1; - igrf_date.hour = igrf_date.minute = igrf_date.second = -1; - igrf_date.dayno = igrf_date.daysinyear = -1; - - fprintf(stdout, "Date range for current IGRF model is: %4d to %4d\n\n", - IGRF_FIRST_EPOCH, IGRF_LAST_EPOCH+5); - return (-3); - } - - myear = igrf_date.year/5*5; /* epoch year */ - nmx = (igrf_date.year < 1995) ? 10 : 13; /* order of expansion */ - i = (myear - IGRF_FIRST_EPOCH)/5; /* index of first set of coefs */ - - if (fyear < IGRF_LAST_EPOCH) { - /* interpolate bounding coefficients */ - for (l=1; l<=nmx; l++) { /* no l = 0 term in IGRF */ - for (m=-l; m<=l; m++) { - k = l * (l+1) + m; /* SGS: changes indexing */ - IGRF_coefs[k] = IGRF_coef_set[i][k] + (fyear-myear)* - (IGRF_coef_set[i+1][k]-IGRF_coef_set[i][k])/5; - } - } - } else { - /* use secular varation */ - for (l=1; l<=nmx; l++) { /* no l = 0 term in IGRF */ - for (m=-l; m<=l; m++) { - k = l * (l+1) + m; /* SGS: changes indexing */ - IGRF_coefs[k] = IGRF_coef_set[i][k] + (fyear-myear)*IGRF_svs[k]; - } - } - } - - /* compute the components of the unit vector EzMag in geographic coordinates: - * sin(theta0)*cos(lambda0), sin(theta0)*sin(lambda0) - */ - -/* C & IDL index: k = l * (l+1) + m */ - g10 = -IGRF_coefs[2]; /* 1*2+0 = 2 */ - g11 = IGRF_coefs[3]; /* 1*2+1 = 3 */ - h11 = IGRF_coefs[1]; /* 1*2-1 = 1 */ - - sq = g11*g11 + h11*h11; - - sqq = sqrt(sq); - sqr = sqrt(g10*g10 + sq); - - geopack.sl0 = -h11/sqq; - geopack.cl0 = -g11/sqq; - geopack.st0 = sqq/sqr; - geopack.ct0 = g10/sqr; - - geopack.stcl = geopack.st0*geopack.cl0; - geopack.stsl = geopack.st0*geopack.sl0; - geopack.ctsl = geopack.ct0*geopack.sl0; - geopack.ctcl = geopack.ct0*geopack.cl0; - - #if DEBUG > 0 - printf("sl0 = %lf\n", geopack.sl0); - printf("cl0 = %lf\n", geopack.cl0); - printf("st0 = %lf\n", geopack.st0); - printf("ct0 = %lf\n", geopack.ct0); - printf("stcl = %lf\n", geopack.stcl); - printf("stsl = %lf\n", geopack.stsl); - printf("ctsl = %lf\n", geopack.ctsl); - printf("ctcl = %lf\n", geopack.ctcl); - #endif - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_SetDateTime -; -; PURPOSE: -; Function to set date and time. MUST be called at least once BEFORE -; any calls to IGRF functions. -; -; CALLING SEQUENCE: -; err = IGRF_SetDateTime(year, month, day, hour, minute, second); -; -; Input Arguments: -; year - year [1965-2014] -; month - month of year [01-12] -; day - day of month [01-31] -; hour - hour of day [00-24] -; minute - minute of hour [00-60] -; second - second of minute [00-60] -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int IGRF_SetDateTime(int year, int month, int day, - int hour, int minute, int second) -{ - char *fname; - int err; - - /* load coefficients if not already loaded */ - if (igrf_date.year != year || igrf_date.month != month || igrf_date.day != day) { - fname = getenv("IGRF_12_COEFFS"); - if (fname==NULL) { - err = IGRF_loadcoeffs(IGRF_FILE); - } else { - err = IGRF_loadcoeffs(fname); - } - } - - - if (err) return (err); - - igrf_date.year = year; - igrf_date.month = month; - igrf_date.day = day; - igrf_date.hour = hour; - igrf_date.minute = minute; - igrf_date.second = second; - igrf_date.dayno = dayno(year,month,day,&(igrf_date.daysinyear)); - - #if DEBUG > 0 - printf("IGRF_SetDateTime\n"); - printf("%03d: %04d%02d%02d %02d%02d:%02d\n", - igrf_date.dayno, igrf_date.year, igrf_date.month, igrf_date.day, - igrf_date.hour, igrf_date.minute, igrf_date.second); - #endif - - err = IGRF_interpolate_coefs(); - - return (err); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_GetDateTime -; -; PURPOSE: -; Function to get date and time. -; -; CALLING SEQUENCE: -; err = IGRF_GetDateTime(year, month, day, hour, minute, second, dayno); -; -; Output Arguments (integer pointers): -; year - year [1965-2014] -; month - month of year [01-12] -; day - day of month [01-31] -; hour - hour of day [00-24] -; minute - minute of hour [00-60] -; second - second of minute [00-60] -; dayno - day of year [01-366] -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int IGRF_GetDateTime(int *year, int *month, int *day, - int *hour, int *minute, int *second, int *dayno) -{ - *year = igrf_date.year; - *month = igrf_date.month; - *day = igrf_date.day; - *hour = igrf_date.hour; - *minute = igrf_date.minute; - *second = igrf_date.second; - *dayno = igrf_date.dayno; - - return 0; -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_SetNow -; -; PURPOSE: -; Function to set date and time to current computer time in UT. -; -; CALLING SEQUENCE: -; err = IGRF_SetNow(); -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int IGRF_SetNow(void) -{ - /* current time */ - char *fname; - int err,dyno; - double fyear; - time_t now; - struct tm *tm_now; - - /* load coefficients if not already loaded */ - if (igrf_date.year != (*tm_now).tm_year + 1900 || igrf_date.month != (*tm_now).tm_mon + 1 || igrf_date.day != (*tm_now).tm_mday) { - fname = getenv("IGRF_12_COEFFS"); - if (fname==NULL) { - err = IGRF_loadcoeffs(IGRF_FILE); - } else { - err = IGRF_loadcoeffs(fname); - } - } - - if (err) return (err); - - now = time(NULL); - tm_now = gmtime(&now); /* right now in UT */ - - igrf_date.year = (*tm_now).tm_year + 1900; - igrf_date.month = (*tm_now).tm_mon + 1; - igrf_date.day = (*tm_now).tm_mday; - igrf_date.hour = (*tm_now).tm_hour; - igrf_date.minute = (*tm_now).tm_min; - igrf_date.second = (*tm_now).tm_sec; - igrf_date.dayno = (*tm_now).tm_yday + 1; - dyno = dayno(igrf_date.year,0,0,&(igrf_date.daysinyear)); - - #if DEBUG > 0 - printf("IGRF_SetNow\n"); - printf("%03d: %04d%02d%02d %02d%02d:%02d\n", - igrf_date.dayno, igrf_date.year, igrf_date.month, igrf_date.day, - igrf_date.hour, igrf_date.minute, igrf_date.second); - #endif - - fprintf(stderr, "\nIGRF: No date/time specified, using current time: "); - fprintf(stderr, "%04d%02d%02d %02d%02d:%02d\n\n", - igrf_date.year, igrf_date.month, igrf_date.day, - igrf_date.hour, igrf_date.minute, igrf_date.second); - - err = IGRF_interpolate_coefs(); - - return (err); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; IGRF_msg_notime -; -; PURPOSE: -; Display error message because no date and time have been set. -; -; CALLING SEQUENCE: -; IGRF_msg_notime(); -; -;+----------------------------------------------------------------------------- -*/ - -void IGRF_msg_notime(void) { -fprintf(stderr, -"\n" -"***************************************************************************\n" -"* IGRF ERROR: No Date/Time Set *\n" -"* *\n" -"* You must specifiy the date and time in order to use IGRF models. Before *\n" -"* calling IGRF functions you must set the date and time to the integer *\n" -"* using the function: *\n" -"* *\n" -"* IGRF_SetDateTime(year,month,day,hour,minute,second); *\n" -"* *\n" -"* or to the current computer time in UT using the function: *\n" -"* *\n" -"* IGRF_SetNow(); *\n" -"* *\n" -"* subsequent calls to IGRF functions will use the last date and time *\n" -"* that was set, so update to the actual date and time that is desired. *\n" -"***************************************************************************" -"\n\n"); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; sph2car -; -; PURPOSE: -; Converts spherical coordinates into Cartesian coordinates. -; -; CALLING SEQUENCE: -; err = sph2car(r,theta,phi, x,y,z); -; -; Input Arguments: -; r - geocentric distance [RE, where RE=6371.2 km] -; theta - co-latitude [radians] -; phi - longitude [radians] -; -; Output Arguments (pointers to type double): -; x - Cartesian components -; y -; z -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ -//int sph2car(double r,double theta,double phi, double *x,double *y,double *z) { -int sph2car(const double rtp[], double xyz[]) { - double sq; - - sq = rtp[0]*sin(rtp[1]); - xyz[0] = sq*cos(rtp[2]); - xyz[1] = sq*sin(rtp[2]); - xyz[2] = rtp[0]*cos(rtp[1]); -/* - sq = r*sin(theta); - *x = sq*cos(phi); - *y = sq*sin(phi); - *z = r *cos(theta); -*/ - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; car2sph -; -; PURPOSE: -; Converts Cartesian coordinates into spherical coordinates. -; -; CALLING SEQUENCE: -; err = car2sph(x,y,z, r,theta,phi); -; -; Input Arguments -; x - Cartesian components [RE] -; y -; z -; -; Output Arguments: -; r - geocentric distance [RE] -; theta - co-latitude [radians] -; phi - longitude [radians] -; -; Return Value: -; error code -; -; Note: at the poles (x=0 and y=0) it is assumed that phi=0 -; -;+----------------------------------------------------------------------------- -*/ - -//int car2sph(double x,double y,double z, double *r,double *theta,double *phi) { -int car2sph(const double xyz[], double rtp[]) { - double sq; - - sq = xyz[0]*xyz[0] + xyz[1]*xyz[1]; - rtp[0] = sqrt(sq + xyz[2]*xyz[2]); - - if (sq == 0.) { - rtp[2] = 0.; - rtp[1] = (xyz[2] < 0) ? M_PI : 0.; - } else { - sq = sqrt(sq); - rtp[2] = atan2(xyz[1],xyz[0]); - rtp[1] = atan2(sq,xyz[2]); - if (rtp[2] < 0) rtp[2] += 2*M_PI; - } -/* - sq = x*x + y*y; - *r = sqrt(sq + z*z); - - if (sq == 0.) { - *phi = 0.; - *theta = (z < 0) ? M_PI : 0.; - } else { - sq = sqrt(sq); - *phi = atan2(y,x); - *theta = atan2(sq,z); - if (*phi < 0) *phi += 2*M_PI; - } -*/ - - return (0); - } - -/*----------------------------------------------------------------------------- -; -; NAME: -; bspcar -; -; PURPOSE: -; Converts spherical field components to Cartesian components. -; -; CALLING SEQUENCE: -; err = bspcar(theta,phi, br,btheta,bphi, bx,by,bz); -; -; Input Arguments -; theta - colatitude of point [radians] -; phi - longitude of point [radians] -; br - radial component [nT]; radially positive -; btheta - colatitude component [nT]; southward positive -; bphi - longitude component [nT]; eastward positive -; -; Output Arguments: -; bx - Cartesian components [RE] -; by -; bz -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -//int bspcar(double theta,double phi, double br,double btheta,double bphi, -// double *bx, double *by, double *bz) { -int bspcar(double theta,double phi, const double brtp[], double bxyz[]) { - double st,ct,sp,cp,be; - - st = sin(theta); - ct = cos(theta); - sp = sin(phi); - cp = cos(phi); - be = brtp[0]*st + brtp[1]*ct; - - bxyz[0] = be*cp - brtp[2]*sp; - bxyz[1] = be*sp + brtp[2]*cp; - bxyz[2] = brtp[0]*ct - brtp[1]*st; -/* - st = sin(theta); - ct = cos(theta); - sp = sin(phi); - cp = cos(phi); - be = br*st + btheta*ct; - - *bx = be*cp - bphi*sp; - *by = be*sp + bphi*cp; - *bz = br*ct - btheta*st; -*/ - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; bcarsp -; -; PURPOSE: -; Converts Cartesian field components into spherical components. -; -; CALLING SEQUENCE: -; err = bcarsp(theta,phi, bx,by,bz, br,btheta,bphi); -; -; Input Arguments -; x,y,z - Cartesian components of point -; bx,by,bz - Cartesian field components [nT] -; -; Output Arguments: -; br - spherical field components [nT] -; btheta -; bphi -; -; Return Value: -; error code -; -; Note: at the poles (theta=0 or pi) it is assumed that phi=0 and therefore -; btheta=bx and bphi=by -; -;+----------------------------------------------------------------------------- -*/ - -//int bcarsp(double x,double y,double z, double bx,double by,double bz, -// double *br,double *btheta,double *bphi) { -int bcarsp(const double xyz[], const double bxyz[], double brtp[]) { - double r,rho,rho2,cp,sp,ct,st; - - rho2 = xyz[0]*xyz[0] + xyz[1]*xyz[1]; - r = sqrt(rho2 + xyz[2]*xyz[2]); - rho = sqrt(rho2); - - if (rho == 0.) { - cp = 1.; - sp = 0.; - } else { - cp = xyz[0]/rho; - sp = xyz[1]/rho; - } - - ct = xyz[2]/r; - st = rho/r; - - brtp[0] = (xyz[0]*bxyz[0] + xyz[1]*bxyz[1] + xyz[2]*bxyz[2])/r; - brtp[1] = (bxyz[0]*cp + bxyz[1]*sp)*ct - bxyz[1]*st; - brtp[2] = bxyz[1]*cp - bxyz[0]*sp; -/* - rho2 = x*x+y*y; - r = sqrt(rho2 + z*z); - rho = sqrt(rho2); - - if (rho == 0.) { - cp = 1.; - sp = 0.; - } else { - cp = x/rho; - sp = y/rho; - } - - ct = z/r; - st = rho/r; - - *br = (x*bx + y*by + z*bz)/r; - *btheta = (bx*cp + by*sp)*ct - bz*st; - *bphi = by*cp - bx*sp; -*/ - - return (0); -} - - -//int geo2mag(double xg,double yg,double zg, double *xm,double *ym,double *zm) { -int geo2mag(const double xyzg[], double xyzm[]) { - - xyzm[0] = xyzg[0]*geopack.ctcl + xyzg[1]*geopack.ctsl - xyzg[2]*geopack.st0; - xyzm[1] = xyzg[1]*geopack.cl0 - xyzg[0]*geopack.sl0; - xyzm[2] = xyzg[0]*geopack.stcl + xyzg[1]*geopack.stsl + xyzg[2]*geopack.ct0; -/* - *xm = xg*geopack.ctcl + yg*geopack.ctsl - zg*geopack.st0; - *ym = yg*geopack.cl0 - xg*geopack.sl0; - *zm = xg*geopack.stcl + yg*geopack.stsl + zg*geopack.ct0; -*/ - - return (0); -} - -//int mag2geo(double xm,double ym,double zm, double *xg,double *yg,double *zg) { -int mag2geo(const double xyzm[], double xyzg[]) { - - xyzg[0] = xyzm[0]*geopack.ctcl - xyzm[1]*geopack.sl0 + xyzm[2]*geopack.stcl; - xyzg[1] = xyzm[0]*geopack.ctsl + xyzm[1]*geopack.cl0 + xyzm[2]*geopack.stsl; - xyzg[2] = xyzm[2]*geopack.ct0 - xyzm[0]*geopack.st0; -/* - *xg = xm*geopack.ctcl - ym*geopack.sl0 + zm*geopack.stcl; - *yg = xm*geopack.ctsl + ym*geopack.cl0 + zm*geopack.stsl; - *zg = zm*geopack.ct0 - xm*geopack.st0; -*/ - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; geod2geoc -; -; PURPOSE: -; Convert from geodetic coordinates (as specified by WGS84) to -; geocentric coordinates using algorithm from IGRF Fortran code. -; -; CALLING SEQUENCE: -; err = geod2geoc(lat,lon,alt, rtp); -; -; Input Arguments: -; lat,lon - geodetic latitude and longitude [degrees N and E] -; alt - distance above sea level [km] -; -; Output Argument: -; rtp[3] - geocentric coordinates: radial distance from center -; of Earth [RE], angle from north pole [radians], -; azimuthal angle [radians] -; -; Return Value: -; error code -; -;+----------------------------------------------------------------------------- -*/ - -int geod2geoc(double lat, double lon, double alt, double rtp[]) { - - double a,b,f,a2,b2,st,ct,one,two,three,rho,cd,sd; - double r,theta,phi; - - a = 6378.1370; /* semi-major axis */ - f = 1./298.257223563; /* flattening */ - b = a*(1. -f); /* semi-minor axis */ - a2 = a*a; - b2 = b*b; - theta = (90. -lat)*DTOR; /* colatitude in radians */ - st = sin(theta); - ct = cos(theta); - one = a2*st*st; - two = b2*ct*ct; - three = one + two; - rho = sqrt(three); /* [km] */ - r = sqrt(alt*(alt+2*rho) + (a2*one + b2*two)/three); /* [km] */ - cd = (alt+rho)/r; - sd = (a2-b2)/rho *ct*st/r; - - rtp[0] = r/RE; /* units of RE */ - rtp[1] = acos(ct*cd - st*sd); - rtp[2] = lon*DTOR; - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; plh2xyz -; -; PURPOSE: -; Convert from geodetic coordinates (as specified by WGS84) to geocentric -; coordinates (RE = 6371.2 km) using an alternate method from wikipedia. -; -; CALLING SEQUENCE: -; [r,theta,phi] = geod2geoc(lat,lon,alt, rtp) -; -; Input Arguments: -; lat,lon - geodetic latitude and longitude [degrees N and E] -; alt - distance above sea level [km] -; -; Output Argument: -; rtp[3] - r: radial distance from center of Earth [RE], -; theta: angle from north pole [radians], -; phi: azimuthal angle [radians] -; Return Value: -; err - error code -; -;+----------------------------------------------------------------------------- -*/ - -int plh2xyz(double lat, double lon, double alt, double rtp[]) -{ - double a,b,f,ee,st,ct,sp,cp,N,Nac,x,y,z,r,t; - - a = 6378.1370; /* semi-major axis */ - f = 1./298.257223563; /* flattening */ - b = a*(1. -f); /* semi-minor axis */ - ee = (2. - f) * f; - - st = sin(lat*DTOR); - ct = cos(lat*DTOR); - sp = sin(lon*DTOR); - cp = cos(lon*DTOR); - - N = a / sqrt(1. - ee*st*st); - Nac = (N + alt) * ct; - - x = Nac * cp; - y = Nac * sp; - z = (N*(1. - ee)+alt) * st; - - r = sqrt(Nac*Nac + z*z); - t = acos(z/r); - - rtp[0] = r/RE; /* units of RE */ - rtp[1] = t; - rtp[2] = lon*DTOR; - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; geoc2geod -; -; PURPOSE: -; Convert from geocentric coordinates (RE = 6371.2 km) to geodetic -; coordinates (as specified by WGS84) using algorithm from wikipedia. -; -; CALLING SEQUENCE: -; [lat,lon,h] = geoc2geod(lat,lon,r, llh) -; -; Input Arguments: -; lat,lon - geocentric latitude and longitude [degrees N and E] -; r - radial distance from center of Earth [RE] -; -; Output Argument: -; llh[3] - geodetic latitude and longitude using WGS84 [radians], -; distance above sea level [km] -; Return Value: -; err - error code -; -;+----------------------------------------------------------------------------- -*/ - -int geoc2geod(double lat, double lon, double r, double llh[]) -{ - double a,f,b,ee,e4,aa, theta,phi, st,ct,sp,cp, x,y,z; - double k0i,pp,zeta,rho,s,rho3,t,u,v,w,kappa; - - a = 6378.1370; /* semi-major axis */ - f = 1./298.257223563; /* flattening */ - b = a*(1. -f); /* semi-minor axis */ - ee = (2. - f) * f; - e4 = ee*ee; - aa = a*a; - - theta = (90. - lat)*DTOR; - phi = lon * DTOR; - - st = sin(theta); - ct = cos(theta); - sp = sin(phi); - cp = cos(phi); - - x = r*RE * st * cp; - y = r*RE * st * sp; - z = r*RE * ct; - - k0i = 1. - ee; - pp = x*x + y*y; - zeta = k0i*z*z/aa; - rho = (pp/aa + zeta - e4)/6.; - s = e4*zeta*pp/(4.*aa); - rho3 = rho*rho*rho; - t = pow(rho3 + s + sqrt(s*(s+2*rho3)), 1./3.); - u = rho + t + rho*rho/t; - v = sqrt(u*u + e4*zeta); - w = ee*(u + v - zeta)/(2.*v); - kappa = 1. + ee*(sqrt(u+v+w*w) + w)/(u + v); - - llh[0] = atan2(z*kappa,sqrt(pp))/DTOR; - llh[1] = lon; - llh[2] = sqrt(pp + z*z*kappa*kappa)/ee * (1./kappa - k0i); - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_Newval -; -; PURPOSE: -; Advance position along magnetic field line by one step, i.e., -; numerical field-line tracing. -; -; CALLING SEQUENCE: -; k = AACGM_v2_Newval(xyz, dir, ds) -; -; Input Arguments: -; xyz - Cartesian position -; dir - direction along field-line to trace -; ds - stepsize to take -; -; Return value: -; k[3] - rate functions evaluated -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_Newval(double xyz[], int idir, double ds, double k[]) { - int j; - double rtp[3], brtp[3], bxyz[3]; - double bmag; - - car2sph(xyz, rtp); /* convert to spherical coords */ - IGRF_compute(rtp, brtp); /* compute the IGRF field here */ - bspcar(rtp[1],rtp[2], brtp, bxyz); /* convert field to Cartesian */ - - bmag = sqrt(bxyz[0]*bxyz[0] + bxyz[1]*bxyz[1] + bxyz[2]*bxyz[2]); - for (j=0; j<3; j++) k[j] = ds*idir*bxyz[j]/bmag; - - return (0); -} - -/*----------------------------------------------------------------------------- -; -; NAME: -; AACGM_v2_RK45 -; -; PURPOSE: -; Advance position along magnetic field line by one step, i.e., -; numerical field-line tracing using either a fixed stepsize RK4 method -; or a Runge-Kutta-Fehlberg adaptive stepsize ODE solver. -; -; CALLING SEQUENCE: -; AACGM_v2_RK45, xyz, dir, ds, eps, fixed=fixed, max_ds=max_ds, RRds=RRds -; -; Input Arguments: -; xyz - Cartesian position -; dir - direction along field-line to trace -; ds - stepsize to take -; -; Keywords: -; fixed - set this keyword to do RK4 method with stepsize ds -; max_ds - maximum stepsize that is allowed, in units of RE -; RRds - set to use a maximum stepsize that is proportional -; to cube of the distance from the origin. -; -; Return Value: -; err - error code -; -; NOTES: -; -; position variables (x,y,z) are modified directly -; -; HISTORY: -; -; Revision 1.0 150122 SGS initial version -; -;+----------------------------------------------------------------------------- -*/ - -int AACGM_v2_RK45(double xyz[], int idir, double *ds, double eps, int code) { - char ch; - int k; - double bmag,tmp,rr,delt; - double k1[3],k2[3],k3[3],k4[3],k5[3],k6[3], w1[3],w2[3]; - double rtp[3], brtp[3], bxyz[3]; - double xyztmp[3]; - -//function test_aacgm_rk45, x,y,z, idir, ds, eps, noadapt=noadapt, $ -// max_ds=max_ds, RRds=RRds - -/* - ; if noadapt is set then just do straight RK4 and ds is spatial step size - ; in kilometers - ; default is to do adapative step size where eps is error in km - ; set max_ds to the maximum step size (in RE) to prevent too large step -*/ - /* convert position to spherical coords */ - car2sph(xyz, rtp); - - /* compute IGRF field in spherical coords */ - IGRF_compute(rtp, brtp); - - /* convert field from spherical coords to Cartesian */ - bspcar(rtp[1],rtp[2], brtp, bxyz); - - /* magnitude of field to normalize vector */ - bmag = sqrt(bxyz[0]*bxyz[0] + bxyz[1]*bxyz[1] + bxyz[2]*bxyz[2]); - - if (code == 0) { /* no adaptive stepping */ - /**************\ - * RK4 Method * - \**************/ - for (k=0;k<3;k++) k1[k] = (*ds)*idir*bxyz[k]/bmag; - for (k=0;k<3;k++) xyztmp[k] = xyz[k] + .5*k1[k]; - AACGM_v2_Newval(xyztmp,idir,*ds, k2); - for (k=0;k<3;k++) xyztmp[k] = xyz[k] + .5*k2[k]; - AACGM_v2_Newval(xyztmp,idir,*ds, k3); - for (k=0;k<3;k++) xyztmp[k] = xyz[k] + k3[k]; - AACGM_v2_Newval(xyztmp,idir,*ds, k4); - - for (k=0; k<3; k++) - xyz[k] += (k1[k] + k2[k]+k2[k] + k3[k]+k3[k] + k4[k])/6.; - } else { - - /************************\ - * Adaptive RK45 method * - \************************/ - rr = eps+1; /* just to get into the loop */ - while (rr > eps) { - for (k=0;k<3;k++) k1[k] = (*ds)*idir*bxyz[k]/bmag; - for (k=0;k<3;k++) xyztmp[k] = xyz[k] + k1[k]/4.; - AACGM_v2_Newval(xyztmp,idir,*ds, k2); - for (k=0;k<3;k++) xyztmp[k] = xyz[k] + (3.*k1[k] + 9.*k2[k])/32.; - AACGM_v2_Newval(xyztmp,idir,*ds, k3); - for (k=0;k<3;k++) xyztmp[k] = xyz[k] + (1932.*k1[k] - 7200.*k2[k] + - 7296.*k3[k])/2197.; - AACGM_v2_Newval(xyztmp,idir,*ds, k4); - for (k=0;k<3;k++) - xyztmp[k] = xyz[k] + 439.*k1[k]/216. - 8.*k2[k] + - 3680.*k3[k]/513. - 845.*k4[k]/4104.; - AACGM_v2_Newval(xyztmp,idir,*ds, k5); - for (k=0;k<3;k++) - xyztmp[k] = xyz[k] - 8.*k1[k]/27. + 2.*k2[k] - 3544.*k3[k]/2565. + - 1859.*k4[k]/4104. - 11.*k5[k]/40.; - AACGM_v2_Newval(xyztmp,idir,*ds, k6); - - rr = 0.; - for (k=0;k<3;k++) { - w1[k] = xyz[k] + 25.*k1[k]/216. + 1408.*k3[k]/2565. + - 2197.*k4[k]/4104. - k5[k]/5.; - w2[k] = xyz[k] + 16.*k1[k]/135. + 6656.*k3[k]/12825. + - 28561.*k4[k]/56430. - 9.*k5[k]/50. + - 2.*k6[k]/55.; - rr += (w1[k]-w2[k])*(w1[k]-w2[k]); - } - rr = sqrt(rr)/(*ds); - - if (fabs(rr) > 1e-16) { - delt = 0.84 *pow(eps/rr,0.25); /* this formula sucks because I have - no it where it came from. - Obviously it involves factors in - the LTEs of the two methods, but - I cannot find them written down - anywhere. */ - //newds = ds * delt; - //ds = newds; - *ds *= delt; - - /* maximum stepsize is fixed to max_ds in units of Re */ - //if keyword_set(max_ds) then ds = min([max_ds,ds]) - /* maximum stepsize is r^2 * 1km, where r is in units of Re */ - //if keyword_set(RRds) then ds = min([50*r*r*r/RE, ds]) - *ds = MIN(50*rtp[0]*rtp[0]*rtp[0]/RE, *ds); - } /* otherwise leave the stepsize alone */ - } - - /* we use the RK4 solution */ - for (k=0;k<3;k++) xyz[k] = w1[k]; - /* - ; I would assume that using the higher order RK5 method is better, but - ; there is the suggestion that using the RK4 solution guarantees accuracy - ; while the RK5 does not. Apparently some texts are now suggesting using - ; the RK5 solution... - for (k=0;k<3;k++) xyz[k] = w2[k]; - */ - } - - return (0); -} - diff --git a/src/c_aacgm_v2/test_aacgm.c b/src/c_aacgm_v2/test_aacgm.c deleted file mode 100644 index b7705555..00000000 --- a/src/c_aacgm_v2/test_aacgm.c +++ /dev/null @@ -1,212 +0,0 @@ -#include -#include "aacgmlib_v2.h" - -void next(void); - -int main(void) -{ -double lat,lon,hgt; -double h; -double rtp[3]; -double mlat,mlon,r; -int err; -int year, month, day, hour, minute, second; - -printf("AACGM-v2 Test Program\n\n"); - -/* compute AACGM-v2 lat/lon with no time specified */ -printf("TEST: no date/time\n"); -lat = 45.5; -lon = -23.5; -hgt = 1135.; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); -if (err == 0) { - printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); - printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); - printf("\n\n"); -} -next(); - -printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", 1850,1,22,0,0,0); -AACGM_v2_SetDateTime(1850, 1, 22, 0,0,0); /* this should fail */ -next(); -printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", 1900,1,22,0,0,0); -AACGM_v2_SetDateTime(1900, 1, 22, 0,0,0); /* this is valid */ -next(); -printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", 2020,1,22,0,0,0); -AACGM_v2_SetDateTime(2020, 1, 22, 0,0,0); /* this shoudl fail */ -next(); -printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", 2019,1,22,0,0,0); -AACGM_v2_SetDateTime(2019, 1, 22, 0,0,0); /* this is valid */ -next(); - -year = 2014; -month = 3; -day = 22; -hour = 3; -minute = 11; -second = 0; -printf("TEST: Setting time to : %04d%02d%02d %02d%02d:%02d\n", - year, month, day, hour, minute, second); - -/* set date and time */ -AACGM_v2_SetDateTime(year, month, day, hour, minute, second); - -printf("TEST: geographic to AACGM-v2\n"); -/* compute AACGM lat/lon */ -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); - -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -//printf("%lf %lf\n", fyear, fyear_old); -printf("\n\n"); -next(); - -printf("TEST: AACGM-v2 to geographic\n"); -/* do the inverse: A2G */ -err = AACGM_v2_Convert(mlat,mlon,hgt, &lat,&lon, &r, A2G); - -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("\n\n"); -next(); - -/* compare tracing to coefficients */ -lat = 45.5; -lon = -23.5; -hgt = 150.; - -/* set date and time */ -AACGM_v2_SetDateTime(2018,1,1,0,0,0); - -printf("TEST: geographic to AACGM-v2; coefficients\n"); -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -next(); - -printf("TEST: geographic to AACGM-v2; tracing\n"); -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -next(); - -printf("TEST: geographic to AACGM-v2; too high\n"); -hgt = 2500; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -next(); - -printf("TEST: geographic to AACGM-v2; trace high\n"); -hgt = 7500; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -next(); - -printf("TEST: geographic to AACGM-v2; coefficient high\n"); -hgt = 7500; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|BADIDEA); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -next(); - -printf("TEST: geographic to AACGM-v2; trace and back\n"); -hgt = 0; -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A|TRACE); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -err = AACGM_v2_Convert(mlat,mlon,hgt, &lat,&lon, &r, A2G|TRACE); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); - -/* the proper altitude in geocentric coordinates is given by: */ -geod2geoc(lat,lon,hgt, rtp); -h = (rtp[0]-1.d)*RE; -err = AACGM_v2_Convert(mlat,mlon,h, &lat,&lon, &r, A2G|TRACE); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); - -next(); - -lat = 45.5; -lon = -23.5; - -printf("TEST: geographic to AACGM-v2; coeff and back\n"); -hgt = 0; -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -err = AACGM_v2_Convert(mlat,mlon,hgt, &lat,&lon, &r, A2G); -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -next(); - -return (0); - -/* pick a different year */ -year = 1997; -AACGM_v2_SetDateTime(year, month, day, hour, minute, second); - -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); - -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -//printf("%lf %lf\n", fyear, fyear_old); -printf("\n\n"); - -/* pick a different lat/lon; should not need to do any interpolations */ -lat = 65.5; -lon = 93.5; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); - -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -//printf("%lf %lf\n", fyear, fyear_old); -printf("\n\n"); - -/* pick a different height; should only need to do height interpolation */ -hgt = 0.; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); - -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -printf("\n\n"); - -/* do another lat/lon; no interpolations */ -lat = 75.5; -lon = 73.5; -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); - -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -printf("\n\n"); - -/* pick another year; should require loading new coeffs and both interps */ -year = 2004; -month = 3; -day = 22; -hour = 3; -minute = 11; -second = 0; - -AACGM_v2_SetDateTime(year, month, day, hour, minute, second); - -err = AACGM_v2_Convert(lat,lon,hgt, &mlat,&mlon, &r, G2A); - -printf("lat = %lf, lon = %lf, height = %lf\n", lat,lon,hgt); -printf("mlat = %lf, mlon = %lf, r = %lf\n", mlat,mlon,r); -printf("\n\n"); -} - -void next(void) -{ - char ch; - - printf("Press Enter to continue "); - do { - scanf("%c", &ch); - } while (ch != '\n'); - -// printf("\n\n"); - printf("\f"); -} - diff --git a/tests/test_c_aacgmv2.py b/tests/test_c_aacgmv2.py deleted file mode 100644 index 1b95e749..00000000 --- a/tests/test_c_aacgmv2.py +++ /dev/null @@ -1,159 +0,0 @@ -from __future__ import division, print_function, absolute_import, unicode_literals - -import numpy as np -import pytest - -import aacgmv2 -from aacgmv2._aacgmv2 import A2G, G2A, TRACE, BADIDEA, ALLOWTRACE, GEOCENTRIC - - -def test_module_structure(): - assert aacgmv2 - assert aacgmv2._aacgmv2 - assert aacgmv2._aacgmv2.setDateTime - assert aacgmv2._aacgmv2.aacgmConvert - - -def test_constants(): - assert aacgmv2._aacgmv2.G2A == 0 - assert aacgmv2._aacgmv2.A2G == 1 - assert aacgmv2._aacgmv2.TRACE == 2 - assert aacgmv2._aacgmv2.ALLOWTRACE == 4 - assert aacgmv2._aacgmv2.BADIDEA == 8 - assert aacgmv2._aacgmv2.GEOCENTRIC == 16 - - -def test_setDateTime(): - assert aacgmv2._aacgmv2.setDateTime(2013, 1, 1, 0, 0, 0) is None - assert aacgmv2._aacgmv2.setDateTime(2015, 3, 4, 5, 6, 7) is None - assert aacgmv2._aacgmv2.setDateTime(2017, 12, 31, 23, 59, 59) is None - - -def test_aacgmConvert_G2A_coeff(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, G2A) - np.testing.assert_almost_equal(mlat, 48.1896, decimal=4) - np.testing.assert_almost_equal(mlon, 57.7635, decimal=4) - assert r == 1 - - aacgmv2._aacgmv2.setDateTime(2018, 1, 1, 0, 0, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, G2A) - np.testing.assert_almost_equal(mlat, 58.1633, decimal=4) - np.testing.assert_almost_equal(mlon, 81.0719, decimal=4) - assert r == 1 - - -def test_aacgmConvert_A2G_coeff(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, A2G) - np.testing.assert_almost_equal(mlat, 30.7534, decimal=4) - np.testing.assert_almost_equal(mlon, -94.1806, decimal=4) - assert r == 1 - - aacgmv2._aacgmv2.setDateTime(2018, 1, 1, 0, 0, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, A2G) - np.testing.assert_almost_equal(mlat, 50.3910, decimal=4) - np.testing.assert_almost_equal(mlon, -77.7919, decimal=4) - assert r == 1 - - -def test_aacgmConvert_G2A_TRACE(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, G2A | TRACE) - np.testing.assert_almost_equal(mlat, 48.1948, decimal=4) - np.testing.assert_almost_equal(mlon, 57.7588, decimal=4) - assert r == 1 - - aacgmv2._aacgmv2.setDateTime(2018, 1, 1, 0, 0, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, G2A | TRACE) - np.testing.assert_almost_equal(mlat, 58.1633, decimal=4) - np.testing.assert_almost_equal(mlon, 81.0756, decimal=4) - assert r == 1 - - -def test_aacgmConvert_A2G_TRACE(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, A2G | TRACE) - np.testing.assert_almost_equal(mlat, 30.7644, decimal=4) - np.testing.assert_almost_equal(mlon, -94.1809, decimal=4) - assert r == 1 - - aacgmv2._aacgmv2.setDateTime(2018, 1, 1, 0, 0, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, A2G | TRACE) - np.testing.assert_almost_equal(mlat, 50.3958, decimal=4) - np.testing.assert_almost_equal(mlon, -77.8019, decimal=4) - assert r == 1 - - -def test_aacgmConvert_high_denied(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - with pytest.raises(RuntimeError): - aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 5500, G2A) - - -def test_aacgmConvert_high_TRACE(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 5500, G2A | TRACE) - np.testing.assert_almost_equal(mlat, 59.9748, decimal=4) - np.testing.assert_almost_equal(mlon, 57.7425, decimal=4) - assert r == 1 - - -def test_aacgmConvert_high_ALLOWTRACE(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 5500, G2A | ALLOWTRACE) - np.testing.assert_almost_equal(mlat, 59.9748, decimal=4) - np.testing.assert_almost_equal(mlon, 57.7425, decimal=4) - assert r == 1 - - -def test_aacgmConvert_high_BADIDEA(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 5500, G2A | BADIDEA) - np.testing.assert_almost_equal(mlat, 58.7154, decimal=4) - np.testing.assert_almost_equal(mlon, 56.5830, decimal=4) - assert r == 1 - - -def test_aacgmConvert_GEOCENTRIC_G2A_coeff(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, G2A | GEOCENTRIC) - np.testing.assert_almost_equal(mlat, 48.3779, decimal=4) - np.testing.assert_almost_equal(mlon, 57.7974, decimal=4) - assert r == 1 - - -def test_aacgmConvert_GEOCENTRIC_A2G_coeff(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, A2G | GEOCENTRIC) - np.testing.assert_almost_equal(mlat, 30.6101, decimal=4) - np.testing.assert_almost_equal(mlon, -94.1806, decimal=4) - assert r == 1 - - -def test_aacgmConvert_GEOCENTRIC_G2A_TRACE(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, G2A | TRACE | GEOCENTRIC) - np.testing.assert_almost_equal(mlat, 48.3830, decimal=4) - np.testing.assert_almost_equal(mlon, 57.7926, decimal=4) - assert r == 1 - - -def test_aacgmConvert_GEOCENTRIC_A2G_TRACE(): - aacgmv2._aacgmv2.setDateTime(2014, 3, 22, 3, 11, 0) - - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(45.5, -23.5, 1135, A2G | TRACE | GEOCENTRIC) - np.testing.assert_almost_equal(mlat, 30.6211, decimal=4) - np.testing.assert_almost_equal(mlon, -94.1809, decimal=4) - assert r == 1 - - -def test_forbidden(): - mlat, mlon, r = aacgmv2._aacgmv2.aacgmConvert(7, 0, 0, G2A) - assert np.isnan(mlat) - assert np.isnan(mlon) diff --git a/tests/test_cmd_aacgmv2.py b/tests/test_cmd_aacgmv2.py deleted file mode 100644 index b6332aec..00000000 --- a/tests/test_cmd_aacgmv2.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, print_function, absolute_import, unicode_literals - -import os -import subprocess - -import numpy as np - - -def setup_function(function): - try: - os.remove('tests/output.txt') - except: - pass - -teardown_function = setup_function - - -def test_module_invocation(): - p = subprocess.Popen(['python', '-m', 'aacgmv2', 'convert', '-i', 'tests/test_convert.txt', '-d', '20150224', - '-o', 'tests/output.txt']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [[57.4761, 93.5572], [58.5332, 93.9607], [59.5852, 94.3897]], rtol=1e-4) - - -def test_convert_g2a(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert.txt', '-d', '20150224', - '-o', 'tests/output.txt']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [[57.4761, 93.5572], [58.5332, 93.9607], [59.5852, 94.3897]], rtol=1e-4) - - -def test_convert_a2g(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert.txt', '-d', '20150224', - '-o', 'tests/output.txt', '-v']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [[51.6547, -66.6601], [52.6725, -66.7555], [53.6914, -66.8552]], rtol=1e-4) - - -def test_convert_trace_g2a(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert.txt', '-d', '20150224', - '-o', 'tests/output.txt', '-t']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [[57.4736, 93.5676], [58.5305, 93.9716], [59.5825, 94.4009]], rtol=1e-4) - - -def test_convert_trace_a2g(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert.txt', '-d', '20150224', - '-o', 'tests/output.txt', '-t', '-v']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [[51.6454, -66.6444], [52.6671, -66.7432], [53.6899, -66.8469]], rtol=1e-4) - - -def test_convert_geocentric(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert.txt', '-d', '20150224', - '-o', 'tests/output.txt', '-g']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [[57.6697, 93.6319], [58.7223, 94.0385], [59.7695, 94.4708]], rtol=1e-4) - - -def test_convert_today(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert.txt']) - p.communicate() - p.wait() - - -def test_convert_single_line(): - p = subprocess.Popen(['aacgmv2', 'convert', '-i', 'tests/test_convert_single_line.txt', - '-d', '20150224', '-o', 'tests/output.txt']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [57.4761, 93.5572], rtol=1e-4) - - -def test_convert_stdin_stdout(): - p = subprocess.Popen('echo 60 15 300 | aacgmv2 convert -d 20150224', shell=True, stdout=subprocess.PIPE) - stdout, _ = p.communicate() - p.wait() - assert b'57.47612194 93.55719875' in stdout - - -def test_convert_mlt_a2m(): - p = subprocess.Popen(['aacgmv2', 'convert_mlt', '-i', 'tests/test_convert_mlt.txt', - '20150224140015', '-o', 'tests/output.txt']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [9.056476, 9.78981, 10.523143], rtol=1e-6) - - -def test_convert_mlt_m2a(): - p = subprocess.Popen(['aacgmv2', 'convert_mlt', '-i', 'tests/test_convert_mlt.txt', - '20150224140015', '-o', 'tests/output.txt', '-v']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, [240.152854, 45.152854, 210.152854], rtol=1e-6) - - -def test_convert_mlt_single_line(): - p = subprocess.Popen(['aacgmv2', 'convert_mlt', '-i', 'tests/test_convert_mlt_single_line.txt', - '20150224140015', '-o', 'tests/output.txt']) - p.communicate() - p.wait() - data = np.loadtxt('tests/output.txt') - np.testing.assert_allclose(data, 9.0564764, rtol=1e-6) - - -def test_convert_mlt_stdin_stdout(): - p = subprocess.Popen('echo 12 | aacgmv2 convert_mlt -v 20150224140015', shell=True, stdout=subprocess.PIPE) - stdout, _ = p.communicate() - p.wait() - assert b'45.15285362' in stdout - - -def test_convert_mlt_stdin_stdout_order(): - p = subprocess.Popen('echo 12 | aacgmv2 convert_mlt 20150224140015 -v', shell=True, stdout=subprocess.PIPE) - stdout, _ = p.communicate() - p.wait() - assert b'45.15285362' in stdout diff --git a/tests/test_py_aacgmv2.py b/tests/test_py_aacgmv2.py deleted file mode 100644 index 8741efbb..00000000 --- a/tests/test_py_aacgmv2.py +++ /dev/null @@ -1,218 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, print_function, absolute_import, unicode_literals - -import datetime as dt - -import numpy as np -import pytest - -import aacgmv2 -from aacgmv2._aacgmv2 import A2G, G2A, TRACE, BADIDEA, ALLOWTRACE, GEOCENTRIC - -date = (2015, 1, 1, 0, 0, 0) -dtObj = dt.datetime(*date) - - -def test_module_structure(): - assert aacgmv2 - assert aacgmv2.convert - - -def test_output_type(): - lat, lon = aacgmv2.convert(60, 0, 300, dtObj) - print(type(lat)) - print(lat.shape) - print(lat.size) - assert isinstance(lat, np.ndarray) - assert isinstance(lon, np.ndarray) - - lat, lon = aacgmv2.convert([60], [0], [300], dtObj) - assert isinstance(lat, np.ndarray) - assert isinstance(lon, np.ndarray) - - lat, lon = aacgmv2.convert([60, 61], [0, 0], [300, 300], dtObj) - assert isinstance(lat, np.ndarray) - assert isinstance(lon, np.ndarray) - - lat, lon = aacgmv2.convert([60, 61, 62], 0, 300, dtObj) - assert isinstance(lat, np.ndarray) - assert isinstance(lon, np.ndarray) - - lat, lon = aacgmv2.convert(np.array([60, 61, 62]), 0, 300, dtObj) - assert isinstance(lat, np.ndarray) - assert isinstance(lon, np.ndarray) - - lat, lon = aacgmv2.convert(np.array([[60, 61, 62], [63, 64, 65]]), 0, 300, dtObj) - assert isinstance(lat, np.ndarray) - assert isinstance(lon, np.ndarray) - - -def test_output_shape_size(): - lat, lon = aacgmv2.convert(60, 0, 300, dtObj) - assert lat.shape == tuple() - assert lon.shape == tuple() - assert lat.size == 1 - assert lon.size == 1 - - lat, lon = aacgmv2.convert([60], [0], [300], dtObj) - assert lat.shape == (1,) - assert lon.shape == (1,) - assert lat.size == 1 - assert lon.size == 1 - - lat, lon = aacgmv2.convert([60, 61], [0, 0], [300, 300], dtObj) - assert lat.shape == (2,) - assert lon.shape == (2,) - assert lat.size == 2 - assert lon.size == 2 - - lat, lon = aacgmv2.convert([60, 61, 62], 0, 300, dtObj) - assert lat.shape == (3,) - assert lon.shape == (3,) - assert lat.size == 3 - assert lon.size == 3 - - lat, lon = aacgmv2.convert(np.array([60, 61, 62]), 0, 300, dtObj) - assert lat.shape == (3,) - assert lon.shape == (3,) - assert lat.size == 3 - assert lon.size == 3 - - lat, lon = aacgmv2.convert(np.array([[60, 61, 62], - [63, 64, 65]]), - 0, 300, dtObj) - assert lat.shape == (2, 3) - assert lon.shape == (2, 3) - assert lat.size == 6 - assert lon.size == 6 - - -def test_convert_result_values_shape(): - lat, lon = aacgmv2.convert(np.array([[60, 61, 62], - [63, 64, 65]]), - 0, 300, dtObj) - aacgmv2._aacgmv2.setDateTime(*date) - assert (lat[0, 0], lon[0, 0], 1) == aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, G2A) - assert (lat[0, 1], lon[0, 1], 1) == aacgmv2._aacgmv2.aacgmConvert(61, 0, 300, G2A) - assert (lat[0, 2], lon[0, 2], 1) == aacgmv2._aacgmv2.aacgmConvert(62, 0, 300, G2A) - assert (lat[1, 0], lon[1, 0], 1) == aacgmv2._aacgmv2.aacgmConvert(63, 0, 300, G2A) - assert (lat[1, 1], lon[1, 1], 1) == aacgmv2._aacgmv2.aacgmConvert(64, 0, 300, G2A) - assert (lat[1, 2], lon[1, 2], 1) == aacgmv2._aacgmv2.aacgmConvert(65, 0, 300, G2A) - - -def test_convert_datetime_date(): - lat_1, lon_1 = aacgmv2.convert(60, 0, 300, dt.date(2013, 12, 1)) - lat_2, lon_2 = aacgmv2.convert(60, 0, 300, dt.datetime(2013, 12, 1, 0, 0, 0)) - assert lat_1 == lat_2 - assert lon_1 == lon_2 - - -def test_convert_result_values_G2A_coeff(): - lat_p, lon_p = aacgmv2.convert(60, 0, 300, dtObj) - aacgmv2._aacgmv2.setDateTime(*date) - lat_c, lon_c, _ = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, G2A) - assert lat_p == lat_c - assert lon_p == lon_c - - -def test_convert_result_values_A2G_coeff(): - lat_p, lon_p = aacgmv2.convert(60, 0, 300, dtObj, a2g=True) - aacgmv2._aacgmv2.setDateTime(*date) - lat_c, lon_c, _ = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, A2G) - assert lat_p == lat_c - assert lon_p == lon_c - - -def test_convert_result_values_G2A_trace(): - lat_p, lon_p = aacgmv2.convert(60, 0, 300, dtObj, trace=True) - aacgmv2._aacgmv2.setDateTime(*date) - lat_c, lon_c, _ = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, G2A | TRACE) - assert lat_p == lat_c - assert lon_p == lon_c - - -def test_convert_result_values_A2G_trace(): - lat_p, lon_p = aacgmv2.convert(60, 0, 300, dtObj, a2g=True, trace=True) - aacgmv2._aacgmv2.setDateTime(*date) - lat_c, lon_c, _ = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, A2G | TRACE) - assert lat_p == lat_c - assert lon_p == lon_c - - -def test_convert_result_values_allowtrace(): - lat, lon = aacgmv2.convert(60, 0, [300, 5000], dtObj, allowtrace=True) - aacgmv2._aacgmv2.setDateTime(*date) - assert (lat[0], lon[0], 1) == aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, ALLOWTRACE) - assert (lat[1], lon[1], 1) == aacgmv2._aacgmv2.aacgmConvert(60, 0, 5000, ALLOWTRACE) - - -def test_convert_result_values_badidea(): - lat, lon = aacgmv2.convert(60, 0, [300, 5000], dtObj, badidea=True) - aacgmv2._aacgmv2.setDateTime(*date) - assert (lat[0], lon[0], 1) == aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, BADIDEA) - assert (lat[1], lon[1], 1) == aacgmv2._aacgmv2.aacgmConvert(60, 0, 5000, BADIDEA) - - -def test_convert_result_values_geocentric(): - lat_p, lon_p = aacgmv2.convert(60, 0, 300, dtObj, geocentric=True) - aacgmv2._aacgmv2.setDateTime(*date) - lat_c, lon_c, _ = aacgmv2._aacgmv2.aacgmConvert(60, 0, 300, GEOCENTRIC) - assert lat_p == lat_c - assert lon_p == lon_c - - -def test_warning_below_ground(): - with pytest.warns(UserWarning): - aacgmv2.convert(60, 0, -1, dtObj) - with pytest.warns(UserWarning): - aacgmv2.convert(60, 0, [300, -1], dtObj) - - -def test_exception_maxalt(): - with pytest.raises(ValueError): - aacgmv2.convert(60, 0, 2001, dtObj) - with pytest.raises(ValueError): - aacgmv2.convert(60, 0, [300, 2001], dtObj) - - # the following should not raise exceptions - aacgmv2.convert(60, 0, 2001, dtObj, trace=True) - aacgmv2.convert(60, 0, 2001, dtObj, allowtrace=True) - aacgmv2.convert(60, 0, 2001, dtObj, badidea=True) - - -def test_exception_lat90(): - with pytest.raises(ValueError): - aacgmv2.convert(91, 0, 300, dtObj) - with pytest.raises(ValueError): - aacgmv2.convert(-91, 0, 300, dtObj) - with pytest.raises(ValueError): - aacgmv2.convert([60, 91], 0, 300, dtObj) - with pytest.raises(ValueError): - aacgmv2.convert([60, -91], 0, 300, dtObj) - - # the following should not raise exceptions - aacgmv2.convert(90, 0, 300, dtObj) - aacgmv2.convert(-90, 0, 300, dtObj) - - -def test_forbidden(): - mlat, mlon = aacgmv2.convert(7, 0, 0) - assert np.isnan(mlat) - assert np.isnan(mlon) - - -def test_MLT_forward_backward(): - mlon = aacgmv2.convert_mlt(12, dtObj, m2a=True) - mlt = aacgmv2.convert_mlt(mlon, dtObj) - np.testing.assert_allclose(mlt, 12) - - -def test_MLT_a2m(): - mlt = aacgmv2.convert_mlt([1, 12, 23], dt.datetime(2015, 2, 24, 14, 0, 15)) - np.testing.assert_allclose(mlt, [9.056476, 9.78981 , 10.523143], rtol=1e-6) - - -def test_MLT_m2a(): - mlon = aacgmv2.convert_mlt([1, 12, 23], dt.datetime(2015, 2, 24, 14, 0, 15), m2a=True) - np.testing.assert_allclose(mlon, [240.152854, 45.152854, 210.152854], rtol=1e-6) diff --git a/tox.ini b/tox.ini index f781bee7..704fe5ea 100644 --- a/tox.ini +++ b/tox.ini @@ -2,30 +2,14 @@ envlist = clean, check, - 2.7-np1.10, - 2.7-np1.10-nocover, - 2.7-np1.8, - 2.7-np1.8-nocover, - 2.7-np1.9, - 2.7-np1.9-nocover, - 3.3-np1.10, - 3.3-np1.10-nocover, - 3.3-np1.8, - 3.3-np1.8-nocover, - 3.3-np1.9, - 3.3-np1.9-nocover, - 3.4-np1.10, - 3.4-np1.10-nocover, - 3.4-np1.8, - 3.4-np1.8-nocover, - 3.4-np1.9, - 3.4-np1.9-nocover, - 3.5-np1.10, - 3.5-np1.10-nocover, - 3.5-np1.8, - 3.5-np1.8-nocover, - 3.5-np1.9, - 3.5-np1.9-nocover, + 2.7, + 2.7-nocover, + 3.4, + 3.4-nocover, + 3.5, + 3.5-nocover, + 3.6, + 3.6-nocover, report, docs @@ -37,7 +21,8 @@ passenv = * deps = pytest - pytest-capturelog + logbook + numpy commands = python setup.py clean --all build_ext --force --inplace {posargs:py.test -vv --ignore=src --doctest-glob='*.rst'} @@ -108,7 +93,6 @@ commands = coverage xml --ignore-errors codecov [] - [testenv:extension-coveralls] deps = cpp-coveralls @@ -132,49 +116,7 @@ skip_install = true usedevelop = false deps = coverage -[testenv:2.7-np1.10] -basepython = {env:TOXPYTHON:python2.7} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.10,<1.11 - -[testenv:2.7-np1.10-nocover] -basepython = {env:TOXPYTHON:python2.7} -deps = - {[testenv]deps} - numpy>=1.10,<1.11 - -[testenv:2.7-np1.8] -basepython = {env:TOXPYTHON:python2.7} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.8,<1.9 - -[testenv:2.7-np1.8-nocover] -basepython = {env:TOXPYTHON:python2.7} -deps = - {[testenv]deps} - numpy>=1.8,<1.9 - -[testenv:2.7-np1.9] +[testenv:2.7] basepython = {env:TOXPYTHON:python2.7} setenv = {[testenv]setenv} @@ -187,120 +129,11 @@ commands = deps = {[testenv]deps} pytest-cov - numpy>=1.9,<1.10 -[testenv:2.7-np1.9-nocover] +[testenv:2.7-nocover] basepython = {env:TOXPYTHON:python2.7} -deps = - {[testenv]deps} - numpy>=1.9,<1.10 -[testenv:3.3-np1.10] -basepython = {env:TOXPYTHON:python3.3} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.10,<1.11 - -[testenv:3.3-np1.10-nocover] -basepython = {env:TOXPYTHON:python3.3} -deps = - {[testenv]deps} - numpy>=1.10,<1.11 - -[testenv:3.3-np1.8] -basepython = {env:TOXPYTHON:python3.3} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.8,<1.9 - -[testenv:3.3-np1.8-nocover] -basepython = {env:TOXPYTHON:python3.3} -deps = - {[testenv]deps} - numpy>=1.8,<1.9 - -[testenv:3.3-np1.9] -basepython = {env:TOXPYTHON:python3.3} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.9,<1.10 - -[testenv:3.3-np1.9-nocover] -basepython = {env:TOXPYTHON:python3.3} -deps = - {[testenv]deps} - numpy>=1.9,<1.10 - -[testenv:3.4-np1.10] -basepython = {env:TOXPYTHON:python3.4} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.10,<1.11 - -[testenv:3.4-np1.10-nocover] -basepython = {env:TOXPYTHON:python3.4} -deps = - {[testenv]deps} - numpy>=1.10,<1.11 - -[testenv:3.4-np1.8] -basepython = {env:TOXPYTHON:python3.4} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.8,<1.9 - -[testenv:3.4-np1.8-nocover] -basepython = {env:TOXPYTHON:python3.4} -deps = - {[testenv]deps} - numpy>=1.8,<1.9 - -[testenv:3.4-np1.9] +[testenv:3.4] basepython = {env:TOXPYTHON:python3.4} setenv = {[testenv]setenv} @@ -313,15 +146,11 @@ commands = deps = {[testenv]deps} pytest-cov - numpy>=1.9,<1.10 -[testenv:3.4-np1.9-nocover] +[testenv:3.4-nocover] basepython = {env:TOXPYTHON:python3.4} -deps = - {[testenv]deps} - numpy>=1.9,<1.10 -[testenv:3.5-np1.10] +[testenv:3.5] basepython = {env:TOXPYTHON:python3.5} setenv = {[testenv]setenv} @@ -334,16 +163,12 @@ commands = deps = {[testenv]deps} pytest-cov - numpy>=1.10,<1.11 -[testenv:3.5-np1.10-nocover] +[testenv:3.5-nocover] basepython = {env:TOXPYTHON:python3.5} -deps = - {[testenv]deps} - numpy>=1.10,<1.11 -[testenv:3.5-np1.8] -basepython = {env:TOXPYTHON:python3.5} +[testenv:3.6] +basepython = {env:TOXPYTHON:python3.6} setenv = {[testenv]setenv} WITH_COVERAGE=yes @@ -355,34 +180,9 @@ commands = deps = {[testenv]deps} pytest-cov - numpy>=1.8,<1.9 -[testenv:3.5-np1.8-nocover] -basepython = {env:TOXPYTHON:python3.5} -deps = - {[testenv]deps} - numpy>=1.8,<1.9 - -[testenv:3.5-np1.9] -basepython = {env:TOXPYTHON:python3.5} -setenv = - {[testenv]setenv} - WITH_COVERAGE=yes - PY_CCOV=-coverage -usedevelop = true -commands = - python setup.py clean --all build_ext --force --inplace - {posargs:py.test --cov --cov-report=term-missing -vv --doctest-glob='*.rst'} -deps = - {[testenv]deps} - pytest-cov - numpy>=1.9,<1.10 - -[testenv:3.5-np1.9-nocover] -basepython = {env:TOXPYTHON:python3.5} -deps = - {[testenv]deps} - numpy>=1.9,<1.10 +[testenv:3.6-nocover] +basepython = {env:TOXPYTHON:python3.6} [testenv:2.7-buildonly-nocover] @@ -391,12 +191,6 @@ deps = skip_install = true commands = -[testenv:3.3-buildonly-nocover] -basepython = {env:TOXPYTHON:python3.3} -deps = -skip_install = true -commands = - [testenv:3.4-buildonly-nocover] basepython = {env:TOXPYTHON:python3.4} deps = @@ -409,4 +203,10 @@ deps = skip_install = true commands = +[testenv:3.6-buildonly-nocover] +basepython = {env:TOXPYTHON:python3.6} +deps = +skip_install = true +commands = +