1 /* Python header files. */
2 
3 #define PY_SSIZE_T_CLEAN
4 #include "Python.h"
5 
6 #if PY_MAJOR_VERSION == 2
7 #define PyLong_AsLong PyInt_AsLong
8 #define PyLong_FromLong PyInt_FromLong
9 #define PyUnicode_Check3 PyString_Check
10 #undef PyUnicode_FromFormat
11 #undef PyUnicode_FromString
12 #undef PyUnicode_FromStringAndSize
13 #define PyUnicode_FromFormat PyString_FromFormat
14 #define PyUnicode_FromString PyString_FromString
15 #define PyUnicode_FromStringAndSize PyString_FromStringAndSize
16 #define PyUnicode_Type PyString_Type
17 #define PyUnicode_AsUTF8 PyString_AsString
18 #undef PyVarObject_HEAD_INIT
19 #define PyVarObject_HEAD_INIT(p, b) PyObject_HEAD_INIT(p) 0,
20 #define OB_REFCNT ob_refcnt
21 #else
22 #define PyUnicode_Check3 PyUnicode_Check
23 #define OB_REFCNT ob_base.ob_refcnt
24 #if PY_MINOR_VERSION == 0 || PY_MINOR_VERSION == 1 || PY_MINOR_VERSION == 2
25 #define PyUnicode_AsUTF8 _PyUnicode_AsString
26 #endif
27 #endif
28 
29 #include "datetime.h"
30 #include "structmember.h"
31 
32 /* The libastro header files. */
33 
34 #include "astro.h"
35 #include "preferences.h"
36 
37 /* Undo the astro.h #defines of common body attribute names. */
38 
39 #undef mjd
40 #undef lat
41 #undef lng
42 #undef tz
43 #undef temp
44 #undef pressure
45 #undef elev
46 #undef dip
47 #undef epoch
48 #undef tznm
49 #undef mjed
50 
51 /* Various phrases that need to be repeated frequently in docstrings. */
52 
53 #define D " as a float giving radians, or a string giving degrees:minutes:seconds"
54 #define H " as a float giving radians, or a string giving hours:minutes:seconds"
55 
56 /* Since different Body attributes are calculated with different
57    routines, the compute() function itself just writes the time into
58    the Body's `now' cache, and the VALID bits - stored in the user
59    flags field of each `obj' - are used to coordinate lazy computation
60    of the fields the user actually tries to access. */
61 
62 #define VALID_GEO   FUSER0	/* Now has mjd and epoch */
63 #define VALID_TOPO  FUSER1	/* Now has entire Observer */
64 #define VALID_OBJ   FUSER2	/* object fields have been computed */
65 #define VALID_RISET FUSER3	/* riset fields have been computed */
66 
67 #define VALID_LIBRATION FUSER4	/* moon libration fields computed */
68 #define VALID_COLONG  FUSER5	/* moon co-longitude fields computed */
69 
70 #define VALID_CML   FUSER4	/* jupiter central meridian computed */
71 
72 #define VALID_RINGS   FUSER4	/* saturn ring fields computed */
73 
74 /* Global access to the module, once initialized. */
75 
76 static PyObject *module = 0;
77 
78 /* Core data structures. */
79 
80 typedef struct {
81      PyObject_HEAD
82      Now now;
83 } Observer;
84 
85 typedef struct {
86      PyObject_HEAD
87      Now now;			/* cache of last argument to compute() */
88      Obj obj;			/* the ephemeris object */
89      RiseSet riset;		/* rising and setting */
90      PyObject *name;		/* object name */
91 } Body;
92 
93 typedef Body Planet, PlanetMoon;
94 typedef Body FixedBody, BinaryStar;
95 typedef Body EllipticalBody, ParabolicBody, HyperbolicBody;
96 
97 typedef struct {
98      PyObject_HEAD
99      Now now;			/* cache of last observer */
100      Obj obj;			/* the ephemeris object */
101      RiseSet riset;		/* rising and setting */
102      PyObject *name;		/* object name */
103      double llat, llon;		/* libration */
104      double c, k, s;		/* co-longitude and illumination */
105 } Moon;
106 
107 typedef struct {
108      PyObject_HEAD
109      Now now;			/* cache of last observer */
110      Obj obj;			/* the ephemeris object */
111      RiseSet riset;		/* rising and setting */
112      PyObject *name;		/* object name */
113      double cmlI, cmlII;	/* positions of Central Meridian */
114 } Jupiter;
115 
116 typedef struct {
117      PyObject_HEAD
118      Now now;			/* cache of last observer */
119      Obj obj;			/* the ephemeris object */
120      RiseSet riset;		/* rising and setting */
121      PyObject *name;		/* object name */
122      double etilt, stilt;	/* tilt of rings */
123 } Saturn;
124 
125 typedef struct {
126      PyObject_HEAD
127      Now now;			/* cache of last observer */
128      Obj obj;			/* the ephemeris object */
129      RiseSet riset;		/* rising and setting */
130      PyObject *name;		/* object name */
131      PyObject *catalog_number;	/* TLE catalog number */
132 } EarthSatellite;
133 
134 /* Forward declaration. */
135 
136 static int Body_obj_cir(Body *body, char *fieldname, unsigned topocentric);
137 
138 /* Return the mjd of the current time. */
139 
mjd_now(void)140 static double mjd_now(void)
141 {
142      return 25567.5 + time(NULL)/3600.0/24.0;
143 }
144 
145 /* Return the floating-point equivalent value of a Python number. */
146 
PyNumber_AsDouble(PyObject * o,double * dp)147 static int PyNumber_AsDouble(PyObject *o, double *dp)
148 {
149      PyObject *f = PyNumber_Float(o);
150      if (!f) return -1;
151      *dp = PyFloat_AsDouble(f);
152      Py_DECREF(f);
153      return 0;
154 }
155 
156 /* Convert a base-60 ("sexagesimal") string like "02:30:00" into a
157    double value like 2.5.  Uses Python split() and float(), which are
158    slower than raw C but are sturdy and robust and eliminate all of the
159    locale problems to which raw C calls are liable. */
160 
161 static PyObject *scansexa_split = 0;
162 
scansexa(PyObject * o,double * dp)163 static int scansexa(PyObject *o, double *dp) {
164      if (!scansexa_split) {
165          scansexa_split = PyObject_GetAttrString(module, "_scansexa_split");
166          if (!scansexa_split)
167              return -1;
168      }
169      PyObject *list = PyObject_CallFunction(scansexa_split, "O", o);
170      if (!list) {
171           return -1;
172      }
173      int length = PyList_Size(list);
174      double d = 0.0;
175      int i;
176      for (i=length-1; i>=0; i--) {
177           d /= 60.0;
178           PyObject *item = PyList_GetItem(list, i);  /* borrowed reference! */
179           if (!item) {  /* should never happen, but just in case */
180                Py_DECREF(list);
181                return -1;
182           }
183           Py_ssize_t item_length = PyUnicode_GET_SIZE(item);
184           if (item_length == 0) {
185                continue;  /* accept empty string for 0 */
186           }
187           PyObject *verdict = PyObject_CallMethod(item, "isspace", NULL);
188           if (!verdict) {  /* shouldn't happen unless we're out of memory? */
189                Py_DECREF(list);
190                return -1;
191           }
192           int is_verdict_true = PyObject_IsTrue(verdict);
193           Py_DECREF(verdict);
194           if (is_verdict_true) {
195                continue;  /* accept whitespace for 0 */
196           }
197           PyObject *float_obj = PyNumber_Float(item);
198           if (!float_obj) {  /* can't parse nonempty string as float? error! */
199                Py_DECREF(list);
200                return -1;
201           }
202           double n = PyFloat_AsDouble(float_obj);
203           d = copysign(d, n);
204           d += n;
205           Py_DECREF(float_obj);
206      }
207      *dp = d;
208      Py_DECREF(list);
209      return 0;
210 }
211 
212 /* The libastro library offers a "getBuiltInObjs()" function that
213    initializes the list of planets that XEphem displays by default.
214    Rather than duplicate its logic for how to build each objects, we
215    simply make a copy of one of its objects when the user asks for a
216    planet or moon.
217 
218    This function is exposed by the module so that ephem.py can build
219    Planet classes dynamically; each element of the list it returns is
220    a tuple that looks like (7, "Planet", "Pluto"): */
221 
builtin_planets(PyObject * self)222 static PyObject *builtin_planets(PyObject *self)
223 {
224      PyObject *list = 0, *tuple = 0;
225      Obj *objects;
226      int i, n = getBuiltInObjs(&objects);
227 
228      list = PyList_New(n);
229      if (!list) goto fail;
230 
231      for (i=0; i < n; i++) {
232           tuple = Py_BuildValue(
233                "iss", i, objects[i].pl_moon ? "PlanetMoon" : "Planet",
234                objects[i].o_name);
235           if (!tuple) goto fail;
236           if (PyList_SetItem(list, i, tuple) == -1) goto fail;
237      }
238 
239      return list;
240 fail:
241      Py_XDECREF(list);
242      Py_XDECREF(tuple);
243      return 0;
244 }
245 
246 /* This function is used internally to copy the attributes of a
247    built-in libastro object into a new Planet (see above). */
248 
copy_planet_from_builtin(Planet * planet,int builtin_index)249 static int copy_planet_from_builtin(Planet *planet, int builtin_index)
250 {
251      Obj *builtins;
252      int max = getBuiltInObjs(&builtins);
253      if (builtin_index < 0 || builtin_index >= max) {
254           PyErr_Format(PyExc_TypeError, "internal error: libastro has"
255                        " no builtin object at slot %d", builtin_index);
256           return -1;
257      }
258      memcpy(&planet->obj, &builtins[builtin_index], sizeof(Obj));
259      return 0;
260 }
261 
262 /* Angle: Python float which prints itself as a sexagesimal value,
263    like 'hours:minutes:seconds' or 'degrees:minutes:seconds',
264    depending on the factor given it. */
265 
266 static PyTypeObject AngleType;
267 
268 typedef struct {
269      PyFloatObject f;
270      double factor;
271 } AngleObject;
272 
Angle_new(PyObject * self,PyObject * args,PyObject * kw)273 static PyObject *Angle_new(PyObject *self, PyObject *args, PyObject *kw)
274 {
275      PyErr_SetString(PyExc_TypeError,
276                      "you can only create an ephem.Angle"
277                      " through ephem.degrees() or ephem.hours()");
278      return 0;
279 }
280 
new_Angle(double radians,double factor)281 static PyObject* new_Angle(double radians, double factor)
282 {
283      AngleObject *ea;
284      ea = PyObject_NEW(AngleObject, &AngleType);
285      if (ea) {
286 	  ea->f.ob_fval = radians;
287 	  ea->factor = factor;
288      }
289      return (PyObject*) ea;
290 }
291 
Angle_format(PyObject * self)292 static char *Angle_format(PyObject *self)
293 {
294      AngleObject *ea = (AngleObject*) self;
295      static char buffer[13];
296      fs_sexa(buffer, ea->f.ob_fval * ea->factor, 3,
297              ea->factor == radhr(1) ? 360000 : 36000);
298      return buffer[0] != ' ' ? buffer
299 	  : buffer[1] != ' ' ? buffer + 1
300 	  : buffer + 2;
301 }
302 
Angle_str(PyObject * self)303 static PyObject* Angle_str(PyObject *self)
304 {
305      return PyUnicode_FromString(Angle_format(self));
306 }
307 
Angle_print(PyObject * self,FILE * fp,int flags)308 static int Angle_print(PyObject *self, FILE *fp, int flags)
309 {
310      fputs(Angle_format(self), fp);
311      return 0;
312 }
313 
Angle_pos(PyObject * self)314 static PyObject *Angle_pos(PyObject *self)
315 {
316      Py_INCREF(self);
317      return self;
318 }
319 
Angle_neg(PyObject * self)320 static PyObject *Angle_neg(PyObject *self)
321 {
322      AngleObject *ea = (AngleObject*) self;
323      double radians = ea->f.ob_fval;
324      return new_Angle(- radians, ea->factor);
325 }
326 
Angle_get_norm(PyObject * self,void * v)327 static PyObject *Angle_get_norm(PyObject *self, void *v)
328 {
329      AngleObject *ea = (AngleObject*) self;
330      double radians = ea->f.ob_fval;
331      if (radians < 0)
332           return new_Angle(fmod(radians, 2*PI) + 2*PI, ea->factor);
333      if (radians >= 2*PI)
334           return new_Angle(fmod(radians, 2*PI), ea->factor);
335      Py_INCREF(self);
336      return self;
337 }
338 
Angle_get_znorm(PyObject * self,void * v)339 static PyObject *Angle_get_znorm(PyObject *self, void *v)
340 {
341      AngleObject *ea = (AngleObject*) self;
342      double radians = ea->f.ob_fval;
343      if (radians <= -PI)
344           return new_Angle(fmod(radians + PI, 2*PI) + PI, ea->factor);
345      if (radians > PI)
346           return new_Angle(fmod(radians - PI, 2*PI) - PI, ea->factor);
347      Py_INCREF(self);
348      return self;
349 }
350 
351 static PyNumberMethods Angle_NumberMethods = {
352      NULL, NULL, NULL, NULL, NULL, NULL, /* skip six fields */
353 #if PY_MAJOR_VERSION == 2
354      NULL,
355 #endif
356      Angle_neg, /* nb_negative */
357      Angle_pos, /* nb_positive */
358      NULL
359 };
360 
361 static PyGetSetDef Angle_getset[] = {
362      {"norm", Angle_get_norm, NULL,
363       "Return this angle normalized to the interval [0, 2*pi).", 0},
364      {"znorm", Angle_get_znorm, NULL,
365       "Return this angle normalized to the interval (-pi, pi].", 0},
366      {NULL}
367 };
368 
369 static PyTypeObject AngleType = {
370      PyVarObject_HEAD_INIT(NULL, 0)
371      "ephem.Angle",
372      sizeof(AngleObject),
373      0,
374      0,				/* tp_dealloc */
375      Angle_print,		/* tp_print */
376      0,				/* tp_getattr */
377      0,				/* tp_setattr */
378      0,				/* tp_compare */
379      0,				/* tp_repr */
380      &Angle_NumberMethods,      /* tp_as_number */
381      0,				/* tp_as_sequence */
382      0,				/* tp_as_mapping */
383      0,				/* tp_hash */
384      0,				/* tp_call */
385      Angle_str,			/* tp_str */
386      0,				/* tp_getattro */
387      0,				/* tp_setattro */
388      0,				/* tp_as_buffer */
389      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
390      "An angle in radians that can print itself in an astronomical format.\n"
391      "Use ephem.degrees() and ephem.radians() to create one.", /* tp_doc */
392      0,				/* tp_traverse */
393      0,				/* tp_clear */
394      0,				/* tp_richcompare */
395      0,				/* tp_weaklistoffset */
396      0,				/* tp_iter */
397      0,				/* tp_iternext */
398      0,                         /* tp_methods */
399      0,				/* tp_members */
400      Angle_getset,		/* tp_getset */
401      0, /* &PyFloatType*/	/* tp_base */
402      0,				/* tp_dict */
403      0,				/* tp_descr_get */
404      0,				/* tp_descr_set */
405      0,				/* tp_dictoffset */
406      0,				/* tp_init */
407      0,				/* tp_alloc */
408      (newfunc) Angle_new,       /* tp_new */
409      0,				/* tp_free */
410 };
411 
412 /* Date: a Python Float that can print itself as a date, and that
413    supports triple() and tuple() methods for extracting its date and
414    time components. */
415 
416 static PyTypeObject DateType;
417 
418 typedef PyFloatObject DateObject;
419 
parse_mjd_from_number(PyObject * o,double * mjdp)420 static int parse_mjd_from_number(PyObject *o, double *mjdp)
421 {
422      return PyNumber_AsDouble(o, mjdp);
423 }
424 
parse_mjd_from_string(PyObject * so,double * mjdp)425 static int parse_mjd_from_string(PyObject *so, double *mjdp)
426 {
427      /* Run the Python code: s = so.strip() */
428      PyObject *emptytuple = PyTuple_New(0);
429      PyObject *split_func = PyObject_GetAttrString(so, "split");
430      PyObject *pieces = PyObject_Call(split_func, emptytuple, 0);
431      Py_ssize_t len = PyObject_Length(pieces);
432      int year = 0, month = 1;
433      double day = 1.0;
434 
435      Py_DECREF(emptytuple);
436      Py_DECREF(split_func);
437 
438      if ((len < 1) || (len > 2))
439 	  goto fail;
440 
441      if (len >= 1) {
442 	  int i;
443           const char *s = PyUnicode_AsUTF8(PyList_GetItem(pieces, 0));
444           if (!s) goto fail;
445 
446 	  /* Make sure all characters are in set '-/.0123456789' */
447 
448 	  for (i=0; s[i]; i++) {
449 	       if (s[i] != '-' && s[i] != '/' && s[i] != '.'
450 		   && (s[i] < '0' || s[i] > '9')) {
451 		    goto fail;
452                }
453           }
454 
455 	  f_sscandate((char*) s, PREF_YMD, &month, &day, &year);
456      }
457 
458      if (len >= 2) {
459           double hours;
460           int status = scansexa(PyList_GetItem(pieces, 1), &hours);
461           if (status == -1) {
462                goto fail;
463           }
464 	  day += hours / 24.;
465      }
466 
467      cal_mjd(month, day, year, mjdp);
468 
469      Py_DECREF(pieces);
470      return 0;
471 
472 fail:
473      if (! PyErr_Occurred()) {
474 	  PyObject *repr = PyObject_Repr(so);
475           PyObject *complaint = PyUnicode_FromFormat(
476 	       "your date string %s does not look like a year/month/day"
477 	       " optionally followed by hours:minutes:seconds",
478                PyUnicode_AsUTF8(repr));
479 	  PyErr_SetObject(PyExc_ValueError, complaint);
480 	  Py_DECREF(repr);
481 	  Py_DECREF(complaint);
482      }
483      Py_DECREF(pieces);
484      return -1;
485 }
486 
parse_mjd_from_tuple(PyObject * value,double * mjdp)487 static int parse_mjd_from_tuple(PyObject *value, double *mjdp)
488 {
489      double day = 1.0, hours = 0.0, minutes = 0.0, seconds = 0.0;
490      int year, month = 1;
491      if (!PyArg_ParseTuple(value, "i|idddd:date.tuple", &year, &month, &day,
492 			   &hours, &minutes, &seconds)) return -1;
493      cal_mjd(month, day, year, mjdp);
494      if (hours) *mjdp += hours / 24.;
495      if (minutes) *mjdp += minutes / (24. * 60.);
496      if (seconds) *mjdp += seconds / (24. * 60. * 60.);
497      return 0;
498 }
499 
parse_mjd_from_datetime(PyObject * value,double * mjdp)500 static int parse_mjd_from_datetime(PyObject *value, double *mjdp)
501 {
502      cal_mjd(PyDateTime_GET_MONTH(value),
503              PyDateTime_GET_DAY(value),
504              PyDateTime_GET_YEAR(value),
505              mjdp);
506 
507      if (!PyDateTime_Check(value))
508           return 0;  // the value is a mere Date
509 
510      *mjdp += PyDateTime_DATE_GET_HOUR(value) / 24.;
511      *mjdp += PyDateTime_DATE_GET_MINUTE(value) / (24. * 60.);
512      *mjdp += PyDateTime_DATE_GET_SECOND(value) / (24. * 60. * 60.);
513      *mjdp += PyDateTime_DATE_GET_MICROSECOND(value)
514           / (24. * 60. * 60. * 1000000.);
515 
516      PyObject *offset = PyObject_CallMethod(value, "utcoffset", NULL);
517      if (!offset)
518           return -1;
519 
520      if (offset == Py_None) {
521           Py_DECREF(offset);
522           return 0;  // no time zone information: assume UTC
523      }
524 
525      PyObject *seconds = PyObject_CallMethod(offset, "total_seconds", NULL);
526      Py_DECREF(offset);
527      if (!seconds)
528           return -1;
529 
530      double seconds_double;
531      if (PyNumber_AsDouble(seconds, &seconds_double)) {
532           Py_DECREF(seconds);
533           return -1;
534      }
535      Py_DECREF(seconds);
536 
537      *mjdp -= seconds_double / (24. * 60. * 60.);
538      return 0;
539 }
540 
parse_mjd(PyObject * value,double * mjdp)541 static int parse_mjd(PyObject *value, double *mjdp)
542 {
543      if (PyNumber_Check(value))
544 	  return parse_mjd_from_number(value, mjdp);
545      else if (PyUnicode_Check3(value))
546 	  return parse_mjd_from_string(value, mjdp);
547      else if (PyTuple_Check(value))
548 	  return parse_mjd_from_tuple(value, mjdp);
549      else if (PyDate_Check(value))
550           return parse_mjd_from_datetime(value, mjdp);
551      PyErr_SetString(PyExc_ValueError, "dates must be initialized"
552                      " from a number, string, tuple, or datetime");
553      return -1;
554 }
555 
mjd_six(double mjd,int * yearp,int * monthp,int * dayp,int * hourp,int * minutep,double * secondp)556 static void mjd_six(double mjd, int *yearp, int *monthp, int *dayp,
557 		    int *hourp, int *minutep, double *secondp)
558 {
559      mjd += 0.5 / 8.64e+10;  /* half microsecond, so floor() becomes "round" */
560      mjd_cal(mjd, monthp, &mjd, yearp);
561 
562      double day = floor(mjd);
563      double fraction = mjd - day;
564      *dayp = (int) day;
565 
566      /* Turns out a Windows "long" is only 32 bits, so "long long". */
567      long long us = floor(fraction * 8.64e+10);  /* microseconds per day */
568      long long minute = us / 60000000;
569      us -= minute * 60000000;
570      *secondp = ((double) us) / 1e6;
571      *hourp = (int) (minute / 60);
572      *minutep = (int) (minute - *hourp * 60);
573 }
574 
build_Date(double mjd)575 static PyObject* build_Date(double mjd)
576 {
577      DateObject *new = PyObject_New(PyFloatObject, &DateType);
578      if (new) new->ob_fval = mjd;
579      return (PyObject*) new;
580 }
581 
Date_new(PyObject * self,PyObject * args,PyObject * kw)582 static PyObject *Date_new(PyObject *self, PyObject *args, PyObject *kw)
583 {
584      PyObject *arg;
585      double mjd;
586      if (kw) {
587 	  PyErr_SetString(PyExc_TypeError,
588 			  "this function does not accept keyword arguments");
589 	  return 0;
590      }
591      if (!PyArg_ParseTuple(args, "O:date", &arg)) return 0;
592      if (parse_mjd(arg, &mjd)) return 0;
593      return build_Date(mjd);
594 }
595 
Date_format_value(double value)596 static char *Date_format_value(double value)
597 {
598      static char buffer[64];
599      int year, month, day, hour, minute;
600      double second;
601      /* Note the offset, which makes us round to the nearest second. */
602      mjd_six(value + 0.5 / (24.0 * 60.0 * 60.0),
603              &year, &month, &day, &hour, &minute, &second);
604      sprintf(buffer, "%d/%d/%d %02d:%02d:%02d",
605 	     year, month, day, hour, minute, (int) second);
606      return buffer;
607 }
608 
Date_format(PyObject * self)609 static char *Date_format(PyObject *self)
610 {
611      DateObject *d = (DateObject*) self;
612      return Date_format_value(d->ob_fval);
613 }
614 
Date_str(PyObject * self)615 static PyObject* Date_str(PyObject *self)
616 {
617      return PyUnicode_FromString(Date_format(self));
618 }
619 
Date_print(PyObject * self,FILE * fp,int flags)620 static int Date_print(PyObject *self, FILE *fp, int flags)
621 {
622      fputs(Date_format(self), fp);
623      return 0;
624 }
625 
Date_triple(PyObject * self)626 static PyObject *Date_triple(PyObject *self)
627 {
628      int year, month;
629      double day;
630      DateObject *d = (DateObject*) self;
631      mjd_cal(d->ob_fval, &month, &day, &year);
632      return Py_BuildValue("iid", year, month, day);
633 }
634 
Date_tuple(PyObject * self)635 static PyObject *Date_tuple(PyObject *self)
636 {
637      int year, month, day, hour, minute;
638      double second;
639      DateObject *d = (DateObject*) self;
640      mjd_six(d->ob_fval, &year, &month, &day, &hour, &minute, &second);
641      return Py_BuildValue("iiiiid", year, month, day, hour, minute, second);
642 }
643 
Date_datetime(PyObject * self)644 static PyObject *Date_datetime(PyObject *self)
645 {
646      int year, month, day, hour, minute;
647      double second;
648      DateObject *d = (DateObject*) self;
649      mjd_six(d->ob_fval, &year, &month, &day, &hour, &minute, &second);
650      return PyDateTime_FromDateAndTime(
651           year, month, day, hour, minute, (int) floor(second),
652           (int) floor(1e6 * fmod(second, 1.0))
653           );
654 }
655 
656 static PyMethodDef Date_methods[] = {
657      {"triple", (PyCFunction) Date_triple, METH_NOARGS,
658       "Return the date as a (year, month, day_with_fraction) tuple"},
659      {"tuple", (PyCFunction) Date_tuple, METH_NOARGS,
660       "Return the date as a (year, month, day, hour, minute, second) tuple"},
661      {"datetime", (PyCFunction) Date_datetime, METH_NOARGS,
662       "Return the date as a (year, month, day, hour, minute, second) tuple"},
663      {NULL}
664 };
665 
666 static PyTypeObject DateType = {
667      PyVarObject_HEAD_INIT(NULL, 0)
668      "ephem.Date",
669      sizeof(PyFloatObject),
670      0,
671      0,				/* tp_dealloc */
672      Date_print,		/* tp_print */
673      0,				/* tp_getattr */
674      0,				/* tp_setattr */
675      0,				/* tp_compare */
676      0,				/* tp_repr */
677      0,				/* tp_as_number */
678      0,				/* tp_as_sequence */
679      0,				/* tp_as_mapping */
680      0,				/* tp_hash */
681      0,				/* tp_call */
682      Date_str,			/* tp_str */
683      0,				/* tp_getattro */
684      0,				/* tp_setattro */
685      0,				/* tp_as_buffer */
686      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
687      "Floating point value used by ephem to represent a date.\n"
688      "The value is the number of days since 1899 December 31 12:00 UT. When\n"
689      "creating an instance you can pass in a Python datetime instance,"
690      " timetuple,\nyear-month-day triple, or a plain float. Run str() on"
691      "this object to see\nthe UTC date it represents.", /* tp_doc */
692      0,				/* tp_traverse */
693      0,				/* tp_clear */
694      0,				/* tp_richcompare */
695      0,				/* tp_weaklistoffset */
696      0,				/* tp_iter */
697      0,				/* tp_iternext */
698      Date_methods,		/* tp_methods */
699      0,				/* tp_members */
700      0,				/* tp_getset */
701      0, /*&PyFloatType,*/	/* tp_base */
702      0,				/* tp_dict */
703      0,				/* tp_descr_get */
704      0,				/* tp_descr_set */
705      0,				/* tp_dictoffset */
706      0,				/* tp_init */
707      0,				/* tp_alloc */
708      (newfunc) Date_new,	/* tp_new */
709      0,				/* tp_free */
710 };
711 
712 /*
713  * Both pairs of routines below have to access a structure member to
714  * which they have been given the offset.
715  */
716 
717 #define THE_FLOAT (* (float*) ((char*)self + (size_t)v))
718 #define THE_DOUBLE (* (double*) ((char*)self + (size_t)v))
719 
720 /*
721  * Sexigesimal values can be assigned either floating-point numbers or
722  * strings like '33:45:10', and return special Angle floats that
723  * give pretty textual representations when asked for their str().
724  */
725 
726 typedef struct {
727      int type;			/* T_FLOAT or T_DOUBLE */
728      int offset;		/* offset of structure member */
729      double ifactor;		/* internal units per radian */
730      double efactor;		/* external units per radian */
731 } AngleMember;
732 
parse_angle(PyObject * value,double factor,double * result)733 static int parse_angle(PyObject *value, double factor, double *result)
734 {
735      if (PyNumber_Check(value)) {
736 	  return PyNumber_AsDouble(value, result);
737      } else if (PyUnicode_Check3(value)) {
738 	  double scaled;
739           if (scansexa(value, &scaled) == -1) {
740                return -1;
741 	  }
742 	  *result = scaled / factor;
743 	  return 0;
744      } else {
745 	  PyErr_SetString(PyExc_TypeError,
746 			  "angle can only be created from string or number");
747 	  return -1;
748      }
749 }
750 
to_angle(PyObject * value,double efactor,int * status)751 static double to_angle(PyObject *value, double efactor, int *status)
752 {
753      double r;
754 #if PY_MAJOR_VERSION == 2
755      /* Support Unicode strings under Python 2 */
756      if (PyUnicode_Check(value)) {
757           value = PyUnicode_AsUTF8String(value);
758           if (!value) {
759 	       *status = -1;
760                return 0;
761           }
762      }
763 #endif
764      if (PyNumber_Check(value)) {
765 	  value = PyNumber_Float(value);
766 	  if (!value) {
767 	       *status = -1;
768 	       return 0;
769 	  }
770 	  r = PyFloat_AsDouble(value);
771 	  Py_DECREF(value);
772 	  *status = 0;
773 	  return r;
774      } else if (PyUnicode_Check3(value)) {
775 	  double scaled;
776           *status = scansexa(value, &scaled);
777 	  return scaled / efactor;
778      } else {
779 	  PyErr_SetString(PyExc_TypeError,
780 			  "can only update value with string or number");
781 	  *status = -1;
782 	  return 0;
783      }
784 }
785 
786 /* Hours stored as radian double. */
787 
getd_rh(PyObject * self,void * v)788 static PyObject* getd_rh(PyObject *self, void *v)
789 {
790      return new_Angle(THE_DOUBLE, radhr(1));
791 }
792 
setd_rh(PyObject * self,PyObject * value,void * v)793 static int setd_rh(PyObject *self, PyObject *value, void *v)
794 {
795      int status;
796      THE_DOUBLE = to_angle(value, radhr(1), &status);
797      return status;
798 }
799 
800 /* Degrees stored as radian double. */
801 
getd_rd(PyObject * self,void * v)802 static PyObject* getd_rd(PyObject *self, void *v)
803 {
804      return new_Angle(THE_DOUBLE, raddeg(1));
805 }
806 
setd_rd(PyObject * self,PyObject * value,void * v)807 static int setd_rd(PyObject *self, PyObject *value, void *v)
808 {
809      int status;
810      THE_DOUBLE = to_angle(value, raddeg(1), &status);
811      return status;
812 }
813 
814 /* Degrees stored as degrees, but for consistency we return their
815    floating point value as radians. */
816 
getf_dd(PyObject * self,void * v)817 static PyObject* getf_dd(PyObject *self, void *v)
818 {
819      return new_Angle(THE_FLOAT * degrad(1), raddeg(1));
820 }
821 
setf_dd(PyObject * self,PyObject * value,void * v)822 static int setf_dd(PyObject *self, PyObject *value, void *v)
823 {
824      int status;
825      THE_FLOAT = (float) to_angle(value, raddeg(1), &status);
826      return status;
827 }
828 
829 /* MDJ stored as double. */
830 
getd_mjd(PyObject * self,void * v)831 static PyObject* getd_mjd(PyObject *self, void *v)
832 {
833      return build_Date(THE_DOUBLE);
834 }
835 
setd_mjd(PyObject * self,PyObject * value,void * v)836 static int setd_mjd(PyObject *self, PyObject *value, void *v)
837 {
838      double result;
839      if (parse_mjd(value, &result)) return -1;
840      THE_DOUBLE = result;
841      return 0;
842 }
843 
844 /* #undef THE_FLOAT
845    #undef THE_DOUBLE */
846 
847 /*
848  * The following values are ones for which XEphem provides special
849  * routines for their reading and writing, usually because they are
850  * encoded into one or two bytes to save space in the obj structure
851  * itself.  These are simply wrappers around those functions.
852  */
853 
854 /* Spectral codes. */
855 
get_f_spect(PyObject * self,void * v)856 static PyObject* get_f_spect(PyObject *self, void *v)
857 {
858      Body *b = (Body*) self;
859      return PyUnicode_FromStringAndSize(b->obj.f_spect, 2);
860 }
861 
set_f_spect(PyObject * self,PyObject * value,void * v)862 static int set_f_spect(PyObject *self, PyObject *value, void *v)
863 {
864      Body *b = (Body*) self;
865      const char *s;
866      if (!PyUnicode_Check3(value)) {
867 	  PyErr_SetString(PyExc_ValueError, "spectral code must be a string");
868 	  return -1;
869      }
870      s = PyUnicode_AsUTF8(value);
871      if (!s)
872           return -1;
873      if (s[0] == '\0' || s[1] == '\0' || s[2] != '\0') {
874 	  PyErr_SetString(PyExc_ValueError,
875 			  "spectral code must be two characters long");
876 	  return -1;
877      }
878      b->obj.f_spect[0] = s[0];
879      b->obj.f_spect[1] = s[1];
880      return 0;
881 }
882 
883 /* Fixed object diameter ratio. */
884 
get_f_ratio(PyObject * self,void * v)885 static PyObject* get_f_ratio(PyObject *self, void *v)
886 {
887      Body *b = (Body*) self;
888      return PyFloat_FromDouble(get_ratio(&b->obj));
889 }
890 
set_f_ratio(PyObject * self,PyObject * value,void * v)891 static int set_f_ratio(PyObject *self, PyObject *value, void *v)
892 {
893      Body *b = (Body*) self;
894      double maj, min;
895      if (!PyArg_ParseTuple(value, "dd", &maj, &min)) return -1;
896      set_ratio(&b->obj, maj, min);
897      return 0;
898 }
899 
900 /* Position angle of fixed object. */
901 
get_f_pa(PyObject * self,void * v)902 static PyObject* get_f_pa(PyObject *self, void *v)
903 {
904      Body *b = (Body*) self;
905      return PyFloat_FromDouble(get_pa(&b->obj));
906 }
907 
set_f_pa(PyObject * self,PyObject * value,void * v)908 static int set_f_pa(PyObject *self, PyObject *value, void *v)
909 {
910      Body *b = (Body*) self;
911      if (!PyNumber_Check(value)) {
912 	  PyErr_SetString(PyExc_ValueError, "position angle must be a float");
913 	  return -1;
914      }
915      set_pa(&b->obj, PyFloat_AsDouble(value));
916      return 0;
917 }
918 
919 /* Proper motion of fixed object; presented in milli-arcseconds per
920    year, but stored as radians per day in a float. */
921 
922 #define PROPER (1.327e-11)      /* from libastro's dbfmt.c */
923 
getf_proper_ra(PyObject * self,void * v)924 static PyObject* getf_proper_ra(PyObject *self, void *v)
925 {
926      Body *b = (Body*) self;
927      return PyFloat_FromDouble(b->obj.f_pmRA * cos(b->obj.f_dec) / PROPER);
928 }
929 
setf_proper_ra(PyObject * self,PyObject * value,void * v)930 static int setf_proper_ra(PyObject *self, PyObject *value, void *v)
931 {
932      Body *b = (Body*) self;
933      if (!PyNumber_Check(value)) {
934 	  PyErr_SetString(PyExc_ValueError, "express proper motion"
935                           " as milli-arcseconds per year");
936 	  return -1;
937      }
938      b->obj.f_pmRA = (float)
939        (PyFloat_AsDouble(value) / cos(b->obj.f_dec) * PROPER);
940      return 0;
941 }
942 
getf_proper_dec(PyObject * self,void * v)943 static PyObject* getf_proper_dec(PyObject *self, void *v)
944 {
945      Body *b = (Body*) self;
946      return PyFloat_FromDouble(b->obj.f_pmdec / PROPER);
947 }
948 
setf_proper_dec(PyObject * self,PyObject * value,void * v)949 static int setf_proper_dec(PyObject *self, PyObject *value, void *v)
950 {
951      Body *b = (Body*) self;
952      if (!PyNumber_Check(value)) {
953 	  PyErr_SetString(PyExc_ValueError, "express proper motion"
954                           " as milli-arcseconds per year");
955 	  return -1;
956      }
957      b->obj.f_pmdec = (float) (PyFloat_AsDouble(value) * PROPER);
958      return 0;
959 }
960 
961 /*
962  * Observer object.
963  */
964 
965 /*
966  * Constructor and methods.
967  */
968 
Observer_init(PyObject * self,PyObject * args,PyObject * kwds)969 static int Observer_init(PyObject *self, PyObject *args, PyObject *kwds)
970 {
971      Observer *o = (Observer*) self;
972      static char *kwlist[] = {0};
973      if (!PyArg_ParseTupleAndKeywords(args, kwds, ":Observer", kwlist))
974 	  return -1;
975      o->now.n_mjd = mjd_now();
976      o->now.n_lat = o->now.n_lng = o->now.n_tz = o->now.n_elev
977 	  = o->now.n_dip = 0;
978      o->now.n_temp = 15.0;
979      o->now.n_pressure = 1010;
980      o->now.n_epoch = J2000;
981      return 0;
982 }
983 
Observer_sidereal_time(PyObject * self)984 static PyObject *Observer_sidereal_time(PyObject *self)
985 {
986      Observer *o = (Observer*) self;
987      double lst;
988      now_lst(&o->now, &lst);
989      return new_Angle(hrrad(lst), radhr(1));
990 }
991 
Observer_radec_of(PyObject * self,PyObject * args,PyObject * kwds)992 static PyObject *Observer_radec_of(PyObject *self, PyObject *args,
993 				   PyObject *kwds)
994 {
995      Observer *o = (Observer*) self;
996      static char *kwlist[] = {"az", "alt", 0};
997      PyObject *azo, *alto, *rao, *deco;
998      double az, alt, lst, ha, ra, dec;
999 
1000      if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:Observer.radec_of",
1001 				      kwlist, &azo, &alto))
1002 	  return 0;
1003 
1004      if (parse_angle(azo, raddeg(1), &az) == -1)
1005 	  return 0;
1006      if (parse_angle(alto, raddeg(1), &alt) == -1)
1007 	  return 0;
1008 
1009      now_lst(&o->now, &lst);
1010      lst = hrrad(lst);
1011      unrefract(o->now.n_pressure, o->now.n_temp, alt, &alt);
1012      aa_hadec(o->now.n_lat, alt, az, &ha, &dec);
1013      ra = fmod(lst - ha, 2*PI);
1014 
1015      pref_set(PREF_EQUATORIAL, PREF_TOPO); /* affects call to ap_as? */
1016      if (o->now.n_epoch != EOD)
1017 	  ap_as(&o->now, o->now.n_epoch, &ra, &dec);
1018 
1019      rao = new_Angle(ra, radhr(1));
1020      if (!rao) return 0;
1021      deco = new_Angle(dec, raddeg(1));
1022      if (!deco) return 0;
1023      return Py_BuildValue("NN", rao, deco);
1024 }
1025 
1026 /*
1027  * Member access.
1028  */
1029 
get_elev(PyObject * self,void * v)1030 static PyObject *get_elev(PyObject *self, void *v)
1031 {
1032      Observer *o = (Observer*) self;
1033      return PyFloat_FromDouble(o->now.n_elev * ERAD);
1034 }
1035 
set_elev(PyObject * self,PyObject * value,void * v)1036 static int set_elev(PyObject *self, PyObject *value, void *v)
1037 {
1038      int r;
1039      double n;
1040      Observer *o = (Observer*) self;
1041      if (!PyNumber_Check(value)) {
1042 	  PyErr_SetString(PyExc_TypeError, "Elevation must be numeric");
1043 	  return -1;
1044      }
1045      r = PyNumber_AsDouble(value, &n);
1046      if (!r) o->now.n_elev = n / ERAD;
1047      return 0;
1048 }
1049 
1050 /*
1051  * Observer class type.
1052  *
1053  * Note that we commandeer the n_dip field for our own purposes.
1054  * XEphem uses it to compute the beginning and end of twilight; since
1055  * we have no twilight functions, we use it to store the displacement
1056  * for rising and setting calculations.
1057  */
1058 
1059 #define VOFF(member) ((void*) OFF(member))
1060 #define OFF(member) offsetof(Observer, now.member)
1061 
1062 static PyMethodDef Observer_methods[] = {
1063      {"sidereal_time", (PyCFunction) Observer_sidereal_time, METH_NOARGS,
1064       "compute the local sidereal time for this location and time"},
1065      {"radec_of", (PyCFunction) Observer_radec_of,
1066       METH_VARARGS | METH_KEYWORDS,
1067       "compute the right ascension and declination of a point"
1068       " identified by its azimuth and altitude"},
1069      {NULL}
1070 };
1071 
1072 static PyGetSetDef Observer_getset[] = {
1073      {"date", getd_mjd, setd_mjd, "Date", VOFF(n_mjd)},
1074      {"lat", getd_rd, setd_rd, "Latitude north of the Equator" D, VOFF(n_lat)},
1075      {"lon", getd_rd, setd_rd, "Longitude east of Greenwich" D, VOFF(n_lng)},
1076      {"elevation", get_elev, set_elev,
1077       "Elevation above sea level in meters", NULL},
1078      {"horizon", getd_rd, setd_rd,
1079       "The angle above (+) or below (-) the horizon at which an object"
1080       " should be considered at the moment of rising or setting" D,
1081       VOFF(n_dip)},
1082      {"epoch", getd_mjd, setd_mjd, "Precession epoch", VOFF(n_epoch)},
1083 
1084      /* For compatibility with older scripts: */
1085      {"long", getd_rd, setd_rd, "Longitude east of Greenwich" D, VOFF(n_lng)},
1086 
1087      {NULL}
1088 };
1089 
1090 static PyMemberDef Observer_members[] = {
1091      {"temp", T_DOUBLE, OFF(n_temp), 0,
1092       "alias for 'temperature' attribute"},
1093      {"temperature", T_DOUBLE, OFF(n_temp), 0,
1094       "atmospheric temperature in degrees Celsius"},
1095      {"pressure", T_DOUBLE, OFF(n_pressure), 0,
1096       "atmospheric pressure in millibar"},
1097      {NULL}
1098 };
1099 
1100 #undef OFF
1101 
1102 static PyTypeObject ObserverType = {
1103      PyVarObject_HEAD_INIT(NULL, 0)
1104      "_libastro.Observer",
1105      sizeof(Observer),
1106      0,
1107      0,				/* tp_dealloc */
1108      0,				/* tp_print */
1109      0,				/* tp_getattr */
1110      0,				/* tp_setattr */
1111      0,				/* tp_compare */
1112      0,				/* tp_repr */
1113      0,				/* tp_as_number */
1114      0,				/* tp_as_sequence */
1115      0,				/* tp_as_mapping */
1116      0,				/* tp_hash */
1117      0,				/* tp_call */
1118      0,				/* tp_str */
1119      0,				/* tp_getattro */
1120      0,				/* tp_setattro */
1121      0,				/* tp_as_buffer */
1122      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/* tp_flags */
1123      "Describes an observer standing on the Earth's surface.\n"
1124      "This object can also store the time at which the observer is watching;\n"
1125      "the epoch in which they want astrometric coordinates returned; and the\n"
1126      "temperature and barometric pressure, which affect horizon refraction.\n"
1127      "See Body.compute() for how to use instances of this class.", /* tp_doc */
1128      0,				/* tp_traverse */
1129      0,				/* tp_clear */
1130      0,				/* tp_richcompare */
1131      0,				/* tp_weaklistoffset */
1132      0,				/* tp_iter */
1133      0,				/* tp_iternext */
1134      Observer_methods,		/* tp_methods */
1135      Observer_members,		/* tp_members */
1136      Observer_getset,		/* tp_getset */
1137      0,				/* tp_base */
1138      0,				/* tp_dict */
1139      0,				/* tp_descr_get */
1140      0,				/* tp_descr_set */
1141      0,				/* tp_dictoffset */
1142      Observer_init,		/* tp_init */
1143      0,				/* tp_alloc */
1144      0,				/* tp_new */
1145      0				/* tp_free */
1146 };
1147 
1148 /*
1149  *
1150  * BODIES
1151  *
1152  */
1153 
1154 static PyTypeObject BodyType;
1155 static PyTypeObject PlanetType;
1156 static PyTypeObject JupiterType;
1157 static PyTypeObject SaturnType;
1158 static PyTypeObject MoonType;
1159 static PyTypeObject PlanetMoonType;
1160 
1161 static PyObject* Body_compute(PyObject *self, PyObject *args, PyObject *kwds);
1162 
1163 /* Body and Planet may be initialized privately by their subclasses
1164    using these setup functions. */
1165 
Body_setup(Body * body)1166 static void Body_setup(Body *body)
1167 {
1168      body->obj.o_flags = 0;
1169 }
1170 
1171 /* Body, Planet, and PlanetMoon are abstract classes that resist
1172    instantiation. */
1173 
Body_init(PyObject * self,PyObject * args,PyObject * kw)1174 static int Body_init(PyObject *self, PyObject *args, PyObject *kw)
1175 {
1176      PyErr_SetString(PyExc_TypeError, "you cannot create a generic Body");
1177      return -1;
1178 }
1179 
Planet_setup(Planet * planet,int builtin_index,PyObject * args,PyObject * kw)1180 static int Planet_setup(Planet *planet, int builtin_index,
1181                         PyObject *args, PyObject *kw)
1182 {
1183      if (copy_planet_from_builtin(planet, builtin_index) == -1)
1184           return -1;
1185      planet->name = 0;
1186      if (PyTuple_Check(args) && PyTuple_Size(args)) {
1187 	  PyObject *result = Body_compute((PyObject*) planet, args, kw);
1188 	  if (!result) return -1;
1189 	  Py_DECREF(result);
1190      }
1191      return 0;
1192 }
1193 
Planet_init(PyObject * self,PyObject * args,PyObject * kw)1194 static int Planet_init(PyObject *self, PyObject *args, PyObject *kw)
1195 {
1196      int builtin_index;
1197      PyObject *o = PyObject_GetAttrString(self, "__planet__");
1198      if (!o) {
1199           PyErr_SetString(PyExc_TypeError, "internal error: cannot"
1200                            " init Planet without a __planet__ code");
1201           return -1;
1202      }
1203      builtin_index = PyLong_AsLong(o);
1204      Py_DECREF(o);
1205      if ((builtin_index == -1) && PyErr_Occurred()) {
1206           PyErr_SetString(PyExc_TypeError, "internal error: __planet__"
1207                            " code must be an integer");
1208           return -1;
1209      }
1210      return Planet_setup((Planet*) self, builtin_index, args, kw);
1211 }
1212 
1213 /* But Jupiter, Saturn, and the Moon, as concrete classes, do allow
1214    initialization. */
1215 
Jupiter_init(PyObject * self,PyObject * args,PyObject * kw)1216 static int Jupiter_init(PyObject *self, PyObject *args, PyObject *kw)
1217 {
1218      return Planet_setup((Planet*) self, JUPITER, args, kw);
1219 }
1220 
Saturn_init(PyObject * self,PyObject * args,PyObject * kw)1221 static int Saturn_init(PyObject *self, PyObject *args, PyObject *kw)
1222 {
1223      return Planet_setup((Planet*) self, SATURN, args, kw);
1224 }
1225 
Moon_init(PyObject * self,PyObject * args,PyObject * kw)1226 static int Moon_init(PyObject *self, PyObject *args, PyObject *kw)
1227 {
1228      return Planet_setup((Planet*) self, MOON, args, kw);
1229 }
1230 
1231 /* Bodies need to unlink any name object when finished. */
1232 
Body_dealloc(PyObject * self)1233 static void Body_dealloc(PyObject *self)
1234 {
1235      Body *body = (Body*) self;
1236      Py_XDECREF(body->name);
1237      Py_TYPE(self)->tp_free(self);
1238 }
1239 
1240 /* The user-configurable body types also share symmetric
1241    initialization code. */
1242 
1243 #define INIT(NAME, CODE) \
1244 static int NAME##_init(PyObject* self, PyObject* args, PyObject *kw) \
1245 { \
1246      Body *body = (Body*) self; \
1247      Body_setup(body); \
1248      body->name = Py_None; \
1249      Py_INCREF(Py_None); \
1250      body->obj.o_name[0] = '\0'; \
1251      body->obj.o_type = CODE; \
1252      return 0; \
1253 }
1254 
FixedBody_init(PyObject * self,PyObject * args,PyObject * kw)1255 static int FixedBody_init(PyObject* self, PyObject* args, PyObject *kw)
1256 {
1257      Body *body = (Body*) self;
1258      static char *kwlist[] = {0};
1259      if (!PyArg_ParseTupleAndKeywords(args, kw, ":FixedBody", kwlist))
1260          return -1;
1261      Body_setup(body);
1262      body->name = Py_None;
1263      Py_INCREF(Py_None);
1264      body->obj.o_name[0] = '\0';
1265      body->obj.o_type = FIXED;
1266      body->obj.f_epoch = J2000;
1267      return 0;
1268 }
1269 
INIT(BinaryStar,BINARYSTAR)1270 INIT(BinaryStar, BINARYSTAR)
1271 INIT(EllipticalBody, ELLIPTICAL)
1272 INIT(HyperbolicBody, HYPERBOLIC)
1273 INIT(ParabolicBody, PARABOLIC)
1274 
1275 static int EarthSatellite_init(PyObject* self, PyObject* args, PyObject *kw)
1276 {
1277      EarthSatellite *body = (EarthSatellite*) self;
1278      Body_setup((Body*) body);
1279      body->name = Py_None;
1280      Py_INCREF(Py_None);
1281      body->catalog_number = Py_None;
1282      Py_INCREF(Py_None);
1283      body->obj.o_name[0] = '\0';
1284      body->obj.o_type = EARTHSAT;
1285      return 0;
1286 }
1287 
1288 #undef INIT
1289 
1290 /* This compute() method does not actually compute anything, since
1291    several different computations would be necessary to determine the
1292    value of all of a body's fields, and the user may not want them
1293    all; so compute() just stashes away the Observer or date it is
1294    given, and the fields are computed when the user actually accesses
1295    them. */
1296 
Body_compute(PyObject * self,PyObject * args,PyObject * kwds)1297 static PyObject* Body_compute(PyObject *self, PyObject *args, PyObject *kwds)
1298 {
1299      Body *body = (Body*) self;
1300      static char *kwlist[] = {"when", "epoch", 0};
1301      PyObject *when_arg = 0, *epoch_arg = 0;
1302 
1303      if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO:Body.compute", kwlist,
1304 				      &when_arg, &epoch_arg))
1305 	  return 0;
1306 
1307      if (when_arg && PyObject_TypeCheck(when_arg, &ObserverType)) {
1308 
1309 	  /* compute(observer) */
1310 
1311 	  Observer *observer = (Observer*) when_arg;
1312 	  if (epoch_arg) {
1313 	       PyErr_SetString(PyExc_ValueError,
1314 			       "cannot supply an epoch= keyword argument "
1315 			       "because an Observer specifies its own epoch");
1316 	       return 0;
1317 	  }
1318 	  body->now = observer->now;
1319 	  body->obj.o_flags = VALID_GEO | VALID_TOPO;
1320      } else {
1321 	  double when_mjd, epoch_mjd;
1322 
1323 	  /* compute(date, epoch) where date defaults to current time
1324 	     and epoch defaults to 2000.0 */
1325 
1326 	  if (when_arg) {
1327 	       if (parse_mjd(when_arg, &when_mjd) == -1) goto fail;
1328 	  } else
1329 	       when_mjd = mjd_now();
1330 
1331 	  if (epoch_arg) {
1332 	       if (parse_mjd(epoch_arg, &epoch_mjd) == -1) goto fail;
1333 	  } else
1334 	       epoch_mjd = J2000;
1335 
1336 	  /* Since libastro always does topocentric computation, we
1337 	     need to provide a reasonable location and weather. */
1338 	  body->now.n_mjd = when_mjd;
1339 	  body->now.n_lat = body->now.n_lng = body->now.n_tz
1340 	       = body->now.n_elev = body->now.n_dip = 0;
1341 	  body->now.n_temp = 15.0;
1342 	  body->now.n_pressure = 0; /* no refraction */
1343 	  body->now.n_epoch = epoch_mjd;
1344 
1345 	  body->obj.o_flags = VALID_GEO;
1346      }
1347 
1348      if (body->obj.o_type == EARTHSAT) {
1349           double days_from_epoch = fabs(body->obj.es_epoch - body->now.n_mjd);
1350           if (days_from_epoch > 365.0) {
1351 	       PyErr_Format(PyExc_ValueError, "TLE elements are valid for"
1352                " a few weeks around their epoch, but you are asking about"
1353                " a date %d days from the epoch", (int) days_from_epoch);
1354                goto fail;
1355           }
1356      }
1357 
1358      Py_INCREF(Py_None);
1359      return Py_None;
1360 
1361 fail:
1362      return 0;
1363 }
1364 
Body_parallactic_angle(PyObject * self)1365 static PyObject* Body_parallactic_angle(PyObject *self)
1366 {
1367      PyObject *a1, *a2;
1368      Body *body = (Body*) self;
1369      double ha, pa;
1370      if (Body_obj_cir(body, "parallactic_angle", 1) == -1)
1371           return 0;
1372      radec2ha(&(body->now), body->obj.s_astrora, body->obj.s_astrodec, &ha);
1373      pa = parallacticLHD(body->now.n_lat, ha, body->obj.s_astrodec);
1374      a1 = new_Angle(pa, raddeg(1));
1375      if (!a1)
1376           return 0;
1377      a2 = Angle_get_znorm(a1, 0);
1378      Py_XDECREF(a1);
1379      return a2;
1380 }
1381 
Body_writedb(PyObject * self)1382 static PyObject* Body_writedb(PyObject *self)
1383 {
1384      Body *body = (Body*) self;
1385      char line[1024];
1386      db_write_line(&body->obj, line);
1387      return PyUnicode_FromString(line);
1388 }
1389 
1390 void Body__copy_struct(Body *, Body *);
1391 
Body_copy(PyObject * self)1392 static PyObject* Body_copy(PyObject *self)
1393 {
1394      Body *body = (Body *) self;
1395      Body *newbody = (Body*) Py_TYPE(self)->tp_alloc(Py_TYPE(self), 0);
1396      if (!newbody) return 0;
1397      Body__copy_struct(body, newbody);
1398      return (PyObject*) newbody;
1399 }
1400 
Body_repr(PyObject * body_object)1401 static PyObject* Body_repr(PyObject *body_object)
1402 {
1403      Body *body = (Body*) body_object;
1404      if (body->name) {
1405 	  const char *name;
1406 	  PyObject *repr, *result;
1407 	  repr = PyObject_Repr(body->name);
1408 	  if (!repr)
1409                return 0;
1410 	  name = PyUnicode_AsUTF8(repr);
1411 	  if (!name) {
1412                Py_DECREF(repr);
1413                return 0;
1414           }
1415 	  result = PyUnicode_FromFormat("<%s %s at %p>",
1416                                         Py_TYPE(body)->tp_name, name, body);
1417 	  Py_DECREF(repr);
1418 	  return result;
1419      } else if (body->obj.o_name[0])
1420 	  return PyUnicode_FromFormat("<%s \"%s\" at %p>",
1421                                       Py_TYPE(body)->tp_name,
1422                                       body->obj.o_name, body);
1423      else
1424 	  return PyUnicode_FromFormat("<%s at %p>",
1425                                       Py_TYPE(body)->tp_name, body);
1426 }
1427 
1428 static PyMethodDef Body_methods[] = {
1429      {"compute", (PyCFunction) Body_compute, METH_VARARGS | METH_KEYWORDS,
1430       "compute the location of the body for the given date or Observer,"
1431       " or for the current time if no date is supplied"},
1432      {"parallactic_angle", (PyCFunction) Body_parallactic_angle, METH_NOARGS,
1433       "return the parallactic angle to the body; an Observer must have been"
1434       " provided to the most recent compute() call, because a parallactic"
1435       " angle is always measured with respect to a specfic observer"},
1436      {"writedb", (PyCFunction) Body_writedb, METH_NOARGS,
1437       "return a string representation of the body "
1438       "appropriate for inclusion in an ephem database file"},
1439      {"copy", (PyCFunction) Body_copy, METH_NOARGS,
1440       "Return a new copy of this body"},
1441      {"__copy__", (PyCFunction) Body_copy, METH_VARARGS,
1442       "Return a new copy of this body"},
1443      {NULL}
1444 };
1445 
build_hours(double radians)1446 static PyObject* build_hours(double radians)
1447 {
1448      return new_Angle(radians, radhr(1));
1449 }
1450 
build_degrees(double radians)1451 static PyObject* build_degrees(double radians)
1452 {
1453      return new_Angle(radians, raddeg(1));
1454 }
1455 
build_degrees_from_degrees(double degrees)1456 static PyObject* build_degrees_from_degrees(double degrees)
1457 {
1458      return build_degrees(degrees / raddeg(1));
1459 }
1460 
build_mag(double raw)1461 static PyObject* build_mag(double raw)
1462 {
1463      return PyFloat_FromDouble(raw / MAGSCALE);
1464 }
1465 
1466 /* These are the functions which are each responsible for filling in
1467    some of the fields of an ephem.Body or one of its subtypes.  When
1468    called they determine whether the information in the fields for
1469    which they are responsible is up-to-date, and re-compute them if
1470    not.  By checking body->valid they can determine how much
1471    information the last compute() call supplied. */
1472 
Body_obj_cir(Body * body,char * fieldname,unsigned topocentric)1473 static int Body_obj_cir(Body *body, char *fieldname, unsigned topocentric)
1474 {
1475      if (body->obj.o_flags == 0) {
1476 	  PyErr_Format(PyExc_RuntimeError,
1477 		       "field %s undefined until first compute()",
1478 		       fieldname);
1479 	  return -1;
1480      }
1481      if (topocentric && (body->obj.o_flags & VALID_TOPO) == 0) {
1482 	  PyErr_Format(PyExc_RuntimeError,
1483 		       "field %s undefined because the most recent compute() "
1484 		       "was supplied a date rather than an Observer",
1485 		       fieldname);
1486 	  return -1;
1487      }
1488      if (body->obj.o_flags & VALID_OBJ)
1489 	  return 0;
1490      pref_set(PREF_EQUATORIAL, body->obj.o_flags & VALID_TOPO ?
1491 	      PREF_TOPO : PREF_GEO);
1492      if (obj_cir(& body->now, & body->obj) == -1) {
1493 	  PyErr_Format(PyExc_RuntimeError, "cannot compute the body's position"
1494                        " at %s", Date_format_value(body->now.n_mjd));
1495 	  return -1;
1496      }
1497      body->obj.o_flags |= VALID_OBJ;
1498      return 0;
1499 }
1500 
Body_riset_cir(Body * body,char * fieldname)1501 static int Body_riset_cir(Body *body, char *fieldname)
1502 {
1503      static char *warning =
1504           "the ephem.Body attributes 'rise_time', 'rise_az',"
1505           " 'transit_time', 'transit_alt', 'set_time', 'set_az',"
1506           " 'circumpolar', and 'never_up' are deprecated;"
1507           " please convert your program to use the ephem.Observer"
1508           " functions next_rising(), previous_rising(), next_transit(),"
1509           " and so forth\n";
1510      static int warned_already = 0;
1511      if (!warned_already) {
1512           if (PyErr_Warn(PyExc_DeprecationWarning, warning)) return -1;
1513           warned_already = 1;
1514      }
1515      if ((body->obj.o_flags & VALID_RISET) == 0) {
1516 	  if (body->obj.o_flags == 0) {
1517 	       PyErr_Format(PyExc_RuntimeError, "field %s undefined"
1518 			    " until first compute()", fieldname);
1519 	       return -1;
1520 	  }
1521 	  if ((body->obj.o_flags & VALID_TOPO) == 0) {
1522 	       PyErr_Format(PyExc_RuntimeError, "field %s undefined because"
1523 			    " last compute() supplied a date"
1524 			    " rather than an Observer", fieldname);
1525 	       return -1;
1526 	  }
1527 	  riset_cir(& body->now, & body->obj,
1528 		    - body->now.n_dip, & body->riset);
1529 	  body->obj.o_flags |= VALID_RISET;
1530      }
1531      if (body->riset.rs_flags & RS_ERROR) {
1532 	  PyErr_Format(PyExc_RuntimeError, "error computing rise, transit,"
1533 		       " and set circumstances");
1534 	  return -1;
1535      }
1536      return 0;
1537 }
1538 
Moon_llibration(Moon * moon,char * fieldname)1539 static int Moon_llibration(Moon *moon, char *fieldname)
1540 {
1541      if (moon->obj.o_flags & VALID_LIBRATION)
1542 	  return 0;
1543      if (moon->obj.o_flags == 0) {
1544 	  PyErr_Format(PyExc_RuntimeError,
1545 		       "field %s undefined until first compute()", fieldname);
1546 	  return -1;
1547      }
1548      llibration(MJD0 + moon->now.n_mjd, &moon->llat, &moon->llon);
1549      moon->obj.o_flags |= VALID_LIBRATION;
1550      return 0;
1551 }
1552 
Moon_colong(Moon * moon,char * fieldname)1553 static int Moon_colong(Moon *moon, char *fieldname)
1554 {
1555      if (moon->obj.o_flags & VALID_COLONG)
1556 	  return 0;
1557      if (moon->obj.o_flags == 0) {
1558 	  PyErr_Format(PyExc_RuntimeError,
1559 		       "field %s undefined until first compute()", fieldname);
1560 	  return -1;
1561      }
1562      moon_colong(MJD0 + moon->now.n_mjd, 0, 0,
1563 		 &moon->c, &moon->k, 0, &moon->s);
1564      moon->obj.o_flags |= VALID_COLONG;
1565      return 0;
1566 }
1567 
Jupiter_cml(Jupiter * jupiter,char * fieldname)1568 static int Jupiter_cml(Jupiter *jupiter, char *fieldname)
1569 {
1570      if (jupiter->obj.o_flags & VALID_CML)
1571           return 0;
1572      if (jupiter->obj.o_flags == 0) {
1573 	  PyErr_Format(PyExc_RuntimeError,
1574 		       "field %s undefined until first compute()", fieldname);
1575 	  return -1;
1576      }
1577      if (Body_obj_cir((Body*) jupiter, fieldname, 0) == -1) return -1;
1578      meeus_jupiter(jupiter->now.n_mjd, &jupiter->cmlI, &jupiter->cmlII, 0);
1579      jupiter->obj.o_flags |= VALID_CML;
1580      return 0;
1581 }
1582 
Saturn_satrings(Saturn * saturn,char * fieldname)1583 static int Saturn_satrings(Saturn *saturn, char *fieldname)
1584 {
1585      double lsn, rsn, bsn;
1586      if (saturn->obj.o_flags & VALID_RINGS)
1587 	  return 0;
1588      if (saturn->obj.o_flags == 0) {
1589 	  PyErr_Format(PyExc_RuntimeError,
1590 		       "field %s undefined until first compute()", fieldname);
1591 	  return -1;
1592      }
1593      if (Body_obj_cir((Body*) saturn, fieldname, 0) == -1) return -1;
1594      sunpos(saturn->now.n_mjd, &lsn, &rsn, &bsn);
1595      satrings(saturn->obj.s_hlat, saturn->obj.s_hlong, saturn->obj.s_sdist,
1596 	      lsn + PI, rsn, MJD0 + saturn->now.n_mjd,
1597 	      &saturn->etilt, &saturn->stilt);
1598      saturn->obj.o_flags |= VALID_RINGS;
1599      return 0;
1600 }
1601 
1602 /* The Body and Body-subtype fields themselves. */
1603 
1604 #define GET_FIELD(name, field, builder) \
1605   static PyObject *Get_##name (PyObject *self, void *v) \
1606   { \
1607     BODY *body = (BODY*) self; \
1608     if (CALCULATOR(body, #name CARGS) == -1) return 0; \
1609     return builder(body->field); \
1610   }
1611 
1612 /* Attributes computed by obj_cir that need only a date and epoch. */
1613 
1614 #define BODY Body
1615 #define CALCULATOR Body_obj_cir
1616 #define CARGS ,0
1617 
1618 GET_FIELD(epoch, now.n_epoch, build_Date)
1619 GET_FIELD(ha, obj.s_ha, build_hours)
1620 GET_FIELD(ra, obj.s_ra, build_hours)
1621 GET_FIELD(dec, obj.s_dec, build_degrees)
1622 GET_FIELD(gaera, obj.s_gaera, build_hours)
1623 GET_FIELD(gaedec, obj.s_gaedec, build_degrees)
1624 GET_FIELD(astrora, obj.s_astrora, build_hours)
1625 GET_FIELD(astrodec, obj.s_astrodec, build_degrees)
1626 GET_FIELD(elong, obj.s_elong, build_degrees_from_degrees)
1627 GET_FIELD(mag, obj.s_mag, build_mag)
1628 GET_FIELD(size, obj.s_size, PyFloat_FromDouble)
1629 GET_FIELD(radius, obj.s_size * 2*PI / 360. / 60. / 60. / 2., build_degrees)
1630 
1631 GET_FIELD(hlong, obj.s_hlong, build_degrees)
1632 GET_FIELD(hlat, obj.s_hlat, build_degrees)
1633 GET_FIELD(sun_distance, obj.s_sdist, PyFloat_FromDouble)
1634 GET_FIELD(earth_distance, obj.s_edist, PyFloat_FromDouble)
1635 GET_FIELD(phase, obj.s_phase, PyFloat_FromDouble)
1636 
1637 GET_FIELD(x, obj.pl_x, PyFloat_FromDouble)
1638 GET_FIELD(y, obj.pl_y, PyFloat_FromDouble)
1639 GET_FIELD(z, obj.pl_z, PyFloat_FromDouble)
1640 GET_FIELD(earth_visible, obj.pl_evis, PyFloat_FromDouble)
1641 GET_FIELD(sun_visible, obj.pl_svis, PyFloat_FromDouble)
1642 
1643 GET_FIELD(sublat, obj.s_sublat, build_degrees)
1644 GET_FIELD(sublong, obj.s_sublng, build_degrees)
1645 GET_FIELD(elevation, obj.s_elev, PyFloat_FromDouble)
1646 GET_FIELD(eclipsed, obj.s_eclipsed, PyBool_FromLong)
1647 
1648 /* Attributes computed by obj_cir that need an Observer. */
1649 
1650 #undef CARGS
1651 #define CARGS ,1
1652 
1653 GET_FIELD(az, obj.s_az, build_degrees)
1654 GET_FIELD(alt, obj.s_alt, build_degrees)
1655 
1656 GET_FIELD(range, obj.s_range, PyFloat_FromDouble)
1657 GET_FIELD(range_velocity, obj.s_rangev, PyFloat_FromDouble)
1658 
1659 #undef CALCULATOR
1660 #undef CARGS
1661 
1662 /* Attributes computed by riset_cir, which always require an Observer,
1663    and which might be None. */
1664 
1665 #define CALCULATOR Body_riset_cir
1666 #define CARGS
1667 #define FLAGGED(mask, builder) \
1668  (body->riset.rs_flags & (mask | RS_NEVERUP)) \
1669  ? (Py_INCREF(Py_None), Py_None) : builder
1670 
1671 GET_FIELD(rise_time, riset.rs_risetm,
1672 	  FLAGGED(RS_CIRCUMPOLAR | RS_NORISE, build_Date))
1673 GET_FIELD(rise_az, riset.rs_riseaz,
1674 	  FLAGGED(RS_CIRCUMPOLAR | RS_NORISE, build_degrees))
1675 GET_FIELD(transit_time, riset.rs_trantm, FLAGGED(RS_NOTRANS, build_Date))
1676 GET_FIELD(transit_alt, riset.rs_tranalt, FLAGGED(RS_NOTRANS, build_degrees))
1677 GET_FIELD(set_time, riset.rs_settm,
1678 	  FLAGGED(RS_CIRCUMPOLAR | RS_NOSET, build_Date))
1679 GET_FIELD(set_az, riset.rs_setaz,
1680 	  FLAGGED(RS_CIRCUMPOLAR | RS_NOSET, build_degrees))
1681 
1682 #undef CALCULATOR
1683 #undef BODY
1684 
1685 #define BODY Moon
1686 #define CALCULATOR Moon_llibration
1687 
GET_FIELD(libration_lat,llat,build_degrees)1688 GET_FIELD(libration_lat, llat, build_degrees)
1689 GET_FIELD(libration_long, llon, build_degrees)
1690 
1691 #undef CALCULATOR
1692 
1693 #define CALCULATOR Moon_colong
1694 
1695 GET_FIELD(colong, c, build_degrees)
1696 GET_FIELD(moon_phase, k, PyFloat_FromDouble)
1697 GET_FIELD(subsolar_lat, s, build_degrees)
1698 
1699 #undef CALCULATOR
1700 #undef BODY
1701 
1702 #define BODY Jupiter
1703 #define CALCULATOR Jupiter_cml
1704 
1705 GET_FIELD(cmlI, cmlI, build_degrees)
1706 GET_FIELD(cmlII, cmlII, build_degrees)
1707 
1708 #undef CALCULATOR
1709 #undef BODY
1710 
1711 #define BODY Saturn
1712 #define CALCULATOR Saturn_satrings
1713 
1714 GET_FIELD(earth_tilt, etilt, build_degrees)
1715 GET_FIELD(sun_tilt, stilt, build_degrees)
1716 
1717 #undef CALCULATOR
1718 #undef BODY
1719 
1720 /* Attribute access that needs to be hand-written. */
1721 
1722 static PyObject *Get_name(PyObject *self, void *v)
1723 {
1724      Body *body = (Body*) self;
1725      if (body->name) {
1726 	  Py_INCREF(body->name);
1727 	  return body->name;
1728      } else
1729 	  return PyUnicode_FromString(body->obj.o_name);
1730 }
1731 
Set_name(PyObject * self,PyObject * value,void * v)1732 static int Set_name(PyObject *self, PyObject *value, void *v)
1733 {
1734      Body *body = (Body*) self;
1735      const char *name = PyUnicode_AsUTF8(value);
1736      if (!name)
1737           return -1;
1738      strncpy(body->obj.o_name, name, MAXNM);
1739      body->obj.o_name[MAXNM - 1] = '\0';
1740      Py_XDECREF(body->name);
1741      Py_INCREF(value);
1742      body->name = value;
1743      return 0;
1744 }
1745 
Set_mag(PyObject * self,PyObject * value,void * v)1746 static int Set_mag(PyObject *self, PyObject *value, void *v)
1747 {
1748      Body *b = (Body*) self;
1749      double mag;
1750      if (PyNumber_AsDouble(value, &mag) == -1) return -1;
1751      set_fmag(&b->obj, mag);
1752      return 0;
1753 }
1754 
Get_circumpolar(PyObject * self,void * v)1755 static PyObject *Get_circumpolar(PyObject *self, void *v)
1756 {
1757      Body *body = (Body*) self;
1758      if (Body_riset_cir(body, "circumpolar") == -1) return 0;
1759      return PyBool_FromLong(body->riset.rs_flags & RS_CIRCUMPOLAR);
1760 }
1761 
Get_neverup(PyObject * self,void * v)1762 static PyObject *Get_neverup(PyObject *self, void *v)
1763 {
1764      Body *body = (Body*) self;
1765      if (Body_riset_cir(body, "neverup") == -1) return 0;
1766      return PyBool_FromLong(body->riset.rs_flags & RS_NEVERUP);
1767 }
1768 
1769 /* Allow getting and setting of EllipticalBody magnitude model
1770    coefficients, which can be either H/G or gk but which use the same
1771    storage either way. */
1772 
Get_HG(PyObject * self,void * v)1773 static PyObject *Get_HG(PyObject *self, void *v)
1774 {
1775      Body *body = (Body*) self;
1776      if (body->obj.e_mag.whichm != MAG_HG) {
1777 	  PyErr_Format(PyExc_RuntimeError,
1778 		       "this object has g/k magnitude coefficients");
1779 	  return 0;
1780      }
1781      return PyFloat_FromDouble(THE_FLOAT);
1782 }
1783 
Get_gk(PyObject * self,void * v)1784 static PyObject *Get_gk(PyObject *self, void *v)
1785 {
1786      Body *body = (Body*) self;
1787      if (body->obj.e_mag.whichm != MAG_gk) {
1788 	  PyErr_Format(PyExc_RuntimeError,
1789 		       "this object has H/G magnitude coefficients");
1790 	  return 0;
1791      }
1792      return PyFloat_FromDouble(THE_FLOAT);
1793 }
1794 
Set_HG(PyObject * self,PyObject * value,void * v)1795 static int Set_HG(PyObject *self, PyObject *value, void *v)
1796 {
1797      Body *body = (Body*) self;
1798      double n;
1799      if (PyNumber_AsDouble(value, &n) == -1) return -1;
1800      THE_FLOAT = (float) n;
1801      body->obj.e_mag.whichm = MAG_HG;
1802      return 0;
1803 }
1804 
Set_gk(PyObject * self,PyObject * value,void * v)1805 static int Set_gk(PyObject *self, PyObject *value, void *v)
1806 {
1807      Body *body = (Body*) self;
1808      double n;
1809      if (PyNumber_AsDouble(value, &n) == -1) return -1;
1810      THE_FLOAT = (float) n;
1811      body->obj.e_mag.whichm = MAG_gk;
1812      return 0;
1813 }
1814 
1815 /* Get/Set arrays. */
1816 
1817 #define OFF(member) offsetof(Body, obj.member)
1818 
1819 static PyGetSetDef Body_getset[] = {
1820      {"name", Get_name, 0, "object name (read-only string)"},
1821 
1822      {"ha", Get_ha, 0, "hour angle" H},
1823      {"ra", Get_ra, 0, "right ascension" H},
1824      {"dec", Get_dec, 0, "declination" D},
1825      {"g_ra", Get_gaera, 0, "apparent geocentric right ascension" H},
1826      {"g_dec", Get_gaedec, 0, "apparent geocentric declination" D},
1827      {"a_ra", Get_astrora, 0, "astrometric geocentric right ascension" H},
1828      {"a_dec", Get_astrodec, 0, "astrometric geocentric declination" D},
1829      {"a_epoch", Get_epoch, 0,
1830       "date giving the equinox of the body's astrometric right"
1831       " ascension and declination",
1832      },
1833      {"elong", Get_elong, 0, "elongation" D},
1834      {"mag", Get_mag, 0, "magnitude"},
1835      {"size", Get_size, 0, "visual size in arcseconds"},
1836      {"radius", Get_radius, 0, "visual radius" D},
1837 
1838      {"az", Get_az, 0, "azimuth" D},
1839      {"alt", Get_alt, 0, "altitude" D},
1840 
1841      {"rise_time", Get_rise_time, 0, "rise time"},
1842      {"rise_az", Get_rise_az, 0, "azimuth at which the body rises" D},
1843      {"transit_time", Get_transit_time, 0, "transit time"},
1844      {"transit_alt", Get_transit_alt, 0, "transit altitude" D},
1845      {"set_time", Get_set_time, 0, "set time"},
1846      {"set_az", Get_set_az, 0, "azimuth at which the body sets" D},
1847      {"circumpolar", Get_circumpolar, 0,
1848       "whether object remains above the horizon this day"},
1849      {"neverup", Get_neverup, 0,
1850       "whether object never rises above the horizon this day"},
1851      {NULL}
1852 };
1853 
1854 static PyGetSetDef Planet_getset[] = {
1855      {"hlon", Get_hlong, 0,
1856       "heliocentric longitude (but Sun().hlon means the hlon of Earth)" D},
1857      {"hlat", Get_hlat, 0,
1858       "heliocentric latitude (but Sun().hlat means the hlat of Earth)" D},
1859      {"sun_distance", Get_sun_distance, 0, "distance from sun in AU"},
1860      {"earth_distance", Get_earth_distance, 0, "distance from earth in AU"},
1861      {"phase", Get_phase, 0, "phase as percent of the surface illuminated"},
1862 
1863      /* For compatibility with older scripts: */
1864      {"hlong", Get_hlong, 0, "heliocentric longitude" D},
1865 
1866      {NULL}
1867 };
1868 
1869 /* Some day, when planetary moons support all Body and Planet fields,
1870    this will become a list only of fields peculiar to planetary moons,
1871    and PlanetMoon will inherit from Planet; but for now, there is no
1872    inheritance, and this must list every fields valid for a planetary
1873    moon (see libastro's plmoon.c for details). */
1874 
1875 static PyGetSetDef PlanetMoon_getset[] = {
1876      {"name", Get_name, 0, "object name (read-only string)"},
1877 
1878      {"a_ra", Get_astrora, 0, "astrometric geocentric right ascension" H},
1879      {"a_dec", Get_astrodec, 0, "astrometric geocentric declination" D},
1880 
1881      {"g_ra", Get_gaera, 0, "apparent geocentric right ascension" H},
1882      {"g_dec", Get_gaedec, 0, "apparent geocentric declination" D},
1883 
1884      {"ra", Get_ra, 0, "right ascension" H},
1885      {"dec", Get_dec, 0, "declination" D},
1886 
1887      {"az", Get_az, 0, "azimuth" D},
1888      {"alt", Get_alt, 0, "altitude" D},
1889 
1890      {"x", Get_x, 0, "how far east or west of its planet"
1891       " the moon lies in the sky in planet radii; east is positive"},
1892      {"y", Get_y, 0, "how far north or south of its planet"
1893       " the moon lies in the sky in planet radii; south is positive"},
1894      {"z", Get_z, 0, "how much closer or farther from Earth the moon is"
1895       " than its planet"
1896       " in planet radii; closer to Earth is positive"},
1897 
1898      {"earth_visible", Get_earth_visible, 0, "whether visible from earth"},
1899      {"sun_visible", Get_sun_visible, 0, "whether visible from sun"},
1900      {NULL}
1901 };
1902 
1903 static PyGetSetDef Moon_getset[] = {
1904      {"libration_lat", Get_libration_lat, 0, "lunar libration in latitude" D},
1905      {"libration_long", Get_libration_long, 0,
1906       "lunar libration in longitude" D},
1907      {"colong", Get_colong, 0, "lunar selenographic colongitude" D},
1908      {"moon_phase", Get_moon_phase, 0,
1909       "fraction of lunar surface illuminated when viewed from earth"},
1910      {"subsolar_lat", Get_subsolar_lat, 0,
1911       "lunar latitude of subsolar point" D},
1912      {NULL}
1913 };
1914 
1915 static PyGetSetDef Jupiter_getset[] = {
1916      {"cmlI", Get_cmlI, 0, "central meridian longitude, System I" D},
1917      {"cmlII", Get_cmlII, 0, "central meridian longitude, System II" D},
1918      {NULL}
1919 };
1920 
1921 static PyGetSetDef Saturn_getset[] = {
1922      {"earth_tilt", Get_earth_tilt, 0,
1923       "tilt of rings towards Earth, positive for southward tilt," D},
1924      {"sun_tilt", Get_sun_tilt, 0,
1925       "tilt of rings towards Sun, positive for southward tilt," D},
1926      {NULL}
1927 };
1928 
1929 static PyGetSetDef FixedBody_getset[] = {
1930      {"name", Get_name, Set_name, "object name"},
1931      {"mag", Get_mag, Set_mag, "magnitude", 0},
1932      {"_spect",  get_f_spect, set_f_spect, "spectral codes", 0},
1933      {"_ratio", get_f_ratio, set_f_ratio,
1934       "ratio of minor to major diameter", VOFF(f_ratio)},
1935      {"_pa", get_f_pa, set_f_pa, "position angle E of N", VOFF(f_pa)},
1936      {"_epoch", getd_mjd, setd_mjd, "epoch for _ra and _dec", VOFF(f_epoch)},
1937      {"_ra", getd_rh, setd_rh, "fixed right ascension", VOFF(f_RA)},
1938      {"_dec", getd_rd, setd_rd, "fixed declination", VOFF(f_dec)},
1939      {"_pmra", getf_proper_ra, setf_proper_ra,
1940       "right ascension proper motion", 0},
1941      {"_pmdec", getf_proper_dec, setf_proper_dec,
1942       "declination proper motion", 0},
1943      {NULL}
1944 };
1945 
1946 static PyMemberDef FixedBody_members[] = {
1947      {"_class", T_CHAR, OFF(f_class), 0, "object classification"},
1948      {NULL}
1949 };
1950 
1951 static PyGetSetDef EllipticalBody_getset[] = {
1952      {"name", Get_name, Set_name, "object name"},
1953      {"_inc", getf_dd, setf_dd, "inclination (degrees)", VOFF(e_inc)},
1954      {"_Om", getf_dd, setf_dd,
1955       "longitude of ascending node (degrees)", VOFF(e_Om)},
1956      {"_om", getf_dd, setf_dd, "argument of perihelion (degrees)", VOFF(e_om)},
1957      {"_M", getf_dd, setf_dd, "mean anomaly (degrees)", VOFF(e_M)},
1958      {"_epoch_M", getd_mjd, setd_mjd, "epoch of mean anomaly",
1959       VOFF(e_cepoch)},
1960      {"_epoch", getd_mjd, setd_mjd, "epoch for _inc, _Om, and _om",
1961       VOFF(e_epoch)},
1962      {"_H", Get_HG, Set_HG, "magnitude coefficient", VOFF(e_mag.m1)},
1963      {"_G", Get_HG, Set_HG, "magnitude coefficient", VOFF(e_mag.m2)},
1964      {"_g", Get_gk, Set_gk, "magnitude coefficient", VOFF(e_mag.m1)},
1965      {"_k", Get_gk, Set_gk, "magnitude coefficient", VOFF(e_mag.m2)},
1966      {NULL}
1967 };
1968 
1969 static PyMemberDef EllipticalBody_members[] = {
1970      {"_a", T_FLOAT, OFF(e_a), 0, "mean distance (AU)"},
1971      {"_size", T_FLOAT, OFF(e_size), 0, "angular size at 1 AU (arcseconds)"},
1972      {"_e", T_DOUBLE, OFF(e_e), 0, "eccentricity"},
1973      {NULL}
1974 };
1975 
1976 static PyGetSetDef HyperbolicBody_getset[] = {
1977      {"name", Get_name, Set_name, "object name"},
1978      {"_epoch", getd_mjd, setd_mjd, "epoch date of _inc, _Om, and _om",
1979       VOFF(h_epoch)},
1980      {"_epoch_p", getd_mjd, setd_mjd, "epoch of perihelion", VOFF(h_ep)},
1981      {"_inc", getf_dd, setf_dd, "inclination (degrees)", VOFF(h_inc)},
1982      {"_Om", getf_dd, setf_dd,
1983       "longitude of ascending node (degrees)", VOFF(h_Om)},
1984      {"_om", getf_dd, setf_dd,
1985       "argument of perihelion (degrees)", VOFF(h_om)},
1986      {NULL}
1987 };
1988 
1989 static PyMemberDef HyperbolicBody_members[] = {
1990      {"_e", T_FLOAT, OFF(h_e), 0, "eccentricity"},
1991      {"_q", T_FLOAT, OFF(h_qp), 0, "perihelion distance (AU)"},
1992      {"_g", T_FLOAT, OFF(h_g), 0, "magnitude coefficient"},
1993      {"_k", T_FLOAT, OFF(h_k), 0, "magnitude coefficient"},
1994      {"_size", T_FLOAT, OFF(h_size), 0, "angular size at 1 AU (arcseconds)"},
1995      {NULL}
1996 };
1997 
1998 static PyGetSetDef ParabolicBody_getset[] = {
1999      {"name", Get_name, Set_name, "object name"},
2000      {"_epoch", getd_mjd, setd_mjd, "reference epoch", VOFF(p_epoch)},
2001      {"_epoch_p", getd_mjd, setd_mjd, "epoch of perihelion", VOFF(p_ep)},
2002      {"_inc", getf_dd, setf_dd, "inclination (degrees)", VOFF(p_inc)},
2003      {"_om", getf_dd, setf_dd, "argument of perihelion (degrees)", VOFF(p_om)},
2004      {"_Om", getf_dd, setf_dd,
2005       "longitude of ascending node (degrees)", VOFF(p_Om)},
2006      {NULL}
2007 };
2008 
2009 static PyMemberDef ParabolicBody_members[] = {
2010      {"_q", T_FLOAT, OFF(p_qp), 0, "perihelion distance (AU)"},
2011      {"_g", T_FLOAT, OFF(p_g), 0, "magnitude coefficient"},
2012      {"_k", T_FLOAT, OFF(p_k), 0, "magnitude coefficient"},
2013      {"_size", T_FLOAT, OFF(p_size), 0, "angular size at 1 AU (arcseconds)"},
2014      {NULL}
2015 };
2016 
2017 static PyGetSetDef EarthSatellite_getset[] = {
2018      {"name", Get_name, Set_name, "object name"},
2019      {"epoch", getd_mjd, setd_mjd, "reference epoch (mjd)", VOFF(es_epoch)},
2020 
2021      /* backwards compatibility */
2022 
2023      {"_epoch", getd_mjd, setd_mjd, "reference epoch (mjd)", VOFF(es_epoch)},
2024      {"_inc", getf_dd, setf_dd, "inclination (degrees)", VOFF(es_inc)},
2025      {"_raan", getf_dd, setf_dd,
2026       "right ascension of ascending node (degrees)", VOFF(es_raan)},
2027      {"_ap", getf_dd, setf_dd,
2028       "argument of perigee at epoch (degrees)", VOFF(es_ap)},
2029      {"_M", getf_dd, setf_dd,
2030       "mean anomaly (degrees from perigee at epoch)", VOFF(es_M)},
2031 
2032      /* results */
2033 
2034      {"sublat", Get_sublat, 0, "latitude beneath satellite" D},
2035      {"sublong", Get_sublong, 0, "longitude beneath satellite" D},
2036      {"elevation", Get_elevation, 0, "height above sea level in meters"},
2037      {"range", Get_range, 0, "distance from observer to satellite in meters"},
2038      {"range_velocity", Get_range_velocity, 0,
2039       "range rate of change in meters per second"},
2040      {"eclipsed", Get_eclipsed, 0, "whether satellite is in earth's shadow"},
2041      {NULL}
2042 };
2043 
2044 static PyMemberDef EarthSatellite_members[] = {
2045      {"n", T_DOUBLE, OFF(es_n), 0,
2046       "mean motion (revolutions per day)"},
2047      {"inc", T_FLOAT, OFF(es_inc), 0,
2048       "orbit inclination (degrees)"},
2049      {"raan", T_FLOAT, OFF(es_raan), 0,
2050       "right ascension of ascending node (degrees)"},
2051      {"e", T_FLOAT, OFF(es_e), 0,
2052       "eccentricity"},
2053      {"ap", T_FLOAT, OFF(es_ap), 0,
2054       "argument of perigee at epoch (degrees)"},
2055      {"M", T_FLOAT, OFF(es_M), 0,
2056       "mean anomaly (degrees from perigee at epoch)"},
2057      {"decay", T_FLOAT, OFF(es_decay), 0,
2058       "orbit decay rate (revolutions per day-squared)"},
2059      {"drag", T_FLOAT, OFF(es_drag), 0,
2060       "object drag coefficient (per earth radius)"},
2061      {"orbit", T_INT, OFF(es_orbit), 0,
2062       "integer orbit number of epoch"},
2063 
2064      /* backwards compatibility */
2065 
2066      {"_n", T_DOUBLE, OFF(es_n), 0, "mean motion (revolutions per day)"},
2067      {"_e", T_FLOAT, OFF(es_e), 0, "eccentricity"},
2068      {"_decay", T_FLOAT, OFF(es_decay), 0,
2069       "orbit decay rate (revolutions per day-squared)"},
2070      {"_drag", T_FLOAT, OFF(es_drag), 0,
2071       "object drag coefficient (per earth radius)"},
2072      {"_orbit", T_INT, OFF(es_orbit), 0, "integer orbit number of epoch"},
2073      {"catalog_number", T_OBJECT, offsetof(EarthSatellite, catalog_number), 0,
2074       "catalog number from TLE file"},
2075      {NULL}
2076 };
2077 
2078 #undef OFF
2079 
2080 /*
2081  * The Object corresponds to the `any' object fields of X,
2082  * and implements most of the computational methods.  While Fixed
2083  * inherits directly from an Object, all other object types
2084  * inherit from PlanetObject to make the additional position
2085  * fields available for inspection.
2086  *
2087  * Note that each subclass is exactly the size of its parent, which is
2088  * rather unusual but reflects the fact that all of these are just
2089  * various wrappers around the same X Obj structure.
2090  */
2091 static char body_doc[] =
2092      "A celestial body, that can compute() its sky position";
2093 
2094 static char earth_satellite_doc[] = "\
2095 A satellite in orbit around the Earth, usually built by passing the\
2096  text of a TLE entry to the `ephem.readtle()` routine. You can read\
2097  and write its orbital parameters through the following attributes:\n\
2098 \n\
2099 _ap -- argument of perigee at epoch (degrees)\n\
2100 _decay -- orbit decay rate (revolutions per day-squared)\n\
2101 _drag -- object drag coefficient (per earth radius)\n\
2102 _e -- eccentricity\n\
2103 _epoch -- reference epoch (mjd)\n\
2104 _inc -- inclination (degrees)\n\
2105 _M -- mean anomaly (degrees from perigee at epoch)\n\
2106 _n -- mean motion (revolutions per day)\n\
2107 _orbit -- integer orbit number of epoch\n\
2108 _raan -- right ascension of ascending node (degrees)\n\
2109 ";
2110 
2111 static PyTypeObject BodyType = {
2112      PyVarObject_HEAD_INIT(NULL, 0)
2113      "ephem.Body",
2114      sizeof(Body),
2115      0,
2116      Body_dealloc,		/* tp_dealloc */
2117      0,				/* tp_print */
2118      0,				/* tp_getattr */
2119      0,				/* tp_setattr */
2120      0,				/* tp_compare */
2121      Body_repr,			/* tp_repr */
2122      0,				/* tp_as_number */
2123      0,				/* tp_as_sequence */
2124      0,				/* tp_as_mapping */
2125      0,				/* tp_hash */
2126      0,				/* tp_call */
2127      0,				/* tp_str */
2128      0,				/* tp_getattro */
2129      0,				/* tp_setattro */
2130      0,				/* tp_as_buffer */
2131      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2132      body_doc,			/* tp_doc */
2133      0,				/* tp_traverse */
2134      0,				/* tp_clear */
2135      0,				/* tp_richcompare */
2136      0,				/* tp_weaklistoffset */
2137      0,				/* tp_iter */
2138      0,				/* tp_iternext */
2139      Body_methods,		/* tp_methods */
2140      0,				/* tp_members */
2141      Body_getset,		/* tp_getset */
2142      0,				/* tp_base */
2143      0,				/* tp_dict */
2144      0,				/* tp_descr_get */
2145      0,				/* tp_descr_set */
2146      0,				/* tp_dictoffset */
2147      Body_init,			/* tp_init */
2148      0,				/* tp_alloc */
2149      0,				/* tp_new */
2150      0,				/* tp_free */
2151 };
2152 
2153 static PyTypeObject PlanetType = {
2154      PyVarObject_HEAD_INIT(NULL, 0)
2155      "ephem.Planet",
2156      sizeof(Planet),
2157      0,
2158      0,				/* tp_dealloc */
2159      0,				/* tp_print */
2160      0,				/* tp_getattr */
2161      0,				/* tp_setattr */
2162      0,				/* tp_compare */
2163      0,				/* tp_repr */
2164      0,				/* tp_as_number */
2165      0,				/* tp_as_sequence */
2166      0,				/* tp_as_mapping */
2167      0,				/* tp_hash */
2168      0,				/* tp_call */
2169      0,				/* tp_str */
2170      0,				/* tp_getattro */
2171      0,				/* tp_setattro */
2172      0,				/* tp_as_buffer */
2173      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2174      body_doc,			/* tp_doc */
2175      0,				/* tp_traverse */
2176      0,				/* tp_clear */
2177      0,				/* tp_richcompare */
2178      0,				/* tp_weaklistoffset */
2179      0,				/* tp_iter */
2180      0,				/* tp_iternext */
2181      0,				/* tp_methods */
2182      0,				/* tp_members */
2183      Planet_getset,		/* tp_getset */
2184      &BodyType,			/* tp_base */
2185      0,				/* tp_dict */
2186      0,				/* tp_descr_get */
2187      0,				/* tp_descr_set */
2188      0,				/* tp_dictoffset */
2189      Planet_init,		/* tp_init */
2190      0,				/* tp_alloc */
2191      0,				/* tp_new */
2192      0,				/* tp_free */
2193 };
2194 
2195 static PyTypeObject PlanetMoonType = {
2196      PyVarObject_HEAD_INIT(NULL, 0)
2197      "ephem.PlanetMoon",
2198      sizeof(PlanetMoon),
2199      0,
2200      Body_dealloc,		/* tp_dealloc */
2201      0,				/* tp_print */
2202      0,				/* tp_getattr */
2203      0,				/* tp_setattr */
2204      0,				/* tp_compare */
2205      Body_repr,			/* tp_repr */
2206      0,				/* tp_as_number */
2207      0,				/* tp_as_sequence */
2208      0,				/* tp_as_mapping */
2209      0,				/* tp_hash */
2210      0,				/* tp_call */
2211      0,				/* tp_str */
2212      0,				/* tp_getattro */
2213      0,				/* tp_setattro */
2214      0,				/* tp_as_buffer */
2215      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2216      body_doc,			/* tp_doc */
2217      0,				/* tp_traverse */
2218      0,				/* tp_clear */
2219      0,				/* tp_richcompare */
2220      0,				/* tp_weaklistoffset */
2221      0,				/* tp_iter */
2222      0,				/* tp_iternext */
2223      Body_methods,		/* tp_methods */
2224      0,				/* tp_members */
2225      PlanetMoon_getset,		/* tp_getset */
2226      0,				/* tp_base */
2227      0,				/* tp_dict */
2228      0,				/* tp_descr_get */
2229      0,				/* tp_descr_set */
2230      0,				/* tp_dictoffset */
2231      Planet_init,               /* tp_init */
2232      0,				/* tp_alloc */
2233      0,				/* tp_new */
2234      0,				/* tp_free */
2235 };
2236 
2237 static PyTypeObject JupiterType = {
2238      PyVarObject_HEAD_INIT(NULL, 0)
2239      "ephem.Jupiter",
2240      sizeof(Jupiter),
2241      0,
2242      0,				/* tp_dealloc */
2243      0,				/* tp_print */
2244      0,				/* tp_getattr */
2245      0,				/* tp_setattr */
2246      0,				/* tp_compare */
2247      0,				/* tp_repr */
2248      0,				/* tp_as_number */
2249      0,				/* tp_as_sequence */
2250      0,				/* tp_as_mapping */
2251      0,				/* tp_hash */
2252      0,				/* tp_call */
2253      0,				/* tp_str */
2254      0,				/* tp_getattro */
2255      0,				/* tp_setattro */
2256      0,				/* tp_as_buffer */
2257      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2258      "Create a Body instance representing Jupiter.",			/* tp_doc */
2259      0,				/* tp_traverse */
2260      0,				/* tp_clear */
2261      0,				/* tp_richcompare */
2262      0,				/* tp_weaklistoffset */
2263      0,				/* tp_iter */
2264      0,				/* tp_iternext */
2265      0,				/* tp_methods */
2266      0,				/* tp_members */
2267      Jupiter_getset,		/* tp_getset */
2268      &PlanetType,		/* tp_base */
2269      0,				/* tp_dict */
2270      0,				/* tp_descr_get */
2271      0,				/* tp_descr_set */
2272      0,				/* tp_dictoffset */
2273      Jupiter_init,		/* tp_init */
2274      0,				/* tp_alloc */
2275      0,				/* tp_new */
2276      0,				/* tp_free */
2277 };
2278 
2279 static PyTypeObject SaturnType = {
2280      PyVarObject_HEAD_INIT(NULL, 0)
2281      "ephem.Saturn",
2282      sizeof(Saturn),
2283      0,
2284      0,				/* tp_dealloc */
2285      0,				/* tp_print */
2286      0,				/* tp_getattr */
2287      0,				/* tp_setattr */
2288      0,				/* tp_compare */
2289      0,				/* tp_repr */
2290      0,				/* tp_as_number */
2291      0,				/* tp_as_sequence */
2292      0,				/* tp_as_mapping */
2293      0,				/* tp_hash */
2294      0,				/* tp_call */
2295      0,				/* tp_str */
2296      0,				/* tp_getattro */
2297      0,				/* tp_setattro */
2298      0,				/* tp_as_buffer */
2299      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2300      "Create a Body instance representing Saturn.",			/* tp_doc */
2301      0,				/* tp_traverse */
2302      0,				/* tp_clear */
2303      0,				/* tp_richcompare */
2304      0,				/* tp_weaklistoffset */
2305      0,				/* tp_iter */
2306      0,				/* tp_iternext */
2307      0,				/* tp_methods */
2308      0,				/* tp_members */
2309      Saturn_getset,		/* tp_getset */
2310      &PlanetType,		/* tp_base */
2311      0,				/* tp_dict */
2312      0,				/* tp_descr_get */
2313      0,				/* tp_descr_set */
2314      0,				/* tp_dictoffset */
2315      Saturn_init,		/* tp_init */
2316      0,				/* tp_alloc */
2317      0,				/* tp_new */
2318      0,				/* tp_free */
2319 };
2320 
2321 static PyTypeObject MoonType = {
2322      PyVarObject_HEAD_INIT(NULL, 0)
2323      "ephem.Moon",
2324      sizeof(Moon),
2325      0,
2326      0,				/* tp_dealloc */
2327      0,				/* tp_print */
2328      0,				/* tp_getattr */
2329      0,				/* tp_setattr */
2330      0,				/* tp_compare */
2331      0,				/* tp_repr */
2332      0,				/* tp_as_number */
2333      0,				/* tp_as_sequence */
2334      0,				/* tp_as_mapping */
2335      0,				/* tp_hash */
2336      0,				/* tp_call */
2337      0,				/* tp_str */
2338      0,				/* tp_getattro */
2339      0,				/* tp_setattro */
2340      0,				/* tp_as_buffer */
2341      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2342      "Create a Body Instance representing the Moon.",			/* tp_doc */
2343      0,				/* tp_traverse */
2344      0,				/* tp_clear */
2345      0,				/* tp_richcompare */
2346      0,				/* tp_weaklistoffset */
2347      0,				/* tp_iter */
2348      0,				/* tp_iternext */
2349      0,				/* tp_methods */
2350      0,				/* tp_members */
2351      Moon_getset,		/* tp_getset */
2352      &PlanetType,		/* tp_base */
2353      0,				/* tp_dict */
2354      0,				/* tp_descr_get */
2355      0,				/* tp_descr_set */
2356      0,				/* tp_dictoffset */
2357      Moon_init,			/* tp_init */
2358      0,				/* tp_alloc */
2359      0,				/* tp_new */
2360      0,				/* tp_free */
2361 };
2362 
2363 static PyTypeObject FixedBodyType = {
2364      PyVarObject_HEAD_INIT(NULL, 0)
2365      "ephem.FixedBody",
2366      sizeof(FixedBody),
2367      0,
2368      0,				/* tp_dealloc */
2369      0,				/* tp_print */
2370      0,				/* tp_getattr */
2371      0,				/* tp_setattr */
2372      0,				/* tp_compare */
2373      0,				/* tp_repr */
2374      0,				/* tp_as_number */
2375      0,				/* tp_as_sequence */
2376      0,				/* tp_as_mapping */
2377      0,				/* tp_hash */
2378      0,				/* tp_call */
2379      0,				/* tp_str */
2380      0,				/* tp_getattro */
2381      0,				/* tp_setattro */
2382      0,				/* tp_as_buffer */
2383      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2384      body_doc,			/* tp_doc */
2385      0,				/* tp_traverse */
2386      0,				/* tp_clear */
2387      0,				/* tp_richcompare */
2388      0,				/* tp_weaklistoffset */
2389      0,				/* tp_iter */
2390      0,				/* tp_iternext */
2391      0,				/* tp_methods */
2392      FixedBody_members,		/* tp_members */
2393      FixedBody_getset,		/* tp_getset */
2394      &BodyType,			/* tp_base */
2395      0,				/* tp_dict */
2396      0,				/* tp_descr_get */
2397      0,				/* tp_descr_set */
2398      0,				/* tp_dictoffset */
2399      FixedBody_init,		/* tp_init */
2400      0,				/* tp_alloc */
2401      0,				/* tp_new */
2402      0,				/* tp_free */
2403 };
2404 
2405 static PyTypeObject BinaryStarType = {
2406      PyVarObject_HEAD_INIT(NULL, 0)
2407      "ephem.BinaryStar",
2408      sizeof(BinaryStar),
2409      0,
2410      0,				/* tp_dealloc */
2411      0,				/* tp_print */
2412      0,				/* tp_getattr */
2413      0,				/* tp_setattr */
2414      0,				/* tp_compare */
2415      0,				/* tp_repr */
2416      0,				/* tp_as_number */
2417      0,				/* tp_as_sequence */
2418      0,				/* tp_as_mapping */
2419      0,				/* tp_hash */
2420      0,				/* tp_call */
2421      0,				/* tp_str */
2422      0,				/* tp_getattro */
2423      0,				/* tp_setattro */
2424      0,				/* tp_as_buffer */
2425      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2426      body_doc,			/* tp_doc */
2427      0,				/* tp_traverse */
2428      0,				/* tp_clear */
2429      0,				/* tp_richcompare */
2430      0,				/* tp_weaklistoffset */
2431      0,				/* tp_iter */
2432      0,				/* tp_iternext */
2433      0,				/* tp_methods */
2434      0,				/* tp_members */
2435      0,				/* tp_getset */
2436      &PlanetType,		/* tp_base */
2437      0,				/* tp_dict */
2438      0,				/* tp_descr_get */
2439      0,				/* tp_descr_set */
2440      0,				/* tp_dictoffset */
2441      BinaryStar_init,		/* tp_init */
2442      0,				/* tp_alloc */
2443      0,				/* tp_new */
2444      0,				/* tp_free */
2445 };
2446 
2447 static PyTypeObject EllipticalBodyType = {
2448      PyVarObject_HEAD_INIT(NULL, 0)
2449      "ephem.EllipticalBody",
2450      sizeof(EllipticalBody),
2451      0,
2452      0,				/* tp_dealloc */
2453      0,				/* tp_print */
2454      0,				/* tp_getattr */
2455      0,				/* tp_setattr */
2456      0,				/* tp_compare */
2457      0,				/* tp_repr */
2458      0,				/* tp_as_number */
2459      0,				/* tp_as_sequence */
2460      0,				/* tp_as_mapping */
2461      0,				/* tp_hash */
2462      0,				/* tp_call */
2463      0,				/* tp_str */
2464      0,				/* tp_getattro */
2465      0,				/* tp_setattro */
2466      0,				/* tp_as_buffer */
2467      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2468      body_doc,			/* tp_doc */
2469      0,				/* tp_traverse */
2470      0,				/* tp_clear */
2471      0,				/* tp_richcompare */
2472      0,				/* tp_weaklistoffset */
2473      0,				/* tp_iter */
2474      0,				/* tp_iternext */
2475      0,				/* tp_methods */
2476      EllipticalBody_members,	/* tp_members */
2477      EllipticalBody_getset,	/* tp_getset */
2478      &PlanetType,		/* tp_base */
2479      0,				/* tp_dict */
2480      0,				/* tp_descr_get */
2481      0,				/* tp_descr_set */
2482      0,				/* tp_dictoffset */
2483      EllipticalBody_init,	/* tp_init */
2484      0,				/* tp_alloc */
2485      0,				/* tp_new */
2486      0,				/* tp_free */
2487 };
2488 
2489 static PyTypeObject HyperbolicBodyType = {
2490      PyVarObject_HEAD_INIT(NULL, 0)
2491      "ephem.HyperbolicBody",
2492      sizeof(HyperbolicBody),
2493      0,
2494      0,				/* tp_dealloc */
2495      0,				/* tp_print */
2496      0,				/* tp_getattr */
2497      0,				/* tp_setattr */
2498      0,				/* tp_compare */
2499      0,				/* tp_repr */
2500      0,				/* tp_as_number */
2501      0,				/* tp_as_sequence */
2502      0,				/* tp_as_mapping */
2503      0,				/* tp_hash */
2504      0,				/* tp_call */
2505      0,				/* tp_str */
2506      0,				/* tp_getattro */
2507      0,				/* tp_setattro */
2508      0,				/* tp_as_buffer */
2509      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2510      body_doc,			/* tp_doc */
2511      0,				/* tp_traverse */
2512      0,				/* tp_clear */
2513      0,				/* tp_richcompare */
2514      0,				/* tp_weaklistoffset */
2515      0,				/* tp_iter */
2516      0,				/* tp_iternext */
2517      0,				/* tp_methods */
2518      HyperbolicBody_members,	/* tp_members */
2519      HyperbolicBody_getset,	/* tp_getset */
2520      &PlanetType,		/* tp_base */
2521      0,				/* tp_dict */
2522      0,				/* tp_descr_get */
2523      0,				/* tp_descr_set */
2524      0,				/* tp_dictoffset */
2525      HyperbolicBody_init,	/* tp_init */
2526      0,				/* tp_alloc */
2527      0,				/* tp_new */
2528      0,				/* tp_free */
2529 };
2530 
2531 static PyTypeObject ParabolicBodyType = {
2532      PyVarObject_HEAD_INIT(NULL, 0)
2533      "ephem.ParabolicBody",
2534      sizeof(ParabolicBody),
2535      0,
2536      0,				/* tp_dealloc */
2537      0,				/* tp_print */
2538      0,				/* tp_getattr */
2539      0,				/* tp_setattr */
2540      0,				/* tp_compare */
2541      0,				/* tp_repr */
2542      0,				/* tp_as_number */
2543      0,				/* tp_as_sequence */
2544      0,				/* tp_as_mapping */
2545      0,				/* tp_hash */
2546      0,				/* tp_call */
2547      0,				/* tp_str */
2548      0,				/* tp_getattro */
2549      0,				/* tp_setattro */
2550      0,				/* tp_as_buffer */
2551      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
2552      body_doc,			/* tp_doc */
2553      0,				/* tp_traverse */
2554      0,				/* tp_clear */
2555      0,				/* tp_richcompare */
2556      0,				/* tp_weaklistoffset */
2557      0,				/* tp_iter */
2558      0,				/* tp_iternext */
2559      0,				/* tp_methods */
2560      ParabolicBody_members,	/* tp_members */
2561      ParabolicBody_getset,	/* tp_getset */
2562      &PlanetType,		/* tp_base */
2563      0,				/* tp_dict */
2564      0,				/* tp_descr_get */
2565      0,				/* tp_descr_set */
2566      0,				/* tp_dictoffset */
2567      ParabolicBody_init,	/* tp_init */
2568      0,				/* tp_alloc */
2569      0,				/* tp_new */
2570      0,				/* tp_free */
2571 };
2572 
2573 static PyTypeObject EarthSatelliteType = {
2574      PyVarObject_HEAD_INIT(NULL, 0)
2575      "ephem.EarthSatellite",
2576      sizeof(EarthSatellite),
2577      0,
2578      0,				/* tp_dealloc */
2579      0,				/* tp_print */
2580      0,				/* tp_getattr */
2581      0,				/* tp_setattr */
2582      0,				/* tp_compare */
2583      0,				/* tp_repr */
2584      0,				/* tp_as_number */
2585      0,				/* tp_as_sequence */
2586      0,				/* tp_as_mapping */
2587      0,				/* tp_hash */
2588      0,				/* tp_call */
2589      0,				/* tp_str */
2590      0,				/* tp_getattro */
2591      0,				/* tp_setattro */
2592      0,				/* tp_as_buffer */
2593      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/* tp_flags */
2594      earth_satellite_doc,	/* tp_doc */
2595      0,				/* tp_traverse */
2596      0,				/* tp_clear */
2597      0,				/* tp_richcompare */
2598      0,				/* tp_weaklistoffset */
2599      0,				/* tp_iter */
2600      0,				/* tp_iternext */
2601      0,				/* tp_methods */
2602      EarthSatellite_members,	/* tp_members */
2603      EarthSatellite_getset,	/* tp_getset */
2604      &BodyType,			/* tp_base */
2605      0,				/* tp_dict */
2606      0,				/* tp_descr_get */
2607      0,				/* tp_descr_set */
2608      0,				/* tp_dictoffset */
2609      EarthSatellite_init,	/* tp_init */
2610      0,				/* tp_alloc */
2611      0,				/* tp_new */
2612      0,				/* tp_free */
2613 };
2614 
2615 /* Return the current time. */
2616 
build_now(PyObject * self)2617 static PyObject* build_now(PyObject *self)
2618 {
2619      return build_Date(mjd_now());
2620 }
2621 
2622 /* Compute the separation between two objects. */
2623 
separation_arg(PyObject * arg,double * lngi,double * lati)2624 static int separation_arg(PyObject *arg, double *lngi, double *lati)
2625 {
2626      char err_message[] = "each separation argument "
2627 	  "must be an Observer, an Body, "
2628 	  "or a pair of numeric coordinates";
2629      if (PyObject_IsInstance(arg, (PyObject*) &ObserverType)) {
2630 	  Observer *o = (Observer*) arg;
2631 	  *lngi = o->now.n_lng;
2632 	  *lati = o->now.n_lat;
2633 	  return 0;
2634      } else if (PyObject_IsInstance(arg, (PyObject*) &BodyType)) {
2635 	  Body *b = (Body*) arg;
2636 	  if (Body_obj_cir(b, "ra", 0)) return -1;
2637 	  *lngi = b->obj.s_ra;
2638 	  *lati = b->obj.s_dec;
2639 	  return 0;
2640      } else if (PySequence_Check(arg) && PySequence_Size(arg) == 2) {
2641           int rval = -1;
2642 	  PyObject *lngo = 0, *lato = 0, *lngf = 0, *latf = 0;
2643 	  lngo = PySequence_GetItem(arg, 0);
2644 	  if (!lngo) goto fail;
2645 	  lato = PySequence_GetItem(arg, 1);
2646 	  if (!lato) goto fail;
2647 	  if (!PyNumber_Check(lngo) || !PyNumber_Check(lato)) {
2648                PyErr_SetString(PyExc_TypeError, err_message);
2649                goto fail;
2650           }
2651 	  lngf = PyNumber_Float(lngo);
2652 	  if (!lngf) goto fail;
2653 	  latf = PyNumber_Float(lato);
2654 	  if (!latf) goto fail;
2655 	  *lngi = PyFloat_AsDouble(lngf);
2656 	  *lati = PyFloat_AsDouble(latf);
2657           rval = 0;
2658      fail:
2659           Py_XDECREF(lngo);
2660           Py_XDECREF(lato);
2661 	  Py_XDECREF(lngf);
2662 	  Py_XDECREF(latf);
2663           return rval;
2664      } else {
2665           PyErr_SetString(PyExc_TypeError, err_message);
2666           return -1;
2667      }
2668 }
2669 
py_unrefract(PyObject * self,PyObject * args)2670 static PyObject* py_unrefract(PyObject *self, PyObject *args)
2671 {
2672      double pr, tr, aa, ta;
2673      if (!PyArg_ParseTuple(args, "ddd:py_unrefract", &pr, &tr, &aa))
2674           return 0;
2675      unrefract(pr, tr, aa, &ta);
2676      return new_Angle(ta, raddeg(1));
2677 }
2678 
separation(PyObject * self,PyObject * args)2679 static PyObject* separation(PyObject *self, PyObject *args)
2680 {
2681      double plat, plng, qlat, qlng;
2682      double spy, cpy, px, qx, sqy, cqy, cosine;
2683      PyObject *p, *q;
2684      if (!PyArg_ParseTuple(args, "OO:separation", &p, &q)) return 0;
2685      if (separation_arg(p, &plng, &plat)) return 0;
2686      if (separation_arg(q, &qlng, &qlat)) return 0;
2687 
2688      /* rounding errors in the trigonometry might return >0 for this case */
2689      if ((plat == qlat) && (plng == qlng))
2690           return new_Angle(0.0, raddeg(1));
2691 
2692      spy = sin (plat);
2693      cpy = cos (plat);
2694      px = plng;
2695      qx = qlng;
2696      sqy = sin (qlat);
2697      cqy = cos (qlat);
2698 
2699      cosine = spy*sqy + cpy*cqy*cos(px-qx);
2700      if (cosine >= 1.0)  /* rounding sometimes makes this >1 */
2701           return new_Angle(0.0, raddeg(1));
2702 
2703      return new_Angle(acos(cosine), raddeg(1));
2704 }
2705 
2706 /* Read various database formats, with strings as input. */
2707 
build_body_from_obj(PyObject * name,Obj * op)2708 static PyObject *build_body_from_obj(PyObject *name, Obj *op)
2709 {
2710      PyTypeObject *type;
2711      Body *body;
2712      switch (op->o_type) {
2713      case FIXED:
2714 	  type = &FixedBodyType;
2715 	  break;
2716      case ELLIPTICAL:
2717 	  type = &EllipticalBodyType;
2718 	  break;
2719      case HYPERBOLIC:
2720 	  type = &HyperbolicBodyType;
2721 	  break;
2722      case PARABOLIC:
2723 	  type = &ParabolicBodyType;
2724 	  break;
2725      case EARTHSAT:
2726 	  type = &EarthSatelliteType;
2727 	  break;
2728      default:
2729 	  PyErr_Format(PyExc_ValueError,
2730 		       "cannot build object of unexpected type %d",
2731 		       op->o_type);
2732 	  Py_DECREF(name);
2733 	  return 0;
2734      }
2735      body = (Body*) PyType_GenericNew(type, 0, 0);
2736      if (!body) {
2737 	  Py_DECREF(name);
2738 	  return 0;
2739      }
2740      body->obj = *op;
2741      if (Set_name((PyObject*) body, name, 0) == -1) {
2742           Py_DECREF(body);
2743           Py_DECREF(name);
2744           return 0;
2745      }
2746      Py_DECREF(name);
2747      return (PyObject*) body;
2748 }
2749 
readdb(PyObject * self,PyObject * args)2750 static PyObject* readdb(PyObject *self, PyObject *args)
2751 {
2752      char *line, *comma, errmsg[256];
2753      PyObject *name;
2754      Obj obj;
2755      if (!PyArg_ParseTuple(args, "s:readdb", &line)) return 0;
2756      if (db_crack_line(line, &obj, 0, 0, errmsg) == -1) {
2757 	  PyErr_SetString(PyExc_ValueError,
2758 			  errmsg[0] ? errmsg :
2759 			  "line does not conform to ephem database format");
2760 	  return 0;
2761      }
2762      comma = strchr(line, ',');
2763      if (comma)
2764 	  name = PyUnicode_FromStringAndSize(line, comma - line);
2765      else
2766 	  name = PyUnicode_FromString(line);
2767      if (!name)
2768 	  return 0;
2769      return build_body_from_obj(name, &obj);
2770 }
2771 
readtle(PyObject * self,PyObject * args)2772 static PyObject* readtle(PyObject *self, PyObject *args)
2773 {
2774      int result;
2775      const char *l0, *l1, *l2;
2776      PyObject *name, *stripped_name, *body, *catalog_number;
2777      Obj obj;
2778      if (!PyArg_ParseTuple(args, "O!ss:readtle",
2779 			   &PyUnicode_Type, &name, &l1, &l2))
2780 	  return 0;
2781      l0 = PyUnicode_AsUTF8(name);
2782      if (!l0)
2783           return 0;
2784      result = db_tle((char*) l0, (char*) l1, (char*) l2, &obj);
2785      if (result) {
2786 	  PyErr_SetString(PyExc_ValueError,
2787 			  (result == -2) ?
2788                           "incorrect TLE checksum at end of line" :
2789                           "line does not conform to tle format");
2790 	  return 0;
2791      }
2792      stripped_name = PyObject_CallMethod(name, "strip", 0);
2793      if (!stripped_name)
2794 	  return 0;
2795      body = build_body_from_obj(stripped_name, &obj);
2796      if (!body)
2797 	  return 0;
2798      catalog_number = PyLong_FromLong((long) strtod(l1+2, 0));
2799      if (!catalog_number)
2800 	  return 0;
2801      ((EarthSatellite*) body)->catalog_number = catalog_number;
2802      return body;
2803 }
2804 
2805 /* Create various sorts of angles. */
2806 
degrees(PyObject * self,PyObject * args)2807 static PyObject *degrees(PyObject *self, PyObject *args)
2808 {
2809      PyObject *o;
2810      double value;
2811      if (!PyArg_ParseTuple(args, "O:degrees", &o)) return 0;
2812      if (parse_angle(o, raddeg(1), &value) == -1) return 0;
2813      return new_Angle(value, raddeg(1));
2814 }
2815 
hours(PyObject * self,PyObject * args)2816 static PyObject *hours(PyObject *self, PyObject *args)
2817 {
2818      PyObject *o;
2819      double value;
2820      if (!PyArg_ParseTuple(args, "O:hours", &o)) return 0;
2821      if (parse_angle(o, radhr(1), &value) == -1) return 0;
2822      return new_Angle(value, radhr(1));
2823 }
2824 
2825 /* Finally, we wrap some helpful libastro functions. */
2826 
2827 /* Return which page of a star atlas on which a location lies. */
2828 
uranometria(PyObject * self,PyObject * args)2829 static PyObject* uranometria(PyObject *self, PyObject *args)
2830 {
2831      PyObject *rao, *deco;
2832      double ra, dec;
2833      if (!PyArg_ParseTuple(args, "OO:uranometria", &rao, &deco)) return 0;
2834      if (parse_angle(rao, radhr(1), &ra) == -1) return 0;
2835      if (parse_angle(deco, raddeg(1), &dec) == -1) return 0;
2836      return PyUnicode_FromString(um_atlas(ra, dec));
2837 }
2838 
uranometria2000(PyObject * self,PyObject * args)2839 static PyObject* uranometria2000(PyObject *self, PyObject *args)
2840 {
2841      PyObject *rao, *deco;
2842      double ra, dec;
2843      if (!PyArg_ParseTuple(args, "OO:uranometria2000", &rao, &deco)) return 0;
2844      if (parse_angle(rao, radhr(1), &ra) == -1) return 0;
2845      if (parse_angle(deco, raddeg(1), &dec) == -1) return 0;
2846      return PyUnicode_FromString(u2k_atlas(ra, dec));
2847 }
2848 
millennium_atlas(PyObject * self,PyObject * args)2849 static PyObject* millennium_atlas(PyObject *self, PyObject *args)
2850 {
2851      PyObject *rao, *deco;
2852      double ra, dec;
2853      if (!PyArg_ParseTuple(args, "OO:millennium_atlas", &rao, &deco)) return 0;
2854      if (parse_angle(rao, radhr(1), &ra) == -1) return 0;
2855      if (parse_angle(deco, raddeg(1), &dec) == -1) return 0;
2856      return PyUnicode_FromString(msa_atlas(ra, dec));
2857 }
2858 
2859 /* Return in which constellation a particular coordinate lies. */
2860 
2861 int cns_pick(double r, double d, double e);
2862 char *cns_name(int id);
2863 
constellation(PyObject * self,PyObject * args,PyObject * kwds)2864 static PyObject* constellation
2865 (PyObject *self, PyObject *args, PyObject *kwds)
2866 {
2867      static char *kwlist[] = {"position", "epoch", NULL};
2868      PyObject *position_arg = 0, *epoch_arg = 0;
2869      PyObject *s0 = 0, *s1 = 0, *ora = 0, *odec = 0, *oepoch = 0;
2870      PyObject *result;
2871      double ra, dec, epoch = J2000;
2872 
2873      if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:constellation", kwlist,
2874 				      &position_arg, &epoch_arg))
2875 	  return 0;
2876 
2877      if (PyObject_IsInstance(position_arg, (PyObject*) &BodyType)) {
2878 	  Body *b = (Body*) position_arg;
2879 	  if (epoch_arg) {
2880 	       PyErr_SetString(PyExc_TypeError, "you cannot specify an epoch= "
2881 			       "when providing a body for the position, since "
2882 			       "bodies themselves specify the epoch of their "
2883 			       "coordinates");
2884 	       goto fail;
2885 	  }
2886 	  if (b->obj.o_flags == 0) {
2887 	       PyErr_SetString(PyExc_TypeError, "you cannot ask about "
2888 			       "the constellation in which a body "
2889 			       "lies until you have used compute() to "
2890 			       "determine its position");
2891 	       goto fail;
2892 	  }
2893 	  if (Body_obj_cir(b, "ra", 0) == -1)
2894 	       goto fail;
2895 	  ra = b->obj.s_astrora;
2896 	  dec = b->obj.s_astrodec;
2897 	  epoch = b->now.n_epoch;
2898      } else {
2899 	  if (!PySequence_Check(position_arg)) {
2900 	       PyErr_SetString(PyExc_TypeError, "you must specify a position "
2901 			       "by providing either a body or a sequence of "
2902 			       "two numeric coordinates");
2903 	       goto fail;
2904 	  }
2905 	  if (PySequence_Length(position_arg) != 2) {
2906 	       PyErr_SetString(PyExc_ValueError, "the sequence specifying a "
2907 			       "position must have exactly two coordinates");
2908 	       goto fail;
2909 	  }
2910 	  if (epoch_arg)
2911 	       if (parse_mjd(epoch_arg, &epoch) == -1) goto fail;
2912 
2913 	  s0 = PySequence_GetItem(position_arg, 0);
2914 	  if (!s0) goto fail;
2915 	  s1 = PySequence_GetItem(position_arg, 1);
2916 	  if (!s1 || !PyNumber_Check(s0) || !PyNumber_Check(s1)) goto fail;
2917 	  ora = PyNumber_Float(s0);
2918 	  if (!ora) goto fail;
2919 	  odec = PyNumber_Float(s1);
2920 	  if (!odec) goto fail;
2921 	  ra = PyFloat_AsDouble(ora);
2922 	  dec = PyFloat_AsDouble(odec);
2923 
2924 	  if (epoch_arg) {
2925 	       oepoch = PyNumber_Float(epoch_arg);
2926 	       if (!oepoch) goto fail;
2927 	       epoch = PyFloat_AsDouble(oepoch);
2928 	  }
2929      }
2930 
2931      {
2932 	  char *s = cns_name(cns_pick(ra, dec, epoch));
2933 	  result = Py_BuildValue("s#s", s, 3, s+5);
2934 	  goto leave;
2935      }
2936 
2937 fail:
2938      result = 0;
2939 leave:
2940      Py_XDECREF(s0);
2941      Py_XDECREF(s1);
2942      Py_XDECREF(ora);
2943      Py_XDECREF(odec);
2944      Py_XDECREF(oepoch);
2945      return result;
2946 }
2947 
julian_date(PyObject * self,PyObject * args)2948 static PyObject *julian_date(PyObject *self, PyObject *args)
2949 {
2950      PyObject *o = 0;
2951      double mjd;
2952      if (!PyArg_ParseTuple(args, "|O:julian_date", &o)) return 0;
2953      if (!o)
2954 	  mjd = mjd_now();
2955      else if (PyObject_IsInstance(o, (PyObject*) &ObserverType))
2956 	  mjd = ((Observer*) o)->now.n_mjd;
2957      else if (parse_mjd(o, &mjd) == -1)
2958 	  return 0;
2959      return PyFloat_FromDouble(mjd + 2415020.0);
2960 }
2961 
delta_t(PyObject * self,PyObject * args)2962 static PyObject *delta_t(PyObject *self, PyObject *args)
2963 {
2964      PyObject *o = 0;
2965      double mjd;
2966      if (!PyArg_ParseTuple(args, "|O:delta_t", &o)) return 0;
2967      if (!o)
2968 	  mjd = mjd_now();
2969      else if (PyObject_IsInstance(o, (PyObject*) &ObserverType))
2970 	  mjd = ((Observer*) o)->now.n_mjd;
2971      else if (parse_mjd(o, &mjd) == -1)
2972 	  return 0;
2973      return PyFloat_FromDouble(deltat(mjd));
2974 }
2975 
moon_phases(PyObject * self,PyObject * args)2976 static PyObject *moon_phases(PyObject *self, PyObject *args)
2977 {
2978      PyObject *o = 0, *d;
2979      double mjd, mjn, mjf;
2980      if (!PyArg_ParseTuple(args, "|O:moon_phases", &o)) return 0;
2981      if (!o)
2982 	  mjd = mjd_now();
2983      else if (PyObject_IsInstance(o, (PyObject*) &ObserverType))
2984 	  mjd = ((Observer*) o)->now.n_mjd;
2985      else if (parse_mjd(o, &mjd) == -1)
2986 	  return 0;
2987      moonnf(mjd, &mjn, &mjf);
2988      o = PyDict_New();
2989      if (!o) return 0;
2990      d = build_Date(mjn);
2991      if (!d) return 0;
2992      if (PyDict_SetItemString(o, "new", d) == -1) return 0;
2993      d = build_Date(mjf);
2994      if (!d) return 0;
2995      if (PyDict_SetItemString(o, "full", d) == -1) return 0;
2996      return o;
2997 }
2998 
my_eq_ecl(PyObject * self,PyObject * args)2999 static PyObject *my_eq_ecl(PyObject *self, PyObject *args)
3000 {
3001      double mjd, ra, dec, lg, lt;
3002      if (!PyArg_ParseTuple(args, "ddd:eq_ecl", &mjd, &ra, &dec)) return 0;
3003      eq_ecl(mjd, ra, dec, &lt, &lg);
3004      return Py_BuildValue("NN", build_degrees(lg), build_degrees(lt));
3005 }
3006 
my_ecl_eq(PyObject * self,PyObject * args)3007 static PyObject *my_ecl_eq(PyObject *self, PyObject *args)
3008 {
3009      double mjd, ra, dec, lg, lt;
3010      if (!PyArg_ParseTuple(args, "ddd:ecl_eq", &mjd, &lg, &lt)) return 0;
3011      ecl_eq(mjd, lt, lg, &ra, &dec);
3012      return Py_BuildValue("NN", build_hours(ra), build_degrees(dec));
3013 }
3014 
my_eq_gal(PyObject * self,PyObject * args)3015 static PyObject *my_eq_gal(PyObject *self, PyObject *args)
3016 {
3017      double mjd, ra, dec, lg, lt;
3018      if (!PyArg_ParseTuple(args, "ddd:eq_gal", &mjd, &ra, &dec)) return 0;
3019      eq_gal(mjd, ra, dec, &lt, &lg);
3020      return Py_BuildValue("NN", build_degrees(lg), build_degrees(lt));
3021 }
3022 
my_gal_eq(PyObject * self,PyObject * args)3023 static PyObject *my_gal_eq(PyObject *self, PyObject *args)
3024 {
3025      double mjd, ra, dec, lg, lt;
3026      if (!PyArg_ParseTuple(args, "ddd:gal_eq", &mjd, &lg, &lt)) return 0;
3027      gal_eq(mjd, lt, lg, &ra, &dec);
3028      return Py_BuildValue("NN", build_hours(ra), build_degrees(dec));
3029 }
3030 
my_precess(PyObject * self,PyObject * args)3031 static PyObject *my_precess(PyObject *self, PyObject *args)
3032 {
3033      double mjd1, mjd2, ra, dec;
3034      if (!PyArg_ParseTuple(args, "dddd:precess", &mjd1, &mjd2, &ra, &dec))
3035           return 0;
3036      precess(mjd1, mjd2, &ra, &dec);
3037      return Py_BuildValue("NN", build_hours(ra), build_degrees(dec));
3038 }
3039 
_next_pass(PyObject * self,PyObject * args)3040 static PyObject *_next_pass(PyObject *self, PyObject *args)
3041 {
3042      Observer *observer;
3043      Body *body;
3044      RiseSet rs;
3045      if (!PyArg_ParseTuple(args, "O!O!", &ObserverType, &observer,
3046                            &BodyType, &body))
3047           return 0;
3048      riset_cir(& observer->now, & body->obj, - body->now.n_dip, & rs);
3049      if (rs.rs_flags & RS_CIRCUMPOLAR) {
3050           PyErr_SetString(PyExc_ValueError, "that satellite appears to be"
3051                           " circumpolar and so will never cross the horizon");
3052           return 0;
3053      }
3054      if (rs.rs_flags & RS_NEVERUP) {
3055           PyErr_SetString(PyExc_ValueError, "that satellite seems to stay"
3056                           " always below your horizon");
3057           return 0;
3058      }
3059      if (rs.rs_flags & RS_ERROR) {
3060           PyErr_SetString(PyExc_ValueError, "cannot find when that"
3061                           " satellite next crosses the horizon");
3062           return 0;
3063      }
3064      {
3065           PyObject *risetm, *riseaz, *trantm, *tranalt, *settm, *setaz;
3066 
3067           if (rs.rs_flags & RS_NORISE) {
3068                Py_INCREF(Py_None);
3069                risetm = Py_None;
3070                Py_INCREF(Py_None);
3071                riseaz = Py_None;
3072           } else {
3073                risetm = build_Date(rs.rs_risetm);
3074                riseaz = build_degrees(rs.rs_riseaz);
3075           }
3076 
3077           if (rs.rs_flags & (RS_NORISE | RS_NOSET | RS_NOTRANS)) {
3078                Py_INCREF(Py_None);
3079                trantm = Py_None;
3080                Py_INCREF(Py_None);
3081                tranalt = Py_None;
3082           } else {
3083                trantm = build_Date(rs.rs_trantm);
3084                tranalt = build_degrees(rs.rs_tranalt);
3085           }
3086 
3087           if (rs.rs_flags & (RS_NORISE | RS_NOSET)) {
3088                Py_INCREF(Py_None);
3089                settm = Py_None;
3090                Py_INCREF(Py_None);
3091                setaz = Py_None;
3092           } else {
3093                settm = build_Date(rs.rs_settm);
3094                setaz = build_degrees(rs.rs_setaz);
3095           }
3096 
3097           return Py_BuildValue(
3098                "(OOOOOO)", risetm, riseaz, trantm, tranalt, settm, setaz
3099                );
3100      }
3101 }
3102 
3103 /*
3104  * Why is this all the way down here?  It needs to refer to the type
3105  * objects themselves, which are defined rather late in the file.
3106  */
3107 
Body__copy_struct(Body * body,Body * newbody)3108 void Body__copy_struct(Body *body, Body *newbody)
3109 {
3110      memcpy((void *)&newbody->now, (void *)&body->now, sizeof(Now));
3111      memcpy((void *)&newbody->obj, (void *)&body->obj, sizeof(Obj));
3112      memcpy((void *)&newbody->riset, (void *)&body->riset, sizeof(RiseSet));
3113 
3114      newbody->name = body->name;
3115      Py_XINCREF(newbody->name);
3116 
3117      if (PyObject_IsInstance((PyObject*) body, (PyObject*) &MoonType)) {
3118           Moon *a = (Moon *) newbody, *b = (Moon *) body;
3119           a->llat = b->llat;
3120           a->llon = b->llon;
3121           a->c = b->c;
3122           a->k = b->k;
3123           a->s = b->s;
3124      }
3125      if (PyObject_IsInstance((PyObject*) body, (PyObject*) &JupiterType)) {
3126           Jupiter *a = (Jupiter *) newbody, *b = (Jupiter *) body;
3127           a->cmlI = b->cmlI;
3128           a->cmlII = b->cmlII;
3129      }
3130      if (PyObject_IsInstance((PyObject*) body, (PyObject*) &SaturnType)) {
3131           Saturn *a = (Saturn *) newbody, *b = (Saturn *) body;
3132           a->etilt = b->etilt;
3133           a->stilt = b->stilt;
3134      }
3135      if (PyObject_IsInstance((PyObject*) body, (PyObject*) &EarthSatelliteType)) {
3136           EarthSatellite *a = (EarthSatellite*) newbody;
3137           EarthSatellite *b = (EarthSatellite*) body;
3138           a->catalog_number = b->catalog_number;
3139           Py_XINCREF(a->name);
3140      }
3141 }
3142 
3143 /*
3144  * The global methods table and the module initialization function.
3145  */
3146 
3147 static PyMethodDef libastro_methods[] = {
3148 
3149      {"degrees", degrees, METH_VARARGS, "build an angle measured in degrees"},
3150      {"hours", hours, METH_VARARGS, "build an angle measured in hours of arc"},
3151      {"now", (PyCFunction) build_now, METH_NOARGS, "Return the current time"},
3152      {"readdb", readdb, METH_VARARGS, "Read an ephem database entry"},
3153      {"readtle", readtle, METH_VARARGS, "Read TLE-format satellite elements"},
3154      {"unrefract", py_unrefract, METH_VARARGS, "Reverse angle of refraction"},
3155      {"separation", separation, METH_VARARGS,
3156       "Return the angular separation between two objects or positions" D},
3157 
3158      {"uranometria", uranometria, METH_VARARGS,
3159       "given right ascension and declination (in radians), return the page"
3160       " of the original Uranometria displaying that location"},
3161      {"uranometria2000", uranometria2000, METH_VARARGS,
3162       "given right ascension and declination (in radians), return the page"
3163       " of the Uranometria 2000.0 displaying that location"},
3164      {"millennium_atlas", millennium_atlas, METH_VARARGS,
3165       "given right ascension and declination (in radians), return the page"
3166       " of the Millenium Star Atlas displaying that location"},
3167 
3168      {"constellation", (PyCFunction) constellation,
3169       METH_VARARGS | METH_KEYWORDS,
3170       "Return the constellation in which the object or coordinates lie"},
3171      {"julian_date", (PyCFunction) julian_date, METH_VARARGS,
3172       "Return the Julian date of the current time,"
3173       " or of an argument that can be converted into an ephem.Date."},
3174      {"delta_t", (PyCFunction) delta_t, METH_VARARGS,
3175       "Compute the difference between Terrestrial Time and Coordinated"
3176       " Universal Time."},
3177      {"moon_phases", (PyCFunction) moon_phases, METH_VARARGS,
3178       "compute the new and full moons nearest a given date"},
3179 
3180      {"eq_ecl", (PyCFunction) my_eq_ecl, METH_VARARGS,
3181       "compute the ecliptic longitude and latitude of an RA and dec"},
3182      {"ecl_eq", (PyCFunction) my_ecl_eq, METH_VARARGS,
3183       "compute the ecliptic longitude and latitude of an RA and dec"},
3184      {"eq_gal", (PyCFunction) my_eq_gal, METH_VARARGS,
3185       "compute the ecliptic longitude and latitude of an RA and dec"},
3186      {"gal_eq", (PyCFunction) my_gal_eq, METH_VARARGS,
3187       "compute the ecliptic longitude and latitude of an RA and dec"},
3188 
3189      {"precess", (PyCFunction) my_precess, METH_VARARGS,
3190       "precess a right ascension and declination to another equinox"},
3191 
3192      {"builtin_planets", (PyCFunction) builtin_planets, METH_NOARGS,
3193       "return the list of built-in planet objects"},
3194 
3195      {"_next_pass", (PyCFunction) _next_pass, METH_VARARGS,
3196       "Return as a tuple the next rising, culmination, and setting"
3197       " of an EarthSatellite"},
3198 
3199      {NULL}
3200 };
3201 
3202 #if PY_MAJOR_VERSION == 3
3203 
3204 /* PyDoc_STRVAR(doc_module, */
3205 /* "(Put documentation here.)"); */
3206 
3207 static struct PyModuleDef libastro_module = {
3208      PyModuleDef_HEAD_INIT,
3209      "_libastro",
3210      "Astronomical calculations for Python",
3211      -1,
3212      libastro_methods
3213 };
3214 
3215 #endif
3216 
PyInit__libastro(void)3217 PyObject *PyInit__libastro(void)
3218 {
3219      PyDateTime_IMPORT;
3220 
3221      /* Initialize pointers to objects external to this module. */
3222 
3223      AngleType.tp_base = &PyFloat_Type;
3224      DateType.tp_base = &PyFloat_Type;
3225 
3226      ObserverType.tp_new = PyType_GenericNew;
3227      BodyType.tp_new = PyType_GenericNew;
3228      PlanetMoonType.tp_new = PyType_GenericNew;
3229 
3230      /* Ready each type. */
3231 
3232      PyType_Ready(&AngleType);
3233      PyType_Ready(&DateType);
3234 
3235      PyType_Ready(&ObserverType);
3236 
3237      PyType_Ready(&BodyType);
3238      PyType_Ready(&PlanetType);
3239      PyType_Ready(&PlanetMoonType);
3240 
3241      PyType_Ready(&JupiterType);
3242      PyType_Ready(&SaturnType);
3243      PyType_Ready(&MoonType);
3244 
3245      PyType_Ready(&FixedBodyType);
3246      PyType_Ready(&BinaryStarType);
3247      PyType_Ready(&EllipticalBodyType);
3248      PyType_Ready(&HyperbolicBodyType);
3249      PyType_Ready(&ParabolicBodyType);
3250      PyType_Ready(&EarthSatelliteType);
3251 
3252 #if PY_MAJOR_VERSION == 2
3253      module = Py_InitModule3("_libastro", libastro_methods,
3254                              "Astronomical calculations for Python");
3255 #else
3256      module = PyModule_Create(&libastro_module);
3257 #endif
3258      if (!module) return 0;
3259 
3260      {
3261 	  struct {
3262 	       char *name;
3263 	       PyObject *obj;
3264 	  } objects[] = {
3265 	       { "Angle", (PyObject*) & AngleType },
3266 	       { "Date", (PyObject*) & DateType },
3267 
3268 	       { "Observer", (PyObject*) & ObserverType },
3269 
3270 	       { "Body", (PyObject*) & BodyType },
3271 	       { "Planet", (PyObject*) & PlanetType },
3272 	       { "PlanetMoon", (PyObject*) & PlanetMoonType },
3273 	       { "Jupiter", (PyObject*) & JupiterType },
3274 	       { "Saturn", (PyObject*) & SaturnType },
3275 	       { "Moon", (PyObject*) & MoonType },
3276 
3277 	       { "FixedBody", (PyObject*) & FixedBodyType },
3278 	       /* { "BinaryStar", (PyObject*) & BinaryStarType }, */
3279 	       { "EllipticalBody", (PyObject*) & EllipticalBodyType },
3280 	       { "ParabolicBody", (PyObject*) & ParabolicBodyType },
3281 	       { "HyperbolicBody", (PyObject*) & HyperbolicBodyType },
3282 	       { "EarthSatellite", (PyObject*) & EarthSatelliteType },
3283 
3284 	       { "meters_per_au", PyFloat_FromDouble(MAU) },
3285 	       { "earth_radius", PyFloat_FromDouble(ERAD) },
3286 	       { "moon_radius", PyFloat_FromDouble(MRAD) },
3287 	       { "sun_radius", PyFloat_FromDouble(SRAD) },
3288 
3289 	       { "MJD0", PyFloat_FromDouble(MJD0) },
3290 	       { "J2000", PyFloat_FromDouble(J2000) },
3291 
3292 	       { NULL }
3293 	  };
3294 	  int i;
3295 	  for (i=0; objects[i].name; i++)
3296 	       if (PyModule_AddObject(module, objects[i].name, objects[i].obj)
3297 		   == -1)
3298 		    return 0;
3299      }
3300 
3301      /* Set a default preference. */
3302 
3303      pref_set(PREF_DATE_FORMAT, PREF_YMD);
3304 
3305      /* Tell libastro that we do not have data files anywhere. */
3306 
3307      setMoonDir(NULL);
3308 
3309      /* All done! */
3310 
3311      return module;
3312 }
3313 
3314 #if PY_MAJOR_VERSION == 2
3315 #ifdef PyMODINIT_FUNC
3316 PyMODINIT_FUNC
3317 #else
3318 DL_EXPORT(void)
3319 #endif
init_libastro(void)3320 init_libastro(void)
3321 {
3322      PyInit__libastro();
3323 }
3324 #endif
3325