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