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