diff options
Diffstat (limited to 'kstars/kstars/skymapdraw.cpp')
-rw-r--r-- | kstars/kstars/skymapdraw.cpp | 1920 |
1 files changed, 1920 insertions, 0 deletions
diff --git a/kstars/kstars/skymapdraw.cpp b/kstars/kstars/skymapdraw.cpp new file mode 100644 index 00000000..e032b37a --- /dev/null +++ b/kstars/kstars/skymapdraw.cpp @@ -0,0 +1,1920 @@ +/*************************************************************************** + 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 "customcatalog.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 ); + + showFocusCoords( true ); + drawBoxes( p ); + + //draw FOV symbol + ksw->data()->fovSymbol.draw( p, (float)(Options::fOVSize() * Options::zoomFactor()/57.3/60.0) ); + drawTelescopeSymbols( p ); + drawObservingList( 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 ); + + 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 ); + } + } + } +} + +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::drawObservingList( QPainter &psky, double scale ) { + psky.setPen( QPen( QColor( data->colorScheme()->colorNamed( "ObsListColor" ) ), 1 ) ); + + if ( ksw && ksw->observingList()->count() ) { + for ( SkyObject* obj = ksw->observingList()->first(); obj; obj = ksw->observingList()->next() ) { + + if ( checkVisibility( obj, fov(), XRange ) ) { + QPoint o = getXY( obj, Options::useAltAz(), Options::useRefraction() ); + + // label object if it is currently on screen + if (o.x() >= 0 && o.x() <= width() && o.y() >=0 && o.y() <= height() ) { + if ( Options::obsListSymbol() ) { + int size = int(20*scale); + int x1 = o.x() - size/2; + int y1 = o.y() - size/2; + psky.drawArc( x1, y1, size, size, -60*16, 120*16 ); + psky.drawArc( x1, y1, size, size, 120*16, 120*16 ); + } + if ( Options::obsListText() ) { + drawNameLabel( psky, obj, o.x(), o.y(), scale ); + } + } + } + } + } +} + +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), useAltAz(false); + SkyPoint indi_sp; + + 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++) + { + useAltAz = false; + useJ2000 = false; + + // 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 == NULL) + { + eqNum = devMenu->mgr.at(i)->indi_dev.at(j)->findProp("HORIZONTAL_COORD"); + if (eqNum == NULL) continue; + else + useAltAz = true; + } + else + useJ2000 = true; + } + + // make sure it has RA and DEC properties + if ( eqNum) + { + //fprintf(stderr, "Looking for RA label\n"); + if (useAltAz) + { + lp = eqNum->findElement("AZ"); + if (!lp) + continue; + + dms azDMS(lp->value); + + lp = eqNum->findElement("ALT"); + if (!lp) + continue; + + dms altDMS(lp->value); + + indi_sp.setAz(azDMS); + indi_sp.setAlt(altDMS); + + } + else + { + + 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; + + indi_sp.setRA(raDMS); + indi_sp.setDec(decDMS); + + if (useJ2000) + { + indi_sp.setRA0(raDMS); + indi_sp.setDec0(decDMS); + indi_sp.apparentCoord( (double) J2000, ksw->data()->ut().djd()); + } + + if ( Options::useAltAz() ) indi_sp.EquatorialToHorizontal( ksw->LST(), ksw->geo()->lat() ); + + } + + QPoint P = getXY( &indi_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 ); + + 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./5.; //120 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, 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; + + 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, double scale ) { + int Width = int( scale * width() ); + int Height = int( scale * height() ); + + //Draw Constellation Names + 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 + //don't label unnamed stars with the generic "star" name + bool drawName = ( Options::showStarNames() && (curStar->name() != i18n("star") ) ); + if ( !checkSlewing && (curStar->mag() <= Options::magLimitDrawStarInfo() ) + && ( drawName || Options::showStarMagnitudes() ) ) { + + psky.setPen( QColor( data->colorScheme()->colorNamed( "SNameColor" ) ) ); + QFont stdFont( psky.font() ); + QFont smallFont( stdFont ); + smallFont.setPointSize( stdFont.pointSize() - 2 ); + if ( Options::zoomFactor() < 10.*MINZOOM ) { + psky.setFont( smallFont ); + } else { + psky.setFont( stdFont ); + } + + curStar->drawLabel( psky, o.x(), o.y(), Options::zoomFactor(), + drawName, Options::showStarMagnitudes(), scale ); + + //reset font + psky.setFont( stdFont ); + } + } + } + } + } + } +} + +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 ) { + double PositionAngle = 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<data->CustomCatalogs.count(); ++i ) { + if ( Options::showCatalog()[i] ) { + QPtrList<SkyObject> cat = data->CustomCatalogs.at(i)->objList(); + + for ( SkyObject *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 > 0 ) { + QChar c = starobj->color(); + QPixmap *spixmap = starpix->getPixmap( &c, size ); + + // Try to paint with the selected color + spixmap->fill(QColor( data->CustomCatalogs.at(i)->color() )); + starobj->draw( psky, sky, spixmap, o.x(), o.y(), true, scale ); + // After drawing the star we display some extra info like + // the name ... + bool drawName = ( Options::showStarNames() && (starobj->name() != i18n("star") ) ); + if ( !checkSlewing && (starobj->mag() <= Options::magLimitDrawStarInfo() ) + && ( drawName || Options::showStarMagnitudes() ) ) { + + psky.setPen( QColor( data->colorScheme()->colorNamed( "SNameColor" ) ) ); + QFont stdFont( psky.font() ); + QFont smallFont( stdFont ); + smallFont.setPointSize( stdFont.pointSize() - 2 ); + if ( Options::zoomFactor() < 10.*MINZOOM ) { + psky.setFont( smallFont ); + } else { + psky.setFont( stdFont ); + } + + starobj->drawLabel( psky, o.x(), o.y(), Options::zoomFactor(), drawName, Options::showStarMagnitudes(), scale ); + + //reset font + psky.setFont( stdFont ); + } + } + } else { + DeepSkyObject *dso = (DeepSkyObject*)obj; + double pa = findPA( dso, o.x(), o.y(), scale ); + dso->drawImage( psky, o.x(), o.y(), pa, Options::zoomFactor() ); + + psky.setBrush( NoBrush ); + psky.setPen( QColor( data->CustomCatalogs.at(i)->color() ) ); + + dso->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); + + QFont stdFont( psky.font() ); + QFont smallFont( stdFont ); + smallFont.setPointSize( stdFont.pointSize() - 2 ); + if ( Options::zoomFactor() < 10.*MINZOOM ) { + psky.setFont( smallFont ); + } else { + psky.setFont( stdFont ); + } + + //Stars + if ( obj->type() == SkyObject::STAR ) { + ((StarObject*)obj)->drawLabel( psky, x, y, Options::zoomFactor(), true, false, scale ); + psky.setFont( stdFont ); + 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() ); + + //Reset font + psky.setFont( stdFont ); +} + +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 ); + + if ( drawMW ) drawMilkyWay( p, scale ); + if ( drawGrid ) drawCoordinateGrid( p, scale ); + + if ( drawCBounds ) drawConstellationBoundaries( p, scale ); + if ( drawCLines ) drawConstellationLines( p, scale ); + if ( drawCNames ) drawConstellationNames( p, scale ); + + if ( Options::showEquator() ) drawEquator( p, scale ); + if ( Options::showEcliptic() ) drawEcliptic( p, scale ); + + drawStars( p, scale ); + drawDeepSkyObjects( p, scale ); + drawSolarSystem( p, drawPlanets, scale ); + drawAttachedLabels( p, scale ); + drawObservingList( p, scale ); + drawHorizon( p, 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; } +} |