xref: /plan9/sys/src/cmd/scat/scat.c (revision 22a127bbfe4dd304949cc596400de973c0138e31)
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 
main(int argc,char * argv[])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
reset(void)80 reset(void)
81 {
82 	nrec = 0;
83 	cur = rec;
84 }
85 
86 void
grow(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
copy(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
eopen(char * s)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
Eread(int f,char * name,void * addr,long n)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*
skipbl(char * s)142 skipbl(char *s)
143 {
144 	while(*s!=0 && (*s==' ' || *s=='\t'))
145 		s++;
146 	return s;
147 }
148 
149 char*
skipstr(char * s,char * t)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
Long(long * l)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
Short(short * s)169 Short(short *s)
170 {
171 	uchar *p;
172 
173 	p = (uchar*)s;
174 	return p[0]|(p[1]<<8);
175 }
176 
177 void
nameopen(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
saoopen(void)223 saoopen(void)
224 {
225 	if(saodb == 0){
226 		nameopen();
227 		saodb = eopen("sao");
228 	}
229 }
230 
231 void
ngcopen(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
abellopen(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
patchopen(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
mopen(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
constelopen(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
lowercase(char * s)302 lowercase(char *s)
303 {
304 	for(; *s; s++)
305 		if('A'<=*s && *s<='Z')
306 			*s += 'a'-'A';
307 }
308 
309 int
loadngc(long index)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
loadabell(long index)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
loadsao(int index)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
loadplanet(int index,Record * r)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
loadpatch(long index)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
loadtype(int t)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
flatten(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
ism(int index)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*
alpha(char * s,char * t)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*
text(char * s,char * t)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
cull(char * s,int keep,int dobbox)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
compar(void * va,void * vb)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
sort(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*
togreek(char * s)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*
fromgreek(char * s)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
coords(int deg)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
coords(int deg)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
pplate(char * flags)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
lookup(char * s,int doreset)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*
ngcstring(int d)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
printnames(Record * r)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
equal(char * s1,char * s2)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
parsename(char * s)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*
dist_grp(int dg)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*
rich_grp(int dg)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*
nameof(Record * r)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
prrec(Record * r)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 }
1668