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, <, &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, <)) 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, <, &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, <)) 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