/* code to manage specifying the local horizon. * * to build a standalone test program: * cc -DTEST_MAIN -I../../libastro -I../../libip -I/usr/X11R6/lib -L../../libastro -L../../libip -L/usr/X11R6/lib hznmenu.c -lXm -lXt -lX11 -lastro -lip -lm */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xephem.h" static double hzn_getdispl (void); static int hzn_rdmap (void); static void hzn_create (void); static void hzn_close_cb (Widget w, XtPointer client, XtPointer call); static void hzn_unmap_cb (Widget w, XtPointer client, XtPointer call); static void hzn_help_cb (Widget w, XtPointer client, XtPointer call); static void hzn_save_cb (Widget w, XtPointer client, XtPointer call); static void hzn_edit_cb (Widget w, XtPointer client, XtPointer call); static void hzn_chsfn_cb (Widget w, XtPointer client, XtPointer call); static void hzn_displtb_cb (Widget w, XtPointer client, XtPointer call); static void hzn_displtf_cb (Widget w, XtPointer client, XtPointer call); static void hzn_filetb_cb (Widget w, XtPointer client, XtPointer call); static void hzn_filetf_cb (Widget w, XtPointer client, XtPointer call); static void hzn_radio (int choose_displ); static void hzn_choose (int choose_displ); static void smoothProfile(void); static void buildCon(void); static int azcmp_f (const void *v1, const void *v2); static void hzn_read (FILE *fp); static void hzn_write (FILE *fp); static Widget hznshell_w; /* the main shell */ static Widget displtb_w; /* fixed displacement TB */ static Widget displtf_w; /* fixed displacement TF */ static Widget filetb_w; /* file name TB */ static Widget filetf_w; /* file name TF */ static Widget edittb_w; /* TB whether editing a new profile */ #define PSTEP degrad(1) /* max alt or az step in profile, rads */ #define FLDW 20 /* text fields width */ typedef struct { double z, a; /* az/alt profile, rads E of N and up */ } Profile; static Profile *profile; /* malloced Profile[nprofile] sorted by inc az*/ static int nprofile; /* entries in profile[] */ static int want_smoothed = 1; /* whether want smoothed profile[] */ static int is_smoothed; /* whether profile[] is smoothed now */ #define SZP sizeof(Profile) /* handy */ static char hzncategory[] = "Horizon Map"; /* Save category */ #if !defined (TEST_MAIN) void hzn_manage() { if (!hznshell_w) hzn_create(); XtManageChild (hznshell_w); } void hzn_unmanage() { if (hznshell_w) XtUnmanageChild (hznshell_w); } int hznDrawing() { if (!hznshell_w) hzn_create(); return (XmToggleButtonGetState (edittb_w)); } /* call to turn off editing */ void hznEditingOff() { if (XmToggleButtonGetState (edittb_w)) XmToggleButtonSetState (edittb_w, False, True); } /* called to put up or remove the watch cursor. */ void hzn_cursor (c) Cursor c; { Window win; if (hznshell_w && (win = XtWindow(hznshell_w)) != 0) { Display *dsp = XtDisplay(hznshell_w); if (c) XDefineCursor (dsp, win, c); else XUndefineCursor (dsp, win); } } /* return number of profile entries */ int hznNProfile() { if (!hznshell_w) hzn_create(); if (want_smoothed && !is_smoothed) smoothProfile(); return (nprofile); } /* whether to smooth profiles */ void hznRawProfile (int on) { want_smoothed = !on; } /* given an index into the profile return the alt and az */ void hznProfile (int i, double *altp, double *azp) { if (!hznshell_w) hzn_create(); if (i < 0 || i >= nprofile) { printf ("Bogus hznProfile %d\n", i); abort(); } *altp = profile[i].a; *azp = profile[i].z; } /* given an az, return the horizon altitude, both in rads. */ double hznAlt(az) double az; { Profile *pb, *pt; double daz; int t, b; if (!hznshell_w) hzn_create(); if (want_smoothed && !is_smoothed) smoothProfile(); /* binary bracket */ range (&az, 2*PI); t = nprofile - 1; b = 0; while (b < t-1) { int m = (t+b)/2; double maz = profile[m].z; if (az < maz) t = m; else b = m; } pt = &profile[t]; pb = &profile[b]; daz = pt->z - pb->z; if (b == t || daz == 0) return (pb->a); return (pb->a + (az - pb->z)*(pt->a - pb->a)/daz); } #endif /* !TEST_MAIN */ /* add alt/az to profile[] * we remove all profile entries from this az to previous az. * call with start=1 to initialize "previous" position. */ void hznAdd (int start, double newalt, double newaz) { static int prev; /* index of previous insertion */ /* first time pick closest smaller entry as prev */ if (start) { for (prev = 0; prev < nprofile-1; prev++) if (profile[prev+1].z >= newaz) break; } /* break in two when crossing through north */ if (profile[prev].z > newaz + PI) { double dz0 = 2*PI-profile[prev].z; double dz = dz0 + newaz; double alt0 = profile[prev].a + dz0*(newalt-profile[prev].a)/dz; hznAdd (0, alt0, 2*PI); prev = 0; profile[prev].a = alt0; profile[prev].z = 0; hznAdd (0, newalt, newaz); return; } else if (newaz > profile[prev].z + PI) { double dz0 = 0-profile[prev].z; double dz = dz0 - (2*PI - newaz); double alt0 = profile[prev].a + dz0*(newalt-profile[prev].a)/dz; hznAdd (0, alt0, 0.0); prev = nprofile - 1; profile[prev].a = alt0; profile[prev].z = 2*PI; hznAdd (0, newalt, newaz); return; } /* remove portion from (prev .. new] */ /* TODO: just compute the indeces and do one memmove() */ if (profile[prev].z < newaz) { for (++prev; prev < nprofile && profile[prev].z <= newaz; ) memmove (profile+prev, profile+prev+1, (--nprofile-prev)*SZP); } else if (profile[prev].z > newaz) { for (--prev; prev >= 0 && profile[prev].z >= newaz; prev--) memmove (profile+prev, profile+prev+1, (--nprofile-prev)*SZP); prev++; } else { /* profile[prev].z == newaz */ profile[prev].a = newalt; return; } /* make room for and insert new entry at prev */ profile = (Profile *) XtRealloc ((char *) profile, (nprofile+1)*SZP); memmove (profile+prev+1, profile+prev, (nprofile++ -prev)*SZP); profile[prev].a = newalt; profile[prev].z = newaz; /* no longer smoothed */ is_smoothed = 0; } #if !defined (TEST_MAIN) static void hzn_create() { Widget w, sep_w, om_w; Arg args[20]; char *s[1]; int n; /* create main form */ n = 0; XtSetArg (args[n], XmNautoUnmanage, False); n++; XtSetArg (args[n], XmNcolormap, xe_cm); n++; XtSetArg (args[n], XmNmarginHeight, 10); n++; XtSetArg (args[n], XmNmarginWidth, 10); n++; XtSetArg (args[n], XmNverticalSpacing, 10); n++; XtSetArg (args[n], XmNhorizontalSpacing, 4); n++; XtSetArg (args[n], XmNdefaultPosition, False); n++; hznshell_w = XmCreateFormDialog (svshell_w, "Horizon", args, n); set_something (hznshell_w, XmNcolormap, (XtArgVal)xe_cm); set_xmstring (hznshell_w, XmNdialogTitle, "xephem Sky Horizon setup"); XtAddCallback (hznshell_w, XmNhelpCallback, hzn_help_cb, NULL); XtAddCallback (hznshell_w, XmNunmapCallback, hzn_unmap_cb, NULL); sr_reg (XtParent(hznshell_w), "XEphem*Horizon.x", hzncategory, 0); sr_reg (XtParent(hznshell_w), "XEphem*Horizon.y", hzncategory, 0); /* fixed */ n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, 5); n++; XtSetArg (args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++; XtSetArg (args[n], XmNindicatorType, XmONE_OF_MANY); n++; displtb_w = XmCreateToggleButton (hznshell_w, "UseDisplacement",args,n); XtAddCallback (displtb_w, XmNvalueChangedCallback, hzn_displtb_cb, 0); wtip (displtb_w, "Define horizon with one altitude at all azimuths"); XmToggleButtonGetState(displtb_w); sr_reg (displtb_w, NULL, hzncategory, 0); set_xmstring (displtb_w, XmNlabelString, " Constant ° "); XtManageChild (displtb_w); n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNleftWidget, displtb_w); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNcolumns, FLDW); n++; displtf_w = XmCreateTextField (hznshell_w, "Displacement", args, n); wtip (displtf_w, "Angle of Horizon cutoff above local horizontal"); XtAddCallback (displtf_w, XmNactivateCallback, hzn_displtf_cb, 0); sr_reg (displtf_w, NULL, hzncategory, 0); XtManageChild (displtf_w); /* file field */ n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, displtf_w); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, 5); n++; XtSetArg (args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++; XtSetArg (args[n], XmNindicatorType, XmONE_OF_MANY); n++; filetb_w = XmCreateToggleButton (hznshell_w, "UseMapFile", args, n); wtip (filetb_w, "Define horizon as a table of altitudes and azimuths"); XtAddCallback (filetb_w, XmNvalueChangedCallback, hzn_filetb_cb, 0); set_xmstring (filetb_w, XmNlabelString, " File name: "); XtManageChild (filetb_w); n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, displtf_w); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNleftWidget, filetb_w); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNcolumns, FLDW); n++; filetf_w = XmCreateTextField (hznshell_w, "MapFilename", args, n); wtip (filetf_w, "Name of file containing horizon table"); XtAddCallback (filetf_w, XmNactivateCallback, hzn_filetf_cb, NULL); sr_reg (filetf_w, NULL, hzncategory, 0); XtManageChild (filetf_w); /* perform the default */ hzn_choose (XmToggleButtonGetState(displtb_w)); /* browse list */ s[0] = ".hzn"; om_w = createFSM (hznshell_w, s, 1, "auxil", hzn_chsfn_cb); wtip (om_w, "Select a file containing a horizon profile"); n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, filetf_w); n++; XtSetArg (args[n], XmNtopOffset, 8); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNleftWidget, filetb_w); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetValues (om_w, args, n); /* save */ n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, filetf_w); n++; XtSetArg (args[n], XmNtopOffset, 10); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, 10); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_OPPOSITE_WIDGET); n++; XtSetArg (args[n], XmNrightWidget, filetb_w); n++; w = XmCreatePushButton (hznshell_w, "Save", args, n); XtAddCallback (w, XmNactivateCallback, hzn_save_cb, NULL); wtip (w, "Save current horizon drawing to the file"); XtManageChild (w); /* edit new */ n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, om_w); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, 5); n++; XtSetArg (args[n], XmNindicatorType, XmN_OF_MANY); n++; edittb_w = XmCreateToggleButton (hznshell_w, "Edit", args, n); set_xmstring (edittb_w, XmNlabelString, "Edit with mouse"); XtAddCallback (edittb_w, XmNvalueChangedCallback, hzn_edit_cb, NULL); wtip (edittb_w, "Define horizon as a table of altitudes and azimuths"); XtManageChild (edittb_w); /* controls at the bottom */ n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, edittb_w); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++; sep_w = XmCreateSeparator (hznshell_w, "HZS", args, n); XtManageChild (sep_w); n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, sep_w); n++; XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, 22); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNrightPosition, 38); n++; w = XmCreatePushButton (hznshell_w, "Close", args, n); XtAddCallback (w, XmNactivateCallback, hzn_close_cb, NULL); wtip (w, "Close this window"); XtManageChild (w); n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, sep_w); n++; XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, 62); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNrightPosition, 78); n++; w = XmCreatePushButton (hznshell_w, "Help", args, n); XtAddCallback (w, XmNactivateCallback, hzn_help_cb, NULL); wtip (w, "Get more information about this window"); XtManageChild (w); } /* called from Close */ /* ARGSUSED */ static void hzn_close_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { XtUnmanageChild (hznshell_w); } /* called when unmapped for any reason */ /* ARGSUSED */ static void hzn_unmap_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { hznEditingOff(); } /* called when edit TB changes */ /* ARGSUSED */ static void hzn_edit_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { if (XmToggleButtonGetState(w)) { sv_hznOn(); /* public service feature */ hzn_radio (0); } } /* called from Help */ /* ARGSUSED */ static void hzn_help_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { static char *msg[] = {"Specify local horizon."}; hlp_dialog ("Horizon", msg, sizeof(msg)/sizeof(msg[0])); } /* called from the Save PB and the Save file name TF. * N.B. don't use call */ /* ARGSUSED */ static void hzn_save_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { char buf[1024]; char *fn; FILE *fp; /* create file */ fn = XmTextFieldGetString (filetf_w); sprintf (buf, "%s/%s", getPrivateDir(), fn); fp = fopen (buf, "w"); if (!fp) { sprintf (buf+strlen(buf), ":\n%s", syserrstr()); XtFree (fn); return; } /* write profile */ hzn_write (fp); /* finished */ fclose (fp); hznEditingOff(); XtFree (fn); } /* Displacement TB callback */ /* ARGSUSED */ static void hzn_displtb_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { hzn_choose (XmToggleButtonGetState (w)); } /* Displacement TF callback */ /* ARGSUSED */ static void hzn_displtf_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { hzn_choose (1); } /* File TB callback */ /* ARGSUSED */ static void hzn_filetb_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { hzn_choose (!XmToggleButtonGetState (w)); } /* File TF callback */ /* ARGSUSED */ static void hzn_filetf_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { hzn_choose (0); } /* called when a file is chosen from the cascading list. * file name is w's label. */ /* ARGSUSED */ static void hzn_chsfn_cb (w, client, call) Widget w; XtPointer client; XtPointer call; { char *fn; /* get file name, load into filetf_w */ get_xmstring (w, XmNlabelString, &fn); XmTextFieldSetString (filetf_w, fn); XtFree (fn); /* display if possible */ hzn_choose (0); hznEditingOff(); sv_hznOn(); /* public service feature */ } /* get displacement string into displ */ static double hzn_getdispl() { double d; char *str = XmTextFieldGetString (displtf_w); d = degrad(strtod(str,NULL)); XtFree (str); return (d); } /* implement radio box behavior */ static void hzn_radio (int choose_displ) { XmToggleButtonSetState (displtb_w, choose_displ, False); XmToggleButtonSetState (filetb_w, !choose_displ, False); } /* called to make a choice. * control the Tbs as well as do the work. */ static void hzn_choose (choose_displ) int choose_displ; { /* do the work */ if (choose_displ) buildCon(); else { if (hzn_rdmap() < 0) { choose_displ = 1; /* revert back */ buildCon(); } } /* set radio choice */ hzn_radio (choose_displ); /* sky view update */ sv_update (mm_get_now(), 1); } /* open the horizon map named in filetf_w and create a profile[] smoothed by * increasing az. * we make sure the profile has complete coverage from 0..360 degrees. * return 0 if ok, else -1 */ static int hzn_rdmap() { char *fn = XmTextFieldGetString (filetf_w); FILE *fp; /* open file, try Private then Shared area */ fp = fopend (fn, "auxil", "r"); if (!fp) { XtFree (fn); return (-1); } /* read new profile */ if (profile) { XtFree ((char *)profile); profile = NULL; } nprofile = 0; hzn_read (fp); fclose (fp); if (nprofile == 0) { xe_msg (1, "%s:\nContains no horizon entries.\nReverting to constant displacement", fn); XtFree (fn); buildCon(); return (-1); } else { xe_msg (0, "%s:\nRead %d horizon entr%s.", fn, nprofile, nprofile == 1 ? "y" : "ies"); } /* if get here nprofile > 0. smooth and keep it that way */ smoothProfile(); /* done with filename */ XtFree (fn); return (0); } /* fill the profile with a constant elevation model */ static void buildCon() { double a = hzn_getdispl (); /* seed the profile then smooth */ profile = (Profile *) XtRealloc ((char *)profile, 1*SZP); profile[0].z = 0; profile[0].a = a; nprofile = 1; smoothProfile(); } #endif /* !TEST_MAIN */ /* make sure the profile has complete coverage from 0..360 degrees with * no steps in Alt or Az greater than PSTEP. */ static void smoothProfile() { Profile *newp; double alt0; int nnew, nmax; int i; if (!nprofile) return; /* insure sorted */ qsort ((void *)profile, nprofile, SZP, azcmp_f); /* seed with 0 crossing */ alt0 = profile[0].a; nmax = 100; newp = (Profile *) XtMalloc (nmax*SZP); newp[0].a = alt0; newp[0].z = 0; nnew = 1; /* make new copy, insuring complete PSTEP coverage */ for (i = 0; i <= nprofile; i++) { double la = newp[nnew-1].a; double lz = newp[nnew-1].z; double da = (i == nprofile ? alt0 : profile[i].a) - la; double dz = (i == nprofile ? 2*PI : profile[i].z) - lz; int na = (int)ceil(fabs(da)/PSTEP); int nz = (int)ceil(fabs(dz)/PSTEP); int j, n = na > nz ? na : nz; if (nnew + n > nmax) newp = (Profile *) XtRealloc((char*)newp,(nmax=nnew+n+100)*SZP); for (j = 1; j <= n; j++) { newp[nnew].a = la + j*da/n; newp[nnew].z = lz + j*dz/n; nnew++; } } /* newp is now the new profile */ XtFree ((char *)profile); newp = (Profile *) XtRealloc((char*)newp, nnew*SZP); profile = newp; nprofile = nnew; /* ok */ is_smoothed = 1; } /* compare two Profiles' az, in qsort fashion */ static int azcmp_f (const void *v1, const void *v2) { double diff = ((Profile *)v1)->z - ((Profile *)v2)->z; return (diff < 0 ? -1 : (diff > 0 ? 1 : 0)); } /* read the given file into profile[] */ static void hzn_read (FILE *fp) { char buf[1024]; /* read and store in profile[] */ while (fgets (buf, sizeof(buf), fp)) { double a, z; if (sscanf (buf, "%lf %lf", &z, &a) != 2) continue; z = degrad(z); a = degrad(a); radecrange (&z, &a); profile = (Profile *) XtRealloc ((char *) profile,(nprofile+1)*SZP); profile[nprofile].a = a; profile[nprofile].z = z; nprofile++; } } /* write profile to file fp */ static void hzn_write (FILE *fp) { int i; for (i = 0; i < nprofile; i++) fprintf (fp, "%8.4f %8.4f\n", raddeg(profile[i].z), raddeg(profile[i].a)); } #if defined (TEST_MAIN) int main (int ac, char *av[]) { hzn_read (stdin); hznAdd (0, degrad(70), degrad(20)); hznAdd (0, degrad(40), degrad(350)); hznAdd (0, degrad(60), degrad(300)); hznAdd (0, degrad(40), degrad(301)); hznAdd (0, degrad(20), degrad(302)); hznAdd (0, degrad(10), degrad(303)); hznAdd (0, degrad(50), degrad(304)); smoothProfile(); hzn_write (stdout); return (0); } #endif /* TEST_MAIN */ /* For RCS Only -- Do Not Edit */ static char *rcsid[2] = {(char *)rcsid, "@(#) $RCSfile: hznmenu.c,v $ $Date: 2004/05/10 23:36:34 $ $Revision: 1.21 $ $Name: $"};