File indexing completed on 2024-05-05 15:55:34

0001 /*
0002     SPDX-FileCopyrightText: 2021 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <QPainter>
0008 
0009 #include "mosaictilesmanager.h"
0010 
0011 #include "kstars.h"
0012 #include "kstarsdata.h"
0013 #include "ekos_scheduler_debug.h"
0014 
0015 #include <QGraphicsSceneMouseEvent>
0016 
0017 namespace Ekos
0018 {
0019 MosaicTilesManager::MosaicTilesManager(QWidget *parent) : QObject(parent), QGraphicsItem()
0020 {
0021     brush.setStyle(Qt::NoBrush);
0022     QColor lightGray(200, 200, 200, 100);
0023     pen.setColor(lightGray);
0024     pen.setWidth(1);
0025 
0026     textBrush.setStyle(Qt::SolidPattern);
0027     textPen.setColor(Qt::red);
0028     textPen.setWidth(2);
0029 
0030     setFlags(QGraphicsItem::ItemIsMovable);
0031     setAcceptDrops(true);
0032 }
0033 
0034 MosaicTilesManager::~MosaicTilesManager()
0035 {
0036     qDeleteAll(tiles);
0037 }
0038 
0039 
0040 void MosaicTilesManager::setSkyCenter(SkyPoint center)
0041 {
0042     skyCenter = center;
0043 }
0044 
0045 void MosaicTilesManager::setPositionAngle(double positionAngle)
0046 {
0047     pa = std::fmod(positionAngle * -1 + 360.0, 360.0);
0048 
0049     // Rotate the whole mosaic around its local center
0050     setTransformOriginPoint(QPointF());
0051     setRotation(pa);
0052 }
0053 
0054 void MosaicTilesManager::setGridDimensions(int width, int height)
0055 {
0056     m_HorizontalTiles = width;
0057     m_VerticalTiles = height;
0058 }
0059 
0060 void MosaicTilesManager::setSingleTileFOV(double fov_x, double fov_y)
0061 {
0062     fovW = fov_x;
0063     fovH = fov_y;
0064 }
0065 
0066 void MosaicTilesManager::setMosaicFOV(double mfov_x, double mfov_y)
0067 {
0068     mfovW = mfov_x;
0069     mfovH = mfov_y;
0070 }
0071 
0072 void MosaicTilesManager::setOverlap(double value)
0073 {
0074     overlap = (value < 0) ? 0 : (1 < value) ? 1 : value;
0075 }
0076 
0077 
0078 QSizeF MosaicTilesManager::adjustCoordinate(QPointF tileCoord)
0079 {
0080     // Compute the declination of the tile row from the mosaic center
0081     double const dec = skyCenter.dec0().Degrees() + tileCoord.y() / m_PixelScale.height();
0082 
0083     // Adjust RA based on the shift in declination
0084     QSizeF const toSpherical(
0085         1 / (m_PixelScale.width() * cos(dec * dms::DegToRad)),
0086         1 / (m_PixelScale.height()));
0087 
0088     // Return the adjusted coordinates as a QSizeF in degrees
0089     return QSizeF(tileCoord.x() * toSpherical.width(), tileCoord.y() * toSpherical.height());
0090 }
0091 
0092 void MosaicTilesManager::updateTiles(QPointF skymapCenter, bool s_shaped)
0093 {
0094     prepareGeometryChange();
0095     skymapCenter = QPointF(0, 0);
0096 
0097     qDeleteAll(tiles);
0098     tiles.clear();
0099 
0100     // Sky map has objects moving from left to right, so configure the mosaic from right to left, column per column
0101 
0102     // Offset is our tile size with an overlap removed
0103     double const xOffset = fovW * (1 - overlap);
0104     double const yOffset = fovH * (1 - overlap);
0105 
0106     // We start at top right corner, (0,0) being the center of the tileset
0107     double initX = +(fovW + xOffset * (m_HorizontalTiles - 1)) / 2.0 - fovW;
0108     double initY = -(fovH + yOffset * (m_VerticalTiles - 1)) / 2.0;
0109 
0110     double x = initX, y = initY;
0111 
0112     qCDebug(KSTARS_EKOS_SCHEDULER) << "Mosaic Tile FovW" << fovW << "FovH" << fovH << "initX" << x << "initY" << y <<
0113                                    "Offset X " << xOffset << " Y " << yOffset << " rotation " << getPA() << " reverseOdd " << s_shaped;
0114 
0115     int index = 0;
0116     for (int col = 0; col < m_HorizontalTiles; col++)
0117     {
0118         y = (s_shaped && (col % 2)) ? (y - yOffset) : initY;
0119 
0120         for (int row = 0; row < m_VerticalTiles; row++)
0121         {
0122             OneTile *tile = new OneTile();
0123 
0124             if (!tile)
0125                 continue;
0126 
0127             tile->pos.setX(x);
0128             tile->pos.setY(y);
0129 
0130             tile->center.setX(tile->pos.x() + (fovW / 2.0));
0131             tile->center.setY(tile->pos.y() + (fovH / 2.0));
0132 
0133             // The location of the tile on the sky map refers to the center of the mosaic, and rotates with the mosaic itself
0134             QPointF tileSkyLocation = skymapCenter - rotatePoint(tile->center, QPointF(), rotation());
0135 
0136             // Compute the adjusted location in RA/DEC
0137             QSizeF const tileSkyOffsetScaled = adjustCoordinate(tileSkyLocation);
0138 
0139             tile->skyCenter.setRA0((skyCenter.ra0().Degrees() + tileSkyOffsetScaled.width()) / 15.0);
0140             tile->skyCenter.setDec0(skyCenter.dec0().Degrees() + tileSkyOffsetScaled.height());
0141             tile->skyCenter.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
0142 
0143             tile->rotation = tile->skyCenter.ra0().Degrees() - skyCenter.ra0().Degrees();
0144 
0145             // Large rotations handled wrong by the algorithm - prefer doing multiple mosaics
0146             if (abs(tile->rotation) <= 90.0)
0147             {
0148                 tile->index = ++index;
0149                 tiles.append(tile);
0150             }
0151             else
0152             {
0153                 delete tile;
0154                 tiles.append(nullptr);
0155             }
0156 
0157             y += (s_shaped && (col % 2)) ? -yOffset : +yOffset;
0158         }
0159 
0160         x -= xOffset;
0161     }
0162 }
0163 
0164 MosaicTilesManager::OneTile *MosaicTilesManager::getTile(int row, int col)
0165 {
0166     int offset = row * m_HorizontalTiles + col;
0167 
0168     if (offset < 0 || offset >= tiles.size())
0169         return nullptr;
0170 
0171     return tiles[offset];
0172 }
0173 
0174 QRectF MosaicTilesManager::boundingRect() const
0175 {
0176     const double xOffset = m_HorizontalTiles > 1 ? overlap : 0;
0177     const double yOffset = m_VerticalTiles > 1 ? overlap : 0;
0178     //    QRectF rect = QRectF(0, 0, fovW + xOffset * qMax(1, (m_HorizontalTiles - 1)), fovH + yOffset * qMax(1,
0179     //                         (m_VerticalTiles - 1)));
0180     const double xOverlapped = fovW * xOffset * m_HorizontalTiles;
0181     const double yOverlapped = fovH * yOffset * m_VerticalTiles;
0182 
0183     const double totalW = fovW * m_HorizontalTiles;
0184     const double totalH = fovH * m_VerticalTiles;
0185     QRectF rect = QRectF(-totalW / 2, -totalH / 2, totalW + xOverlapped, totalH + yOverlapped);
0186     return rect;
0187 }
0188 
0189 void MosaicTilesManager::mousePressEvent(QGraphicsSceneMouseEvent *event)
0190 {
0191     QGraphicsItem::mousePressEvent(event);
0192     QPointF pos = event->screenPos();
0193     m_LastPosition = pos;
0194 }
0195 
0196 void MosaicTilesManager::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
0197 {
0198     QGraphicsItem::mouseReleaseEvent(event);
0199     QPointF delta = event->screenPos() - m_LastPosition;
0200     emit newOffset(delta);
0201 }
0202 
0203 void MosaicTilesManager::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
0204 {
0205     if (!tiles.size())
0206         return;
0207 
0208     QFont defaultFont = painter->font();
0209     QRect const oneRect(-fovW / 2, -fovH / 2, fovW, fovH);
0210 
0211     // HACK: all tiles should be QGraphicsItem instances so that texts would be scaled properly
0212     double const fontScale = 1 / log(tiles.size() < 4 ? 4 : tiles.size());
0213 
0214     // Draw a light background field first to help detect holes - reduce alpha as we are stacking tiles over this
0215     painter->setBrush(QBrush(QColor(255, 0, 0, (200 * m_PainterAlpha) / 100), Qt::SolidPattern));
0216     painter->setPen(QPen(painter->brush(), 2, Qt::PenStyle::DotLine));
0217     painter->drawRect(QRectF(QPointF(-mfovW / 2, -mfovH / 2), QSizeF(mfovW, mfovH)));
0218 
0219     // Fill tiles with a transparent brush to show overlaps
0220     QBrush tileBrush(QColor(0, 255, 0, (200 * m_PainterAlpha) / 100), Qt::SolidPattern);
0221 
0222     // Draw each tile, adjusted for rotation
0223     for (int row = 0; row < m_VerticalTiles; row++)
0224     {
0225         for (int col = 0; col < m_HorizontalTiles; col++)
0226         {
0227             OneTile const * const tile = getTile(row, col);
0228             if (tile)
0229             {
0230                 QTransform const transform = painter->worldTransform();
0231                 painter->translate(tile->center);
0232                 painter->rotate(tile->rotation);
0233 
0234                 painter->setBrush(tileBrush);
0235                 painter->setPen(pen);
0236 
0237                 painter->drawRect(oneRect);
0238 
0239                 painter->setWorldTransform(transform);
0240             }
0241         }
0242     }
0243 
0244     // Overwrite with tile information
0245     for (int row = 0; row < m_VerticalTiles; row++)
0246     {
0247         for (int col = 0; col < m_HorizontalTiles; col++)
0248         {
0249             OneTile const * const tile = getTile(row, col);
0250             if (tile)
0251             {
0252                 QTransform const transform = painter->worldTransform();
0253                 painter->translate(tile->center);
0254                 painter->rotate(tile->rotation);
0255 
0256                 painter->setBrush(textBrush);
0257                 painter->setPen(textPen);
0258 
0259                 defaultFont.setPointSize(50 * fontScale);
0260                 painter->setFont(defaultFont);
0261                 painter->drawText(oneRect, Qt::AlignRight | Qt::AlignTop, QString("%1.").arg(tile->index));
0262 
0263                 defaultFont.setPointSize(20 * fontScale);
0264                 painter->setFont(defaultFont);
0265                 painter->drawText(oneRect, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1\n%2")
0266                                   .arg(tile->skyCenter.ra0().toHMSString())
0267                                   .arg(tile->skyCenter.dec0().toDMSString()));
0268                 painter->drawText(oneRect, Qt::AlignHCenter | Qt::AlignBottom, QString("%1%2°")
0269                                   .arg(tile->rotation >= 0.01 ? '+' : tile->rotation <= -0.01 ? '-' : '~')
0270                                   .arg(abs(tile->rotation), 5, 'f', 2));
0271 
0272                 painter->setWorldTransform(transform);
0273             }
0274         }
0275     }
0276 }
0277 
0278 QPointF MosaicTilesManager::rotatePoint(QPointF pointToRotate, QPointF centerPoint, double paDegrees)
0279 {
0280     double angleInRadians = paDegrees * dms::DegToRad;
0281     double cosTheta       = cos(angleInRadians);
0282     double sinTheta       = sin(angleInRadians);
0283 
0284     QPointF rotation_point;
0285 
0286     rotation_point.setX((cosTheta * (pointToRotate.x() - centerPoint.x()) -
0287                          sinTheta * (pointToRotate.y() - centerPoint.y()) + centerPoint.x()));
0288     rotation_point.setY((sinTheta * (pointToRotate.x() - centerPoint.x()) +
0289                          cosTheta * (pointToRotate.y() - centerPoint.y()) + centerPoint.y()));
0290 
0291     return rotation_point;
0292 }
0293 }