Index: trunk/Ohana/src/libdvo/Makefile
===================================================================
--- trunk/Ohana/src/libdvo/Makefile	(revision 36489)
+++ trunk/Ohana/src/libdvo/Makefile	(revision 36490)
@@ -107,6 +107,6 @@
 $(SRC)/convert.$(ARCH).o                \
 $(SRC)/HostTable.$(ARCH).o              \
-$(SRC)/BoundaryTree.$(ARCH).o
-
+$(SRC)/BoundaryTree.$(ARCH).o           \
+$(SRC)/TessellationTable.$(ARCH).o
 
 # $(SRC)/dvo_convert_panstarrs.$(ARCH).o 
Index: trunk/Ohana/src/libdvo/include/dvo.h
===================================================================
--- trunk/Ohana/src/libdvo/include/dvo.h	(revision 36489)
+++ trunk/Ohana/src/libdvo/include/dvo.h	(revision 36490)
@@ -308,4 +308,6 @@
 # define BOUNDARY_TREE_NAME_LENGTH 128
 
+// BoundaryTree is a structure to describe the 3pi RINGS skycell boundaries in terms of lines of constant (RA,DEC)
+// the structure is flexible for a variety of RINGS-like tessellations, but is not appropriate for the LOCAL style tess
 typedef struct {
   int FixedGridDEC;	      // is the DEC sequence linear?
@@ -342,13 +344,36 @@
 } BoundaryTree;
 
-// XXX DROP? // a reduced-subset structure for relastro
-// XXX DROP? typedef struct {
-// XXX DROP?   double         R;
-// XXX DROP?   double         D;
-// XXX DROP?   unsigned short Nmeasure;
-// XXX DROP?   int            measureOffset;
-// XXX DROP?   uint32_t       flags;
-// XXX DROP?   int            catID;
-// XXX DROP? } AverageTinyAstro;
+typedef enum { TESS_NONE, TESS_LOCAL, TESS_RINGS } TessType;
+
+// TessellationTable is a structure to describe the parameters of a set of "tessellations"
+// (these are not strictly tessellations but projection sets as only the non-local
+// versions can cover the full sky).  For LOCAL projection cells, the structure describes
+// the boundaries of a SINGLE projection cell with Nx * Ny skycells and includes some
+// basic parameters (not used by the fullsky, eg RINGS, tessellations)
+typedef struct {
+  double Rmin; // this tessellation is valid only for RA >= Rmin
+  double Rmax; // this tessellation is valid only for RA <  Rmax
+  double Dmin; // this tessellation is valid only for DEC >= Dmin
+  double Dmax; // this tessellation is valid only for DEC <  Dmax
+
+  double Xo;
+  double Yo;
+  double Ro;
+  double Do;
+  double dPix;
+  int dX;
+  int dY;
+
+  int NX_SUB;
+  int NY_SUB;
+
+  char *basename;
+  int Nbasename;
+  int projectIDoff;
+  int skycellIDoff;
+
+  TessType type; // 
+  BoundaryTree *tree;
+} TessellationTable;
 
 // a reduced-subset structure for relphot
@@ -387,4 +412,61 @@
 } MeasureTiny;
 
+
+/** STRUCT DEFINITION **/
+typedef struct {
+  float            dR;                   // RA offset (arcsec)
+  float            dD;                   // DEC offset (arcsec)
+  float            M;                    // catalog mag (mag)
+  float            Mcal;                 // image cal mag (mag)
+  float            Map;                  // aperture mag (mag)
+  float            Mkron;                // kron magnitude (mag)
+  float            dMkron;               // kron magnitude error (mag)
+  float            dM;                   // mag error (mag)
+  float            dMcal;                // systematic calibration error (mag)
+  float            dt;                   // exposure time (2.5*log(exptime))
+  float            FluxPSF;              // flux from psf fit (counts/sec?)
+  float            dFluxPSF;             // error on psf flux (counts/sec?)
+  float            FluxKron;             // flux from kron ap (counts/sec?)
+  float            dFluxKron;            // error on kron flux (counts/sec?)
+  float            airmass;              // (airmass - 1) (airmass)
+  float            az;                   // telescope azimuth
+  float            Xccd;                 // X coord on chip (raw value) (pixels)
+  float            Yccd;                 // Y coord on chip (raw value) (pixels)
+  float            Sky;                  // local estimate of sky flux (counts/sec)
+  float            dSky;                 // local estimate of sky flux (counts/sec)
+  int              t;                    // time in seconds (UNIX)
+  unsigned int     averef;               // reference to average entry      
+  unsigned int     detID;                // detection ID
+  unsigned int     imageID;              // reference to DVO image ID
+  unsigned int     objID;                // unique ID for object in table
+  unsigned int     catID;                // unique ID for table in which object was first realized
+  uint64_t         extID;                // external ID (eg PSPS detID)
+  float            psfQF;                // psf coverage/quality factor
+  float            psfQFperf;            // psf coverage / quality factor (all mask bits)
+  float            psfChisq;             // psf fit chisq
+  int              psfNdof;              // psf degrees of freedom
+  int              psfNpix;              // psf number of pixels
+  float            crNsigma;             // Nsigma deviation towards CR
+  float            extNsigma;            // Nsigma deviation towards EXT
+  short            FWx;                  // object fwhm major axis (1/100 of pixels)
+  short            FWy;                  // object fwhm minor axis (1/100 of pixels )
+  short            theta;                // angle wrt ccd X dir ((0xffff/360) deg)
+  short            Mxx;                  // second moments in pixel coords (1/100 of pixels)
+  short            Mxy;                  // second moments in pixel coords (1/100 of pixels)
+  short            Myy;                  // second moments in pixel coords (1/100 of pixels)
+  unsigned short   t_msec;               // time fraction of second (milliseconds)
+  unsigned short   photcode;             // photcode
+  short            dXccd;                // X coord error on chip (1/100 of pixels)
+  short            dYccd;                // Y coord error on chip (1/100 of pixels)
+  short            dRsys;                // systematic error from astrom (1/100 of pixels)
+  short            posangle;             // position angle sky to chip ((0xffff/360) deg)
+  float            pltscale;             // plate scale (arcsec/pixel)
+  unsigned int     dbFlags;              // flags supplied by analysis in database
+  unsigned int     photFlags;            // flags supplied by photometry program
+} Measure_PS1_V4alt;
+
+Measure_PS1_V4alt *gfits_table_get_Measure_PS1_V4alt (FTable *table, off_t *Ndata, char *swapped);
+Measure *Measure_PS1_V4alt_ToInternal (Measure_PS1_V4alt *in, off_t Nvalues);
+
 /* a catalog contains this data */
 typedef struct Catalog {
@@ -717,8 +799,17 @@
 int free_tiny_values (Catalog *catalog);
 
+BoundaryTree *BoundaryTreeLoad(char *filename);
+BoundaryTree *BoundaryTreeRead(Header *headerPHU, Header *headerZone, FILE *f);
+
+int BoundaryTreeSave(char *filename, BoundaryTree *tree);
+int BoundaryTreeWrite(FILE *f, BoundaryTree *tree);
+
 int BoundaryTreeCellCoords (BoundaryTree *tree, int *zone, int *band, double ra, double dec);
-int BoundaryTreeSave(char *filename, BoundaryTree *tree);
-BoundaryTree *BoundaryTreeLoad(char *filename);
 int BoundaryTreeProjection (double *x, double *y, double r, double d, BoundaryTree *tree, int zone, int band);
+
+TessellationTable *TessellationTableLoad(char *filename, int *Ntess);
+int TessellationTableSave(char *filename, TessellationTable *tess, int Ntess);
+int TessellationPrimaryCellIDs (TessellationTable *tess, int Ntess, int *tessID, int *projID, int *skycellID, double ra, double dec);
+void TessellationTableInit (TessellationTable *tess, int Ntess);
 
 void dvo_average_init (Average *average);
Index: trunk/Ohana/src/libdvo/include/dvodb.h
===================================================================
--- trunk/Ohana/src/libdvo/include/dvodb.h	(revision 36489)
+++ trunk/Ohana/src/libdvo/include/dvodb.h	(revision 36490)
@@ -375,5 +375,5 @@
 
 // Some values used by code moved to libdvo from opihi.
-enum {OPIHI_FLT, OPIHI_INT};
+enum {OPIHI_NOTYPE, OPIHI_FLT, OPIHI_INT};
 #define opihi_flt double
 #define opihi_int int
Index: trunk/Ohana/src/libdvo/src/BoundaryTree.c
===================================================================
--- trunk/Ohana/src/libdvo/src/BoundaryTree.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/BoundaryTree.c	(revision 36490)
@@ -1,10 +1,10 @@
 # include "dvo.h"
 
-# define GET_COLUMN_NEW(OUT,NAME,TYPE)					\
-  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+# define GET_COLUMN_NEW(HEADER,FTABLE,OUT,NAME,TYPE)				\
+  TYPE *OUT = gfits_get_bintable_column_data (HEADER, FTABLE, NAME, type, &Nrow, &Ncol); \
   myAssert (!strcmp(type, #TYPE), "wrong column type");
 
-# define GET_COLUMN_RAW(OUT,NAME,TYPE)					\
-  OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+# define GET_COLUMN_RAW(HEADER,FTABLE,OUT,NAME,TYPE)			\
+  OUT = gfits_get_bintable_column_data (HEADER, FTABLE, NAME, type, &Nrow, &Ncol); \
   myAssert (!strcmp(type, #TYPE), "wrong column type");
 
@@ -12,159 +12,182 @@
 
 BoundaryTree *BoundaryTreeLoad(char *filename) {
+
+  Header header;
+  Header theader;
+  Matrix matrix;
+
+  header.buffer = NULL;
+  matrix.buffer = NULL;
+  theader.buffer = NULL;
+  BoundaryTree *tree = NULL;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open boundary tree file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (DEBUG) fprintf (stderr, "can't read boundary tree header\n");
+    goto escape;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (DEBUG) fprintf (stderr, "can't read boundary tree matrix\n");
+    goto escape;
+  }
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) {
+    if (DEBUG) fprintf (stderr, "can't read boundary tree zone table header\n");
+    goto escape;
+  }
+
+  tree = BoundaryTreeRead (&header, &theader, f);
+
+escape:
+
+  gfits_free_header (&theader);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  fclose (f);
+
+  return tree;
+}
+
+// assume we are pointing at the relevant table portion
+BoundaryTree *BoundaryTreeRead(Header *headerPHU, Header *headerZone, FILE *f) {
 
   int i, j, nz, nb, Ncol;
   off_t Nrow;
   char type[16];
-  Header header;
-  Header theader;
-  Matrix matrix;
-  FTable ftable;
-
-  header.buffer = NULL;
-  matrix.buffer = NULL;
-  ftable.buffer = NULL;
-  theader.buffer = NULL;
+
+  FTable ftableZone;
+
+  Header headerCell;
+  FTable ftableCell;
+
+  ftableZone.buffer = NULL;
+  ftableCell.buffer = NULL;
+  headerCell.buffer = NULL;
+
+  // we must have already read in the Zone table header section
+  ftableZone.header =  headerZone;
+  ftableCell.header = &headerCell;
+
   BoundaryTree *tree = NULL;
 
-  FILE *f = fopen (filename, "r");
-  if (!f) {
-    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
-    return NULL;
-  }
-
-  /* load in PHU segment (ignore) */
-  if (!gfits_fread_header (f, &header)) {
-    if (DEBUG) fprintf (stderr, "can't read image subset header\n");
-    goto escape;
-  }
-  if (!gfits_fread_matrix (f, &matrix, &header)) {
-    if (DEBUG) fprintf (stderr, "can't read image subset matrix\n");
-    goto escape;
-  }
-
   ALLOCATE (tree, BoundaryTree, 1);
 
-  gfits_scan (&header, "DEC_ORI", "%lf", 1, &tree->DEC_origin);
-  gfits_scan (&header, "DEC_OFF", "%lf", 1, &tree->DEC_offset);
-
-  gfits_scan (&header, "NX_SUB", "%d", 1, &tree->NX_SUB);
-  gfits_scan (&header, "NY_SUB", "%d", 1, &tree->NY_SUB);
-  gfits_scan (&header, "PIXSCALE", "%lf", 1, &tree->dPix);
-
-  ftable.header = &theader;
+  // we need to read the boundary tree parameters from the correct header
+  // put them in the PHU header in any case?
+  gfits_scan (headerPHU, "DEC_ORI",  "%lf", 1, &tree->DEC_origin);
+  gfits_scan (headerPHU, "DEC_OFF",  "%lf", 1, &tree->DEC_offset);
+  gfits_scan (headerPHU, "NX_SUB",   "%d",  1, &tree->NX_SUB);
+  gfits_scan (headerPHU, "NY_SUB",   "%d",  1, &tree->NY_SUB);
+  gfits_scan (headerPHU, "PIXSCALE", "%lf", 1, &tree->dPix);
 
   /*** zone information table ***/
-  { 
-    // load data for this header 
-    if (!gfits_load_header (f, &theader)) goto escape;
-
-    // read the fits table bytes
-    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) goto escape;
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftableZone, FALSE)) goto escape;
  
-    // need to create and assign to flat-field correction
-    GET_COLUMN_RAW(tree->Nband,       "NBAND",  	 int);
-    GET_COLUMN_RAW(tree->RA_origin,   "RA_ORIGIN",   double);
-    GET_COLUMN_RAW(tree->RA_offset,   "RA_OFFSET",   double);
-    GET_COLUMN_RAW(tree->DEC_min  ,   "DEC_MIN",     double);
-    GET_COLUMN_RAW(tree->DEC_max  ,   "DEC_MAX",     double);
-    GET_COLUMN_RAW(tree->DEC_min_raw, "DEC_MIN_RAW", double);
-    GET_COLUMN_RAW(tree->DEC_max_raw, "DEC_MAX_RAW", double);
-    gfits_free_header (&theader);
-    gfits_free_table  (&ftable);
-
-    fprintf (stderr, "loaded data for %lld zones\n", (long long) Nrow);
-    tree->Nzone = Nrow;
-
-    // allocate the storage arrays
-    ALLOCATE (tree->ra,   double *, tree->Nzone);
-    ALLOCATE (tree->dec,  double *, tree->Nzone);
-    ALLOCATE (tree->Xo,   double *, tree->Nzone);
-    ALLOCATE (tree->Yo,   double *, tree->Nzone);
-    ALLOCATE (tree->dX,      int *, tree->Nzone);
-    ALLOCATE (tree->dY,      int *, tree->Nzone);
-    ALLOCATE (tree->cell,    int *, tree->Nzone);
-    ALLOCATE (tree->projID,  int *, tree->Nzone);
-    ALLOCATE (tree->name,  char **, tree->Nzone);
-    for (i = 0; i < tree->Nzone; i++) {
-      ALLOCATE (tree->ra[i],   double, tree->Nband[i]);
-      ALLOCATE (tree->dec[i],  double, tree->Nband[i]);
-      ALLOCATE (tree->Xo[i],   double, tree->Nband[i]);
-      ALLOCATE (tree->Yo[i],   double, tree->Nband[i]);
-      ALLOCATE (tree->dX[i],      int, tree->Nband[i]);
-      ALLOCATE (tree->dY[i],      int, tree->Nband[i]);
-      ALLOCATE (tree->cell[i],    int, tree->Nband[i]);
-      ALLOCATE (tree->projID[i],  int, tree->Nband[i]);
-      ALLOCATE (tree->name[i], char *, tree->Nband[i]);
-      for (j = 0; j < tree->Nband[i]; j++) {
-	ALLOCATE (tree->name[i][j], char, BOUNDARY_TREE_NAME_LENGTH);
-      }
+  // need to create and assign to flat-field correction
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->Nband,       "NBAND",  	    int);
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->RA_origin,   "RA_ORIGIN",   double);
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->RA_offset,   "RA_OFFSET",   double);
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->DEC_min  ,   "DEC_MIN",     double);
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->DEC_max  ,   "DEC_MAX",     double);
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->DEC_min_raw, "DEC_MIN_RAW", double);
+  GET_COLUMN_RAW(headerZone, &ftableZone, tree->DEC_max_raw, "DEC_MAX_RAW", double);
+  gfits_free_table  (&ftableZone);
+
+  fprintf (stderr, "loaded data for %lld zones\n", (long long) Nrow);
+  tree->Nzone = Nrow;
+
+  // allocate the storage arrays
+  ALLOCATE (tree->ra,   double *, tree->Nzone);
+  ALLOCATE (tree->dec,  double *, tree->Nzone);
+  ALLOCATE (tree->Xo,   double *, tree->Nzone);
+  ALLOCATE (tree->Yo,   double *, tree->Nzone);
+  ALLOCATE (tree->dX,      int *, tree->Nzone);
+  ALLOCATE (tree->dY,      int *, tree->Nzone);
+  ALLOCATE (tree->cell,    int *, tree->Nzone);
+  ALLOCATE (tree->projID,  int *, tree->Nzone);
+  ALLOCATE (tree->name,  char **, tree->Nzone);
+  for (i = 0; i < tree->Nzone; i++) {
+    ALLOCATE (tree->ra[i],   double, tree->Nband[i]);
+    ALLOCATE (tree->dec[i],  double, tree->Nband[i]);
+    ALLOCATE (tree->Xo[i],   double, tree->Nband[i]);
+    ALLOCATE (tree->Yo[i],   double, tree->Nband[i]);
+    ALLOCATE (tree->dX[i],      int, tree->Nband[i]);
+    ALLOCATE (tree->dY[i],      int, tree->Nband[i]);
+    ALLOCATE (tree->cell[i],    int, tree->Nband[i]);
+    ALLOCATE (tree->projID[i],  int, tree->Nband[i]);
+    ALLOCATE (tree->name[i], char *, tree->Nband[i]);
+    for (j = 0; j < tree->Nband[i]; j++) {
+      ALLOCATE (tree->name[i][j], char, BOUNDARY_TREE_NAME_LENGTH);
     }
   }
 
   /*** cell information table ***/
-  { 
-    // load data for this header 
-    if (!gfits_load_header (f, &theader)) goto escape;
-
-    // read the fits table bytes
-    if (!gfits_fread_ftable_data (f, &ftable, FALSE)) goto escape;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &headerCell)) goto escape;
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftableCell, FALSE)) goto escape;
  
-    // need to create and assign to flat-field correction
-    GET_COLUMN_NEW(R,     "RA",   	 double);
-    GET_COLUMN_NEW(D,     "DEC",  	 double);
-    GET_COLUMN_NEW(zone,  "ZONE",        int);
-    GET_COLUMN_NEW(band,  "BAND",        int);
-    GET_COLUMN_NEW(index, "INDEX",       int);
-    GET_COLUMN_NEW(Xo,    "X_CENT",      double);
-    GET_COLUMN_NEW(Yo,    "Y_CENT",      double);
-    GET_COLUMN_NEW(dX,    "X_GRID",      int);
-    GET_COLUMN_NEW(dY,    "Y_GRID",      int);
-    GET_COLUMN_NEW(name,  "NAME",        char); // XXX how is this done?
-    gfits_free_header (&theader);
-    gfits_free_table  (&ftable);
-    fprintf (stderr, "loaded data for %lld cells\n", (long long) Nrow);
-
-    // assign the storage arrays
-    for (i = 0; i < Nrow; i++) {
-      nz = zone[i];
-      nb = band[i];
-      tree->ra[nz][nb] = R[i];
-      tree->dec[nz][nb] = D[i];
-      tree->Xo[nz][nb] = Xo[i];
-      tree->Yo[nz][nb] = Yo[i];
-      tree->dX[nz][nb] = dX[i];
-      tree->dY[nz][nb] = dY[i];
-      tree->cell[nz][nb] = i; // XXX ?
-      memcpy(tree->name[nz][nb], &name[i*BOUNDARY_TREE_NAME_LENGTH], BOUNDARY_TREE_NAME_LENGTH);
-      // XXX parse out the ID from the name (skycell.NNNN)
-      tree->projID[nz][nb] = atoi(&tree->name[nz][nb][8]);
-    }
-
-    free (R     );
-    free (D     );
-    free (zone  );
-    free (band  );
-    free (Xo    );
-    free (Yo    );
-    free (dX    );
-    free (dY    );
-    free (index );
-    free (name  );
-  }
-
-  gfits_free_header (&header);
-  gfits_free_matrix (&matrix);
-  fclose (f);
+  // need to create and assign to flat-field correction
+  GET_COLUMN_NEW(&headerCell, &ftableCell, R,     "RA",   	 double);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, D,     "DEC",  	 double);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, zone,  "ZONE",        int);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, band,  "BAND",        int);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, index, "INDEX",       int);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, Xo,    "X_CENT",      double);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, Yo,    "Y_CENT",      double);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, dX,    "X_GRID",      int);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, dY,    "Y_GRID",      int);
+  GET_COLUMN_NEW(&headerCell, &ftableCell, name,  "NAME",        char); // XXX how is this done?
+  gfits_free_header (&headerCell);
+  gfits_free_table  (&ftableCell);
+
+  fprintf (stderr, "loaded data for %lld cells\n", (long long) Nrow);
+
+  // assign the storage arrays
+  for (i = 0; i < Nrow; i++) {
+    nz = zone[i];
+    nb = band[i];
+    tree->ra[nz][nb] = R[i];
+    tree->dec[nz][nb] = D[i];
+    tree->Xo[nz][nb] = Xo[i];
+    tree->Yo[nz][nb] = Yo[i];
+    tree->dX[nz][nb] = dX[i];
+    tree->dY[nz][nb] = dY[i];
+    tree->cell[nz][nb] = i; // XXX ?
+    memcpy(tree->name[nz][nb], &name[i*BOUNDARY_TREE_NAME_LENGTH], BOUNDARY_TREE_NAME_LENGTH);
+    // XXX parse out the ID from the name (skycell.NNNN)
+    tree->projID[nz][nb] = atoi(&tree->name[nz][nb][8]);
+  }
+
+  free (R     );
+  free (D     );
+  free (zone  );
+  free (band  );
+  free (Xo    );
+  free (Yo    );
+  free (dX    );
+  free (dY    );
+  free (index );
+  free (name  );
 
   return tree;
 
 escape:
-  gfits_free_header (&header);
-  gfits_free_matrix (&matrix);
-  gfits_free_header (&theader);
-  gfits_free_table  (&ftable);
+  gfits_free_header (&headerCell);
+  gfits_free_table  (&ftableCell);
+  gfits_free_table  (&ftableZone);
   if (tree) free (tree);
 
-  fclose (f);
   return NULL;
 }
@@ -173,9 +196,6 @@
 int BoundaryTreeSave(char *filename, BoundaryTree *tree) {
 
-  int i, nz, nb;
   Header header;
-  Header theader;
   Matrix matrix;
-  FTable ftable;
 
   gfits_init_header (&header);
@@ -202,4 +222,16 @@
   gfits_free_header (&header);
   gfits_free_matrix (&matrix);
+
+  BoundaryTreeWrite (f, tree);
+  fclose (f);
+
+  return TRUE;
+}
+
+int BoundaryTreeWrite(FILE *f, BoundaryTree *tree) {
+
+  int i, nz, nb;
+  Header theader;
+  FTable ftable;
 
   /*** zone information table ***/
Index: trunk/Ohana/src/libdvo/src/ImageMetadataSelection.c
===================================================================
--- trunk/Ohana/src/libdvo/src/ImageMetadataSelection.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/ImageMetadataSelection.c	(revision 36490)
@@ -18,5 +18,5 @@
   field.pc1_2  = field.pc2_1  = 0.0;
   field.Npolyterms = 0;
-  strcpy (field.ctype, "RA---SIN");
+  strcpy (field.ctype, "DEC--SIN");
 
   /* mosaic defines a frame with 0,0 at the mosaic center, and 1 arcsec / pixel */
@@ -26,5 +26,5 @@
   mosaic.pc1_2  = mosaic.pc2_1  = 0.0;
   mosaic.Npolyterms = 0;
-  strcpy (mosaic.ctype, "RA---SIN");
+  strcpy (mosaic.ctype, "DEC--SIN");
 
   if ((image = ImageMetadataLoad (filename, &Nimage)) == NULL) return (FALSE);
Index: trunk/Ohana/src/libdvo/src/ImageSelection.c
===================================================================
--- trunk/Ohana/src/libdvo/src/ImageSelection.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/ImageSelection.c	(revision 36490)
@@ -28,5 +28,5 @@
       mosaic.pc1_2  = mosaic.pc2_1  = 0.0;
       mosaic.Npolyterms = 0;
-      strcpy (mosaic.ctype, "RA---SIN");
+      strcpy (mosaic.ctype, "DEC--SIN");
   }
 
Index: trunk/Ohana/src/libdvo/src/TessellationTable.c
===================================================================
--- trunk/Ohana/src/libdvo/src/TessellationTable.c	(revision 36490)
+++ trunk/Ohana/src/libdvo/src/TessellationTable.c	(revision 36490)
@@ -0,0 +1,573 @@
+# include "dvo.h"
+
+# define GET_COLUMN_NEW(OUT,NAME,TYPE)					\
+  TYPE *OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+# define GET_COLUMN_RAW(OUT,NAME,TYPE)					\
+  OUT = gfits_get_bintable_column_data (&theader, &ftable, NAME, type, &Nrow, &Ncol); \
+  myAssert (!strcmp(type, #TYPE), "wrong column type");
+
+# define DEBUG 0
+
+void TessellationTableInit (TessellationTable *tess, int Ntess) {
+
+  int i;
+
+  for (i = 0; i < Ntess; i++) {
+    tess[i].Rmin = NAN;
+    tess[i].Rmax = NAN;
+    tess[i].Dmin = NAN;
+    tess[i].Dmax = NAN;
+
+    tess[i].Xo = NAN;
+    tess[i].Yo = NAN;
+    tess[i].Ro = NAN;
+    tess[i].Do = NAN;
+
+    tess[i].dPix = NAN;
+
+    tess[i].dX = 0;
+    tess[i].dY = 0;
+
+    tess[i].NX_SUB = 0;
+    tess[i].NY_SUB = 0;
+
+    tess[i].basename = NULL;
+    tess[i].Nbasename = 0;
+
+    tess[i].projectIDoff = 0;
+    tess[i].skycellIDoff = 0;
+
+    tess[i].type = TESS_NONE;
+    tess[i].tree = NULL;
+  }
+  return;
+}
+
+// backwards compatible load : if the first table is a BoundaryTree, not a TessellationTable, just load that.
+TessellationTable *TessellationTableLoad(char *filename, int *Ntess) {
+
+  int i, Ncol;
+  off_t Nrow;
+  char type[16];
+  Header header;
+  Header theader;
+  Matrix matrix;
+  FTable ftable;
+
+  header.buffer = NULL;
+  matrix.buffer = NULL;
+  ftable.buffer = NULL;
+  theader.buffer = NULL;
+  TessellationTable *tess = NULL;
+  *Ntess = 0;
+
+  FILE *f = fopen (filename, "r");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open image subset file %s\n", filename);
+    return NULL;
+  }
+
+  /* load in PHU segment (ignore) */
+  if (!gfits_fread_header (f, &header)) {
+    if (DEBUG) fprintf (stderr, "can't read image subset header\n");
+    goto escape;
+  }
+  if (!gfits_fread_matrix (f, &matrix, &header)) {
+    if (DEBUG) fprintf (stderr, "can't read image subset matrix\n");
+    goto escape;
+  }
+
+  ftable.header = &theader;
+
+  // load data for this header 
+  if (!gfits_load_header (f, &theader)) goto escape;
+
+  // what kind of table have I just read (if any!)
+  char extname[64];
+  gfits_scan (&theader, "EXTNAME", "%s", 1, extname);
+
+  // is this a BoundaryTree?  if so, it must be stand-alone: load just that and generate a
+  // containing TessellationTable.
+  if (!strcmp(extname, "ZONE_DATA")) {
+    BoundaryTree *tree = BoundaryTreeRead (&header, &theader, f);
+    gfits_free_header (&theader);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+    fclose (f);
+    
+    ALLOCATE (tess, TessellationTable, 1);
+    TessellationTableInit (tess, 1);
+    tess[0].tree = tree;
+    tess[0].type = TESS_RINGS;
+    tess[0].Rmin =   0;
+    tess[0].Rmax = 360;
+    tess[0].Dmin = -90;
+    tess[0].Dmax = +90;
+    *Ntess = 1;
+    return tess;
+  }
+  
+  if (strcmp(extname, "TESS_DATA")) {
+    fprintf (stderr, "header is neither ZONE_DATA nor TESS_DATA, problem with file\n");
+    goto escape;
+  }
+
+  // read the fits table bytes
+  if (!gfits_fread_ftable_data (f, &ftable, FALSE)) goto escape;
+ 
+  // need to create and assign to flat-field correction
+  GET_COLUMN_NEW(Ro,     "RA",     double);
+  GET_COLUMN_NEW(Do,     "DEC",    double);
+  GET_COLUMN_NEW(Xo,     "X_CENT", double);
+  GET_COLUMN_NEW(Yo,     "Y_CENT", double);
+  GET_COLUMN_NEW(dX,     "X_GRID", int);
+  GET_COLUMN_NEW(dY,     "Y_GRID", int);
+  GET_COLUMN_NEW(Rmin,   "R_MIN",  double);
+  GET_COLUMN_NEW(Rmax,   "R_MAX",  double);
+  GET_COLUMN_NEW(Dmin,   "D_MIN",  double);
+  GET_COLUMN_NEW(Dmax,   "D_MAX",  double);
+  GET_COLUMN_NEW(dPix,   "SCALE",  double);
+  GET_COLUMN_NEW(NX_SUB, "NX_SUB", int);
+  GET_COLUMN_NEW(NY_SUB, "NY_SUB", int);
+  GET_COLUMN_NEW(TYPE,   "TYPE",   int);
+
+  GET_COLUMN_NEW(PROJECT_ID_OFF, "PROJECT_ID_OFF", int);
+  GET_COLUMN_NEW(SKYCELL_ID_OFF, "SKYCELL_ID_OFF", int);
+
+  GET_COLUMN_NEW(BASENAME, "BASENAME", char);
+
+  *Ntess = Nrow;
+  ALLOCATE (tess, TessellationTable, *Ntess);
+  for (i = 0; i < *Ntess; i++) {
+    tess[i].Ro     = Ro[i]    ;
+    tess[i].Do     = Do[i]    ;
+    tess[i].Xo     = Xo[i]    ;
+    tess[i].Yo     = Yo[i]    ;
+    tess[i].dX     = dX[i]    ;
+    tess[i].dY     = dY[i]    ;
+    tess[i].Rmin   = Rmin[i]  ;
+    tess[i].Rmax   = Rmax[i]  ;
+    tess[i].Dmin   = Dmin[i]  ;
+    tess[i].Dmax   = Dmax[i]  ;
+    tess[i].dPix   = dPix[i]  ;
+    tess[i].NX_SUB = NX_SUB[i];
+    tess[i].NY_SUB = NY_SUB[i];
+    tess[i].type   = TYPE[i];
+
+    tess[i].projectIDoff = PROJECT_ID_OFF[i];
+    tess[i].skycellIDoff = SKYCELL_ID_OFF[i];
+
+    ALLOCATE (tess[i].basename, char, BOUNDARY_TREE_NAME_LENGTH);
+    memcpy (tess[i].basename, &BASENAME[i*BOUNDARY_TREE_NAME_LENGTH], BOUNDARY_TREE_NAME_LENGTH);
+    tess[i].Nbasename = strlen (tess[i].basename);
+
+    tess[i].tree   = NULL;
+  }
+
+  free(Ro    );
+  free(Do    );
+  free(Xo    );
+  free(Yo    );
+  free(dX    );
+  free(dY    );
+  free(Rmin  );
+  free(Rmax  );
+  free(Dmin  );
+  free(Dmax  );
+  free(dPix  );
+  free(NX_SUB);
+  free(NY_SUB);
+  free(TYPE);
+
+  free(PROJECT_ID_OFF);
+  free(SKYCELL_ID_OFF);
+
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+
+  // fprintf (stderr, "loaded data for %lld projection cell\n", (long long) *Ntess);
+
+  /*** check for a RINGS entry ***/
+
+  // find the RINGS tessellation (if present)
+  TessellationTable *rings = NULL;
+  for (i = 0; i < *Ntess; i++) {
+    if (tess[i].type == TESS_LOCAL) continue;
+    if (tess[i].type != TESS_RINGS) continue;
+    rings = &tess[i];
+  }
+
+  if (!rings) {
+    fclose (f);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+    return (tess);
+  }
+
+  // load data for this header, or exit
+  if (!gfits_load_header (f, &theader))  {
+    fprintf (stderr, "warning : RINGS tessellation listed in table but not present\n");
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+    return tess;
+  }
+
+  // what kind of table have I just read (if any!)
+  gfits_scan (&theader, "EXTNAME", "%s", 1, extname);
+
+  if (strcmp(extname, "ZONE_DATA")) {
+    fprintf (stderr, "warning : RINGS tessellation listed in table but not present\n");
+    gfits_free_header (&theader);
+    gfits_free_header (&header);
+    gfits_free_matrix (&matrix);
+    return tess;
+  }
+
+  // is this a BoundaryTree?  if so, it must be stand-alone: load just that and generate a
+  // containing TessellationTable.
+  BoundaryTree *tree = BoundaryTreeRead (&header, &theader, f);
+  gfits_free_header (&theader);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  fclose (f);
+    
+  rings->tree = tree;
+  return tess;
+
+escape:
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+  gfits_free_header (&theader);
+  gfits_free_table  (&ftable);
+  if (tess) free (tess);
+
+  fclose (f);
+  return NULL;
+}
+
+// we are passed a TessellationTable structure, write it to a FITS table.
+// if one of the tess entries is a RINGS tessellation, write it as a boundary tree
+int TessellationTableSave(char *filename, TessellationTable *tess, int Ntess) {
+
+  int i;
+  Header header;
+  Matrix matrix;
+
+  gfits_init_header (&header);
+  header.extend = TRUE;
+  gfits_create_header (&header);
+  gfits_create_matrix (&header, &matrix);
+
+  FILE *f = fopen (filename, "w");
+  if (!f) {
+    fprintf (stderr, "ERROR: cannot open boundary tess file for output %s\n", filename);
+    return FALSE;
+  }
+
+  // find the RINGS tessellation (if present)
+  BoundaryTree *rings = NULL;
+  for (i = 0; i < Ntess; i++) {
+    if (tess[i].type == TESS_LOCAL) continue;
+    if (tess[i].type != TESS_RINGS) continue;
+    rings = tess[i].tree;
+  }
+
+  // if we have a RINGS entry, we need to save some information in the header to define the layout
+  if (rings) {
+    gfits_modify (&header, "DEC_ORI", "%lf", 1, rings->DEC_origin);
+    gfits_modify (&header, "DEC_OFF", "%lf", 1, rings->DEC_offset);
+
+    gfits_modify (&header, "NX_SUB", "%d", 1, rings->NX_SUB);
+    gfits_modify (&header, "NY_SUB", "%d", 1, rings->NY_SUB);
+    gfits_modify (&header, "PIXSCALE", "%lf", 1, rings->dPix);
+  }
+
+  gfits_fwrite_header  (f, &header);
+  gfits_fwrite_matrix  (f, &matrix);
+  gfits_free_header (&header);
+  gfits_free_matrix (&matrix);
+
+  /** LOCAL tessellation table info **/
+  Header theader;
+  FTable ftable;
+
+  gfits_create_table_header (&theader, "BINTABLE", "TESS_DATA");
+
+  gfits_define_bintable_column (&theader, "D", "RA",     "ra (J2000) of cell center", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "DEC",    "dec (J2000) of cell center", "degree", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "X_CENT", "projection cell center pixel", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "Y_CENT", "projection cell center pixel", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "X_GRID", "skycell grid spacing", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "Y_GRID", "skycell grid spacing", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "R_MIN",  "RA limit (lower)", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "R_MAX",  "RA limit (upper)", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "D_MIN",  "DEC limit (lower)", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "D_MAX",  "DEC limit (upper)", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "D", "SCALE",  "pixel scale for projection cell", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "NX_SUB", "skycell subdivision in x", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "NY_SUB", "skycell subdivision in y", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "TYPE",   "type of tessellation", "none", 1.0, 0.0);
+
+  gfits_define_bintable_column (&theader, "J", "PROJECT_ID_OFF", "offset in image name to projection cell ID", "none", 1.0, 0.0);
+  gfits_define_bintable_column (&theader, "J", "SKYCELL_ID_OFF", "offset in image name to skycell ID", "none", 1.0, 0.0);
+
+  char fmt[16];
+  snprintf (fmt, 16, "%dA", BOUNDARY_TREE_NAME_LENGTH);
+  gfits_define_bintable_column (&theader, fmt, "BASENAME", "image base name", "none", 1.0, 0.0);
+
+  // generate the output array that carries the data
+  gfits_create_table (&theader, &ftable);
+  
+  // int Nout = (rings == NULL) ? Ntess : Ntess - 1;
+  int Nout = Ntess;
+
+  // create intermediate storage arrays
+  // NOTE: we have to unroll the 2D arrays in tess into 1D arrays
+  double *Ro            ; ALLOCATE (Ro    ,    double, Nout);
+  double *Do            ; ALLOCATE (Do    ,    double, Nout);
+  double *Xo            ; ALLOCATE (Xo    ,    double, Nout);
+  double *Yo            ; ALLOCATE (Yo    ,    double, Nout);
+  int    *dX            ; ALLOCATE (dX    ,    int,    Nout);
+  int    *dY            ; ALLOCATE (dY    ,    int,    Nout);
+  double *Rmin          ; ALLOCATE (Rmin  ,    double, Nout);
+  double *Rmax          ; ALLOCATE (Rmax  ,    double, Nout);
+  double *Dmin          ; ALLOCATE (Dmin  ,    double, Nout);
+  double *Dmax          ; ALLOCATE (Dmax  ,    double, Nout);
+  double *dPix          ; ALLOCATE (dPix  ,    double, Nout);
+  int    *NX_SUB        ; ALLOCATE (NX_SUB,    int,    Nout);
+  int    *NY_SUB        ; ALLOCATE (NY_SUB,    int,    Nout);
+  int    *TYPE          ; ALLOCATE (TYPE,      int,    Nout);
+
+  int    *PROJECT_ID_OFF; ALLOCATE (PROJECT_ID_OFF, int, Nout);
+  int    *SKYCELL_ID_OFF; ALLOCATE (SKYCELL_ID_OFF, int, Nout);
+
+  char   *BASENAME      ; ALLOCATE (BASENAME,  char,   Nout*BOUNDARY_TREE_NAME_LENGTH);
+  memset (BASENAME, 0, Nout*BOUNDARY_TREE_NAME_LENGTH);
+
+  // assign the storage arrays
+  // XXX I was not writing out the RINGS tess entry, but that probably is not useful (drop
+  // N vs i in future?)
+  int N = 0;
+  for (i = 0; i < Ntess; i++) {
+    // if (tess->type != TESS_LOCAL) continue;
+    Ro[N]     = tess[i].Ro;
+    Do[N]     = tess[i].Do;
+    Xo[N]     = tess[i].Xo;
+    Yo[N]     = tess[i].Yo;
+    dX[N]     = tess[i].dX;
+    dY[N]     = tess[i].dY;
+    Rmin[N]   = tess[i].Rmin;
+    Rmax[N]   = tess[i].Rmax;
+    Dmin[N]   = tess[i].Dmin;
+    Dmax[N]   = tess[i].Dmax;
+    dPix[N]   = tess[i].dPix;
+    NX_SUB[N] = tess[i].NX_SUB;
+    NY_SUB[N] = tess[i].NY_SUB;
+    TYPE[N]   = tess[i].type;
+
+    PROJECT_ID_OFF[N] = tess[i].projectIDoff;
+    SKYCELL_ID_OFF[N] = tess[i].skycellIDoff;
+
+    if (tess[i].basename) {
+      memcpy(&BASENAME[N*BOUNDARY_TREE_NAME_LENGTH], tess[i].basename, BOUNDARY_TREE_NAME_LENGTH);
+    }
+
+    N++; 
+  }
+
+  // add the columns to the output array
+  gfits_set_bintable_column (&theader, &ftable, "RA",     Ro,     Nout);
+  gfits_set_bintable_column (&theader, &ftable, "DEC",    Do,     Nout);
+  gfits_set_bintable_column (&theader, &ftable, "X_CENT", Xo,     Nout);
+  gfits_set_bintable_column (&theader, &ftable, "Y_CENT", Yo,     Nout);
+  gfits_set_bintable_column (&theader, &ftable, "X_GRID", dX,     Nout);
+  gfits_set_bintable_column (&theader, &ftable, "Y_GRID", dY,     Nout);
+  gfits_set_bintable_column (&theader, &ftable, "R_MIN",  Rmin,   Nout);
+  gfits_set_bintable_column (&theader, &ftable, "R_MAX",  Rmax,   Nout);
+  gfits_set_bintable_column (&theader, &ftable, "D_MIN",  Dmin,   Nout);
+  gfits_set_bintable_column (&theader, &ftable, "D_MAX",  Dmax,   Nout);
+  gfits_set_bintable_column (&theader, &ftable, "SCALE",  dPix,   Nout);
+  gfits_set_bintable_column (&theader, &ftable, "NX_SUB", NX_SUB, Nout);
+  gfits_set_bintable_column (&theader, &ftable, "NY_SUB", NY_SUB, Nout);
+  gfits_set_bintable_column (&theader, &ftable, "TYPE",   TYPE,   Nout);
+
+  gfits_set_bintable_column (&theader, &ftable, "PROJECT_ID_OFF", PROJECT_ID_OFF, Nout);
+  gfits_set_bintable_column (&theader, &ftable, "SKYCELL_ID_OFF", SKYCELL_ID_OFF, Nout);
+  gfits_set_bintable_column (&theader, &ftable, "BASENAME", BASENAME, Nout);
+
+  free (Ro    );
+  free (Do    );
+  free (Xo    );
+  free (Yo    );
+  free (dX    );
+  free (dY    );
+  free (Rmin  );
+  free (Rmax  );
+  free (Dmin  );
+  free (Dmax  );
+  free (dPix  );
+  free (NX_SUB);
+  free (NY_SUB);
+  free (TYPE);
+
+  free (PROJECT_ID_OFF);
+  free (SKYCELL_ID_OFF);
+  free (BASENAME);
+
+  gfits_fwrite_Theader (f, &theader);
+  gfits_fwrite_table  (f, &ftable);
+  gfits_free_header (&theader);
+  gfits_free_table (&ftable);
+
+  if (rings) {
+    BoundaryTreeWrite(f, rings);
+  }
+
+  fclose (f);
+
+  return TRUE;
+}
+
+int TessellationLocalProjection (double *x, double *y, double r, double d, TessellationTable *tess) {
+
+    double Xo = tess->Xo;
+    double Yo = tess->Yo;
+    double Ro = tess->Ro;
+    double Do = tess->Do;
+    double dPix = tess->dPix;
+
+    // this block only depends on Ro, Do
+
+    double sdp  = sin(RAD_DEG*Do);
+    double cdp  = cos(RAD_DEG*Do);
+    double salp = sin(RAD_DEG*(r - Ro));
+    double calp = cos(RAD_DEG*(r - Ro));
+    double sdel = sin(RAD_DEG*d);
+    double cdel = cos(RAD_DEG*d);
+    
+    double stht = sdel*sdp + cdel*cdp*calp;    /* sin(theta) */
+    double sphi = cdel*salp;                   /* = cos(theta)*sin(phi) */
+    double cphi = cdel*sdp*calp - sdel*cdp;    /* = cos(theta)*cos(phi) */
+
+    // defines the TAN projection (one of zenithal projections available, libdvo/src/coordops.c
+    // R = cot (theta) = cos(theta) / sin(theta)
+    double L, M;
+    if (stht == 0) {
+	double Rc = hypot(sphi, cphi);
+	L = 180.0 * sphi / Rc;
+	M = 180.0 * cphi / Rc;
+    } else {
+	L = +DEG_RAD * sphi / stht;
+	M = -DEG_RAD * cphi / stht;
+    }
+
+    // scale, rotation, parity:
+    // rotation == 0.0 (pc1_1 == pc2_2 == 1.0, pc1_2 = pc2_1 = 0.0)
+
+    // if there were rotation or parity:
+    // Ro = (coords[0].pc1_1*coords[0].pc2_2 - coords[0].pc1_2*coords[0].pc2_1);
+    // Xo = (coords[0].pc2_2*L - coords[0].pc1_2*M) / Ro;
+    // Yo = (coords[0].pc1_1*M - coords[0].pc2_1*L) / Ro;
+
+    double X = L;
+    double Y = M;
+
+    // scale is dPix
+
+    *x = Xo - X / dPix;
+    *y = Yo + Y / dPix;
+    
+    return TRUE;
+}
+
+static int Nfail = 0;
+
+// for the given ra,dec : find the valid tessellation and the containing projection / skycell IDs
+int TessellationPrimaryCellIDs (TessellationTable *tess, int Ntess, int *tessID, int *projID, int *skycellID, double ra, double dec) {
+
+  int i, zone, band;
+
+  *tessID = -1;
+  *projID = -1;
+  *skycellID = -1;
+
+  if (!tess) return FALSE;
+
+  // find the tessellation which includes this location
+  int myTess = -1;
+  for (i = 0; i < Ntess; i++) {
+    if (ra  <  tess[i].Rmin) continue;
+    if (ra  >= tess[i].Rmax) continue;
+    if (dec <  tess[i].Dmin) continue;
+    if (dec >= tess[i].Dmax) continue;
+    myTess = i;
+    break;
+  }
+  if (myTess < 0) {
+    if (Nfail < 100) {
+      fprintf (stderr, "no matching tessellation @ %f,%f\n", ra, dec);
+    }
+    Nfail ++;
+    return FALSE;
+  }
+
+  if (tess[myTess].type == TESS_NONE) {
+    if (Nfail < 100) {
+      fprintf (stderr, "tess has invalid type\n");
+      Nfail ++;
+    }
+    return FALSE;
+  }
+
+  if (tess[myTess].type == TESS_LOCAL) {
+    // I have ra, dec, and the primary projection cell.  In order to choose the primary skycell,
+    // I just need to project to ra,dec to X,Y based on the center of the cell and then get the subdivision right.
+    
+    double x = 0.0;
+    double y = 0.0;
+    TessellationLocalProjection (&x, &y, ra, dec, &tess[myTess]);
+  
+    int xi = x / tess[myTess].dX;
+    int yi = y / tess[myTess].dY;
+    int N = xi + tess[myTess].NX_SUB * yi;
+  
+    *tessID = myTess;
+    *projID = 0;
+    *skycellID = N;
+
+    // *projID = tess[myTess].projID :: NOTE : The projection cell may be non-zero like eg
+    // STS.  However, I'm not certain we are going to implement it like this.
+
+    return TRUE;
+  }
+
+  if (tess[myTess].type == TESS_RINGS) {
+    if (!BoundaryTreeCellCoords (tess[myTess].tree, &zone, &band, ra, dec)) {
+      fprintf (stderr, "mismatch!\n");
+      return FALSE;
+    }
+  
+    // I have ra, dec, and the primary projection cell.  In order to choose the primary skycell,
+    // I just need to project to ra,dec to X,Y based on the center of the cell and then get the subdivision right.
+    
+    double x = 0.0;
+    double y = 0.0;
+    BoundaryTreeProjection (&x, &y, ra, dec, tess[myTess].tree, zone, band);
+  
+    int xi = x / tess[myTess].tree->dX[zone][band];
+    int yi = y / tess[myTess].tree->dY[zone][band];
+    int N = xi + tess[myTess].tree->NX_SUB * yi;
+  
+    *tessID = myTess;
+    *projID = tess[myTess].tree->projID[zone][band];
+    *skycellID = N;
+
+    return TRUE;
+  }
+
+  fprintf (stderr, "invalid tessellation type!\n");
+  return FALSE;
+}
+
Index: trunk/Ohana/src/libdvo/src/coordops.c
===================================================================
--- trunk/Ohana/src/libdvo/src/coordops.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/coordops.c	(revision 36490)
@@ -1,3 +1,5 @@
 # include <dvo.h>
+
+/* note that Coords.ctype carries the DEC (ctype2) value */
 
 static Coords mosaic;
@@ -621,4 +623,5 @@
 
   /* modifications to the ctype? */
+  /* note that Coords.ctype carries the DEC (ctype2) value */
   OldAIPS = FALSE;
   gfits_modify (header, "CTYPE2",   "%s",  1, coords[0].ctype);
Index: trunk/Ohana/src/libdvo/src/dbCheckStack.c
===================================================================
--- trunk/Ohana/src/libdvo/src/dbCheckStack.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/dbCheckStack.c	(revision 36490)
@@ -7,6 +7,4 @@
 // these would be better done using bit values to test for field? temp? float?
 //
-
-#define OPIHI_INT 1
 
 int dbCheckStack (dbStack *stack, int Nstack, int table, dbField **inFields, int *inNfields) {
Index: trunk/Ohana/src/libdvo/src/dvo_convert.c
===================================================================
--- trunk/Ohana/src/libdvo/src/dvo_convert.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/dvo_convert.c	(revision 36490)
@@ -154,4 +154,19 @@
     fprintf (stderr, "EXTNAME missing for measure table\n");
     return (FALSE);
+  }
+
+  // block to convert broken tables (PS1_V4 made before the Xfix addition)
+  if (!strcmp (extname, "DVO_MEASURE_PS1_V4") && (ftable[0].header[0].Naxis[0] == 176)) {
+    fprintf (stderr, "reading alt PS1_V4 format\n");
+    Measure_PS1_V4alt *tmpMeasure;
+    tmpMeasure = gfits_table_get_Measure_PS1_V4alt (ftable, Nmeasure, NULL);
+    if (!tmpMeasure) {
+      fprintf (stderr, "ERROR: failed to read measures\n");
+      exit (2);
+    }
+    measure = Measure_PS1_V4alt_ToInternal (tmpMeasure, *Nmeasure);
+    free (tmpMeasure);
+    *format = DVO_FORMAT_PS1_V4;
+    return (measure); 
   }
 
Index: trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V4.c
===================================================================
--- trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V4.c	(revision 36489)
+++ trunk/Ohana/src/libdvo/src/dvo_convert_PS1_V4.c	(revision 36490)
@@ -481,2 +481,168 @@
   return (out);
 }
+
+/*** there are some mini dvodbs with the wrong PS1_V4 format (missing Xoff,Yoff / Xfix,Yfix) ************/
+
+Measure *Measure_PS1_V4alt_ToInternal (Measure_PS1_V4alt *in, off_t Nvalues) {
+
+  off_t i;
+  Measure *out;
+
+  ALLOCATE_ZERO (out, Measure, Nvalues);
+
+  for (i = 0; i < Nvalues; i++) {
+    dvo_measure_init (&out[i]);
+
+    out[i].dR         = in[i].dR;
+    out[i].dD         = in[i].dD;
+    out[i].M          = in[i].M;
+    out[i].Mcal       = in[i].Mcal;
+    out[i].Map        = in[i].Map;
+    out[i].Mkron      = in[i].Mkron;
+    out[i].dMkron     = in[i].dMkron;
+    out[i].dM         = in[i].dM;
+    out[i].dMcal      = in[i].dMcal;
+    out[i].dt         = in[i].dt;
+    out[i].FluxPSF    = in[i].FluxPSF;
+    out[i].dFluxPSF   = in[i].dFluxPSF;
+    out[i].FluxKron   = in[i].FluxKron;
+    out[i].dFluxKron  = in[i].dFluxKron;
+    out[i].airmass    = in[i].airmass;
+    out[i].az         = in[i].az;
+    out[i].Xccd       = in[i].Xccd;
+    out[i].Yccd       = in[i].Yccd;
+    out[i].Xfix       = in[i].Xccd;
+    out[i].Yfix       = in[i].Yccd;
+    out[i].Sky        = in[i].Sky;
+    out[i].dSky       = in[i].dSky;
+    out[i].t          = in[i].t;
+    out[i].t_msec     = in[i].t_msec;
+    out[i].averef     = in[i].averef;
+    out[i].detID      = in[i].detID;
+    out[i].imageID    = in[i].imageID;
+    out[i].objID      = in[i].objID;
+    out[i].catID      = in[i].catID;
+    out[i].extID      = in[i].extID;
+    out[i].psfQF      = in[i].psfQF;
+    out[i].psfQFperf  = in[i].psfQFperf;
+    out[i].psfChisq   = in[i].psfChisq;
+    out[i].psfNdof    = in[i].psfNdof;
+    out[i].psfNpix    = in[i].psfNpix;
+    out[i].crNsigma   = in[i].crNsigma;
+    out[i].extNsigma  = in[i].extNsigma;
+    out[i].FWx 	      = in[i].FWx;
+    out[i].FWy 	      = in[i].FWy;
+    out[i].theta      = in[i].theta;
+    out[i].Mxx 	      = in[i].Mxx;
+    out[i].Mxy 	      = in[i].Mxy;
+    out[i].Myy 	      = in[i].Myy;
+    out[i].dXccd      = in[i].dXccd;
+    out[i].dYccd      = in[i].dYccd;
+    out[i].dRsys      = in[i].dRsys;
+    out[i].posangle   = in[i].posangle;
+    out[i].pltscale   = in[i].pltscale;
+    out[i].photcode   = in[i].photcode;
+    out[i].dbFlags    = in[i].dbFlags;
+    out[i].photFlags  = in[i].photFlags;
+  }
+  return (out);
+}
+
+int gfits_convert_Measure_PS1_V4alt (Measure_PS1_V4alt *data, off_t size, off_t nitems) {
+
+  off_t i;
+  unsigned char *byte, tmp;
+
+  if (size != 176) { 
+    fprintf (stderr, "WARNING: mismatch in data types Measure_PS1_V4alt: "OFF_T_FMT" vs %d\n",  size,  176);
+    return (FALSE);
+  }
+
+  /* provide initial values to avoid compiler warnings for non-BYTE_SWAP arch */
+  i = tmp = 0;
+  byte = NULL;
+
+# ifdef BYTE_SWAP
+  byte = (unsigned char *) data;
+  for (i = 0; i < nitems; i++, byte += 176) {
+    /** BYTE SWAP **/
+    SWAP_WORD (0); // D_RA
+    SWAP_WORD (4); // D_DEC
+    SWAP_WORD (8); // MAG
+    SWAP_WORD (12); // M_CAL
+    SWAP_WORD (16); // M_APER
+    SWAP_WORD (20); // M_KRON
+    SWAP_WORD (24); // M_KRON_ERR
+    SWAP_WORD (28); // MAG_ERR
+    SWAP_WORD (32); // MAG_CAL_ERR
+    SWAP_WORD (36); // M_TIME
+    SWAP_WORD (40); // FLUX_PSF
+    SWAP_WORD (44); // FLUX_PSF_ERR
+    SWAP_WORD (48); // FLUX_KRON
+    SWAP_WORD (52); // FLUX_KRON_ERR
+    SWAP_WORD (56); // AIRMASS
+    SWAP_WORD (60); // AZ
+    SWAP_WORD (64); // X_CCD
+    SWAP_WORD (68); // Y_CCD
+    SWAP_WORD (72); // SKY_FLUX	   
+    SWAP_WORD (76); // SKY_FLUX_ERR
+    SWAP_WORD (80); // TIME
+    SWAP_WORD (84); // AVE_REF
+    SWAP_WORD (88); // DET_ID
+    SWAP_WORD (92); // IMAGE_ID
+    SWAP_WORD (96); // OBJ_ID
+    SWAP_WORD (100); // CAT_ID
+    SWAP_DBLE (104); // EXT_ID
+    SWAP_WORD (112); // PSF_QF	      
+    SWAP_WORD (116); // PSF_QF_PERFECT 
+    SWAP_WORD (120); // PSF_CHISQ     
+    SWAP_WORD (124); // PSF_NDOF      
+    SWAP_WORD (128); // PSF_NPIX      
+    SWAP_WORD (132); // CR_NSIGMA     
+    SWAP_WORD (136); // EXT_NSIGMA    
+    SWAP_BYTE (140); // FWHM_MAJOR 
+    SWAP_BYTE (142); // FWHM_MINOR 
+    SWAP_BYTE (144); // PSF_THETA  
+    SWAP_BYTE (146); // MXX	   
+    SWAP_BYTE (148); // MXY	   
+    SWAP_BYTE (150); // MYY	   
+    SWAP_BYTE (152); // TIME_MSEC  
+    SWAP_BYTE (154); // PHOTCODE   
+    SWAP_BYTE (156); // X_CCD_ERR  
+    SWAP_BYTE (158); // Y_CCD_ERR  
+    SWAP_BYTE (160); // POS_SYS_ERR
+    SWAP_BYTE (162); // POSANGLE   
+    SWAP_WORD (164); // PLTSCALE  
+    SWAP_WORD (168); // DB_FLAGS  
+    SWAP_WORD (172); // PHOT_FLAGS
+  }
+# endif  
+
+  return (TRUE);
+} 
+
+/*** add test of EXTNAME and header-defined columns? ***/
+/* return internal structure representation */
+Measure_PS1_V4alt *gfits_table_get_Measure_PS1_V4alt (FTable *ftable, off_t *Ndata, char *swapped) {
+
+  int Ncols;
+  Measure_PS1_V4alt *data;
+
+  Ncols = ftable[0].header[0].Naxis[0];
+  if (Ncols != 176) {
+    fprintf (stderr, "ERROR: mis-match in table size: width is %d but should be %d bytes\n", Ncols, 176);
+    return NULL;
+  }
+
+  *Ndata = ftable[0].header[0].Naxis[1];
+  data = (Measure_PS1_V4alt *) ftable[0].buffer;
+  if ((swapped == NULL) || (*swapped == FALSE)) {
+    if (!gfits_convert_Measure_PS1_V4alt (data, sizeof (Measure_PS1_V4alt), *Ndata)) {
+      return NULL;
+    }
+    gfits_table_scale_data (ftable);
+    if (swapped != NULL) *swapped = TRUE;
+  }
+  return (data);
+}
+
