File indexing completed on 2024-09-15 03:29:33
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 }