File indexing completed on 2024-05-05 03:49:53

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