Git commit af76a2a8b548d87c8e8b383c8c2a6cec1fe5267f by Akarsh Simha.
Committed on 18/02/2024 at 13:48.
Pushed by asimha into branch 'master'.

New feature: Allow the user to mirror the sky map

With a new option in the "View" menu, the user will be able to mirror the sky 
map so as to be able to match the view through an erecting prism used for 
example on a Schmidt-Cassegrain or Refracting type telescope.

The rotation feature overlay now also marks East in addition to north and 
zenith, so as to know easily whether the display is mirrored or not.

M  +5    -2    doc/config.docbook
M  +1    -0    kstars/data/kstarsui.rc
M  +1    -1    kstars/kstars.cpp
M  +5    -0    kstars/kstars.kcfg
M  +5    -0    kstars/kstarsactions.cpp
M  +5    -3    kstars/kstarsinit.cpp
M  +3    -0    kstars/projections/projector.cpp
M  +7    -4    kstars/projections/projector.h
M  +1    -4    kstars/skymap.cpp
M  +53   -48   kstars/skymapdrawabstract.cpp

https://invent.kde.org/education/kstars/-/commit/af76a2a8b548d87c8e8b383c8c2a6cec1fe5267f

diff --git a/doc/config.docbook b/doc/config.docbook
index f3c2f225dc..a65cf581f1 100644
--- a/doc/config.docbook
+++ b/doc/config.docbook
@@ -1621,13 +1621,15 @@ from the 
<menuchoice><guimenu>Settings</guimenu><guisubmenu>FOV Symbols</guisubm
 <sect1 id="skymap_orientation">
   <title>Adjusting orientation of the sky map</title>
   <para>
-    You can tweak various settings to make the orientation of the sky map 
match the view through your optical instrument, provided (as of this version) 
the instrument does not mirror the field-of-view (as is done by prisms used 
with SCTs and refractors).
+    You can tweak various settings to make the orientation of the sky map 
match the view through your optical instrument.
   </para>
   <para>
     First, pick the coordinate system that matches your mount. For an 
equatorially mounted instrument, switch to the Equatorial Coordinate mode in 
the <guimenu>View</guimenu> menu. The option to toggle the coordinate system 
should read <guilabel>Switch to Horizontal View (Horizontal 
Coordinates)</guilabel> when the current mode is Equatorial Coordinates. For an 
altazimuth-mounted instrument or naked-eye viewing, switch to Horizontal 
Coordinates, so that the option in the <guimenu>View</guimenu> menu reads 
<guilabel>Switch to Star Globe View (Equatorial Coordinates)</guilabel>. This 
sets the base coordinate system used to render the sky map, and also sets the 
reference for the orientation of the skymap: zenith or north.
   </para>
   <para>
-    To rotate the sky map freely, you can hold down the &Shift; key and drag 
the mouse on the sky map. A temporary overlay will appear showing the direction 
of north and zenith at the point, and displaying the angle they make with the 
vertical in a counterclockwise sense. The orientations of zenith and north will 
update as you rotate the sky map. Letting go of &Shift; or the mouse button 
will stop the rotation operation. As you pan the sky map or focus it on 
different objects, the rotation you set is retained as an offset from the 
reference direction. The reference direction is north when using Equatorial 
Coordinates and zenith when using Horizontal Coordinates. As a reminder, the 
reference direction is solid and brighter in the temporary overlay. For the two 
common orientations of erect and inverted, the rotation can be set / reset 
using the <menuchoice><guimenu>View</guimenu><guisubmenu>Skymap 
Orientation</guisubmenu></menuchoice> submenu. Select "North Down" or "Zenith 
Down" as is applicable to set an orientation of 180 degrees.
+    If your instrument is using an erecting prism, typically used on 
Schmidt-Cassegrain and refracting type telescopes, the view through the 
eyepiece will be mirrored horizontally. You can have the sky map match this by 
checking the <guilabel>Mirrored View</guilabel> option under the 
<guimenu>View</guimenu> menu.
+  <para>
+    Next, to rotate the sky map freely, you can hold down the &Shift; key and 
drag the mouse on the sky map. A temporary overlay will appear showing the 
direction of north and zenith at the point, and displaying the angle they make 
with the vertical in a counterclockwise sense. The orientations of zenith and 
north will update as you rotate the sky map. Letting go of &Shift; or the mouse 
button will stop the rotation operation. As you pan the sky map or focus it on 
different objects, the rotation you set is retained as an offset from the 
reference direction. The reference direction is north when using Equatorial 
Coordinates and zenith when using Horizontal Coordinates. As a reminder, the 
reference direction is solid and brighter in the temporary overlay. For the two 
common orientations of erect and inverted, the rotation can be set / reset 
using the <menuchoice><guimenu>View</guimenu><guisubmenu>Skymap 
Orientation</guisubmenu></menuchoice> submenu. Select "North Down" or "Zenith 
Down" as is applicable to set an orientation of 180 degrees.
   </para>
   <para>
     If you are visually observing through an eyepiece of an instrument, you 
may need to do some more correction. For the common case of a large Dobsonian 
telescope (or more generally a Newtonian design mounted on an altazimuth 
mount), a systematic additional correction is of help. This correction applies 
because we stand erect while using the telescope irrespective of the angle the 
telescope tube is making with the ground. So as we move the telescope in 
altitude, an additional correction depending on the altitude of the object 
needs to be applied to make the sky map match the view through the eyepiece. 
This correction is enabled by checking the <guilabel>Erect observer 
correction</guilabel> checkbox in the 
<menuchoice><guimenu>View</guimenu><guisubmenu>Skymap 
Orientation</guisubmenu></menuchoice> submenu. This correction only makes sense 
in Horizontal Coordinate mode and is disabled when using equatorial coordinates.
@@ -1638,6 +1640,7 @@ from the 
<menuchoice><guimenu>Settings</guimenu><guisubmenu>FOV Symbols</guisubm
       <listitem><para>Naked-eye observing: Choose Horizontal Coordinates and a 
<guilabel>Zenith Up</guilabel> orientation under 
<menuchoice><guimenu>View</guimenu><guisubmenu>Skymap 
Orientation</guisubmenu></menuchoice>.</para></listitem>
       <listitem><para>Camera on an equatorially mounted telescope: Choose 
Equatorial Coordinates and adjust the orientation of the sky map so that it 
matches your camera. As your mount points to different regions of the sky, the 
orientation should be rendered correctly.</para></listitem>
       <listitem><para>Using binoculars: Same settings as Naked-eye 
observing</para></listitem>
+      <listitem><para>Eyepiece of an altazimuth Schmidt-Cassegrin telescope 
with an erecting prism: Under the <guimenu>View</guimenu> menu, choose 
<guilabel>Mirrored View</guilabel>, and under the <guisubmenu>Skymap 
Orientation</guisubmenu> sub-menu, choose <guilabel>Zenith Up</guilabel>. 
Finally, tweak the rotation manually to match the eyepiece view according to 
the angle you are using for your erecting prism.</para></listitem>
       <listitem><para>Using a RACI finder scope on an altazimuth mounted 
telescope: Same settings as Naked-eye observing, except you may need to tweak 
the orientation manually once if you have it mounted at an 
angle</para></listitem>
       <listitem><para>Using a straight-through (inverted view) finder scope on 
an altazimuth mounted telescope: Choose Horizontal Coordinates and a sky-map 
orientation of <guilabel>Zenith Down</guilabel> in 
<menuchoice><guimenu>View</guimenu><guisubmenu>Skymap 
Orientation</guisubmenu></menuchoice> submenu</para></listitem>
       <listitem><para>Eyepiece of a Dobsonian telescope: Choose Horizontal 
Coordinates, and in the <menuchoice><guimenu>View</guimenu><guisubmenu>Skymap 
Orientation</guisubmenu></menuchoice> submenu, select <guilabel>Zenith 
Down</guilabel> and check the <guilabel>Erect observer correction</guilabel> 
option. Then adjust the orientation manually once to match your telescope 
eyepiece view, and it should henceforth track it correctly.</para></listitem>
diff --git a/kstars/data/kstarsui.rc b/kstars/data/kstarsui.rc
index da69591619..8a59a30064 100644
--- a/kstars/data/kstarsui.rc
+++ b/kstars/data/kstarsui.rc
@@ -48,6 +48,7 @@
                 <Action name="fullscreen" />
                 <Separator />
                 <Action name="coordsys" />
+                <Action name="mirror_skymap" />
                 <Action name="skymap_orientation" />
                 <Action name="toggle_terrain" />
                 <Action name="toggle_image_overlays" />
diff --git a/kstars/kstars.cpp b/kstars/kstars.cpp
index 9d0efaa490..6cd2f27545 100644
--- a/kstars/kstars.cpp
+++ b/kstars/kstars.cpp
@@ -168,7 +168,6 @@ KStars::KStars(bool doSplash, bool clockrun, const QString 
&startdate)
     domeGroup->setExclusive(false);
     skymapOrientationGroup = new QActionGroup(this);
 
-
     m_KStarsData = KStarsData::Create();
     Q_ASSERT(m_KStarsData);
     //Set Geographic Location from Options
@@ -345,6 +344,7 @@ void KStars::applyConfig(bool doApplyFocus)
     
actionCollection()->action("show_satellites")->setChecked(Options::showSatellites());
     
actionCollection()->action("erect_observer_correction")->setChecked(Options::erectObserverCorrection());
     
actionCollection()->action("erect_observer_correction")->setEnabled(Options::useAltAz());
+    
actionCollection()->action("mirror_skymap")->setChecked(Options::mirrorSkyMap());
     statusBar()->setVisible(Options::showStatusBar());
 
     //color scheme
diff --git a/kstars/kstars.kcfg b/kstars/kstars.kcfg
index ceae1c310a..8e8d91625a 100644
--- a/kstars/kstars.kcfg
+++ b/kstars/kstars.kcfg
@@ -806,6 +806,11 @@
          <min>0.</min>
          <max>359.9999</max>
       </entry>
+      <entry name="MirrorSkyMap" type="Bool">
+         <label>Mirrors the sky map</label>
+         <whatsthis>Enable this if you want the sky map to be mirrored 
left-right, e.g. to match the view through an erecting prism.</whatsthis>
+         <default>false</default>
+      </entry>
       <entry name="ErectObserverCorrection" type="Bool">
          <label>Orients the sky-map to account for an erect observer at the 
eyepiece</label>
          <whatsthis>Enable this if you are using your eye at the eyepiece in 
an altazimuth mounted Newtonian telescope. This accounts for the fact that the 
observer stands erect as the telescope moves up and down, so that the 
orientation of the sky map will track what is seen in your eyepiece once it is 
set up correctly.</whatsthis>
diff --git a/kstars/kstarsactions.cpp b/kstars/kstarsactions.cpp
index 26b2aede1d..c0f7c291fd 100644
--- a/kstars/kstarsactions.cpp
+++ b/kstars/kstarsactions.cpp
@@ -1743,12 +1743,17 @@ void KStars::slotSkyMapOrientation()
     {
         Options::setSkyRotation(180.0);
     }
+    else if (sender() == actionCollection()->action("mirror_skymap"))
+    {
+        ;
+    }
     else
     {
         Q_ASSERT(false && "Unhandled orientation action");
         qCWarning(KSTARS) << "Unhandled orientation action";
     }
 
+    
Options::setMirrorSkyMap(actionCollection()->action("mirror_skymap")->isChecked());
     
Options::setErectObserverCorrection(actionCollection()->action("erect_observer_correction")->isChecked());
     map()->forceUpdate();
 }
diff --git a/kstars/kstarsinit.cpp b/kstars/kstarsinit.cpp
index 823f51cee8..8774628ac8 100644
--- a/kstars/kstarsinit.cpp
+++ b/kstars/kstarsinit.cpp
@@ -132,7 +132,6 @@ bool KStars::setResourceFile(QString const rc)
     else return false;
 }
 
-
 void KStars::initActions()
 {
     // Check if we have this specific Breeze icon. If not, try to set the 
theme search path and if appropriate, the icon theme rcc file
@@ -269,6 +268,11 @@ void KStars::initActions()
                 i18n("Switch to Horizontal View (Horizontal &Coordinates)"))
             << QKeySequence("Space");
 
+    newToggleAction(
+        actionCollection(), "mirror_skymap",
+        i18nc("Mirror the view of the sky map", "Mirrored View"),
+        this, SLOT(slotSkyMapOrientation())) << QKeySequence(Qt::CTRL + 
Qt::SHIFT + Qt::Key_M);;
+
     actionCollection()->addAction("toggle_terrain", this, SLOT(slotTerrain()))
             << (Options::showTerrain() ? i18n("Hide Terrain") :
                 i18n("Show Terrain"))
@@ -339,7 +343,6 @@ void KStars::initActions()
     newToggleAction(actionCollection(), "show_sbJ2000RADec", i18n("Show 
J2000.0 RA/Dec Field"), this,
                     SLOT(slotShowGUIItem(bool)));
 
-
     populateThemes();
 
     //Color scheme actions.  These are added to the "colorschemes" KActionMenu.
@@ -742,7 +745,6 @@ void KStars::repopulateOrientation()
         << ToolTip(i18nc("Orientation of the sky map",
                          "This mode is selected automatically if you manually 
rotated the sky map using Shift + Drag mouse action, to inform you that the 
orientation is arbitrary")));
 
-
     orientationActionMenu->addSeparator();
     QAction *erectObserverAction = newToggleAction(
                                        actionCollection(), 
"erect_observer_correction",
diff --git a/kstars/projections/projector.cpp b/kstars/projections/projector.cpp
index d8ee191e6a..cb469efe21 100644
--- a/kstars/projections/projector.cpp
+++ b/kstars/projections/projector.cpp
@@ -371,6 +371,9 @@ QVector<Eigen::Vector2f> Projector::groundPoly(SkyPoint 
*labelpoint, bool *drawL
         return ground;
     }
 
+    if (m_vp.mirror)
+        std::reverse(ground.begin(), ground.end());
+
     //In Gnomonic projection, or if sufficiently zoomed in, we can complete
     //the ground polygon by simply adding offscreen points
     //FIXME: not just gnomonic
diff --git a/kstars/projections/projector.h b/kstars/projections/projector.h
index 4d2b437a89..e08d4df980 100644
--- a/kstars/projections/projector.h
+++ b/kstars/projections/projector.h
@@ -42,10 +42,11 @@ class ViewParams
         bool useRefraction;
         bool useAltAz;
         bool fillGround; ///<If the ground is filled, then points below 
horizon are invisible
+        bool mirror;     // Mirrors along the X-axis
         SkyPoint *focus;
         ViewParams() : width(0), height(0), zoomFactor(0), rotationAngle(0),
             useRefraction(false), useAltAz(false), fillGround(false),
-            focus(nullptr) {}
+            mirror(false), focus(nullptr) {}
 };
 
 /**
@@ -309,10 +310,11 @@ class Projector
          */
         inline Eigen::Vector2f rst(double x, double y) const
         {
+            const double sgn = m_vp.mirror ? -1. : 1.;
             return
             {
-                m_vp.width / 2 - m_vp.zoomFactor * (x * 
m_vp.rotationAngle.cos() - y * m_vp.rotationAngle.sin()),
-                m_vp.height / 2 - m_vp.zoomFactor * (x * 
m_vp.rotationAngle.sin() + y * m_vp.rotationAngle.cos())
+                m_vp.width / 2 - m_vp.zoomFactor * (x * sgn * 
m_vp.rotationAngle.cos() - y * m_vp.rotationAngle.sin()),
+                    m_vp.height / 2 - m_vp.zoomFactor * (x * sgn * 
m_vp.rotationAngle.sin() + y * m_vp.rotationAngle.cos())
             };
         }
 
@@ -330,11 +332,12 @@ class Projector
          */
         inline Eigen::Vector2f derst(double x, double y) const
         {
+            const double sgn = m_vp.mirror ? -1. : 1;
             const double X = (m_vp.width / 2 - x) / m_vp.zoomFactor;
             const double Y = (m_vp.height / 2 - y) / m_vp.zoomFactor;
             return
             {
-                m_vp.rotationAngle.cos() * X + m_vp.rotationAngle.sin() * Y,
+                sgn * (m_vp.rotationAngle.cos() * X + m_vp.rotationAngle.sin() 
* Y),
                 -m_vp.rotationAngle.sin() * X + m_vp.rotationAngle.cos() * Y
             };
         }
diff --git a/kstars/skymap.cpp b/kstars/skymap.cpp
index 8173b8ffae..b0a48ecb99 100644
--- a/kstars/skymap.cpp
+++ b/kstars/skymap.cpp
@@ -540,7 +540,6 @@ void SkyMap::slotCopyCoordinates()
                                        Alt.toDMSString()));
 }
 
-
 void SkyMap::slotCopyTLE()
 {
 
@@ -555,7 +554,6 @@ void SkyMap::slotCopyTLE()
         tle = "NO TLE FOR OBJECT";
     }
 
-
     QApplication::clipboard()->setText(tle);
 }
 
@@ -1246,6 +1244,7 @@ void SkyMap::setupProjector()
     p.useRefraction = Options::useRefraction();
     p.zoomFactor    = Options::zoomFactor();
     p.rotationAngle = determineSkyRotation();
+    p.mirror        = Options::mirrorSkyMap();
     p.fillGround    = Options::showGround();
     //Check if we need a new projector
     if (m_proj && Options::projection() == m_proj->type())
@@ -1364,5 +1363,3 @@ void SkyMap::slotStartXplanetViewer()
     else
         new XPlanetImageViewer(i18n("Saturn"), this);
 }
-
-
diff --git a/kstars/skymapdrawabstract.cpp b/kstars/skymapdrawabstract.cpp
index b53687f5ed..9e7395354c 100644
--- a/kstars/skymapdrawabstract.cpp
+++ b/kstars/skymapdrawabstract.cpp
@@ -86,7 +86,10 @@ void SkyMapDrawAbstract::drawOverlays(QPainter &p, bool 
drawFov)
 
     drawZoomBox(p);
 
-    drawOrientationArrows(p);
+    if (m_SkyMap->rotationStart.x() > 0 && m_SkyMap->rotationStart.y() > 0)
+    {
+        drawOrientationArrows(p);
+    }
 
     // FIXME: Maybe we should take care of this differently. Maybe
     // drawOverlays should remain in SkyMap, since it just calls
@@ -113,51 +116,51 @@ void SkyMapDrawAbstract::drawAngleRuler(QPainter &p)
 
 void SkyMapDrawAbstract::drawOrientationArrows(QPainter &p)
 {
-    if (m_SkyMap->rotationStart.x() > 0 && m_SkyMap->rotationStart.y() > 0)
+    auto* data = m_KStarsData;
+    const SkyPoint centerSkyPoint = m_SkyMap->m_proj->fromScreen(
+                                                                 
p.viewport().center(),
+                                                                 data->lst(), 
data->geo()->lat());
+
+    QPointF centerScreenPoint = p.viewport().center();
+    double northRotation = m_SkyMap->m_proj->findNorthPA(
+                                                         &centerSkyPoint, 
centerScreenPoint.x(), centerScreenPoint.y());
+    double zenithRotation = m_SkyMap->m_proj->findZenithPA(
+                                                           &centerSkyPoint, 
centerScreenPoint.x(), centerScreenPoint.y());
+
+    QColor overlayColor(data->colorScheme()->colorNamed("CompassColor"));
+    p.setPen(Qt::NoPen);
+    auto drawArrow = [&](double angle, const QString & marker, const float 
labelRadius, const bool primary)
     {
-        auto* data = m_KStarsData;
-        const SkyPoint centerSkyPoint = m_SkyMap->m_proj->fromScreen(
-                                            p.viewport().center(),
-                                            data->lst(), data->geo()->lat());
-
-        QPointF centerScreenPoint = p.viewport().center();
-        double northRotation = m_SkyMap->m_proj->findNorthPA(
-                                   &centerSkyPoint, centerScreenPoint.x(), 
centerScreenPoint.y());
-        double zenithRotation = m_SkyMap->m_proj->findZenithPA(
-                                    &centerSkyPoint, centerScreenPoint.x(), 
centerScreenPoint.y());
-
-        QColor overlayColor(data->colorScheme()->colorNamed("CompassColor"));
-        p.setPen(Qt::NoPen);
-        auto drawArrow = [&](double angle, const QString & marker, const float 
labelRadius, const bool primary)
+        constexpr float radius = 150.0f; // In pixels
+        const auto fontMetrics = QFontMetricsF(QFont());
+        QTransform transform;
+        QColor color = overlayColor;
+        color.setAlphaF(primary ? 1.0 : 0.75);
+        QPen pen(color, 1.0, primary ? Qt::SolidLine : Qt::DotLine);
+        QBrush brush(color);
+
+        QPainterPath arrowstem;
+        arrowstem.moveTo(0.f, 0.f);
+        arrowstem.lineTo(0.f, -radius + radius / 7.5f);
+        transform.reset();
+        transform.translate(centerScreenPoint.x(), centerScreenPoint.y());
+        transform.rotate(angle);
+        arrowstem = transform.map(arrowstem);
+        p.strokePath(arrowstem, pen);
+
+        QPainterPath arrowhead;
+        arrowhead.moveTo(0.f, 0.f);
+        arrowhead.lineTo(-radius / 30.f, radius / 7.5f);
+        arrowhead.lineTo(radius / 30.f, radius / 7.5f);
+        arrowhead.lineTo(0.f, 0.f);
+        arrowhead.addText(QPointF(-1.1 * fontMetrics.width(marker), radius / 
7.5f + 1.2f * fontMetrics.ascent()),
+                          QFont(), marker);
+        transform.translate(0, -radius);
+        arrowhead = transform.map(arrowhead);
+        p.fillPath(arrowhead, brush);
+
+        if (labelRadius > 0.f)
         {
-            constexpr float radius = 150.0f; // In pixels
-            const auto fontMetrics = QFontMetricsF(QFont());
-            QTransform transform;
-            QColor color = overlayColor;
-            color.setAlphaF(primary ? 1.0 : 0.75);
-            QPen pen(color, 1.0, primary ? Qt::SolidLine : Qt::DotLine);
-            QBrush brush(color);
-
-            QPainterPath arrowstem;
-            arrowstem.moveTo(0.f, 0.f);
-            arrowstem.lineTo(0.f, -radius + radius / 7.5f);
-            transform.reset();
-            transform.translate(centerScreenPoint.x(), centerScreenPoint.y());
-            transform.rotate(angle);
-            arrowstem = transform.map(arrowstem);
-            p.strokePath(arrowstem, pen);
-
-            QPainterPath arrowhead;
-            arrowhead.moveTo(0.f, 0.f);
-            arrowhead.lineTo(-radius / 30.f, radius / 7.5f);
-            arrowhead.lineTo(radius / 30.f, radius / 7.5f);
-            arrowhead.lineTo(0.f, 0.f);
-            arrowhead.addText(QPointF(-1.1 * fontMetrics.width(marker), radius 
/ 7.5f + 1.2f * fontMetrics.ascent()),
-                              QFont(), marker);
-            transform.translate(0, -radius);
-            arrowhead = transform.map(arrowhead);
-            p.fillPath(arrowhead, brush);
-
             QRectF angleMarkerRect(centerScreenPoint.x() - labelRadius, 
centerScreenPoint.y() - labelRadius,
                                    2.f * labelRadius, 2.f * labelRadius);
             p.setPen(pen);
@@ -179,11 +182,13 @@ void SkyMapDrawAbstract::drawOrientationArrows(QPainter 
&p)
             transform.rotate(90);
             angleLabel = transform.map(angleLabel);
             p.fillPath(angleLabel, brush);
+        }
 
-        };
-        drawArrow(northRotation, i18nc("North", "N"), 80.f, 
!Options::useAltAz());
-        drawArrow(zenithRotation, i18nc("Zenith", "Z"), 40.f, 
Options::useAltAz());
-    }
+    };
+    auto eastRotation = northRotation + (m_SkyMap->m_proj->viewParams().mirror 
? 90 : -90);
+    drawArrow(northRotation, i18nc("North", "N"), 80.f, !Options::useAltAz());
+    drawArrow(eastRotation, i18nc("East", "E"), -1.f, !Options::useAltAz());
+    drawArrow(zenithRotation, i18nc("Zenith", "Z"), 40.f, Options::useAltAz());
 }
 
 void SkyMapDrawAbstract::drawZoomBox(QPainter &p)

Reply via email to