12 /* convert to milliarcsec */
13 static long c = MILLIARCSEC; /* 1 degree */
14 static long m5 = 1250*60*60; /* 5 minutes of ra */
45 double mylat, mylon, mysid;
48 int (*projection)(struct place*, double*, double*);
50 char *fontname = "/lib/font/bit/lucida/unicode.6.font";
52 /* types Coord and Loc correspond to types in map(3) thus:
53 Coord == struct coord;
54 Loc == struct place, except longitudes are measured
55 positive east for Loc and west for struct place
58 typedef struct Xyz Xyz;
59 typedef struct coord Coord;
60 typedef struct Loc Loc;
70 Xyz north = { 0, 0, 1 };
77 display = initdisplay(nil, nil, drawerror);
79 fprint(2, "initdisplay failed: %r\n");
82 grey = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, 0x777777FF);
83 lightgrey = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, 0xAAAAAAFF);
84 alphagrey = allocimage(display, Rect(0, 0, 2, 2), RGBA32, 1, 0x77777777);
85 green = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, 0x00AA00FF);
86 lightblue = allocimage(display, Rect(0, 0, 1, 1), CMAP8, 1, 0x009EEEFF);
87 ocstipple = allocimage(display, Rect(0, 0, 2, 2), CMAP8, 1, 0xAAAAAAFF);
88 draw(ocstipple, Rect(0, 0, 1, 1), display->black, nil, ZP);
89 draw(ocstipple, Rect(1, 1, 2, 2), display->black, nil, ZP);
91 suncolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xFFFF77FF);
92 mooncolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xAAAAAAFF);
93 shadowcolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0x00000055);
94 mercurycolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xFFAAAAFF);
95 venuscolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xAAAAFFFF);
96 marscolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xFF5555FF);
97 jupitercolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xFFFFAAFF);
98 saturncolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xAAFFAAFF);
99 uranuscolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0x77DDDDFF);
100 neptunecolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0x77FF77FF);
101 plutocolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0x7777FFFF);
102 cometcolor = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xAAAAFFFF);
103 font = openfont(display, fontname);
105 fprint(2, "warning: no font %s: %r\n", fontname);
110 * Function heavens() for setting up observer-eye-view
111 * sky charts, plus subsidiary functions.
112 * Written by Doug McIlroy.
115 /* heavens(nlato, wlono, nlatc, wlonc) sets up a map(3)-style
116 coordinate change (by calling orient()) and returns a
117 projection function heavens for calculating a star map
118 centered on nlatc,wlonc and rotated so it will appear
119 upright as seen by a ground observer at nlato,wlono.
120 all coordinates (north latitude and west longitude)
125 mkcoord(double degrees)
129 c.l = degrees*PI/180;
141 v.x = p.nlat.c * p.wlon.c;
142 v.y = -p.nlat.c * p.wlon.s;
149 return a.x*b.x + a.y*b.y + a.z*b.z;
157 v.x = a.y*b.z - a.z*b.y;
158 v.y = a.z*b.x - a.x*b.z;
159 v.z = a.x*b.y - a.y*b.x;
166 return sqrt(dot(a, a));
169 /* An azimuth vector from a to b is a tangent to
170 the sphere at a pointing along the (shorter)
171 great-circle direction to b. It lies in the
172 plane of the vectors a and b, is perpendicular
173 to a, and has a positive compoent along b,
174 provided a and b span a 2-space. Otherwise
175 it is null. (aXb)Xa, where X denotes cross
176 product, is such a vector. */
181 return cross(cross(a,b), a);
184 /* Find the azimuth of point b as seen
185 from point a, given that a is distinct
186 from either pole and from b */
189 azimuth(Xyz a, Xyz b)
191 Xyz aton = azvec(a,north);
192 Xyz atob = azvec(a,b);
193 double lenaton = len(aton);
194 double lenatob = len(atob);
195 double az = acos(dot(aton,atob)/(lenaton*lenatob));
197 if(dot(b,cross(north,a)) < 0)
202 /* Find the rotation (3rd argument of orient() in the
203 map projection package) for the map described
204 below. The return is radians; it must be converted
205 to degrees for orient().
209 rot(struct place center, struct place zenith)
211 Xyz cen = ptov(center);
212 Xyz zen = ptov(zenith);
214 if(cen.z > 1-FUZZ) /* center at N pole */
215 return PI + zenith.wlon.l;
216 if(cen.z < FUZZ-1) /* at S pole */
217 return -zenith.wlon.l;
218 if(fabs(dot(cen,zen)) > 1-FUZZ) /* at zenith */
220 return azimuth(cen, zen);
224 heavens(double zlatdeg, double zlondeg, double clatdeg, double clondeg))(Loc*, double*, double*)
229 center.nlat = mkcoord(clatdeg);
230 center.wlon = mkcoord(clondeg);
231 zenith.nlat = mkcoord(zlatdeg);
232 zenith.wlon = mkcoord(zlondeg);
233 projection = stereographic();
234 orient(clatdeg, clondeg, rot(center, zenith)*180/PI);
239 maptoxy(long ra, long dec, double *x, double *y)
247 pl.nlat.s = sin(lat);
248 pl.nlat.c = cos(lat);
250 pl.wlon.s = sin(lon);
251 pl.wlon.c = cos(lon);
253 return projection(&pl, x, y);
256 /* end of 'heavens' section */
259 setmap(long ramin, long ramax, long decmin, long decmax, Rectangle r, int zenithup)
262 double minx, maxx, miny, maxy;
265 mapra = ramax/2+ramin/2;
266 mapdec = decmax/2+decmin/2;
269 projection = heavens(mylat, mysid, (double)mapdec/c, (double)mapra/c);
271 orient((double)mapdec/c, (double)mapra/c, 0.);
272 projection = stereographic();
274 mapx0 = (r.max.x+r.min.x)/2;
275 mapy0 = (r.max.y+r.min.y)/2;
276 maps = ((double)Dy(r))/(double)(decmax-decmin);
277 if(maptoxy(ramin, decmin, &minx, &miny) < 0)
279 if(maptoxy(ramax, decmax, &maxx, &maxy) < 0)
282 * It's tricky to get the scale right. This noble attempt scales
283 * to fit the lengths of the sides of the rectangle.
285 mapscale = Dx(r)/(minx-maxx);
286 if(mapscale > Dy(r)/(maxy-miny))
287 mapscale = Dy(r)/(maxy-miny);
289 * near the pole It's not a rectangle, though, so this scales
290 * to fit the corners of the trapezoid (a triangle at the pole).
292 mapscale *= (cos(angle(mapdec))+1.)/2;
294 /* upside down, between zenith and pole */
295 fprint(2, "reverse plot\n");
296 mapscale = -mapscale;
302 map(long ra, long dec)
307 if(maptoxy(ra, dec, &x, &y) > 0){
308 p.x = mapscale*x + mapx0;
309 p.y = mapscale*-y + mapy0;
318 dsize(int mag) /* mag is 10*magnitude; return disc size */
322 mag += 25; /* make mags all positive; sirius is -1.6m */
324 /* if plate scale is huge, adjust */
325 if(maps < 100.0/MILLIARCSEC)
327 if(maps < 50.0/MILLIARCSEC)
333 drawname(Image *scr, Image *col, char *s, int ra, int dec)
339 p = addpt(map(ra, dec), Pt(4, -1)); /* font has huge ascent */
340 string(scr, p, col, ZP, font, s);
348 diam = DEG(angle(diam)*MILLIARCSEC); /* diam in milliarcsec */
349 diam /= 2; /* radius */
350 /* convert to plate scale */
351 /* BUG: need +100 because we sometimes crash in library if we plot the exact center */
352 p0 = map(mapra+100, mapdec);
353 p1 = map(mapra+100+diam, mapdec);
354 return hypot(p0.x-p1.x, p0.y-p1.y);
358 drawdisc(Image *scr, DAngle semidiam, int ring, Image *color, Point pt, char *s)
362 d = npixels(semidiam*2);
365 fillellipse(scr, pt, d, d, color, ZP);
367 ellipse(scr, pt, 5*d/2, d/2, 0, color, ZP);
369 ellipse(scr, pt, d, d, 0, grey, ZP);
371 d = stringwidth(font, s);
373 pt.y -= font->height/2 + 1;
374 string(scr, pt, display->black, ZP, font, s);
379 drawplanet(Image *scr, Planetrec *p, Point pt)
381 if(strcmp(p->name, "sun") == 0){
382 drawdisc(scr, p->semidiam, 0, suncolor, pt, nil);
385 if(strcmp(p->name, "moon") == 0){
386 drawdisc(scr, p->semidiam, 0, mooncolor, pt, nil);
389 if(strcmp(p->name, "shadow") == 0){
390 drawdisc(scr, p->semidiam, 2, shadowcolor, pt, nil);
393 if(strcmp(p->name, "mercury") == 0){
394 drawdisc(scr, p->semidiam, 0, mercurycolor, pt, "m");
397 if(strcmp(p->name, "venus") == 0){
398 drawdisc(scr, p->semidiam, 0, venuscolor, pt, "v");
401 if(strcmp(p->name, "mars") == 0){
402 drawdisc(scr, p->semidiam, 0, marscolor, pt, "M");
405 if(strcmp(p->name, "jupiter") == 0){
406 drawdisc(scr, p->semidiam, 0, jupitercolor, pt, "J");
409 if(strcmp(p->name, "saturn") == 0){
410 drawdisc(scr, p->semidiam, 1, saturncolor, pt, "S");
414 if(strcmp(p->name, "uranus") == 0){
415 drawdisc(scr, p->semidiam, 0, uranuscolor, pt, "U");
419 if(strcmp(p->name, "neptune") == 0){
420 drawdisc(scr, p->semidiam, 0, neptunecolor, pt, "N");
424 if(strcmp(p->name, "pluto") == 0){
425 drawdisc(scr, p->semidiam, 0, plutocolor, pt, "P");
429 if(strcmp(p->name, "comet") == 0){
430 drawdisc(scr, p->semidiam, 0, cometcolor, pt, "C");
434 pt.x -= stringwidth(font, p->name)/2;
435 pt.y -= font->height/2;
436 string(scr, pt, grey, ZP, font, p->name);
445 /* stop early to simplify inner loop adjustment */
447 for(i=0,r=rec; i<nrec-nlast; i++,r++)
448 if(r->type == Planet)
449 if(name==nil || strcmp(r->planet.name, name)==0){
451 memmove(rec+i, rec+i+1, (nrec-i-1)*sizeof(Record));
460 bbox(long extrara, long extradec, int quantize)
465 int rah, ram, d1, d2;
471 decmax = -0x7FFFFFFF;
473 for(i=0,r=rec; i<nrec; i++,r++){
474 if(r->type == Patch){
475 radec(r->index, &rah, &ram, &dec);
477 r0 = c/cos(dec*PI/180);
486 }else if(r->type==SAO || r->type==NGC || r->type==Planet || r->type==Abell){
489 d1 = 0, d2 = 0, r0 = 0;
492 if(dec+d2+extradec > decmax)
493 decmax = dec+d2+extradec;
494 if(dec-d1-extradec < decmin)
495 decmin = dec-d1-extradec;
501 if(ra+r0+extrara > ramax)
502 ramax = ra+r0+extrara;
503 if(ra-extrara < ramin)
517 /* quantize to degree boundaries */
520 ramax += m5-(ramax%m5);
524 decmin -= c - (-decmin)%c;
527 decmax += c-(decmax%c);
528 }else if(decmax < 0){
530 decmax += ((-decmax)%c);
535 if(ramax-ramin > 270*c){
536 fprint(2, "ra range too wide %ld°\n", (ramax-ramin)/c);
539 }else if(ramax-ramin > 270*c){
541 return bbox(0, 0, quantize);
548 inbbox(DAngle ra, DAngle dec)
557 if(ramin > ramax){ /* straddling 0h0m */
572 mapdec = abs(mapdec)+c/2;
583 #define GREY (nogrey? display->white : grey)
596 int dx, dy, nogrid, textlevel, nogrey, zenithup;
610 if(t = alpha(flags, "nogrid")){
615 if((t = alpha(flags, "zenith")) || (t = alpha(flags, "zenithup")) ){
620 if((t = alpha(flags, "notext")) || (t = alpha(flags, "nolabel")) ){
625 if((t = alpha(flags, "alltext")) || (t = alpha(flags, "alllabel")) ){
630 if(t = alpha(flags, "dx")){
631 dx = strtol(t, &t, 0);
633 fprint(2, "dx %d too small (min 100) in plot\n", dx);
639 if(t = alpha(flags, "dy")){
640 dy = strtol(t, &t, 0);
642 fprint(2, "dy %d too small (min 100) in plot\n", dy);
648 if((t = alpha(flags, "nogrey")) || (t = alpha(flags, "nogray"))){
654 fprint(2, "syntax error in plot\n");
662 if(bbox(0, 0, 1) < 0)
664 if(ramax-ramin<100 || decmax-decmin<100){
665 fprint(2, "plot too small\n");
669 scr = allocimage(display, Rect(0, 0, dx, dy), RGB24, 0, DBlack);
671 fprint(2, "can't allocate image: %r\n");
676 rect = insetrect(rect, 40);
677 if(setmap(ramin, ramax, decmin, decmax, rect, zenithup) < 0){
678 fprint(2, "can't set up map coordinates\n");
683 for(j=0; j<nelem(pts); j++){
684 /* use double to avoid overflow */
685 v = (double)j / (double)(nelem(pts)-1);
686 v = decmin + v*(decmax-decmin);
689 bezspline(scr, pts, nelem(pts), Endsquare, Endsquare, 0, GREY, ZP);
697 p.x -= stringwidth(font, hm5(angle(ra)))/2;
698 string(scr, p, GREY, ZP, font, hm5(angle(ra)));
699 p = pts[nelem(pts)-1];
700 p.x -= stringwidth(font, hm5(angle(ra)))/2;
702 string(scr, p, GREY, ZP, font, hm5(angle(ra)));
709 for(y=decmin; y<=decmax; y+=c){
710 for(j=0; j<nelem(pts); j++){
711 /* use double to avoid overflow */
712 v = (double)j / (double)(nelem(pts)-1);
713 v = ramin + v*(ramax-ramin);
716 bezspline(scr, pts, nelem(pts), Endsquare, Endsquare, 0, GREY, ZP);
719 p.y -= font->height/2;
720 string(scr, p, GREY, ZP, font, deg(angle(y)));
721 p = pts[nelem(pts)-1];
722 p.x -= 3+stringwidth(font, deg(angle(y)));
723 p.y -= font->height/2;
724 string(scr, p, GREY, ZP, font, deg(angle(y)));
727 /* reorder to get planets in front of stars */
729 tolast("moon"); /* moon is in front of everything... */
730 tolast("shadow"); /* ... except the shadow */
732 for(i=0,r=rec; i<nrec; i++,r++){
742 if(name==nil && textlevel>1 && r->type==SAO){
743 snprint(buf, sizeof buf, "SAO%ld", r->index);
747 drawname(scr, nogrey? display->white : alphagrey, name, ra, dec);
749 if(r->type == Planet){
750 drawplanet(scr, &r->planet, map(ra, dec));
761 fillellipse(scr, map(ra, dec), m, m, nogrey? display->white : lightgrey, ZP);
763 ellipse(scr, map(ra, dec), m+1, m+1, 0, display->black, ZP);
764 fillellipse(scr, map(ra, dec), m, m, display->white, ZP);
768 if(r->type == Abell){
769 ellipse(scr, addpt(map(ra, dec), Pt(-3, 2)), 2, 1, 0, lightblue, ZP);
770 ellipse(scr, addpt(map(ra, dec), Pt(3, 2)), 2, 1, 0, lightblue, ZP);
771 ellipse(scr, addpt(map(ra, dec), Pt(0, -2)), 1, 2, 0, lightblue, ZP);
776 j = npixels(r->ngc.diam);
783 ellipse(scr, map(ra, dec), j, k, 0, lightblue, ZP);
788 j = npixels(r->ngc.diam);
791 ellipse(scr, p, j, j, 0, green, ZP);
792 line(scr, Pt(p.x, p.y+j+1), Pt(p.x, p.y+j+4),
793 Endsquare, Endsquare, 0, green, ZP);
794 line(scr, Pt(p.x, p.y-(j+1)), Pt(p.x, p.y-(j+4)),
795 Endsquare, Endsquare, 0, green, ZP);
796 line(scr, Pt(p.x+j+1, p.y), Pt(p.x+j+4, p.y),
797 Endsquare, Endsquare, 0, green, ZP);
798 line(scr, Pt(p.x-(j+1), p.y), Pt(p.x-(j+4), p.y),
799 Endsquare, Endsquare, 0, green, ZP);
805 j = npixels(r->ngc.diam);
808 r1.min = Pt(p.x-j, p.y-j);
809 r1.max = Pt(p.x+j, p.y+j);
810 if(r->ngc.type != DiffuseN)
811 draw(scr, r1, ocstipple, ocstipple, ZP);
812 line(scr, Pt(p.x-j, p.y-j), Pt(p.x+j, p.y-j),
813 Endsquare, Endsquare, 0, green, ZP);
814 line(scr, Pt(p.x-j, p.y+j), Pt(p.x+j, p.y+j),
815 Endsquare, Endsquare, 0, green, ZP);
816 line(scr, Pt(p.x-j, p.y-j), Pt(p.x-j, p.y+j),
817 Endsquare, Endsquare, 0, green, ZP);
818 line(scr, Pt(p.x+j, p.y-j), Pt(p.x+j, p.y+j),
819 Endsquare, Endsquare, 0, green, ZP);
824 j = npixels(r->ngc.diam);
827 fillellipse(scr, p, j, j, ocstipple, ZP);
831 j = npixels(r->ngc.diam);
835 ellipse(scr, p, j, j, 0, lightgrey, ZP);
836 line(scr, Pt(p.x-(j-1), p.y), Pt(p.x+j, p.y),
837 Endsquare, Endsquare, 0, lightgrey, ZP);
838 line(scr, Pt(p.x, p.y-(j-1)), Pt(p.x, p.y+j),
839 Endsquare, Endsquare, 0, lightgrey, ZP);
844 flushimage(display, 1);
849 runcommand(char *command, int p[2])
851 switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
860 execl("/bin/rc", "rc", "-c", command, nil);
861 fprint(2, "can't exec %s: %r", command);
868 parseplanet(char *line, Planetrec *p)
876 line[10] = '\0'; /* terminate name */
877 s = strrchr(line, ' ');
883 for(i=0; s[i]!='\0'; i++)
884 p->name[i] = tolower(s[i]);
886 nfld = getfields(line+11, fld, nelem(fld), 1, " ");
887 p->ra = dangle(getra(fld[0]));
888 p->dec = dangle(getra(fld[1]));
889 p->az = atof(fld[2])*MILLIARCSEC;
890 p->alt = atof(fld[3])*MILLIARCSEC;
891 p->semidiam = atof(fld[4])*1000;
893 p->phase = atof(fld[5]);
899 astro(char *flags, int initial)
903 char cmd[256], buf[4096], *lines[20], *fld[10];
905 snprint(cmd, sizeof cmd, "/bin/astro -p %s", flags);
907 fprint(2, "can't pipe: %r\n");
910 if(runcommand(cmd, p) < 0){
913 fprint(2, "can't run astro: %r");
917 n = readn(p[0], buf, sizeof buf-1);
919 fprint(2, "no data from astro\n");
923 Bwrite(&bout, buf, n);
925 np = getfields(buf, lines, nelem(lines), 0, "\n");
927 fprint(2, "astro: not enough output\n");
930 Bprint(&bout, "%s\n", lines[0]);
932 /* get latitude and longitude */
933 if(getfields(lines[0], fld, nelem(fld), 1, " ") < 8)
934 fprint(2, "astro: can't read longitude: too few fields\n");
936 mysid = getra(fld[5])*180./PI;
937 mylat = getra(fld[6])*180./PI;
938 mylon = getra(fld[7])*180./PI;
941 * Each time we run astro, we generate a new planet list
942 * so multiple appearances of a planet may exist as we plot
943 * its motion over time.
945 planet = malloc(NPlanet*sizeof planet[0]);
947 fprint(2, "astro: malloc failed: %r\n");
950 memset(planet, 0, NPlanet*sizeof planet[0]);
952 parseplanet(lines[i], &planet[i-1]);