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