File indexing completed on 2025-01-05 03:59:33

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2014 Torsten Rahn <rahn@kde.org>
0004 //
0005 
0006 // Local
0007 #include "VerticalPerspectiveProjection.h"
0008 #include "AbstractProjection_p.h"
0009 
0010 #include <QIcon>
0011 #include <qmath.h>
0012 
0013 #include <klocalizedstring.h>
0014 
0015 // Marble
0016 #include "ViewportParams.h"
0017 #include "GeoDataPoint.h"
0018 #include "GeoDataLineString.h"
0019 #include "GeoDataCoordinates.h"
0020 #include "MarbleGlobal.h"
0021 #include "AzimuthalProjection_p.h"
0022 
0023 #include "digikam_debug.h"
0024 
0025 #define SAFE_DISTANCE
0026 
0027 namespace Marble
0028 {
0029 
0030 class VerticalPerspectiveProjectionPrivate : public AzimuthalProjectionPrivate
0031 {
0032   public:
0033     explicit VerticalPerspectiveProjectionPrivate( VerticalPerspectiveProjection * parent );
0034 
0035     void calculateConstants(qreal radius) const;
0036 
0037     mutable qreal m_P; ///< Distance of the point of perspective in earth diameters
0038     mutable qreal m_previousRadius;
0039     mutable qreal m_altitudeToPixel;
0040     mutable qreal m_perspectiveRadius;
0041     mutable qreal m_pPfactor;
0042 
0043     Q_DECLARE_PUBLIC( VerticalPerspectiveProjection )
0044 };
0045 
0046 VerticalPerspectiveProjection::VerticalPerspectiveProjection()
0047     : AzimuthalProjection( new VerticalPerspectiveProjectionPrivate( this ) )
0048 {
0049     setMinLat( minValidLat() );
0050     setMaxLat( maxValidLat() );
0051 }
0052 
0053 VerticalPerspectiveProjection::VerticalPerspectiveProjection( VerticalPerspectiveProjectionPrivate *dd )
0054         : AzimuthalProjection( dd )
0055 {
0056     setMinLat( minValidLat() );
0057     setMaxLat( maxValidLat() );
0058 }
0059 
0060 VerticalPerspectiveProjection::~VerticalPerspectiveProjection()
0061 {
0062 }
0063 
0064 
0065 VerticalPerspectiveProjectionPrivate::VerticalPerspectiveProjectionPrivate( VerticalPerspectiveProjection * parent )
0066         : AzimuthalProjectionPrivate( parent ),
0067           m_P(1),
0068           m_previousRadius(1),
0069           m_altitudeToPixel(1),
0070           m_perspectiveRadius(1),
0071           m_pPfactor(1)
0072 {
0073 }
0074 
0075 
0076 QString VerticalPerspectiveProjection::name() const
0077 {
0078     return i18n( "Vertical Perspective Projection" );
0079 }
0080 
0081 QString VerticalPerspectiveProjection::description() const
0082 {
0083     return i18n( "<p><b>Vertical Perspective Projection</b> (\"orthogonal\")</p><p> Shows the earth as it appears from a relatively short distance above the surface. Applications: Used for Virtual Globes.</p>" );
0084 }
0085 
0086 QIcon VerticalPerspectiveProjection::icon() const
0087 {
0088     return QIcon::fromTheme(QStringLiteral("map-globe"));
0089 }
0090 
0091 void VerticalPerspectiveProjectionPrivate::calculateConstants(qreal radius) const
0092 {
0093     if (radius == m_previousRadius)  return;
0094     m_previousRadius = radius;
0095     m_P = 1.5 + 3 * 1000 * 0.4 / radius / qTan(0.5 * 110 * DEG2RAD);
0096     m_altitudeToPixel = radius / (EARTH_RADIUS * qSqrt((m_P-1)/(m_P+1)));
0097     m_perspectiveRadius = radius / qSqrt((m_P-1)/(m_P+1));
0098     m_pPfactor = (m_P+1)/(m_perspectiveRadius*m_perspectiveRadius*(m_P-1));
0099 }
0100 
0101 qreal VerticalPerspectiveProjection::clippingRadius() const
0102 {
0103     return 1;
0104 }
0105 
0106 bool VerticalPerspectiveProjection::screenCoordinates( const GeoDataCoordinates &coordinates,
0107                                              const ViewportParams *viewport,
0108                                              qreal &x, qreal &y, bool &globeHidesPoint ) const
0109 {
0110     Q_D(const VerticalPerspectiveProjection);
0111     d->calculateConstants(viewport->radius());
0112     const qreal P =  d->m_P;
0113     const qreal deltaLambda = coordinates.longitude() - viewport->centerLongitude();
0114     const qreal phi = coordinates.latitude();
0115     const qreal phi1 = viewport->centerLatitude();
0116 
0117     qreal cosC = qSin( phi1 ) * qSin( phi ) + qCos( phi1 ) * qCos( phi ) * qCos( deltaLambda );
0118 
0119     // Don't display placemarks that are below 10km altitude and
0120     // are on the Earth's backside (where cosC < 1/P)
0121     if (cosC < 1/P && coordinates.altitude() < 10000) {
0122         globeHidesPoint = true;
0123         return false;
0124     }
0125 
0126     // Let (x, y) be the position on the screen of the placemark ..
0127     // First determine the position in unit coordinates:
0128     qreal k = (P - 1) / (P - cosC); // scale factor
0129     x = ( qCos( phi ) * qSin( deltaLambda ) ) * k;
0130     y = ( qCos( phi1 ) * qSin( phi ) - qSin( phi1 ) * qCos( phi ) * qCos( deltaLambda ) ) * k;
0131 
0132     // Transform to screen coordinates
0133     qreal pixelAltitude = (coordinates.altitude() + EARTH_RADIUS) * d->m_altitudeToPixel;
0134     x *= pixelAltitude;
0135     y *= pixelAltitude;
0136 
0137 
0138     // Don't display satellites that are on the Earth's backside:
0139     if (cosC < 1/P && x*x+y*y < viewport->radius() * viewport->radius()) {
0140         globeHidesPoint = true;
0141         return false;
0142     }
0143     // The remaining placemarks are definitely not on the Earth's backside
0144     globeHidesPoint = false;
0145 
0146     x += viewport->width() / 2;
0147     y = viewport->height() / 2 - y;
0148 
0149     // Skip placemarks that are outside the screen area
0150     return !(x < 0 || x >= viewport->width() || y < 0 || y >= viewport->height());
0151 }
0152 
0153 bool VerticalPerspectiveProjection::screenCoordinates( const GeoDataCoordinates &coordinates,
0154                                              const ViewportParams *viewport,
0155                                              qreal *x, qreal &y,
0156                                              int &pointRepeatNum,
0157                                              const QSizeF& size,
0158                                              bool &globeHidesPoint ) const
0159 {
0160     pointRepeatNum = 0;
0161     globeHidesPoint = false;
0162 
0163     bool visible = screenCoordinates( coordinates, viewport, *x, y, globeHidesPoint );
0164 
0165     // Skip placemarks that are outside the screen area
0166     if ( *x + size.width() / 2.0 < 0.0 || *x >= viewport->width() + size.width() / 2.0
0167          || y + size.height() / 2.0 < 0.0 || y >= viewport->height() + size.height() / 2.0 )
0168     {
0169         return false;
0170     }
0171 
0172     // This projection doesn't have any repetitions,
0173     // so the number of screen points referring to the geopoint is one.
0174     pointRepeatNum = 1;
0175     return visible;
0176 }
0177 
0178 
0179 bool VerticalPerspectiveProjection::geoCoordinates( const int x, const int y,
0180                                           const ViewportParams *viewport,
0181                                           qreal& lon, qreal& lat,
0182                                           GeoDataCoordinates::Unit unit ) const
0183 {
0184     Q_D(const VerticalPerspectiveProjection);
0185     d->calculateConstants(viewport->radius());
0186     const qreal P = d->m_P;
0187     const qreal rx = ( - viewport->width()  / 2 + x );
0188     const qreal ry = (   viewport->height() / 2 - y );
0189     const qreal p2 = rx*rx + ry*ry;
0190 
0191     if (p2 == 0) {
0192         lon = viewport->centerLongitude();
0193         lat = viewport->centerLatitude();
0194         return true;
0195     }
0196 
0197     const qreal pP = p2*d->m_pPfactor;
0198 
0199     if ( pP > 1) return false;
0200 
0201     const qreal p = qSqrt(p2);
0202     const qreal fract = d->m_perspectiveRadius*(P-1)/p;
0203     const qreal c = qAsin((P-qSqrt(1-pP))/(fract+1/fract));
0204     const qreal sinc = qSin(c);
0205 
0206     const qreal centerLon = viewport->centerLongitude();
0207     const qreal centerLat = viewport->centerLatitude();
0208     lon = centerLon + qAtan2(rx*sinc, (p*qCos(centerLat)*qCos(c) - ry*qSin(centerLat)*sinc));
0209 
0210     while ( lon < -M_PI ) lon += 2 * M_PI;
0211     while ( lon >  M_PI ) lon -= 2 * M_PI;
0212 
0213     lat = qAsin(qCos(c)*qSin(centerLat) + (ry*sinc*qCos(centerLat))/p);
0214 
0215     if ( unit == GeoDataCoordinates::Degree ) {
0216         lon *= RAD2DEG;
0217         lat *= RAD2DEG;
0218     }
0219 
0220     return true;
0221 }
0222 
0223 }