File indexing completed on 2022-12-06 18:58:54

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.fillGround    = Options::showGround();
0746 
0747     //Check if we need a new projector
0748     if (m_proj && Options::projection() == m_proj->type())
0749         m_proj->setViewParams(p);
0750     else
0751     {
0752         delete m_proj;
0753         switch (Options::projection())
0754         {
0755             case Projector::Gnomonic:
0756                 m_proj = new GnomonicProjector(p);
0757                 break;
0758             case Projector::Stereographic:
0759                 m_proj = new StereographicProjector(p);
0760                 break;
0761             case Projector::Orthographic:
0762                 m_proj = new OrthographicProjector(p);
0763                 break;
0764             case Projector::AzimuthalEquidistant:
0765                 m_proj = new AzimuthalEquidistantProjector(p);
0766                 break;
0767             case Projector::Equirectangular:
0768                 m_proj = new EquirectangularProjector(p);
0769                 break;
0770             case Projector::Lambert:
0771             default:
0772                 //TODO: implement other projection classes
0773                 m_proj = new LambertProjector(p);
0774                 break;
0775         }
0776     }
0777 }
0778 
0779 void SkyMapLite::setZoomMouseCursor()
0780 {
0781     mouseMoveCursor = false; // no mousemove cursor
0782     QBitmap cursor  = zoomCursorBitmap(2);
0783     QBitmap mask    = zoomCursorBitmap(4);
0784     setCursor(QCursor(cursor, mask));
0785 }
0786 
0787 void SkyMapLite::setDefaultMouseCursor()
0788 {
0789     mouseMoveCursor = false; // no mousemove cursor
0790     QBitmap cursor  = defaultCursorBitmap(2);
0791     QBitmap mask    = defaultCursorBitmap(3);
0792     setCursor(QCursor(cursor, mask));
0793 }
0794 
0795 void SkyMapLite::setMouseMoveCursor()
0796 {
0797     if (mouseButtonDown)
0798     {
0799         setCursor(Qt::SizeAllCursor); // cursor shape defined in qt
0800         mouseMoveCursor = true;
0801     }
0802 }
0803 
0804 bool SkyMapLite::isSlewing() const
0805 {
0806     return (getSlewing() || (clockSlewing && data->clock()->isActive()));
0807 }
0808 
0809 int SkyMapLite::harvardToIndex(char c)
0810 {
0811     // Convert spectral class to numerical index.
0812     // If spectral class is invalid return index for white star (A class)
0813 
0814     switch (c)
0815     {
0816         case 'o':
0817         case 'O':
0818             return 0;
0819         case 'b':
0820         case 'B':
0821             return 1;
0822         case 'a':
0823         case 'A':
0824             return 2;
0825         case 'f':
0826         case 'F':
0827             return 3;
0828         case 'g':
0829         case 'G':
0830             return 4;
0831         case 'k':
0832         case 'K':
0833             return 5;
0834         case 'm':
0835         case 'M':
0836             return 6;
0837         // For unknown spectral class assume A class (white star)
0838         default:
0839             return 2;
0840     }
0841 }
0842 
0843 QVector<QVector<QPixmap *>> SkyMapLite::getImageCache()
0844 {
0845     return imageCache;
0846 }
0847 
0848 QSGTexture *SkyMapLite::textToTexture(QString text, QColor color, bool zoomFont)
0849 {
0850     if (isInitialized)
0851     {
0852         QFont f;
0853 
0854         if (zoomFont)
0855         {
0856             f = SkyLabeler::Instance()->drawFont();
0857         }
0858         else
0859         {
0860             f = SkyLabeler::Instance()->stdFont();
0861         }
0862 
0863         qreal ratio = window()->effectiveDevicePixelRatio();
0864 
0865         QFontMetrics fm(f);
0866 
0867         int width  = fm.width(text);
0868         int height = fm.height();
0869         //        double rotation = 0;
0870 
0871         ////        switch(m_skyMapOrientation) {
0872         ////        case(SkyMapOrientation::Top0):
0873         ////            width = fm.width(text);
0874         ////            height = fm.height();
0875         ////            rotation = 0;
0876         ////            break;
0877         ////        case(SkyMapOrientation::Right90):
0878         ////            width = fm.height();
0879         ////            height = fm.width(text);
0880         ////            rotation = 90;
0881         ////        case(SkyMapOrientation::Bottom180):
0882         //////            width = ;
0883         //////            height = ;
0884         ////            rotation = 180;
0885         ////        case(SkyMapOrientation::Left270):
0886         //////            width = ;
0887         //////            height;
0888         ////            rotation = 270;
0889         ////        }
0890 
0891         f.setPointSizeF(f.pointSizeF() * ratio);
0892 
0893         QImage label((width)*ratio, (height)*ratio, QImage::Format_ARGB32_Premultiplied);
0894 
0895         label.fill(Qt::transparent);
0896 
0897         m_painter.begin(&label);
0898 
0899         m_painter.setFont(f);
0900 
0901         m_painter.setPen(color);
0902 
0903         //        m_painter.drawRect(0,0, label.width(), label.height());
0904         //        m_painter.rotate(getSkyRotation());
0905         m_painter.drawText(0, (height - fm.descent()) * ratio, text);
0906 
0907         m_painter.end();
0908 
0909         QSGTexture *texture = window()->createTextureFromImage(label, QQuickWindow::TextureCanUseAtlas);
0910         return texture;
0911     }
0912     else
0913     {
0914         return nullptr;
0915     }
0916 }
0917 
0918 void SkyMapLite::addFOVSymbol(const QString &FOVName, bool initialState)
0919 {
0920     m_FOVSymbols.append(FOVName);
0921     //Emit signal whenever new value was added
0922     emit symbolsFOVChanged(m_FOVSymbols);
0923 
0924     m_FOVSymVisible.append(initialState);
0925 }
0926 
0927 bool SkyMapLite::isFOVVisible(int index)
0928 {
0929     return m_FOVSymVisible.value(index);
0930 }
0931 
0932 void SkyMapLite::setFOVVisible(int index, bool visible)
0933 {
0934     if (index >= 0 && index < m_FOVSymVisible.size())
0935     {
0936         m_FOVSymVisible[index] = visible;
0937         forceUpdate();
0938     }
0939 }
0940 
0941 void SkyMapLite::setSlewing(bool newSlewing)
0942 {
0943     if (m_slewing != newSlewing)
0944     {
0945         m_slewing = newSlewing;
0946         emit slewingChanged(newSlewing);
0947     }
0948 }
0949 
0950 void SkyMapLite::setCenterLocked(bool centerLocked)
0951 {
0952     m_centerLocked = centerLocked;
0953     emit centerLockedChanged(centerLocked);
0954 }
0955 
0956 void SkyMapLite::setAutomaticMode(bool automaticMode)
0957 {
0958 #if defined(Q_OS_ANDROID)
0959     if (m_automaticMode != automaticMode)
0960     {
0961         m_automaticMode = automaticMode;
0962         if (automaticMode)
0963         {
0964             m_deviceOrientation->startSensors();
0965             automaticModeTimer.start();
0966         }
0967         else
0968         {
0969             automaticModeTimer.stop();
0970             m_deviceOrientation->stopSensors();
0971         }
0972     }
0973 #else
0974     Q_UNUSED(automaticMode);
0975 #endif
0976 }
0977 
0978 void SkyMapLite::updateAutomaticMode()
0979 {
0980 #if defined(Q_OS_ANDROID)
0981     m_deviceOrientation->getOrientation();
0982     if (Options::useRefraction() && Options::useAltAz())
0983     {
0984         setFocusAltAz(SkyPoint::unrefract(dms(m_deviceOrientation->getAltitude())), dms(m_deviceOrientation->getAzimuth()));
0985     }
0986     else
0987     {
0988         setFocusAltAz(dms(m_deviceOrientation->getAltitude()), dms(m_deviceOrientation->getAzimuth()));
0989     }
0990 
0991     setSkyRotation(-1 * m_deviceOrientation->getRoll());
0992 #endif
0993 }
0994 
0995 void SkyMapLite::initStarImages()
0996 {
0997     if (isInitialized)
0998     {
0999         //Delete all existing pixmaps
1000         if (imageCache.length() != 0)
1001         {
1002             foreach (QVector<QPixmap *> vec, imageCache)
1003             {
1004                 qDeleteAll(vec.begin(), vec.end());
1005             }
1006             clearTextures = true;
1007         }
1008 
1009         imageCache = QVector<QVector<QPixmap *>>(nSPclasses);
1010 
1011         QMap<char, QColor> ColorMap;
1012         const int starColorIntensity = Options::starColorIntensity();
1013 
1014         //On high-dpi screens star will look pixelized if don't multiply scaling factor by this ratio
1015         //Check PointNode::setNode() to see how it works
1016         qreal ratio = window()->effectiveDevicePixelRatio();
1017 
1018         switch (Options::starColorMode())
1019         {
1020             case 1: // Red stars.
1021                 ColorMap.insert('O', QColor::fromRgb(255, 0, 0));
1022                 ColorMap.insert('B', QColor::fromRgb(255, 0, 0));
1023                 ColorMap.insert('A', QColor::fromRgb(255, 0, 0));
1024                 ColorMap.insert('F', QColor::fromRgb(255, 0, 0));
1025                 ColorMap.insert('G', QColor::fromRgb(255, 0, 0));
1026                 ColorMap.insert('K', QColor::fromRgb(255, 0, 0));
1027                 ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
1028                 break;
1029             case 2: // Black stars.
1030                 ColorMap.insert('O', QColor::fromRgb(0, 0, 0));
1031                 ColorMap.insert('B', QColor::fromRgb(0, 0, 0));
1032                 ColorMap.insert('A', QColor::fromRgb(0, 0, 0));
1033                 ColorMap.insert('F', QColor::fromRgb(0, 0, 0));
1034                 ColorMap.insert('G', QColor::fromRgb(0, 0, 0));
1035                 ColorMap.insert('K', QColor::fromRgb(0, 0, 0));
1036                 ColorMap.insert('M', QColor::fromRgb(0, 0, 0));
1037                 break;
1038             case 3: // White stars
1039                 ColorMap.insert('O', QColor::fromRgb(255, 255, 255));
1040                 ColorMap.insert('B', QColor::fromRgb(255, 255, 255));
1041                 ColorMap.insert('A', QColor::fromRgb(255, 255, 255));
1042                 ColorMap.insert('F', QColor::fromRgb(255, 255, 255));
1043                 ColorMap.insert('G', QColor::fromRgb(255, 255, 255));
1044                 ColorMap.insert('K', QColor::fromRgb(255, 255, 255));
1045                 ColorMap.insert('M', QColor::fromRgb(255, 255, 255));
1046             case 0:  // Real color
1047             default: // And use real color for everything else
1048                 ColorMap.insert('O', QColor::fromRgb(0, 0, 255));
1049                 ColorMap.insert('B', QColor::fromRgb(0, 200, 255));
1050                 ColorMap.insert('A', QColor::fromRgb(0, 255, 255));
1051                 ColorMap.insert('F', QColor::fromRgb(200, 255, 100));
1052                 ColorMap.insert('G', QColor::fromRgb(255, 255, 0));
1053                 ColorMap.insert('K', QColor::fromRgb(255, 100, 0));
1054                 ColorMap.insert('M', QColor::fromRgb(255, 0, 0));
1055         }
1056 
1057         for (char color : ColorMap.keys())
1058         {
1059             //Add new spectral class
1060 
1061             QPixmap BigImage(15, 15);
1062             BigImage.fill(Qt::transparent);
1063 
1064             QPainter p;
1065             p.begin(&BigImage);
1066 
1067             if (Options::starColorMode() == 0)
1068             {
1069                 qreal h, s, v, a;
1070                 p.setRenderHint(QPainter::Antialiasing, false);
1071                 QColor starColor = ColorMap[color];
1072                 starColor.getHsvF(&h, &s, &v, &a);
1073                 for (int i = 0; i < 8; i++)
1074                 {
1075                     for (int j = 0; j < 8; j++)
1076                     {
1077                         qreal x    = i - 7;
1078                         qreal y    = j - 7;
1079                         qreal dist = sqrt(x * x + y * y) / 7.0;
1080                         starColor.setHsvF(h, qMin(qreal(1), dist < (10 - starColorIntensity) / 10.0 ? 0 : dist), v,
1081                                           qMax(qreal(0), dist < (10 - starColorIntensity) / 20.0 ? 1 : 1 - dist));
1082                         p.setPen(starColor);
1083                         p.drawPoint(i, j);
1084                         p.drawPoint((14 - i), j);
1085                         p.drawPoint(i, (14 - j));
1086                         p.drawPoint((14 - i), (14 - j));
1087                     }
1088                 }
1089             }
1090             else
1091             {
1092                 p.setRenderHint(QPainter::Antialiasing, true);
1093                 p.setPen(QPen(ColorMap[color], 2.0));
1094                 p.setBrush(p.pen().color());
1095                 p.drawEllipse(QRectF(2, 2, 10, 10));
1096             }
1097             p.end();
1098             //[nSPclasses][nStarSizes];
1099             // Cache array slice
1100 
1101             QVector<QPixmap *> *pmap = &imageCache[harvardToIndex(color)];
1102             pmap->append(new QPixmap(BigImage));
1103             for (int size = 1; size < nStarSizes; size++)
1104             {
1105                 pmap->append(new QPixmap(
1106                                  BigImage.scaled(size * ratio, size * ratio, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
1107             }
1108         }
1109         //}
1110         starColorMode = Options::starColorMode();
1111     }
1112 }