/***************************************************************************
                          skymapdraw.cpp  -  K Desktop Planetarium
                             -------------------
    begin                : Sun Mar 2 2003
    copyright            : (C) 2001 by Jason Harris
    email                : jharris@30doradus.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

//This file contains drawing functions SkyMap class.

#include <stdlib.h> // abs
#include <math.h> //log10()
#include <iostream>

#include <qpaintdevicemetrics.h>
#include <qpainter.h>

#include "skymap.h"
#include "Options.h"
#include "kstars.h"
#include "kstarsdata.h"
#include "ksnumbers.h"
#include "skyobject.h"
#include "deepskyobject.h"
#include "starobject.h"
#include "ksplanetbase.h"
#include "ksasteroid.h"
#include "kscomet.h"
#include "ksmoon.h"
#include "jupitermoons.h"
#include "infoboxes.h"
#include "simclock.h"
#include "csegment.h"
#include "devicemanager.h"
#include "indimenu.h"
#include "indiproperty.h"
#include "indielement.h"
#include "indidevice.h"

void SkyMap::drawOverlays( QPixmap *pm ) {
	if ( ksw ) { //only if we are drawing in the GUI window
		QPainter p;
		p.begin( pm );

		infoBoxes()->focusCoordChanged( focus() );
		drawBoxes( p );

		//draw FOV symbol
		ksw->data()->fovSymbol.draw( p, (float)(Options::fOVSize() * Options::zoomFactor()/57.3/60.0) );
		drawTelescopeSymbols( p );
		drawZoomBox( p );
		if ( transientObject() ) drawTransientLabel( p );
		if (isAngleMode()) {
			updateAngleRuler();
			drawAngleRuler( p );
		}
	}
}

void SkyMap::drawAngleRuler( QPainter &p ) {
	p.setPen( QPen( data->colorScheme()->colorNamed( "AngularRuler" ), 1, DotLine ) );
	p.drawLine( beginRulerPoint, endRulerPoint );
}

void SkyMap::drawZoomBox( QPainter &p ) {
	//draw the manual zoom-box, if it exists
	if ( ZoomRect.isValid() ) {
		p.setPen( QPen( "white", 1, DotLine ) );
		p.drawRect( ZoomRect.x(), ZoomRect.y(), ZoomRect.width(), ZoomRect.height() );
	}
}

void SkyMap::drawTransientLabel( QPainter &p ) {
	if ( transientObject() ) {
		p.setPen( TransientColor );

		QFont stdFont( p.font() );
		QFont smallFont( stdFont );
		smallFont.setPointSize( stdFont.pointSize() - 2 );
		if ( Options::zoomFactor() < 10.*MINZOOM ) {
			p.setFont( smallFont );
		} else {
			p.setFont( stdFont );
		}

		if ( checkVisibility( transientObject(), fov(), XRange ) ) {
			QPoint o = getXY( transientObject(), Options::useAltAz(), Options::useRefraction(), 1.0 );
			if ( o.x() >= 0 && o.x() <= width() && o.y() >= 0 && o.y() <= height() ) {
				drawNameLabel( p, transientObject(), o.x(), o.y(), 1.0 );
			}
		}

		//reset font
		p.setFont( stdFont );
	}
}

void SkyMap::drawBoxes( QPainter &p ) {
	if ( ksw ) { //only if we are drawing in the GUI window
		ksw->infoBoxes()->drawBoxes( p,
				data->colorScheme()->colorNamed( "BoxTextColor" ),
				data->colorScheme()->colorNamed( "BoxGrabColor" ),
				data->colorScheme()->colorNamed( "BoxBGColor" ), Options::boxBGMode() );
	}
}

void SkyMap::drawTelescopeSymbols(QPainter &psky) {

	if ( ksw ) { //ksw doesn't exist in non-GUI mode!
		INDI_P *eqNum;
		INDI_P *portConnect;
		INDI_E *lp;
		INDIMenu *devMenu = ksw->getINDIMenu();
		bool useJ2000 = false;

		if (!Options::indiCrosshairs() || devMenu == NULL)
			return;

		psky.setPen( QPen( QColor( data->colorScheme()->colorNamed("TargetColor" ) ) ) );
		psky.setBrush( NoBrush );
		int pxperdegree = int(Options::zoomFactor()/57.3);

		//fprintf(stderr, "in draw telescope function with mgrsize of %d\n", devMenu->mgr.size());
		for (uint i=0; i < devMenu->mgr.count(); i++)
		{
			for (uint j=0; j < devMenu->mgr.at(i)->indi_dev.count(); j++)
			{
				// make sure the dev is on first
				if (devMenu->mgr.at(i)->indi_dev.at(j)->isOn())
				{
				        portConnect = devMenu->mgr.at(i)->indi_dev.at(j)->findProp("CONNECTION");

					if (!portConnect)
					 return;

					 if (portConnect->state == PS_BUSY)
					  return;

					eqNum = devMenu->mgr.at(i)->indi_dev.at(j)->findProp("EQUATORIAL_EOD_COORD");
					
					if (eqNum == NULL)
					{
						eqNum = devMenu->mgr.at(i)->indi_dev.at(j)->findProp("EQUATORIAL_COORD");
						if (eqNum) useJ2000 = true;
					}

					// make sure it has RA and DEC properties
					if ( eqNum)
					{
						//fprintf(stderr, "Looking for RA label\n");
						lp = eqNum->findElement("RA");
						if (!lp)
							continue;

						// express hours in degrees on the celestial sphere
						dms raDMS(lp->value);
						raDMS.setD ( raDMS.Degrees() * 15.0);

						lp = eqNum->findElement("DEC");
						if (!lp)
							continue;

						dms decDMS(lp->value);


						//kdDebug() << "the KStars RA is " << raDMS.toHMSString() << endl;
						//kdDebug() << "the KStars DEC is " << decDMS.toDMSString() << "\n****************" << endl;

						SkyPoint sp( &raDMS, &decDMS);
						
						if (useJ2000)
							sp.apparentCoord( (double) J2000, ksw->data()->ut().djd());
							
						sp.EquatorialToHorizontal( ksw->LST(), ksw->geo()->lat() );

						QPoint P = getXY( &sp, Options::useAltAz(), Options::useRefraction() );

						int s1 = pxperdegree/2;
						int s2 = pxperdegree;
						int s3 = 2*pxperdegree;

						int x0 = P.x();  int y0 = P.y();
						int x1 = x0 - s1/2;  int y1 = y0 - s1/2;
						int x2 = x0 - s2/2;  int y2 = y0 - s2/2;
						int x3 = x0 - s3/2;  int y3 = y0 - s3/2;

						//Draw radial lines
						psky.drawLine( x1, y0, x3, y0 );
						psky.drawLine( x0+s2, y0, x0+s1/2, y0 );
						psky.drawLine( x0, y1, x0, y3 );
						psky.drawLine( x0, y0+s1/2, x0, y0+s2 );
						//Draw circles at 0.5 & 1 degrees
						psky.drawEllipse( x1, y1, s1, s1 );
						psky.drawEllipse( x2, y2, s2, s2 );

						//FIXME the label is not displayed optimally (needs to be somewhat centered vertically)
						psky.drawText( x0+s2 + 2 , y0, QString(devMenu->mgr.at(i)->indi_dev.at(j)->label) );

					}
				}
			}
		}
	}

}

void SkyMap::drawMilkyWay( QPainter& psky, double scale )
{
	int ptsCount = 0;
	int mwmax = int( scale * Options::zoomFactor()/100.);
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	int thick(1);
	if ( ! Options::fillMilkyWay() ) thick=3;

	psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "MWColor" ) ), thick, SolidLine ) );
	psky.setBrush( QBrush( QColor( data->colorScheme()->colorNamed( "MWColor" ) ) ) );
	bool offscreen, lastoffscreen=false;

	for ( register unsigned int j=0; j<11; ++j ) {
		if ( Options::fillMilkyWay() ) {
			ptsCount = 0;
			bool partVisible = false;

			QPoint o = getXY( data->MilkyWay[j].at(0), Options::useAltAz(), Options::useRefraction(), scale );
			if ( o.x() != -10000000 && o.y() != -10000000 ) pts->setPoint( ptsCount++, o.x(), o.y() );
			if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) partVisible = true;

			for ( SkyPoint *p = data->MilkyWay[j].first(); p; p = data->MilkyWay[j].next() ) {
				o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
				if ( o.x() != -10000000 && o.y() != -10000000 ) pts->setPoint( ptsCount++, o.x(), o.y() );
				if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) partVisible = true;
			}

			if ( ptsCount && partVisible ) {
				psky.drawPolygon( (  const QPointArray ) *pts, false, 0, ptsCount );
			}
		} else {
			QPoint o = getXY( data->MilkyWay[j].at(0), Options::useAltAz(), Options::useRefraction(), scale );
			if (o.x()==-10000000 && o.y()==-10000000) offscreen = true;
			else offscreen = false;

			psky.moveTo( o.x(), o.y() );

			for ( register unsigned int i=1; i<data->MilkyWay[j].count(); ++i ) {
				o = getXY( data->MilkyWay[j].at(i), Options::useAltAz(), Options::useRefraction(), scale );
				if (o.x()==-10000000 && o.y()==-10000000) offscreen = true;
				else offscreen = false;

				//don't draw a line if the last point's getXY was (-10000000, -10000000)
				int dx = abs(o.x()-psky.pos().x());
				int dy = abs(o.y()-psky.pos().y());
				if ( (!lastoffscreen && !offscreen) && (dx<mwmax && dy<mwmax) ) {
					psky.lineTo( o.x(), o.y() );
				} else {
					psky.moveTo( o.x(), o.y() );
				}
				lastoffscreen = offscreen;
			}
		}
	}
}

void SkyMap::drawCoordinateGrid( QPainter& psky, double scale )
{
	QPoint cur;

	//Draw coordinate grid
	psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "GridColor" ) ), 1, DotLine ) ); //change to GridColor

	//First, the parallels
	for ( register double Dec=-80.; Dec<=80.; Dec += 20. ) {
		bool newlyVisible = false;
		sp->set( 0.0, Dec );
		if ( Options::useAltAz() ) sp->EquatorialToHorizontal( data->LST, data->geo()->lat() );
		QPoint o = getXY( sp, Options::useAltAz(), Options::useRefraction(), scale );
		QPoint o1 = o;
		cur = o;
		psky.moveTo( o.x(), o.y() );

		double dRA = 1./15.; //180 points along full circle of RA
		for ( register double RA=dRA; RA<24.; RA+=dRA ) {
			sp->set( RA, Dec );
			if ( Options::useAltAz() ) sp->EquatorialToHorizontal( data->LST, data->geo()->lat() );

			if ( checkVisibility( sp, guideFOV, guideXRange ) ) {
				o = getXY( sp, Options::useAltAz(), Options::useRefraction(), scale );

				//When drawing on the printer, the psky.pos() point does NOT get updated
				//when lineTo or moveTo are called.  Grrr.  Need to store current position in QPoint cur.
				int dx = cur.x() - o.x();
				int dy = cur.y() - o.y();
				cur = o;

				if ( abs(dx) < guidemax*scale && abs(dy) < guidemax*scale ) {
					if ( newlyVisible ) {
						newlyVisible = false;
						psky.moveTo( o.x(), o.y() );
					} else {
						psky.lineTo( o.x(), o.y() );
					}
				} else {
					psky.moveTo( o.x(), o.y() );
				}
			}
	}

		//connect the final segment
		int dx = psky.pos().x() - o1.x();
		int dy = psky.pos().y() - o1.y();
		if ( abs(dx) < guidemax*scale && abs(dy) < guidemax*scale ) {
			psky.lineTo( o1.x(), o1.y() );
		} else {
			psky.moveTo( o1.x(), o1.y() );
		}
	}

	//next, the meridians
	for ( register double RA=0.; RA<24.; RA += 2. ) {
		bool newlyVisible = false;
		SkyPoint *sp1 = new SkyPoint( RA, -90. );
		if ( Options::useAltAz() ) sp1->EquatorialToHorizontal( data->LST, data->geo()->lat() );
		QPoint o = getXY( sp1, Options::useAltAz(), Options::useRefraction(), scale );
		cur = o;
		psky.moveTo( o.x(), o.y() );

		double dDec = 1.;
		for ( register double Dec=-89.; Dec<=90.; Dec+=dDec ) {
			sp1->set( RA, Dec );
			if ( Options::useAltAz() ) sp1->EquatorialToHorizontal( data->LST, data->geo()->lat() );

			if ( checkVisibility( sp1, guideFOV, guideXRange ) ) {
				o = getXY( sp1, Options::useAltAz(), Options::useRefraction(), scale );

				//When drawing on the printer, the psky.pos() point does NOT get updated
				//when lineTo or moveTo are called.  Grrr.  Need to store current position in QPoint cur.
				int dx = cur.x() - o.x();
				int dy = cur.y() - o.y();
				cur = o;

				if ( abs(dx) < guidemax*scale && abs(dy) < guidemax*scale ) {
					if ( newlyVisible ) {
						newlyVisible = false;
						psky.moveTo( o.x(), o.y() );
					} else {
						psky.lineTo( o.x(), o.y() );
					}
				} else {
					psky.moveTo( o.x(), o.y() );
				}
			}
		}
		delete sp1;  // avoid memory leak
	}
}

void SkyMap::drawEquator( QPainter& psky, double scale )
{
	//Draw Equator (currently can't be hidden on slew)
	psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "EqColor" ) ), 1, SolidLine ) );

	SkyPoint *p = data->Equator.first();
	QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
	QPoint o1 = o;
	QPoint last = o;
	QPoint cur = o;
	QPoint o2;
	psky.moveTo( o.x(), o.y() );
	bool newlyVisible = false;

	//index of point near the left or top/bottom edge
	uint index1(0), index2(0);
	int xSmall(width() + 100); //ridiculous initial value

	//start loop at second item
	for ( p = data->Equator.next(); p; p = data->Equator.next() ) {
		if ( checkVisibility( p, guideFOV, guideXRange ) ) {
			o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );

			//first iteration for positioning the "Equator" label:
			//flag the onscreen equator point with the smallest positive x value
			//we don't draw the label while slewing
			if ( ! slewing && o.x() > 0 && o.x() < width() && o.y() > 0 && o.y() < height() ) {
				if ( o.x() < xSmall ) {
					xSmall = o.x();
					index1 = data->Equator.at();
				}
			}

			//When drawing on the printer, the psky.pos() point does NOT get updated
			//when lineTo or moveTo are called.  Grrr.  Need to store current position in QPoint cur.
			int dx = cur.x() - o.x();
			int dy = cur.y() - o.y();
			cur = o;

			if ( abs(dx) < guidemax*scale && abs(dy) < guidemax*scale ) {
					if ( newlyVisible ) {
						newlyVisible = false;
						psky.moveTo( last.x(), last.y() );
					}
					psky.lineTo( o.x(), o.y() );
			} else {
				psky.moveTo( o.x(), o.y() );
			}
		} else {
			newlyVisible = true;
		}
		last = o;
	}

	//connect the final segment
	int dx = psky.pos().x() - o1.x();
	int dy = psky.pos().y() - o1.y();
	if ( abs(dx) < guidemax*scale && abs(dy) < guidemax*scale ) {
		psky.lineTo( o1.x(), o1.y() );
	} else {
		psky.moveTo( o1.x(), o1.y() );
	}

	if ( ! slewing && xSmall < width() ) {
		//Draw the "Equator" label.  We have flagged the leftmost onscreen Equator point.
		//If the zoom level is below 1000, simply adopt this point as the anchor for the
		//label.  If the zoom level is 1000 or higher, we interpolate to find the exact
		//point at which the Equator goes offscreen, and anchor from that point.
		p = data->Equator.at(index1);
		double ra0(0.0);  //the RA of our anchor point (the Dec is known to be 0.0
											//since it's the Equator)

		if ( Options::zoomFactor() < 1000. ) {
			ra0 = p->ra()->Hours();

		} else {
			//Somewhere between Equator point p and its immediate neighbor, the Equator goes
			//offscreen.  Determine the exact point at which this happens.
			index2 = index1 + 1;
			if ( index2 >= data->Equator.count() ) index2 -= data->Equator.count();
			SkyPoint *p2 = data->Equator.at(index2);

			o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
			o2 = getXY( p2, Options::useAltAz(), Options::useRefraction(), scale );

			double x1, x2;
			//there are 3 possibilities:  (o2.x() < 0); (o2.y() < 0); (o2.y() > height())
			if ( o2.x() < 0 ) {
				x1 = double(o.x())/double(o.x()-o2.x());
				x2 = -1.0*double(o2.x())/double(o.x()-o2.x());
			} else if ( o2.y() < 0 ) {
				x1 = double(o.y())/double(o.y()-o2.y());
				x2 = -1.0*double(o2.y())/double(o.y()-o2.y());
			} else if ( o2.y() > height() ) {
				x1 = double(height() - o.y())/double(o2.y()-o.y());
				x2 = double(o2.y() - height())/double(o2.y()-o.y());
			} else {  //should never get here
				x1 = 0.0;
				x2 = 1.0;
			}

			//ra0 is the exact RA at which the Equator intersects a screen edge
			ra0 = x1*p2->ra()->Hours() + x2*p->ra()->Hours();
		}

		//LabelPoint is the top left corner of the text label.  It is
		//offset from the anchor point by -1.5 degree (0.1 hour) in RA
		//and -0.4 degree in Dec, scaled by 2000./zoomFactor so that they are
		//independent of zoom.
		SkyPoint LabelPoint( ra0 - 200./Options::zoomFactor(), -800./Options::zoomFactor() );
		if ( Options::useAltAz() )
			LabelPoint.EquatorialToHorizontal( data->LST, data->geo()->lat() );

		//p2 is a SkyPoint offset from LabelPoint in RA by -0.1 hour/zoomFactor.
		//We use this point to determine the rotation angle for the text (which
		//we want to be parallel to the line joining LabelPoint and p2)
		SkyPoint p2 = LabelPoint;
		p2.setRA( p2.ra()->Hours() - 200./Options::zoomFactor() );
		if ( Options::useAltAz() )
			p2.EquatorialToHorizontal( data->LST, data->geo()->lat() );

		//o and o2 are the screen coordinates of LabelPoint and p2.
		o = getXY( &LabelPoint, Options::useAltAz(), Options::useRefraction(), scale );
		o2 = getXY( &p2, Options::useAltAz(), Options::useRefraction() );

		double sx = double( o.x() - o2.x() );
		double sy = double( o.y() - o2.y() );
		double angle;
		if ( sx ) {
			angle = atan( sy/sx )*180.0/dms::PI;
		} else {
			angle = 90.0;
			if ( sy < 0 ) angle = -90.0;
		}

		//Finally, draw the "Equator" label at the determined location and angle
		psky.save();
		psky.translate( o.x(), o.y() );
		psky.rotate( double( angle ) );  //rotate the coordinate system
		psky.drawText( 0, 0, i18n( "Equator" ) );
		psky.restore(); //reset coordinate system
	}
}

void SkyMap::drawEcliptic( QPainter& psky, double scale )
{
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	//Draw Ecliptic (currently can't be hidden on slew)
	psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "EclColor" ) ), 1, SolidLine ) );

	SkyPoint *p = data->Ecliptic.first();
	QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
	QPoint o2 = o;
	QPoint o1 = o;
	QPoint last = o;
	QPoint cur = o;
	psky.moveTo( o.x(), o.y() );

	//index of point near the right or top/bottom edge
	uint index1(0), index2(0);
	int xBig(-100); //ridiculous initial value

	bool newlyVisible = false;
	//Start loop at second item
	for ( p = data->Ecliptic.next(); p; p = data->Ecliptic.next() ) {
		if ( checkVisibility( p, guideFOV, guideXRange ) ) {
			o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );

			//first iteration for positioning the "Ecliptic" label:
			//flag the onscreen equator point with the largest x value
			//we don't draw the label while slewing
			if ( ! slewing && o.x() > 0 && o.x() < width() && o.y() > 0 && o.y() < height() ) {
				if ( o.x() > xBig ) {
					xBig = o.x();
					index1 = data->Ecliptic.at();
				}
			}

			//When drawing on the printer, the psky.pos() point does NOT get updated
			//when lineTo or moveTo are called.  Grrr.  Need to store current position in QPoint cur.
			int dx = cur.x() - o.x();
			int dy = cur.y() - o.y();
			cur = o;

			if ( abs(dx) < guidemax*scale && abs(dy) < guidemax*scale ) {
				if ( newlyVisible ) {
					newlyVisible = false;
					psky.moveTo( last.x(), last.y() );
				}
				psky.lineTo( o.x(), o.y() );
			} else {
				psky.moveTo( o.x(), o.y() );
			}
		} else {
			newlyVisible = true;
		}
		last = o;
	}

	//connect the final segment
	int dx = psky.pos().x() - o1.x();
	int dy = psky.pos().y() - o1.y();
	if ( abs(dx) < Width && abs(dy) < Height ) {
		psky.lineTo( o1.x(), o1.y() );
	} else {
		psky.moveTo( o1.x(), o1.y() );
	}

	if ( ! slewing && xBig > 0 ) {
		//Draw the "Ecliptic" label.  We have flagged the rightmost onscreen Ecliptic point.
		//If the zoom level is below 1000, simply adopt this point as the anchor for the
		//label.  If the zoom level is 1000 or higher, we interpolate to find the exact
		//point at which the Ecliptic goes offscreen, and anchor from that point.
		p = data->Ecliptic.at(index1);
		double ra0(0.0);  //the ra of our anchor point
		double dec0(0.0); //the dec of our anchor point

		if ( Options::zoomFactor() < 1000. ) {
			ra0 = p->ra()->Hours();
			dec0 = p->dec()->Degrees();
		} else {
			//Somewhere between Ecliptic point p and its immediate neighbor, the Ecliptic goes
			//offscreen.  Determine the exact point at which this happens.
			if ( index1 == 0 ) index2 = 0;
			else index2 = index1 - 1;
			SkyPoint *p2 = data->Ecliptic.at(index2);

			o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
			o2 = getXY( p2, Options::useAltAz(), Options::useRefraction(), scale );

			double x1, x2;
			//there are 3 possibilities:  (o2.x() > width()); (o2.y() < 0); (o2.y() > height())
			if ( o2.x() > width() ) {
				x1 = double(width()-o.x())/double(o2.x()-o.x());
				x2 = double(o2.x()-width())/double(o2.x()-o.x());
			} else if ( o2.y() < 0 ) {
				x1 = double(o.y())/double(o.y()-o2.y());
				x2 = -1.0*double(o2.y())/double(o.y()-o2.y());
			} else if ( o2.y() > height() ) {
				x1 = double(height() - o.y())/double(o2.y()-o.y());
				x2 = double(o2.y() - height())/double(o2.y()-o.y());
			} else {  //should never get here
				x1 = 0.0;
				x2 = 1.0;
			}

			//ra0 is the exact RA at which the Ecliptic intersects a screen edge
			ra0 = x1*p2->ra()->Hours() + x2*p->ra()->Hours();
			//dec0 is the exact Dec at which the Ecliptic intersects a screen edge
			dec0 = x1*p2->dec()->Degrees() + x2*p->dec()->Degrees();
		}

		KSNumbers num( data->ut().djd() );
		dms ecLong, ecLat;

		//LabelPoint is offset from the anchor point by +2.0 degree ecl. Long
		//and -0.4 degree in ecl. Lat, scaled by 2000./zoomFactor so that they are
		//independent of zoom.
		SkyPoint LabelPoint(ra0, dec0);
		LabelPoint.findEcliptic( num.obliquity(), ecLong, ecLat );
		ecLong.setD( ecLong.Degrees() + 4000./Options::zoomFactor() );
		ecLat.setD( ecLat.Degrees() - 800./Options::zoomFactor() );
		LabelPoint.setFromEcliptic( num.obliquity(), &ecLong, &ecLat );
		if ( Options::useAltAz() )
			LabelPoint.EquatorialToHorizontal( data->LST, data->geo()->lat() );

		//p2 is a SkyPoint offset from LabelPoint by -1.0 degrees of ecliptic longitude.
		//we use p2 to determine the onscreen rotation angle for the ecliptic label,
		//which we want to be parallel to the line between LabelPoint and p2.
		SkyPoint p2(ra0, dec0);
		p2.findEcliptic( num.obliquity(), ecLong, ecLat );
		ecLong.setD( ecLong.Degrees() + 2000./Options::zoomFactor() );
		ecLat.setD( ecLat.Degrees() - 800./Options::zoomFactor() );
		p2.setFromEcliptic( num.obliquity(), &ecLong, &ecLat );
		if ( Options::useAltAz() )
			p2.EquatorialToHorizontal( data->LST, data->geo()->lat() );

		//o and o2 are the screen positions of LabelPoint and p2.
		o = getXY( &LabelPoint, Options::useAltAz(), Options::useRefraction(), scale );
		o2 = getXY( &p2, Options::useAltAz(), Options::useRefraction() );

		double sx = double( o.x() - o2.x() );
		double sy = double( o.y() - o2.y() );
		double angle;
		if ( sx ) {
			angle = atan( sy/sx )*180.0/dms::PI;
		} else {
			angle = 90.0;
			if ( sy < 0 ) angle = -90.0;
		}

		//Finally, draw the "Ecliptic" label at the determined location and angle
		psky.save();
		psky.translate( o.x(), o.y() );
		psky.rotate( double( angle ) );  //rotate the coordinate system
		psky.drawText( 0, 0, i18n( "Ecliptic" ) );
		psky.restore(); //reset coordinate system
	}
}

void SkyMap::drawHorizon( QPainter& psky, QFont& stdFont, double scale )
{
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	QPtrList<QPoint> points;
	points.setAutoDelete(true);
	QPoint o, o2;

	//Draw Horizon
	//The horizon should not be corrected for atmospheric refraction, so getXY has doRefract=false...
	if (Options::showHorizon() || Options::showGround() ) {
		QPoint OutLeft(0,0), OutRight(0,0);

		psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "HorzColor" ) ), 1, SolidLine ) );
		psky.setBrush( QColor ( data->colorScheme()->colorNamed( "HorzColor" ) ) );
		int ptsCount = 0;
		int maxdist = int(Options::zoomFactor()/4);

		//index of point near the right or top/bottom edge
		uint index1(0), index2(0);
		int xBig(-100); //ridiculous initial value

		for ( SkyPoint *p = data->Horizon.first(); p; p = data->Horizon.next() ) {
			o = getXY( p, Options::useAltAz(), false, scale );  //false: do not refract the horizon
			bool found = false;

			//first iteration for positioning the "Horizon" label:
			//flag the onscreen equator point with the largest x value
			//we don't draw the label while slewing, or if the opaque ground is drawn
			if ( ! slewing && ( ! Options::showGround() || ! Options::useAltAz() )
						&& o.x() > 0 && o.x() < width() && o.y() > 0 && o.y() < height() ) {
				if ( o.x() > xBig ) {
					xBig = o.x();
					index1 = data->Horizon.at();
				}
			}

			//Use the QPtrList of points to pre-sort visible horizon points
			if ( o.x() > -100 && o.x() < Width + 100 && o.y() > -100 && o.y() < Height + 100 ) {
				if ( Options::useAltAz() ) {
					register unsigned int j;
					for ( j=0; j<points.count(); ++j ) {
						if ( o.x() < points.at(j)->x() ) {
							found = true;
							break;
						}
					}
					if ( found ) {
						points.insert( j, new QPoint(o) );
					} else {
						points.append( new QPoint(o) );
					}
				} else {
					points.append( new QPoint(o) );
				}
			} else {  //find the out-of-bounds points closest to the left and right borders
				if ( ( OutLeft.x() == 0 || o.x() > OutLeft.x() ) && o.x() < -100 ) {
					OutLeft.setX( o.x() );
					OutLeft.setY( o.y() );
				}
				if ( ( OutRight.x() == 0 || o.x() < OutRight.x() ) && o.x() >  + 100 ) {
					OutRight.setX( o.x() );
					OutRight.setY( o.y() );
				}
			}
		}

		//Add left-edge and right-edge points based on interpolating the first/last onscreen points
		//to the nearest offscreen points.

		if ( Options::useAltAz() && points.count() > 0 ) {
			//If the edge of the visible sky circle is onscreen, then we should 
			//interpolate the first and last horizon points to the edge of the circle.
			//Otherwise, interpolate to the screen edge.
			double dx = 0.5*double(Width)/Options::zoomFactor(); //center-to-edge ang in radians
			double r0 = 2.0*sin(0.25*dms::PI);
			
			if ( dx < r0 ) { //edge of visible sky circle is not visible
				//Interpolate from first sorted onscreen point to x=-100,
				//using OutLeft to determine the slope
				int xtotal = ( points.at( 0 )->x() - OutLeft.x() );
				int xx = ( points.at( 0 )->x() + 100 ) / xtotal;
				int yp = xx*OutRight.y() + (1-xx)*points.at( 0 )->y();  //interpolated left-edge y value
				QPoint *LeftEdge = new QPoint( -100, yp );
				points.insert( 0, LeftEdge ); //Prepend LeftEdge to the beginning of points
	
				//Interpolate from the last sorted onscreen point to ()+100,
				//using OutRight to determine the slope.
				xtotal = ( OutRight.x() - points.at( points.count() - 1 )->x() );
				xx = ( Width + 100 - points.at( points.count() - 1 )->x() ) / xtotal;
				yp = xx*OutRight.y() + (1-xx)*points.at( points.count() - 1 )->y(); //interpolated right-edge y value
				QPoint *RightEdge = new QPoint( Width+100, yp );
				points.append( RightEdge );
			}
		//If there are no horizon points, then either the horizon doesn't pass through the screen
		//or we're at high zoom, and horizon points lie on either side of the screen.
		} else if ( Options::useAltAz() && OutLeft.y() !=0 && OutRight.y() !=0 &&
						!( OutLeft.y() > Height + 100 && OutRight.y() > Height + 100 ) &&
						!( OutLeft.y() < -100 && OutRight.y() < -100 ) ) {

			//It's possible at high zoom that /no/ horizon points are onscreen.  In this case,
			//interpolate between OutLeft and OutRight directly to construct the horizon polygon.
			int xtotal = ( OutRight.x() - OutLeft.x() );
			int xx = ( OutRight.x() + 100 ) / xtotal;
			int yp = xx*OutLeft.y() + (1-xx)*OutRight.y();  //interpolated left-edge y value
//			QPoint *LeftEdge = new QPoint( -100, yp );
			points.append( new QPoint( -100, yp ) );

			xx = ( Width + 100 - OutLeft.x() ) / xtotal;
			yp = xx*OutRight.y() + (1-xx)*OutLeft.y(); //interpolated right-edge y value
//			QPoint *RightEdge = new QPoint( Width+100, yp );
			points.append( new QPoint( Width+100, yp ) );
 		}

		if ( points.count() ) {
//		Fill the pts array with sorted horizon points, Draw Horizon Line
			pts->setPoint( 0, points.at(0)->x(), points.at(0)->y() );
			if ( Options::showHorizon() ) psky.moveTo( points.at(0)->x(), points.at(0)->y() );

			for ( register unsigned int i=1; i<points.count(); ++i ) {
				pts->setPoint( i, points.at(i)->x(), points.at(i)->y() );

				if ( Options::showHorizon() ) {
					if ( !Options::useAltAz() && ( abs( points.at(i)->x() - psky.pos().x() ) > maxdist ||
								abs( points.at(i)->y() - psky.pos().y() ) > maxdist ) ) {
						psky.moveTo( points.at(i)->x(), points.at(i)->y() );
					} else {
						psky.lineTo( points.at(i)->x(), points.at(i)->y() );
					}

				}
			}

			//connect the last segment back to the beginning
			if ( abs( points.at(0)->x() - psky.pos().x() ) < maxdist && abs( points.at(0)->y() - psky.pos().y() ) < maxdist )
				psky.lineTo( points.at(0)->x(), points.at(0)->y() );

			//Finish the Ground polygon.  If sky edge is visible, the 
			//bottom edge follows the sky circle.  Otherwise, we just 
			//add a square bottom edge, offscreen
			if ( Options::useAltAz() ) {
				if ( Options::showGround() ) {
					ptsCount = points.count();
					
					//center-to-edge ang in radians
					double dx = 0.5*double(Width)/Options::zoomFactor(); 
					double r0 = 2.0*sin(0.25*dms::PI);
					
//					//Second QPointsArray for blocking the region outside the sky circle
//					QPointArray bpts( 100 ); //need 90 points along sky circle, plus 4 to complete polygon
//					uint bpCount(0);
					
					if ( dx > r0 ) { //sky edge is visible
						for ( double t=360.; t >= 180.; t-=2. ) {  //add points along edge of circle
							dms a( t );
							double sa(0.), ca(0.);
							a.SinCos( sa, ca );
							int xx = Width/2 + int(r0*Options::zoomFactor()*ca);
							int yy = Height/2 - int(r0*Options::zoomFactor()*sa);
							
							pts->setPoint( ptsCount++, xx, yy );
//							bpts.setPoint( bpCount++, xx, yy );
						}
						
//						//complete the background polygon, then draw it with SkyColor
//						bpts.setPoint( bpCount++,        -100, Height/2     );
//						bpts.setPoint( bpCount++,        -100, Height + 100 );
//						bpts.setPoint( bpCount++, Width + 100, Height + 100 );
//						bpts.setPoint( bpCount++, Width + 100, Height/2     );
//						psky.setPen( QColor ( data->colorScheme()->colorNamed( "SkyColor" ) ) );
//						psky.setBrush( QColor ( data->colorScheme()->colorNamed( "SkyColor" ) ) );
//						psky.drawPolygon( bpts, false, 0, bpCount );
//						//Reset colors for Horizon polygon
//						psky.setPen( QColor ( data->colorScheme()->colorNamed( "HorzColor" ) ) );
//						psky.setBrush( QColor ( data->colorScheme()->colorNamed( "HorzColor" ) ) );
					
					} else {
						pts->setPoint( ptsCount++, Width+100, Height+100 );   //bottom right corner
						pts->setPoint( ptsCount++, -100, Height+100 );         //bottom left corner
					}
						
					//Draw the Horizon polygon
					psky.drawPolygon( ( const QPointArray ) *pts, false, 0, ptsCount );

					//remove all items in points list
					for ( register unsigned int i=0; i<points.count(); ++i ) {
						points.remove(i);
					}
				}

//	Draw compass heading labels along horizon
				SkyPoint *c = new SkyPoint;
				QPoint cpoint;
				psky.setFont( stdFont );

				if ( Options::showGround() )
					psky.setPen( QColor ( data->colorScheme()->colorNamed( "CompassColor" ) ) );
				else
					psky.setPen( QColor ( data->colorScheme()->colorNamed( "HorzColor" ) ) );

		//North
				c->setAz( 359.99 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "North", "N" ) );
				}

		//NorthEast
				c->setAz( 45.0 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "Northeast", "NE" ) );
				}

		//East
				c->setAz( 90.0 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "East", "E" ) );
				}

		//SouthEast
				c->setAz( 135.0 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "Southeast", "SE" ) );
				}

		//South
				c->setAz( 179.99 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "South", "S" ) );
				}

		//SouthWest
				c->setAz( 225.0 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "Southwest", "SW" ) );
				}

		//West
				c->setAz( 270.0 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "West", "W" ) );
				}

		//NorthWest
				c->setAz( 315.0 );
				c->setAlt( 0.0 );
				if ( !Options::useAltAz() ) c->HorizontalToEquatorial( data->LST, data->geo()->lat() );
				cpoint = getXY( c, Options::useAltAz(), false, scale );
				cpoint.setY( cpoint.y() + int(scale*20) );
				if (cpoint.x() > 0 && cpoint.x() < Width && cpoint.y() > 0 && cpoint.y() < Height ) {
					psky.drawText( cpoint.x(), cpoint.y(), i18n( "Northwest", "NW" ) );
				}

				delete c;
			}
		}

		if ( ! slewing && (! Options::showGround() || ! Options::useAltAz() ) && xBig > 0 ) {
			//Draw the "Horizon" label.  We have flagged the rightmost onscreen Horizon point.
			//If the zoom level is below 1000, simply adopt this point as the anchor for the
			//label.  If the zoom level is 1000 or higher, we interpolate to find the exact
			//point at which the Horizon goes offscreen, and anchor from that point.
			SkyPoint *p = data->Horizon.at(index1);
			double ra0(0.0);  //the ra of our anchor point
			double dec0(0.0); //the dec of our anchor point

			if ( Options::zoomFactor() < 1000. ) {
				ra0 = p->ra()->Hours();
				dec0 = p->dec()->Degrees();
			} else {
				//Somewhere between Horizon point p and its immediate neighbor, the Horizon goes
				//offscreen.  Determine the exact point at which this happens.
				index2 = index1 + 1;
				if ( data->Horizon.count() &&  index2 > data->Horizon.count() - 1 ) index2 -= data->Horizon.count();
				SkyPoint *p2 = data->Horizon.at(index2);

				QPoint o1 = getXY( p, Options::useAltAz(), false, scale );
				QPoint o2 = getXY( p2, Options::useAltAz(), false, scale );

				double x1, x2;
				//there are 3 possibilities:  (o2.x() > width()); (o2.y() < 0); (o2.y() > height())
				if ( o2.x() > width() ) {
					x1 = double(width()-o1.x())/double(o2.x()-o1.x());
					x2 = double(o2.x()-width())/double(o2.x()-o1.x());
				} else if ( o2.y() < 0 ) {
					x1 = double(o1.y())/double(o1.y()-o2.y());
					x2 = -1.0*double(o2.y())/double(o1.y()-o2.y());
				} else if ( o2.y() > height() ) {
					x1 = double(height() - o1.y())/double(o2.y()-o1.y());
					x2 = double(o2.y() - height())/double(o2.y()-o1.y());
				} else {  //should never get here
					x1 = 0.0;
					x2 = 1.0;
				}

				//ra0 is the exact RA at which the Horizon intersects a screen edge
				ra0 = x1*p2->ra()->Hours() + x2*p->ra()->Hours();
				//dec0 is the exact Dec at which the Horizon intersects a screen edge
				dec0 = x1*p2->dec()->Degrees() + x2*p->dec()->Degrees();
			}

			//LabelPoint is offset from the anchor point by -2.0 degrees in azimuth
			//and -0.4 degree altitude, scaled by 2000./zoomFactor so that they are
			//independent of zoom.
			SkyPoint LabelPoint(ra0, dec0);
			LabelPoint.EquatorialToHorizontal( data->LST, data->geo()->lat() );
			LabelPoint.setAlt( LabelPoint.alt()->Degrees() - 800./Options::zoomFactor() );
			LabelPoint.setAz( LabelPoint.az()->Degrees() - 4000./Options::zoomFactor() );
			LabelPoint.HorizontalToEquatorial( data->LST, data->geo()->lat() );
			o = getXY( &LabelPoint, Options::useAltAz(), false, scale );
			if ( o.x() > width() || o.x() < 0 ) {
				//the LabelPoint is offscreen.  Either we are in the Southern hemisphere,
				//or the sky is rotated upside-down.  Use an azimuth offset of +2.0 degrees
			LabelPoint.setAlt( LabelPoint.alt()->Degrees() + 1600./Options::zoomFactor() );
				LabelPoint.setAz( LabelPoint.az()->Degrees() + 8000./Options::zoomFactor() );
				LabelPoint.HorizontalToEquatorial( data->LST, data->geo()->lat() );
				o = getXY( &LabelPoint, Options::useAltAz(), false, scale );
			}

			//p2 is a skypoint offset from LabelPoint by +/-1 degree azimuth (scaled by
			//2000./zoomFactor).  We use p2 to determine the rotation angle for the
			//Horizon label, which we want to be parallel to the line between LabelPoint and p2.
			SkyPoint p2( LabelPoint.ra(), LabelPoint.dec() );
			p2.EquatorialToHorizontal( data->LST, data->geo()->lat() );
			p2.setAz( p2.az()->Degrees() + 2000./Options::zoomFactor() );
			p2.HorizontalToEquatorial( data->LST, data->geo()->lat() );

			//o and o2 are the screen positions of LabelPoint and p2
			o = getXY( &LabelPoint, Options::useAltAz(), false, scale );
			o2 = getXY( &p2, Options::useAltAz(), false, scale );

			double sx = double( o.x() - o2.x() );
			double sy = double( o.y() - o2.y() );
			double angle;
			if ( sx ) {
				angle = atan( sy/sx )*180.0/dms::PI;
			} else {
				angle = 90.0;
				if ( sy < 0 ) angle = -90.0;
			}

			//Finally, draw the "Equator" label at the determined location and angle
			psky.save();
			psky.translate( o.x(), o.y() );
			psky.rotate( double( angle ) );  //rotate the coordinate system
			psky.drawText( 0, 0, i18n( "Horizon" ) );
			psky.restore(); //reset coordinate system
		}
	}  //endif drawing horizon
}

void SkyMap::drawConstellationLines( QPainter& psky, double scale )
{
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	//Draw Constellation Lines
	psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "CLineColor" ) ), 1, SolidLine ) ); //change to colorGrid
	int iLast = -1;

	for ( SkyPoint *p = data->clineList.first(); p; p = data->clineList.next() ) {
		QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );

		if ( ( o.x() >= -1000 && o.x() <= Width+1000 && o.y() >=-1000 && o.y() <= Height+1000 ) ) {
			if ( data->clineModeList.at(data->clineList.at())->latin1()=='M' ) {
				psky.moveTo( o.x(), o.y() );
			} else if ( data->clineModeList.at(data->clineList.at())->latin1()=='D' ) {
				if ( data->clineList.at()== (int)(iLast+1) ) {
					psky.lineTo( o.x(), o.y() );
				} else {
					psky.moveTo( o.x(), o.y() );
				}
			}
			iLast = data->clineList.at();
		}
  }
}

void SkyMap::drawConstellationBoundaries( QPainter &psky, double scale ) {
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "CBoundColor" ) ), 1, SolidLine ) );

	for ( CSegment *seg = data->csegmentList.first(); seg; seg = data->csegmentList.next() ) {
		bool started( false );
		SkyPoint *p = seg->firstNode();
		QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
		if ( ( o.x() >= -1000 && o.x() <= Width+1000 && o.y() >=-1000 && o.y() <= Height+1000 ) ) {
			psky.moveTo( o.x(), o.y() );
			started = true;
		}

		for ( p = seg->nextNode(); p; p = seg->nextNode() ) {
			QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );

			if ( ( o.x() >= -1000 && o.x() <= Width+1000 && o.y() >=-1000 && o.y() <= Height+1000 ) ) {
				if ( started ) {
					psky.lineTo( o.x(), o.y() );
				} else {
					psky.moveTo( o.x(), o.y() );
					started = true;
				}
			} else {
				started = false;
			}
		}
	}
}

void SkyMap::drawConstellationNames( QPainter& psky, QFont& stdFont, double scale ) {
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	//Draw Constellation Names
	psky.setFont( stdFont );
	psky.setPen( QColor( data->colorScheme()->colorNamed( "CNameColor" ) ) );
	for ( SkyObject *p = data->cnameList.first(); p; p = data->cnameList.next() ) {
		if ( checkVisibility( p, fov(), XRange ) ) {
			QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
			if (o.x() >= 0 && o.x() <= Width && o.y() >=0 && o.y() <= Height ) {
				if ( Options::useLatinConstellNames() ) {
					int dx = 5*p->name().length();
					psky.drawText( o.x()-dx, o.y(), p->name() );  // latin constellation names
				} else if ( Options::useLocalConstellNames() ) {
					// can't use translatedName() because we need the context string in i18n()
					int dx = 5*( i18n( "Constellation name (optional)", p->name().local8Bit().data() ).length() );
					psky.drawText( o.x()-dx, o.y(), i18n( "Constellation name (optional)", p->name().local8Bit().data() ) ); // localized constellation names
				} else {
					int dx = 5*p->name2().length();
					psky.drawText( o.x()-dx, o.y(), p->name2() ); //name2 is the IAU abbreviation
				}
			}
		}
  }
}

void SkyMap::drawStars( QPainter& psky, double scale ) {
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	bool checkSlewing = ( ( slewing || ( clockSlewing && data->clock()->isActive() ) )
				&& Options::hideOnSlew() );

//shortcuts to inform wheter to draw different objects
	bool hideFaintStars( checkSlewing && Options::hideStars() );

	if ( Options::showStars() ) {
		//adjust maglimit for ZoomLevel
		double lgmin = log10(MINZOOM);
		double lgmax = log10(MAXZOOM);
		double lgz = log10(Options::zoomFactor());

		double maglim = Options::magLimitDrawStar();
		if ( lgz <= 0.75*lgmax ) maglim -= (Options::magLimitDrawStar() - Options::magLimitDrawStarZoomOut() )*(0.75*lgmax - lgz)/(0.75*lgmax - lgmin);
		float sizeFactor = 6.0 + (lgz - lgmin);

		for ( StarObject *curStar = data->starList.first(); curStar; curStar = data->starList.next() ) {
			// break loop if maglim is reached
			if ( curStar->mag() > maglim || ( hideFaintStars && curStar->mag() > Options::magLimitHideStar() ) ) break;

			if ( checkVisibility( curStar, fov(), XRange ) ) {
				QPoint o = getXY( curStar, Options::useAltAz(), Options::useRefraction(), scale );

				// draw star if currently on screen
				if (o.x() >= 0 && o.x() <= Width && o.y() >=0 && o.y() <= Height ) {
					int size = int( scale * ( sizeFactor*( maglim - curStar->mag())/maglim ) + 1 );

					if ( size > 0 ) {
						QChar c = curStar->color();
						QPixmap *spixmap = starpix->getPixmap( &c, size );
						curStar->draw( psky, sky, spixmap, o.x(), o.y(), true, scale );

						// now that we have drawn the star, we can display some extra info
						if ( !checkSlewing && (curStar->mag() <= Options::magLimitDrawStarInfo() )
								&& ( Options::showStarNames() || Options::showStarMagnitudes() ) ) {

							psky.setPen( QColor( data->colorScheme()->colorNamed( "SNameColor" ) ) );
							curStar->drawLabel( psky, o.x(), o.y(), Options::zoomFactor(),
									Options::showStarNames(), Options::showStarMagnitudes(), scale );
						}
					}
				}
			}
		}
	}
}

void SkyMap::drawDeepSkyCatalog( QPainter& psky, QPtrList<DeepSkyObject>& catalog, QColor& color,
			bool drawObject, bool drawImage, double scale )
{
	if ( drawObject || drawImage ) {  //don't do anything if nothing is to be drawn!
		int Width = int( scale * width() );
		int Height = int( scale * height() );

		// Set color once
		psky.setPen( color );
		psky.setBrush( NoBrush );
		QColor colorHST  = data->colorScheme()->colorNamed( "HSTColor" );

		double maglim = Options::magLimitDrawDeepSky();

		//FIXME
		//disabling faint limits until the NGC/IC catalog has reasonable mags
		//adjust maglimit for ZoomLevel
		//double lgmin = log10(MINZOOM);
		//double lgmax = log10(MAXZOOM);
		//double lgz = log10(Options::zoomFactor());
		//if ( lgz <= 0.75*lgmax ) maglim -= (Options::magLimitDrawDeepSky() - Options::magLimitDrawDeepSkyZoomOut() )*(0.75*lgmax - lgz)/(0.75*lgmax - lgmin);
		//else
			maglim = 40.0; //show all deep-sky objects

		for ( DeepSkyObject *obj = catalog.first(); obj; obj = catalog.next() ) {
			if ( checkVisibility( obj, fov(), XRange ) ) {
				float mag = obj->mag();
				//only draw objects if flags set and its brighter than maglim (unless mag is undefined (=99.9)
				if ( mag > 90.0 || mag < (float)maglim ) {
					QPoint o = getXY( obj, Options::useAltAz(), Options::useRefraction(), scale );
					if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) {
						//PA for Deep-Sky objects is 90 + PA because major axis is horizontal at PA=0
						double PositionAngle = 90. + findPA( obj, o.x(), o.y(), scale );

						//Draw Image
						if ( drawImage && Options::zoomFactor() > 5.*MINZOOM ) {
							obj->drawImage( psky, o.x(), o.y(), PositionAngle, Options::zoomFactor(), scale );
						}

						//Draw Symbol
						if ( drawObject ) {
							//change color if extra images are available
							// most objects don't have those, so we only change colors temporarily
							// for the few exceptions. Changing color is expensive!!!
							bool bColorChanged = false;
							if ( obj->isCatalogM() && obj->ImageList.count() > 1 ) {
								psky.setPen( colorHST );
								bColorChanged = true;
							} else if ( (!obj->isCatalogM()) && obj->ImageList.count() ) {
								psky.setPen( colorHST );
								bColorChanged = true;
							}

							obj->drawSymbol( psky, o.x(), o.y(), PositionAngle, Options::zoomFactor(), scale );

							// revert temporary color change
							if ( bColorChanged ) {
								psky.setPen( color );
							}
						}
					}
				}
			} else { //Object failed checkVisible(); delete it's Image pointer, if it exists.
				if ( obj->image() ) {
					obj->deleteImage();
				}
			}
		}
	}
}

void SkyMap::drawDeepSkyObjects( QPainter& psky, double scale )
{
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	QImage ScaledImage;

	bool checkSlewing = ( ( slewing || ( clockSlewing && data->clock()->isActive() ) )
				&& Options::hideOnSlew() );

//shortcuts to inform wheter to draw different objects
	bool drawMess( Options::showDeepSky() && ( Options::showMessier() || Options::showMessierImages() ) && !(checkSlewing && Options::hideMessier() ) );
	bool drawNGC( Options::showDeepSky() && Options::showNGC() && !(checkSlewing && Options::hideNGC() ) );
	bool drawOther( Options::showDeepSky() && Options::showOther() && !(checkSlewing && Options::hideOther() ) );
	bool drawIC( Options::showDeepSky() && Options::showIC() && !(checkSlewing && Options::hideIC() ) );
	bool drawImages( Options::showMessierImages() );

	// calculate color objects once, outside the loop
	QColor colorMess = data->colorScheme()->colorNamed( "MessColor" );
	QColor colorIC  = data->colorScheme()->colorNamed( "ICColor" );
	QColor colorNGC  = data->colorScheme()->colorNamed( "NGCColor" );

	// draw Messier catalog
	if ( drawMess ) {
		drawDeepSkyCatalog( psky, data->deepSkyListMessier, colorMess, Options::showMessier(), drawImages, scale );
	}

	// draw NGC Catalog
	if ( drawNGC ) {
		drawDeepSkyCatalog( psky, data->deepSkyListNGC, colorNGC, true, drawImages, scale );
	}

	// draw IC catalog
	if ( drawIC ) {
		drawDeepSkyCatalog( psky, data->deepSkyListIC, colorIC, true, drawImages, scale );
	}

	// draw the rest
	if ( drawOther ) {
		//Use NGC color for now...
		drawDeepSkyCatalog( psky, data->deepSkyListOther, colorNGC, true, drawImages, scale );
	}

	//Draw Custom Catalogs
	for ( register unsigned int i=0; i<Options::catalogCount(); ++i ) { //loop over custom catalogs
		if ( Options::showCatalog()[i] ) {

			psky.setBrush( NoBrush );
			psky.setPen( QColor( data->colorScheme()->colorNamed( "NGCColor" ) ) );

			QPtrList<DeepSkyObject> cat = data->CustomCatalogs[ Options::catalogName()[i] ];

			for ( DeepSkyObject *obj = cat.first(); obj; obj = cat.next() ) {

				if ( checkVisibility( obj, fov(), XRange ) ) {
					QPoint o = getXY( obj, Options::useAltAz(), Options::useRefraction(), scale );

					if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) {

						if ( obj->type()==0 ) {
							StarObject *starobj = (StarObject*)obj;
							float zoomlim = 7.0 + ( Options::zoomFactor()/MINZOOM)/50.0;
							float mag = starobj->mag();
							float sizeFactor = 2.0;
							int size = int( sizeFactor*(zoomlim - mag) ) + 1;
							if (size>23) size=23;
							if ( size ) {
								QChar c = starobj->color();
								QPixmap *spixmap = starpix->getPixmap( &c, size );
								starobj->draw( psky, sky, spixmap, o.x(), o.y(), true, scale );
							}
						} else {
							//PA for Deep-Sky objects is 90 + PA because major axis is horizontal at PA=0
							double pa = 90. + findPA( obj, o.x(), o.y(), scale );
							obj->drawImage( psky, o.x(), o.y(), pa, Options::zoomFactor() );
							obj->drawSymbol( psky, o.x(), o.y(), pa, Options::zoomFactor() );
						}
					}
				}
			}
		}
	}
}

void SkyMap::drawAttachedLabels( QPainter &psky, double scale ) {
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	psky.setPen( data->colorScheme()->colorNamed( "UserLabelColor" ) );

	bool checkSlewing = ( slewing || ( clockSlewing && data->clock()->isActive() ) ) && Options::hideOnSlew();
	bool drawPlanets( Options::showPlanets() && !(checkSlewing && Options::hidePlanets() ) );
	bool drawComets( drawPlanets && Options::showComets() );
	bool drawAsteroids( drawPlanets && Options::showAsteroids() );
	bool drawMessier( Options::showDeepSky() && ( Options::showMessier() || Options::showMessierImages() ) && !(checkSlewing && Options::hideMessier() ) );
	bool drawNGC( Options::showDeepSky() && Options::showNGC() && !(checkSlewing && Options::hideNGC() ) );
	bool drawIC( Options::showDeepSky() && Options::showIC() && !(checkSlewing && Options::hideIC() ) );
	bool drawOther( Options::showDeepSky() && Options::showOther() && !(checkSlewing && Options::hideOther() ) );
	bool drawSAO = ( Options::showStars() );
	bool hideFaintStars( checkSlewing && Options::hideStars() );

	for ( SkyObject *obj = data->ObjLabelList.first(); obj; obj = data->ObjLabelList.next() ) {
		//Only draw an attached label if the object is being drawn to the map
		//reproducing logic from other draw funcs here...not an optimal solution
		if ( obj->type() == SkyObject::STAR ) {
			if ( ! drawSAO ) return;
			if ( obj->mag() > Options::magLimitDrawStar() ) return;
			if ( hideFaintStars && obj->mag() > Options::magLimitHideStar() ) return;
		}
		if ( obj->type() == SkyObject::PLANET ) {
			if ( ! drawPlanets ) return;
			if ( obj->name() == "Sun" && ! Options::showSun() ) return;
			if ( obj->name() == "Mercury" && ! Options::showMercury() ) return;
			if ( obj->name() == "Venus" && ! Options::showVenus() ) return;
			if ( obj->name() == "Moon" && ! Options::showMoon() ) return;
			if ( obj->name() == "Mars" && ! Options::showMars() ) return;
			if ( obj->name() == "Jupiter" && ! Options::showJupiter() ) return;
			if ( obj->name() == "Saturn" && ! Options::showSaturn() ) return;
			if ( obj->name() == "Uranus" && ! Options::showUranus() ) return;
			if ( obj->name() == "Neptune" && ! Options::showNeptune() ) return;
			if ( obj->name() == "Pluto" && ! Options::showPluto() ) return;
		}
		if ( obj->type() >= SkyObject::OPEN_CLUSTER && obj->type() <= SkyObject::GALAXY ) {
			if ( ((DeepSkyObject*)obj)->isCatalogM() && ! drawMessier ) return;
			if ( ((DeepSkyObject*)obj)->isCatalogNGC() && ! drawNGC ) return;
			if ( ((DeepSkyObject*)obj)->isCatalogIC() && ! drawIC ) return;
			if ( ((DeepSkyObject*)obj)->isCatalogNone() && ! drawOther ) return;
		}
		if ( obj->type() == SkyObject::COMET && ! drawComets ) return;
		if ( obj->type() == SkyObject::ASTEROID && ! drawAsteroids ) return;

		if ( checkVisibility( obj, fov(), XRange ) ) {
			QPoint o = getXY( obj, Options::useAltAz(), Options::useRefraction(), scale );
			if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) {
				drawNameLabel( psky, obj, o.x(), o.y(), scale );
			}
		}
	}

	//Attach a label to the centered object
	if ( focusObject() != NULL && Options::useAutoLabel() ) {
		QPoint o = getXY( focusObject(), Options::useAltAz(), Options::useRefraction(), scale );
		if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height )
			drawNameLabel( psky, focusObject(), o.x(), o.y(), scale );
	}
}

void SkyMap::drawNameLabel( QPainter &psky, SkyObject *obj, int x, int y, double scale ) {
	int size(0);

	//Stars
	if ( obj->type() == SkyObject::STAR ) {
		((StarObject*)obj)->drawLabel( psky, x, y, Options::zoomFactor(), true, false, scale );
		return;

	//Solar system
	} else if ( obj->type() == SkyObject::PLANET
						|| obj->type() == SkyObject::ASTEROID
						|| obj->type() == SkyObject::COMET ) {
		KSPlanetBase *p = (KSPlanetBase*)obj;
		size = int( p->angSize() * scale * dms::PI * Options::zoomFactor()/10800.0 );
		int minsize = 4;
		if ( p->type() == SkyObject::ASTEROID || p->type() == SkyObject::COMET )
			minsize = 2;
		if ( p->name() == "Sun" || p->name() == "Moon" )
			minsize = 8;
		if ( size < minsize )
			size = minsize;
		if ( p->name() == "Saturn" )
			size = int(2.5*size);

	//Other
	} else {
		//Calculate object size in pixels
		float majorAxis = ((DeepSkyObject*)obj)->a();
		if ( majorAxis == 0.0 && obj->type() == 1 ) majorAxis = 1.0; //catalog stars
		size = int( majorAxis * scale * dms::PI * Options::zoomFactor()/10800.0 );
	}

	int offset = int( ( 0.5*size + 4 ) );
	psky.drawText( x+offset, y+offset, obj->translatedName() );
}

void SkyMap::drawPlanetTrail( QPainter& psky, KSPlanetBase *ksp, double scale )
{
	if ( ksp->hasTrail() ) {
		int Width = int( scale * width() );
		int Height = int( scale * height() );

		QColor tcolor1 = QColor( data->colorScheme()->colorNamed( "PlanetTrailColor" ) );
		QColor tcolor2 = QColor( data->colorScheme()->colorNamed( "SkyColor" ) );

		SkyPoint *p = ksp->trail()->first();
		QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
		QPoint cur( o );

		bool doDrawLine(false);
		int i = 0;
		int n = ksp->trail()->count();

		if ( ( o.x() >= -1000 && o.x() <= Width+1000 && o.y() >=-1000 && o.y() <= Height+1000 ) ) {
			psky.moveTo(o.x(), o.y());
			doDrawLine = true;
		}

		psky.setPen( QPen( tcolor1, 1 ) );
		for ( p = ksp->trail()->next(); p; p = ksp->trail()->next() ) {
			if ( Options::fadePlanetTrails() ) {
				//Define interpolated color
				QColor tcolor = QColor(
							(i*tcolor1.red()   + (n-i)*tcolor2.red())/n,
							(i*tcolor1.green() + (n-i)*tcolor2.green())/n,
							(i*tcolor1.blue()  + (n-i)*tcolor2.blue())/n );
				++i;
				psky.setPen( QPen( tcolor, 1 ) );
			}

			o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );
			if ( ( o.x() >= -1000 && o.x() <= Width+1000 && o.y() >=-1000 && o.y() <= Height+1000 ) ) {

				//Want to disable line-drawing if this point and the last are both outside bounds of display.
				if ( ! rect().contains( o ) && ! rect().contains( cur ) ) doDrawLine = false;
				cur = o;

				if ( doDrawLine ) {
					psky.lineTo( o.x(), o.y() );
				} else {
					psky.moveTo( o.x(), o.y() );
					doDrawLine = true;
				}
			}
		}
	}
}

void SkyMap::drawSolarSystem( QPainter& psky, bool drawPlanets, double scale )
{
	int Width = int( scale * width() );
	int Height = int( scale * height() );

	if ( drawPlanets ) {
		//Draw all trails first so they never appear "in front of" solar system bodies
		//draw Trail
		if ( Options::showSun() && data->PCat->findByName("Sun")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Sun"), scale );
		if ( Options::showMercury() && data->PCat->findByName("Mercury")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Mercury"), scale );
		if ( Options::showVenus() && data->PCat->findByName("Venus")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Venus"), scale );
		if ( Options::showMoon() && data->Moon->hasTrail() ) drawPlanetTrail( psky, data->Moon, scale );
		if ( Options::showMars() && data->PCat->findByName("Mars")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Mars"), scale );
		if ( Options::showJupiter() && data->PCat->findByName("Jupiter")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Jupiter"), scale );
		if ( Options::showSaturn() && data->PCat->findByName("Saturn")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Saturn"), scale );
		if ( Options::showUranus() && data->PCat->findByName("Uranus")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Uranus"), scale );
		if ( Options::showNeptune() && data->PCat->findByName("Neptune")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Neptune"), scale );
		if ( Options::showPluto() && data->PCat->findByName("Pluto")->hasTrail() ) drawPlanetTrail( psky, data->PCat->findByName("Pluto"), scale );
		if ( Options::showAsteroids() ) {
			for ( KSAsteroid *ast = data->asteroidList.first(); ast; ast = data->asteroidList.next() ) {
				if ( ast->mag() > Options::magLimitAsteroid() ) break;
				if ( ast->hasTrail() ) drawPlanetTrail( psky, ast, scale );
			}
		}
		if ( Options::showComets() ) {
			for ( KSComet *com = data->cometList.first(); com; com = data->cometList.next() ) {
				if ( com->hasTrail() ) drawPlanetTrail( psky, com, scale );
			}
		}

		//Now draw the actual solar system bodies.  Draw furthest to closest.
		//Draw Asteroids
		if ( Options::showAsteroids() ) {
			for ( KSAsteroid *ast = data->asteroidList.first(); ast; ast = data->asteroidList.next() ) {
				if ( ast->mag() > Options::magLimitAsteroid() ) break;

				if ( checkVisibility( ast, fov(), XRange ) ) {
					psky.setPen( QPen( QColor( "gray" ) ) );
					psky.setBrush( QBrush( QColor( "gray" ) ) );
					QPoint o = getXY( ast, Options::useAltAz(), Options::useRefraction(), scale );

					if ( ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) ) {
						int size = int( ast->angSize() * scale * dms::PI * Options::zoomFactor()/10800.0 );
						if ( size < 1 ) size = 1;
						int x1 = o.x() - size/2;
						int y1 = o.y() - size/2;
						psky.drawEllipse( x1, y1, size, size );

						//draw Name
						if ( Options::showAsteroidNames() && ast->mag() < Options::magLimitAsteroidName() ) {
							psky.setPen( QColor( data->colorScheme()->colorNamed( "PNameColor" ) ) );
							drawNameLabel( psky, ast, o.x(), o.y(), scale );
						}
					}
				}
			}
		}

		//Draw Comets
		if ( Options::showComets() ) {
			for ( KSComet *com = data->cometList.first(); com; com = data->cometList.next() ) {
				if ( checkVisibility( com, fov(), XRange ) ) {
					psky.setPen( QPen( QColor( "cyan4" ) ) );
					psky.setBrush( QBrush( QColor( "cyan4" ) ) );
					QPoint o = getXY( com, Options::useAltAz(), Options::useRefraction(), scale );

					if ( ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) ) {
						int size = int( com->angSize() * scale * dms::PI * Options::zoomFactor()/10800.0 );
						if ( size < 1 ) size = 1;
						int x1 = o.x() - size/2;
						int y1 = o.y() - size/2;
						psky.drawEllipse( x1, y1, size, size );

						//draw Name
						if ( Options::showCometNames() && com->rsun() < Options::maxRadCometName() ) {
							psky.setPen( QColor( data->colorScheme()->colorNamed( "PNameColor" ) ) );
							drawNameLabel( psky, com, o.x(), o.y(), scale );
						}
					}
				}
			}
		}

		//Draw Pluto
		if ( Options::showPluto() ) {
			drawPlanet(psky, data->PCat->findByName("Pluto"), QColor( "gray" ), 50.*MINZOOM, 1, scale );
		}

		//Draw Neptune
		if ( Options::showNeptune() ) {
			drawPlanet(psky, data->PCat->findByName("Neptune"), QColor( "SkyBlue" ), 20.*MINZOOM, 1, scale );
		}

		//Draw Uranus
		if ( Options::showUranus() ) {
			drawPlanet(psky, data->PCat->findByName("Uranus"), QColor( "LightSeaGreen" ), 20.*MINZOOM, 1, scale );
		}

		//Draw Saturn
		if ( Options::showSaturn() ) {
			drawPlanet(psky, data->PCat->findByName("Saturn"), QColor( "LightYellow2" ), 20.*MINZOOM, 2, scale );
		}

		//Draw Jupiter and its moons.
		//Draw all moons first, then Jupiter, then redraw moons that are in front of Jupiter.
		if ( Options::showJupiter() ) {
			//Draw Jovian moons
			psky.setPen( QPen( QColor( "white" ) ) );
			if ( Options::zoomFactor() > 10.*MINZOOM ) {
				for ( unsigned int i=0; i<4; ++i ) {
					QPoint o = getXY( data->jmoons->pos(i), Options::useAltAz(), Options::useRefraction(), scale );
					if ( ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) ) {
						psky.drawEllipse( o.x()-1, o.y()-1, 2, 2 );
					}
				}
			}

			drawPlanet(psky, data->PCat->findByName("Jupiter"), QColor( "Goldenrod" ), 20.*MINZOOM, 1, scale );

			//Re-draw Jovian moons which are in front of Jupiter, also draw all 4 moon labels.
			psky.setPen( QPen( QColor( "white" ) ) );
			if ( Options::zoomFactor() > 10.*MINZOOM ) {
				QFont pfont = psky.font();
				QFont moonFont = psky.font();
				moonFont.setPointSize( pfont.pointSize() - 2 );
				psky.setFont( moonFont );

				for ( unsigned int i=0; i<4; ++i ) {
					QPoint o = getXY( data->jmoons->pos(i), Options::useAltAz(), Options::useRefraction(), scale );
					if ( ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) ) {
						if ( data->jmoons->z(i) < 0.0 ) //Moon is nearer than Jupiter
							psky.drawEllipse( o.x()-1, o.y()-1, 2, 2 );

						//Draw Moon name labels if at high zoom
						if ( Options::showPlanetNames() && Options::zoomFactor() > 50.*MINZOOM ) {
							int offset = int(3*scale);
							psky.drawText( o.x() + offset, o.y() + offset, data->jmoons->name(i) );
						}
					}
				}

				//reset font
				psky.setFont( pfont );
			}
		}

		//Draw Mars
		if ( Options::showMars() ) {
			drawPlanet(psky, data->PCat->findByName("Mars"), QColor( "Red" ), 20.*MINZOOM, 1, scale );
		}

		//For the inner planets, we need to determine the distance-order
		//because the order can change with time
		double rv = data->PCat->findByName("Venus")->rearth();
		double rm = data->PCat->findByName("Mercury")->rearth();
		double rs = data->PCat->findByName("Sun")->rearth();
		unsigned int iv(0), im(0), is(0);
		if ( rm > rs ) im++;
		if ( rm > rv ) im++;
		if ( rv > rs ) iv++;
		if ( rv > rm ) iv++;
		if ( rs > rm ) is++;
		if ( rs > rv ) is++;

		for ( unsigned int i=0; i<3; i++ ) {
			if ( i==is ) {
				//Draw Sun
				if ( Options::showSun() ) {
					drawPlanet(psky, data->PCat->findByName("Sun"), QColor( "Yellow" ), MINZOOM, 1, scale );
				}
			} else if ( i==im ) {
				//Draw Mercury
				if ( Options::showMercury() ) {
					drawPlanet(psky, data->PCat->findByName("Mercury"), QColor( "SlateBlue1" ), 20.*MINZOOM, 1, scale );
				}
			} else if ( i==iv ) {
				//Draw Venus
				if ( Options::showVenus() ) {
					drawPlanet(psky, data->PCat->findByName("Venus"), QColor( "LightGreen" ), 20.*MINZOOM, 1, scale );
				}
			}
		}

		//Draw Moon
		if ( Options::showMoon() ) {
			drawPlanet(psky, data->Moon, QColor( "White" ), MINZOOM, 1, scale );
		}
	}
}

void SkyMap::drawPlanet( QPainter &psky, KSPlanetBase *p, QColor c,
		double zoommin, int resize_mult, double scale ) {

	if ( checkVisibility( p, fov(), XRange ) ) {
		int Width = int( scale * width() );
		int Height = int( scale * height() );

		int sizemin = 4;
		if ( p->name() == "Sun" || p->name() == "Moon" ) sizemin = 8;
		sizemin = int( sizemin * scale );

		psky.setPen( c );
		psky.setBrush( c );
		QPoint o = getXY( p, Options::useAltAz(), Options::useRefraction(), scale );

		//Is planet onscreen?
		if ( o.x() >= 0 && o.x() <= Width && o.y() >= 0 && o.y() <= Height ) {
			int size = int( p->angSize() * scale * dms::PI * Options::zoomFactor()/10800.0 );
			if ( size < sizemin ) size = sizemin;

			//Draw planet image if:
			if ( Options::showPlanetImages() &&  //user wants them,
					int(Options::zoomFactor()) >= int(zoommin) &&  //zoomed in enough,
					!p->image()->isNull() &&  //image loaded ok,
					size < Width ) {  //and size isn't too big.

				//Image size must be modified to account for possibility that rotated image's
				//size is bigger than original image size.  The rotated image is a square
				//superscribed on the original image.  The superscribed square is larger than
				//the original square by a factor of (cos(t) + sin(t)) where t is the angle by
				//which the two squares are rotated (in our case, equal to the position angle +
				//the north angle, reduced between 0 and 90 degrees).
				//The proof is left as an exercise to the student :)
				dms pa( findPA( p, o.x(), o.y(), scale ) );
				double spa, cpa;
				pa.SinCos( spa, cpa );
				cpa = fabs(cpa);
				spa = fabs(spa);
				size = int( size * (cpa + spa) );

				//Because Saturn has rings, we inflate its image size by a factor 2.5
				if ( p->name() == "Saturn" ) size = int(2.5*size);

				if (resize_mult != 1) {
					size *= resize_mult;
				}

				p->scaleRotateImage( size, pa.Degrees() );
				int x1 = o.x() - p->image()->width()/2;
				int y1 = o.y() - p->image()->height()/2;
				psky.drawImage( x1, y1, *(p->image()));

			} else {                                   //Otherwise, draw a simple circle.

				psky.drawEllipse( o.x()-size/2, o.y()-size/2, size, size );
			}

			//draw Name
			if ( Options::showPlanetNames() ) {
				psky.setPen( QColor( data->colorScheme()->colorNamed( "PNameColor" ) ) );
				drawNameLabel( psky, p, o.x(), o.y(), scale );
			}
		}
	}
}

void SkyMap::exportSkyImage( const QPaintDevice *pd ) {
	QPainter p;

	//shortcuts to inform wheter to draw different objects
	bool drawPlanets( Options::showPlanets() );
	bool drawMW( Options::showMilkyWay() );
	bool drawCNames( Options::showCNames() );
	bool drawCLines( Options::showCLines() );
	bool drawCBounds( Options::showCBounds() );
	bool drawGrid( Options::showGrid() );

	p.begin( pd );
	QPaintDeviceMetrics pdm( p.device() );

	//scale image such that it fills 90% of the x or y dimension on the paint device
	double xscale = double(pdm.width()) / double(width());
	double yscale = double(pdm.height()) / double(height());
	double scale = (xscale < yscale) ? xscale : yscale;

	int pdWidth = int( scale * width() );
	int pdHeight = int( scale * height() );
	int x1 = int( 0.5*(pdm.width()  - pdWidth) );
	int y1 = int( 0.5*(pdm.height()  - pdHeight) );

	p.setClipRect( QRect( x1, y1, pdWidth, pdHeight ) );
	p.setClipping( true );

	//Fill background with sky color
	p.fillRect( x1, y1, pdWidth, pdHeight, QBrush( data->colorScheme()->colorNamed( "SkyColor" ) ) );

	if ( x1 || y1 ) p.translate( x1, y1 );

	QFont stdFont = p.font();
	QFont smallFont = p.font();
	smallFont.setPointSize( stdFont.pointSize() - 2 );

	if ( drawMW ) drawMilkyWay( p, scale );
	if ( drawGrid ) drawCoordinateGrid( p, scale );

	if ( drawCBounds ) drawConstellationBoundaries( p, scale );
	if ( drawCLines ) drawConstellationLines( p, scale );
	if ( drawCNames ) drawConstellationNames( p, stdFont, scale );

	if ( Options::showEquator() ) drawEquator( p, scale );
	if ( Options::showEcliptic() ) drawEcliptic( p, scale );

	// stars and planets use the same font size
	if ( Options::zoomFactor() < 10.*MINZOOM ) {
		p.setFont( smallFont );
	} else {
		p.setFont( stdFont );
	}

	drawStars( p, scale );
	drawDeepSkyObjects( p, scale );
	drawSolarSystem( p, drawPlanets, scale );
	drawAttachedLabels( p, scale );
	drawHorizon( p, stdFont, scale );

	p.end();
}

void SkyMap::setMapGeometry() {
	guidemax = int(Options::zoomFactor()/10.0);

	isPoleVisible = false;
	if ( Options::useAltAz() ) {
		XRange = 1.2*fov()/cos( focus()->alt()->radians() );
		Ymax = fabs( focus()->alt()->Degrees() ) + fov();
	} else {
		XRange = 1.2*fov()/cos( focus()->dec()->radians() );
		Ymax = fabs( focus()->dec()->Degrees() ) + fov();
	}
	if ( Ymax >= 90. ) isPoleVisible = true;

	//at high zoom, double FOV for guide lines so they don't disappear.
	guideFOV = fov();
	guideXRange = XRange;
	if ( Options::zoomFactor() > 10.*MINZOOM ) { guideFOV *= 2.0; guideXRange *= 2.0; }
}
