/*****************************************************************************
******************************************************************************
**<AUTO>
**
** FILE:	usno.c
**
**<HTML>
**	This file contains functions to read the USNO-B catalog.
**</HTML>
**</AUTO>
**
**
** ENVIRONMENT:
**	C
**
******************************************************************************
******************************************************************************
*/

#include <dervish.h>
#include "taCatalogs.h"

#define nChunks 96               /* Number of chunks per zone */
#define nMaxPerRead 100000       /* Maximum objects to read at once */
#define arrayIncr   100000       /* Number of objects per memory allocation */

/* Each zone is divided into 96 chunks, 3.75 degrees wide in ra, by the
 * accelerator file. */
#define nZones      1800         /* Number of dec zones */
#define zoneWidth   0.1          /* Width of each zone in dec (deg) */
#define chunkSize   3.75         /* Width of each chunk in ra (deg) */
#define stripeWidth 3.0          /* Generous width of each scan in nu (deg) */

static int versionStringLength = 80;   /* Maximum length of version strings */


/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taUSNOMax
**
**<HTML>
**	Determine the maximum number of stars from USNO-B that will intersect
**	a strip, 3 degrees wide, along a section of a great circle (equinox
**	J2000).
**	This reads the accelerator files only, counting the objects within each
**	chunk that intersects the scan, without reading the actual objects and
**	determining which objects in the chunk don't intersect.
** <p>
**	Returns the maximum number of intersecting objects, or -1 on error.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
taUSNOMax(double node          /* IN: ascending node of GC (degrees) */,
	  double incl          /* IN: inclination of GC (degrees) */,
	  double minMu         /* IN: minimum mu scanned (degrees) */,
	  double maxMu         /* IN: maximum mu scanned (degrees) */,
	  double minNu         /* IN: minimum nu scanned (degrees) */,
	  double maxNu         /* IN: maximum nu scanned (degrees) */,
	  const char *catDir   /* IN: directory containing USNO files */)
{
  int zone, ira, i;
  double ra, dec, mu, nu;
  int first;                 /* Have we read the accelerator file yet? */
  char fileName[300];
  int frec[nChunks], nrec[nChunks];
  int nObjs = 0;
  int zone1, zone2, raDec, chunk1, chunk2, chunk;
  FILE *fp;
  double chunkRadius, overlapRadius;

  /* Are limits in ra/dec, as indicated by incl=0 (speeds things if they are)*/
  if (fabs(incl) < 0.0000001)
    raDec = 1;
  else
    raDec = 0;

  /* Each chunk is 3.75 degrees in RA by 0.1 degrees in dec, for a maximum
   * radius of 1.9 degrees.  Each scan is about 2.5 degrees wide, which we'll
   * generously treat as 3.0 deg.  Thus, we will read a chunk if it's center
   * lies within 3.0/2. + 1.9 of the stripe great circle. */
  chunkRadius = sqrt(zoneWidth * zoneWidth / 4. + chunkSize * chunkSize / 4);
  overlapRadius = stripeWidth / 2. + chunkRadius;  /* degrees */

  /* Loop over USNO zones.  The zones are each 0.1 deg wide in south
   * polar distance (SPD), starting with 0 < SPD < 0.1. */
  if (raDec)
    {
      zone1 = (int) ((minNu + 90.) / zoneWidth);
      zone2 = (int) ((maxNu + 90.) / zoneWidth);
      chunk1 = (int) (minMu / chunkSize);
      chunk2 = (int) (maxMu / chunkSize);
      if (chunk2 < chunk1) chunk2 += nChunks;
    }
  else
    {
      zone1 = 0;
      zone2 = nZones - 1;
      chunk1 = 0;
      chunk2 = nChunks - 1;
    }
  for (zone = zone1; zone <= zone2; zone++)
    {
      first = 1;

      /* For each 3.75 degree chunk (as divided up in the acc file) ... */
      for (chunk = chunk1; chunk <= chunk2; chunk++)
	{
	  ira = chunk;
	  if (ira >= nChunks) ira -= nChunks;
	  if (! raDec)
	    {
	      /* Compute chunk center in great circle coordinates. */
	      ra = (ira + 0.5) * chunkSize;
	      dec = (zone + 0.5) * zoneWidth - 90.;
	      atEqToGC(ra, dec, &mu, &nu, node, incl);

	      /* Skip chunk if it doesn't overlap the scan. */
	      if (mu + overlapRadius < minMu) mu = mu + 360.;
	      if (mu - overlapRadius > maxMu) continue;
	      if (maxNu > minNu)
		{
		  if (nu + overlapRadius < minNu) continue;
		  if (nu - overlapRadius > maxNu) continue;
		}
	      else
		{
		  if (fabs(nu) > overlapRadius) continue;
		}
	    }

	  /* It overlaps the scan.  Read it in. */

	  /* If first chunk, read the accelerator file. */
	  if (first == 1)
	    { 
	      /* Read the accelerator file */
	      sprintf(fileName, "%s/%03d/b%04d.acc", catDir, zone / 10, zone);
	      if ((fp = fopen(fileName, "r")) == (FILE *)NULL)
		{
		  shErrStackPush("taUSNOMax: unable to open acc file %s\n",
				 fileName);
		  return -1;
		}
	      for (i = 0; i < nChunks; i++)
		if (fscanf(fp, "%*lf %d %d", &(frec[i]), &(nrec[i])) != 2)
		  {
		    fclose(fp);
		    shErrStackPush("taUSNOMax: error reading acc file %s\n",
				   fileName);
		    return -1;
		  }
	      fclose (fp);
	      first = 0;
	    }
	  nObjs += nrec[ira];
	}
    }
  return nObjs;
}


/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taUSNORead
**
**<HTML>
**	Read in that portion of USNOB that intersects a scan, as
**	defined	by a strip, 3 degrees wide, along a section of a great circle.
**</HTML>
**
**</AUTO>
**============================================================================
*/
TA_USNO_CATALOG*
taUSNORead(double node          /* IN: ascending node of GC (degrees) */,
	   double incl          /* IN: inclination of GC (degrees) */,
	   double equinox       /* IN: equinox of GC coords (years) */,
	   double minMu         /* IN: minimum mu scanned (degrees) */,
	   double maxMu         /* IN: maximum mu scanned (degrees) */,
	   double minNu         /* IN: minimum nu scanned (degrees) */,
	   double maxNu         /* IN: maximum nu scanned (degrees) */,
	   const char *catDir   /* IN: directory containing USNOB files */,
	   double epoch         /* IN: Apply proper motions to this epoch
				 *     (Julian years) */,
	   int maxObjects       /* IN: maximum number of objects expected */,
	   TA_USNO_CATALOG *catalog /* MOD: catalog (NULL for new start) */)
{
  int zone;
  int arraySize, k;
  char fileName[300];
  int newCatalog = 0;
  int zone1, zone2, raDec, field, i, nRead;
  double fieldEpoch[11];
  FILE *fp;

  /* Validate inputs */
  if (catalog != NULL) shAssert(maxObjects > 0);

  /* Equinox must be J2000 */
  if (fabs(equinox - 2000.) > 0.00001)
    {
      shErrStackPush("taUSNORead: equinox not J2000");
      return NULL;
    }

  /* Are limits in ra/dec, as indicated by incl=0 (speeds things if they are)*/
  if (fabs(incl) < 0.0000001)
    raDec = 1;
  else
    raDec = 0;

  /* Create the TA_USNO_CATALOG */
  if (catalog == NULL)
    {
      newCatalog = 1;
      catalog = shMalloc(sizeof(TA_USNO_CATALOG));
      shAssert(catalog != NULL);
      catalog->version = shMalloc(versionStringLength * sizeof(char));
      shAssert(catalog->version != NULL);
      sprintf(catalog->version, "USNO-B1.0");
      for (k = 0; k < 10000; k++)
	catalog->epoch[k] = 0;
      catalog->catEpoch = epoch;

      /* Read the field epochs file */
      sprintf(fileName, "%s/epochs.dat", catDir);
      if ((fp = fopen(fileName, "r")) == NULL)
	{
	  shErrStackPush("taUSNORead: couldn't open epochs file");
	  return NULL;
	}
      while ((nRead = fscanf(fp, "%d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
			     &field, &(fieldEpoch[0]), &(fieldEpoch[1]),
			     &(fieldEpoch[2]), &(fieldEpoch[3]),
			     &(fieldEpoch[4]), &(fieldEpoch[5]),
			     &(fieldEpoch[6]), &(fieldEpoch[7]),
			     &(fieldEpoch[8]), &(fieldEpoch[9]),
			     &(fieldEpoch[10]))) != EOF)
	{
	  /* Check that it read enough values. */
	  if (nRead != 12)
	    {
	      shErrStackPush("taUSNORead: bad line in epochs file");
	      return NULL;
	    }

	  /* Stuff in epochs array. */
	  for (i = 0; i < 10; i++)
	    if (fieldEpoch[i] > 0)
	      {
		shAssert(fieldEpoch[i] > 1930. && fieldEpoch[i] < 2010.);
		catalog->epoch[i*1000+field] = fieldEpoch[i];
	      }
	}

      /* Close the file */
      fclose(fp);

      /* Initial allocation */
      if (maxObjects <= 0)
	{
	  /* Determine the maximum number of objects which intersect the scan,
	   * so that we can make one memory allocation for that amount. */
	  if ((arraySize = taUSNOMax(node, incl, minMu, maxMu, minNu, maxNu,
				     catDir)) == -1)
	    return NULL;
	}
      else
	{
	  arraySize = maxObjects;
	}

      /* Initialize the output array of USNO objects. */
      if ((catalog->obj = (TA_USNO*) shMalloc(arraySize*sizeof(TA_USNO)))==NULL)
	{
	  shErrStackPush("taUSNORead: couldn't allocate memory");
	  return NULL;
	}
    }
  else
    {
      arraySize = maxObjects;
    }
  catalog->nObj = 0;

  /* Loop over USNO zones.  The zones are each 0.1 degrees wide in south
   * polar distance (SPD), starting with 0 < SPD < 0.1. */
  if (raDec)
    {
      zone1 = (int) ((minNu + 90.) / zoneWidth);
      zone2 = (int) ((maxNu + 90.) / zoneWidth);
    }
  else
    {
      zone1 = 0;
      zone2 = nZones - 1;
    }
  for (zone = zone1; zone <= zone2; zone++)
    {
      if (taUSNOZoneRead(node, incl, minMu, maxMu, minNu, maxNu, catDir,
			 epoch, zone, catalog, &arraySize) != SH_SUCCESS)
	{
	  if (catalog->obj != NULL) shFree(catalog->obj);
	  return NULL;
	};
    }

  /* Resize arrays to final size */
  if (newCatalog == 1 && maxObjects <= 0 && catalog->nObj < arraySize
      && catalog->nObj > 0)
    {
      if ((catalog->obj = (TA_USNO*) shRealloc(catalog->obj,
					       catalog->nObj*sizeof(TA_USNO)))
	  == NULL)
	{
	  shErrStackPush("taUSNORead: couldn't reallocate memory");
	  return NULL;
	}
    }

  /* Return */
  return catalog;
}


/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taUSNOZoneRead
**
**<HTML>
**	Read in that portion of a USNO-B zone that intersects a scan, as
**	defined	by a strip along a section of a great circle.
**	The great circle definition is assumed to be for equinox J2000.
**</HTML>
**
**</AUTO>
**============================================================================
*/
RET_CODE
taUSNOZoneRead(double node          /* IN: ascending node of GC (degrees) */,
	       double incl          /* IN: inclination of GC (degrees) */,
	       double minMu         /* IN: minimum mu scanned (degrees) */,
	       double maxMu         /* IN: maximum mu scanned (degrees) */,
	       double minNu         /* IN: minimum nu scanned (degrees) */,
	       double maxNu         /* IN: maximum nu scanned (degrees) */,
	       const char *catDir   /* IN: directory containing USNO files */,
	       double epoch         /* IN: Apply proper motions to this epoch
				     *     (Julian years) */,
	       int zone             /* IN: USNO-B SPD zone */,
	       TA_USNO_CATALOG *catalog /* MOD: USNO catalog */,
	       int *arraySize       /* MOD: number of objects allocated */)
{
  int ira, i, raInt, spdInt;
  double ra, dec, lambda, eta, mu, nu;
  int first = 1;                 /* Have we read the accelerator file yet? */
  char fileName[300];
  FILE *fp;
  int fd, nToGo, nToRead;
  int frec[nChunks], nrec[nChunks];
  long pos;
  int buf[nMaxPerRead*15];
  char *swapBuf, b;
  size_t nBytes;
  const int recSize = 80;   /* Size of each object record (bytes) */
  const int intSize = 20;   /* Size of each object record (ints) */
  int field[5], mag[5], nFit;
  double chunkRadius, overlapRadius, raPlate[5], decPlate[5];
  double raAtEpoch, decAtEpoch;
  int catInt, k, pmRa, pmDec, tmpField;
  int raDec, chunk1, chunk2, chunk;
  int nGot;

  /* Are limits in ra/dec, as indicated by incl=0 (speeds things if they are)*/
  if (fabs(incl) < 0.0000001)
    raDec = 1;
  else
    raDec = 0;

  /* Each chunk is 3.75 degrees in RA by 0.1 degrees in dec, for a maximum
   * radius of 1.9 degrees.  Each scan is about 2.5 degrees wide, which
   * we'll generously treat as 3.0 deg.  Thus, we will read a chunk if it's
   * center lies within 3.0/2. + 1.9 of the stripe great circle. */
  chunkRadius = sqrt(zoneWidth * zoneWidth / 4. + chunkSize * chunkSize / 4);
  overlapRadius = stripeWidth / 2. + chunkRadius;  /* degrees */

  /* For each 3.75 degree chunk (as divided up in the accelerator file) ... */
  if (raDec)
    {
      chunk1 = (int) (minMu / chunkSize);
      chunk2 = (int) (maxMu / chunkSize);
      if (chunk2 < chunk1) chunk2 += nChunks;
    }
  else
    {
      chunk1 = 0;
      chunk2 = nChunks - 1;
    }
  for (chunk = chunk1; chunk <= chunk2; chunk++)
    {
      ira = chunk;
      if (ira >= nChunks) ira -= nChunks;
      if (! raDec)
	{
	  /* Compute chunk center in great circle coordinates. */
	  ra = (ira + 0.5) * chunkSize;
	  dec = (zone + 0.5) * zoneWidth - 90.;
	  atEqToGC(ra, dec, &mu, &nu, node, incl);

	  /* Skip chunk if it doesn't overlap the scan. */
	  if (mu + overlapRadius < minMu) mu = mu + 360.;
	  if (mu - overlapRadius > maxMu) continue;
	  if (maxNu > minNu)
	    {
	      if (nu + overlapRadius < minNu) continue;
	      if (nu - overlapRadius > maxNu) continue;
	    }
	  else
	    {
	      if (fabs(nu) > overlapRadius) continue;
	    }
	}

      /* It overlaps the scan.  Read it in. */

      /* If first chunk, read the accelerator file and open the data file. */
      if (first == 1)
	{ 
	  /* Read the accelerator file */
	  sprintf(fileName, "%s/%03d/b%04d.acc", catDir, zone / 10, zone);
	  if ((fp = fopen(fileName, "r")) == (FILE *)NULL)
	    {
	      shErrStackPush("taUSNOZoneRead: unable to open acc file %s\n",
			     fileName);
	      return SH_GENERIC_ERROR;
	    }
	  for (i = 0; i < nChunks; i++)
	    if (fscanf(fp, "%*lf %d %d", &(frec[i]), &(nrec[i])) != 2)
	      {
		fclose(fp);
		shErrStackPush("taUSNOZoneRead: error reading acc file %s\n",
			       fileName);
		return SH_GENERIC_ERROR;
	      }
	  fclose (fp);

	  /* Open the data file */
	  sprintf(fileName, "%s/%03d/b%04d.cat", catDir, zone / 10, zone);
	  if ((fd = open(fileName, 0)) == -1)
	    {
	      shErrStackPush("taUSNOZoneRead: unable to open cat file %s\n",
			     fileName);
	      return SH_GENERIC_ERROR;
	    }
	  first = 0;
	}

      /* Go to the start of the chunk */
      /* Accelerator files are 1-indexed */
      pos = (frec[ira] - 1) * recSize;
      if (lseek(fd, pos, SEEK_SET) != pos)
	{
	  shErrStackPush("taUSNOZoneRead: unable to lseek cat file %s\n",
			 fileName);
	  return SH_GENERIC_ERROR;
	}
      
      /* Read the objects, nMaxPerRead at a time */
      nToGo = nrec[ira];
      while (nToGo > 0)
	{
	  nToRead = (nToGo <= nMaxPerRead ? nToGo : nMaxPerRead);
	  nBytes = nToRead * recSize;
	  if (read(fd, buf, nBytes) != nBytes)
	    {
	      shErrStackPush("taUSNOZoneRead: too few bytes read");
	      return SH_GENERIC_ERROR;
	    }

#ifdef SKIP
	  /*#ifndef SDSS_LITTLE_ENDIAN*/
	  /* USNO-B stored in LITTLE_ENDIAN order. Byte swap if necessary */
	  swapBuf = (char*) &(buf[0]);
	  for (i = 0; i < nBytes; i += 4)
	    {
	      b = swapBuf[i];
	      swapBuf[i] = swapBuf[i+3];
	      swapBuf[i+3] = b;
	      b = swapBuf[i+1];
	      swapBuf[i+1] = swapBuf[i+2];
	      swapBuf[i+2] = b;
	    }
#endif

	  /* Copy the objects to the array */
	  for (i = 0; i < nToRead; i++)
	    {
	      /* Include only those objects with nFit >= 3 */
	      catInt = buf[i*intSize+3];
	      nFit = (catInt / 100000000) % 10;
	      if (nFit < 3) continue;

	      /* Get coordinates (degrees) */
	      raInt = buf[i*intSize];
	      spdInt = buf[i*intSize+1];
	      ra = raInt / 360000.;
	      dec = spdInt / 360000. - 90.;
	      raAtEpoch = ra;
	      decAtEpoch = dec;
	      
	      /* Get and apply proper motions */
	      catInt = buf[i*intSize+2];
	      pmRa = ((catInt%10000) - 5000) * 2; /* mas/yr */
	      pmDec = ((catInt/10000)%10000 - 5000) * 2; /* mas/yr */
	      if (pmRa != 0 || pmDec != 0)
		{
		  /* Good proper motion measured.  Apply it. */
		  raAtEpoch = ra + (pmRa / 3600000.) *
		    (epoch - 2000.) / cos(dec * at_deg2Rad);
		  decAtEpoch = dec + (pmDec / 3600000.) *
		    (epoch - 2000.);
		}

	      /* Reject object outside mu/nu boundaries */
	      atEqToGC(raAtEpoch, decAtEpoch, &mu, &nu, node, incl);
	      if (mu < minMu) mu = mu + 360.;
	      if (mu > maxMu) continue;
	      if (maxNu > minNu)
		 {
		    if (nu < minNu) continue;
		    if (nu > maxNu) continue;
		 }
	      else
		 {
		    if (fabs(nu) > stripeWidth / 2.) continue;
		 }
	      
	      /* Clear all plate info */
	      for (k = 0; k < 5; k++)
		{
		  mag[k] = 0;
		  field[k] = 0;
		  raPlate[k] = 0;
		  decPlate[k] = 0;
		}

	      /* For each plate ... */
	      nGot = 0;
	      for (k = 0; k < 5; k++)
		{
		  /* Skip if it wasn't detected on this plate */
		  catInt = buf[i*intSize+5+k];
		  if (catInt == 0) continue;
		  tmpField = (catInt / 10000) % 10000;
		  if (tmpField == 0) continue;
		  
		  /* Detected on this plate */
		  nGot++;

		  /* Verify plate number is valid */
		  switch (k)
		    {
		    case 0:
		      shAssert(tmpField > 0 && tmpField < 1000);
		      break;
		    case 1:
		      shAssert((tmpField > 1000 && tmpField < 2000) ||
			       (tmpField > 5000 && tmpField < 5607));
		      break;
		    case 2:
		      shAssert((tmpField > 2000 && tmpField < 3000) ||
			       (tmpField > 4000 && tmpField < 5000));
		      break;
		    case 3:
		      shAssert((tmpField > 3000 && tmpField < 4000) ||
			       (tmpField > 5606 && tmpField < 5895) ||
			       (tmpField > 6000 && tmpField < 7000));
		      break;
		    case 4:
		      shAssert(tmpField > 7000 && tmpField < 10000);
		      break;
		    }
		  shAssert(catalog->epoch[tmpField] > 1930. &&
			   catalog->epoch[tmpField] < 2010.);

		  /* Fill in plate info */
		  shAssert(field[k] == 0);
		  field[k] = tmpField;
		  mag[k] = catInt % 10000;
		  if (nFit > 1)
		    {
		      /* Determine plate position */
		      catInt = buf[i*intSize+10+k];
		      raPlate[k] = ra + (pmRa / 3600000.) *
			(catalog->epoch[field[k]] - 2000.) /
			cos(dec * at_deg2Rad);
		      decPlate[k] = dec + (pmDec / 3600000.) *
			(catalog->epoch[field[k]] - 2000.);
		      raPlate[k] += ((catInt%10000) - 5000.) /
			(360000. * cos(dec * at_deg2Rad));
		      decPlate[k] += ((catInt/10000)%10000 - 5000.) /
			360000.;
		    }
		  else
		    {
		      /* No proper motion measured. */
		      raPlate[k] = ra;
		      decPlate[k] = dec;
		    }
		}
	      shAssert(nGot == nFit);

	      /* Reallocate memory if necessary */
	      if (catalog->nObj == *arraySize)
		{
		  *arraySize += arrayIncr;
		  if ((catalog->obj =
		       (TA_USNO*) shRealloc(catalog->obj,
					    *arraySize*sizeof(TA_USNO)))
		      == NULL)
		    {
		      shErrStackPush("taUSNOZoneRead: couldn't reallocate memory");
		      return SH_GENERIC_ERROR;
		    }
		}

	      /* Add object to the array of potentially matching objects. */
	      catalog->obj[catalog->nObj].ra =
		 (int) (raAtEpoch * 360000. + 0.5);
	      catalog->obj[catalog->nObj].spd =
		 (int) ((decAtEpoch + 90.) * 360000. + 0.5);
	      catalog->obj[catalog->nObj].ra2000 = raInt;
	      catalog->obj[catalog->nObj].spd2000 = spdInt;
		    
	      /* Set magnitudes and fields. */
	      for (k = 0; k < 5; k++)
		 {
		    catalog->obj[catalog->nObj].mag[k] = mag[k];
		    catalog->obj[catalog->nObj].field[k] = field[k];
		    catalog->obj[catalog->nObj].plateRa[k] =
		      (int) (raPlate[k] * 360000. + 0.5);
		    catalog->obj[catalog->nObj].plateSpd[k] =
		      (int) ((decPlate[k] + 90.) * 360000. + 0.5);
		 }

	      /* Remaining attributes */
	      catalog->obj[catalog->nObj].pmRa = pmRa;
	      catalog->obj[catalog->nObj].pmDec = pmDec;
	      atEqToSurvey(raAtEpoch, decAtEpoch, &lambda, &eta);
	      catalog->obj[catalog->nObj].lambda =
		(int) (lambda * 360000. + 0.5);
	      catalog->nObj++;
	    }
	  nToGo -= nToRead;
	}
    }

  /* Close up */
  if (first == 0) close(fd);
  return SH_SUCCESS;
}


/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taUSNOToEpoch
**
**<HTML>
**	Convert positions in a USNO catalog to the specified epoch.
**</HTML>
**
**</AUTO>
**============================================================================
*/
void
taUSNOToEpoch(TA_USNO_CATALOG *catalog /* MOD: USNO catalog */,
	      double epoch         /* IN: Apply proper motions to this epoch
				    *     (Julian years) */)
{
  int i;
  TA_USNO *obj;
  double dec, lambda, eta;

  for (i = 0; i < catalog->nObj; i++)
    {
      obj = &(catalog->obj[i]);
      if (obj->pmRa != 0 || obj->pmDec != 0)
	{
	  /* Good proper motion measured.  Apply it. */
	  dec = obj->spd2000 / 360000. - 90.;
	  obj->ra = (int) (obj->ra2000 + obj->pmRa / 10. * (epoch - 2000.) /
			    cos(dec * at_deg2Rad) + 0.5);
	  if (obj->ra < 0) obj->ra += 360 * 360000;
	  if (obj->ra > 360 * 360000) obj->ra -= 360 * 360000;
	  obj->spd =
	    (int) (obj->spd2000 + obj->pmDec / 10. * (epoch - 2000.) + 0.5);
	  atEqToSurvey(obj->ra / 360000., obj->spd / 360000. - 90.,
		       &lambda, &eta);
	  obj->lambda = (int) (lambda * 360000. + 0.5);
	}
    }
  catalog->catEpoch = epoch;
}
