URI:
       tscat.c - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tscat.c (30145B)
       ---
            1 #include <u.h>
            2 #include <libc.h>
            3 #include <bio.h>
            4 #include <draw.h>
            5 #include <event.h>
            6 #include "sky.h"
            7 #include "strings.c"
            8 
            9 enum
           10 {
           11         NNGC=7840,        /* number of NGC numbers [1..NNGC] */
           12         NIC = 5386,        /* number of IC numbers */
           13         NNGCrec=NNGC+NIC,        /* number of records in the NGC catalog (including IC's, starting at NNGC */
           14         NMrec=122,        /* number of M records */
           15         NM=110,                /* number of M numbers */
           16         NAbell=2712,        /* number of records in the Abell catalog */
           17         NName=1000,        /* number of prose names; estimated maximum (read from editable text file) */
           18         NBayer=1517,        /* number of bayer entries */
           19         NSAO=258998,        /* number of SAO stars */
           20         MAXcon=1932,        /* maximum number of patches in a constellation */
           21         Ncon=88,        /* number of constellations */
           22         Npatch=92053,        /* highest patch number */
           23 };
           24 
           25 char                ngctype[NNGCrec];
           26 Mindexrec        mindex[NMrec];
           27 Namerec                name[NName];
           28 Bayerec                bayer[NBayer];
           29 int32                con[MAXcon];
           30 ushort                conindex[Ncon+1];
           31 int32                patchaddr[Npatch+1];
           32 
           33 Record        *rec;
           34 Record        *orec;
           35 Record        *cur;
           36 
           37 char        *dir;
           38 int        saodb;
           39 int        ngcdb;
           40 int        abelldb;
           41 int        ngctypedb;
           42 int        mindexdb;
           43 int        namedb;
           44 int        bayerdb;
           45 int        condb;
           46 int        conindexdb;
           47 int        patchdb;
           48 char        parsed[3];
           49 int32        nrec;
           50 int32        nreca;
           51 int32        norec;
           52 int32        noreca;
           53 
           54 Biobuf        bin;
           55 Biobuf        bout;
           56 
           57 void
           58 main(int argc, char *argv[])
           59 {
           60         char *line;
           61 
           62         dir = unsharp(DIR);
           63         Binit(&bin, 0, OREAD);
           64         Binit(&bout, 1, OWRITE);
           65         if(argc != 1)
           66                 dir = argv[1];
           67         astro("", 1);
           68         while(line = Brdline(&bin, '\n')){
           69                 line[Blinelen(&bin)-1] = 0;
           70                 lookup(line, 1);
           71                 Bflush(&bout);
           72         }
           73         if(display != nil){
           74                 closedisplay(display);
           75                 /* automatic refresh of rio window is triggered by mouse */
           76         /*        close(open("/dev/mouse", OREAD)); */
           77         }
           78         return;
           79 }
           80 
           81 void
           82 reset(void)
           83 {
           84         nrec = 0;
           85         cur = rec;
           86 }
           87 
           88 void
           89 grow(void)
           90 {
           91         nrec++;
           92         if(nreca < nrec){
           93                 nreca = nrec+50;
           94                 rec = realloc(rec, nreca*sizeof(Record));
           95                 if(rec == 0){
           96                         fprint(2, "scat: realloc fails\n");
           97                         exits("realloc");
           98                 }
           99         }
          100         cur = rec+nrec-1;
          101 }
          102 
          103 void
          104 copy(void)
          105 {
          106         if(noreca < nreca){
          107                 noreca = nreca;
          108                 orec = realloc(orec, nreca*sizeof(Record));
          109                 if(orec == 0){
          110                         fprint(2, "scat: realloc fails\n");
          111                         exits("realloc");
          112                 }
          113         }
          114         memmove(orec, rec, nrec*sizeof(Record));
          115         norec = nrec;
          116 }
          117 
          118 int
          119 eopen(char *s)
          120 {
          121         char buf[128];
          122         int f;
          123 
          124         sprint(buf, "%s/%s.scat", dir, s);
          125         f = open(buf, 0);
          126         if(f<0){
          127                 fprint(2, "scat: can't open %s\n", buf);
          128                 exits("open");
          129         }
          130         return f;
          131 }
          132 
          133 
          134 void
          135 Eread(int f, char *name, void *addr, int32 n)
          136 {
          137         if(read(f, addr, n) != n){        /* BUG! */
          138                 fprint(2, "scat: read error on %s\n", name);
          139                 exits("read");
          140         }
          141 }
          142 
          143 char*
          144 skipbl(char *s)
          145 {
          146         while(*s!=0 && (*s==' ' || *s=='\t'))
          147                 s++;
          148         return s;
          149 }
          150 
          151 char*
          152 skipstr(char *s, char *t)
          153 {
          154         while(*s && *s==*t)
          155                 s++, t++;
          156         return skipbl(s);
          157 }
          158 
          159 /* produce little-endian int32 at address l */
          160 int32
          161 Long(int32 *l)
          162 {
          163         uchar *p;
          164 
          165         p = (uchar*)l;
          166         return (int32)p[0]|((int32)p[1]<<8)|((int32)p[2]<<16)|((int32)p[3]<<24);
          167 }
          168 
          169 /* produce little-endian int32 at address l */
          170 int
          171 Short(short *s)
          172 {
          173         uchar *p;
          174 
          175         p = (uchar*)s;
          176         return p[0]|(p[1]<<8);
          177 }
          178 
          179 void
          180 nameopen(void)
          181 {
          182         Biobuf b;
          183         int i;
          184         char *l, *p;
          185 
          186         if(namedb == 0){
          187                 namedb = eopen("name");
          188                 Binit(&b, namedb, OREAD);
          189                 for(i=0; i<NName; i++){
          190                         l = Brdline(&b, '\n');
          191                         if(l == 0)
          192                                 break;
          193                         p = strchr(l, '\t');
          194                         if(p == 0){
          195                 Badformat:
          196                                 Bprint(&bout, "warning: name.scat bad format; line %d\n", i+1);
          197                                 break;
          198                         }
          199                         *p++ = 0;
          200                         strcpy(name[i].name, l);
          201                         if(strncmp(p, "ngc", 3) == 0)
          202                                 name[i].ngc = atoi(p+3);
          203                         else if(strncmp(p, "ic", 2) == 0)
          204                                 name[i].ngc = atoi(p+2)+NNGC;
          205                         else if(strncmp(p, "sao", 3) == 0)
          206                                 name[i].sao = atoi(p+3);
          207                         else if(strncmp(p, "abell", 5) == 0)
          208                                 name[i].abell = atoi(p+5);
          209                         else
          210                                 goto Badformat;
          211                 }
          212                 if(i == NName)
          213                         Bprint(&bout, "warning: too many names in name.scat (max %d); extra ignored\n", NName);
          214                 close(namedb);
          215 
          216                 bayerdb = eopen("bayer");
          217                 Eread(bayerdb, "bayer", bayer, sizeof bayer);
          218                 close(bayerdb);
          219                 for(i=0; i<NBayer; i++)
          220                         bayer[i].sao = Long(&bayer[i].sao);
          221         }
          222 }
          223 
          224 void
          225 saoopen(void)
          226 {
          227         if(saodb == 0){
          228                 nameopen();
          229                 saodb = eopen("sao");
          230         }
          231 }
          232 
          233 void
          234 ngcopen(void)
          235 {
          236         if(ngcdb == 0){
          237                 nameopen();
          238                 ngcdb = eopen("ngc2000");
          239                 ngctypedb = eopen("ngc2000type");
          240                 Eread(ngctypedb, "ngctype", ngctype, sizeof ngctype);
          241                 close(ngctypedb);
          242         }
          243 }
          244 
          245 void
          246 abellopen(void)
          247 {
          248         /* nothing extra to do with abell: it's directly indexed by number */
          249         if(abelldb == 0)
          250                 abelldb = eopen("abell");
          251 }
          252 
          253 void
          254 patchopen(void)
          255 {
          256         Biobuf *b;
          257         int32 l, m;
          258         char buf[100];
          259 
          260         if(patchdb == 0){
          261                 patchdb = eopen("patch");
          262                 sprint(buf, "%s/patchindex.scat", dir);
          263                 b = Bopen(buf, OREAD);
          264                 if(b == 0){
          265                         fprint(2, "can't open %s\n", buf);
          266                         exits("open");
          267                 }
          268                 for(m=0,l=0; l<=Npatch; l++)
          269                         patchaddr[l] = m += Bgetc(b)*4;
          270                 Bterm(b);
          271         }
          272 }
          273 
          274 void
          275 mopen(void)
          276 {
          277         int i;
          278 
          279         if(mindexdb == 0){
          280                 mindexdb = eopen("mindex");
          281                 Eread(mindexdb, "mindex", mindex, sizeof mindex);
          282                 close(mindexdb);
          283                 for(i=0; i<NMrec; i++)
          284                         mindex[i].ngc = Short(&mindex[i].ngc);
          285         }
          286 }
          287 
          288 void
          289 constelopen(void)
          290 {
          291         int i;
          292 
          293         if(condb == 0){
          294                 condb = eopen("con");
          295                 conindexdb = eopen("conindex");
          296                 Eread(conindexdb, "conindex", conindex, sizeof conindex);
          297                 close(conindexdb);
          298                 for(i=0; i<Ncon+1; i++)
          299                         conindex[i] = Short((short*)&conindex[i]);
          300         }
          301 }
          302 
          303 void
          304 lowercase(char *s)
          305 {
          306         for(; *s; s++)
          307                 if('A'<=*s && *s<='Z')
          308                         *s += 'a'-'A';
          309 }
          310 
          311 int
          312 loadngc(int32 index)
          313 {
          314         static int failed;
          315         int32 j;
          316 
          317         ngcopen();
          318         j = (index-1)*sizeof(NGCrec);
          319         grow();
          320         cur->type = NGC;
          321         cur->index = index;
          322         seek(ngcdb, j, 0);
          323         /* special case: NGC data may not be available */
          324         if(read(ngcdb, &cur->u.ngc, sizeof(NGCrec)) != sizeof(NGCrec)){
          325                 if(!failed){
          326                         fprint(2, "scat: NGC database not available\n");
          327                         failed++;
          328                 }
          329                 cur->type = NONGC;
          330                 cur->u.ngc.ngc = 0;
          331                 cur->u.ngc.ra = 0;
          332                 cur->u.ngc.dec = 0;
          333                 cur->u.ngc.diam = 0;
          334                 cur->u.ngc.mag = 0;
          335                 return 0;
          336         }
          337         cur->u.ngc.ngc = Short(&cur->u.ngc.ngc);
          338         cur->u.ngc.ra = Long(&cur->u.ngc.ra);
          339         cur->u.ngc.dec = Long(&cur->u.ngc.dec);
          340         cur->u.ngc.diam = Long(&cur->u.ngc.diam);
          341         cur->u.ngc.mag = Short(&cur->u.ngc.mag);
          342         return 1;
          343 }
          344 
          345 int
          346 loadabell(int32 index)
          347 {
          348         int32 j;
          349 
          350         abellopen();
          351         j = index-1;
          352         grow();
          353         cur->type = Abell;
          354         cur->index = index;
          355         seek(abelldb, j*sizeof(Abellrec), 0);
          356         Eread(abelldb, "abell", &cur->u.abell, sizeof(Abellrec));
          357         cur->u.abell.abell = Short(&cur->u.abell.abell);
          358         if(cur->u.abell.abell != index){
          359                 fprint(2, "bad format in abell catalog\n");
          360                 exits("abell");
          361         }
          362         cur->u.abell.ra = Long(&cur->u.abell.ra);
          363         cur->u.abell.dec = Long(&cur->u.abell.dec);
          364         cur->u.abell.glat = Long(&cur->u.abell.glat);
          365         cur->u.abell.glong = Long(&cur->u.abell.glong);
          366         cur->u.abell.rad = Long(&cur->u.abell.rad);
          367         cur->u.abell.mag10 = Short(&cur->u.abell.mag10);
          368         cur->u.abell.pop = Short(&cur->u.abell.pop);
          369         cur->u.abell.dist = Short(&cur->u.abell.dist);
          370         return 1;
          371 }
          372 
          373 int
          374 loadsao(int index)
          375 {
          376         if(index<=0 || index>NSAO)
          377                 return 0;
          378         saoopen();
          379         grow();
          380         cur->type = SAO;
          381         cur->index = index;
          382         seek(saodb, (index-1)*sizeof(SAOrec), 0);
          383         Eread(saodb, "sao", &cur->u.sao, sizeof(SAOrec));
          384         cur->u.sao.ra = Long(&cur->u.sao.ra);
          385         cur->u.sao.dec = Long(&cur->u.sao.dec);
          386         cur->u.sao.dra = Long(&cur->u.sao.dra);
          387         cur->u.sao.ddec = Long(&cur->u.sao.ddec);
          388         cur->u.sao.mag = Short(&cur->u.sao.mag);
          389         cur->u.sao.mpg = Short(&cur->u.sao.mpg);
          390         cur->u.sao.hd = Long(&cur->u.sao.hd);
          391         return 1;
          392 }
          393 
          394 int
          395 loadplanet(int index, Record *r)
          396 {
          397         if(index<0 || index>NPlanet || planet[index].name[0]=='\0')
          398                 return 0;
          399         grow();
          400         cur->type = Planet;
          401         cur->index = index;
          402         /* check whether to take new or existing record */
          403         if(r == nil)
          404                 memmove(&cur->u.planet, &planet[index], sizeof(Planetrec));
          405         else
          406                 memmove(&cur->u.planet, &r->u.planet, sizeof(Planetrec));
          407         return 1;
          408 }
          409 
          410 int
          411 loadpatch(int32 index)
          412 {
          413         int i;
          414 
          415         patchopen();
          416         if(index<=0 || index>Npatch)
          417                 return 0;
          418         grow();
          419         cur->type = Patch;
          420         cur->index = index;
          421         seek(patchdb, patchaddr[index-1], 0);
          422         cur->u.patch.nkey = (patchaddr[index]-patchaddr[index-1])/4;
          423         Eread(patchdb, "patch", cur->u.patch.key, cur->u.patch.nkey*4);
          424         for(i=0; i<cur->u.patch.nkey; i++)
          425                 cur->u.patch.key[i] = Long(&cur->u.patch.key[i]);
          426         return 1;
          427 }
          428 
          429 int
          430 loadtype(int t)
          431 {
          432         int i;
          433 
          434         ngcopen();
          435         for(i=0; i<NNGCrec; i++)
          436                 if(t == (ngctype[i])){
          437                         grow();
          438                         cur->type = NGCN;
          439                         cur->index = i+1;
          440                 }
          441         return 1;
          442 }
          443 
          444 void
          445 flatten(void)
          446 {
          447         int i, j, notflat;
          448         Record *or;
          449         int32 key;
          450 
          451     loop:
          452         copy();
          453         reset();
          454         notflat = 0;
          455         for(i=0,or=orec; i<norec; i++,or++){
          456                 switch(or->type){
          457                 default:
          458                         fprint(2, "bad type %d in flatten\n", or->type);
          459                         break;
          460 
          461                 case NONGC:
          462                         break;
          463 
          464                 case Planet:
          465                 case Abell:
          466                 case NGC:
          467                 case SAO:
          468                         grow();
          469                         memmove(cur, or, sizeof(Record));
          470                         break;
          471 
          472                 case NGCN:
          473                         if(loadngc(or->index))
          474                                 notflat = 1;
          475                         break;
          476 
          477                 case NamedSAO:
          478                         loadsao(or->index);
          479                         notflat = 1;
          480                         break;
          481 
          482                 case NamedNGC:
          483                         if(loadngc(or->index))
          484                                 notflat = 1;
          485                         break;
          486 
          487                 case NamedAbell:
          488                         loadabell(or->index);
          489                         notflat = 1;
          490                         break;
          491 
          492                 case PatchC:
          493                         loadpatch(or->index);
          494                         notflat = 1;
          495                         break;
          496 
          497                 case Patch:
          498                         for(j=1; j<or->u.patch.nkey; j++){
          499                                 key = or->u.patch.key[j];
          500                                 if((key&0x3F) == SAO)
          501                                         loadsao((key>>8)&0xFFFFFF);
          502                                 else if((key&0x3F) == Abell)
          503                                         loadabell((key>>8)&0xFFFFFF);
          504                                 else
          505                                         loadngc((key>>16)&0xFFFF);
          506                         }
          507                         break;
          508                 }
          509         }
          510         if(notflat)
          511                 goto loop;
          512 }
          513 
          514 int
          515 ism(int index)
          516 {
          517         int i;
          518 
          519         for(i=0; i<NMrec; i++)
          520                 if(mindex[i].ngc == index)
          521                         return 1;
          522         return 0;
          523 }
          524 
          525 char*
          526 alpha(char *s, char *t)
          527 {
          528         int n;
          529 
          530         n = strlen(t);
          531         if(strncmp(s, t, n)==0 && (s[n]<'a' || 'z'<s[n]))
          532                 return skipbl(s+n);
          533         return 0;
          534 
          535 }
          536 
          537 char*
          538 text(char *s, char *t)
          539 {
          540         int n;
          541 
          542         n = strlen(t);
          543         if(strncmp(s, t, n)==0 && (s[n]==0 || s[n]==' ' || s[n]=='\t'))
          544                 return skipbl(s+n);
          545         return 0;
          546 
          547 }
          548 
          549 int
          550 cull(char *s, int keep, int dobbox)
          551 {
          552         int i, j, nobj, keepthis;
          553         Record *or;
          554         char *t;
          555         int dogrtr, doless, dom, dosao, dongc, doabell;
          556         int mgrtr, mless;
          557         char obj[100];
          558 
          559         memset(obj, 0, sizeof(obj));
          560         nobj = 0;
          561         dogrtr = 0;
          562         doless = 0;
          563         dom = 0;
          564         dongc = 0;
          565         dosao = 0;
          566         doabell = 0;
          567         mgrtr = mless= 0;
          568         if(dobbox)
          569                 goto Cull;
          570         for(;;){
          571                 if(s[0] == '>'){
          572                         dogrtr = 1;
          573                         mgrtr = 10 * strtod(s+1, &t);
          574                         if(mgrtr==0  && t==s+1){
          575                                 fprint(2, "bad magnitude\n");
          576                                 return 0;
          577                         }
          578                         s = skipbl(t);
          579                         continue;
          580                 }
          581                 if(s[0] == '<'){
          582                         doless = 1;
          583                         mless = 10 * strtod(s+1, &t);
          584                         if(mless==0  && t==s+1){
          585                                 fprint(2, "bad magnitude\n");
          586                                 return 0;
          587                         }
          588                         s = skipbl(t);
          589                         continue;
          590                 }
          591                 if(t = text(s, "m")){
          592                          dom = 1;
          593                         s = t;
          594                         continue;
          595                 }
          596                 if(t = text(s, "sao")){
          597                         dosao = 1;
          598                         s = t;
          599                         continue;
          600                 }
          601                 if(t = text(s, "ngc")){
          602                         dongc = 1;
          603                         s = t;
          604                         continue;
          605                 }
          606                 if(t = text(s, "abell")){
          607                         doabell = 1;
          608                         s = t;
          609                         continue;
          610                 }
          611                 for(i=0; names[i].name; i++)
          612                         if(t = alpha(s, names[i].name)){
          613                                 if(nobj > 100){
          614                                         fprint(2, "too many object types\n");
          615                                         return 0;
          616                                 }
          617                                 obj[nobj++] = names[i].type;
          618                                 s = t;
          619                                 goto Continue;
          620                         }
          621                 break;
          622             Continue:;
          623         }
          624         if(*s){
          625                 fprint(2, "syntax error in object list\n");
          626                 return 0;
          627         }
          628 
          629     Cull:
          630         flatten();
          631         copy();
          632         reset();
          633         if(dom)
          634                 mopen();
          635         if(dosao)
          636                 saoopen();
          637         if(dongc || nobj)
          638                 ngcopen();
          639         if(doabell)
          640                 abellopen();
          641         for(i=0,or=orec; i<norec; i++,or++){
          642                 keepthis = !keep;
          643                 if(dobbox && inbbox(or->u.ngc.ra, or->u.ngc.dec))
          644                         keepthis = keep;
          645                 if(doless && or->u.ngc.mag <= mless)
          646                         keepthis = keep;
          647                 if(dogrtr && or->u.ngc.mag >= mgrtr)
          648                         keepthis = keep;
          649                 if(dom && (or->type==NGC && ism(or->u.ngc.ngc)))
          650                         keepthis = keep;
          651                 if(dongc && or->type==NGC)
          652                         keepthis = keep;
          653                 if(doabell && or->type==Abell)
          654                         keepthis = keep;
          655                 if(dosao && or->type==SAO)
          656                         keepthis = keep;
          657                 for(j=0; j<nobj; j++)
          658                         if(or->type==NGC && or->u.ngc.type==obj[j])
          659                                 keepthis = keep;
          660                 if(keepthis){
          661                         grow();
          662                         memmove(cur, or, sizeof(Record));
          663                 }
          664         }
          665         return 1;
          666 }
          667 
          668 int
          669 compar(const void *va, const void *vb)
          670 {
          671         Record *a=(Record*)va, *b=(Record*)vb;
          672 
          673         if(a->type == b->type)
          674                 return a->index - b->index;
          675         return a->type - b->type;
          676 }
          677 
          678 void
          679 sort(void)
          680 {
          681         int i;
          682         Record *r, *s;
          683 
          684         if(nrec == 0)
          685                 return;
          686         qsort(rec, nrec, sizeof(Record), compar);
          687         r = rec+1;
          688         s = rec;
          689         for(i=1; i<nrec; i++,r++){
          690                 /* may have multiple instances of a planet in the scene */
          691                 if(r->type==s->type && r->index==s->index && r->type!=Planet)
          692                         continue;
          693                 memmove(++s, r, sizeof(Record));
          694         }
          695         nrec = (s+1)-rec;
          696 }
          697 
          698 char        greekbuf[128];
          699 
          700 char*
          701 togreek(char *s)
          702 {
          703         char *t;
          704         int i, n;
          705         Rune r;
          706 
          707         t = greekbuf;
          708         while(*s){
          709                 for(i=1; i<=24; i++){
          710                         n = strlen(greek[i]);
          711                         if(strncmp(s, greek[i], n)==0 && (s[n]==' ' || s[n]=='\t')){
          712                                 s += n;
          713                                 t += runetochar(t, &greeklet[i]);
          714                                 goto Cont;
          715                         }
          716                 }
          717                 n = chartorune(&r, s);
          718                 for(i=0; i<n; i++)
          719                         *t++ = *s++;
          720     Cont:;
          721         }
          722         *t = 0;
          723         return greekbuf;
          724 }
          725 
          726 char*
          727 fromgreek(char *s)
          728 {
          729         char *t;
          730         int i, n;
          731         Rune r;
          732 
          733         t = greekbuf;
          734         while(*s){
          735                 n = chartorune(&r, s);
          736                 for(i=1; i<=24; i++){
          737                         if(r == greeklet[i]){
          738                                 strcpy(t, greek[i]);
          739                                 t += strlen(greek[i]);
          740                                 s += n;
          741                                 goto Cont;
          742                         }
          743                 }
          744                 for(i=0; i<n; i++)
          745                         *t++ = *s++;
          746     Cont:;
          747         }
          748         *t = 0;
          749         return greekbuf;
          750 }
          751 
          752 #ifdef OLD
          753 /*
          754  * Old version
          755  */
          756 int
          757 coords(int deg)
          758 {
          759         int i;
          760         int x, y;
          761         Record *or;
          762         int32 dec, ra, ndec, nra;
          763         int rdeg;
          764 
          765         flatten();
          766         copy();
          767         reset();
          768         deg *= 2;
          769         for(i=0,or=orec; i<norec; i++,or++){
          770                 if(or->type == Planet)        /* must keep it here */
          771                         loadplanet(or->index, or);
          772                 dec = or->u.ngc.dec/MILLIARCSEC;
          773                 ra = or->u.ngc.ra/MILLIARCSEC;
          774                 rdeg = deg/cos((dec*PI)/180);
          775                 for(y=-deg; y<=+deg; y++){
          776                         ndec = dec*2+y;
          777                         if(ndec/2>=90 || ndec/2<=-90)
          778                                 continue;
          779                         /* fp errors hurt here, so we round 1' to the pole */
          780                         if(ndec >= 0)
          781                                 ndec = ndec*500*60*60 + 60000;
          782                         else
          783                                 ndec = ndec*500*60*60 - 60000;
          784                         for(x=-rdeg; x<=+rdeg; x++){
          785                                 nra = ra*2+x;
          786                                 if(nra/2 < 0)
          787                                         nra += 360*2;
          788                                 if(nra/2 >= 360)
          789                                         nra -= 360*2;
          790                                 /* fp errors hurt here, so we round up 1' */
          791                                 nra = nra/2*MILLIARCSEC + 60000;
          792                                 loadpatch(patcha(angle(nra), angle(ndec)));
          793                         }
          794                 }
          795         }
          796         sort();
          797         return 1;
          798 }
          799 #endif
          800 
          801 /*
          802  * New version attempts to match the boundaries of the plot better.
          803  */
          804 int
          805 coords(int deg)
          806 {
          807         int i;
          808         int x, y, xx;
          809         Record *or;
          810         int32 min, circle;
          811         double factor;
          812 
          813         flatten();
          814         circle = 360*MILLIARCSEC;
          815         deg *= MILLIARCSEC;
          816 
          817         /* find center */
          818         folded = 0;
          819         bbox(0, 0, 0);
          820         /* now expand */
          821         factor = cos(angle((decmax+decmin)/2));
          822         if(factor < .2)
          823                 factor = .2;
          824         factor = floor(1/factor);
          825         folded = 0;
          826         bbox(factor*deg, deg, 1);
          827         Bprint(&bout, "%s to ", hms(angle(ramin)));
          828         Bprint(&bout, "%s\n", hms(angle(ramax)));
          829         Bprint(&bout, "%s to ", dms(angle(decmin)));
          830         Bprint(&bout, "%s\n", dms(angle(decmax)));
          831         copy();
          832         reset();
          833         for(i=0,or=orec; i<norec; i++,or++)
          834                 if(or->type == Planet)        /* must keep it here */
          835                         loadplanet(or->index, or);
          836         min = ramin;
          837         if(ramin > ramax)
          838                 min -= circle;
          839         for(x=min; x<=ramax; x+=250*60*60){
          840                 xx = x;
          841                 if(xx < 0)
          842                         xx += circle;
          843                 for(y=decmin; y<=decmax; y+=250*60*60)
          844                         if(-circle/4 < y && y < circle/4)
          845                                 loadpatch(patcha(angle(xx), angle(y)));
          846         }
          847         sort();
          848         cull(nil, 1, 1);
          849         return 1;
          850 }
          851 
          852 void
          853 pplate(char *flags)
          854 {
          855         int i;
          856         int32 c;
          857         int na, rah, ram, d1, d2;
          858         double r0;
          859         int ra, dec;
          860         int32 ramin, ramax, decmin, decmax;        /* all in degrees */
          861         Record *r;
          862         int folded;
          863         Angle racenter, deccenter, rasize, decsize, a[4];
          864         Picture *pic;
          865 
          866         rasize = -1.0;
          867         decsize = -1.0;
          868         na = 0;
          869         for(;;){
          870                 while(*flags==' ')
          871                         flags++;
          872                 if(('0'<=*flags && *flags<='9') || *flags=='+' || *flags=='-'){
          873                         if(na >= 3)
          874                                 goto err;
          875                         a[na++] = getra(flags);
          876                         while(*flags && *flags!=' ')
          877                                 flags++;
          878                         continue;
          879                 }
          880                 if(*flags){
          881         err:
          882                         Bprint(&bout, "syntax error in plate\n");
          883                         return;
          884                 }
          885                 break;
          886         }
          887         switch(na){
          888         case 0:
          889                 break;
          890         case 1:
          891                 rasize = a[0];
          892                 decsize = rasize;
          893                 break;
          894         case 2:
          895                 rasize = a[0];
          896                 decsize = a[1];
          897                 break;
          898         case 3:
          899         case 4:
          900                 racenter = a[0];
          901                 deccenter = a[1];
          902                 rasize = a[2];
          903                 if(na == 4)
          904                         decsize = a[3];
          905                 else
          906                         decsize = rasize;
          907                 if(rasize<0.0 || decsize<0.0){
          908                         Bprint(&bout, "negative sizes\n");
          909                         return;
          910                 }
          911                 goto done;
          912         }
          913         folded = 0;
          914         /* convert to milliarcsec */
          915         c = 1000*60*60;
          916     Again:
          917         if(nrec == 0){
          918                 Bprint(&bout, "empty\n");
          919                 return;
          920         }
          921         ramin = 0x7FFFFFFF;
          922         ramax = -0x7FFFFFFF;
          923         decmin = 0x7FFFFFFF;
          924         decmax = -0x7FFFFFFF;
          925         for(r=rec,i=0; i<nrec; i++,r++){
          926                 if(r->type == Patch){
          927                         radec(r->index, &rah, &ram, &dec);
          928                         ra = 15*rah+ram/4;
          929                         r0 = c/cos(RAD(dec));
          930                         ra *= c;
          931                         dec *= c;
          932                         if(dec == 0)
          933                                 d1 = c, d2 = c;
          934                         else if(dec < 0)
          935                                 d1 = c, d2 = 0;
          936                         else
          937                                 d1 = 0, d2 = c;
          938                 }else if(r->type==SAO || r->type==NGC || r->type==Abell){
          939                         ra = r->u.ngc.ra;
          940                         dec = r->u.ngc.dec;
          941                         d1 = 0, d2 = 0, r0 = 0;
          942                 }else if(r->type==NGCN){
          943                         loadngc(r->index);
          944                         continue;
          945                 }else if(r->type==NamedSAO){
          946                         loadsao(r->index);
          947                         continue;
          948                 }else if(r->type==NamedNGC){
          949                         loadngc(r->index);
          950                         continue;
          951                 }else if(r->type==NamedAbell){
          952                         loadabell(r->index);
          953                         continue;
          954                 }else
          955                         continue;
          956                 if(dec+d2 > decmax)
          957                         decmax = dec+d2;
          958                 if(dec-d1 < decmin)
          959                         decmin = dec-d1;
          960                 if(folded){
          961                         ra -= 180*c;
          962                         if(ra < 0)
          963                                 ra += 360*c;
          964                 }
          965                 if(ra+r0 > ramax)
          966                         ramax = ra+r0;
          967                 if(ra < ramin)
          968                         ramin = ra;
          969         }
          970         if(!folded && ramax-ramin>270*c){
          971                 folded = 1;
          972                 goto Again;
          973         }
          974         racenter = angle(ramin+(ramax-ramin)/2);
          975         deccenter = angle(decmin+(decmax-decmin)/2);
          976         if(rasize<0 || decsize<0){
          977                 rasize = angle(ramax-ramin)*cos(deccenter);
          978                 decsize = angle(decmax-decmin);
          979         }
          980     done:
          981         if(DEG(rasize)>1.1 || DEG(decsize)>1.1){
          982                 Bprint(&bout, "plate too big: %s", ms(rasize));
          983                 Bprint(&bout, " x %s\n", ms(decsize));
          984                 Bprint(&bout, "trimming to 30'x30'\n");
          985                 rasize = RAD(0.5);
          986                 decsize = RAD(0.5);
          987         }
          988         Bprint(&bout, "%s %s ", hms(racenter), dms(deccenter));
          989         Bprint(&bout, "%s", ms(rasize));
          990         Bprint(&bout, " x %s\n", ms(decsize));
          991         Bflush(&bout);
          992         flatten();
          993         pic = image(racenter, deccenter, rasize, decsize);
          994         if(pic == 0)
          995                 return;
          996         Bprint(&bout, "plate %s locn %d %d %d %d\n", pic->name, pic->minx, pic->miny, pic->maxx, pic->maxy);
          997         Bflush(&bout);
          998         displaypic(pic);
          999 }
         1000 
         1001 void
         1002 lookup(char *s, int doreset)
         1003 {
         1004         int i, j, k;
         1005         int rah, ram, deg;
         1006         char *starts, *inputline=s, *t, *u;
         1007         Record *r;
         1008         Rune c;
         1009         int32 n;
         1010         double x;
         1011         Angle ra;
         1012 
         1013         lowercase(s);
         1014         s = skipbl(s);
         1015 
         1016         if(*s == 0)
         1017                 goto Print;
         1018 
         1019         if(t = alpha(s, "flat")){
         1020                 if(*t){
         1021                         fprint(2, "flat takes no arguments\n");
         1022                         return;
         1023                 }
         1024                 if(nrec == 0){
         1025                         fprint(2, "no records\n");
         1026                         return;
         1027                 }
         1028                 flatten();
         1029                 goto Print;
         1030         }
         1031 
         1032         if(t = alpha(s, "print")){
         1033                 if(*t){
         1034                         fprint(2, "print takes no arguments\n");
         1035                         return;
         1036                 }
         1037                 for(i=0,r=rec; i<nrec; i++,r++)
         1038                         prrec(r);
         1039                 return;
         1040         }
         1041 
         1042         if(t = alpha(s, "add")){
         1043                 lookup(t, 0);
         1044                 return;
         1045         }
         1046 
         1047         if(t = alpha(s, "sao")){
         1048                 n = strtoul(t, &u, 10);
         1049                 if(n<=0 || n>NSAO)
         1050                         goto NotFound;
         1051                 t = skipbl(u);
         1052                 if(*t){
         1053                         fprint(2, "syntax error in sao\n");
         1054                         return;
         1055                 }
         1056                 if(doreset)
         1057                         reset();
         1058                 if(!loadsao(n))
         1059                         goto NotFound;
         1060                 goto Print;
         1061         }
         1062 
         1063         if(t = alpha(s, "ngc")){
         1064                 n = strtoul(t, &u, 10);
         1065                 if(n<=0 || n>NNGC)
         1066                         goto NotFound;
         1067                 t = skipbl(u);
         1068                 if(*t){
         1069                         fprint(2, "syntax error in ngc\n");
         1070                         return;
         1071                 }
         1072                 if(doreset)
         1073                         reset();
         1074                 if(!loadngc(n))
         1075                         goto NotFound;
         1076                 goto Print;
         1077         }
         1078 
         1079         if(t = alpha(s, "ic")){
         1080                 n = strtoul(t, &u, 10);
         1081                 if(n<=0 || n>NIC)
         1082                         goto NotFound;
         1083                 t = skipbl(u);
         1084                 if(*t){
         1085                         fprint(2, "syntax error in ic\n");
         1086                         return;
         1087                 }
         1088                 if(doreset)
         1089                         reset();
         1090                 if(!loadngc(n+NNGC))
         1091                         goto NotFound;
         1092                 goto Print;
         1093         }
         1094 
         1095         if(t = alpha(s, "abell")){
         1096                 n = strtoul(t, &u, 10);
         1097                 if(n<=0 || n>NAbell)
         1098                         goto NotFound;
         1099                 if(doreset)
         1100                         reset();
         1101                 if(!loadabell(n))
         1102                         goto NotFound;
         1103                 goto Print;
         1104         }
         1105 
         1106         if(t = alpha(s, "m")){
         1107                 n = strtoul(t, &u, 10);
         1108                 if(n<=0 || n>NM)
         1109                         goto NotFound;
         1110                 mopen();
         1111                 for(j=n-1; mindex[j].m<n; j++)
         1112                         ;
         1113                 if(doreset)
         1114                         reset();
         1115                 while(mindex[j].m == n){
         1116                         if(mindex[j].ngc){
         1117                                 grow();
         1118                                 cur->type = NGCN;
         1119                                 cur->index = mindex[j].ngc;
         1120                         }
         1121                         j++;
         1122                 }
         1123                 goto Print;
         1124         }
         1125 
         1126         for(i=1; i<=Ncon; i++)
         1127                 if(t = alpha(s, constel[i])){
         1128                         if(*t){
         1129                                 fprint(2, "syntax error in constellation\n");
         1130                                 return;
         1131                         }
         1132                         constelopen();
         1133                         seek(condb, 4L*conindex[i-1], 0);
         1134                         j = conindex[i]-conindex[i-1];
         1135                         Eread(condb, "con", con, 4*j);
         1136                         if(doreset)
         1137                                 reset();
         1138                         for(k=0; k<j; k++){
         1139                                 grow();
         1140                                 cur->type = PatchC;
         1141                                 cur->index = Long(&con[k]);
         1142                         }
         1143                         goto Print;
         1144                 }
         1145 
         1146         if(t = alpha(s, "expand")){
         1147                 n = 0;
         1148                 if(*t){
         1149                         if(*t<'0' && '9'<*t){
         1150                 Expanderr:
         1151                                 fprint(2, "syntax error in expand\n");
         1152                                 return;
         1153                         }
         1154                         n = strtoul(t, &u, 10);
         1155                         t = skipbl(u);
         1156                         if(*t)
         1157                                 goto Expanderr;
         1158                 }
         1159                 coords(n);
         1160                 goto Print;
         1161         }
         1162 
         1163         if(t = alpha(s, "plot")){
         1164                 if(nrec == 0){
         1165                         Bprint(&bout, "empty\n");
         1166                         return;
         1167                 }
         1168                 plot(t);
         1169                 return;
         1170         }
         1171 
         1172         if(t = alpha(s, "astro")){
         1173                 astro(t, 0);
         1174                 return;
         1175         }
         1176 
         1177         if(t = alpha(s, "plate")){
         1178                 pplate(t);
         1179                 return;
         1180         }
         1181 
         1182         if(t = alpha(s, "gamma")){
         1183                 while(*t==' ')
         1184                         t++;
         1185                 u = t;
         1186                 x = strtod(t, &u);
         1187                 if(u > t)
         1188                         gam.gamma = x;
         1189                 Bprint(&bout, "%.2f\n", gam.gamma);
         1190                 return;
         1191         }
         1192 
         1193         if(t = alpha(s, "keep")){
         1194                 if(!cull(t, 1, 0))
         1195                         return;
         1196                 goto Print;
         1197         }
         1198 
         1199         if(t = alpha(s, "drop")){
         1200                 if(!cull(t, 0, 0))
         1201                         return;
         1202                 goto Print;
         1203         }
         1204 
         1205         for(i=0; planet[i].name[0]; i++){
         1206                 if(t = alpha(s, planet[i].name)){
         1207                         if(doreset)
         1208                                 reset();
         1209                         loadplanet(i, nil);
         1210                         goto Print;
         1211                 }
         1212         }
         1213 
         1214         for(i=0; names[i].name; i++){
         1215                 if(t = alpha(s, names[i].name)){
         1216                         if(*t){
         1217                                 fprint(2, "syntax error in type\n");
         1218                                 return;
         1219                         }
         1220                         if(doreset)
         1221                                 reset();
         1222                         loadtype(names[i].type);
         1223                         goto Print;
         1224                 }
         1225         }
         1226 
         1227         switch(s[0]){
         1228         case '"':
         1229                 starts = ++s;
         1230                 while(*s != '"')
         1231                         if(*s++ == 0){
         1232                                 fprint(2, "bad star name\n");
         1233                                 return;
         1234                         }
         1235                 *s = 0;
         1236                 if(doreset)
         1237                         reset();
         1238                 j = nrec;
         1239                 saoopen();
         1240                 starts = fromgreek(starts);
         1241                 for(i=0; i<NName; i++)
         1242                         if(equal(starts, name[i].name)){
         1243                                 grow();
         1244                                 if(name[i].sao){
         1245                                         rec[j].type = NamedSAO;
         1246                                         rec[j].index = name[i].sao;
         1247                                 }
         1248                                 if(name[i].ngc){
         1249                                         rec[j].type = NamedNGC;
         1250                                         rec[j].index = name[i].ngc;
         1251                                 }
         1252                                 if(name[i].abell){
         1253                                         rec[j].type = NamedAbell;
         1254                                         rec[j].index = name[i].abell;
         1255                                 }
         1256                                 strcpy(rec[j].u.named.name, name[i].name);
         1257                                 j++;
         1258                         }
         1259                 if(parsename(starts))
         1260                         for(i=0; i<NBayer; i++)
         1261                                 if(bayer[i].name[0]==parsed[0] &&
         1262                                   (bayer[i].name[1]==parsed[1] || parsed[1]==0) &&
         1263                                    bayer[i].name[2]==parsed[2]){
         1264                                         grow();
         1265                                         rec[j].type = NamedSAO;
         1266                                         rec[j].index = bayer[i].sao;
         1267                                         strncpy(rec[j].u.named.name, starts, sizeof(rec[j].u.named.name));
         1268                                         j++;
         1269                                 }
         1270                 if(j == 0){
         1271                         *s = '"';
         1272                         goto NotFound;
         1273                 }
         1274                 break;
         1275 
         1276         case '0': case '1': case '2': case '3': case '4':
         1277         case '5': case '6': case '7': case '8': case '9':
         1278                 strtoul(s, &t, 10);
         1279                 if(*t != 'h'){
         1280         BadCoords:
         1281                         fprint(2, "bad coordinates %s\n", inputline);
         1282                         break;
         1283                 }
         1284                 ra = DEG(getra(s));
         1285                 while(*s && *s!=' ' && *s!='\t')
         1286                         s++;
         1287                 rah = ra/15;
         1288                 ra = ra-rah*15;
         1289                 ram = ra*4;
         1290                 deg = strtol(s, &t, 10);
         1291                 if(t == s)
         1292                         goto BadCoords;
         1293                 /* degree sign etc. is optional */
         1294                 chartorune(&c, t);
         1295                 if(c == 0xb0)
         1296                         deg = DEG(getra(s));
         1297                 if(doreset)
         1298                         reset();
         1299                 if(abs(deg)>=90 || rah>=24)
         1300                         goto BadCoords;
         1301                 if(!loadpatch(patch(rah, ram, deg)))
         1302                         goto NotFound;
         1303                 break;
         1304 
         1305         default:
         1306                 fprint(2, "unknown command %s\n", inputline);
         1307                 return;
         1308         }
         1309 
         1310     Print:
         1311         if(nrec == 0)
         1312                 Bprint(&bout, "empty\n");
         1313         else if(nrec <= 2)
         1314                 for(i=0; i<nrec; i++)
         1315                         prrec(rec+i);
         1316         else
         1317                 Bprint(&bout, "%ld items\n", nrec);
         1318         return;
         1319 
         1320     NotFound:
         1321         fprint(2, "%s not found\n", inputline);
         1322         return;
         1323 }
         1324 
         1325 char *ngctypes[] =
         1326 {
         1327 [Galaxy]                 = "Gx",
         1328 [PlanetaryN]         = "Pl",
         1329 [OpenCl]                 = "OC",
         1330 [GlobularCl]         = "Gb",
         1331 [DiffuseN]                = "Nb",
         1332 [NebularCl]         = "C+N",
         1333 [Asterism]                = "Ast",
         1334 [Knot]                 = "Kt",
         1335 [Triple]                = "***",
         1336 [Double]                = "D*",
         1337 [Single]                = "*",
         1338 [Uncertain]        = "?",
         1339 [Nonexistent]        = "-",
         1340 [Unknown]        = " ",
         1341 [PlateDefect]        = "PD"
         1342 };
         1343 
         1344 char*
         1345 ngcstring(int d)
         1346 {
         1347         if(d<Galaxy || d>PlateDefect)
         1348                 return "can't happen";
         1349         return ngctypes[d];
         1350 }
         1351 
         1352 short        descindex[NINDEX];
         1353 
         1354 void
         1355 printnames(Record *r)
         1356 {
         1357         int i, ok, done;
         1358 
         1359         done = 0;
         1360         for(i=0; i<NName; i++){        /* stupid linear search! */
         1361                 ok = 0;
         1362                 if(r->type==SAO && r->index==name[i].sao)
         1363                         ok = 1;
         1364                 if(r->type==NGC && r->u.ngc.ngc==name[i].ngc)
         1365                         ok = 1;
         1366                 if(r->type==Abell && r->u.abell.abell==name[i].abell)
         1367                         ok = 1;
         1368                 if(ok){
         1369                         if(done++ == 0)
         1370                                 Bprint(&bout, "\t");
         1371                         Bprint(&bout, " \"%s\"", togreek(name[i].name));
         1372                 }
         1373         }
         1374         if(done)
         1375                 Bprint(&bout, "\n");
         1376 }
         1377 
         1378 int
         1379 equal(char *s1, char *s2)
         1380 {
         1381         int c;
         1382 
         1383         while(*s1){
         1384                 if(*s1==' '){
         1385                         while(*s1==' ')
         1386                                 s1++;
         1387                         continue;
         1388                 }
         1389                 while(*s2==' ')
         1390                         s2++;
         1391                 c=*s2;
         1392                 if('A'<=*s2 && *s2<='Z')
         1393                         c^=' ';
         1394                 if(*s1!=c)
         1395                         return 0;
         1396                 s1++, s2++;
         1397         }
         1398         return 1;
         1399 }
         1400 
         1401 int
         1402 parsename(char *s)
         1403 {
         1404         char *blank;
         1405         int i;
         1406 
         1407         blank = strchr(s, ' ');
         1408         if(blank==0 || strchr(blank+1, ' ') || strlen(blank+1)!=3)
         1409                 return 0;
         1410         blank++;
         1411         parsed[0] = parsed[1] = parsed[2] = 0;
         1412         if('0'<=s[0] && s[0]<='9'){
         1413                 i = atoi(s);
         1414                 parsed[0] = i;
         1415                 if(i > 100)
         1416                         return 0;
         1417         }else{
         1418                 for(i=1; i<=24; i++)
         1419                         if(strncmp(greek[i], s, strlen(greek[i]))==0){
         1420                                 parsed[0]=100+i;
         1421                                 goto out;
         1422                         }
         1423                 return 0;
         1424             out:
         1425                 if('0'<=s[strlen(greek[i])] && s[strlen(greek[i])]<='9')
         1426                         parsed[1]=s[strlen(greek[i])]-'0';
         1427         }
         1428         for(i=1; i<=88; i++)
         1429                 if(strcmp(constel[i], blank)==0){
         1430                         parsed[2] = i;
         1431                         return 1;
         1432                 }
         1433         return 0;
         1434 }
         1435 
         1436 char*
         1437 dist_grp(int dg)
         1438 {
         1439         switch(dg){
         1440         default:
         1441                 return "unknown";
         1442         case 1:
         1443                 return "13.3-14.0";
         1444         case 2:
         1445                 return "14.1-14.8";
         1446         case 3:
         1447                 return "14.9-15.6";
         1448         case 4:
         1449                 return "15.7-16.4";
         1450         case 5:
         1451                 return "16.5-17.2";
         1452         case 6:
         1453                 return "17.3-18.0";
         1454         case 7:
         1455                 return ">18.0";
         1456         }
         1457 }
         1458 
         1459 char*
         1460 rich_grp(int dg)
         1461 {
         1462         switch(dg){
         1463         default:
         1464                 return "unknown";
         1465         case 0:
         1466                 return "30-40";
         1467         case 1:
         1468                 return "50-79";
         1469         case 2:
         1470                 return "80-129";
         1471         case 3:
         1472                 return "130-199";
         1473         case 4:
         1474                 return "200-299";
         1475         case 5:
         1476                 return ">=300";
         1477         }
         1478 }
         1479 
         1480 char*
         1481 nameof(Record *r)
         1482 {
         1483         NGCrec *n;
         1484         SAOrec *s;
         1485         Abellrec *a;
         1486         static char buf[128];
         1487         int i;
         1488 
         1489         switch(r->type){
         1490         default:
         1491                 return nil;
         1492         case SAO:
         1493                 s = &r->u.sao;
         1494                 if(s->name[0] == 0)
         1495                         return nil;
         1496                 if(s->name[0] >= 100){
         1497                         i = snprint(buf, sizeof buf, "%C", greeklet[s->name[0]-100]);
         1498                         if(s->name[1])
         1499                                 i += snprint(buf+i, sizeof buf-i, "%d", s->name[1]);
         1500                 }else
         1501                         i = snprint(buf, sizeof buf, " %d", s->name[0]);
         1502                 snprint(buf+i, sizeof buf-i, " %s", constel[(uchar)s->name[2]]);
         1503                 break;
         1504         case NGC:
         1505                 n = &r->u.ngc;
         1506                 if(n->type >= Uncertain)
         1507                         return nil;
         1508                 if(n->ngc <= NNGC)
         1509                         snprint(buf, sizeof buf, "NGC%4d ", n->ngc);
         1510                 else
         1511                         snprint(buf, sizeof buf, "IC%4d ", n->ngc-NNGC);
         1512                 break;
         1513         case Abell:
         1514                 a = &r->u.abell;
         1515                 snprint(buf, sizeof buf, "Abell%4d", a->abell);
         1516                 break;
         1517         }
         1518         return buf;
         1519 }
         1520 
         1521 void
         1522 prrec(Record *r)
         1523 {
         1524         NGCrec *n;
         1525         SAOrec *s;
         1526         Abellrec *a;
         1527         Planetrec *p;
         1528         int i, rah, ram, dec, nn;
         1529         int32 key;
         1530 
         1531         if(r) switch(r->type){
         1532         default:
         1533                 fprint(2, "can't prrec type %d\n", r->type);
         1534                 exits("type");
         1535 
         1536         case Planet:
         1537                 p = &r->u.planet;
         1538                 Bprint(&bout, "%s", p->name);
         1539                 Bprint(&bout, "\t%s %s",
         1540                         hms(angle(p->ra)),
         1541                         dms(angle(p->dec)));
         1542                 Bprint(&bout, " %3.2f° %3.2f°",
         1543                         p->az/(double)MILLIARCSEC, p->alt/(double)MILLIARCSEC);
         1544                 Bprint(&bout, " %s",
         1545                         ms(angle(p->semidiam)));
         1546                 if(r->index <= 1)
         1547                         Bprint(&bout, " %g", p->phase);
         1548                 Bprint(&bout, "\n");
         1549                 break;
         1550 
         1551         case NGC:
         1552                 n = &r->u.ngc;
         1553                 if(n->ngc <= NNGC)
         1554                         Bprint(&bout, "NGC%4d ", n->ngc);
         1555                 else
         1556                         Bprint(&bout, "IC%4d ", n->ngc-NNGC);
         1557                 Bprint(&bout, "%s ", ngcstring(n->type));
         1558                 if(n->mag == UNKNOWNMAG)
         1559                         Bprint(&bout, "----");
         1560                 else
         1561                         Bprint(&bout, "%.1f%c", n->mag/10.0, n->magtype);
         1562                 Bprint(&bout, "\t%s %s\t%c%.1f'\n",
         1563                         hm(angle(n->ra)),
         1564                         dm(angle(n->dec)),
         1565                         n->diamlim,
         1566                         DEG(angle(n->diam))*60.);
         1567                 prdesc(n->desc, desctab, descindex);
         1568                 printnames(r);
         1569                 break;
         1570 
         1571         case Abell:
         1572                 a = &r->u.abell;
         1573                 Bprint(&bout, "Abell%4d  %.1f %.2f° %dMpc", a->abell, a->mag10/10.0,
         1574                         DEG(angle(a->rad)), a->dist);
         1575                 Bprint(&bout, "\t%s %s\t%.2f %.2f\n",
         1576                         hm(angle(a->ra)),
         1577                         dm(angle(a->dec)),
         1578                         DEG(angle(a->glat)),
         1579                         DEG(angle(a->glong)));
         1580                 Bprint(&bout, "\tdist grp: %s  rich grp: %s  %d galaxies/°²\n",
         1581                         dist_grp(a->distgrp),
         1582                         rich_grp(a->richgrp),
         1583                         a->pop);
         1584                 printnames(r);
         1585                 break;
         1586 
         1587         case SAO:
         1588                 s = &r->u.sao;
         1589                 Bprint(&bout, "SAO%6ld  ", r->index);
         1590                 if(s->mag==UNKNOWNMAG)
         1591                         Bprint(&bout, "---");
         1592                 else
         1593                         Bprint(&bout, "%.1f", s->mag/10.0);
         1594                 if(s->mpg==UNKNOWNMAG)
         1595                         Bprint(&bout, ",---");
         1596                 else
         1597                         Bprint(&bout, ",%.1f", s->mpg/10.0);
         1598                 Bprint(&bout, "  %s %s  %.4fs %.3f\"",
         1599                         hms(angle(s->ra)),
         1600                         dms(angle(s->dec)),
         1601                         DEG(angle(s->dra))*(4*60),
         1602                         DEG(angle(s->ddec))*(60*60));
         1603                 Bprint(&bout, "  %.3s %c %.2s %ld %d",
         1604                         s->spec, s->code, s->compid, s->hd, s->hdcode);
         1605                 if(s->name[0])
         1606                         Bprint(&bout, " \"%s\"", nameof(r));
         1607                 Bprint(&bout, "\n");
         1608                 printnames(r);
         1609                 break;
         1610 
         1611         case Patch:
         1612                 radec(r->index, &rah, &ram, &dec);
         1613                 Bprint(&bout, "%dh%dm %d°", rah, ram, dec);
         1614                 key = r->u.patch.key[0];
         1615                 Bprint(&bout, " %s", constel[key&0xFF]);
         1616                 if((key>>=8) & 0xFF)
         1617                         Bprint(&bout, " %s", constel[key&0xFF]);
         1618                 if((key>>=8) & 0xFF)
         1619                         Bprint(&bout, " %s", constel[key&0xFF]);
         1620                 if((key>>=8) & 0xFF)
         1621                         Bprint(&bout, " %s", constel[key&0xFF]);
         1622                 for(i=1; i<r->u.patch.nkey; i++){
         1623                         key = r->u.patch.key[i];
         1624                         switch(key&0x3F){
         1625                         case SAO:
         1626                                 Bprint(&bout, " SAO%ld", (key>>8)&0xFFFFFF);
         1627                                 break;
         1628                         case Abell:
         1629                                 Bprint(&bout, " Abell%ld", (key>>8)&0xFFFFFF);
         1630                                 break;
         1631                         default:        /* NGC */
         1632                                 nn = (key>>16)&0xFFFF;
         1633                                 if(nn > NNGC)
         1634                                         Bprint(&bout, " IC%d", nn-NNGC);
         1635                                 else
         1636                                         Bprint(&bout, " NGC%d", nn);
         1637                                 Bprint(&bout, "(%s)", ngcstring(key&0x3F));
         1638                                 break;
         1639                         }
         1640                 }
         1641                 Bprint(&bout, "\n");
         1642                 break;
         1643 
         1644         case NGCN:
         1645                 if(r->index <= NNGC)
         1646                         Bprint(&bout, "NGC%ld\n", r->index);
         1647                 else
         1648                         Bprint(&bout, "IC%ld\n", r->index-NNGC);
         1649                 break;
         1650 
         1651         case NamedSAO:
         1652                 Bprint(&bout, "SAO%ld \"%s\"\n", r->index, togreek(r->u.named.name));
         1653                 break;
         1654 
         1655         case NamedNGC:
         1656                 if(r->index <= NNGC)
         1657                         Bprint(&bout, "NGC%ld \"%s\"\n", r->index, togreek(r->u.named.name));
         1658                 else
         1659                         Bprint(&bout, "IC%ld \"%s\"\n", r->index-NNGC, togreek(r->u.named.name));
         1660                 break;
         1661 
         1662         case NamedAbell:
         1663                 Bprint(&bout, "Abell%ld \"%s\"\n", r->index, togreek(r->u.named.name));
         1664                 break;
         1665 
         1666         case PatchC:
         1667                 radec(r->index, &rah, &ram, &dec);
         1668                 Bprint(&bout, "%dh%dm %d\n", rah, ram, dec);
         1669                 break;
         1670         }
         1671 }