[comp.sources.misc] v09i039: ephem, v4.8, 2 of 5

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (11/28/89)

Posting-number: Volume 9, Issue 39
Submitted-by: ecd@umn-cs.cs.umn.edu@ncs-med.UUCP (Elwood C. Downey)
Archive-name: ephem2/part02

# This is a "shell archive" file; run it with sh.
# This is file 2.
echo x screen.h
cat > screen.h << 'xXx'
/* screen layout details
 *
 * it looks better if the fields are drawn in some nice order so it you
 * rearrange the fields, check the menu printing functions.
 */

/* size of screen */
#define	NR	24
#define	NC	80

#define	ASPECT	(4./3.)	/* screen width to height dimensions ratio */

#define	GAP	6	/* gap between field name and value */

#define	COL1		1
#define	COL2		27
#define	COL3		44
#define	COL4		61	/* calendar */

#define	R_PROMPT	1	/* prompt row */
#define	C_PROMPT	COL1

#define	R_NEWCIR	2
#define	C_NEWCIR	((NC-17)/2) /* 17 is length of the message */

#define	R_TOP		3	/* first row of top menu items */

#define	R_TZN	(R_TOP+0)
#define	C_TZN	COL1
#define	R_LT	R_TZN
#define	C_LT	(C_TZN+GAP-2)
#define	R_LD	R_TZN
#define	C_LD	(C_TZN+13)

#define	R_UT	(R_TOP+1)
#define	C_UT	COL1
#define	C_UTV	(C_UT+GAP-2)
#define	R_UD	R_UT
#define	C_UD	(C_UT+13)

#define	R_JD	(R_TOP+2)
#define	C_JD	COL1
#define	C_JDV	(C_JD+GAP+3)

#define	R_LST	(R_TOP)
#define	C_LST	COL2
#define	C_LSTV	(C_LST+GAP)

#define	R_LAT	(R_TOP+0)
#define	C_LAT	COL3
#define	C_LATV	(C_LAT+4)

#define	R_DAWN	(R_TOP+2)
#define	C_DAWN	COL2
#define	C_DAWNV	(C_DAWN+GAP+3)

#define	R_STPSZ	(R_TOP+7)
#define	C_STPSZ	COL2
#define	C_STPSZV (C_STPSZ+GAP-1)

#define	R_HEIGHT (R_TOP+2)
#define	C_HEIGHT COL3
#define	C_HEIGHTV (C_HEIGHT+GAP)

#define	R_PRES	(R_TOP+4)
#define	C_PRES	COL3
#define	C_PRESV	(C_PRES+GAP)

#define	R_WATCH	(R_TOP+4)
#define	C_WATCH	COL1

#define	R_SRCH	(R_TOP+5)
#define	C_SRCH	COL1
#define	C_SRCHV	(C_SRCH+16)

#define	R_PLOT	(R_TOP+6)
#define	C_PLOT	(COL1)
#define	C_PLOTV (C_PLOT+20)

#define	R_ALTM	(R_TOP+7)
#define	C_ALTM	COL1
#define	C_ALTMV	(C_ALTM+10)

#define	R_TZONE	(R_TOP+5)
#define	C_TZONE	COL3
#define	C_TZONEV (C_TZONE+GAP-1)

#define	R_LONG	(R_TOP+1)
#define	C_LONG	COL3
#define	C_LONGV	(C_LONG+4)

#define	R_DUSK	(R_TOP+3)
#define	C_DUSK	COL2
#define	C_DUSKV	(C_DUSK+GAP+3)

#define	R_NSTEP (R_TOP+6)
#define	C_NSTEP	COL2
#define	C_NSTEPV (C_NSTEP+GAP)

#define	R_TEMP	(R_TOP+3)
#define	C_TEMP	COL3
#define	C_TEMPV	(C_TEMP+GAP)

#define	R_EPOCH		(R_TOP+6)
#define	C_EPOCH		COL3
#define	C_EPOCHV	(C_EPOCH+GAP)

#define	R_MNUDEP	(R_TOP+6)
#define	C_MNUDEP	COL3
#define	C_MNUDEPV	(C_EPOCH+GAP)

#define	R_LON	(R_TOP+4)
#define	C_LON	COL2
#define	C_LONV	(C_LON+GAP+3)

#define	R_CAL	R_TOP
#define	C_CAL   COL4

/* menu 1 info table */
#define	R_PLANTAB	(R_TOP+9)
#define	R_SUN		(R_PLANTAB+2)
#define	R_MOON		(R_PLANTAB+3)
#define	R_MERCURY	(R_PLANTAB+4)
#define	R_VENUS		(R_PLANTAB+5)
#define	R_MARS		(R_PLANTAB+6)
#define	R_JUPITER	(R_PLANTAB+7)
#define	R_SATURN	(R_PLANTAB+8)
#define	R_URANUS	(R_PLANTAB+9)
#define	R_NEPTUNE	(R_PLANTAB+10)
#define	R_PLUTO		(R_PLANTAB+11)
#define	R_OBJX		(R_PLANTAB+12)
#define	C_OBJ		1
#define	C_RA		4
#define	C_DEC		12
#define	C_AZ		19
#define	C_ALT		26
#define	C_HLONG		33
#define	C_HLAT		40
#define	C_EDIST		47
#define C_SDIST 	54
#define	C_ELONG		61
#define	C_SIZE		68
#define	C_MAG		73
#define	C_PHASE		78

/* menu 2 screen items */
#define	C_RISETM	10
#define	C_RISEAZ	18
#define	C_TRANSTM	31
#define	C_TRANSALT	39
#define	C_SETTM		52
#define	C_SETAZ		60
#define	C_TUP		72

/* menu 3 items */
#define	C_SUN		4
#define	C_MOON		11
#define	C_MERCURY	18
#define	C_VENUS		25
#define	C_MARS		32
#define	C_JUPITER	39
#define	C_SATURN	46
#define	C_URANUS	53
#define	C_NEPTUNE	60
#define	C_PLUTO		67
#define	C_OBJX		74

#define	MAXOBJXNM	2	/* object x's name. does not include trail 0 */

#define	PW	(NC-C_PROMPT+1)	/* total prompt line width */

/* macros to pack a row/col and menu selection flags all into an int.
 * (use this rather than a structure because we can compare them so easily.
 * could use bit fields and a union, but then can't init them or use switch.)
 * bit field defs: [15..14]=menu  [13..12]=flags  [11..7]=row  [6..0]=column.
 * see sel_fld.c.
 * F_MNUX also used in main to manage which bottom menu is up.
 */
#define	F_CHG		(1<<12)	/* field may be picked for changing */
#define	F_PLT		(1<<13)	/* field may be picked for plotting */
#define	F_MMNU		(0<<14)	/* field is on main menu */
#define	F_MNU1		(1<<14)	/* field is on menu 1 */
#define	F_MNU2	 	(2<<14)	/* field is on menu 2 */
#define	F_MNU3		(3<<14)	/* field is on menu 3 */
#define	rcfpack(r,c,f)	((f) | ((r) << 7) | (c))
#define	unpackr(p)	(((p) >> 7) & 0x1f)
#define	unpackc(p)	((p) & 0x7f)
#define	unpackrc(p)	((p) & 0xfff)
#define	tstpackf(p,f)	(((p) & ((f)&0x3000)) && \
		    (((p)&0xc000) == ((f)&0xc000) || ((p)&0xc000) == 0))

/* additions to the planet defines from astro.h.
 * must not conflict, and must fit in range 0..15.
 */
#define	SUN	(PLUTO+1)
#define	MOON	(PLUTO+2)
#define	OBJX	(PLUTO+3)	/* the user-defined object */
#define	NOBJ	(OBJX+1)	/* total number of objects */

#define	cntrl(x)	((x) & 037)
#define	QUIT		cntrl('d')	/* char to exit program */
#define	HELP		'?'		/* char to give help message */
#define	REDRAW		cntrl('l')	/* char to redraw (like vi) */
#define	VERSION		cntrl('v')	/* char to display version number */
#define	END		'q'		/* char to quit current mode */
xXx
echo x aa_hadec.c
cat > aa_hadec.c << 'xXx'
#include <stdio.h>
#include <math.h>
#include "astro.h"

/* given latitude (n+, radians), lat, altitude (up+, radians), alt, and
 * azimuth (angle round to the east from north+, radians),
 * return hour angle (radians), ha, and declination (radians), dec.
 */
aa_hadec (lat, alt, az, ha, dec)
double lat;
double alt, az;
double *ha, *dec;
{
	aaha_aux (lat, az, alt, ha, dec);
}

/* given latitude (n+, radians), lat, hour angle (radians), ha, and declination
 * (radians), dec,
 * return altitude (up+, radians), alt, and
 * azimuth (angle round to the east from north+, radians),
 */
hadec_aa (lat, ha, dec, alt, az)
double lat;
double ha, dec;
double *alt, *az;
{
	aaha_aux (lat, ha, dec, az, alt);
}

/* the actual formula is the same for both transformation directions so
 * do it here once for each way.
 * N.B. all arguments are in radians.
 */
static
aaha_aux (lat, x, y, p, q)
double lat;
double x, y;
double *p, *q;
{
	static double lastlat = -1000.;
	static double sinlastlat, coslastlat;
	double sy, cy;
	double sx, cx;
	double sq, cq;
	double a;
	double cp;

	/* latitude doesn't change much, so try to reuse the sin and cos evals.
	 */
	if (lat != lastlat) {
	    sinlastlat = sin (lat);
	    coslastlat = cos (lat);
	    lastlat = lat;
	}

	sy = sin (y);
	cy = cos (y);
	sx = sin (x);
	cx = cos (x);

/* define GOODATAN2 if atan2 returns full range -PI through +PI.
 */
#ifdef GOODATAN2
	*q = asin ((sy*sinlastlat) + (cy*coslastlat*cx));
	*p = atan2 (-cy*sx, -cy*cx*sinlastlat + sy*coslastlat);
#else
#define	EPS	(1e-20)
	sq = (sy*sinlastlat) + (cy*coslastlat*cx);
	*q = asin (sq);
	cq = cos (*q);
	a = coslastlat*cq;
	if (a > -EPS && a < EPS)
	    a = a < 0 ? -EPS : EPS; /* avoid / 0 */
	cp = (sy - (sinlastlat*sq))/a;
	if (cp >= 1.0)	/* the /a can be slightly > 1 */
	    *p = 0.0;
	else if (cp <= -1.0)
	    *p = PI;
	else
	    *p = acos ((sy - (sinlastlat*sq))/a);
	if (sx>0) *p = 2.0*PI - *p;
#endif
}
xXx
echo x altmenus.c
cat > altmenus.c << 'xXx'
/* printing routines for the three alternative bottom half menus,
 * "menu1", "menu2" and "menu3".
 */

#include <stdio.h>
#include <math.h>
#include "astro.h"
#include "circum.h"
#include "screen.h"

static int altmenu = F_MNU1;	/* which alternate menu is up; one of F_MNUi */
static int alt2_stdhzn;	/* whether to use STDHZN (aot ADPHZN) horizon algthm  */
static int alt3_geoc;	/* whether to use geocentric (aot topocentric) vantage*/

/* table of screen rows given a body #define from astro/h or screen.h */
static short bodyrow[NOBJ] = {
	R_MERCURY, R_VENUS, R_MARS, R_JUPITER, R_SATURN,
	R_URANUS, R_NEPTUNE, R_PLUTO, R_SUN, R_MOON, R_OBJX
};
/* table of screen cols for third menu format, given body #define ... */
static short bodycol[NOBJ] = {
	C_MERCURY, C_VENUS, C_MARS, C_JUPITER, C_SATURN,
	C_URANUS, C_NEPTUNE, C_PLUTO, C_SUN, C_MOON, C_OBJX
};

/* let op decide which alternate menu should be up,
 * including any menu-specific setup they might require.
 * return 0 if things changed to require updating the alt menu; else -1.
 */
altmenu_setup()
{
	static char *flds[5] = {
	    "Data menu", "Rise/Set menu", "Separations menu"
	};
	int newmenu = altmenu, newhzn = alt2_stdhzn, newgeoc = alt3_geoc;
	int new;
	int fn = altmenu == F_MNU1 ? 0 : altmenu == F_MNU2 ? 1 : 2;

    ask:
	flds[3]= newhzn ? "Standard hzn" : "Adaptive hzn";
	flds[4]= newgeoc? "Geocentric" : "Topocentric";

	switch (popup (flds, fn, 5)) {
	case 0: newmenu = F_MNU1; break;
	case 1: newmenu = F_MNU2; break;
	case 2: newmenu = F_MNU3; break;
	case 3: newhzn ^= 1; fn = 3; goto ask;
	case 4: newgeoc ^= 1; fn = 4; goto ask;
	default: return (-1);
	}

	new = 0;
	if (newmenu != altmenu) {
	    altmenu = newmenu;
	    new++;
	}
	if (newhzn != alt2_stdhzn) {
	    alt2_stdhzn = newhzn;
	    if (newmenu == F_MNU2)
		new++;
	}
	if (newgeoc != alt3_geoc) {
	    alt3_geoc = newgeoc;
	    if (newmenu == F_MNU3)
		new++;
	}
	return (new ? 0 : -1);
}

/* erase the info for the given planet */
alt_nobody (p)
int p;
{
	c_pos (bodyrow[p], C_RA);
	c_eol();
}

alt_body (b, force, np)
int b;		/* which body, ala astro.h and screen.h defines */
int force;	/* if !0 then draw for sure, else just if changed since last */
Now *np;
{
	switch (altmenu) {
	case F_MNU1: alt1_body (b, force, np); break;
	case F_MNU2: alt2_body (b, force, np); break;
	case F_MNU3: alt3_body (b, force, np); break;
	}
}

/* draw the labels for the current alternate menu format */
alt_labels ()
{
	switch (altmenu) {
	case F_MNU1: alt1_labels (); break;
	case F_MNU2: alt2_labels (); break;
	case F_MNU3: alt3_labels (); break;
	}
}

/* erase the labels for the current alternate menu format */
alt_nolabels ()
{
	int i;

	f_string (R_ALTM, C_ALTMV, "             ");
	for (i = R_PLANTAB; i < R_SUN; i++) {
	    c_pos (i, C_RA);
	    c_eol();
	}
}

alt_menumask()
{
	return (altmenu);
}

/* handy function to return the next planet in the order in which they are
 * displayed in the lower half of the screen.
 * input is a given planet, return is the next planet.
 * if input is not legal, then first planet is returned; when input is the
 * last planet, then -1 is returned.
 * typical usage is something like:
 *   for (p = nxtbody(-1); p != -1; p = nxtbody(p))
 */
nxtbody(c)
int c;
{
	static short nxtpl[NOBJ] = {
	    VENUS, MARS, JUPITER, SATURN, URANUS,
	    NEPTUNE, PLUTO, OBJX, MOON, MERCURY, -1
	};

	if (c < MERCURY || c >= NOBJ)
	    return (SUN);
	else
	    return (nxtpl[c]);
}

static
alt1_labels()
{
	f_string (R_ALTM, C_ALTMV, "  Planet Data");

	f_string (R_PLANTAB,	C_RA,	"R.A.");
	f_string (R_PLANTAB+1,	C_RA,	"Hr:Mn.d");
	f_string (R_PLANTAB,	C_DEC,	"Dec");
	f_string (R_PLANTAB+1,	C_DEC,	"Deg:Mn");
	f_string (R_PLANTAB,	C_AZ,	"Az");
	f_string (R_PLANTAB+1,	C_AZ,	"Deg E");
	f_string (R_PLANTAB,	C_ALT,	"Alt");
	f_string (R_PLANTAB+1,	C_ALT,	"Deg Up");
	f_string (R_PLANTAB,	C_HLONG,"Helio");
	f_string (R_PLANTAB+1,	C_HLONG,"Long");
	f_string (R_PLANTAB,	C_HLAT,	"Helio");
	f_string (R_PLANTAB+1,	C_HLAT,	"Lat");
	f_string (R_PLANTAB,	C_EDIST,"Ea Dst");
	f_string (R_PLANTAB+1,	C_EDIST,"AU(mi)");
	f_string (R_PLANTAB,	C_SDIST,"Sn Dst");
	f_string (R_PLANTAB+1,	C_SDIST,"AU");
	f_string (R_PLANTAB,	C_ELONG,"Elong");
	f_string (R_PLANTAB+1,	C_ELONG,"Deg E");
	f_string (R_PLANTAB,	C_SIZE,	"Size");
	f_string (R_PLANTAB+1,	C_SIZE,	"ArcS");
	f_string (R_PLANTAB,	C_MAG,	"VMag");
	f_string (R_PLANTAB,	C_PHASE,"Phs");
	f_char (R_PLANTAB+1,	C_PHASE,'%');
}

static
alt2_labels()
{
	f_string (R_ALTM, C_ALTMV, "Rise/Set Info");

	f_string (R_PLANTAB,	C_RISETM+5,	"Rise");
	f_string (R_PLANTAB+1,	C_RISETM+1,	"Time");
	f_string (R_PLANTAB+1,	C_RISEAZ+2,	"Az");
	f_string (R_PLANTAB,	C_TRANSTM+3,	"Transit");
	f_string (R_PLANTAB+1,	C_TRANSTM+1,	"Time");
	f_string (R_PLANTAB+1,	C_TRANSALT+2,	"Alt");
	f_string (R_PLANTAB,	C_SETTM+5,	"Set");
	f_string (R_PLANTAB+1,	C_SETTM+1,	"Time");
	f_string (R_PLANTAB+1,	C_SETAZ+2,	"Az");
	f_string (R_PLANTAB,	C_TUP,		"Hrs Up");
}

static
alt3_labels()
{
	f_string (R_ALTM, C_ALTMV, "  Separations");

	f_string (R_PLANTAB,	C_SUN+2,	"Sun");
	f_string (R_PLANTAB,	C_MOON+1,	"Moon");
	f_string (R_PLANTAB,	C_MERCURY+1,	"Merc");
	f_string (R_PLANTAB,	C_VENUS+1,	"Venus");
	f_string (R_PLANTAB,	C_MARS+1,	"Mars");
	f_string (R_PLANTAB,	C_JUPITER+2,	"Jup");
	f_string (R_PLANTAB,	C_SATURN,	"Saturn");
	f_string (R_PLANTAB,	C_URANUS,	"Uranus");
	f_string (R_PLANTAB,	C_NEPTUNE+2,	"Nep");
	f_string (R_PLANTAB,	C_PLUTO+1,	"Pluto");

	if (objx_ison()) {
	    char xnm[MAXOBJXNM+1];
	    char buf[10];
	    objx_get ((double *)0, (double *)0, (double *)0, xnm);
	    sprintf (buf, "%-2.2s", xnm); /* set all spaces */
	    f_string (R_PLANTAB, C_OBJX, buf);
	}
}

/* print body info in first menu format */
static
alt1_body (p, force, np)
int p;		/* which body, as in astro.h/screen.h defines */
int force;	/* whether to print for sure or only if things have changed */
Now *np;
{
	Sky sky;
	double as = plot_ison() || srch_ison() ? 0.0 : 60.0;
	int row = bodyrow[p];

	if (body_cir (p, as, np, &sky) || force) {
	    if (p == OBJX)
		pr_objxname();
	    f_ra (row, C_RA, sky.s_ra);
	    f_angle (row, C_DEC, sky.s_dec);
	    if (p != MOON && p != OBJX) {
		f_angle (row, C_HLONG, sky.s_hlong);
		f_angle (row, C_HLAT, sky.s_hlat);
	    }

	    if (p == MOON) {
		/* distance is on km, show in miles */
		f_double (R_MOON, C_EDIST, "%6.0f", sky.s_edist/1.609344);
	    } else if (p != OBJX) {
		/* show distance in au */
		f_double (row, C_EDIST,(sky.s_edist>=10.0)?"%6.3f":"%6.4f",
								sky.s_edist);
	    }
	    if (p != OBJX)
		f_double (row, C_SDIST, (sky.s_sdist>=10.0)?"%6.3f":"%6.4f",
								sky.s_sdist);
	    f_double (row, C_ELONG, "%6.1f", sky.s_elong);
	    if (p != OBJX) {
		f_double (row, C_SIZE, (p==MOON||p==SUN)?"%4.0f":"%4.1f",
								sky.s_size);
		f_double (row, C_MAG, (p==MOON||p==SUN)?"%4.0f":"%4.1f",
								sky.s_mag);
		if (p != SUN) {
		    char buf[10];
		    /* would just do this if Turbo-C 2.0 "%?.0f" worked:
		     * f_double (row, C_PHASE, "%3.0f", sky.s_phase);
		     */
		    sprintf (buf, "%3d", (int)(sky.s_phase+0.5));
		    f_string (row, C_PHASE, buf);
		}
	    }
	}

	f_angle (row, C_AZ, sky.s_az);
	f_angle (row, C_ALT, sky.s_alt);
}

/* print body info in the second menu format */
static
alt2_body (p, force, np)
int p;		/* which body, as in astro.h/screen.h defines */
int force;	/* whether to print for sure or only if things have changed */
Now *np;
{
	double ltr, lts, ltt, azr, azs, altt;
	int row = bodyrow[p];
	int status;
	double tmp;
	int today_tup = 0;

	if (!riset_cir (p, np, alt2_stdhzn?STDHZN:ADPHZN, &ltr, &lts, &ltt,
					&azr, &azs, &altt, &status) && !force)
	    return;

	alt_nobody (p);
	if (p == OBJX)
	    pr_objxname();

	if (status & RS_ERROR) {
	    /* can not find where body is! */
	    f_string (row, C_RISETM, "?Error?");
	    return;
	}
	if (status & RS_CIRCUMPOLAR) {
	    /* body is up all day */
	    f_string (row, C_RISETM, "Circumpolar");
	    f_mtime (row, C_TRANSTM, ltt);
	    if (status & RS_2TRANS)
		f_char (row, C_TRANSTM+5, '+');
	    f_angle (row, C_TRANSALT, altt);
	    f_string (row, C_TUP, "24:00"); /*f_mtime() changes to 0:00 */
	    return;
	}
	if (status & RS_NEVERUP) {
	    /* body never up at all today */
	    f_string (row, C_RISETM, "Never up");
	    f_mtime (row, C_TUP, 0.0);
	    return;
	}

	if (status & RS_NORISE) {
	    /* object does not rise as such today */
	    f_string (row, C_RISETM, "Never rises");
	    ltr = 0.0; /* for TUP */
	    today_tup = 1;
	} else {
	    f_mtime (row, C_RISETM, ltr);
	    if (status & RS_2RISES) {
		/* object rises more than once today */
		f_char (row, C_RISETM+5, '+');
	    }
	    f_angle (row, C_RISEAZ, azr);
	}

	if (status & RS_NOTRANS)
	    f_string (row, C_TRANSTM, "No transit");
	else {
	    f_mtime (row, C_TRANSTM, ltt);
	    if (status & RS_2TRANS)
		f_char (row, C_TRANSTM+5, '+');
	    f_angle (row, C_TRANSALT, altt);
	}

	if (status & RS_NOSET) {
	    /* object does not set as such today */
	    f_string (row, C_SETTM, "Never sets");
	    lts = 24.0;	/* for TUP */
	    today_tup = 1;
	} else {
	    f_mtime (row, C_SETTM, lts);
	    if (status & RS_2SETS)
		f_char (row, C_SETTM+5, '+');
	    f_angle (row, C_SETAZ, azs);
	}

	tmp = lts - ltr;
	if (tmp < 0)
	    tmp = 24.0 + tmp;
	f_mtime (row, C_TUP, tmp);
	if (today_tup)
	    f_char (row, C_TUP+5, '+');
}

/* print body info in third menu format. this may be either the geocentric
 *   or topocentric angular separation between object p and each of the others.
 *   the latter, of course, includes effects of refraction and so can change
 *   quite rapidly near the time of each planets rise or set.
 * for now, we don't save old values so we always redo everything and ignore
 *  the "force" argument. this isn't that bad since body_cir() has memory and
 *   will avoid most computations as we hit them again in the lower triangle.
 */
/*ARGSUSED*/
static
alt3_body (p, force, np)
int p;		/* which body, as in astro.h/screen.h defines */
int force;	/* whether to print for sure or only if things have changed */
Now *np;
{
	int row = bodyrow[p];
	Sky skyp, skyq;
	double spy, cpy, px, *qx, *qy;
	int wantx = objx_ison();
	double as = plot_ison() || srch_ison() ? 0.0 : 60.0;
	int q;

	(void) body_cir (p, as, np, &skyp);
	if (alt3_geoc) {
	    /* use ra for "x", dec for "y". */
	    spy = sin (skyp.s_dec);
	    cpy = cos (skyp.s_dec);
	    px = skyp.s_ra;
	    qx = &skyq.s_ra;
	    qy = &skyq.s_dec;
	} else {
	    /* use azimuth for "x", altitude for "y". */
	    spy = sin (skyp.s_alt);
	    cpy = cos (skyp.s_alt);
	    px = skyp.s_az;
	    qx = &skyq.s_az;
	    qy = &skyq.s_alt;
	}
	if (p == OBJX)
	    pr_objxname();
	for (q = nxtbody(-1); q != -1; q = nxtbody(q))
	    if (q != p && (q != OBJX || wantx)) {
		double csep;
		(void) body_cir (q, as, np, &skyq);
		csep = spy*sin(*qy) + cpy*cos(*qy)*cos(px-*qx);
		f_angle (row, bodycol[q], acos(csep));
	    }
}

static
pr_objxname()
{
	char n[MAXOBJXNM+1];
	char buf[10];
	objx_get ((double *)0, (double *)0, (double *)0, n);
	sprintf (buf, "%-2.2s", n); /* set all spaces */
	f_string (R_OBJX, C_OBJ, buf);
}
xXx
echo x anomaly.c
cat > anomaly.c << 'xXx'
#include <stdio.h>
#include <math.h>
#include "astro.h"

#define	TWOPI	(2*PI)

/* given the mean anomaly, ma, and the eccentricity, s, of elliptical motion,
 * find the true anomaly, *nu, and the eccentric anomaly, *ea.
 * all angles in radians.
 */
anomaly (ma, s, nu, ea)
double ma, s;
double *nu, *ea;
{
	double m, dla, fea;

	m = ma-TWOPI*(int)(ma/TWOPI);
	fea = m;
	while (1) {
	    dla = fea-(s*sin(fea))-m;
	    if (fabs(dla)<1e-6)
		break;
	    dla /= 1-(s*cos(fea));
	    fea -= dla;
	}

	*nu = 2*atan(sqrt((1+s)/(1-s))*tan(fea/2));
	*ea = fea;
}
xXx
echo x cal_mjd.c
cat > cal_mjd.c << 'xXx'
#include <stdio.h>
#include <math.h>
#include "astro.h"

/* given a date in months, mn, days, dy, years, yr,
 * return the modified Julian date (number of days elapsed since 1900 jan 0.5),
 * *mjd.
 */
cal_mjd (mn, dy, yr, mjd)
int mn, yr;
double dy;
double *mjd;
{
	int b, d, m, y;
	long c;

	m = mn;
	y = (yr < 0) ? yr + 1 : yr;
	if (mn < 3) {
	    m += 12;
	    y -= 1;
	}

	if (yr < 1582 || yr == 1582 && (mn < 10 || mn == 10 && dy < 15)) 
	    b = 0;
	else {
	    int a;
	    a = y/100;
	    b = 2 - a + a/4;
	}

	if (y < 0)
	    c = (long)((365.25*y) - 0.75) - 694025L;
	else
	    c = (long)(365.25*y) - 694025L;

	d = 30.6001*(m+1);

	*mjd = b + c + d + dy - 0.5;
}

/* given the modified Julian date (number of days elapsed since 1900 jan 0.5,),
 * mjd, return the calendar date in months, *mn, days, *dy, and years, *yr.
 */
mjd_cal (mjd, mn, dy, yr)
double mjd;
int *mn, *yr;
double *dy;
{
	double d, f;
	double i, a, b, ce, g;

	d = mjd + 0.5;
	i = floor(d);
	f = d-i;
	if (f == 1) {
	    f = 0;
	    i += 1;
	}

	if (i > -115860.0) {
	    a = floor((i/36524.25)+.9983573)+14;
	    i += 1 + a - floor(a/4.0);
	}

	b = floor((i/365.25)+.802601);
	ce = i - floor((365.25*b)+.750001)+416;
	g = floor(ce/30.6001);
	*mn = g - 1;
	*dy = ce - floor(30.6001*g)+f;
	*yr = b + 1899;

	if (g > 13.5)
	    *mn = g - 13;
	if (*mn < 2.5)
	    *yr = b + 1900;
	if (*yr < 1)
	    *yr -= 1;
}

/* given an mjd, set *dow to 0..6 according to which dayof the week it falls
 * on (0=sunday) or set it to -1 if can't figure it out.
 */
mjd_dow (mjd, dow)
double mjd;
int *dow;
{
	/* cal_mjd() uses Gregorian dates on or after Oct 15, 1582.
	 * (Pope Gregory XIII dropped 10 days, Oct 5..14, and improved the leap-
	 * year algorithm). however, Great Britian and the colonies did not
	 * adopt it until Sept 14, 1752 (they dropped 11 days, Sept 3-13,
	 * due to additional accumulated error). leap years before 1752 thus
	 * can not easily be accounted for from the cal_mjd() number...
	 */
	if (mjd < -53798.5) {
	    /* pre sept 14, 1752 too hard to correct */
	    *dow = -1;
	    return;
	}
	*dow = ((int)floor(mjd-.5) + 1) % 7; /* 1/1/1900 (mjd 0.5) is a Monday*/
	if (*dow < 0)
	    *dow += 7;
}

/* given a mjd, return the the number of days in the month.  */
mjd_dpm (mjd, ndays)
double mjd;
int *ndays;
{
	static short dpm[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	int m, y;
	double d;

	mjd_cal (mjd, &m, &d, &y);
	*ndays = (m==2 && ((y%4==0 && y%100!=0)||y%400==0)) ? 29 : dpm[m-1];
}


/* given a mjd, return the year as a double. */
mjd_year (mjd, yr)
double mjd;
double *yr;
{
	int m, y;
	double d;
	double e0, e1;	/* mjd of start of this year, start of next year */

	mjd_cal (mjd, &m, &d, &y);
	cal_mjd (1, 1.0, y, &e0);
	cal_mjd (12, 31.0, y, &e1); e1 += 1.0;
	*yr = y + (mjd - e0)/(e1 - e0);
}
xXx
echo x circum.c
cat > circum.c << 'xXx'
/* fill in a Sky struct with all we know about each object.
 *(object-x is in objx.c)
 */

#include <stdio.h>
#include <math.h>
#include "astro.h"
#include "circum.h"
#include "screen.h"	/* just for SUN and MOON */

/* find body p's circumstances now.
 * to save some time the caller may specify a desired accuracy, in arc seconds.
 * if, based on its mean motion, it would not have moved this much since the
 * last time we were called we only recompute altitude and azimuth and avoid
 * recomputing the planet's heliocentric position. use 0.0 for best possible.
 * return 0 if only alt/az changes, else 1 if all other stuff updated too.
 * TODO: beware of fast retrograde motions.
 */
body_cir (p, as, np, sp)
int p;
double as;
Now *np;
Sky *sp;
{
	typedef struct {
	    double l_dpas;	/* mean days per arc second */
	    Now l_now;		/* when l_sky was found */
	    Sky l_sky;
	} Last;
	/* must be in same order as the astro.h #define's */
	static Last last[8] =
	    {{.000068},{.00017},{.00053},{.0034},{.0092},{.027},{.046},{.069}};
	double lst, alt, az;
	Last *lp;
	int new;

	switch (p) {
	case SUN:
	    return (sun_cir (as, np, sp));
	case MOON:
	    return (moon_cir (as, np, sp));
	case OBJX:
	    return (objx_cir (as, np, sp));
	}

	/* if less than l_every days from last time for this planet
	 * just redo alt/az.
	 */
	lp = last + p;
	if (same_cir(np,&lp->l_now) && about_now(np,&lp->l_now,as*lp->l_dpas)) {
	    *sp = lp->l_sky;
	    new = 0;
	} else {
	    double lpd0, psi0; /* heliocentric ecliptic longitude and latitude */
	    double rp0;		/* dist from sun */
	    double rho0;	/* dist from earth */
	    double lam, bet;	/* geocentric ecliptic long and lat */
	    double dia, mag;	/* angular diameter at 1 AU and magnitude */
	    double lsn, rsn;	/* true geoc lng of sun, dist from sn to earth*/
	    double deps, dpsi;
	    double a, ca, sa;
	    double el;	/* elongation */
	    double f;	/* phase from earth */

	    lp->l_now = *np;
	    plans (mjd, p, &lpd0, &psi0, &rp0, &rho0, &lam, &bet, &dia, &mag);
	    nutation (mjd, &deps, &dpsi); /* correct for nutation */
	    lam += dpsi;
	    sunpos (mjd, &lsn, &rsn);
	    /* correct for 20.4" aberration */
	    a = lsn-lam;
	    ca = cos(a);
	    sa = sin(a);
	    lam -= degrad(20.4/3600)*ca/cos(bet);
	    bet -= degrad(20.4/3600)*sa*sin(bet);

	    ecl_eq (mjd, bet, lam, &sp->s_ra, &sp->s_dec);
	    if (epoch != EOD)
		precess (mjd, epoch, &sp->s_ra, &sp->s_dec);
	    sp->s_edist = rho0;
	    sp->s_sdist = rp0;
	    elongation (lam, bet, lsn, &el);
	    el = raddeg(el);
	    sp->s_elong = el;
	    sp->s_size = dia/rho0;
	    f = 0.5*(1+cos(lam-lpd0));
	    sp->s_phase = f*100.0; /* percent */
	    sp->s_mag = 5.0*log(rp0*rho0/sqrt(f))/log(10.0) + mag;
	    sp->s_hlong = lpd0;
	    sp->s_hlat = psi0;
	    new = 1;
	}

	/* alt, az; correct for refraction, in place */
	now_lst (np, &lst);
	hadec_aa (lat, hrrad(lst) - sp->s_ra, sp->s_dec, &alt, &az);
	refract (pressure, temp, alt, &alt);
	sp->s_alt = alt;
	sp->s_az = az;
	lp->l_sky = *sp;
	return (new);
}

/* find local times when sun is 18 degrees below horizon.
 * return 0 if just returned same stuff as previous call, else 1 if new.
 */
twilight_cir (np, dawn, dusk, status)
Now *np;
double *dawn, *dusk;
int *status;
{
	static Now last_now;
	static double last_dawn, last_dusk;
	static int last_status;
	int new;

	if (same_cir (np, &last_now) && same_lday (np, &last_now)) {
	    *dawn = last_dawn;
	    *dusk = last_dusk;
	    *status = last_status;
	    new = 0;
	} else {
	    double x;
	    (void) riset_cir (SUN,np,TWILIGHT,dawn,dusk,&x,&x,&x,&x,status);
	    last_dawn = *dawn;
	    last_dusk = *dusk;
	    last_status = *status;
	    last_now = *np;
	    new = 1;
	}
	return (new);
}

/* find sun's circumstances now.
 * as is the desired accuracy, in arc seconds; use 0.0 for best possible.
 * return 0 if only alt/az changes, else 1 if all other stuff updated too.
 */
sun_cir (as, np, sp)
double as;
Now *np;
Sky *sp;
{
	static Sky last_sky;
	static Now last_now;
	double lst, alt, az;
	int new;

	if (same_cir (np, &last_now) && about_now (np, &last_now, as*.00028)) {
	    *sp = last_sky;
	    new = 0;
	} else {
	    double lsn, rsn;
	    double deps, dpsi;

	    last_now = *np;
	    sunpos (mjd, &lsn, &rsn);		/* sun's true ecliptic long
						 * and dist
						 */
	    nutation (mjd, &deps, &dpsi);	/* correct for nutation */
	    lsn += dpsi-degrad(20.4/3600);	/* and 20.4" aberration */

	    sp->s_edist = rsn;
	    sp->s_sdist = 0.0;
	    sp->s_elong = 0.0;
	    sp->s_size = raddeg(4.65242e-3/rsn)*3600*2;
	    sp->s_mag = -26.8;
	    sp->s_hlong = lsn-PI;	/* geo- to helio- centric */
	    range (&sp->s_hlong, 2*PI);
	    sp->s_hlat = 0.0;

	    ecl_eq (mjd, 0.0, lsn, &sp->s_ra, &sp->s_dec);
	    if (epoch != EOD)
		precess (mjd, epoch, &sp->s_ra, &sp->s_dec);
	    new = 1;
	}

	now_lst (np, &lst);
	hadec_aa (lat, hrrad(lst) - sp->s_ra, sp->s_dec, &alt, &az);
	refract (pressure, temp, alt, &alt);
	sp->s_alt = alt;
	sp->s_az = az;
	last_sky = *sp;
	return (new);
}

/* find moon's circumstances now.
 * as is the desired accuracy, in arc seconds; use 0.0 for best possible.
 * return 0 if only alt/az changes, else 1 if all other stuff updated too.
 */
moon_cir (as, np, sp)
double as;
Now *np;
Sky *sp;
{
	static Sky last_sky;
	static Now last_now;
	static double ehp;
	double lst, alt, az;
	double ha, dec;
	int new;

	if (same_cir (np, &last_now) && about_now (np, &last_now, as*.000021)) {
	    *sp = last_sky;
	    new = 0;
	} else {
	    double lam, bet;
	    double deps, dpsi;
	    double lsn, rsn;	/* sun long in rads, earth-sun dist in au */
	    double edistau;	/* earth-moon dist, in au */
	    double el;		/* elongation, rads east */

	    last_now = *np;
	    moon (mjd, &lam, &bet, &ehp);	/* moon's true ecliptic loc */
	    nutation (mjd, &deps, &dpsi);	/* correct for nutation */
	    lam += dpsi;
	    range (&lam, 2*PI);

	    sp->s_edist = 6378.14/sin(ehp);	/* earth-moon dist, want km */
	    sp->s_size = 3600*31.22512*sin(ehp);/* moon angular dia, seconds */

	    ecl_eq (mjd, bet, lam, &sp->s_ra, &sp->s_dec);
	    if (epoch != EOD)
		precess (mjd, epoch, &sp->s_ra, &sp->s_dec);

	    sunpos (mjd, &lsn, &rsn);
	    range (&lsn, 2*PI);
	    elongation (lam, bet, lsn, &el);

	    /* solve triangle of earth, sun, and elongation for moon-sun dist */
	    edistau = sp->s_edist/1.495979e8; /* km -> au */
	    sp->s_sdist =
		sqrt (edistau*edistau + rsn*rsn - 2.0*edistau*rsn*cos(el));

	    /* TODO: improve mag; this is based on a flat moon model. */
	    sp->s_mag = -12 + 2.5*(log10(PI) - log10(PI/2*(1+1.e-6-cos(el))));

	    sp->s_elong = raddeg(el);	/* want degrees */
	    sp->s_phase = fabs(el)/PI*100.0;	/* want non-negative % */
	    sp->s_hlong = sp->s_hlat = 0.0;
	    new = 1;
	}

	/* show topocentric alt/az by correcting ra/dec for parallax 
	 * as well as refraction.
	 */
	now_lst (np, &lst);
	ha = hrrad(lst) - sp->s_ra;
	ta_par (ha, sp->s_dec, lat, height, ehp, &ha, &dec);
	hadec_aa (lat, ha, dec, &alt, &az);
	refract (pressure, temp, alt, &alt);
	sp->s_alt = alt;
	sp->s_az = az;
	last_sky = *sp;
	return (new);
}

/* given geocentric ecliptic longitude and latitude, lam and bet, of some object
 * and the longitude of the sun, lsn, find the elongation, el. this is the
 * actual angular separation of the object from the sun, not just the difference
 * in the longitude. the sign, however, IS set simply as a test on longitude
 * such that el will be >0 for an evening object <0 for a morning object.
 * to understand the test for el sign, draw a graph with lam going from 0-2*PI
 *   down the vertical axis, lsn going from 0-2*PI across the hor axis. then
 *   define the diagonal regions bounded by the lines lam=lsn+PI, lam=lsn and
 *   lam=lsn-PI. the "morning" regions are any values to the lower left of the
 *   first line and bounded within the second pair of lines.
 * all angles in radians.
 */
elongation (lam, bet, lsn, el)
double lam, bet, lsn;
double *el;
{
	*el = acos(cos(bet)*cos(lam-lsn));
	if (lam>lsn+PI || lam>lsn-PI && lam<lsn) *el = - *el;
}

/* return whether the two Nows are for the same observing circumstances. */
same_cir (n1, n2)
register Now *n1, *n2;
{
	return (n1->n_lat == n2->n_lat
		&& n1->n_lng == n2->n_lng
		&& n1->n_temp == n2->n_temp
		&& n1->n_pressure == n2->n_pressure
		&& n1->n_height == n2->n_height
		&& n1->n_tz == n2->n_tz
		&& n1->n_epoch == n2->n_epoch);
}

/* return whether the two Nows are for the same LOCAL day */
same_lday (n1, n2)
Now *n1, *n2;
{
	return (mjd_day(n1->n_mjd - n1->n_tz/24.0) ==
		mjd_day(n2->n_mjd - n2->n_tz/24.0)); 
}

/* return whether the mjd of the two Nows are within dt */
static
about_now (n1, n2, dt)
Now *n1, *n2;
double dt;
{
	return (fabs (n1->n_mjd - n2->n_mjd) <= dt/2.0);
}

now_lst (np, lst)
Now *np;
double *lst;
{
	utc_gst (mjd_day(mjd), mjd_hr(mjd), lst);
	*lst += radhr(lng);
	range (lst, 24.0);
}

/* round a time in days, *t, to the nearest second, IN PLACE. */
rnd_second (t)
double *t;
{
	*t = floor(*t*SPD+0.5)/SPD;
}
	
double mjd_day(jd)
double jd;
{
	return (floor(jd-0.5)+0.5);
}

double mjd_hr(jd)
double jd;
{
	return ((jd-mjd_day(jd))*24.0);
}
xXx
echo x compiler.c
cat > compiler.c << 'xXx'
/* module to compile and execute a c-style arithmetic expression.
 * public entry points are compile_expr() and execute_expr().
 *
 * one reason this is so nice and tight is that all opcodes are the same size
 * (an int) and the tokens the parser returns are directly usable as opcodes,
 * for the most part. constants and variables are compiled as an opcode
 * with an offset into the auxiliary opcode tape, opx.
 */

#include <math.h>
#include "screen.h"

/* parser tokens and opcodes, as necessary */
#define	HALT	0	/* good value for HALT since program is inited to 0 */
/* binary operators (precedences in table, below) */
#define	ADD	1
#define	SUB	2
#define	MULT	3
#define	DIV	4
#define	AND	5
#define	OR	6
#define	GT	7
#define	GE	8
#define	EQ	9
#define	NE	10
#define	LT	11
#define	LE	12
/* unary op, precedence in NEG_PREC #define, below */
#define	NEG	13
/* symantically operands, ie, constants, variables and all functions */
#define	CONST	14	
#define	VAR	15
#define	ABS	16	/* add functions if desired just like this is done */
/* purely tokens - never get compiled as such */
#define	LPAREN	255
#define	RPAREN	254
#define	ERR	(-1)

/* precedence of each of the binary operators.
 * in case of a tie, compiler associates left-to-right.
 * N.B. each entry's index must correspond to its #define!
 */
static int precedence[] = {0,5,5,6,6,2,1,4,4,3,3,4,4};
#define	NEG_PREC	7	/* negation is highest */

/* execute-time operand stack */
#define	MAX_STACK	16
static double stack[MAX_STACK], *sp;

/* space for compiled opcodes - the "program".
 * opcodes go in lower 8 bits.
 * when an opcode has an operand (as CONST and VAR) it is really in opx[] and
 *   the index is in the remaining upper bits.
 */
#define	MAX_PROG 32
static int program[MAX_PROG], *pc;
#define	OP_SHIFT	8
#define	OP_MASK		0xff

/* auxiliary operand info.
 * the operands (all but lower 8 bits) of CONST and VAR are really indeces
 * into this array. thus, no point in making this any longer than you have
 * bits more than 8 in your machine's int to index into it, ie, make
 *    MAX_OPX <= 1 << ((sizeof(int)-1)*8)
 * also, the fld's must refer to ones being flog'd, so not point in more
 * of these then that might be used for plotting and srching combined.
 */
#define	MAX_OPX	16
typedef union {
    double opu_f;		/* value when opcode is CONST */
    int opu_fld;		/* rcfpack() of field when opcode is VAR */
} OpX;
static OpX opx[MAX_OPX];
static int opxidx;

/* these are global just for easy/rapid access */
static int parens_nest;	/* to check that parens end up nested */
static char *err_msg;	/* caller provides storage; we point at it with this */
static char *cexpr, *lcexpr; /* pointers that move along caller's expression */
static int good_prog;	/* != 0 when program appears to be good */

/* compile the given c-style expression.
 * return 0 and set good_prog if ok,
 * else return -1 and a reason message in errbuf.
 */
compile_expr (ex, errbuf)
char *ex;
char *errbuf;
{
	int instr;

	/* init the globals.
	 * also delete any flogs used in the previous program.
	 */
	cexpr = ex;
	err_msg = errbuf;
	pc = program;
	opxidx = 0;
	parens_nest = 0;
	do {
	    instr = *pc++;
	    if ((instr & OP_MASK) == VAR)
		flog_delete (opx[instr >> OP_SHIFT].opu_fld);
	} while (instr != HALT);

	pc = program;
	if (compile(0) == ERR) {
	    sprintf (err_msg + strlen(err_msg), " at \"%.10s\"", lcexpr);
	    good_prog = 0;
	    return (-1);
	}
	*pc++ = HALT;
	good_prog = 1;
	return (0);
}

/* execute the expression previously compiled with compile_expr().
 * return 0 with *vp set to the answer if ok, else return -1 with a reason
 * why not message in errbuf.
 */
execute_expr (vp, errbuf)
double *vp;
char *errbuf;
{
	int s;

	err_msg = errbuf;
	sp = stack + MAX_STACK;	/* grows towards lower addresses */
	pc = program;
	s = execute(vp);
	if (s < 0)
	    good_prog = 0;
	return (s);
}

/* this is a way for the outside world to ask whether there is currently a
 * reasonable program compiled and able to execute.
 */
prog_isgood()
{
	return (good_prog);
}

/* get and return the opcode corresponding to the next token.
 * leave with lcexpr pointing at the new token, cexpr just after it.
 * also watch for mismatches parens and proper operator/operand alternation.
 */
static
next_token ()
{
	static char toomt[] = "More than %d terms";
	static char badop[] = "Illegal operator";
	int tok = ERR;	/* just something illegal */
	char c;

	while ((c = *cexpr) == ' ')
	    cexpr++;
	lcexpr = cexpr++;

	/* mainly check for a binary operator */
	switch (c) {
	case '\0': --cexpr; tok = HALT; break; /* keep returning HALT */
	case '+': tok = ADD; break; /* compiler knows when it's really unary */
	case '-': tok = SUB; break; /* compiler knows when it's really negate */
	case '*': tok = MULT; break;
	case '/': tok = DIV; break;
	case '(': parens_nest++; tok = LPAREN; break;
	case ')':
	    if (--parens_nest < 0) {
	        sprintf (err_msg, "Too many right parens");
		return (ERR);
	    } else
		tok = RPAREN;
	    break;
	case '|':
	    if (*cexpr == '|') { cexpr++; tok = OR; }
	    else { sprintf (err_msg, badop); return (ERR); }
	    break;
	case '&':
	    if (*cexpr == '&') { cexpr++; tok = AND; }
	    else { sprintf (err_msg, badop); return (ERR); }
	    break;
	case '=':
	    if (*cexpr == '=') { cexpr++; tok = EQ; }
	    else { sprintf (err_msg, badop); return (ERR); }
	    break;
	case '!':
	    if (*cexpr == '=') { cexpr++; tok = NE; }
	    else { sprintf (err_msg, badop); return (ERR); }
	    break;
	case '<':
	    if (*cexpr == '=') { cexpr++; tok = LE; }
	    else tok = LT;
	    break;
	case '>':
	    if (*cexpr == '=') { cexpr++; tok = GE; }
	    else tok = GT;
	    break;
	}

	if (tok != ERR)
	    return (tok);

	/* not op so check for a constant, variable or function */
	if (isdigit(c) || c == '.') {
	    if (opxidx > MAX_OPX) {
		sprintf (err_msg, toomt, MAX_OPX);
		return (ERR);
	    }
	    opx[opxidx].opu_f = atof (lcexpr);
	    tok = CONST | (opxidx++ << OP_SHIFT);
	    skip_double();
	} else if (isalpha(c)) {
	    /* check list of functions */
	    if (strncmp (lcexpr, "abs", 3) == 0) {
		cexpr += 2;
		tok = ABS;
	    } else {
		/* not a function, so assume it's a variable */
		int fld;
		if (opxidx > MAX_OPX) {
		    sprintf (err_msg, toomt, MAX_OPX);
		    return (ERR);
		}
		fld = parse_fieldname ();
		if (fld < 0) {
		    sprintf (err_msg, "Unknown field");
		    return (ERR);
		} else {
		    if (flog_add (fld) < 0) { /* register with field logger */
			sprintf (err_msg, "Sorry; too many fields");
			return (ERR);
		    }
		    opx[opxidx].opu_fld = fld;
		    tok = VAR | (opxidx++ << OP_SHIFT);
		}
	    }
	}

	return (tok);
}

/* move cexpr on past a double.
 * allow sci notation.
 * no need to worry about a leading '-' or '+' but allow them after an 'e'.
 * TODO: this handles all the desired cases, but also admits a bit too much
 *   such as things like 1eee2...3. geeze; to skip a double right you almost
 *   have to go ahead and crack it!
 */
static
skip_double()
{
	int sawe = 0;	/* so we can allow '-' or '+' right after an 'e' */

	while (1) {
	    char c = *cexpr;
	    if (isdigit(c) || c=='.' || (sawe && (c=='-' || c=='+'))) {
		sawe = 0;
		cexpr++;
	    } else if (c == 'e') {
		sawe = 1;
		cexpr++;
	    } else
		break;
	}
}

/* call this whenever you want to dig out the next (sub)expression.
 * keep compiling instructions as long as the operators are higher precedence
 * than prec, then return that "look-ahead" token that wasn't (higher prec).
 * if error, fill in a message in err_msg[] and return ERR.
 */
static
compile (prec)
int prec;
{
	int expect_binop = 0;	/* set after we have seen any operand.
				 * used by SUB so it can tell if it really 
				 * should be taken to be a NEG instead.
				 */
	int tok = next_token ();

        while (1) {
	    int p;
	    if (tok == ERR)
		return (ERR);
	    if (pc - program >= MAX_PROG) {
		sprintf (err_msg, "Program is too long");
		return (ERR);
	    }

	    /* check for special things like functions, constants and parens */
            switch (tok & OP_MASK) {
            case HALT: return (tok);
	    case ADD:
		if (expect_binop)
		    break;	/* procede with binary addition */
		/* just skip a unary positive(?) */
		tok = next_token();
		continue;
	    case SUB:
		if (expect_binop)
		    break;	/* procede with binary subtract */
		tok = compile (NEG_PREC);
		*pc++ = NEG;
		expect_binop = 1;
		continue;
            case ABS: /* other funcs would be handled the same too ... */
		/* eat up the function parenthesized argument */
		if (next_token() != LPAREN || compile (0) != RPAREN) {
		    sprintf (err_msg, "Function arglist error");
		    return (ERR);
		}
		/* then handled same as ... */
            case CONST: /* handled same as... */
	    case VAR:
		*pc++ = tok;
		tok = next_token();
		expect_binop = 1;
		continue;
            case LPAREN:
		if (compile (0) != RPAREN) {
		    sprintf (err_msg, "Unmatched left paren");
		    return (ERR);
		}
		tok = next_token();
		expect_binop = 1;
		continue;
            case RPAREN:
		return (RPAREN);
            }

	    /* everything else is a binary operator */
	    p = precedence[tok];
            if (p > prec) {
                int newtok = compile (p);
		if (newtok == ERR)
		    return (ERR);
                *pc++ = tok;
		expect_binop = 1;
                tok = newtok;
            } else
                return (tok);
        }
}

/* "run" the program[] compiled with compile().
 * if ok, return 0 and the final result,
 * else return -1 with a reason why not message in err_msg.
 */
static
execute(result)
double *result;
{
	int instr; 

	do {
	    instr = *pc++;
	    switch (instr & OP_MASK) {
	    /* put these in numberic order so hopefully even the dumbest
	     * compiler will choose to use a jump table, not a cascade of ifs.
	     */
	    case HALT: break;	/* outer loop will stop us */
	    case ADD:  sp[1] = sp[1] +  sp[0]; sp++; break;
	    case SUB:  sp[1] = sp[1] -  sp[0]; sp++; break;
	    case MULT: sp[1] = sp[1] *  sp[0]; sp++; break;
	    case DIV:  sp[1] = sp[1] /  sp[0]; sp++; break;
	    case AND:  sp[1] = sp[1] && sp[0] ? 1 : 0; sp++; break;
	    case OR:   sp[1] = sp[1] || sp[0] ? 1 : 0; sp++; break;
	    case GT:   sp[1] = sp[1] >  sp[0] ? 1 : 0; sp++; break;
	    case GE:   sp[1] = sp[1] >= sp[0] ? 1 : 0; sp++; break;
	    case EQ:   sp[1] = sp[1] == sp[0] ? 1 : 0; sp++; break;
	    case NE:   sp[1] = sp[1] != sp[0] ? 1 : 0; sp++; break;
	    case LT:   sp[1] = sp[1] <  sp[0] ? 1 : 0; sp++; break;
	    case LE:   sp[1] = sp[1] <= sp[0] ? 1 : 0; sp++; break;
	    case NEG:  *sp = -*sp; break;
	    case CONST: *--sp = opx[instr >> OP_SHIFT].opu_f; break;
	    case VAR:
		if (flog_get (opx[instr >> OP_SHIFT].opu_fld, --sp) < 0) {
		    sprintf (err_msg, "Bug! VAR field not logged");
		    return (-1);
		}
		break;
	    case ABS:  *sp = fabs (*sp); break;
	    default:
		sprintf (err_msg, "Bug! bad opcode: 0x%x", instr);
		return (-1);
	    }
	    if (sp < stack) {
		sprintf (err_msg, "Runtime stack overflow");
		return (-1);
	    } else if (sp - stack > MAX_STACK) {
		sprintf (err_msg, "Bug! runtime stack underflow");
		return (-1);
	    }
	} while (instr != HALT);

	/* result should now be on top of stack */
	if (sp != &stack[MAX_STACK - 1]) {
	    sprintf (err_msg, "Bug! stack has %d items",MAX_STACK-(sp-stack));
	    return (-1);
	}
	*result = *sp;
	return (0);
}

static
isdigit(c)
char c;
{
	return (c >= '0' && c <= '9');
}

static
isalpha (c)
char c;
{
	return ((c >= 'a' && c <= 'z') || (c >=  'A' && c <= 'Z'));
}

/* starting with lcexpr pointing at a string expected to be a field name,
 * return an rcfpack(r,c,0) of the field else -1 if bad.
 * when return, leave lcexpr alone but move cexpr to just after the name.
 */
static
parse_fieldname ()
{
	int r = -1, c = -1; 	/* anything illegal */
	char *fn = lcexpr;	/* likely faster than using the global */
	char f0, f1;
	char *dp;

	/* search for first thing not an alpha char.
	 * leave it in f0 and leave dp pointing to it.
	 */
	dp = fn;
	while (isalpha(f0 = *dp))
	    dp++;

	/* crack the new field name.
	 * when done trying, leave dp pointing at first char just after it.
	 * set r and c if we recognized it.
	 */
	if (f0 == '.') {
	    /* planet.column pair.
	     * first crack the planet portion (pointed to by fn): set r.
	     * then the second portion (pointed to by dp+1): set c.
	     */
	    char xn[MAXOBJXNM+1];

	    f0 = fn[0];
	    f1 = fn[1];

	    /* if there is an object-x we insist on a perfect match */
	    objx_get ((double *)0, (double *)0, (double *)0, xn);
	    if (xn[0] && f0 == xn[0] && (!xn[1] || f1 == xn[1]))
		r = R_OBJX;
	    else
		switch (f0) {
		case 'j':
					r = R_JUPITER;
		    break;
		case 'm':
		    if (f1 == 'a')      r = R_MARS;
		    else if (f1 == 'e') r = R_MERCURY;
		    else if (f1 == 'o') r = R_MOON;
		    break;
		case 'n':
					r = R_NEPTUNE;
		    break;
		case 'p':
					r = R_PLUTO;
		    break;
		case 's':
		    if (f1 == 'a')      r = R_SATURN;
		    else if (f1 == 'u') r = R_SUN;
		    break;
		case 'u':
					r = R_URANUS;
		    break;
		case 'v':
					r = R_VENUS;
		    break;
		}

	    /* now crack the column (stuff after the dp) */
	    dp++;	/* point at good stuff just after the decimal pt */
	    f0 = dp[0];
	    f1 = dp[1];
	    switch (f0) {
	    case 'a':
		if (f1 == 'l')        c = C_ALT;
		else if (f1 == 'z')   c = C_AZ;
		break;
	    case 'd':
				      c = C_DEC;
		break;
	    case 'e':
		if (f1 == 'd')        c = C_EDIST;
		else if (f1 == 'l')   c = C_ELONG;
		break;
	    case 'h':
		if (f1 == 'l') {
		    if (dp[2] == 'a')              c = C_HLAT;
		    else if (dp[2] == 'o')         c = C_HLONG;
		} else if (f1 == 'r' || f1 == 'u') c = C_TUP;
		break;
	    case 'j':
				      c = C_JUPITER;
		break;
	    case 'm':
		if (f1 == 'a')        c = C_MARS;
		else if (f1 == 'e')   c = C_MERCURY;
		else if (f1 == 'o')   c = C_MOON;
		break;
	    case 'n':
				      c = C_NEPTUNE;
		break;
	    case 'p':
		if (f1 == 'h')        c = C_PHASE;
		else if (f1 == 'l')   c = C_PLUTO;
		break;
	    case 'r':
		if (f1 == 'a') {
		    if (dp[2] == 'z') c = C_RISEAZ;
		    else 	      c = C_RA;
		} else if (f1 == 't') c = C_RISETM;
		break;
	    case 's':
		if (f1 == 'a') {
		    if (dp[2] == 'z') c = C_SETAZ;
		    else	      c = C_SATURN;
		} else if (f1 == 'd') c = C_SDIST;
		else if (f1 == 'i')   c = C_SIZE;
		else if (f1 == 't')   c = C_SETTM;
		else if (f1 == 'u')   c = C_SUN;
		break;
	    case 't':
		if (f1 == 'a')        c = C_TRANSALT;
		else if (f1 == 't')   c = C_TRANSTM;
		break;
	    case 'u':
				      c = C_URANUS;
		break;
	    case 'v':
		if (f1 == 'e')        c = C_VENUS;
		else if (f1 == 'm')   c = C_MAG;
		break;
	    }

	    /* now skip dp on past the column stuff */
	    while (isalpha(*dp))
		dp++;
	} else {
	    /* no decimal point; some field in the top of the screen */
	    f0 = fn[0];
	    f1 = fn[1];
	    switch (f0) {
	    case 'd':
		if (f1 == 'a')      r = R_DAWN, c = C_DAWN;
		else if (f1 == 'u') r = R_DUSK, c = C_DUSK;
		break;
	    case 'n':
		r = R_LON, c = C_LON;
		break;
	    }
	}

	cexpr = dp;
	if (r <= 0 || c <= 0) return (-1);
	return (rcfpack (r, c, 0));
}
xXx
echo x eq_ecl.c
cat > eq_ecl.c << 'xXx'
#include <stdio.h>
#include <math.h>
#include "astro.h"

#define	EQtoECL	1
#define	ECLtoEQ	(-1)

/* given the modified Julian date, mjd, and an equitorial ra and dec, each in
 * radians, find the corresponding geocentric ecliptic latitude, *lat, and
 * longititude, *lng, also each in radians.
 * the effects of nutation and the angle of the obliquity are included.
 */
eq_ecl (mjd, ra, dec, lat, lng)
double mjd, ra, dec;
double *lat, *lng;
{
	ecleq_aux (EQtoECL, mjd, ra, dec, lng, lat);
}

/* given the modified Julian date, mjd, and a geocentric ecliptic latitude,
 * *lat, and longititude, *lng, each in radians, find the corresponding
 * equitorial ra and dec, also each in radians.
 * the effects of nutation and the angle of the obliquity are included.
 */
ecl_eq (mjd, lat, lng, ra, dec)
double mjd, lat, lng;
double *ra, *dec;
{
	ecleq_aux (ECLtoEQ, mjd, lng, lat, ra, dec);
}

static
ecleq_aux (sw, mjd, x, y, p, q)
int sw;			/* +1 for eq to ecliptic, -1 for vv. */
double mjd, x, y;	/* sw==1: x==ra, y==dec.  sw==-1: x==lng, y==lat. */
double *p, *q;		/* sw==1: p==lng, q==lat. sw==-1: p==ra, q==dec. */
{
	static double lastmjd;		/* last mjd calculated */
	static double seps, ceps;	/* sin and cos of mean obliquity */
	double sx, cx, sy, cy, ty;

	if (mjd != lastmjd) {
	    double deps, dpsi, eps;
	    obliquity (mjd, &eps);		/* mean obliquity for date */
#ifdef NONUTATION
#define NONUTATION
	    deps = 0;				/* checkout handler did not
						 * include nutation correction.
						 */
#else
	    nutation (mjd, &deps, &dpsi);	/* correctin due to nutation */
#endif
	    eps += deps;			/* true obliquity for date */
    	    seps = sin(eps);
	    ceps = cos(eps);
	    lastmjd = mjd;
	}

	sy = sin(y);
	cy = cos(y);				/* always non-negative */
        if (fabs(cy)<1e-20) cy = 1e-20;		/* insure > 0 */
        ty = sy/cy;
	cx = cos(x);
	sx = sin(x);
        *q = asin((sy*ceps)-(cy*seps*sx*sw));
        *p = atan(((sx*ceps)+(ty*seps*sw))/cx);
        if (cx<0) *p += PI;		/* account for atan quad ambiguity */
	range (p, 2*PI);
}
xXx
echo x flog.c
cat > flog.c << 'xXx'
/* this is a simple little package to manage the saving and retrieving of
 * field values, which we call field logging or "flogs". a flog consists of a
 * field location, ala rcfpack(), and its value as a double. you can reset the
 * list of flogs, add to and remove from the list of registered fields and log
 * a field if it has been registered.
 *
 * this is used by the plotting and searching facilities of ephem to maintain
 * the values of the fields that are being plotted or used in search
 * expressions.
 *
 * a field can be in use for more than one
 * thing at a time (eg, all the X plot values may the same time field, or
 * searching and plotting might be on at one time using the same field) so
 * we consider the field to be in use as long a usage count is > 0.
 */

#include "screen.h"

#define	NFLOGS	32

typedef struct {
	int fl_usagecnt;	/* number of "users" logging to this field */
	int fl_fld;		/* an rcfpack(r,c,0) */
	double fl_val;
} FLog;

static FLog flog[NFLOGS];

/* add fld to the list. if already there, just increment usage count.
 * return 0 if ok, else -1 if no more room.
 */
flog_add (fld)
int fld;
{
	FLog *flp, *unusedflp = 0;

	/* scan for fld already in list, or find an unused one along the way */
	for (flp = &flog[NFLOGS]; --flp >= flog; ) {
	    if (flp->fl_usagecnt > 0) {
		if (flp->fl_fld == fld) {
		    flp->fl_usagecnt++;
		    return (0);
		}
	    } else
		unusedflp = flp;
	}
	if (unusedflp) {
	    unusedflp->fl_fld = fld;
	    unusedflp->fl_usagecnt = 1;
	    return (0);
	}
	return (-1);
}

/* decrement usage count for flog for fld. if goes to 0 take it out of list.
 * ok if not in list i guess...
 */
flog_delete (fld)
int fld;
{
	FLog *flp;

	for (flp = &flog[NFLOGS]; --flp >= flog; )
	    if (flp->fl_fld == fld && flp->fl_usagecnt > 0) {
		if (--flp->fl_usagecnt <= 0) {
		    flp->fl_usagecnt = 0;
		}
		break;
	    }
}

/* if plotting or searching is active then
 * if rcfpack(r,c,0) is in the fld list, set its value to val.
 * return 0 if ok, else -1 if not in list.
 */
flog_log (r, c, val)
int r, c;
double val;
{
	if (plot_ison() || srch_ison()) {
	    FLog *flp;
	    int fld = rcfpack (r, c, 0);
	    for (flp = &flog[NFLOGS]; --flp >= flog; )
		if (flp->fl_fld == fld && flp->fl_usagecnt > 0) {
		    flp->fl_val = val;
		    return(0);
		}
	    return (-1);
	} else
	    return (0);
}

/* search for fld in list. if find it return its value.
 * return 0 if found it, else -1 if not in list.
 */
flog_get (fld, vp)
int fld;
double *vp;
{
	FLog *flp;

	for (flp = &flog[NFLOGS]; --flp >= flog; )
	    if (flp->fl_fld == fld && flp->fl_usagecnt > 0) {
		*vp = flp->fl_val;
		return (0);
	    }
	return (-1);
}
xXx
echo x formats.c
cat > formats.c << 'xXx'
/* basic formating routines.
 * all the screen oriented printing should go through here.
 */

#include <stdio.h>
#include <math.h>
#include "astro.h"
#include "screen.h"

/* suppress screen io if this is true, but always flog stuff.
 */
static int f_scrnoff;
f_on ()
{
	f_scrnoff = 0;
}
f_off ()
{
	f_scrnoff = 1;
}

/* draw n blanks at the given cursor position.  */
f_blanks (r, c, n)
int r, c, n;
{
	if (f_scrnoff)
	    return;
	c_pos (r, c);
	while (--n >= 0)
	    putchar (' ');
}

/* print the given value, v, in "sexadecimal" format at [r,c]
 * ie, in the form A:m.P, where A is a digits wide, P is p digits.
 * if p == 0, then no decimal point either.
 */
f_sexad (r, c, a, p, mod, v)
int r, c;
int a, p;	/* left space, min precision */
int mod;	/* don't let whole portion get this big */
double v;
{
	char astr[32], str[32];
	int dec;
	double frac;
	int visneg;

	(void) flog_log (r, c, v);

	if (f_scrnoff)
	    return;

	if (v >= 0.0)
	    visneg = 0;
	else {
	    visneg = 1;
	    v = -v;
	}

	dec = v;
	frac = (v - dec)*60.0;
	sprintf (str, "59.%.*s5", p, "999999999");
	if (frac >= atof (str)) {
	    dec += 1;
	    frac = 0.0;
	}
	dec %= mod;
	if (dec == 0 && visneg)
	    strcpy (str, "-0");
	else
	    sprintf (str, "%d", visneg ? -dec : dec);

	/* would just do this if Turbo-C 2.0 %?.0f" worked:
	 * sprintf (astr, "%*s:%0*.*f", a, str, p == 0 ? 2 : p+3, p, frac);
	 */
	if (p == 0)
	    sprintf (astr, "%*s:%02d", a, str, (int)(frac+0.5));
	else
	    sprintf (astr, "%*s:%0*.*f", a, str, p+3, p, frac);
	f_string (r, c, astr);
}

/* print the given value, t, in sexagesimal format at [r,c]
 * ie, in the form T:mm:ss, where T is nd digits wide.
 * N.B. we assume nd >= 2.
 */
f_sexag (r, c, nd, t)
int r, c, nd;
double t;
{
	char tstr[32];
	int h, m, s;
	int tisneg;
	
	(void) flog_log (r, c, t);
	if (f_scrnoff)
	    return;
	dec_sex (t, &h, &m, &s, &tisneg);
	if (h == 0 && tisneg)
	    sprintf (tstr, "%*s-0:%02d:%02d", nd-2, "", m, s);
	else
	    sprintf (tstr, "%*d:%02d:%02d", nd, tisneg ? -h : h, m, s);
	f_string (r, c, tstr);
}

/* print angle ra, in radians, in ra hours as hh:mm.m at [r,c]
 * N.B. we assume ra is >= 0.
 */
f_ra (r, c, ra)
int r, c;
double ra;
{
	f_sexad (r, c, 2, 1, 24, radhr(ra));
}

/* print time, t, as hh:mm:ss */
f_time (r, c, t)
int r, c;
double t;
{
	f_sexag (r, c, 2, t);
}

/* print time, t, as +/-hh:mm:ss (don't show leading +) */
f_signtime (r, c, t)
int r, c;
double t;
{
	f_sexag (r, c, 3, t);
}

/* print time, t, as hh:mm */
f_mtime (r, c, t)
int r, c;
double t;
{
	f_sexad (r, c, 2, 0, 24, t);
}

/* print angle, a, in rads, as degress at [r,c] in form ddd:mm */
f_angle(r, c, a)
int r, c;
double a;
{
	f_sexad (r, c, 3, 0, 360, raddeg(a));
}

/* print angle, a, in rads, as degress at [r,c] in form dddd:mm:ss */
f_gangle(r, c, a)
int r, c;
double a;
{
	f_sexag (r, c, 4, raddeg(a));
}

/* print the given modified Julian date, jd, as the starting date at [r,c]
 * in the form mm/dd/yyyy.
 */
f_date (r, c, jd)
int r, c;
double jd;
{
	char dstr[32];
	int m, y;
	double d, tmp;

	/* shadow to the plot subsystem as years. */
	mjd_year (jd, &tmp);
	(void) flog_log (r, c, tmp);
	if (f_scrnoff)
	    return;

	mjd_cal (jd, &m, &d, &y);

	sprintf (dstr, "%2d/%02d/%04d", m, (int)(d), y);
	f_string (r, c, dstr);
}

f_char (row, col, c)
int row, col;
char c;
{
	if (f_scrnoff)
	    return;
	c_pos (row, col);
	putchar (c);
}

f_string (r, c, s)
int r, c;
char *s;
{
	if (f_scrnoff)
	    return;
	c_pos (r, c);
	fputs (s, stdout);
}

f_double (r, c, fmt, f)
int r, c;
char *fmt;
double f;
{
	char str[80];
	(void) flog_log (r, c, f);
	sprintf (str, fmt, f);
	f_string (r, c, str);
}

/* print prompt line */
f_prompt (p)
char *p;
{
	c_pos (R_PROMPT, C_PROMPT);
	c_eol ();
	c_pos (R_PROMPT, C_PROMPT);
	fputs (p, stdout);
}

/* print a message and wait for op to hit any key */
f_msg (m)
char *m;
{
	f_prompt (m);
	(void) read_char();
}

/* crack a line of the form X?X?X into its components,
 *   where X is an integer and ? can be any character except '0-9' or '-',
 *   such as ':' or '/'.
 * only change those fields that are specified:
 *   eg:  ::10	only changes *s
 *        10    only changes *d
 *        10:0  changes *d and *m
 * if see '-' anywhere, first non-zero component will be made negative.
 */
f_sscansex (bp, d, m, s)
char *bp;
int *d, *m, *s;
{
	char c;
	int *p = d;
	int *nonzp = 0;
	int sawneg = 0;
	int innum = 0;

	while (c = *bp++)
	    if (c >= '0' && c <= '9') {
		if (!innum) {
		    *p = 0;
		    innum = 1;
		}
		*p = *p*10 + (c - '0');
		if (*p && !nonzp)
		    nonzp = p;
	    } else if (c == '-') {
		sawneg = 1;
	    } else if (c != ' ') {
		/* advance to next component */
		p = (p == d) ? m : s;
		innum = 0;
	    }

	if (sawneg && nonzp)
	    *nonzp = -*nonzp;
}

/* just like dec_sex() but makes the first non-zero element negative if
 * x is negative (instead of returning a sign flag).
 */
f_dec_sexsign (x, h, m, s)
double x;
int *h, *m, *s;
{
	int n;
	dec_sex (x, h, m, s, &n);
	if (n) {
	    if (*h)
		*h = -*h;
	    else if (*m)
		*m = -*m;
	    else
		*s = -*s;
	}
}
xXx