File indexing completed on 2024-12-01 09:41:29

0001 /*
0002     SPDX-FileCopyrightText: 2016 Artem Fedoskin <afedoskin3@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "skymaplite.h"
0007 #include "kstarsdata.h"
0008 #include "kstarslite.h"
0009 
0010 #include "indi/inditelescopelite.h"
0011 #include "indi/clientmanagerlite.h"
0012 #include "kstarslite/skyitems/telescopesymbolsitem.h"
0013 
0014 #include "projections/projector.h"
0015 #include "projections/lambertprojector.h"
0016 #include "projections/gnomonicprojector.h"
0017 #include "projections/stereographicprojector.h"
0018 #include "projections/orthographicprojector.h"
0019 #include "projections/azimuthalequidistantprojector.h"
0020 #include "projections/equirectangularprojector.h"
0021 
0022 #include "kstarslite/skypointlite.h"
0023 #include "kstarslite/skyobjectlite.h"
0024 
0025 #include "skylabeler.h"
0026 #include "Options.h"
0027 #include "skymesh.h"
0028 
0029 #include "kstarslite/skyitems/rootnode.h"
0030 #include "kstarslite/skyitems/skynodes/skynode.h"
0031 
0032 #include "ksplanetbase.h"
0033 #include "ksutils.h"
0034 
0035 #include <QSGSimpleRectNode>
0036 //#include <QSGNode>
0037 #include <QBitmap>
0038 #include <QSGTexture>
0039 #include <QQuickWindow>
0040 #include <QLinkedList>
0041 #include <QQmlContext>
0042 #include <QScreen>
0043 
0044 #include "kstarslite/deviceorientation.h"
0045 
0046 namespace
0047 {
0048 // Draw bitmap for zoom cursor. Width is size of pen to draw with.
0049 QBitmap zoomCursorBitmap(int width)
0050 {
0051     QBitmap b(32, 32);
0052     b.fill(Qt::color0);
0053     int mx = 16, my = 16;
0054     // Begin drawing
0055     QPainter p;
0056     p.begin(&b);
0057     p.setPen(QPen(Qt::color1, width));
0058     p.drawEllipse(mx - 7, my - 7, 14, 14);
0059     p.drawLine(mx + 5, my + 5, mx + 11, my + 11);
0060     p.end();
0061     return b;
0062 }
0063 
0064 // Draw bitmap for default cursor. Width is size of pen to draw with.
0065 QBitmap defaultCursorBitmap(int width)
0066 {
0067     QBitmap b(32, 32);
0068     b.fill(Qt::color0);
0069     int mx = 16, my = 16;
0070     // Begin drawing
0071     QPainter p;
0072     p.begin(&b);
0073     p.setPen(QPen(Qt::color1, width));
0074     // 1. diagonal
0075     p.drawLine(mx - 2, my - 2, mx - 8, mx - 8);
0076     p.drawLine(mx + 2, my + 2, mx + 8, mx + 8);
0077     // 2. diagonal
0078     p.drawLine(mx - 2, my + 2, mx - 8, mx + 8);
0079     p.drawLine(mx + 2, my - 2, mx + 8, mx - 8);
0080     p.end();
0081     return b;
0082 }
0083 }
0084 
0085 SkyMapLite *SkyMapLite::pinstance = nullptr;
0086 
0087 RootNode *SkyMapLite::m_rootNode = nullptr;
0088 
0089 int SkyMapLite::starColorMode = 0;
0090 
0091 SkyMapLite::SkyMapLite()
0092     : data(KStarsData::Instance())
0093 #if defined(Q_OS_ANDROID)
0094     ,
0095       m_deviceOrientation(new DeviceOrientation(this))
0096 #endif
0097 {
0098     setAcceptHoverEvents(true);
0099     setAcceptedMouseButtons(Qt::AllButtons);
0100     setFlag(ItemHasContents, true);
0101 
0102     m_rootNode = nullptr;
0103     m_magLim   = 2.222 * log10(static_cast<double>(Options::starDensity())) + 0.35;
0104 
0105     setSlewing(false);
0106 
0107     m_ClickedObjectLite = new SkyObjectLite;
0108     m_ClickedPointLite  = new SkyPointLite;
0109 
0110     qmlRegisterType<SkyObjectLite>("KStarsLite", 1, 0, "SkyObjectLite");
0111     qmlRegisterType<SkyPointLite>("KStarsLite", 1, 0, "SkyPointLite");
0112 
0113     m_tapBeganTimer.setSingleShot(true);
0114 
0115     setupProjector();
0116 
0117     // Set pinstance to yourself
0118     pinstance = this;
0119 
0120     connect(this, SIGNAL(destinationChanged()), this, SLOT(slewFocus()));
0121     connect(KStarsData::Instance(), SIGNAL(skyUpdate(bool)), this, SLOT(slotUpdateSky(bool)));
0122 
0123     ClientManagerLite *clientMng = KStarsLite::Instance()->clientManagerLite();
0124 
0125     connect(clientMng, &ClientManagerLite::telescopeAdded,
0126             [this](TelescopeLite * newTelescope)
0127     {
0128         this->m_newTelescopes.append(newTelescope->getDevice());
0129     });
0130     connect(clientMng, &ClientManagerLite::telescopeRemoved,
0131             [this](TelescopeLite * newTelescope)
0132     {
0133         this->m_delTelescopes.append(newTelescope->getDevice());
0134     });
0135 #if defined(Q_OS_ANDROID)
0136     //Automatic mode
0137     automaticModeTimer.setInterval(5);
0138     connect(&automaticModeTimer, SIGNAL(timeout()), this, SLOT(updateAutomaticMode()));
0139     setAutomaticMode(false);
0140 #endif
0141 }
0142 
0143 QSGNode *SkyMapLite::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
0144 {
0145     Q_UNUSED(updatePaintNodeData);
0146     RootNode *n = static_cast<RootNode *>(oldNode);
0147 
0148     /* This code deletes all nodes that are representing dynamic stars and not needed anymore (under construction) */
0149     //qDeleteAll(m_deleteNodes);
0150     //m_deleteNodes.clear();
0151 
0152     if (m_loadingFinished && isInitialized)
0153     {
0154         if (!n)
0155         {
0156             n          = new RootNode();
0157             m_rootNode = n;
0158         }
0159         /** Add or delete telescope crosshairs **/
0160         if (m_newTelescopes.count() > 0)
0161         {
0162             foreach (INDI::BaseDevice *telescope, m_newTelescopes)
0163             {
0164                 n->telescopeSymbolsItem()->addTelescope(telescope);
0165             }
0166             m_newTelescopes.clear();
0167         }
0168 
0169         if (m_delTelescopes.count() > 0)
0170         {
0171             foreach (INDI::BaseDevice *telescope, m_delTelescopes)
0172             {
0173                 n->telescopeSymbolsItem()->removeTelescope(telescope);
0174             }
0175             m_delTelescopes.clear();
0176         }
0177         //Notify RootNode that textures for point node should be recreated
0178         n->update(clearTextures);
0179         clearTextures = false;
0180     }
0181 
0182     //Memory Leaks test
0183     /*if(m_loadingFinished) {
0184         if(!n) {
0185             n = new RootNode();
0186         }
0187         n->testLeakAdd();
0188         n->update();
0189         m_loadingFinished = false;
0190     } else {
0191         if (n) {
0192             n->testLeakDelete();
0193         }
0194         m_loadingFinished = true;
0195     }*/
0196     return n;
0197 }
0198 
0199 double SkyMapLite::deleteLimit()
0200 {
0201     double lim = (MAXZOOM / MINZOOM) / sqrt(Options::zoomFactor()) / 3; //(MAXZOOM/MINZOOM - Options::zoomFactor())/130;
0202     return lim;
0203 }
0204 
0205 void SkyMapLite::deleteSkyNode(SkyNode *skyNode)
0206 {
0207     m_deleteNodes.append(skyNode);
0208 }
0209 
0210 QSGTexture *SkyMapLite::getCachedTexture(int size, char spType)
0211 {
0212     return textureCache[harvardToIndex(spType)][size];
0213 }
0214 
0215 SkyMapLite *SkyMapLite::createInstance()
0216 {
0217     delete pinstance;
0218     pinstance = new SkyMapLite();
0219     return pinstance;
0220 }
0221 
0222 void SkyMapLite::initialize(QQuickItem *parent)
0223 {
0224     if (parent)
0225     {
0226         setParentItem(parent);
0227         // Whenever the wrapper's(parent) dimensions changed, change SkyMapLite too
0228         connect(parent, &QQuickItem::widthChanged, this, &SkyMapLite::resizeItem);
0229         connect(parent, &QQuickItem::heightChanged, this, &SkyMapLite::resizeItem);
0230 
0231         isInitialized = true;
0232     }
0233 
0234     resizeItem(); /* Set initial size pf SkyMapLite. Without it on Android SkyMapLite is
0235     not displayed until screen orientation is not changed*/
0236 
0237     //Initialize images for stars
0238     initStarImages();
0239 }
0240 
0241 SkyMapLite::~SkyMapLite()
0242 {
0243     // Delete image cache
0244     for (auto &imgCache : imageCache)
0245         qDeleteAll(imgCache);
0246 
0247     // Delete textures generated from image cache
0248     for (auto &tCache : textureCache)
0249         qDeleteAll(tCache);
0250 }
0251 
0252 void SkyMapLite::setFocus(SkyPoint *p)
0253 {
0254     setFocus(p->ra(), p->dec());
0255 }
0256 
0257 void SkyMapLite::setFocus(const dms &ra, const dms &dec)
0258 {
0259     Options::setFocusRA(ra.Hours());
0260     Options::setFocusDec(dec.Degrees());
0261 
0262     focus()->set(ra, dec);
0263     focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0264 }
0265 
0266 void SkyMapLite::setFocusAltAz(const dms &alt, const dms &az)
0267 {
0268     Options::setFocusRA(focus()->ra().Hours());
0269     Options::setFocusDec(focus()->dec().Degrees());
0270     focus()->setAlt(alt);
0271     focus()->setAz(az);
0272     focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0273 
0274     setSlewing(false);
0275     forceUpdate(); //need a total update, or slewing with the arrow keys doesn't work.
0276 }
0277 
0278 void SkyMapLite::setDestination(const SkyPoint &p)
0279 {
0280     setDestination(p.ra(), p.dec());
0281 }
0282 
0283 void SkyMapLite::setDestination(const dms &ra, const dms &dec)
0284 {
0285     destination()->set(ra, dec);
0286     destination()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0287     emit destinationChanged();
0288 }
0289 
0290 void SkyMapLite::setDestinationAltAz(const dms &alt, const dms &az, bool altIsRefracted)
0291 {
0292     if (altIsRefracted)
0293     {
0294         // The alt in the SkyPoint is always actual, not apparent
0295         destination()->setAlt(SkyPoint::unrefract(alt));
0296     }
0297     else
0298     {
0299         destination()->setAlt(alt);
0300     }
0301     destination()->setAz(az);
0302     destination()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0303     emit destinationChanged();
0304 }
0305 
0306 void SkyMapLite::setClickedPoint(SkyPoint *f)
0307 {
0308     ClickedPoint = *f;
0309     m_ClickedPointLite->setPoint(f);
0310 }
0311 
0312 void SkyMapLite::setClickedObject(SkyObject *o)
0313 {
0314     ClickedObject = o;
0315     m_ClickedObjectLite->setObject(o);
0316 }
0317 
0318 void SkyMapLite::setFocusObject(SkyObject *o)
0319 {
0320     FocusObject = o;
0321     if (FocusObject)
0322         Options::setFocusObject(FocusObject->name());
0323     else
0324         Options::setFocusObject(i18n("nothing"));
0325 }
0326 
0327 void SkyMapLite::slotCenter()
0328 {
0329     /*KStars* kstars = KStars::Instance();
0330     TrailObject* trailObj = dynamic_cast<TrailObject*>( focusObject() );*/
0331 
0332     setFocusPoint(clickedPoint());
0333     if (Options::useAltAz())
0334     {
0335         focusPoint()->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
0336         focusPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0337     }
0338     else
0339     {
0340         focusPoint()->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
0341     }
0342     qDebug() << "Centering on " << focusPoint()->ra().toHMSString() << " " << focusPoint()->dec().toDMSString();
0343 
0344     //clear the planet trail of old focusObject, if it was temporary
0345     /*if( trailObj && data->temporaryTrail ) {
0346         trailObj->clearTrail();
0347         data->temporaryTrail = false;
0348     }*/
0349 
0350     //If the requested object is below the opaque horizon, issue a warning message
0351     //(unless user is already pointed below the horizon)
0352     if (Options::useAltAz() && Options::showGround() && focus()->alt().Degrees() > SkyPoint::altCrit &&
0353             focusPoint()->alt().Degrees() <= SkyPoint::altCrit)
0354     {
0355         QString caption = i18n("Requested Position Below Horizon");
0356         QString message = i18n("The requested position is below the horizon.\nWould you like to go there anyway?");
0357         /*if ( KMessageBox::warningYesNo( this, message, caption,
0358                                         KGuiItem(i18n("Go Anyway")), KGuiItem(i18n("Keep Position")), "dag_focus_below_horiz" )==KMessageBox::No ) {
0359             setClickedObject( nullptr );
0360             setFocusObject( nullptr );
0361             Options::setIsTracking( false );
0362 
0363             return;
0364         }*/
0365     }
0366 
0367     //set FocusObject before slewing.  Otherwise, KStarsData::updateTime() can reset
0368     //destination to previous object...
0369     setFocusObject(ClickedObject);
0370     Options::setIsTracking(true);
0371     /*if ( kstars ) {
0372         kstars->actionCollection()->action("track_object")->setIcon( QIcon::fromTheme("document-encrypt") );
0373         kstars->actionCollection()->action("track_object")->setText( i18n( "Stop &Tracking" ) );
0374     }*/
0375 
0376     //If focusObject is a SS body and doesn't already have a trail, set the temporaryTrail
0377 
0378     /*if( Options::useAutoTrail() && trailObj && trailObj->hasTrail() ) {
0379         trailObj->addToTrail();
0380         data->temporaryTrail = true;
0381     }*/
0382 
0383     //update the destination to the selected coordinates
0384     if (Options::useAltAz())
0385     {
0386         setDestinationAltAz(focusPoint()->alt(), focusPoint()->az(), false);
0387     }
0388     else
0389     {
0390         setDestination(*focusPoint());
0391     }
0392 
0393     focusPoint()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0394 
0395     //display coordinates in statusBar
0396     emit mousePointChanged(focusPoint());
0397     //showFocusCoords(); //update FocusBox
0398     //Lock center so that user could only zoom on touch-enabled devices
0399 }
0400 
0401 void SkyMapLite::slewFocus()
0402 {
0403     //Don't slew if the mouse button is pressed
0404     //Also, no animated slews if the Manual Clock is active
0405     //08/2002: added possibility for one-time skipping of slew with snapNextFocus
0406     if (!mouseButtonDown)
0407     {
0408         bool goSlew = (Options::useAnimatedSlewing() && !data->snapNextFocus()) &&
0409                       !(data->clock()->isManualMode() && data->clock()->isActive());
0410         if (goSlew)
0411         {
0412             double dX, dY;
0413             double maxstep = 10.0;
0414             if (Options::useAltAz())
0415             {
0416                 dX = destination()->az().Degrees() - focus()->az().Degrees();
0417                 dY = destination()->alt().Degrees() - focus()->alt().Degrees();
0418             }
0419             else
0420             {
0421                 dX = destination()->ra().Degrees() - focus()->ra().Degrees();
0422                 dY = destination()->dec().Degrees() - focus()->dec().Degrees();
0423             }
0424 
0425             //switch directions to go the short way around the celestial sphere, if necessary.
0426             dX = KSUtils::reduceAngle(dX, -180.0, 180.0);
0427 
0428             double r0 = sqrt(dX * dX + dY * dY);
0429             if (r0 < 20.0) //smaller slews have smaller maxstep
0430             {
0431                 maxstep *= (10.0 + 0.5 * r0) / 20.0;
0432             }
0433             double step = 0.1;
0434             double r    = r0;
0435             while (r > step)
0436             {
0437                 //DEBUG
0438                 //qDebug() << step << ": " << r << ": " << r0 << endl;
0439                 double fX = dX / r;
0440                 double fY = dY / r;
0441 
0442                 if (Options::useAltAz())
0443                 {
0444                     focus()->setAlt(focus()->alt().Degrees() + fY * step);
0445                     focus()->setAz(dms(focus()->az().Degrees() + fX * step).reduce());
0446                     focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0447                 }
0448                 else
0449                 {
0450                     fX = fX / 15.; //convert RA degrees to hours
0451                     SkyPoint newFocus(focus()->ra().Hours() + fX * step, focus()->dec().Degrees() + fY * step);
0452                     setFocus(&newFocus);
0453                     focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0454                 }
0455 
0456                 setSlewing(true);
0457 
0458                 forceUpdate();
0459                 qApp->processEvents(); //keep up with other stuff
0460 
0461                 if (Options::useAltAz())
0462                 {
0463                     dX = destination()->az().Degrees() - focus()->az().Degrees();
0464                     dY = destination()->alt().Degrees() - focus()->alt().Degrees();
0465                 }
0466                 else
0467                 {
0468                     dX = destination()->ra().Degrees() - focus()->ra().Degrees();
0469                     dY = destination()->dec().Degrees() - focus()->dec().Degrees();
0470                 }
0471 
0472                 //switch directions to go the short way around the celestial sphere, if necessary.
0473                 dX = KSUtils::reduceAngle(dX, -180.0, 180.0);
0474                 r  = sqrt(dX * dX + dY * dY);
0475 
0476                 //Modify step according to a cosine-shaped profile
0477                 //centered on the midpoint of the slew
0478                 //NOTE: don't allow the full range from -PI/2 to PI/2
0479                 //because the slew will never reach the destination as
0480                 //the speed approaches zero at the end!
0481                 double t = dms::PI * (r - 0.5 * r0) / (1.05 * r0);
0482                 step     = cos(t) * maxstep;
0483             }
0484         }
0485 
0486         //Either useAnimatedSlewing==false, or we have slewed, and are within one step of destination
0487         //set focus=destination.
0488         if (Options::useAltAz())
0489         {
0490             setFocusAltAz(destination()->alt(), destination()->az());
0491             focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0492         }
0493         else
0494         {
0495             setFocus(destination());
0496             focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0497         }
0498 
0499         setSlewing(false);
0500 
0501         //Turn off snapNextFocus, we only want it to happen once
0502         if (data->snapNextFocus())
0503         {
0504             data->setSnapNextFocus(false);
0505         }
0506 
0507         //Start the HoverTimer. if the user leaves the mouse in place after a slew,
0508         //we want to attach a label to the nearest object.
0509         if (Options::useHoverLabel())
0510             m_HoverTimer.start(HOVER_INTERVAL);
0511 
0512         forceUpdate();
0513     }
0514 }
0515 
0516 void SkyMapLite::slotClockSlewing()
0517 {
0518     //If the current timescale exceeds slewTimeScale, set clockSlewing=true, and stop the clock.
0519     if ((fabs(data->clock()->scale()) > Options::slewTimeScale()) ^ clockSlewing)
0520     {
0521         data->clock()->setManualMode(!clockSlewing);
0522         clockSlewing = !clockSlewing;
0523         // don't change automatically the DST status
0524         KStarsLite *kstars = KStarsLite::Instance();
0525         if (kstars)
0526             kstars->updateTime(false);
0527     }
0528 }
0529 
0530 /*void SkyMapLite::updateFocus() {
0531     if( slewing )
0532         return;
0533 
0534     //Tracking on an object
0535     if ( Options::isTracking() && focusObject() != nullptr ) {
0536         if ( Options::useAltAz() ) {
0537             //Tracking any object in Alt/Az mode requires focus updates
0538             focusObject()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0539             setFocusAltAz( focusObject()->alt(), focusObject()->az() );
0540             focus()->HorizontalToEquatorial( data->lst(), data->geo()->lat() );
0541             setDestination( *focus() );
0542         } else {
0543             //Tracking in equatorial coords
0544             setFocus( focusObject() );
0545             focus()->EquatorialToHorizontal( data->lst(), data->geo()->lat() );
0546             setDestination( *focus() );
0547         }
0548 
0549     //Tracking on empty sky
0550     } else if ( Options::isTracking() && focusPoint() != nullptr ) {
0551         if ( Options::useAltAz() ) {
0552             //Tracking on empty sky in Alt/Az mode
0553             setFocus( focusPoint() );
0554             focus()->EquatorialToHorizontal( data->lst(), data->geo()->lat() );
0555             setDestination( *focus() );
0556         }
0557 
0558     // Not tracking and not slewing, let sky drift by
0559     // This means that horizontal coordinates are constant.
0560     } else {
0561         focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat() );
0562     }
0563 }*/
0564 
0565 void SkyMapLite::resizeItem()
0566 {
0567     if (parentItem())
0568     {
0569         setWidth(parentItem()->width());
0570         setHeight(parentItem()->height());
0571     }
0572     forceUpdate();
0573 }
0574 
0575 void SkyMapLite::slotZoomIn()
0576 {
0577     setZoomFactor(Options::zoomFactor() * DZOOM);
0578 }
0579 
0580 void SkyMapLite::slotZoomOut()
0581 {
0582     setZoomFactor(Options::zoomFactor() / DZOOM);
0583 }
0584 
0585 void SkyMapLite::slotZoomDefault()
0586 {
0587     setZoomFactor(DEFAULTZOOM);
0588 }
0589 
0590 void SkyMapLite::slotSelectObject(SkyObject *skyObj)
0591 {
0592     ClickedPoint  = *skyObj;
0593     ClickedObject = skyObj;
0594     /*if ( Options::useAltAz() ) {
0595         setDestinationAltAz( skyObj->altRefracted(), skyObj->az() );
0596     } else {
0597         setDestination( *skyObj );
0598     }*/
0599     //Update selected SkyObject (used in FindDialog, DetailDialog)
0600     m_ClickedObjectLite->setObject(skyObj);
0601     emit objectLiteChanged();
0602     slotCenter();
0603 }
0604 
0605 void SkyMapLite::setSkyRotation(double skyRotation)
0606 {
0607     if (m_skyRotation != skyRotation)
0608     {
0609         m_skyRotation = skyRotation;
0610         emit skyRotationChanged(skyRotation);
0611 
0612         if (skyRotation >= 0 && skyRotation < 90)
0613         {
0614             m_skyMapOrientation = SkyMapOrientation::Top0;
0615         }
0616         else if (skyRotation >= 90 && skyRotation < 180)
0617         {
0618             m_skyMapOrientation = SkyMapOrientation::Right90;
0619         }
0620         else if (skyRotation >= 180 && skyRotation < 270)
0621         {
0622             m_skyMapOrientation = SkyMapOrientation::Bottom180;
0623         }
0624         else if (skyRotation >= 270 && skyRotation < 360)
0625         {
0626             m_skyMapOrientation = SkyMapOrientation::Left270;
0627         }
0628     }
0629 }
0630 
0631 void SkyMapLite::setZoomFactor(double factor)
0632 {
0633     Options::setZoomFactor(KSUtils::clamp(factor, MINZOOM, MAXZOOM));
0634 
0635     forceUpdate();
0636     emit zoomChanged();
0637 }
0638 
0639 void SkyMapLite::forceUpdate()
0640 {
0641     setupProjector();
0642 
0643     // We delay one draw cycle before re-indexing
0644     // we MUST ensure CLines do not get re-indexed while we use DRAW_BUF
0645     // so we do it here.
0646     //m_CLines->reindex( &m_reindexNum );
0647     // This queues re-indexing for the next draw cycle
0648     //m_reindexNum = KSNumbers( data->updateNum()->julianDay() );
0649 
0650     // This ensures that the JIT updates are synchronized for the entire draw
0651     // cycle so the sky moves as a single sheet.  May not be needed.
0652     data->syncUpdateIDs();
0653 
0654     SkyMesh *m_skyMesh = SkyMesh::Instance(3);
0655     if (m_skyMesh)
0656     {
0657         // prepare the aperture
0658         // FIXME_FOV: We may want to rejigger this to allow
0659         // wide-angle views --hdevalence
0660         double radius = m_proj->fov();
0661         if (radius > 180.0)
0662             radius = 180.0;
0663 
0664         if (m_skyMesh->inDraw())
0665         {
0666             printf("Warning: aborting concurrent SkyMapComposite::draw()\n");
0667             return;
0668         }
0669 
0670         //m_skyMesh->inDraw( true );
0671         m_skyMesh->aperture(&Focus, radius + 1.0, DRAW_BUF); // divide by 2 for testing
0672 
0673         // create the no-precess aperture if needed
0674         if (Options::showEquatorialGrid() || Options::showHorizontalGrid() || Options::showCBounds() ||
0675                 Options::showEquator())
0676         {
0677             m_skyMesh->index(&Focus, radius + 1.0, NO_PRECESS_BUF);
0678         }
0679     }
0680     update();
0681 }
0682 
0683 void SkyMapLite::slotUpdateSky(bool now)
0684 {
0685     Q_UNUSED(now);
0686     updateFocus();
0687     forceUpdate();
0688 }
0689 
0690 void SkyMapLite::updateFocus()
0691 {
0692     if (getSlewing())
0693         return;
0694 
0695     //Tracking on an object
0696     if (Options::isTracking() && focusObject() != nullptr)
0697     {
0698         if (Options::useAltAz())
0699         {
0700             //Tracking any object in Alt/Az mode requires focus updates
0701             focusObject()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0702             setFocusAltAz(focusObject()->alt(), focusObject()->az());
0703             focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0704             setDestination(*focus());
0705         }
0706         else
0707         {
0708             //Tracking in equatorial coords
0709             setFocus(focusObject());
0710             focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0711             setDestination(*focus());
0712         }
0713 
0714         //Tracking on empty sky
0715     }
0716     else if (Options::isTracking() && focusPoint() != nullptr)
0717     {
0718         if (Options::useAltAz())
0719         {
0720             //Tracking on empty sky in Alt/Az mode
0721             setFocus(focusPoint());
0722             focus()->EquatorialToHorizontal(data->lst(), data->geo()->lat());
0723             setDestination(*focus());
0724         }
0725 
0726         // Not tracking and not slewing, let sky drift by
0727         // This means that horizontal coordinates are constant.
0728     }
0729     else
0730     {
0731         focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0732     }
0733 }
0734 
0735 void SkyMapLite::setupProjector()
0736 {
0737     //Update View Parameters for projection
0738     ViewParams p;
0739     p.focus         = focus();
0740     p.height        = height();
0741     p.width         = width();
0742     p.useAltAz      = Options::useAltAz();
0743     p.useRefraction = Options::useRefraction();
0744     p.zoomFactor    = Options::zoomFactor();
0745     p.rotationAngle = Options::skyRotation();
0746     p.fillGround    = Options::showGround();
0747 
0748     //Check if we need a new projector
0749     if (m_proj && Options::projection() == m_proj->type())
0750         m_proj->setViewParams(p);
0751     else
0752     {
0753         delete m_proj;
0754         switch (Options::projection())
0755         {
0756             case Projector::Gnomonic:
0757                 m_proj = new GnomonicProjector(p);
0758                 break;
0759             case Projector::Stereographic:
0760                 m_proj = new StereographicProjector(p);
0761                 break;
0762             case Projector::Orthographic:
0763                 m_proj = new OrthographicProjector(p);
0764                 break;
0765             case Projector::AzimuthalEquidistant:
0766                 m_proj = new AzimuthalEquidistantProjector(p);
0767                 break;
0768             case Projector::Equirectangular:
0769                 m_proj = new EquirectangularProjector(p);
0770                 break;
0771             case Projector::Lambert:
0772             default:
0773                 //TODO: implement other projection classes
0774                 m_proj = new LambertProjector(p);
0775                 break;
0776         }
0777     }
0778 }
0779 
0780 void SkyMapLite::setZoomMouseCursor()
0781 {
0782     mouseMoveCursor = false; // no mousemove cursor
0783     QBitmap cursor  = zoomCursorBitmap(2);
0784     QBitmap mask    = zoomCursorBitmap(4);
0785     setCursor(QCursor(cursor, mask));
0786 }
0787 
0788 void SkyMapLite::setDefaultMouseCursor()
0789 {
0790     mouseMoveCursor = false; // no mousemove cursor
0791     QBitmap cursor  = defaultCursorBitmap(2);
0792     QBitmap mask    = defaultCursorBitmap(3);
0793     setCursor(QCursor(cursor, mask));
0794 }
0795 
0796 void SkyMapLite::setMouseMoveCursor()
0797 {
0798     if (mouseButtonDown)
0799     {
0800         setCursor(Qt::SizeAllCursor); // cursor shape defined in qt
0801         mouseMoveCursor = true;
0802     }
0803 }
0804 
0805 bool SkyMapLite::isSlewing() const
0806 {
0807     return (getSlewing() || (clockSlewing && data->clock()->isActive()));
0808 }
0809 
0810 int SkyMapLite::harvardToIndex(char c)
0811 {
0812     // Convert spectral class to numerical index.
0813     // If spectral class is invalid return index for white star (A class)
0814 
0815     switch (c)
0816     {
0817         case 'o':
0818         case 'O':
0819             return 0;
0820         case 'b':
0821         case 'B':
0822             return 1;
0823         case 'a':
0824         case 'A':
0825             return 2;
0826         case 'f':
0827         case 'F':
0828             return 3;
0829         case 'g':
0830         case 'G':
0831             return 4;
0832         case 'k':
0833         case 'K':
0834             return 5;
0835         case 'm':
0836         case 'M':
0837             return 6;
0838         // For unknown spectral class assume A class (white star)
0839         default:
0840             return 2;
0841     }
0842 }
0843 
0844 QVector<QVector<QPixmap *>> SkyMapLite::getImageCache()
0845 {
0846     return imageCache;
0847 }
0848 
0849 QSGTexture *SkyMapLite::textToTexture(QString text, QColor color, bool zoomFont)
0850 {
0851     if (isInitialized)
0852     {
0853         QFont f;
0854 
0855         if (zoomFont)
0856         {
0857             f = SkyLabeler::Instance()->drawFont();
0858         }
0859         else
0860         {
0861             f = SkyLabeler::Instance()->stdFont();
0862         }
0863 
0864         qreal ratio = window()->effectiveDevicePixelRatio();
0865 
0866         QFontMetrics fm(f);
0867 
0868         int width  = fm.width(text);
0869         int height = fm.height();
0870         //        double rotation = 0;
0871 
0872         ////        switch(m_skyMapOrientation) {
0873         ////        case(SkyMapOrientation::Top0):
0874         ////            width = fm.width(text);
0875         ////            height = fm.height();
0876         ////            rotation = 0;
0877         ////            break;
0878         ////        case(SkyMapOrientation::Right90):
0879         ////            width = fm.height();
0880         ////            height = fm.width(text);
0881         ////            rotation = 90;
0882         ////        case(SkyMapOrientation::Bottom180):
0883         //////            width = ;
0884         //////            height = ;
0885         ////            rotation = 180;
0886         ////        case(SkyMapOrientation::Left270):
0887         //////            width = ;
0888         //////            height;
0889         ////            rotation = 270;
0890         ////        }
0891 
0892         f.setPointSizeF(f.pointSizeF() * ratio);
0893 
0894         QImage label((width)*ratio, (height)*ratio, QImage::Format_ARGB32_Premultiplied);
0895 
0896         label.fill(Qt::transparent);
0897 
0898         m_painter.begin(&label);
0899 
0900         m_painter.setFont(f);
0901 
0902         m_painter.setPen(color);
0903 
0904         //        m_painter.drawRect(0,0, label.width(), label.height());
0905         //        m_painter.rotate(getSkyRotation());
0906         m_painter.drawText(0, (height - fm.descent()) * ratio, text);
0907 
0908         m_painter.end();
0909 
0910         QSGTexture *texture = window()->createTextureFromImage(label, QQuickWindow::TextureCanUseAtlas);
0911         return texture;
0912     }
0913     else
0914     {
0915         return nullptr;
0916     }
0917 }
0918 
0919 void SkyMapLite::addFOVSymbol(const QString &FOVName, bool initialState)
0920 {
0921     m_FOVSymbols.append(FOVName);
0922     //Emit signal whenever new value was added
0923     emit symbolsFOVChanged(m_FOVSymbols);
0924 
0925     m_FOVSymVisible.append(initialState);
0926 }
0927 
0928 bool SkyMapLite::isFOVVisible(int index)
0929 {
0930     return m_FOVSymVisible.value(index);
0931 }
0932 
0933 void SkyMapLite::setFOVVisible(int index, bool visible)
0934 {
0935     if (index >= 0 && index < m_FOVSymVisible.size())
0936     {
0937         m_FOVSymVisible[index] = visible;
0938         forceUpdate();
0939     }
0940 }
0941 
0942 void SkyMapLite::setSlewing(bool newSlewing)
0943 {
0944     if (m_slewing != newSlewing)
0945     {
0946         m_slewing = newSlewing;
0947         emit slewingChanged(newSlewing);
0948     }
0949 }
0950 
0951 void SkyMapLite::setCenterLocked(bool centerLocked)
0952 {
0953     m_centerLocked = centerLocked;
0954     emit centerLockedChanged(centerLocked);
0955 }
0956 
0957 void SkyMapLite::setAutomaticMode(bool automaticMode)
0958 {
0959 #if defined(Q_OS_ANDROID)
0960     if (m_automaticMode != automaticMode)
0961     {
0962         m_automaticMode = automaticMode;
0963         if (automaticMode)
0964         {
0965             m_deviceOrientation->startSensors();
0966             automaticModeTimer.start();
0967         }
0968         else
0969         {
0970             automaticModeTimer.stop();
0971             m_deviceOrientation->stopSensors();
0972         }
0973     }
0974 #else
0975     Q_UNUSED(automaticMode);
0976 #endif
0977 }
0978 
0979 void SkyMapLite::updateAutomaticMode()
0980 {
0981 #if defined(Q_OS_ANDROID)
0982     m_deviceOrientation->getOrientation();
0983     if (Options::useRefraction() && Options::useAltAz())
0984     {
0985         setFocusAltAz(SkyPoint::unrefract(dms(m_deviceOrientation->getAltitude())), dms(m_deviceOrientation->getAzimuth()));
0986     }
0987     else
0988     {
0989         setFocusAltAz(dms(m_deviceOrientation->getAltitude()), dms(m_deviceOrientation->getAzimuth()));
0990     }
0991 
0992     setSkyRotation(-1 * m_deviceOrientation->getRoll());
0993 #endif
0994 }
0995 
0996 void SkyMapLite::initStarImages()
0997 {
0998     if (isInitialized)
0999     {
1000         //Delete all existing pixmaps
1001         if (imageCache.length() != 0)
1002         {
1003             foreach (QVector<QPixmap *> vec, imageCache)
1004             {
1005                 qDeleteAll(vec.begin(), vec.end());
1006             }
1007             clearTextures = true;
1008         }
1009 
1010         imageCache = QVector<QVector<QPixmap *>>(nSPclasses);
1011 
1012         QMap<char, QColor> ColorMap;
1013         const int starColorIntensity = Options::starColorIntensity();
1014 
1015         //On high-dpi screens star will look pixelized if don't multiply scaling factor by this ratio
1016         //Check PointNode::setNode() to see how it works
1017         qreal ratio = window()->effectiveDevicePixelRatio();
1018 
1019         switch (Options::starColorMode())
1020         {
1021             case 1: // Red stars.
1022                 ColorMap.insert('O', QColor::fromRgb(255, 0, 0));
1023                 ColorMap.insert('B', QColor::fromRgb(255, 0, 0));
1024                 ColorMap.insert('A', QColor::fromRgb(255, 0, 0));
1025                 ColorMap.insert('F', QColor::fromRgb(255, 0, 0));
1026                 ColorMap.insert('G', QColor::fromRgb(255, 0, 0));
1027                 ColorMap.insert('K', QColor::fromRgb(255, 0, 0));
1028                 ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
1029                 break;
1030             case 2: // Black stars.
1031                 ColorMap.insert('O', QColor::fromRgb(0, 0, 0));
1032                 ColorMap.insert('B', QColor::fromRgb(0, 0, 0));
1033                 ColorMap.insert('A', QColor::fromRgb(0, 0, 0));
1034                 ColorMap.insert('F', QColor::fromRgb(0, 0, 0));
1035                 ColorMap.insert('G', QColor::fromRgb(0, 0, 0));
1036                 ColorMap.insert('K', QColor::fromRgb(0, 0, 0));
1037                 ColorMap.insert('M', QColor::fromRgb(0, 0, 0));
1038                 break;
1039             case 3: // White stars
1040                 ColorMap.insert('O', QColor::fromRgb(255, 255, 255));
1041                 ColorMap.insert('B', QColor::fromRgb(255, 255, 255));
1042                 ColorMap.insert('A', QColor::fromRgb(255, 255, 255));
1043                 ColorMap.insert('F', QColor::fromRgb(255, 255, 255));
1044                 ColorMap.insert('G', QColor::fromRgb(255, 255, 255));
1045                 ColorMap.insert('K', QColor::fromRgb(255, 255, 255));
1046                 ColorMap.insert('M', QColor::fromRgb(255, 255, 255));
1047             case 0:  // Real color
1048             default: // And use real color for everything else
1049                 ColorMap.insert('O', QColor::fromRgb(0, 0, 255));
1050                 ColorMap.insert('B', QColor::fromRgb(0, 200, 255));
1051                 ColorMap.insert('A', QColor::fromRgb(0, 255, 255));
1052                 ColorMap.insert('F', QColor::fromRgb(200, 255, 100));
1053                 ColorMap.insert('G', QColor::fromRgb(255, 255, 0));
1054                 ColorMap.insert('K', QColor::fromRgb(255, 100, 0));
1055                 ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
1056         }
1057 
1058         for (char color : ColorMap.keys())
1059         {
1060             //Add new spectral class
1061 
1062             QPixmap BigImage(15, 15);
1063             BigImage.fill(Qt::transparent);
1064 
1065             QPainter p;
1066             p.begin(&BigImage);
1067 
1068             if (Options::starColorMode() == 0)
1069             {
1070                 qreal h, s, v, a;
1071                 p.setRenderHint(QPainter::Antialiasing, false);
1072                 QColor starColor = ColorMap[color];
1073                 starColor.getHsvF(&h, &s, &v, &a);
1074                 for (int i = 0; i < 8; i++)
1075                 {
1076                     for (int j = 0; j < 8; j++)
1077                     {
1078                         qreal x    = i - 7;
1079                         qreal y    = j - 7;
1080                         qreal dist = sqrt(x * x + y * y) / 7.0;
1081                         starColor.setHsvF(h, qMin(qreal(1), dist < (10 - starColorIntensity) / 10.0 ? 0 : dist), v,
1082                                           qMax(qreal(0), dist < (10 - starColorIntensity) / 20.0 ? 1 : 1 - dist));
1083                         p.setPen(starColor);
1084                         p.drawPoint(i, j);
1085                         p.drawPoint((14 - i), j);
1086                         p.drawPoint(i, (14 - j));
1087                         p.drawPoint((14 - i), (14 - j));
1088                     }
1089                 }
1090             }
1091             else
1092             {
1093                 p.setRenderHint(QPainter::Antialiasing, true);
1094                 p.setPen(QPen(ColorMap[color], 2.0));
1095                 p.setBrush(p.pen().color());
1096                 p.drawEllipse(QRectF(2, 2, 10, 10));
1097             }
1098             p.end();
1099             //[nSPclasses][nStarSizes];
1100             // Cache array slice
1101 
1102             QVector<QPixmap *> *pmap = &imageCache[harvardToIndex(color)];
1103             pmap->append(new QPixmap(BigImage));
1104             for (int size = 1; size < nStarSizes; size++)
1105             {
1106                 pmap->append(new QPixmap(
1107                                  BigImage.scaled(size * ratio, size * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
1108             }
1109         }
1110         //}
1111         starColorMode = Options::starColorMode();
1112     }
1113 }