plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

scat.c (30145B)


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