File indexing completed on 2024-06-16 06:48:05

0001 /*
0002     SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // This file implements the class SkyMapDrawAbstract, and is almost
0008 // identical to the older skymapdraw.cpp file, written by Jason
0009 // Harris. Essentially, skymapdraw.cpp was renamed and modified.
0010 // -- asimha (2011)
0011 
0012 #include <QPainter>
0013 #include <QPixmap>
0014 #include <QPainterPath>
0015 
0016 #include "skymapdrawabstract.h"
0017 #include "skymap.h"
0018 #include "Options.h"
0019 #include "fov.h"
0020 #include "kstars.h"
0021 #include "kstarsdata.h"
0022 #include "ksnumbers.h"
0023 #include "ksutils.h"
0024 #include "skyobjects/skyobject.h"
0025 #include "skyobjects/catalogobject.h"
0026 #include "catalogsdb.h"
0027 #include "skyobjects/starobject.h"
0028 #include "skyobjects/ksplanetbase.h"
0029 #include "simclock.h"
0030 #include "observinglist.h"
0031 #include "skycomponents/constellationboundarylines.h"
0032 #include "skycomponents/skylabeler.h"
0033 #include "skycomponents/skymapcomposite.h"
0034 #include "skyqpainter.h"
0035 #include "projections/projector.h"
0036 #include "projections/lambertprojector.h"
0037 
0038 #include <config-kstars.h>
0039 
0040 #ifdef HAVE_INDI
0041 #include <basedevice.h>
0042 #include "indi/indilistener.h"
0043 #include "indi/driverinfo.h"
0044 #include "indi/indistd.h"
0045 #include "indi/indimount.h"
0046 #endif
0047 
0048 bool SkyMapDrawAbstract::m_DrawLock = false;
0049 
0050 SkyMapDrawAbstract::SkyMapDrawAbstract(SkyMap *sm) : m_KStarsData(KStarsData::Instance()), m_SkyMap(sm)
0051 {
0052     //m_fpstime.start();
0053     //m_framecount = 0;
0054 }
0055 
0056 void SkyMapDrawAbstract::drawOverlays(QPainter &p, bool drawFov)
0057 {
0058     if (!KStars::Instance())
0059         return;
0060 
0061     //draw labels
0062     SkyLabeler::Instance()->draw(p);
0063 
0064     if (drawFov)
0065     {
0066         //draw FOV symbol
0067         foreach (FOV *fov, m_KStarsData->getVisibleFOVs())
0068         {
0069             if (fov->lockCelestialPole())
0070             {
0071                 SkyPoint centerSkyPoint = SkyMap::Instance()->projector()->fromScreen(p.viewport().center(), KStarsData::Instance()->lst(),
0072                                           KStarsData::Instance()->geo()->lat());
0073                 QPointF screenSkyPoint = p.viewport().center();
0074                 double northRotation = SkyMap::Instance()->projector()->findNorthPA(&centerSkyPoint, screenSkyPoint.x(),
0075                                        screenSkyPoint.y());
0076                 fov->setCenter(centerSkyPoint);
0077                 fov->setNorthPA(northRotation);
0078             }
0079             fov->draw(p, Options::zoomFactor());
0080         }
0081     }
0082 
0083     drawSolverFOV(p);
0084 
0085     drawTelescopeSymbols(p);
0086 
0087     drawZoomBox(p);
0088 
0089     drawOrientationArrows(p);
0090 
0091     // FIXME: Maybe we should take care of this differently. Maybe
0092     // drawOverlays should remain in SkyMap, since it just calls
0093     // certain drawing functions which are implemented in
0094     // SkyMapDrawAbstract. Really, it doesn't draw anything on its
0095     // own.
0096     if (m_SkyMap->rulerMode)
0097     {
0098         m_SkyMap->updateAngleRuler();
0099         drawAngleRuler(p);
0100     }
0101 }
0102 
0103 void SkyMapDrawAbstract::drawAngleRuler(QPainter &p)
0104 {
0105     //FIXME use sky painter.
0106     p.setPen(QPen(m_KStarsData->colorScheme()->colorNamed("AngularRuler"), 3.0, Qt::DotLine));
0107     p.drawLine(
0108         m_SkyMap->m_proj->toScreen(m_SkyMap->AngularRuler.point(
0109                                        0)), // FIXME: More ugliness. m_proj should probably be a single-instance class, or we should have our own instance etc.
0110         m_SkyMap->m_proj->toScreen(m_SkyMap->AngularRuler.point(
0111                                        1))); // FIXME: Again, AngularRuler should be something better -- maybe a class in itself. After all it's used for more than one thing after we integrate the StarHop feature.
0112 }
0113 
0114 void SkyMapDrawAbstract::drawOrientationArrows(QPainter &p)
0115 {
0116     if (m_SkyMap->rotationStart.x() > 0 && m_SkyMap->rotationStart.y() > 0)
0117     {
0118         auto* data = m_KStarsData;
0119         const SkyPoint centerSkyPoint = m_SkyMap->m_proj->fromScreen(
0120                                             p.viewport().center(),
0121                                             data->lst(), data->geo()->lat());
0122 
0123         QPointF centerScreenPoint = p.viewport().center();
0124         double northRotation = m_SkyMap->m_proj->findNorthPA(
0125                                    &centerSkyPoint, centerScreenPoint.x(), centerScreenPoint.y());
0126         double zenithRotation = m_SkyMap->m_proj->findZenithPA(
0127                                     &centerSkyPoint, centerScreenPoint.x(), centerScreenPoint.y());
0128 
0129         QColor overlayColor(data->colorScheme()->colorNamed("CompassColor"));
0130         p.setPen(Qt::NoPen);
0131         auto drawArrow = [&](double angle, const QString & marker, const float labelRadius, const bool primary)
0132         {
0133             constexpr float radius = 150.0f; // In pixels
0134             const auto fontMetrics = QFontMetricsF(QFont());
0135             QTransform transform;
0136             QColor color = overlayColor;
0137             color.setAlphaF(primary ? 1.0 : 0.75);
0138             QPen pen(color, 1.0, primary ? Qt::SolidLine : Qt::DotLine);
0139             QBrush brush(color);
0140 
0141             QPainterPath arrowstem;
0142             arrowstem.moveTo(0.f, 0.f);
0143             arrowstem.lineTo(0.f, -radius + radius / 7.5f);
0144             transform.reset();
0145             transform.translate(centerScreenPoint.x(), centerScreenPoint.y());
0146             transform.rotate(angle);
0147             arrowstem = transform.map(arrowstem);
0148             p.strokePath(arrowstem, pen);
0149 
0150             QPainterPath arrowhead;
0151             arrowhead.moveTo(0.f, 0.f);
0152             arrowhead.lineTo(-radius / 30.f, radius / 7.5f);
0153             arrowhead.lineTo(radius / 30.f, radius / 7.5f);
0154             arrowhead.lineTo(0.f, 0.f);
0155             arrowhead.addText(QPointF(-1.1 * fontMetrics.width(marker), radius / 7.5f + 1.2f * fontMetrics.ascent()),
0156                               QFont(), marker);
0157             transform.translate(0, -radius);
0158             arrowhead = transform.map(arrowhead);
0159             p.fillPath(arrowhead, brush);
0160 
0161             QRectF angleMarkerRect(centerScreenPoint.x() - labelRadius, centerScreenPoint.y() - labelRadius,
0162                                    2.f * labelRadius, 2.f * labelRadius);
0163             p.setPen(pen);
0164             if (abs(angle) < 0.01)
0165             {
0166                 angle = 0.;
0167             }
0168             double arcAngle = angle <= 0. ? -angle : 360. - angle;
0169             p.drawArc(angleMarkerRect, 90 * 16, int(arcAngle * 16.));
0170 
0171             QPainterPath angleLabel;
0172             QString angleLabelText = QString::number(int(round(arcAngle))) + "°";
0173             angleLabel.addText(QPointF(-fontMetrics.width(angleLabelText) / 2.f, 1.2f * fontMetrics.ascent()),
0174                                QFont(), angleLabelText);
0175             transform.reset();
0176             transform.translate(centerScreenPoint.x(), centerScreenPoint.y());
0177             transform.rotate(angle);
0178             transform.translate(0, -labelRadius);
0179             transform.rotate(90);
0180             angleLabel = transform.map(angleLabel);
0181             p.fillPath(angleLabel, brush);
0182 
0183         };
0184         drawArrow(northRotation, i18nc("North", "N"), 80.f, !Options::useAltAz());
0185         drawArrow(zenithRotation, i18nc("Zenith", "Z"), 40.f, Options::useAltAz());
0186     }
0187 }
0188 
0189 void SkyMapDrawAbstract::drawZoomBox(QPainter &p)
0190 {
0191     //draw the manual zoom-box, if it exists
0192     if (m_SkyMap->ZoomRect.isValid())
0193     {
0194         p.setPen(QPen(Qt::white, 1.0, Qt::DotLine));
0195         p.drawRect(m_SkyMap->ZoomRect.x(), m_SkyMap->ZoomRect.y(), m_SkyMap->ZoomRect.width(),
0196                    m_SkyMap->ZoomRect.height());
0197     }
0198 }
0199 
0200 void SkyMapDrawAbstract::drawObjectLabels(QList<SkyObject *> &labelObjects)
0201 {
0202     bool checkSlewing =
0203         (m_SkyMap->slewing || (m_SkyMap->clockSlewing && m_KStarsData->clock()->isActive())) && Options::hideOnSlew();
0204     if (checkSlewing && Options::hideLabels())
0205         return;
0206 
0207     SkyLabeler *skyLabeler = SkyLabeler::Instance();
0208     skyLabeler->resetFont(); // use the zoom dependent font
0209 
0210     skyLabeler->setPen(m_KStarsData->colorScheme()->colorNamed("UserLabelColor"));
0211 
0212     bool drawPlanets   = Options::showSolarSystem() && !(checkSlewing && Options::hidePlanets());
0213     bool drawComets    = drawPlanets && Options::showComets();
0214     bool drawAsteroids = drawPlanets && Options::showAsteroids();
0215     bool drawOther      = Options::showDeepSky() && Options::showOther() && !(checkSlewing && Options::hideOther());
0216     bool drawStars      = Options::showStars();
0217     bool hideFaintStars = checkSlewing && Options::hideStars();
0218 
0219     //Attach a label to the centered object
0220     if (m_SkyMap->focusObject() != nullptr && Options::useAutoLabel())
0221     {
0222         QPointF o =
0223             m_SkyMap->m_proj->toScreen(m_SkyMap->focusObject()); // FIXME: Same thing. m_proj should be accessible here.
0224         skyLabeler->drawNameLabel(m_SkyMap->focusObject(), o);
0225     }
0226 
0227     foreach (SkyObject *obj, labelObjects)
0228     {
0229         //Only draw an attached label if the object is being drawn to the map
0230         //reproducing logic from other draw funcs here...not an optimal solution
0231         if (obj->type() == SkyObject::STAR || obj->type() == SkyObject::CATALOG_STAR ||
0232                 obj->type() == SkyObject::MULT_STAR)
0233         {
0234             if (!drawStars)
0235                 continue;
0236             //            if ( obj->mag() > Options::magLimitDrawStar() ) continue;
0237             if (hideFaintStars && obj->mag() > Options::magLimitHideStar())
0238                 continue;
0239         }
0240         if (obj->type() == SkyObject::PLANET)
0241         {
0242             if (!drawPlanets)
0243                 continue;
0244             if (obj->name() == i18n("Sun") && !Options::showSun())
0245                 continue;
0246             if (obj->name() == i18n("Mercury") && !Options::showMercury())
0247                 continue;
0248             if (obj->name() == i18n("Venus") && !Options::showVenus())
0249                 continue;
0250             if (obj->name() == i18n("Moon") && !Options::showMoon())
0251                 continue;
0252             if (obj->name() == i18n("Mars") && !Options::showMars())
0253                 continue;
0254             if (obj->name() == i18n("Jupiter") && !Options::showJupiter())
0255                 continue;
0256             if (obj->name() == i18n("Saturn") && !Options::showSaturn())
0257                 continue;
0258             if (obj->name() == i18n("Uranus") && !Options::showUranus())
0259                 continue;
0260             if (obj->name() == i18n("Neptune") && !Options::showNeptune())
0261                 continue;
0262             //if ( obj->name() == i18n( "Pluto" ) && ! Options::showPluto() ) continue;
0263         }
0264         if ((obj->type() >= SkyObject::OPEN_CLUSTER && obj->type() <= SkyObject::GALAXY) ||
0265                 (obj->type() >= SkyObject::ASTERISM && obj->type() <= SkyObject::QUASAR) ||
0266                 (obj->type() == SkyObject::RADIO_SOURCE))
0267         {
0268             if (((CatalogObject *)obj)->getCatalog().id == -1 && !drawOther)
0269                 continue;
0270         }
0271         if (obj->type() == SkyObject::COMET && !drawComets)
0272             continue;
0273         if (obj->type() == SkyObject::ASTEROID && !drawAsteroids)
0274             continue;
0275 
0276         if (!m_SkyMap->m_proj->checkVisibility(obj))
0277             continue; // FIXME: m_proj should be a member of this class.
0278         QPointF o = m_SkyMap->m_proj->toScreen(obj);
0279         if (!m_SkyMap->m_proj->onScreen(o))
0280             continue;
0281 
0282         skyLabeler->drawNameLabel(obj, o);
0283     }
0284 
0285     skyLabeler->useStdFont(); // use the StdFont for the guides.
0286 }
0287 
0288 void SkyMapDrawAbstract::drawSolverFOV(QPainter &psky)
0289 {
0290     Q_UNUSED(psky)
0291 
0292 #ifdef HAVE_INDI
0293 
0294     for (auto oneFOV : KStarsData::Instance()->getTransientFOVs())
0295     {
0296         QVariant visible = oneFOV->property("visible");
0297         if (visible.isNull() || visible.toBool() == false)
0298             continue;
0299 
0300         if (oneFOV->objectName() == "sensor_fov")
0301         {
0302             oneFOV->setColor(KStars::Instance()->data()->colorScheme()->colorNamed("SensorFOVColor").name());
0303             SkyPoint centerSkyPoint = SkyMap::Instance()->projector()->fromScreen(psky.viewport().center(),
0304                                       KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
0305             QPointF screenSkyPoint = psky.viewport().center();
0306             double northRotation = SkyMap::Instance()->projector()->findNorthPA(&centerSkyPoint, screenSkyPoint.x(),
0307                                    screenSkyPoint.y());
0308             oneFOV->setCenter(centerSkyPoint);
0309             oneFOV->setNorthPA(northRotation);
0310             oneFOV->draw(psky, Options::zoomFactor());
0311         }
0312         else if (oneFOV->objectName() == "solver_fov")
0313         {
0314             bool isVisible = false;
0315             SkyPoint p = oneFOV->center();
0316             if (std::isnan(p.ra().Degrees()))
0317                 continue;
0318 
0319             p.EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat());
0320             QPointF point        = SkyMap::Instance()->projector()->toScreen(&p, true, &isVisible);
0321             double northRotation = SkyMap::Instance()->projector()->findNorthPA(&p, point.x(), point.y());
0322             oneFOV->setNorthPA(northRotation);
0323             oneFOV->draw(psky, Options::zoomFactor());
0324         }
0325     }
0326 #endif
0327 }
0328 
0329 void SkyMapDrawAbstract::drawTelescopeSymbols(QPainter &psky)
0330 {
0331     Q_UNUSED(psky)
0332 
0333 #ifdef HAVE_INDI
0334     if (!Options::showTargetCrosshair())
0335         return;
0336 
0337     if (INDIListener::Instance()->size() == 0)
0338         return;
0339     SkyPoint indi_sp;
0340 
0341     psky.setPen(QPen(QColor(m_KStarsData->colorScheme()->colorNamed("TargetColor"))));
0342     psky.setBrush(Qt::NoBrush);
0343     float pxperdegree = Options::zoomFactor() / 57.3;
0344 
0345     for (auto &oneDevice : INDIListener::devices())
0346     {
0347         if (!(oneDevice->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE) || oneDevice->isConnected() == false)
0348             continue;
0349 
0350         auto mount = oneDevice->getMount();
0351         if (!mount)
0352             continue;
0353 
0354         auto coordNP = mount->currentCoordinates();
0355 
0356         QPointF P = m_SkyMap->m_proj->toScreen(&coordNP);
0357         if (Options::useAntialias())
0358         {
0359             float s1 = 0.5 * pxperdegree;
0360             float s2 = pxperdegree;
0361             float s3 = 2.0 * pxperdegree;
0362 
0363             float x0 = P.x();
0364             float y0 = P.y();
0365             float x1 = x0 - 0.5 * s1;
0366             float y1 = y0 - 0.5 * s1;
0367             float x2 = x0 - 0.5 * s2;
0368             float y2 = y0 - 0.5 * s2;
0369             float x3 = x0 - 0.5 * s3;
0370             float y3 = y0 - 0.5 * s3;
0371 
0372             //Draw radial lines
0373             psky.drawLine(QPointF(x1, y0), QPointF(x3, y0));
0374             psky.drawLine(QPointF(x0 + s2, y0), QPointF(x0 + 0.5 * s1, y0));
0375             psky.drawLine(QPointF(x0, y1), QPointF(x0, y3));
0376             psky.drawLine(QPointF(x0, y0 + 0.5 * s1), QPointF(x0, y0 + s2));
0377             //Draw circles at 0.5 & 1 degrees
0378             psky.drawEllipse(QRectF(x1, y1, s1, s1));
0379             psky.drawEllipse(QRectF(x2, y2, s2, s2));
0380 
0381             psky.drawText(QPointF(x0 + s2 + 2., y0), mount->getDeviceName());
0382         }
0383         else
0384         {
0385             int s1 = int(0.5 * pxperdegree);
0386             int s2 = int(pxperdegree);
0387             int s3 = int(2.0 * pxperdegree);
0388 
0389             int x0 = int(P.x());
0390             int y0 = int(P.y());
0391             int x1 = x0 - s1 / 2;
0392             int y1 = y0 - s1 / 2;
0393             int x2 = x0 - s2 / 2;
0394             int y2 = y0 - s2 / 2;
0395             int x3 = x0 - s3 / 2;
0396             int y3 = y0 - s3 / 2;
0397 
0398             //Draw radial lines
0399             psky.drawLine(QPoint(x1, y0), QPoint(x3, y0));
0400             psky.drawLine(QPoint(x0 + s2, y0), QPoint(x0 + s1 / 2, y0));
0401             psky.drawLine(QPoint(x0, y1), QPoint(x0, y3));
0402             psky.drawLine(QPoint(x0, y0 + s1 / 2), QPoint(x0, y0 + s2));
0403             //Draw circles at 0.5 & 1 degrees
0404             psky.drawEllipse(QRect(x1, y1, s1, s1));
0405             psky.drawEllipse(QRect(x2, y2, s2, s2));
0406 
0407             psky.drawText(QPoint(x0 + s2 + 2, y0), mount->getDeviceName());
0408         }
0409     }
0410 #endif
0411 }
0412 
0413 void SkyMapDrawAbstract::exportSkyImage(QPaintDevice *pd, bool scale)
0414 {
0415     SkyQPainter p(m_SkyMap, pd);
0416     p.begin();
0417     p.setRenderHint(QPainter::SmoothPixmapTransform, true);
0418 
0419     exportSkyImage(&p, scale);
0420 
0421     p.end();
0422 }
0423 
0424 void SkyMapDrawAbstract::exportSkyImage(SkyQPainter *painter, bool scale)
0425 {
0426     bool vectorStarState;
0427     vectorStarState = painter->getVectorStars();
0428     painter->setVectorStars(
0429         true); // Since we are exporting an image, we may use vector stars without worrying about time
0430     painter->setRenderHint(QPainter::Antialiasing, Options::useAntialias());
0431 
0432     if (scale)
0433     {
0434         //scale sky image to fit paint device
0435         qDebug() << Q_FUNC_INFO << "Scaling true while exporting Sky Image";
0436         double xscale = double(painter->device()->width()) / double(m_SkyMap->width());
0437         double yscale = double(painter->device()->height()) / double(m_SkyMap->height());
0438         double scale  = qMin(xscale, yscale);
0439         qDebug() << Q_FUNC_INFO << "xscale: " << xscale << "yscale: " << yscale << "chosen scale: " << scale;
0440         painter->scale(scale, scale);
0441     }
0442 
0443     painter->drawSkyBackground();
0444     m_KStarsData->skyComposite()->draw(painter);
0445     drawOverlays(*painter);
0446     painter->setVectorStars(vectorStarState); // Restore the state of the painter
0447 }
0448 
0449 /* JM 2016-05-03: Not needed since we're not using OpenGL for now
0450  * void SkyMapDrawAbstract::calculateFPS()
0451 {
0452     if(m_framecount == 25) {
0453         //float sec = m_fpstime.elapsed()/1000.;
0454         // qDebug() << Q_FUNC_INFO << "FPS " << m_framecount/sec;
0455         m_framecount = 0;
0456         m_fpstime.restart();
0457     }
0458     ++m_framecount;
0459 }*/
0460 
0461 void SkyMapDrawAbstract::setDrawLock(bool state)
0462 {
0463     m_DrawLock = state;
0464 }