You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1377 lines
45 KiB
1377 lines
45 KiB
/***************************************************************************
|
|
skymap.cpp - Trinity Desktop Planetarium
|
|
-------------------
|
|
begin : Sat Feb 10 2001
|
|
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. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <tdeapplication.h>
|
|
#include <tdeconfig.h>
|
|
#include <kiconloader.h>
|
|
#include <kstatusbar.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdeaction.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <tqmemarray.h>
|
|
#include <tqpointarray.h>
|
|
#include <tqcursor.h>
|
|
#include <tqbitmap.h>
|
|
#include <tqpainter.h>
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "skymap.h"
|
|
#include "Options.h"
|
|
#include "kstars.h"
|
|
#include "kstarsdata.h"
|
|
#include "imageviewer.h"
|
|
#include "infoboxes.h"
|
|
#include "detaildialog.h"
|
|
#include "addlinkdialog.h"
|
|
#include "kspopupmenu.h"
|
|
#include "simclock.h"
|
|
#include "skyobject.h"
|
|
#include "deepskyobject.h"
|
|
#include "ksmoon.h"
|
|
#include "ksasteroid.h"
|
|
#include "kscomet.h"
|
|
#include "starobject.h"
|
|
#include "customcatalog.h"
|
|
|
|
SkyMap::SkyMap(KStarsData *d, TQWidget *parent, const char *name )
|
|
: TQWidget (parent,name), computeSkymap(true), angularDistanceMode(false),
|
|
ksw(0), data(d), pmenu(0), sky(0), sky2(0), IBoxes(0),
|
|
ClickedObject(0), FocusObject(0), TransientObject(0),
|
|
starpix(0), pts(0), sp(0)
|
|
{
|
|
if ( parent ) ksw = (KStars*) parent->parent();
|
|
else ksw = 0;
|
|
|
|
pts = new TQPointArray( 2000 ); // points for milkyway and horizon
|
|
sp = new SkyPoint(); // needed by coordinate grid
|
|
|
|
ZoomRect = TQRect();
|
|
|
|
setDefaultMouseCursor(); // set the cross cursor
|
|
|
|
// load the pixmaps of stars
|
|
starpix = new StarPixmap( data->colorScheme()->starColorMode(), data->colorScheme()->starColorIntensity() );
|
|
|
|
setBackgroundColor( TQColor( data->colorScheme()->colorNamed( "SkyColor" ) ) );
|
|
setBackgroundMode( TQWidget::NoBackground );
|
|
setFocusPolicy( TQ_StrongFocus );
|
|
setMinimumSize( 380, 250 );
|
|
setSizePolicy( TQSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Expanding ) );
|
|
|
|
setMouseTracking (true); //Generate MouseMove events!
|
|
midMouseButtonDown = false;
|
|
mouseButtonDown = false;
|
|
slewing = false;
|
|
clockSlewing = false;
|
|
|
|
ClickedObject = NULL;
|
|
FocusObject = NULL;
|
|
|
|
sky = new TQPixmap();
|
|
sky2 = new TQPixmap();
|
|
pmenu = new KSPopupMenu( ksw );
|
|
|
|
//Initialize Transient label stuff
|
|
TransientTimeout = 100; //fade label color every 0.2 sec
|
|
connect( &HoverTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotTransientLabel() ) );
|
|
connect( &TransientTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotTransientTimeout() ) );
|
|
|
|
IBoxes = new InfoBoxes( Options::windowWidth(), Options::windowHeight(),
|
|
Options::positionTimeBox(), Options::shadeTimeBox(),
|
|
Options::positionGeoBox(), Options::shadeGeoBox(),
|
|
Options::positionFocusBox(), Options::shadeFocusBox(),
|
|
data->colorScheme()->colorNamed( "BoxTextColor" ),
|
|
data->colorScheme()->colorNamed( "BoxGrabColor" ),
|
|
data->colorScheme()->colorNamed( "BoxBGColor" ) );
|
|
|
|
IBoxes->showTimeBox( Options::showTimeBox() );
|
|
IBoxes->showFocusBox( Options::showFocusBox() );
|
|
IBoxes->showGeoBox( Options::showGeoBox() );
|
|
IBoxes->timeBox()->setAnchorFlag( Options::stickyTimeBox() );
|
|
IBoxes->geoBox()->setAnchorFlag( Options::stickyGeoBox() );
|
|
IBoxes->focusBox()->setAnchorFlag( Options::stickyFocusBox() );
|
|
|
|
IBoxes->geoChanged( data->geo() );
|
|
|
|
connect( IBoxes->timeBox(), TQT_SIGNAL( shaded(bool) ), data, TQT_SLOT( saveTimeBoxShaded(bool) ) );
|
|
connect( IBoxes->geoBox(), TQT_SIGNAL( shaded(bool) ), data, TQT_SLOT( saveGeoBoxShaded(bool) ) );
|
|
connect( IBoxes->focusBox(), TQT_SIGNAL( shaded(bool) ), data, TQT_SLOT( saveFocusBoxShaded(bool) ) );
|
|
connect( IBoxes->timeBox(), TQT_SIGNAL( moved(TQPoint) ), data, TQT_SLOT( saveTimeBoxPos(TQPoint) ) );
|
|
connect( IBoxes->geoBox(), TQT_SIGNAL( moved(TQPoint) ), data, TQT_SLOT( saveGeoBoxPos(TQPoint) ) );
|
|
connect( IBoxes->focusBox(), TQT_SIGNAL( moved(TQPoint) ), data, TQT_SLOT( saveFocusBoxPos(TQPoint) ) );
|
|
|
|
connect( this, TQT_SIGNAL( destinationChanged() ), this, TQT_SLOT( slewFocus() ) );
|
|
|
|
//Initialize Refraction correction lookup table arrays. RefractCorr1 is for calculating
|
|
//the apparent altitude from the true altitude, and RefractCorr2 is for the reverse.
|
|
for ( unsigned int index = 0; index <184; ++index ) {
|
|
double alt = -1.75 + index*0.5; //start at -1.75 degrees to get midpoint value for each interval.
|
|
|
|
RefractCorr1[index] = 1.02 / tan( dms::PI*( alt + 10.3/(alt + 5.11) )/180.0 ) / 60.0; //correction in degrees.
|
|
RefractCorr2[index] = -1.0 / tan( dms::PI*( alt + 7.31/(alt + 4.4) )/180.0 ) / 60.0;
|
|
}
|
|
}
|
|
|
|
SkyMap::~SkyMap() {
|
|
delete starpix;
|
|
delete pts;
|
|
delete sp;
|
|
delete sky;
|
|
delete sky2;
|
|
delete pmenu;
|
|
delete IBoxes;
|
|
|
|
//Deprecated...DeepSkyObject dtor now handles this itself.
|
|
/*//delete any remaining object Image pointers
|
|
for ( DeepSkyObject *obj = data->deepSkyListMessier.first(); obj; obj = data->deepSkyListMessier.next() ) {
|
|
if ( obj->image() ) obj->deleteImage();
|
|
}
|
|
for ( DeepSkyObject *obj = data->deepSkyListNGC.first(); obj; obj = data->deepSkyListNGC.next() ) {
|
|
if ( obj->image() ) obj->deleteImage();
|
|
}
|
|
for ( DeepSkyObject *obj = data->deepSkyListIC.first(); obj; obj = data->deepSkyListIC.next() ) {
|
|
if ( obj->image() ) obj->deleteImage();
|
|
}
|
|
for ( DeepSkyObject *obj = data->deepSkyListOther.first(); obj; obj = data->deepSkyListOther.next() ) {
|
|
if ( obj->image() ) obj->deleteImage();
|
|
}*/
|
|
}
|
|
|
|
void SkyMap::setGeometry( int x, int y, int w, int h ) {
|
|
TQWidget::setGeometry( x, y, w, h );
|
|
sky->resize( w, h );
|
|
sky2->resize( w, h );
|
|
}
|
|
|
|
void SkyMap::setGeometry( const TQRect &r ) {
|
|
TQWidget::setGeometry( r );
|
|
sky->resize( r.width(), r.height() );
|
|
sky2->resize( r.width(), r.height() );
|
|
}
|
|
|
|
|
|
void SkyMap::showFocusCoords( bool coordsOnly ) {
|
|
if ( ! coordsOnly ) {
|
|
//display object info in infoBoxes
|
|
TQString oname;
|
|
oname = i18n( "nothing" );
|
|
if ( focusObject() != NULL && Options::isTracking() )
|
|
oname = focusObject()->translatedLongName();
|
|
|
|
infoBoxes()->focusObjChanged(oname);
|
|
}
|
|
|
|
if ( Options::useAltAz() && Options::useRefraction() ) {
|
|
SkyPoint corrFocus( *(focus()) );
|
|
corrFocus.setAlt( refract( focus()->alt(), false ) );
|
|
corrFocus.HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
corrFocus.setAlt( refract( focus()->alt(), true ) );
|
|
infoBoxes()->focusCoordChanged( &corrFocus );
|
|
} else {
|
|
infoBoxes()->focusCoordChanged( focus() );
|
|
}
|
|
}
|
|
|
|
SkyObject* SkyMap::objectNearest( SkyPoint *p ) {
|
|
double r0 = 200.0/Options::zoomFactor(); //the maximum search radius
|
|
double rmin = r0;
|
|
|
|
//Search stars database for nearby object.
|
|
double rstar_min = r0;
|
|
double starmag_min = 20.0; //absurd initial value
|
|
int istar_min = -1;
|
|
|
|
if ( Options::showStars() ) { //Can only click on a star if it's being drawn!
|
|
|
|
//test RA and dec to see if this star is roughly nearby
|
|
|
|
for ( register unsigned int i=0; i<data->starList.count(); ++i ) {
|
|
SkyObject *test = (SkyObject *)data->starList.at(i);
|
|
|
|
double dRA = test->ra()->Hours() - p->ra()->Hours();
|
|
double dDec = test->dec()->Degrees() - p->dec()->Degrees();
|
|
//determine angular distance between this object and mouse cursor
|
|
double f = 15.0*cos( test->dec()->radians() );
|
|
double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
|
|
if (r < r0 && test->mag() < starmag_min ) {
|
|
istar_min = i;
|
|
rstar_min = r;
|
|
starmag_min = test->mag();
|
|
}
|
|
}
|
|
}
|
|
|
|
//Next, find the nearest solar system body within r0
|
|
double r = 0.0;
|
|
double rsolar_min = r0;
|
|
SkyObject *solarminobj = NULL;
|
|
|
|
if ( Options::showPlanets() )
|
|
solarminobj = data->PCat->findClosest( p, r );
|
|
|
|
if ( r < r0 ) {
|
|
rsolar_min = r;
|
|
} else {
|
|
solarminobj = NULL;
|
|
}
|
|
|
|
//Moon
|
|
if ( Options::showMoon() ) {
|
|
double dRA = data->Moon->ra()->Hours() - p->ra()->Hours();
|
|
double dDec = data->Moon->dec()->Degrees() - p->dec()->Degrees();
|
|
double f = 15.0*cos( data->Moon->dec()->radians() );
|
|
r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
|
|
if (r < rsolar_min) {
|
|
solarminobj= data->Moon;
|
|
rsolar_min = r;
|
|
}
|
|
}
|
|
|
|
//Asteroids
|
|
if ( Options::showAsteroids() ) {
|
|
for ( KSAsteroid *ast = data->asteroidList.first(); ast; ast = data->asteroidList.next() ) {
|
|
//test RA and dec to see if this object is roughly nearby
|
|
double dRA = ast->ra()->Hours() - p->ra()->Hours();
|
|
double dDec = ast->dec()->Degrees() - p->dec()->Degrees();
|
|
double f = 15.0*cos( ast->dec()->radians() );
|
|
double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
|
|
if ( r < rsolar_min && ast->mag() < Options::magLimitAsteroid() ) {
|
|
solarminobj = ast;
|
|
rsolar_min = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Comets
|
|
if ( Options::showComets() ) {
|
|
for ( KSComet *com = data->cometList.first(); com; com = data->cometList.next() ) {
|
|
//test RA and dec to see if this object is roughly nearby
|
|
double dRA = com->ra()->Hours() - p->ra()->Hours();
|
|
double dDec = com->dec()->Degrees() - p->dec()->Degrees();
|
|
double f = 15.0*cos( com->dec()->radians() );
|
|
double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
|
|
if ( r < rsolar_min ) {
|
|
solarminobj = com;
|
|
rsolar_min = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Next, search for nearest deep-sky object within r0
|
|
double rmess_min = r0;
|
|
double rngc_min = r0;
|
|
double ric_min = r0;
|
|
double rother_min = r0;
|
|
int imess_min = -1;
|
|
int ingc_min = -1;
|
|
int iic_min = -1;
|
|
int iother_min = -1;
|
|
|
|
for ( DeepSkyObject *o = data->deepSkyList.first(); o; o = data->deepSkyList.next() ) {
|
|
bool checkObject = false;
|
|
if ( o->isCatalogM() &&
|
|
( Options::showMessier() || Options::showMessierImages() ) ) checkObject = true;
|
|
if ( o->isCatalogNGC() && Options::showNGC() ) checkObject = true;
|
|
if ( o->isCatalogIC() && Options::showIC() ) checkObject = true;
|
|
if ( o->catalog().isEmpty() && Options::showOther() ) checkObject = true;
|
|
|
|
if ( checkObject ) {
|
|
//test RA and dec to see if this object is roughly nearby
|
|
double dRA = o->ra()->Hours() - p->ra()->Hours();
|
|
double dDec = o->dec()->Degrees() - p->dec()->Degrees();
|
|
double f = 15.0*cos( o->dec()->radians() );
|
|
double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
|
|
if ( o->isCatalogM() && r < rmess_min) {
|
|
imess_min = data->deepSkyList.at();
|
|
rmess_min = r;
|
|
}
|
|
if ( o->isCatalogNGC() && r < rngc_min) {
|
|
ingc_min = data->deepSkyList.at();
|
|
rngc_min = r;
|
|
}
|
|
if ( o->isCatalogIC() && r < ric_min) {
|
|
iic_min = data->deepSkyList.at();
|
|
ric_min = r;
|
|
}
|
|
if ( o->catalog().isEmpty() && r < rother_min) {
|
|
iother_min = data->deepSkyList.at();
|
|
rother_min = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Next, search for nearest object within r0 among the custom catalogs
|
|
double rcust_min = r0;
|
|
int icust_min = -1;
|
|
int icust_cat = -1;
|
|
|
|
for ( register unsigned int j=0; j< data->CustomCatalogs.count(); ++j ) {
|
|
if ( Options::showCatalog()[j] ) {
|
|
TQPtrList<SkyObject> catList = data->CustomCatalogs.at(j)->objList();
|
|
|
|
for ( register unsigned int i=0; i<catList.count(); ++i ) {
|
|
//test RA and dec to see if this object is roughly nearby
|
|
SkyObject *test = (SkyObject *)catList.at(i);
|
|
double dRA = test->ra()->Hours()-p->ra()->Hours();
|
|
double dDec = test->dec()->Degrees()-p->dec()->Degrees();
|
|
double f = 15.0*cos( test->dec()->radians() );
|
|
double r = f*f*dRA*dRA + dDec*dDec; //no need to take sqrt, we just want to ID smallest value.
|
|
if (r < rcust_min) {
|
|
icust_cat = j;
|
|
icust_min = i;
|
|
rcust_min = r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int jmin(-1);
|
|
int icat(-1);
|
|
|
|
//Among the objects selected within r0, prioritize the selection by catalog:
|
|
//Planets, Messier, NGC, IC, stars
|
|
if ( istar_min >= 0 && rstar_min < r0 ) {
|
|
rmin = rstar_min;
|
|
icat = 0; //set catalog to star
|
|
}
|
|
|
|
//IC object overrides star, unless star is twice as close as IC object
|
|
if ( iic_min >= 0 && ric_min < r0 && rmin > 0.5*ric_min ) {
|
|
rmin = ric_min;
|
|
icat = 1; //set catalog to Deep Sky
|
|
jmin = iic_min;
|
|
}
|
|
|
|
//NGC object overrides previous selection, unless previous is twice as close
|
|
if ( ingc_min >= 0 && rngc_min < r0 && rmin > 0.5*rngc_min ) {
|
|
rmin = rngc_min;
|
|
icat = 1; //set catalog to Deep Sky
|
|
jmin = ingc_min;
|
|
}
|
|
|
|
//"other" object overrides previous selection, unless previous is twice as close
|
|
if ( iother_min >= 0 && rother_min < r0 && rmin > 0.5*rother_min ) {
|
|
rmin = rother_min;
|
|
icat = 1; //set catalog to Deep Sky
|
|
jmin = iother_min;
|
|
}
|
|
|
|
//Messier object overrides previous selection, unless previous is twice as close
|
|
if ( imess_min >= 0 && rmess_min < r0 && rmin > 0.5*rmess_min ) {
|
|
rmin = rmess_min;
|
|
icat = 1; //set catalog to Deep Sky
|
|
jmin = imess_min;
|
|
}
|
|
|
|
//Custom object overrides previous selection, unless previous is twice as close
|
|
if ( icust_min >= 0 && rcust_min < r0 && rmin > 0.5*rcust_min ) {
|
|
rmin = rcust_min;
|
|
icat = 2; //set catalog to Custom
|
|
}
|
|
|
|
//Solar system body overrides previous selection, unless previous selection is twice as close
|
|
if ( solarminobj != NULL && rmin > 0.5*rsolar_min ) {
|
|
rmin = rsolar_min;
|
|
icat = 3; //set catalog to solar system
|
|
}
|
|
|
|
TQPtrList<SkyObject> cat;
|
|
|
|
switch (icat) {
|
|
case 0: //star
|
|
return data->starList.at(istar_min);
|
|
break;
|
|
|
|
case 1: //Deep-Sky Objects
|
|
return data->deepSkyList.at(jmin);
|
|
break;
|
|
|
|
case 2: //Custom Catalog Object
|
|
cat = data->CustomCatalogs.at(icust_cat)->objList();
|
|
return cat.at(icust_min);
|
|
break;
|
|
|
|
case 3: //solar system object
|
|
return solarminobj;
|
|
break;
|
|
|
|
default: //no object found
|
|
return NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SkyMap::slotTransientLabel( void ) {
|
|
//This function is only called if the HoverTimer manages to timeout.
|
|
//(HoverTimer is restarted with every mouseMoveEvent; so if it times
|
|
//out, that means there was no mouse movement for HOVER_INTERVAL msec.)
|
|
//Identify the object nearest to the mouse cursor as the
|
|
//TransientObject. The TransientObject is automatically labeled
|
|
//in SkyMap::paintEvent().
|
|
//Note that when the TransientObject pointer is not NULL, the next
|
|
//mouseMoveEvent calls fadeTransientLabel(), which will fade out the
|
|
//TransientLabel and then set TransientObject to NULL.
|
|
//
|
|
//Do not show a transient label if the map is in motion, or if the mouse
|
|
//pointer is below the opaque horizon, or if the object has a permanent label
|
|
if ( ! slewing && ! ( Options::useAltAz() && Options::showGround() &&
|
|
mousePoint()->alt()->Degrees() < 0.0 ) ) {
|
|
SkyObject *so = objectNearest( mousePoint() );
|
|
|
|
if ( so && ! isObjectLabeled( so ) ) {
|
|
setTransientObject( so );
|
|
|
|
TransientColor = data->colorScheme()->colorNamed( "UserLabelColor" );
|
|
if ( TransientTimer.isActive() ) TransientTimer.stop();
|
|
update();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//Slots
|
|
|
|
void SkyMap::slotTransientTimeout( void ) {
|
|
//Don't fade label if the transientObject is now the focusObject!
|
|
if ( transientObject() == focusObject() && Options::useAutoLabel() ) {
|
|
setTransientObject( NULL );
|
|
TransientTimer.stop();
|
|
return;
|
|
}
|
|
|
|
//to fade the labels, we will need to smoothly transition from UserLabelColor to SkyColor.
|
|
TQColor c1 = data->colorScheme()->colorNamed( "UserLabelColor" );
|
|
TQColor c2 = data->colorScheme()->colorNamed( "SkyColor" );
|
|
|
|
int dRed = ( c2.red() - c1.red() )/20;
|
|
int dGreen = ( c2.green() - c1.green() )/20;
|
|
int dBlue = ( c2.blue() - c1.blue() )/20;
|
|
int newRed = TransientColor.red() + dRed;
|
|
int newGreen = TransientColor.green() + dGreen;
|
|
int newBlue = TransientColor.blue() + dBlue;
|
|
|
|
//Check to see if we have arrived at the target color (SkyColor).
|
|
//If so, point TransientObject to NULL.
|
|
if ( abs(newRed-c2.red()) < abs(dRed) || abs(newGreen-c2.green()) < abs(dGreen) || abs(newBlue-c2.blue()) < abs(dBlue) ) {
|
|
setTransientObject( NULL );
|
|
TransientTimer.stop();
|
|
} else {
|
|
TransientColor.setRgb( newRed, newGreen, newBlue );
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void SkyMap::setFocusObject( SkyObject *o ) {
|
|
FocusObject = o;
|
|
|
|
if ( FocusObject )
|
|
Options::setFocusObject( FocusObject->name() );
|
|
else
|
|
Options::setFocusObject( i18n( "nothing" ) );
|
|
}
|
|
|
|
void SkyMap::slotCenter( void ) {
|
|
setFocusPoint( clickedPoint() );
|
|
if ( Options::useAltAz() )
|
|
focusPoint()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
|
|
//clear the planet trail of old focusObject, if it was temporary
|
|
if ( focusObject() && focusObject()->isSolarSystem() && data->temporaryTrail ) {
|
|
((KSPlanetBase*)focusObject())->clearTrail();
|
|
data->temporaryTrail = false;
|
|
}
|
|
|
|
//If the requested object is below the opaque horizon, issue a warning message
|
|
//(unless user is already pointed below the horizon)
|
|
if ( Options::useAltAz() && Options::showGround() &&
|
|
focus()->alt()->Degrees() > -1.0 && focusPoint()->alt()->Degrees() < -1.0 ) {
|
|
|
|
TQString caption = i18n( "Requested Position Below Horizon" );
|
|
TQString message = i18n( "The requested position is below the horizon.\nWould you like to go there anyway?" );
|
|
if ( KMessageBox::warningYesNo( this, message, caption,
|
|
i18n("Go Anyway"), i18n("Keep Position"), "dag_focus_below_horiz" )==KMessageBox::No ) {
|
|
setClickedObject( NULL );
|
|
setFocusObject( NULL );
|
|
Options::setIsTracking( false );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
//set FocusObject before slewing. Otherwise, KStarsData::updateTime() can reset
|
|
//destination to previous object...
|
|
setFocusObject( ClickedObject );
|
|
Options::setIsTracking( true );
|
|
if ( ksw ) {
|
|
ksw->actionCollection()->action("track_object")->setIconSet( BarIcon( "encrypted" ) );
|
|
ksw->toolBar( "mainToolBar" )->setButtonIconSet( 4, BarIcon( "encrypted" ) );
|
|
ksw->actionCollection()->action("track_object")->setText( i18n( "Stop &Tracking" ) );
|
|
}
|
|
|
|
//If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail
|
|
if ( focusObject() && focusObject()->isSolarSystem()
|
|
&& Options::useAutoTrail()
|
|
&& ! ((KSPlanetBase*)focusObject())->hasTrail() ) {
|
|
((KSPlanetBase*)focusObject())->addToTrail();
|
|
data->temporaryTrail = true;
|
|
}
|
|
|
|
//update the destination to the selected coordinates
|
|
if ( Options::useAltAz() ) {
|
|
if ( Options::useRefraction() )
|
|
setDestinationAltAz( refract( focusPoint()->alt(), true ).Degrees(), focusPoint()->az()->Degrees() );
|
|
else
|
|
setDestinationAltAz( focusPoint()->alt()->Degrees(), focusPoint()->az()->Degrees() );
|
|
} else {
|
|
setDestination( focusPoint() );
|
|
}
|
|
|
|
focusPoint()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
|
|
//display coordinates in statusBar
|
|
if ( ksw ) {
|
|
TQString sX = focusPoint()->az()->toDMSString();
|
|
TQString sY = focusPoint()->alt()->toDMSString(true);
|
|
if ( Options::useAltAz() && Options::useRefraction() )
|
|
sY = refract( focusPoint()->alt(), true ).toDMSString(true);
|
|
TQString s = sX + ", " + sY;
|
|
ksw->statusBar()->changeItem( s, 1 );
|
|
s = focusPoint()->ra()->toHMSString() + ", " + focusPoint()->dec()->toDMSString(true);
|
|
ksw->statusBar()->changeItem( s, 2 );
|
|
}
|
|
|
|
showFocusCoords(); //update FocusBox
|
|
}
|
|
|
|
void SkyMap::slotDSS( void ) {
|
|
TQString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=1" );
|
|
TQString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" );
|
|
dms ra(0.0), dec(0.0);
|
|
TQString RAString, DecString;
|
|
char decsgn;
|
|
|
|
//ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords
|
|
//if we clicked on empty sky, we need to precess to J2000.
|
|
if ( clickedObject() ) {
|
|
ra.setH( clickedObject()->ra0()->Hours() );
|
|
dec.setD( clickedObject()->dec0()->Degrees() );
|
|
} else {
|
|
//move present coords temporarily to ra0,dec0 (needed for precessToAnyEpoch)
|
|
clickedPoint()->setRA0( clickedPoint()->ra()->Hours() );
|
|
clickedPoint()->setDec0( clickedPoint()->dec()->Degrees() );
|
|
clickedPoint()->precessFromAnyEpoch( data->ut().djd(), J2000 );
|
|
ra.setH( clickedPoint()->ra()->Hours() );
|
|
dec.setD( clickedPoint()->dec()->Degrees() );
|
|
|
|
//restore coords from present epoch
|
|
clickedPoint()->setRA( clickedPoint()->ra0()->Hours() );
|
|
clickedPoint()->setDec( clickedPoint()->dec0()->Degrees() );
|
|
}
|
|
|
|
RAString = RAString.sprintf( "&r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
|
|
|
|
decsgn = '+';
|
|
if ( dec.Degrees() < 0.0 ) decsgn = '-';
|
|
int dd = abs( dec.degree() );
|
|
int dm = abs( dec.arcmin() );
|
|
int ds = abs( dec.arcsec() );
|
|
DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
|
|
|
|
//concat all the segments into the kview command line:
|
|
KURL url (URLprefix + RAString + DecString + URLsuffix);
|
|
|
|
TQString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." );
|
|
new ImageViewer (&url, message, this);
|
|
}
|
|
|
|
void SkyMap::slotDSS2( void ) {
|
|
TQString URLprefix( "http://archive.stsci.edu/cgi-bin/dss_search?v=2r" );
|
|
TQString URLsuffix( "&e=J2000&h=15.0&w=15.0&f=gif&c=none&fov=NONE" );
|
|
dms ra(0.0), dec(0.0);
|
|
TQString RAString, DecString;
|
|
char decsgn;
|
|
|
|
//ra and dec must be the coordinates at J2000. If we clicked on an object, just use the object's ra0, dec0 coords
|
|
//if we clicked on empty sky, we need to precess to J2000.
|
|
if ( clickedObject() ) {
|
|
ra.setH( clickedObject()->ra0()->Hours() );
|
|
dec.setD( clickedObject()->dec0()->Degrees() );
|
|
} else {
|
|
//move present coords temporarily to ra0,dec0 (needed for precessToAnyEpoch)
|
|
clickedPoint()->setRA0( clickedPoint()->ra()->Hours() );
|
|
clickedPoint()->setDec0( clickedPoint()->dec()->Degrees() );
|
|
clickedPoint()->precessFromAnyEpoch( data->ut().djd(), J2000 );
|
|
ra.setH( clickedPoint()->ra()->Hours() );
|
|
dec.setD( clickedPoint()->dec()->Degrees() );
|
|
|
|
//restore coords from present epoch
|
|
clickedPoint()->setRA( clickedPoint()->ra0()->Hours() );
|
|
clickedPoint()->setDec( clickedPoint()->dec0()->Degrees() );
|
|
}
|
|
|
|
RAString = RAString.sprintf( "&r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
|
|
|
|
decsgn = '+';
|
|
if ( dec.Degrees() < 0.0 ) decsgn = '-';
|
|
int dd = abs( dec.degree() );
|
|
int dm = abs( dec.arcmin() );
|
|
int ds = abs( dec.arcsec() );
|
|
|
|
DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
|
|
|
|
//concat all the segments into the kview command line:
|
|
KURL url (URLprefix + RAString + DecString + URLsuffix);
|
|
|
|
TQString message = i18n( "Digitized Sky Survey image provided by the Space Telescope Science Institute." );
|
|
new ImageViewer (&url, message, this);
|
|
}
|
|
|
|
void SkyMap::slotInfo( int id ) {
|
|
TQStringList::Iterator it = clickedObject()->InfoList.at(id-200);
|
|
TQString sURL = (*it);
|
|
KURL url ( sURL );
|
|
if (!url.isEmpty())
|
|
kapp->invokeBrowser(sURL);
|
|
}
|
|
|
|
void SkyMap::slotBeginAngularDistance(void) {
|
|
setPreviousClickedPoint( mousePoint() );
|
|
angularDistanceMode = true;
|
|
beginRulerPoint = getXY( previousClickedPoint(), Options::useAltAz(), Options::useRefraction() );
|
|
endRulerPoint = TQPoint( beginRulerPoint.x(),beginRulerPoint.y() );
|
|
}
|
|
|
|
void SkyMap::slotEndAngularDistance(void) {
|
|
dms angularDistance;
|
|
if(angularDistanceMode) {
|
|
if ( SkyObject *so = objectNearest( mousePoint() ) ) {
|
|
angularDistance = so->angularDistanceTo( previousClickedPoint() );
|
|
ksw->statusBar()->changeItem( so->translatedLongName() +
|
|
" " +
|
|
i18n("Angular distance: " ) +
|
|
angularDistance.toDMSString(), 0 );
|
|
} else {
|
|
angularDistance = mousePoint()->angularDistanceTo( previousClickedPoint() );
|
|
ksw->statusBar()->changeItem( i18n("Angular distance: " ) +
|
|
angularDistance.toDMSString(), 0 );
|
|
}
|
|
angularDistanceMode=false;
|
|
}
|
|
}
|
|
|
|
void SkyMap::slotCancelAngularDistance(void) {
|
|
angularDistanceMode=false;
|
|
}
|
|
|
|
void SkyMap::slotImage( int id ) {
|
|
TQStringList::Iterator it = clickedObject()->ImageList.at(id-100);
|
|
TQStringList::Iterator it2 = clickedObject()->ImageTitle.at(id-100);
|
|
TQString sURL = (*it);
|
|
TQString message = (*it2);
|
|
KURL url ( sURL );
|
|
if (!url.isEmpty())
|
|
new ImageViewer (&url, clickedObject()->messageFromTitle(message), this);
|
|
}
|
|
|
|
bool SkyMap::isObjectLabeled( SkyObject *object ) {
|
|
for ( SkyObject *o = data->ObjLabelList.first(); o; o = data->ObjLabelList.next() ) {
|
|
if ( o == object ) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SkyMap::slotRemoveObjectLabel( void ) {
|
|
for ( SkyObject *o = data->ObjLabelList.first(); o; o = data->ObjLabelList.next() ) {
|
|
if ( o == clickedObject() ) {
|
|
//remove object from list
|
|
data->ObjLabelList.remove();
|
|
break;
|
|
}
|
|
}
|
|
|
|
forceUpdate();
|
|
}
|
|
|
|
void SkyMap::slotAddObjectLabel( void ) {
|
|
data->ObjLabelList.append( clickedObject() );
|
|
//Since we just added a permanent label, we don't want it to fade away!
|
|
if ( transientObject() == clickedObject() ) setTransientObject( NULL );
|
|
forceUpdate();
|
|
}
|
|
|
|
void SkyMap::slotRemovePlanetTrail( void ) {
|
|
//probably don't need this if-statement, but just to be sure...
|
|
if ( clickedObject() && clickedObject()->isSolarSystem() ) {
|
|
((KSPlanetBase*)clickedObject())->clearTrail();
|
|
forceUpdate();
|
|
}
|
|
}
|
|
|
|
void SkyMap::slotAddPlanetTrail( void ) {
|
|
//probably don't need this if-statement, but just to be sure...
|
|
if ( clickedObject() && clickedObject()->isSolarSystem() ) {
|
|
((KSPlanetBase*)clickedObject())->addToTrail();
|
|
forceUpdate();
|
|
}
|
|
}
|
|
|
|
void SkyMap::slotDetail( void ) {
|
|
// check if object is selected
|
|
if ( !clickedObject() ) {
|
|
KMessageBox::sorry( this, i18n("No object selected."), i18n("Object Details") );
|
|
return;
|
|
}
|
|
DetailDialog detail( clickedObject(), data->ut(), data->geo(), ksw );
|
|
detail.exec();
|
|
}
|
|
|
|
void SkyMap::slotClockSlewing() {
|
|
//If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock.
|
|
if ( fabs( data->clock()->scale() ) > Options::slewTimeScale() ) {
|
|
if ( ! clockSlewing ) {
|
|
clockSlewing = true;
|
|
data->clock()->setManualMode( true );
|
|
|
|
// don't change automatically the DST status
|
|
if ( ksw ) ksw->updateTime( false );
|
|
}
|
|
} else {
|
|
if ( clockSlewing ) {
|
|
clockSlewing = false;
|
|
data->clock()->setManualMode( false );
|
|
|
|
// don't change automatically the DST status
|
|
if ( ksw ) ksw->updateTime( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkyMap::setFocus( SkyPoint *p ) {
|
|
setFocus( p->ra()->Hours(), p->dec()->Degrees() );
|
|
}
|
|
|
|
void SkyMap::setFocus( const dms &ra, const dms &dec ) {
|
|
setFocus( ra.Hours(), dec.Degrees() );
|
|
}
|
|
|
|
void SkyMap::setFocus( double ra, double dec ) {
|
|
Focus.set( ra, dec );
|
|
focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
}
|
|
|
|
void SkyMap::setFocusAltAz( const dms &alt, const dms &az) {
|
|
setFocusAltAz( alt.Degrees(), az.Degrees() );
|
|
}
|
|
|
|
void SkyMap::setFocusAltAz(double alt, double az) {
|
|
focus()->setAlt(alt);
|
|
focus()->setAz(az);
|
|
focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
slewing = false;
|
|
|
|
oldfocus()->set( focus()->ra(), focus()->dec() );
|
|
oldfocus()->setAz( focus()->az()->Degrees() );
|
|
oldfocus()->setAlt( focus()->alt()->Degrees() );
|
|
|
|
double dHA = data->LST->Hours() - focus()->ra()->Hours();
|
|
while ( dHA < 0.0 ) dHA += 24.0;
|
|
data->HourAngle->setH( dHA );
|
|
|
|
forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work.
|
|
}
|
|
|
|
void SkyMap::setDestination( SkyPoint *p ) {
|
|
Destination.set( p->ra(), p->dec() );
|
|
destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
emit destinationChanged();
|
|
}
|
|
|
|
void SkyMap::setDestination( const dms &ra, const dms &dec ) {
|
|
Destination.set( ra, dec );
|
|
destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
emit destinationChanged();
|
|
}
|
|
|
|
void SkyMap::setDestination( double ra, double dec ) {
|
|
Destination.set( ra, dec );
|
|
destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
emit destinationChanged();
|
|
}
|
|
|
|
void SkyMap::setDestinationAltAz( const dms &alt, const dms &az) {
|
|
destination()->setAlt(alt);
|
|
destination()->setAz(az);
|
|
destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
emit destinationChanged();
|
|
}
|
|
|
|
void SkyMap::setDestinationAltAz(double alt, double az) {
|
|
destination()->setAlt(alt);
|
|
destination()->setAz(az);
|
|
destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
emit destinationChanged();
|
|
}
|
|
|
|
void SkyMap::updateFocus() {
|
|
if ( Options::isTracking() && focusObject() != NULL ) {
|
|
if ( Options::useAltAz() ) {
|
|
//Tracking any object in Alt/Az mode requires focus updates
|
|
double dAlt = focusObject()->alt()->Degrees();
|
|
if ( Options::useRefraction() )
|
|
dAlt = refract( focusObject()->alt(), true ).Degrees();
|
|
setFocusAltAz( dAlt, focusObject()->az()->Degrees() );
|
|
focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
setDestination( focus() );
|
|
} else {
|
|
//Tracking in equatorial coords
|
|
setFocus( focusObject() );
|
|
focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
setDestination( focus() );
|
|
}
|
|
} else if ( Options::isTracking() && focusPoint() != NULL ) {
|
|
if ( Options::useAltAz() ) {
|
|
//Tracking on empty sky in Alt/Az mode
|
|
setFocus( focusPoint() );
|
|
focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
setDestination( focus() );
|
|
}
|
|
} else if ( ! slewing ) {
|
|
//Not tracking and not slewing, let sky drift by
|
|
if ( Options::useAltAz() ) {
|
|
focus()->setAlt( destination()->alt()->Degrees() );
|
|
focus()->setAz( destination()->az()->Degrees() );
|
|
focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
//destination()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
} else {
|
|
focus()->setRA( data->LST->Hours() - data->HourAngle->Hours() );
|
|
setDestination( focus() );
|
|
focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
destination()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
}
|
|
}
|
|
|
|
//Update the Hour Angle
|
|
data->setHourAngle( data->LST->Hours() - focus()->ra()->Hours() );
|
|
|
|
setOldFocus( focus() );
|
|
oldfocus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
}
|
|
|
|
void SkyMap::slewFocus( void ) {
|
|
double dX, dY, fX, fY, r;
|
|
double step = 1.0;
|
|
SkyPoint newFocus;
|
|
|
|
//Don't slew if the mouse button is pressed
|
|
//Also, no animated slews if the Manual Clock is active
|
|
//08/2002: added possibility for one-time skipping of slew with snapNextFocus
|
|
if ( !mouseButtonDown ) {
|
|
bool goSlew = ( Options::useAnimatedSlewing() &&
|
|
! data->snapNextFocus() ) &&
|
|
!( data->clock()->isManualMode() && data->clock()->isActive() );
|
|
if ( goSlew ) {
|
|
if ( Options::useAltAz() ) {
|
|
dX = destination()->az()->Degrees() - focus()->az()->Degrees();
|
|
dY = destination()->alt()->Degrees() - focus()->alt()->Degrees();
|
|
} else {
|
|
dX = destination()->ra()->Degrees() - focus()->ra()->Degrees();
|
|
dY = destination()->dec()->Degrees() - focus()->dec()->Degrees();
|
|
}
|
|
|
|
//switch directions to go the short way around the celestial sphere, if necessary.
|
|
if ( dX < -180.0 ) dX = 360.0 + dX;
|
|
else if ( dX > 180.0 ) dX = -360.0 + dX;
|
|
|
|
r = sqrt( dX*dX + dY*dY );
|
|
|
|
while ( r > step ) {
|
|
fX = dX / r;
|
|
fY = dY / r;
|
|
|
|
if ( Options::useAltAz() ) {
|
|
focus()->setAlt( focus()->alt()->Degrees() + fY*step );
|
|
focus()->setAz( dms( focus()->az()->Degrees() + fX*step ).reduce() );
|
|
focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
} else {
|
|
fX = fX/15.; //convert RA degrees to hours
|
|
newFocus.set( focus()->ra()->Hours() + fX*step, focus()->dec()->Degrees() + fY*step );
|
|
setFocus( &newFocus );
|
|
focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
}
|
|
|
|
slewing = true;
|
|
//since we are slewing, fade out the transient label
|
|
if ( transientObject() && ! TransientTimer.isActive() )
|
|
fadeTransientLabel();
|
|
|
|
forceUpdate();
|
|
kapp->processEvents(10); //keep up with other stuff
|
|
|
|
if ( Options::useAltAz() ) {
|
|
dX = destination()->az()->Degrees() - focus()->az()->Degrees();
|
|
dY = destination()->alt()->Degrees() - focus()->alt()->Degrees();
|
|
} else {
|
|
dX = destination()->ra()->Degrees() - focus()->ra()->Degrees();
|
|
dY = destination()->dec()->Degrees() - focus()->dec()->Degrees();
|
|
}
|
|
|
|
//switch directions to go the short way around the celestial sphere, if necessary.
|
|
if ( dX < -180.0 ) dX = 360.0 + dX;
|
|
else if ( dX > 180.0 ) dX = -360.0 + dX;
|
|
|
|
r = sqrt( dX*dX + dY*dY );
|
|
}
|
|
}
|
|
|
|
//Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination
|
|
//set focus=destination.
|
|
if ( Options::useAltAz() ) {
|
|
setFocusAltAz( destination()->alt()->Degrees(), destination()->az()->Degrees() );
|
|
focus()->HorizontalToEquatorial( data->LST, data->geo()->lat() );
|
|
} else {
|
|
setFocus( destination() );
|
|
focus()->EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
}
|
|
|
|
data->HourAngle->setH( data->LST->Hours() - focus()->ra()->Hours() );
|
|
slewing = false;
|
|
|
|
//Turn off snapNextFocus, we only want it to happen once
|
|
if ( data->snapNextFocus() ) {
|
|
data->setSnapNextFocus(false);
|
|
}
|
|
|
|
//Start the HoverTimer. if the user leaves the mouse in place after a slew,
|
|
//we want to attach a label to the nearest object.
|
|
if ( Options::useHoverLabel() )
|
|
HoverTimer.start( HOVER_INTERVAL, true );
|
|
|
|
forceUpdate();
|
|
}
|
|
}
|
|
|
|
void SkyMap::invokeKey( int key ) {
|
|
TQKeyEvent *e = new TQKeyEvent( TQEvent::KeyPress, key, 0, 0 );
|
|
keyPressEvent( e );
|
|
delete e;
|
|
}
|
|
|
|
double SkyMap::findPA( SkyObject *o, int x, int y, double scale ) {
|
|
//Find position angle of North using a test point displaced to the north
|
|
//displace by 100/zoomFactor radians (so distance is always 100 pixels)
|
|
//this is 5730/zoomFactor degrees
|
|
double newDec = o->dec()->Degrees() + 5730.0/Options::zoomFactor();
|
|
if ( newDec > 90.0 ) newDec = 90.0;
|
|
SkyPoint test( o->ra()->Hours(), newDec );
|
|
if ( Options::useAltAz() ) test.EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
TQPoint t = getXY( &test, Options::useAltAz(), Options::useRefraction(), scale );
|
|
double dx = double( t.x() - x );
|
|
double dy = double( y - t.y() ); //backwards because TQWidget Y-axis increases to the bottom
|
|
double north;
|
|
if ( dy ) {
|
|
north = atan( dx/dy )*180.0/dms::PI;
|
|
//resolve atan ambiguity:
|
|
if ( dy < 0.0 ) north += 180.0;
|
|
if ( north >= 360.0 ) north -= 360.;
|
|
} else {
|
|
north = 90.0;
|
|
if ( dx > 0 ) north = -90.0;
|
|
}
|
|
|
|
return ( north + o->pa() );
|
|
}
|
|
|
|
TQPoint SkyMap::getXY( SkyPoint *o, bool Horiz, bool doRefraction, double scale ) {
|
|
TQPoint p;
|
|
|
|
double Y, dX;
|
|
double sindX, cosdX, sinY, cosY, sinY0, cosY0;
|
|
|
|
int Width = int( width() * scale );
|
|
int Height = int( height() * scale );
|
|
|
|
double pscale = Options::zoomFactor() * scale;
|
|
|
|
if ( Horiz ) {
|
|
if ( doRefraction ) Y = refract( o->alt(), true ).radians(); //account for atmospheric refraction
|
|
else Y = o->alt()->radians();
|
|
|
|
if ( focus()->az()->Degrees() > 270.0 && o->az()->Degrees() < 90.0 ) {
|
|
dX = 2*dms::PI + focus()->az()->radians() - o->az()->radians();
|
|
} else {
|
|
dX = focus()->az()->radians() - o->az()->radians();
|
|
}
|
|
|
|
focus()->alt()->SinCos( sinY0, cosY0 );
|
|
|
|
} else {
|
|
if (focus()->ra()->Hours() > 18.0 && o->ra()->Hours() < 6.0) {
|
|
dX = 2*dms::PI + o->ra()->radians() - focus()->ra()->radians();
|
|
} else {
|
|
dX = o->ra()->radians() - focus()->ra()->radians();
|
|
}
|
|
Y = o->dec()->radians();
|
|
focus()->dec()->SinCos( sinY0, cosY0 );
|
|
}
|
|
|
|
//Convert dX, Y coords to screen pixel coords.
|
|
#if ( __GLIBC__ >= 2 && __GLIBC_MINOR__ >=1 ) && !defined(__UCLIBC__)
|
|
//GNU version
|
|
sincos( dX, &sindX, &cosdX );
|
|
sincos( Y, &sinY, &cosY );
|
|
#else
|
|
//ANSI version
|
|
sindX = sin(dX);
|
|
cosdX = cos(dX);
|
|
sinY = sin(Y);
|
|
cosY = cos(Y);
|
|
#endif
|
|
|
|
double c = sinY0*sinY + cosY0*cosY*cosdX;
|
|
|
|
if ( c < 0.0 ) { //Object is on "back side" of the celestial sphere; don't plot it.
|
|
p.setX( -10000000 );
|
|
p.setY( -10000000 );
|
|
return p;
|
|
}
|
|
|
|
double k = sqrt( 2.0/( 1 + c ) );
|
|
|
|
p.setX( int( 0.5*Width - pscale*k*cosY*sindX ) );
|
|
p.setY( int( 0.5*Height - pscale*k*( cosY0*sinY - sinY0*cosY*cosdX ) ) );
|
|
|
|
return p;
|
|
}
|
|
|
|
SkyPoint SkyMap::dXdYToRaDec( double dx, double dy, bool useAltAz, dms *LST, const dms *lat, bool doRefract ) {
|
|
//Determine RA and Dec of a point, given (dx, dy): it's pixel
|
|
//coordinates in the SkyMap with the center of the map as the origin.
|
|
|
|
SkyPoint result;
|
|
double sinDec, cosDec, sinDec0, cosDec0, sinc, cosc, sinlat, coslat;
|
|
double xx, yy;
|
|
|
|
double r = sqrt( dx*dx + dy*dy );
|
|
dms centerAngle;
|
|
centerAngle.setRadians( 2.0*asin(0.5*r) );
|
|
|
|
focus()->dec()->SinCos( sinDec0, cosDec0 );
|
|
centerAngle.SinCos( sinc, cosc );
|
|
|
|
if ( useAltAz ) {
|
|
dms HA;
|
|
dms Dec, alt, az, alt0, az0;
|
|
double A;
|
|
double sinAlt, cosAlt, sinAlt0, cosAlt0, sinAz, cosAz;
|
|
// double HA0 = LST - focus.ra();
|
|
az0 = focus()->az()->Degrees();
|
|
alt0 = focus()->alt()->Degrees();
|
|
alt0.SinCos( sinAlt0, cosAlt0 );
|
|
|
|
dx = -dx; //Flip East-west (Az goes in opposite direction of RA)
|
|
yy = dx*sinc;
|
|
xx = r*cosAlt0*cosc - dy*sinAlt0*sinc;
|
|
|
|
A = atan( yy/xx );
|
|
//resolve ambiguity of atan():
|
|
if ( xx<0 ) A = A + dms::PI;
|
|
// if ( xx>0 && yy<0 ) A = A + 2.0*dms::PI;
|
|
|
|
dms deltaAz;
|
|
deltaAz.setRadians( A );
|
|
az = focus()->az()->Degrees() + deltaAz.Degrees();
|
|
alt.setRadians( asin( cosc*sinAlt0 + ( dy*sinc*cosAlt0 )/r ) );
|
|
|
|
if ( doRefract ) alt.setD( refract( &alt, false ).Degrees() ); //find true altitude from apparent altitude
|
|
|
|
az.SinCos( sinAz, cosAz );
|
|
alt.SinCos( sinAlt, cosAlt );
|
|
lat->SinCos( sinlat, coslat );
|
|
|
|
Dec.setRadians( asin( sinAlt*sinlat + cosAlt*coslat*cosAz ) );
|
|
Dec.SinCos( sinDec, cosDec );
|
|
|
|
HA.setRadians( acos( ( sinAlt - sinlat*sinDec )/( coslat*cosDec ) ) );
|
|
if ( sinAz > 0.0 ) HA.setH( 24.0 - HA.Hours() );
|
|
|
|
result.setRA( LST->Hours() - HA.Hours() );
|
|
result.setRA( result.ra()->reduce() );
|
|
result.setDec( Dec.Degrees() );
|
|
|
|
return result;
|
|
|
|
} else {
|
|
yy = dx*sinc;
|
|
xx = r*cosDec0*cosc - dy*sinDec0*sinc;
|
|
|
|
double RARad = ( atan( yy / xx ) );
|
|
//resolve ambiguity of atan():
|
|
if ( xx<0 ) RARad = RARad + dms::PI;
|
|
// if ( xx>0 && yy<0 ) RARad = RARad + 2.0*dms::PI;
|
|
|
|
dms deltaRA, Dec;
|
|
deltaRA.setRadians( RARad );
|
|
Dec.setRadians( asin( cosc*sinDec0 + (dy*sinc*cosDec0)/r ) );
|
|
|
|
result.setRA( focus()->ra()->Hours() + deltaRA.Hours() );
|
|
result.setRA( result.ra()->reduce() );
|
|
result.setDec( Dec.Degrees() );
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
dms SkyMap::refract( const dms *alt, bool findApparent ) {
|
|
if ( alt->Degrees() <= -2.000 ) return dms( alt->Degrees() );
|
|
|
|
int index = int( ( alt->Degrees() + 2.0 )*2. ); //RefractCorr arrays start at alt=-2.0 degrees.
|
|
dms result;
|
|
|
|
//Failsafe: if the index is out of range, return the original angle
|
|
if ( index < 0 || index > 183 ) {
|
|
return dms( alt->Degrees() );
|
|
}
|
|
|
|
if ( findApparent ) {
|
|
result.setD( alt->Degrees() + RefractCorr1[index] );
|
|
} else {
|
|
result.setD( alt->Degrees() + RefractCorr2[index] );
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
// force a new calculation of the skymap (used instead of update(), which may skip the redraw)
|
|
// if now=true, SkyMap::paintEvent() is run immediately, rather than being added to the event queue
|
|
// also, determine new coordinates of mouse cursor.
|
|
void SkyMap::forceUpdate( bool now )
|
|
{
|
|
TQPoint mp( mapFromGlobal( TQCursor::pos() ) );
|
|
double dx = ( 0.5*width() - mp.x() )/Options::zoomFactor();
|
|
double dy = ( 0.5*height() - mp.y() )/Options::zoomFactor();
|
|
|
|
if (! unusablePoint (dx, dy)) {
|
|
//determine RA, Dec of mouse pointer
|
|
setMousePoint( dXdYToRaDec( dx, dy, Options::useAltAz(), data->LST, data->geo()->lat(), Options::useRefraction() ) );
|
|
}
|
|
|
|
computeSkymap = true;
|
|
if ( now ) repaint();
|
|
else update();
|
|
}
|
|
|
|
float SkyMap::fov() {
|
|
if ( width() >= height() )
|
|
return 28.65*width()/Options::zoomFactor();
|
|
else
|
|
return 28.65*height()/Options::zoomFactor();
|
|
}
|
|
|
|
bool SkyMap::checkVisibility( SkyPoint *p, float FOV, double XMax ) {
|
|
double dX, dY;
|
|
bool useAltAz = Options::useAltAz();
|
|
|
|
//Skip objects below the horizon if:
|
|
// + using Horizontal coords,
|
|
// + the ground is drawn,
|
|
// + and either of the following is true:
|
|
// - focus is above the horizon
|
|
// - field of view is larger than 50 degrees
|
|
if ( useAltAz && Options::showGround() && p->alt()->Degrees() < -2.0
|
|
&& ( focus()->alt()->Degrees() > 0. || FOV > 50. ) ) return false;
|
|
|
|
if ( useAltAz ) {
|
|
dY = fabs( p->alt()->Degrees() - focus()->alt()->Degrees() );
|
|
} else {
|
|
dY = fabs( p->dec()->Degrees() - focus()->dec()->Degrees() );
|
|
}
|
|
if ( isPoleVisible ) dY *= 0.75; //increase effective FOV when pole visible.
|
|
if ( dY > FOV ) return false;
|
|
if ( isPoleVisible ) return true;
|
|
|
|
if ( useAltAz ) {
|
|
dX = fabs( p->az()->Degrees() - focus()->az()->Degrees() );
|
|
} else {
|
|
dX = fabs( p->ra()->Degrees() - focus()->ra()->Degrees() );
|
|
}
|
|
if ( dX > 180.0 ) dX = 360.0 - dX; // take shorter distance around sky
|
|
|
|
if ( dX < XMax ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool SkyMap::unusablePoint (double dx, double dy)
|
|
{
|
|
if (dx >= 1.41 || dx <= -1.41 || dy >= 1.41 || dy <= -1.41)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void SkyMap::setZoomMouseCursor()
|
|
{
|
|
mouseMoveCursor = false; // no mousemove cursor
|
|
|
|
TQPainter p;
|
|
TQPixmap cursorPix (32, 32); // size 32x32 (this size is compatible to all systems)
|
|
// the center of the pixmap
|
|
int mx = cursorPix. width() / 2;
|
|
int my = cursorPix. height() / 2;
|
|
|
|
cursorPix.fill (white); // white background
|
|
p.begin (&cursorPix);
|
|
p.setPen (TQPen (black, 2)); // black lines
|
|
|
|
p.drawEllipse( mx - 7, my - 7, 14, 14 );
|
|
p.drawLine( mx + 5, my + 5, mx + 11, my + 11 );
|
|
p.end();
|
|
|
|
// create a mask to make parts of the pixmap invisible
|
|
TQBitmap mask (32, 32);
|
|
mask.fill (color0); // all is invisible
|
|
|
|
p.begin (&mask);
|
|
// paint over the parts which should be visible
|
|
p.setPen (TQPen (color1, 3));
|
|
p.drawEllipse( mx - 7, my - 7, 14, 14 );
|
|
p.drawLine( mx + 5, my + 5, mx + 12, my + 12 );
|
|
p.end();
|
|
|
|
cursorPix.setMask (mask); // set the mask
|
|
TQCursor cursor (cursorPix);
|
|
setCursor (cursor);
|
|
}
|
|
|
|
void SkyMap::setDefaultMouseCursor()
|
|
{
|
|
mouseMoveCursor = false; // no mousemove cursor
|
|
|
|
TQPainter p;
|
|
TQPixmap cursorPix (32, 32); // size 32x32 (this size is compatible to all systems)
|
|
// the center of the pixmap
|
|
int mx = cursorPix. width() / 2;
|
|
int my = cursorPix. height() / 2;
|
|
|
|
cursorPix.fill (white); // white background
|
|
p.begin (&cursorPix);
|
|
p.setPen (TQPen (black, 2)); // black lines
|
|
// 1. diagonal
|
|
p.drawLine (mx - 2, my - 2, mx - 8, mx - 8);
|
|
p.drawLine (mx + 2, my + 2, mx + 8, mx + 8);
|
|
// 2. diagonal
|
|
p.drawLine (mx - 2, my + 2, mx - 8, mx + 8);
|
|
p.drawLine (mx + 2, my - 2, mx + 8, mx - 8);
|
|
p.end();
|
|
|
|
// create a mask to make parts of the pixmap invisible
|
|
TQBitmap mask (32, 32);
|
|
mask.fill (color0); // all is invisible
|
|
|
|
p.begin (&mask);
|
|
// paint over the parts which should be visible
|
|
p.setPen (TQPen (color1, 3));
|
|
// 1. diagonal
|
|
p.drawLine (mx - 2, my - 2, mx - 8, mx - 8);
|
|
p.drawLine (mx + 2, my + 2, mx + 8, mx + 8);
|
|
// 2. diagonal
|
|
p.drawLine (mx - 2, my + 2, mx - 8, mx + 8);
|
|
p.drawLine (mx + 2, my - 2, mx + 8, mx - 8);
|
|
p.end();
|
|
|
|
cursorPix.setMask (mask); // set the mask
|
|
TQCursor cursor (cursorPix);
|
|
setCursor (cursor);
|
|
}
|
|
|
|
void SkyMap::setMouseMoveCursor()
|
|
{
|
|
if (mouseButtonDown)
|
|
{
|
|
setCursor (9); // cursor shape defined in qt
|
|
mouseMoveCursor = true;
|
|
}
|
|
}
|
|
|
|
void SkyMap::addLink( void ) {
|
|
AddLinkDialog adialog( this, clickedObject()->name() );
|
|
TQString entry;
|
|
TQFile file;
|
|
|
|
if ( adialog.exec()==TQDialog::Accepted ) {
|
|
if ( adialog.isImageLink() ) {
|
|
//Add link to object's ImageList, and descriptive text to its ImageTitle list
|
|
clickedObject()->ImageList.append( adialog.url() );
|
|
clickedObject()->ImageTitle.append( adialog.desc() );
|
|
|
|
//Also, update the user's custom image links database
|
|
//check for user's image-links database. If it doesn't exist, create it.
|
|
file.setName( locateLocal( "appdata", "image_url.dat" ) ); //determine filename in local user KDE directory tree.
|
|
|
|
if ( !file.open( IO_ReadWrite | IO_Append ) ) {
|
|
TQString message = i18n( "Custom image-links file could not be opened.\nLink cannot be recorded for future sessions." );
|
|
KMessageBox::sorry( 0, message, i18n( "Could Not Open File" ) );
|
|
return;
|
|
} else {
|
|
entry = clickedObject()->name() + ":" + adialog.desc() + ":" + adialog.url();
|
|
TQTextStream stream( &file );
|
|
stream << entry << endl;
|
|
file.close();
|
|
emit linkAdded();
|
|
}
|
|
} else {
|
|
clickedObject()->InfoList.append( adialog.url() );
|
|
clickedObject()->InfoTitle.append( adialog.desc() );
|
|
|
|
//check for user's image-links database. If it doesn't exist, create it.
|
|
file.setName( locateLocal( "appdata", "info_url.dat" ) ); //determine filename in local user KDE directory tree.
|
|
|
|
if ( !file.open( IO_ReadWrite | IO_Append ) ) {
|
|
TQString message = i18n( "Custom information-links file could not be opened.\nLink cannot be recorded for future sessions." ); KMessageBox::sorry( 0, message, i18n( "Could not Open File" ) );
|
|
return;
|
|
} else {
|
|
entry = clickedObject()->name() + ":" + adialog.desc() + ":" + adialog.url();
|
|
TQTextStream stream( &file );
|
|
stream << entry << endl;
|
|
file.close();
|
|
emit linkAdded();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkyMap::updateAngleRuler() {
|
|
if ( Options::useAltAz() ) PreviousClickedPoint.EquatorialToHorizontal( data->LST, data->geo()->lat() );
|
|
beginRulerPoint = getXY( previousClickedPoint(), Options::useAltAz(), Options::useRefraction() );
|
|
|
|
// endRulerPoint = TQPoint(e->x(), e->y());
|
|
endRulerPoint = mapFromGlobal( TQCursor::pos() );
|
|
}
|
|
|
|
#include "skymap.moc"
|