aburrell/aacgmv2

View on GitHub
aacgmv2/aacgmv2module.c

Summary

Maintainability
Test Coverage
#include <stdio.h>

/*****************************************************************************
 * Copyright (C) 2019 NRL
 * 
 * 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
 *
 * Disclaimer: This code is under the MIT license, whose details can be found at
 *             the root in the LICENSE file
 *****************************************************************************/

#include <Python.h>

#include "aacgmlib_v2.h"
#include "mlt_v2.h"

PyObject *module;

#ifndef PyInt_AsLong
#define PyInt_AsLong PyLong_AsLong
#endif

static PyObject *aacgm_v2_setdatetime(PyObject *self, PyObject *args)
{
  int year, month, day, hour, minute, second, err;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "iiiiii", &year, &month, &day, &hour, &minute,
               &second))
    return(NULL);

  /* Call the AACGM routine */
  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 *aacgm_v2_convert_arr(PyObject *self, PyObject *args)
{
  int code, err;

  long int i, in_num;

  double in_lat, in_lon, in_h, out_lat, out_lon, out_r;

  PyObject *latIn, *lonIn, *hIn, *latOut, *lonOut, *rOut, *badOut, *allOut;

  /* Parse the input as a tuple */
  if(!PyArg_ParseTuple(args, "O!O!O!i", &PyList_Type, &latIn, &PyList_Type,
               &lonIn, &PyList_Type, &hIn, &code))
    return(NULL);

  /* Allocate space for the output data */
  in_num  = PyList_Size(latIn);
  latOut = PyList_New(in_num);
  lonOut = PyList_New(in_num);
  rOut   = PyList_New(in_num);
  badOut = PyList_New(in_num);

  /* Cycle through all of the inputs */
  for(i=0; i<in_num; i++)
    {
      /* Read in the input and convert to doubles. GetItem are BORROWED */
      in_lat = PyFloat_AsDouble(PyList_GetItem(latIn, i));
      in_lon = PyFloat_AsDouble(PyList_GetItem(lonIn, i));
      in_h   = PyFloat_AsDouble(PyList_GetItem(hIn, i));
 
      /* Call the AACGM routine */
      err = AACGM_v2_Convert(in_lat, in_lon, in_h, &out_lat, &out_lon,
                 &out_r, code);

      /* Set the output */
      if(err < 0)
    {
      /* Python 3.7+ raises a SystemError when passing on inf. */
      /* SetItem STEALS the references that are added.         */
      PyList_SetItem(badOut, i, PyLong_FromLong((int long)i));
      PyList_SetItem(latOut, i, PyFloat_FromDouble(-666.0));
      PyList_SetItem(lonOut, i, PyFloat_FromDouble(-666.0));
      PyList_SetItem(rOut, i, PyFloat_FromDouble(-666.0));
    }
      else
    {
      PyList_SetItem(badOut, i, PyLong_FromLong((int long)(-1)));
      PyList_SetItem(latOut, i, PyFloat_FromDouble(out_lat));
      PyList_SetItem(lonOut, i, PyFloat_FromDouble(out_lon));
      PyList_SetItem(rOut, i, PyFloat_FromDouble(out_r));
    }
    }

  /* Set the output tuple */
  allOut = PyTuple_Pack(4, latOut, lonOut, rOut, badOut);

  /* Free memory for the local-only (not input) variables */
  Py_DECREF(latOut);
  Py_DECREF(lonOut);
  Py_DECREF(rOut);
  Py_DECREF(badOut);

  return allOut;
}

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;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "dddi", &in_lat, &in_lon, &in_h, &code))
    return(NULL);

  /* Call the AACGM routine */
  err = AACGM_v2_Convert(in_lat, in_lon, in_h, &out_lat, &out_lon, &out_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, out_r);
}

static PyObject *mltconvert_v2_arr(PyObject *self, PyObject *args)
{
  int i, in_yr, in_mo, in_dy, in_hr, in_mt, in_sc;

  long int in_num;

  double in_lon, out_mlt;

  PyObject *yrIn, *moIn, *dyIn, *hrIn, *mtIn, *scIn, *lonIn, *mltOut;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "O!O!O!O!O!O!O!", &PyList_Type, &yrIn,
               &PyList_Type, &moIn, &PyList_Type, &dyIn, &PyList_Type,
               &hrIn, &PyList_Type, &mtIn, &PyList_Type, &scIn,
               &PyList_Type, &lonIn))
    return(NULL);

  /* Allocate space for the output data */
  in_num  = PyList_Size(lonIn);
  mltOut = PyList_New(in_num);

  /* Cycle through all of the inputs */
  for(i=0; i<in_num; i++)
    {
      /* Read in the input */
      in_yr = (int) PyInt_AsLong(PyList_GetItem(yrIn, i));
      in_mo = (int) PyInt_AsLong(PyList_GetItem(moIn, i));
      in_dy = (int) PyInt_AsLong(PyList_GetItem(dyIn, i));
      in_hr = (int) PyInt_AsLong(PyList_GetItem(hrIn, i));
      in_mt = (int) PyInt_AsLong(PyList_GetItem(mtIn, i));
      in_sc = (int) PyInt_AsLong(PyList_GetItem(scIn, i));
      in_lon = PyFloat_AsDouble(PyList_GetItem(lonIn, i));
           
      /* Call the AACGM routine */
      out_mlt = MLTConvertYMDHMS_v2(in_yr, in_mo, in_dy, in_hr, in_mt, in_sc,
                    in_lon);
      
      PyList_SetItem(mltOut, i, PyFloat_FromDouble(out_mlt));
    }

  return mltOut;
}

static PyObject *mltconvert_v2(PyObject *self, PyObject *args)
{ 
  int yr, mo, dy, hr, mt, sc;

  double mlon, mlt;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "iiiiiid", &yr, &mo, &dy, &hr, &mt, &sc, &mlon))
    return(NULL);

  /* Call the AACGM routine */
  mlt = MLTConvertYMDHMS_v2(yr, mo, dy, hr, mt, sc, mlon);
    
  return Py_BuildValue("d", mlt);
}

static PyObject *mltconvert_yrsec_v2(PyObject *self, PyObject *args)
{
  int yr, yr_sec;

  double mlon, mlt;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "iid", &yr, &yr_sec, &mlon))
    return(NULL);

  /* Call the AACGM routine */
  mlt = MLTConvertYrsec_v2(yr, yr_sec, mlon);

  return Py_BuildValue("d", mlt);
}

static PyObject *inv_mltconvert_v2_arr(PyObject *self, PyObject *args)
{
  int i, in_yr, in_mo, in_dy, in_hr, in_mt, in_sc;

  long int in_num;

  double in_mlt, out_lon;

  PyObject *yrIn, *moIn, *dyIn, *hrIn, *mtIn, *scIn, *mltIn, *lonOut;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "O!O!O!O!O!O!O!", &PyList_Type, &yrIn,
               &PyList_Type, &moIn, &PyList_Type, &dyIn, &PyList_Type,
               &hrIn, &PyList_Type, &mtIn, &PyList_Type, &scIn,
               &PyList_Type, &mltIn))
    return(NULL);

  /* Allocate space for the output data */
  in_num  = PyList_Size(mltIn);
  lonOut = PyList_New(in_num);

  /* Cycle through all of the inputs */
  for(i=0; i<in_num; i++)
    {
      /* Read in the input */
      in_yr = (int) PyInt_AsLong(PyList_GetItem(yrIn, i));
      in_mo = (int) PyInt_AsLong(PyList_GetItem(moIn, i));
      in_dy = (int) PyInt_AsLong(PyList_GetItem(dyIn, i));
      in_hr = (int) PyInt_AsLong(PyList_GetItem(hrIn, i));
      in_mt = (int) PyInt_AsLong(PyList_GetItem(mtIn, i));
      in_sc = (int) PyInt_AsLong(PyList_GetItem(scIn, i));
      in_mlt = PyFloat_AsDouble(PyList_GetItem(mltIn, i));
           
      /* Call the AACGM routine */
      out_lon = inv_MLTConvertYMDHMS_v2(in_yr, in_mo, in_dy, in_hr, in_mt,
                    in_sc, in_mlt);
      
      PyList_SetItem(lonOut, i, PyFloat_FromDouble(out_lon));
    }

  return lonOut;
}

static PyObject *inv_mltconvert_v2(PyObject *self, PyObject *args)
{ 
  int yr, mo, dy, hr, mt, sc;

  double mlon, mlt;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "iiiiiid", &yr, &mo, &dy, &hr, &mt, &sc, &mlt))
    return(NULL);

  /* Call the AACGM routine */
  mlon = inv_MLTConvertYMDHMS_v2(yr, mo, dy, hr, mt, sc, mlt);
    
  return Py_BuildValue("d", mlon);
}

static PyObject *inv_mltconvert_yrsec_v2(PyObject *self, PyObject *args)
{
  int yr, yr_sec;

  double mlon, mlt;

  /* Parse the input as a tupple */
  if(!PyArg_ParseTuple(args, "iid", &yr, &yr_sec, &mlt))
    return(NULL);

  /* Call the AACGM routine */
  mlon = inv_MLTConvertYrsec_v2(yr, yr_sec, mlt);

  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)\n\
\n\
Set the date and time for the IGRF magnetic field.\n\
\n\
Parameters \n\
-------------\n\
year : int\n\
    Four digit year starting from 1590, ending 2025\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\
\n\
Returns\n\
-------------\n\
Void\n" },
  { "convert", aacgm_v2_convert, METH_VARARGS,
    "convert(in_lat, in_lon, height, code)\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\
\n\
    0 - G2A \n\
        Geographic (geodetic) to AACGM-v2    \n\
    1 - A2G \n\
        AACGM-v2 to geographic (geodetic)    \n\
    2 - TRACE \n\
        Use field-line tracing, not coefficients\n\
    4 - ALLOWTRACE \n\
        Use trace only above 2000 km\n\
    8 - BADIDEA \n\
        Use coefficients above 2000 km\n\
    16 - GEOCENTRIC \n\
        Assume inputs are geocentric w/ RE=6371.2\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", },
  { "convert_arr", aacgm_v2_convert_arr, METH_VARARGS,
    "convert_arr(in_lat, in_lon, height, code)\n\
\n\
Converts between geographic/dedic and magnetic coordinates.\n\
\n\
Parameters\n\
-------------\n\
in_lat : list\n\
    Input latitudes in degrees N (code specifies type of latitude)\n\
in_lon : list\n\
    Input longitudes in degrees E (code specifies type of longitude)\n\
height : list\n\
    Altitudes above the surface of the earth in km\n\
code : int    \n\
    Bitwise code for passing options into converter (default=0)\n\
\n\
    0 - G2A \n\
        Geographic (geodetic) to AACGM-v2    \n\
    1 - A2G \n\
        AACGM-v2 to geographic (geodetic)    \n\
    2 - TRACE \n\
        Use field-line tracing, not coefficients\n\
    4 - ALLOWTRACE \n\
        Use trace only above 2000 km\n\
    8 - BADIDEA \n\
        Use coefficients above 2000 km\n\
    16 - GEOCENTRIC \n\
        Assume inputs are geocentric w/ RE=6371.2\n\
\n\
Returns    \n\
-------\n\
out_lat : list\n\
    Output latitudes in degrees\n\
out_lon : list\n\
    Output longitudes in degrees\n\
out_r : list\n\
    Geocentric radial distances in Re\n\
out_bad : list\n\
    Indices at or greater than zero indicate filler data in previous outputs\n\
\n\
Notes \n\
-----\n\
Return values of -666 are used as filler values for lat/lon/r, while filler\n\
values of -1 are used in out_bad if the output in out_lat/lon/r is good\n", },
  {"mlt_convert_arr", mltconvert_v2_arr, METH_VARARGS,
    "mlt_convert_arr(yr, mo, dy, hr, mt, sc, mlon)\n\
\n\
Converts from universal time to magnetic local time.\n\
\n\
Parameters\n\
-------------\n\
yr : list\n\
    4 digit integer year (1590-2025)\n\
mo : list\n\
    Month of year (1-12)\n\
dy : list\n\
    Day of month (1-31)\n\
hr : list\n\
    hours of day (0-23)\n\
mt : list\n\
    Minutes of hour (0-59)\n\
sc : list\n\
    Seconds of minute (0-59)\n\
mlon : list\n\
    Magnetic longitude\n\
\n\
Returns    \n\
-------\n\
mlt : list\n\
    Magnetic local time (hours)\n" },
  {"mlt_convert", mltconvert_v2, METH_VARARGS,
    "mlt_convert(yr, mo, dy, hr, mt, sc, mlon)\n\
\n\
Converts from universal time to magnetic local time.\n\
\n\
Parameters\n\
-------------\n\
yr : int\n\
    4 digit integer year (1590-2025)\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\
\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)\n\
\n\
Converts from universal time to magnetic local time.\n\
\n\
Parameters\n\
-------------\n\
yr : int\n\
    4 digit integer year (1590-2025)\n\
yr_sec : int\n\
    Seconds of year (0-31622400)\n\
mlon : float\n\
    Magnetic longitude\n\
\n\
Returns    \n\
-------\n\
mlt : float\n\
    Magnetic local time (hours)\n" },
    {"inv_mlt_convert_arr", inv_mltconvert_v2_arr, METH_VARARGS,
    "inv_mlt_convert_arr(yr, mo, dy, hr, mt, sc, mlt)\n\
\n\
Converts from universal time and magnetic local time to magnetic longitude.\n\
\n\
Parameters\n\
-------------\n\
yr : list\n\
    4 digit integer year (1590-2025)\n\
mo : list\n\
    Month of year (1-12)\n\
dy : list\n\
    Day of month (1-31)\n\
hr : list\n\
    hours of day (0-23)\n\
mt : list\n\
    Minutes of hour (0-59)\n\
sc : list\n\
    Seconds of minute (0-59)\n\
mlt : list\n\
    Magnetic local time\n\
\n\
Returns    \n\
-------\n\
mlon : list\n\
    Magnetic longitude (degrees)\n" },

  {"inv_mlt_convert", inv_mltconvert_v2, METH_VARARGS,
    "inv_mlt_convert(yr, mo, dy, hr, mt, sc, mlt)\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 (1590-2025)\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\
\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)\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 (1590-2025)\n\
yr_sec : int\n\
    Seconds of year (0-31622400)\n\
mlt : float\n\
    Magnetic local time\n\
\n\
Returns    \n\
-------\n\
mlon : float\n\
    Magnetic longitude (degrees)\n" },
  { NULL, NULL, 0, NULL }
};

/* Different versions of python require different constant declarations */

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;
}