using System;
using System.Collections;
using System.Drawing;
using System.Data.SqlClient;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using Spherical;

namespace Geom
{

	/// <summary>
	/// Canvas defines our own Graphic Enviroment for SDSS
	/// </summary>
	public class Canvas
	{	
		public static string Revision
		{
			get 
			{
				return "$Revision: 1.1.1.1 $";
			}
		}

		private bool	debug=false;		
		private Int32	width, height;					// Image size
		private double	imageScale;						// Image scale 
		private double	ppm;							// image resolution in pixels per arcminute
		private double  pixelScale;						// Pixel scale in arcsec/pixel units
        private float sdssSize;                          // size required for certain things.
		
		public  IProjection proj;						// the abstract Projection object		
		private TANProjection tproj;					// the TAN projection object
		private STRProjection sproj;					// the STR projection object
		private SDSSProjection sdssProj;                // SDSS Projection

		private PointEq v1, v2;							// corners of the viewport

		private Graphics  gc;
		private Bitmap	  img;

		//---------------
		// label formats
		//---------------
		private SolidBrush drawBrush	= new SolidBrush(Color.White);
		private SolidBrush fillBrush	= new SolidBrush(Color.FromArgb(17,59,227));
		private Pen queryPen			= new Pen(Color.Fuchsia, 1);
		private Pen gridPen				= new Pen(Color.LightGreen,1);
        private Pen semiTransGridPen = new Pen(Color.FromArgb(128,  0, 255, 127), 1);
		private Pen rulerPen			= new Pen(Color.LightGreen,2);
		private StringFormat drawFormat = new StringFormat();
		private Font font				= new Font("Arial", 9);

		//------------------------------------
		// define where the label is printed. 
		// yLabel changes as we write lines
		//------------------------------------
		private int xLabel		= 20;
		private int yLabel		= 15;
		private StringBuilder debugMessage = new StringBuilder();		

		/// <summary>
		/// Canvas constructor
		/// </summary>
		public Canvas ( Int32 h_, Int32 w_, double isc_, double ppd_, bool dbg_)
		{			
			debug		= dbg_;
			width		= w_;
			height		= h_;
			ppm			= ppd_ /60.0;
			imageScale	= isc_;
			pixelScale  = 3600.0/ppd_;


			//------------------------------
			// Allocate the drawing canvas 
			//------------------------------
			PixelFormat pf	 = PixelFormat.Format32bppRgb;		// use colors
            //PixelFormat pf = PixelFormat.Format24bppRgb;		// use colors
			img				 = new Bitmap(width, height, pf);	// make our new image to user's adjusted specs
            gc				 = Graphics.FromImage( img);		// make an graphic obj we can draw on	
			gc.SmoothingMode = SmoothingMode.AntiAlias;			// set as default SmoothingMode
            //img.MakeTransparent(Color.Black);
		}

		public void InitializeProjection(double ra_, double dec_, double ppd_, string ptype_)
		{
			//---------------------------------------
			// call the Projection constructor, then
			// cast onto the abstract Interface
			//---------------------------------------
			if (ptype_=="TAN")
			{
				tproj	= new TANProjection( ra_, dec_, ppd_, width, height);
				proj	= (IProjection)tproj;
			}
			if (ptype_=="STR") 
			{
				sproj	= new STRProjection( ra_, dec_, ppd_, width, height);
				proj	= (IProjection)sproj;
			}
			///<comment>
			/// Add another type for SDSS projections here 
			/// SDSS type is added to make the code-base consistent
			/// so that the system can handle a generic image survey
			/// and an SDSS survey
			///</coment>
			if (ptype_ == "SDSS")
			{		
					sdssProj = new SDSSProjection( ra_, dec_, imageScale, width, height);
					proj = (IProjection) sdssProj;
					
			}
			
		}

		/// <summary>
		/// Set the properties for the SDSS Projection type to set certain variables.
		/// </summary>
		/// <param name="sqlConn"></param>
		/// <param name="zoom"></param>
		/// <remarks>
		/// Added by Jayant
		/// </remarks>
		public void setSDSSProperties (SqlConnection sqlConn, int zoom, float size)
		{
            this.sdssSize = size;
			((SDSSProjection)(proj)).setConnection (sqlConn);
			((SDSSProjection)(proj)).setZoom (zoom);
			 ((SDSSProjection)(proj)).setUp ();
		}

		//=============================================================
		// Image related functions
		//=============================================================

		/// <summary>
		/// get image property
		/// </summary>	
		public Bitmap image 
		{
			get 
			{
				return this.img;
			}
		}

		/// <summary>
		/// getBuffer. Returns the bytes of the image buffer as a JPEG.
		/// </summary>
		/// <returns>Byte[] of the JPEG image</returns>
		public Byte[] getBuffer()
		{
				
			MemoryStream theJpeg = new MemoryStream();  // place to store the Jpeg
			img.Save(theJpeg, ImageFormat.Jpeg);		// make the Jpeg				
			Byte[] imgdata		 = theJpeg.ToArray();	// the image to be returned as a Jpeg byte stream	

			//#########################################################################
			// we will still need to write the WCS and copyright info into the header
			//#########################################################################

			return(imgdata);							// return image
		}


		/// <summary>
		/// Invert. It will invert each pixel of the image.
		/// </summary>
		public void Invert() 
		{
			//-------------------------------------------
			// GDI+ still lies to us - the return format   
			//-------------------------------------------
			BitmapData bmData   = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), 
									ImageLockMode.ReadWrite, 
									PixelFormat.Format24bppRgb); 
			int stride		    = bmData.Stride; 
			System.IntPtr Scan0 = bmData.Scan0; 
			unsafe 
			{ 
				byte *p		= (byte *)(void *)Scan0;
				int nOffset = stride - img.Width*3; 
				int nWidth	= img.Width * 3;

				for(int y=0;y < img.Height;++y) 
				{
					for(int x=0; x < nWidth; ++x ) 
					{
						p[0] = (byte)(255 - p[0]);
						++p;
					}
					p += nOffset;
				}
			}
			img.UnlockBits(bmData);
		} 
	
		/// <summary>
		/// GetViewPort. It will compute the ViewPort from the projection.
		/// </summary>
		public string GetViewPort()
		{
			//------------------------
			// construct the viewport
			//------------------------
			PointEq nw = proj.ScreenToEq(new PointF(0.0F,0.0F));
			PointEq ne = proj.ScreenToEq(new PointF((float)(width),0.0F));
			PointEq sw = proj.ScreenToEq(new PointF(0.0F,(float)(height)));
			PointEq se = proj.ScreenToEq(new PointF((float)(width),(float)(height)));

			//----------------------------
			// compute the ra,dec limits
			//----------------------------
			v1   = new PointEq(ne.ra, se.dec);	// lower limit
			v2   = new PointEq(nw.ra, nw.dec);	// upper limit

			if (debug) 
			{
				addDebugMessage("Viewport:\n");
				addDebugMessage(v1.ra.ToString("F5")+","+v1.dec.ToString("F5")+"\n");
				addDebugMessage(v2.ra.ToString("F5")+","+v2.dec.ToString("F5")+"\n");
			}
			
			//----------------------------
			// convert the vertices to V3
			//----------------------------
			double[] nw3 = V3.Normal(nw);
			double[] ne3 = V3.Normal(ne);
			double[] sw3 = V3.Normal(sw);
			double[] se3 = V3.Normal(se);

			//-----------------------------------------
			// compute the normal vectors 
			// corresponding to the great circle edges
			//-----------------------------------------
			double[] n3  = V3.Normalize(V3.Wedge(nw3,ne3));
			double[] s3  = V3.Normalize(V3.Wedge(se3,sw3));
			double[] w3  = V3.Normalize(V3.Wedge(sw3,nw3));
			double[] e3  = V3.Normalize(V3.Wedge(ne3,se3));

			string vs = "REGION CONVEX ";
			vs += n3[0].ToString()+" "+n3[1].ToString()+" "+n3[2].ToString()+" 0.0 ";
			vs += s3[0].ToString()+" "+s3[1].ToString()+" "+s3[2].ToString()+" 0.0 ";
			vs += e3[0].ToString()+" "+e3[1].ToString()+" "+e3[2].ToString()+" 0.0 ";
			vs += w3[0].ToString()+" "+w3[1].ToString()+" "+w3[2].ToString()+" 0.0 ";

			return vs;
		}

		//=============================================================
		// High level drawing routines
		//=============================================================
		
		/// <summary>
		/// drawFrame. Draw the image of the Frame onto the canvas using 
		/// the affine transformation through the Graphics.Transform
		///  </summary>
		/// <param name="xform">The affine transformation of a Field</param>
		/// <param name="tile">The bytes of the JPEG image of the Field</param>
		public void drawFrame(Xform x, Image tile) 
		{	
			//----------------------------------------------------
			// Draw the image onto the canvas using the affine 
			// transformation through the Graphics.Transform
			//----------------------------------------------------	
			PointF[] p	 = FrameWCS.FieldGeometry(true);
            // ---------------------------------------------------
            // This was done to nullify the effect of the flipping
            // caused by the affine transform. The affine transform
            // was causing a 180 degree flip among the frames.
            // The stitching was correct though.
            // ---------------------------------------------------
            tile.RotateFlip(RotateFlipType.Rotate180FlipX);		
			gc.Transform = x.m;
			gc.DrawImage(tile, new PointF[] {p[0], p[1], p[3] });
            tile.Save(@"C:\temp\tile_after_xform.jpg", ImageFormat.Jpeg);
            //Bitmap bm = new Bitmap(tile);
            //bm.MakeTransparent(Color.White);
            //gc.DrawImage(bm, new PointF[] { p[0], p[1], p[3] });

            #region mask galex stuff
            //ImageAttributes imgattr = new ImageAttributes();
            //imgattr.SetColorKey(Color.Black, Color.Black);
            //Rectangle srcrect = new Rectangle(0, 0, width, height);
            //GraphicsUnit units = GraphicsUnit.Pixel;
            //gc.DrawImage(tile, srcrect, 0, 0, width, height, units, imgattr);
            #endregion

            if (debug & false)
			{
				Pen framePen = new Pen(getColor("FRAME"),0.25F);
				//--------------------------------
				// draw the outline for debugging
				//--------------------------------
				gc.DrawLines(framePen, new PointF[] {p[0],p[1],p[2],p[3],p[0]});
			}
			gc.ResetTransform();
		}

		/// <summary>
		/// draws the SDSS frame using the Image (tile) and the transform object
		/// </summary>
		/// <param name="x"></param>
		/// <param name="tile"></param>
		public void drawSDSSFrame (Xform x, Image tile)
		{
			PointF[] p = SdssConstants.FieldGeometry(true);	

			gc.Transform = x.m;
			gc.DrawImage(tile, new PointF[] {p[0], p[1], p[3] });
			if (debug & false)
			{
				Pen framePen = new Pen(getColor("FRAME"),0.25F);
				//--------------------------------
				// draw the outline for debugging
				//--------------------------------
				gc.DrawLines(framePen, new PointF[] {p[0],p[1],p[2],p[3],p[0]});
			}
			gc.ResetTransform();
		}



		/// <summary>
		/// drawField. Draw the outlines of a field specified by the xform.
		///  </summary>
		/// <param name="xform">The affine transformation of a Field</param>
		public void drawField (Xform xf)	
		{
			PointF[] p	 = FrameWCS.FieldGeometry(false);
			gc.Transform = xf.m;
			Pen fieldPen = new Pen(getColor("FIELD"),0.25F);
			gc.DrawLines(fieldPen, new PointF[] {p[0],p[1],p[2],p[3],p[0]});
			gc.ResetTransform();

			if (debug)
			{
				debugMessage.AppendFormat("DrawField {0}\n", xf.info);
			}
			if (debug && false)
			{
				debugMessage.Append("[");
				debugMessage.Append(xf.m.Elements[0].ToString()+",");
				debugMessage.Append(xf.m.Elements[1].ToString()+",");
				debugMessage.Append(xf.m.Elements[2].ToString()+",");
				debugMessage.Append(xf.m.Elements[3].ToString()+",");
				debugMessage.Append(xf.m.Elements[4].ToString()+",");
				debugMessage.Append(xf.m.Elements[5].ToString()+"]\n");			
			}
		}

		/// <summary>
		/// Draws the SDSS Field correspoding to the transform object
		/// </summary>
		/// <param name="xf"></param>
		public void drawSDSSField (Xform xf)
		{
			PointF[] p = SdssConstants.FieldGeometry(false);

			gc.Transform = xf.m;
			Pen fieldPen = new Pen(getColor("FIELD"),0.25F);
			gc.DrawLines(fieldPen, new PointF[] {p[0],p[1],p[2],p[3],p[0]});
			gc.ResetTransform();

			if (debug && false)
			{
				debugMessage.AppendFormat("DrawField {0}\n", xf.info);        
			}
		}


		/// <summary>
		/// drawPlate. Draw the outline of a Plate centered at ra,dec.
		///  </summary>
		/// <param name="pRa">The right ascension of the plate center in degrees</param>
		/// <param name="pDec">The declination of the plate center in degrees</param>
		/// <param name="pRadius">The radius of the circle in arcminutes</param>
		public void drawPlate ( double pRa, double pDec, double pRadius) 
		{  

			// the increment for the angle in degrees
			double  inc	= 1.0;

			double[] n = V3.Normal(pRa,pDec);
			double[] w = V3.West(pRa,pDec);
			double[] u = V3.North(pRa,pDec);

			Pen platePen	= new Pen(Color.HotPink);
			double[] r = new double[3];
			double plateCos = Math.Cos(pRadius * Coord.D2R / 60.0);
			double plateSin = Math.Sin(pRadius * Coord.D2R / 60.0);

			int steps	= (int)(360/inc) + 1;
			PointF[] g	= new PointF[steps];

			inc *= Coord.D2R;
			for (int j = 0; j < steps; j++) 
			{ 
				double phi = (double)(j * inc);				
				double phiCos = Math.Cos(phi) * plateSin;
				double phiSin = Math.Sin(phi) * plateSin;
					
				for (int c = 0; c < 3; c++) 
				{
					r[c] = n[c]*plateCos + phiCos*w[c] + phiSin*u[c];					
				}	
				
				double oDec	= Math.Asin(r[2]) / Coord.D2R;
				double oRa	= Math.Atan2(r[1], r[0]) / Coord.D2R;
				if (oRa < 0) oRa += 360.0;
	
				g[j] = proj.EqToScreen(oRa,oDec);
			}				

			gc.DrawLines(platePen, g);
		}				


		/// <summary>
		/// drawMarker. Draw a mark at a given Eq point
		///  </summary>
		/// <param name="oRa">The right ascension of the object in degrees</param>
		/// <param name="oDec">The declination of the object in degrees</param>
		/// <param name="oPen">The Pen to be used</param>
		/// <param name="shape">The shape of the marker. 0: circle, 1: square, 2 cross</param>
		/// <param name="size">The size of the symbol in screen pixels</param>
		public void drawMarker(double oRa, double oDec, 
			float size, string type, int istyle, int nside, int fill)	
		{
			GraphicsPath path = new GraphicsPath();
			Color cl		  = getColor(type);
			SolidBrush oBrush = new SolidBrush(Color.FromArgb(64,cl));
			Pen	oPen		  = new Pen(cl,0.25F);

            ///<comments>
			/// This had to be added everytime size has to be used.
			/// When alex modified the interface, he removed the size
			/// parameter from the common interface but this is required
			/// for the SDSS interface part so it has to be explicitly set
			/// everytime it needs to be used. 
			///</comments>
			//((SDSSProjection) (proj)).setSize (0);

			PointF p	= proj.EqToScreen(oRa,oDec);
			PointF g;
			PointF[] q  = new PointF[2*nside+1];			

			if (nside<2)
			{
				if (fill>0) 
					gc.FillEllipse(oBrush,p.X-size, p.Y-size, 2*size, 2*size);
				//gc.DrawEllipse(oPen, p.X-size, p.Y-size, 2*size, 2*size);				
				gc.DrawEllipse (oPen,p.X-size, p.Y-size, 2*size, 2*size);
				return;
			}

			int np			= 0;
			double angle	= 0.0;
			double delta	= 2*Math.PI/nside;
			double theta	= (3*Math.PI+delta)/2+ (angle/180.0)*Math.PI;
			float  stellar	= 0.25F;

			if (istyle==0 || nside==2) 
			{
				q[np++] = new PointF(
					p.X + (float)(size*Math.Cos(theta)),
					p.Y + (float)(size*Math.Sin(theta)));
				for (int j=0;j<nside;j++)
				{
					theta += delta;
					q[np++] = new PointF(
						p.X + (float)(size*Math.Cos(theta)),
						p.Y + (float)(size*Math.Sin(theta)));
					path.AddLine(q[np-2].X, q[np-2].Y,q[np-1].X,q[np-1].Y);
				}
			}
			else if (istyle==1) 
			{
				for (int j=0;j<nside+1;j++)
				{
					theta += delta;
					g	= new PointF(
						p.X + (float)(size*Math.Cos(theta)),
						p.Y + (float)(size*Math.Sin(theta)));
					path.AddLine(p.X,p.Y,g.X,g.Y);
				}
			}
			else if (istyle==2) // star shaped
			{
				q[np++] = new PointF(
					p.X + (float)(size*Math.Cos(theta)),
					p.Y + (float)(size*Math.Sin(theta)));
				for (int j=0;j<nside;j++)
				{
					theta += delta;
					q[np++] = new PointF(
						p.X + (float)(size*Math.Cos(theta)),
						p.Y + (float)(size*Math.Sin(theta)));
					g = new PointF(
						p.X + stellar*(q[np-2].X + q[np-1].X - 2*p.X),
						p.Y + stellar*(q[np-2].Y + q[np-1].Y - 2*p.Y));
					path.AddLine(q[np-2].X, q[np-2].Y,g.X,g.Y);
					path.AddLine(g.X, g.Y, q[np-1].X, q[np-1].Y);
				}
			}
			if(fill>0) gc.FillPath(oBrush,path);
			gc.DrawPath(oPen,path);
		}
		
		/// <summary>
		/// drawObjectEq. Draw a marker centered on (ra,dec).
		///  </summary>
		/// <param name="oRa">The right ascension of the object in degrees</param>
		/// <param name="oDec">The declination of the object in degrees</param>
		/// <param name="oPen">The Pen to be used</param>
		/// <param name="shape">The shape of the marker. 0: circle, 1: square, 2 cross</param>
		/// <param name="size">The size of the symbol in screen pixels</param>
		public void drawObjectEq(double oRa, double oDec, string type, int shape, float size)		
		{
			PointF p = proj.EqToScreen(oRa,oDec);
			Pen	oPen = new Pen( getColor(type),0.25F);

			// 0: circle
			if (shape==0)	
				gc.DrawEllipse(oPen, p.X-size/2, p.Y-size/2, size, size);

			// 1: square
			if (shape==1)	
				gc.DrawRectangle(oPen, p.X-size/2 , p.Y-size/2, size, size);

			// 2: cross
			if (shape==2) 
			{
				float a = 0.75F*size;
				float b = 0.25F*size;

				gc.DrawLine(oPen,p.X-a,p.Y-a,p.X-b,p.Y-b);
				gc.DrawLine(oPen,p.X-a,p.Y+a,p.X-b,p.Y+b);
				gc.DrawLine(oPen,p.X+a,p.Y-a,p.X+b,p.Y-b);
				gc.DrawLine(oPen,p.X+a,p.Y+a,p.X+b,p.Y+b);
			}
		}

		/// <summary>
		///  Added from Sams code.
		/// </summary>
		/// <param name="hr"></param>
		/// <param name="imageWidth"></param>
		/// <returns></returns>
		protected GraphicsPath[] HTMRegionToGraphicsPaths(HTMRegion hr, int imageWidth)
		{
			int[][] pathExtents = PathExtents(hr);
			GraphicsPath[] result = new GraphicsPath[pathExtents.Length];

			//----------------------------
			// do an adaptive resolution
			//----------------------------
			int nstep = 100 - 3 * ((int)(pixelScale/10.0));
			//      if (nstep<30) nstep=30;
			if (nstep<400) nstep=400;
			double delta = 2*Math.PI/(nstep-1);

			//----------------------------------------
			// allocate enough storage for the points
			//----------------------------------------
			int npoints = nstep*hr.arc.Length;
			PointF[] pts = new PointF[npoints];

			int ptsOffset = 0;
			// for each path (continuously drawn sequence of arcs)
			for(int pathNum = 0; pathNum < result.Length; ++pathNum)
			{
				result[pathNum] = new GraphicsPath();
				ptsOffset = 0;

				int tx = 0;
				// for each arc
				for(int arcNum = pathExtents[pathNum][0]; arcNum <= pathExtents[pathNum][1]; ++arcNum)
				{
					// add the arc's points to the set of path points
					ptsOffset += HTMArcToPathPoints(hr.arc[arcNum], ref pts, ptsOffset, delta, imageWidth, ref tx);
				}

				// add the set of path points to the path
				addPatch(pts, ptsOffset, result[pathNum]);
			}

			return result;
		}

		protected int HTMArcToPathPoints(HTMArc arc, ref PointF[] pts, int ptsOffset, double delta, int imageWidth, ref int tx)
		{
			double[] r1	= V3.Normal(arc.ra1, arc.dec1);
			double[] r2	= V3.Normal(arc.ra2, arc.dec2);

			//----------------------------------------------------------
			// get the equation of the plane, and the circle of the arc
			//----------------------------------------------------------
			double cth	= arc.c;
			double sth	= Math.Abs(Math.Sin(Math.Acos(cth)));
			double[] n  = {arc.x, arc.y, arc.z};
			PointEq  g  = V3.ToEq(n);
			double[] w  = V3.West(g.ra,g.dec);
			double[] u  = V3.North(g.ra,g.dec);

			// *** DIRECTION OF CHANGE IN RA WRT phi IN GREAT CIRCLE PLANE (-/+ 1)
			// *** phi ALWAYS INCREASES, SO
			// ***     IF NORMAL IS IN NORTHERN HEMISPHERE,
			// ***     THEN INCREASING phi MEANS INCREASING RA
			// ***     ELSE INCREASING phi MEANS DECREASING RA
			double dRASign = ((double)g.dec) / ((double)Math.Abs(g.dec));

			for (int m=0;m<3;m++)
			{
				n[m]  *= cth;
				r1[m] -= n[m];
				r2[m] -= n[m];
				w[m]  *= sth;
				u[m]  *= sth;
			}

			//----------------------------------------------------------
			// compute the two angles defining the two roots on the arc
			//----------------------------------------------------------
			double phi1 = Math.Atan2(V3.Dot(r1,u),V3.Dot(r1,w));
			double phi2 = Math.Atan2(V3.Dot(r2,u),V3.Dot(r2,w));
			if (phi2<=phi1) phi2+=Math.PI*2.0;

			//---------------------------------
			// compute precise number of steps
			//---------------------------------
			int steps = (int) Math.Floor((phi2-phi1)/delta);
			if (steps<15) steps=15;

			//----------------------
			// do the loop over phi
			//----------------------
			double lastRA = Double.NaN, dRA = Double.NaN;

			double[] r  = new double[3];
			double phi = phi1;
			double inc = (phi2-phi1)/(steps-1);
			int i = 0, pt = 0;
			while(i < steps)
			{
				pt = ptsOffset + i;
				for(int m=0;m<3;m++)
				{
					r[m] = n[m] + Math.Cos(phi)*w[m] + Math.Sin(phi)*u[m];
				}

				// *** KEEP A REFERENCE TO THE EQUATORIAL COORDINATE FOR THIS POINT IN OBJECT SPACE
				PointEq peq = V3.ToEq(r);
				// *** PROCEED WITH SCREEN POSITION CALCULATION AND STORE IT
				pts[pt] = proj.EqToScreen(V3.ToEq(r));

				// MERIDIAN CROSSING DETECTOR
				//   IF dRA BETWEEN THIS RA AND LAST RA IS WRONG, THEN WE'VE CROSSED THE MERIDIAN
				//   CASE WHERE RA INCREASES WITH phi, +dRA OK, -dRA => MERIDIAN CROSSING
				//   CASE WHERE RA DECREASES WITH phi, +dRA => MERIDIAN CROSSING, -dRA OK
				dRA = peq.ra - lastRA;
				if(!Double.IsNaN(lastRA) && !(Math.Abs(dRA) < 10E-10) && dRA * dRASign < 0.0)
				{
					if(lastRA > peq.ra)
					{
						tx -= imageWidth;
					}
					else
					{
						tx += imageWidth;
					}
				}

				pts[pt].X += tx;
				// *** KEEP lastRA TO CALCULATE dRA NEXT POINT
				lastRA = peq.ra;
				phi += inc;
				++i;
			}

			return i;
		}
		
		/// <summary></summary>
		/// <param name="hr"></param>
		/// <returns>
		///   An array of pairs of extent indices of paths in hr.  That is,
		///   for the result r, r[n][0] is the first arc with convexid c and patchid p,
		///   and r[n][1] is the last arc with convexid c and patchid p.
		/// </returns>
		protected int[][] PathExtents(HTMRegion hr)
		{
			if(hr.arc != null && hr.arc.Length > 0)
			{
				// over-allocate
				int[][] result = new int[hr.arc.Length][];
				int pathIndex = -1, pathCount = 0;

				HTMArc arc = null;
				long convex = -1; // sure hope nobody uses -1 for a convexid
				int patch = -1; // ditto patchid
				bool drawing = false, draw = false, newConvexOrPatch = false, lastArc = false;

				for(int i = 0; i < hr.arc.Length; ++i)
				{
					arc = hr.arc[i];
					draw = arc.draw > 0;
					newConvexOrPatch = arc.cvx != convex || arc.patch != patch;
					lastArc = i == hr.arc.Length - 1;

					// close the current path?
					// you can only really see this when you draw a truth table of three bools
					// 1) are we drawing
					// 2) do we want to continue drawing
					// 3) does the current arc start a new convex or patch
					// 4) plus a fake bool to see if it's the last arc
					// the only case where we don't close is when
					// a) we're drawing
					// b) we still want to draw
					// c) we don't have a new convex or patch
					// in all other cases, close the current path, as is done here
					if(drawing && (lastArc || !(draw && !newConvexOrPatch)))
					{
						result[pathIndex][1] = i;
					}

					// start a new path?
					// again the truth table
					// the only cases where we start a new path are
					// a) when we're drawing, we want to continue drawing, and we get a new convex/patch
					// b) when we're not drawing and want to start drawing
					if((drawing && draw && newConvexOrPatch) || (!drawing && draw))
					{
						++pathCount;
						pathIndex = pathCount - 1;
						result[pathIndex] = new int[2];
						result[pathIndex][0] = i;
						convex = arc.cvx;
						patch = arc.patch;
					}

					// do we continue in a state of drawing?
					drawing = draw;
				}

				int[][] actualResult = new int[pathCount][];
				Array.Copy(result, 0, actualResult, 0, pathCount);

				return actualResult;
			}
			return new int[0][];
		}


		public static HTMRegion LineRegion(double ra1, double dec1, double ra2, double dec2, double angularWidth, bool positiveNormal)
		{
			int ra=0;
			int dec=1;

			double dRA1 = angularWidth / (2.0 * Math.Cos(dec1 * V3.D2R));
			double dRA2 = angularWidth / (2.0 * Math.Cos(dec2 * V3.D2R));

			double[] p1 = new double[2];
			double[] p2 = new double[2];
			double[] p3 = new double[2];
			double[] p4 = new double[2];

			p1[ra] = ra1 + dRA1;
			p1[dec] = dec1;
			p2[ra] = ra1 - dRA1;
			p2[dec] = dec1;
			p3[ra] = ra2 - dRA2;
			p3[dec] = dec2;
			p4[ra] = ra2 + dRA2;
			p4[dec] = dec2;

			Console.Error.WriteLine("endpoints (" + ra1 + ", " + dec1 + "), (" + ra2 + ", " + dec2 + ")");
			Console.Error.WriteLine("dRA1 = " + dRA1 + " dRA2 = " + dRA2);
			Console.Error.WriteLine("p1 = (" + p1[ra] + ", " + p1[dec] + ")");
			Console.Error.WriteLine("p2 = (" + p2[ra] + ", " + p2[dec] + ")");
			Console.Error.WriteLine("p3 = (" + p3[ra] + ", " + p3[dec] + ")");
			Console.Error.WriteLine("p4 = (" + p4[ra] + ", " + p4[dec] + ")");
			Console.Error.Flush();
			double[] n = V3.Normalize(V3.Wedge(V3.Normal(p1[ra], p1[dec]), V3.Normal(p2[ra], p2[dec])));
			HTMArc arc1 = new HTMArc(0, 0, 1, p1[ra], p1[dec], p2[ra], p2[dec], n[0], n[1], n[2], 0, 0);
			if(positiveNormal)
			{
				n = V3.Normalize(V3.Wedge(V3.Normal(p2[ra], p2[dec]), V3.Normal(p3[ra], p3[dec])));
			}
			else
			{
				n = V3.Normalize(V3.Wedge(V3.Normal(p3[ra], p3[dec]), V3.Normal(p2[ra], p2[dec])));
			}
			HTMArc arc2 = new HTMArc(0, 0, 1, p2[ra], p2[dec], p3[ra], p3[dec], n[0], n[1], n[2], 0, 0);
			n = V3.Normalize(V3.Wedge(V3.Normal(p3[ra], p3[dec]), V3.Normal(p4[ra], p4[dec])));
			HTMArc arc3 = new HTMArc(0, 0, 1, p3[ra], p3[dec], p4[ra], p4[dec], n[0], n[1], n[2], 0, 0);
			if(positiveNormal)
			{
				n = V3.Normalize(V3.Wedge(V3.Normal(p4[ra], p4[dec]), V3.Normal(p1[ra], p1[dec])));
			}
			else
			{
				n = V3.Normalize(V3.Wedge(V3.Normal(p1[ra], p1[dec]), V3.Normal(p4[ra], p4[dec])));
			}
			HTMArc arc4 = new HTMArc(0, 0, 1, p4[ra], p4[dec], p1[ra], p1[dec], n[0], n[1], n[2], 0, 0);
			HTMRegion region = new HTMRegion(0, 0, "PRIMARY");
			region.arc = new HTMArc[]{arc1, arc2, arc3, arc4};

			return region;
		}

		/// <summary>
		/// This is sams version of drawRegion.
		/// It is being used in drawing the arcs.
		/// </summary>
		/// <param name="hr"></param>
		/// <param name="fill"></param>
		/// <param name="imageWidth"></param>
		public void drawRegion4(HTMRegion hr, int fill, int imageWidth)
		{
			Color cl = regionColor(hr.type, hr.mask);
			Pen rpen = new Pen(cl,0.25F);
			SolidBrush oBrush = new SolidBrush(Color.FromArgb(64,cl));
			drawRegion4(hr, fill, imageWidth, rpen, oBrush);
		}
      
		/// <summary>
		/// drawRegion. Draw the outlines of a region.
		///  </summary>
		/// <param name="hr">The struct containing an HTMRegion</param>
		public void drawRegion4(HTMRegion hr, int fill, int imageWidth, Pen outlinePen, Brush fillBrush)
		{
			//      try
		{
			GraphicsPath[] paths = HTMRegionToGraphicsPaths(hr, imageWidth);
			//Color cl = regionColor(hr.type, hr.mask);
			//Pen rpen = new Pen(cl,0.25F);
			//SolidBrush oBrush = new SolidBrush(Color.FromArgb(64,cl));

			RectangleF bounds = RectangleF.Empty;
			int leftBlits = 0, rightBlits = 0;
			for(int i = 0; i < paths.Length; ++i)
			{
				bounds = paths[i].GetBounds();
				rightBlits = bounds.Left < 0 ? -(int)Math.Floor(bounds.Left / imageWidth): 0;
				leftBlits = bounds.Right > imageWidth ? -(int)(Math.Ceiling(bounds.Right / imageWidth) - 1): 0;

				for(int tx = leftBlits * imageWidth; tx <= rightBlits * imageWidth; tx += imageWidth)
				{
					gc.TranslateTransform(tx, 0.0f);
					if(fill>0)
					{
						//gc.FillPath(oBrush,paths[i]);
						gc.FillPath(fillBrush,paths[i]);
					}

					//--------------------------
					// now draw the path itself
					//--------------------------
					//gc.DrawPath(rpen, paths[i]);
					gc.DrawPath(outlinePen, paths[i]);

					gc.ResetTransform();
				}
			}
		}
			//      catch {displayMessage("exception in drawRegion "+hr.rid.ToString()+"\n");}			
			if (debug && false)
			{
				debugMessage.Append("drawRegion\n");        
			}
		}


		/// <summary>
		/// drawRegion. Draw the outlines of a region.
		///  </summary>
		/// <param name="hr">The struct containing an HTMRegion</param>
		public void drawRegion(HTMRegion hr, int fill)	
		{		
			if (hr.arc.Length==0)
			{
				debugMessage.Append("NULL Region "+hr.rid.ToString()+"\n");        
				return;
			}

			//----------------------------
			// do an adaptive resolution
			//----------------------------
			int nstep = 100 -3 * ((int)(pixelScale/10.0));
			if (nstep<30) nstep=30;
			double delta = 2*Math.PI/(nstep-1);

			//-------------
			// create path
			//-------------
			GraphicsPath path = new GraphicsPath();

			//----------------------------------------
			// allocate enough storage for the points
			//----------------------------------------
			int npoints  = nstep*hr.arc.Length;
			PointF[] pts = new PointF[npoints];

			int np = 0;
			try
			{
				Int64 convex = hr.arc[0].cvx;
				Int32 patch  = hr.arc[0].patch;
				for (int k=0;k<hr.arc.Length;k++) 
				{
					//---------------------------------------------------
					// if a new patch or a new convex, draw it right now
					//---------------------------------------------------
					if (hr.arc[k].cvx != convex | hr.arc[k].patch != patch) 
					{
						//drawPatch(pts, np, cl, hr.fill);
						if (np!=0) addPatch(pts, np, path);
						convex = hr.arc[k].cvx;
						patch  = hr.arc[k].patch;
						np = 0;
					}

					//-------------------------------------------
					// if it is not drawable, go to the next arc
					//-------------------------------------------
					if (hr.arc[k].draw==0) continue;

					double[] r1	= V3.Normal(hr.arc[k].ra1,hr.arc[k].dec1);
					double[] r2	= V3.Normal(hr.arc[k].ra2,hr.arc[k].dec2);

					//----------------------------------------------------------
					// get the equation of the plane, and the circle of the arc
					//----------------------------------------------------------
					double cth	= hr.arc[k].c;
					double sth	= Math.Abs(Math.Sin(Math.Acos(cth)));
					double[] n  = {hr.arc[k].x,hr.arc[k].y, hr.arc[k].z};
					PointEq  g  = V3.ToEq(n);
					double[] w  = V3.West(g.ra,g.dec);
					double[] u  = V3.North(g.ra,g.dec);

					for (int m=0;m<3;m++)
					{
						n[m]  *= cth;
						r1[m] -= n[m];
						r2[m] -= n[m];
						w[m]  *= sth;
						u[m]  *= sth;
					}

					//----------------------------------------------------------
					// compute the two angles defining the two roots on the arc
					//----------------------------------------------------------
					double phi1 = Math.Atan2(V3.Dot(r1,u),V3.Dot(r1,w));
					double phi2 = Math.Atan2(V3.Dot(r2,u),V3.Dot(r2,w));
					if (phi2<=phi1) phi2+=Math.PI*2.0;

					//---------------------------------
					// compute precise number of steps
					//---------------------------------
					int steps = (int) Math.Floor((phi2-phi1)/delta);
					if (steps<15) steps=15;

					//----------------------
					// do the loop over phi
					//----------------------
					double[] r = new double[3];
					double phi = phi1;
					double inc = (phi2-phi1)/(steps-1);
					for (int i = 0; i < steps; i++) 
					{ 
						for (int m=0;m<3;m++)
						{
							r[m] = n[m] + Math.Cos(phi)*w[m] + Math.Sin(phi)*u[m];
						}
						pts[np++] = proj.EqToScreen(V3.ToEq(r));
						phi += inc;
					}				
				}

				if (np!=0) addPatch(pts, np, path);
				Pen rpen = new Pen(regionColor(hr.type, hr.mask),0.25F);
				if(fill>0) gc.FillPath(fillBrush,path);

				//--------------------------
				// now draw the path itself
				//--------------------------
				//gc.DrawPath(rpen, path);		
                //gc.DrawPath(gridPen, path);
                gc.DrawPath(semiTransGridPen, path);
			}
			catch {displayMessage("exception in drawRegion "+hr.rid.ToString()+"\n");}			
			if (debug && false)
			{
				debugMessage.Append("drawRegion\n");        
			}
		}


		/// <summary>
		/// addPatch. Insert the outline of a patch given by a polyline into the path.
		///  </summary>
		/// <param name="pts">The array containing the points in screen coordinates</param>
		/// <param name="np">The number of points to be drawn</param>
		/// <param name="path">The GraphicsPath to be appended to</param>

		public void addPatch (PointF[] pts, int np, GraphicsPath path)	
		{
			//-----------------------------------------------
			// copy the points so that we can use a polyline
			//-----------------------------------------------
			PointF[] pp = new PointF[np];
			for (int i=0;i<np;i++) pp[i] = pts[i]; 

			path.CloseFigure();
			path.AddLines(pp);
		}



		
		/// <summary>
		/// drawSpecObj. Draw the position of a SpecObj by ra,dec.
		///  </summary>
		/// <param name="oRa">The right ascension of the object in degrees</param>
		/// <param name="oDec">The declination of the object in degrees</param>
		/// <param name="size">The size of the symbol in screen pixels</param>
		public void drawSpecObj	(double oRa, double oDec, float size)		
		{
			drawObjectEq(oRa, oDec, "SPECTRO", 1, size);
		}


		/// <summary>
		/// drawPhotoObj. Draw the position of a PhotoObj by ra,dec.
		///  </summary>
		/// <param name="oRa">The right ascension of the object in degrees</param>
		/// <param name="oDec">The declination of the object in degrees</param>
		/// <param name="size">The size of the symbol in screen pixels</param>
		public void drawPhotoObj(double oRa, double oDec, float size)
		{				
			drawMarker(oRa, oDec, size, "PHOTO", 2, 6, 0);	
		}


		/// <summary>
		/// drawTargetObj. Draw the position of a TargetObj by ra,dec.
		///  </summary>
		/// <param name="oRa">The right ascension of the object in degrees</param>
		/// <param name="oDec">The declination of the object in degrees</param>
		/// <param name="size">The size of the symbol in screen pixels</param>
		public void drawTargetObj(double oRa, double oDec, float size)
		{
			drawObjectEq(oRa, oDec, "TARGET", 2, size);
		}

		// SDSS specific functions


		/// <summary>
		/// drawBoundingBox. Will draw the bounding box of a given object.
		/// </summary>
		/// <param name="xform">The astrometric transformation object of the Frame</param>
		/// <param name="xmin">The x coordinate of the upper left hand corner</param>
		/// <param name="xmax">The x coordinate of the lower right hand corner</param>
		/// <param name="ymin">The y coordinate of the upper left hand corner</param>
		/// <param name="ymax">The y coordinate of the lower right hand corner</param>
		public void drawBoundingBox (Xform xform, double xmin, double xmax, double ymin, double ymax)		
		{				
			gc.Transform = xform.m;
			Pen bboxPen = new Pen(getColor("BBOX"),0.25F);
			gc.DrawRectangle(bboxPen, (float)xmin, (float)ymin, 
				(float)(xmax-xmin+4), (float)(ymax-ymin+4));		
			gc.ResetTransform();
		}

		/// <summary>
		/// drawOutline. Draws the outline of the object as given by the compressed span.
		/// </summary>
		/// <param name="xform">The astrometric transformation object of the Frame</param>
		/// <param name="span">The compressed outline in span format (R. Lupton)</param>
		public void drawOutline (Xform xform, StringBuilder span)
		{															
			gc.Transform = xform.m;
			Pen outlinePen = new Pen(getColor("OUTLINE"),0.25F);
			// Extend line ends to fill the whole pixel 
			outlinePen.StartCap = LineCap.Square;
			outlinePen.EndCap	= LineCap.Square;								

			//-------------------------------------------------
			span = span.Replace("\"", "");
			try
			{								
				ArrayList l = polyFunk.getPoly(span.ToString());
				for(int i=0; i < l.Count;  i++)
				{
					gc.DrawLine(outlinePen,((Line)l[i]).p1,((Line)l[i]).p2);						
				}					
			}
			catch {displayMessage("Exception in drawOutline " + span+"\n");}			

			if (debug && false) debugMessage.Append("drawOutline: "+span+"\n");
			//------------------------------------------------

			gc.ResetTransform();
		}

	
		/// <summary>
		/// drawMask. Draws a selected list of masks over the canvas.
		/// Masks are encoded as spherical polygons, given by their
		/// corners in equatorial coordinates.
		/// </summary>
		/// <param name="area">The string giving the vertex list</param>
		public void drawMask(StringBuilder area)
		{
			string[] sp = area.ToString().Split(new char[] {' '});

			int np = sp.Length / 2;
			PointF[] pts = new PointF[np];
															
			double oRa, oDec;
			try
			{
				for (int i= 1, j = 0; i < sp.Length ; i+=2, j++) 
				{
					oRa		= Convert.ToDouble(sp[i]);
					oDec	= Convert.ToDouble(sp[i+1]);
					pts[j]	= proj.EqToScreen(oRa, oDec);
				}
				Pen maskPen = new Pen(getColor("MASK"),0.25F);
				gc.DrawPolygon(maskPen,pts);
			}
			catch {displayMessage("exception in drawMask " + area);}			
		}


		/// <summary>
		/// drawLabel. Draws a label at the next line.
		/// </summary>
		/// <param name="label">The text of the label.</param>
		public void drawLabel(string label)
		{
			gc.ResetTransform();
			gc.DrawString(label, font, drawBrush, xLabel, yLabel, drawFormat);
				
			string[] lines = label.Split(new char[] {'\n'});
			yLabel +=  (int)font.SizeInPoints * (lines.Length + 3); 
		}

	

		/// <summary>
		/// drawScale. Draws the marker showing the scale
		/// </summary>
		/// <param name="label">The text of the label.</param>
		public void drawScale(int cf)
		{
            //Pen rulerPen = new Pen(getColor("RULER"),0.25F);
            ////----------------------------------
            //// now draw decide on the tick size
            ////----------------------------------
            //scaleItem[] ruler = new scaleItem []
            //{
            //    new scaleItem("0.5''", 1.0/120.0),
            //    new scaleItem("1''", 1.0/60.0),
            //    new scaleItem("2''", 2.0/60.0),
            //    new scaleItem("5''", 5.0/60.0),
            //    new scaleItem("10''", 1.0/6.0),
            //    new scaleItem("20''", 2.0/6.0),
            //    new scaleItem("1'", 1.0),
            //    new scaleItem("2'", 2.0),
            //    new scaleItem("5'", 5.0),
            //    new scaleItem("10'", 10.0),
            //    new scaleItem("20'", 20.0),
            //    new scaleItem("30'", 30.0),
            //    new scaleItem("1deg", 60.0)
            //};
				
            //double extent = Math.Min(width,height);
            //float xc	  = width/2;
            //float yc	  = height/2;
            //int ticks	  = 10;

            //int i;
            //for (i=0; i < ruler.Length; i++) 
            //{
            //    ticks = (int)Math.Floor(extent / (ruler[i].size*ppm) );
            //    if ((ticks >= 4) && (ticks < 12) ) 	break;
            //}
            //string label	= ruler[i].label;
            //double tickSize = ruler[i].size * ppm;
				
            ////--------------------
            //// draw the tickmarks
            ////--------------------

            //int pos =  0;
            //for (i = -ticks; i <= ticks; i++)
            //{
            //    pos = (int) Math.Floor(i*tickSize);
            //    gc.DrawLine(rulerPen, 0, yc + pos,	cf, yc + pos);
            //    gc.DrawLine(rulerPen, width, yc + pos, width-cf, yc + pos);
            //    gc.DrawLine(rulerPen, xc + pos, 0,	xc + pos,	cf);
            //    gc.DrawLine(rulerPen, xc + pos, height, xc + pos, height-cf);
            //}								

            ////-------------------------------------------------
            //// draw the tick and write the label at the center
            ////-------------------------------------------------
            //int x1	= xLabel;		
            //int x2	= xLabel + (int)tickSize;
            //int len	= 4;
            //int yy  = yLabel+20;
            //gc.DrawLine(rulerPen, x1, yy      , x2, yy      );
            //gc.DrawLine(rulerPen, x1, yy - len, x1, yy + len);
            //gc.DrawLine(rulerPen, x2, yy - len, x2, yy + len);
			
            //xLabel += (int)(0.5*tickSize) - cf;
            //this.drawLabel(label);

            # region old test by jayant

            ///<comments name="jayant">
            /// Draw the arcs using the HTMArc object :
            /// Basically convert the screen coord to eq using screenToEq
            /// then usings sams lineRegion method, convert the eqs to
            /// regions. Using the drawRegion method, draw the region.
            /// 
            /// NOTE : The screenToEq is using CART projections. Im not sure
            /// if SDSS uses these projections. Will test and see. 
            /// If need be, will change later.
            ///</comments>

            ///<test>
            ///</test>
            /*
                PointEq pt1 = proj.ScreenToEq (new PointF (0, yc));
                PointEq pt2 = proj.ScreenToEq (new PointF (100, yc));
                PointF pt3 = proj.EqToScreen (pt1.ra, pt2.dec);
                HTMRegion line = LineRegion(pt1.ra, pt1.dec, pt2.ra, pt2.dec, 3, false);
                //drawRegion4(line, 0, width);
                drawRegion4 (line, 1, width, rulerPen, fillBrush);
            // public void drawRegion4(HTMRegion hr, int fill, int imageWidth, Pen outlinePen, Brush fillBrush)
            */

            # endregion


		}


		/// <summary>
		/// drawGrid. Draws the axes and tickmarks on the canvas.
		/// </summary>
		public void drawCrosshair()
		{
            ////-------------------------------------------------------------------
            //// write the grid with tickmarks 
            //gc.ResetTransform();

            //// first draw X, Y grid and the N, S, E, W labels on the grid

            //int cf		= 8;
            //float inner	= 0.05F*Math.Max(width,height);
            //float outer	= 0.125F;
            //float xc	= width/2;
            //float yc	= height/2;

            //Pen gridPen = new Pen(getColor("GRID"),0.25F);

            //gc.DrawLine(gridPen, xc, yc-inner, xc, height*outer);			// x axis
            //gc.DrawLine(gridPen, xc, yc+inner, xc, height*(1.0F -outer) );	// x axis
            //gc.DrawLine(gridPen, xc-inner, yc, width*outer, yc);			// y axis
            //gc.DrawLine(gridPen, xc+inner, yc, width*(1.0F-outer), yc);		// y axis

            //gc.DrawString("N", font, drawBrush, xc-cf, 2*cf, drawFormat);
            //gc.DrawString("S", font, drawBrush, xc-cf, height-4*cf, drawFormat);				
            //gc.DrawString("E", font, drawBrush, 2*cf, yc-cf, drawFormat);		
            //gc.DrawString("W", font, drawBrush, width-4*cf, yc-cf, drawFormat);


            //drawScale(cf);

            string viewportString = this.GetViewPort();
            float centerRa = (float) (this.width / 2);
            float centerDec = (float) (this.height / 2);
            PointEq vpCenter = ((proj)).ScreenToEq(new PointF(centerRa, centerDec));
            //Test
            PointF center = ((proj)).EqToScreen(new PointEq(195.00, 2.5));
            //PointEq vpCenter = new PointEq(195, 2.5);
            createGridLines(vpCenter.ra, vpCenter.dec, (1/(ppm*60)));

		}


		public void drawRuler()
		{
			// first draw X, Y grid and the N, S, E, W labels on the grid

			int cFudge	= 8;						
			float xc	= width/2;
			float yc	= height/2;
			
			
			// now draw decide on the tick size
			scaleItem[] ruler = new scaleItem []
			{
				new scaleItem("0.5''", 1.0/120.0),
				new scaleItem("1''", 1.0/60.0),
				new scaleItem("2''", 2.0/60.0),
				new scaleItem("5''", 5.0/60.0),
				new scaleItem("10''", 1.0/6.0),
				new scaleItem("20''", 2.0/6.0),
				new scaleItem("1'", 1.0),
				new scaleItem("2'", 2.0),
				new scaleItem("5'", 5.0),
				new scaleItem("10'", 10.0),
				new scaleItem("20'", 20.0),
				new scaleItem("30'", 30.0),
				new scaleItem("1deg", 60.0),
				new scaleItem("1.5deg", 90.0),
				new scaleItem("2deg", 120.0)
			};
				
			double extent = Math.Min(width,height);
			int ticks = 10;

			int i;
			for (i=0; i < ruler.Length; i++) 
			{
				ticks = (int)Math.Floor(extent / (ruler[i].size*ppm) );
				if ((ticks >= 4) && (ticks < 12) ) 	break;
			}
			if (i == ruler.Length) i--;
			string label	= ruler[i].label;
			double tickSize = ruler[i].size * ppm;
				
			// draw the tickmarks

			int pos =  0;
			for (i = -ticks; i <= ticks; i++)
			{
				pos = (int) Math.Floor(i*tickSize);
				gc.DrawLine(gridPen, 0, yc + pos,	cFudge, yc + pos);
				gc.DrawLine(gridPen, width, yc + pos, width-cFudge, yc + pos);
				gc.DrawLine(gridPen, xc + pos, 0,	xc + pos,	cFudge);
				gc.DrawLine(gridPen, xc + pos, height, xc + pos, height-cFudge);
			}								

			// draw the tick and write the label at the center
			
			int x1	= xLabel;		
			int x2	= xLabel + (int)tickSize;
			int len	= 4;
			int yy  = yLabel+20;
			gc.DrawLine(rulerPen, x1, yy      , x2, yy      );
			gc.DrawLine(rulerPen, x1, yy - len, x1, yy + len);
			gc.DrawLine(rulerPen, x2, yy - len, x2, yy + len);
			
			xLabel += (int)(0.5*tickSize) - cFudge;
			this.drawLabel(label);
		}


		//=============================================================
		// Debug and printing routines
		//=============================================================
		
		/// <summary>
		/// addDebugMessage. Add a line to the current debug message string.
		/// </summary>
		/// <param name="s">string to print</param>
		public void addDebugMessage(string s)
		{
			debugMessage.Append(s);
		}

		/// <summary>
		/// drawDebugMessage. Draws the debugmessage at the current location.
		/// </summary>
		public void drawDebugMessage()
		{
			drawLabel(debugMessage.ToString());
		}


		/// <summary>
		/// displayMessage. Draw the message on the screen.
		/// </summary>
		/// <param name="error">String to display</param>
		public void displayMessage(string error)
		{	
			gc.ResetTransform();
			gc.FillRectangle(new SolidBrush(Color.Black),0,0,width,height);
			gc.DrawString(error,new Font("Arial", 10),new SolidBrush(Color.White), 
				10  ,10 ,new StringFormat());
		}

		//=============================================================
		// Support routines
		//=============================================================

		private class scaleItem
		{
			public string label;
			public double size;

			public scaleItem (string strLabel, double vArcminutes) 
			{
				label = strLabel;
				size  = vArcminutes;
			}
		}


		/// <summary>
		/// drawAxis. Debugging, draws the N direction in red, the E in green.
		/// </summary>
		/// <param name="ra_">Right ascension of center</param>
		/// <param name="dec_">Declination of center</param>
		public void drawAxis(double ra_, double dec_)
		{
			PointF p = proj.EqToScreen(ra_,dec_);
			PointF n = proj.EqToScreen(ra_,dec_+0.1F);
			PointF w = proj.EqToScreen(ra_+0.1F,dec_);
			gc.DrawLine(new Pen(Color.FromArgb(255,0,0),0.25F),p,n);
			gc.DrawLine(new Pen(Color.FromArgb(0,255,0),0.25F),p,w);
		}



		/// <summary>
		/// drawSpecObj. Draw the position of a SpecObj by ra,dec.
		///  </summary>
		/// <param name="oRa">The right ascension of the object in degrees</param>
		/// <param name="oDec">The declination of the object in degrees</param>
		/// <param name="size">The size of the symbol in screen pixels</param>
		public void drawQueryObj	(double oRa, double oDec, float size)		
		{
			//((SDSSProjection) (proj)).setSize (size);

			float a = 1.10F * size;
			float b = 0.55F * size;
			
			PointF p = proj.EqToScreen (oRa,oDec);		
			PointF[] tri = new PointF[3];

            float shift = 6;
            p.X -= shift;
            p.Y -= shift;

			tri[0] = new PointF(p.X , p.Y);
			tri[1] = new PointF(p.X + a, p.Y);
			tri[2] = new PointF(p.X + b, p.Y + a);
			
			gc.DrawPolygon(queryPen, tri);
		}

        /// <summary>
        /// Create gridLines using actual grid lines.
        /// </summary>
        /// <param name="scale"></param>
        public void createGridLines(double centerRa, double centerDec, double scale)
        {
            PointEq corner = proj.ScreenToEq(new PointF(0.0F, 0.0F));
            PointEq center = proj.ScreenToEq(new PointF(256.0F, 256.0F));

            double increment = 50 / (ppm * 60);
            double startDec = centerDec - 10 * increment;
            double endDec = centerDec + 10 * increment;

            // ref dec should be at opp ends.
            Cartesian refPointForDec = new Cartesian(0, 0, 1, false);
            Cartesian cornerPoint = new Cartesian(corner.ra, corner.dec);
            Cartesian centerPoint = new Cartesian(centerRa, centerDec);
            Halfspace viewCircle = new Halfspace(centerPoint, cornerPoint.Dot(centerPoint));
            Cartesian p1, p2;
            Arc arc;

            // draw Dec Lines
            for (double dec = startDec; dec < endDec; dec += increment)
            {
                // Dot product of (0,0,1) and (constant dec line)
                double biggerCircleDist = Math.Sin(dec * Const.Degree2Radian);
                Halfspace gridCircle = new Halfspace(refPointForDec, biggerCircleDist);

                int nroots = gridCircle.Roots(viewCircle, out p2, out p1);
                if (nroots != 2) continue;
                arc = new Arc(gridCircle, p1, p2);

                //HTMRegion gridLineRegion = viewCenter.LineRegion(p1.RA, p1.Dec, p2.RA, p2.Dec, (double)(scale / 3600), true);
                HTMRegion reg = new HTMRegion(0, 0, "CHUNK");
                reg.arc = new HTMArc[1];
                reg.arc[0] = new HTMArc(0, 0, 1, p1.RA, p1.Dec, p2.RA, p2.Dec,
                    arc.Circle.Vector.X,
                    arc.Circle.Vector.Y,
                    arc.Circle.Vector.Z,
                    arc.Circle.Cos0, 0);

                drawRegion(reg, 0);
            }

            double startRa = centerRa - 20 * increment;
            double endRa = centerRa + 20 * increment;

            Cartesian refPointForRa = new Cartesian((double)90.0, (double)0.0);
            // draw Ra Lines
            for (double ra = startRa; ra < endRa; ra += increment)
            {

                // Dot product of (0,1,0) and Constant RA line with constant line
                double biggerCircleDist = Math.Sin(ra * Const.Degree2Radian) * Math.Cos(centerDec * Const.Degree2Radian);
                Halfspace gridCircle = new Halfspace(refPointForRa, biggerCircleDist);

                int nroots = gridCircle.Roots(viewCircle, out p2, out p1);
                if (nroots != 2) continue;
                arc = new Arc(gridCircle, p1, p2);

                //HTMRegion gridLineRegion = viewCenter.LineRegion(p1.RA, p1.Dec, p2.RA, p2.Dec, (double)(scale / 3600), true);
                HTMRegion reg = new HTMRegion(0, 0, "CHUNK");
                reg.arc = new HTMArc[1];
                reg.arc[0] = new HTMArc(0, 0, 1, p1.RA, p1.Dec, p2.RA, p2.Dec,
                    arc.Circle.Vector.X,
                    arc.Circle.Vector.Y,
                    arc.Circle.Vector.Z,
                    arc.Circle.Cos0, 0);

                drawRegion(reg, 0);
            }

        }


        /// <summary>
        /// objectPen. Returns a Pen for a given object type.
        /// </summary>
        /// <param name="type">Object type as a string in upper case</param>
        public Color getColor(string type)
		{
			//-------------------------------
			// set the appropriate Pen type
			//-------------------------------
			Color cl;
			switch(type)
			{
				case "PHOTO":
					cl = Color.DodgerBlue;
					break;
				case "SPECTRO":
					cl = Color.Red;
					break;
				case "TARGET":
					cl = Color.GreenYellow;
					break;
				case "BBOX":
					cl = Color.FromArgb(255,0,255);
					break;
				case "OUTLINE":
					cl = Color.FromArgb(0,255,0);
					break;
				case "FIELD":
					cl = Color.Gray;
					break;
				case "FRAME":
					cl = Color.DarkTurquoise;
					break;
				case "MASK":
					cl = Color.Tomato;
					break;
				case "AXIS":
					cl = Color.Gray;
					break;
				case "GRID":
					cl = Color.LightGreen;
					break;
				case "RULER":
                    cl = Color.LightGreen;
					break;
				default:
					cl = Color.White;
					break;
			}
		return cl;
		}


		/// <summary>
		/// regionColor. Returns the drawing color of a given region type.
		/// </summary>
		/// <param name="type">Region type as a string in upper case</param>
		/// <param name="isMask">0 if not a mask, 1 if mask</param>
		public Color regionColor(string type, int isMask)
		{
			//---------------------------
			// set the appropriate color
			//---------------------------
			Color cl;
			switch(type)
			{
				case "CAMCOL":
					cl = Color.White;
					break;
				case "CHUNK":
					cl = Color.Orange;
					break;
				case "PLATE":
					cl = Color.White;
					break;
				case "PRIMARY":
					cl = Color.Yellow;
					break;
				case "RUN":
					cl = Color.Blue;
					break;
				case "SECTOR":
					cl = Color.Yellow;
					break;
				case "SECTORLET":
					cl = Color.Yellow;
					break;
				case "SEGMENT":
					cl = Color.FromArgb(128,255,128);
					break;
				case "SKYBOX":
					cl = Color.FromArgb(128,255,128);
					break;
				case "STAVE":
					cl = Color.FromArgb(0,255,255);
					break;
				case "STRIPE":
					cl = Color.FromArgb(0,255,0);
					break;
				case "TIGEOM":
					cl = Color.FromArgb(255,255,0);
					break;
				case "TILE":
					cl = Color.FromArgb(255,255,0);
					break;
				case "TILEBOX":
					cl = Color.FromArgb(255,255,0);
					break;
				case "WEDGE":
					cl = Color.FromArgb(255,255,0);
					break;
				default:
					cl = Color.White;
					break;
			}
			if (isMask>0) cl = Color.FromArgb(255,0,0);
			return cl;
		}
	}
}

/* Revision History

        $Log: Canvas.cs,v $
        Revision 1.1.1.1  2006/11/14 02:56:46  gupchup
        no message

        Revision 1.1.1.1  2006/04/25 16:07:23  gupchup
        Code base that merges original imgcutout code and WCS frame format imgcutout code

        Revision 1.6  2003/04/10 21:54:32  szalay
         projection interface added

        Revision 1.5  2003/04/08 17:12:00  nieto
        Trying to include projection.cs class in project

        Revision 1.3  2003/03/25 23:18:33  nieto
        testing draw fields with their own coord
		
*/
