File indexing completed on 2025-02-16 06:41:14
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(¢erSkyPoint, 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 ¢erSkyPoint, centerScreenPoint.x(), centerScreenPoint.y()); 0126 double zenithRotation = m_SkyMap->m_proj->findZenithPA( 0127 ¢erSkyPoint, 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(¢erSkyPoint, 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 }