1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2014 Torsten Rahn <rahn@kde.org>
4 //
5 
6 // Local
7 #include "VerticalPerspectiveProjection.h"
8 #include "AbstractProjection_p.h"
9 
10 #include "MarbleDebug.h"
11 
12 // Marble
13 #include "ViewportParams.h"
14 #include "GeoDataPoint.h"
15 #include "GeoDataLineString.h"
16 #include "GeoDataCoordinates.h"
17 #include "MarbleGlobal.h"
18 #include "AzimuthalProjection_p.h"
19 
20 #include <QIcon>
21 #include <qmath.h>
22 
23 #define SAFE_DISTANCE
24 
25 namespace Marble
26 {
27 
28 class VerticalPerspectiveProjectionPrivate : public AzimuthalProjectionPrivate
29 {
30   public:
31     explicit VerticalPerspectiveProjectionPrivate( VerticalPerspectiveProjection * parent );
32 
33     void calculateConstants(qreal radius) const;
34 
35     mutable qreal m_P; ///< Distance of the point of perspective in earth diameters
36     mutable qreal m_previousRadius;
37     mutable qreal m_altitudeToPixel;
38     mutable qreal m_perspectiveRadius;
39     mutable qreal m_pPfactor;
40 
41     Q_DECLARE_PUBLIC( VerticalPerspectiveProjection )
42 };
43 
VerticalPerspectiveProjection()44 VerticalPerspectiveProjection::VerticalPerspectiveProjection()
45     : AzimuthalProjection( new VerticalPerspectiveProjectionPrivate( this ) )
46 {
47     setMinLat( minValidLat() );
48     setMaxLat( maxValidLat() );
49 }
50 
VerticalPerspectiveProjection(VerticalPerspectiveProjectionPrivate * dd)51 VerticalPerspectiveProjection::VerticalPerspectiveProjection( VerticalPerspectiveProjectionPrivate *dd )
52         : AzimuthalProjection( dd )
53 {
54     setMinLat( minValidLat() );
55     setMaxLat( maxValidLat() );
56 }
57 
~VerticalPerspectiveProjection()58 VerticalPerspectiveProjection::~VerticalPerspectiveProjection()
59 {
60 }
61 
62 
VerticalPerspectiveProjectionPrivate(VerticalPerspectiveProjection * parent)63 VerticalPerspectiveProjectionPrivate::VerticalPerspectiveProjectionPrivate( VerticalPerspectiveProjection * parent )
64         : AzimuthalProjectionPrivate( parent ),
65           m_P(1),
66           m_previousRadius(1),
67           m_altitudeToPixel(1),
68           m_perspectiveRadius(1),
69           m_pPfactor(1)
70 {
71 }
72 
73 
name() const74 QString VerticalPerspectiveProjection::name() const
75 {
76     return QObject::tr( "Vertical Perspective Projection" );
77 }
78 
description() const79 QString VerticalPerspectiveProjection::description() const
80 {
81     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>" );
82 }
83 
icon() const84 QIcon VerticalPerspectiveProjection::icon() const
85 {
86     return QIcon(QStringLiteral(":/icons/map-globe.png"));
87 }
88 
calculateConstants(qreal radius) const89 void VerticalPerspectiveProjectionPrivate::calculateConstants(qreal radius) const
90 {
91     if (radius == m_previousRadius)  return;
92     m_previousRadius = radius;
93     m_P = 1.5 + 3 * 1000 * 0.4 / radius / qTan(0.5 * 110 * DEG2RAD);
94     m_altitudeToPixel = radius / (EARTH_RADIUS * qSqrt((m_P-1)/(m_P+1)));
95     m_perspectiveRadius = radius / qSqrt((m_P-1)/(m_P+1));
96     m_pPfactor = (m_P+1)/(m_perspectiveRadius*m_perspectiveRadius*(m_P-1));
97 }
98 
clippingRadius() const99 qreal VerticalPerspectiveProjection::clippingRadius() const
100 {
101     return 1;
102 }
103 
screenCoordinates(const GeoDataCoordinates & coordinates,const ViewportParams * viewport,qreal & x,qreal & y,bool & globeHidesPoint) const104 bool VerticalPerspectiveProjection::screenCoordinates( const GeoDataCoordinates &coordinates,
105                                              const ViewportParams *viewport,
106                                              qreal &x, qreal &y, bool &globeHidesPoint ) const
107 {
108     Q_D(const VerticalPerspectiveProjection);
109     d->calculateConstants(viewport->radius());
110     const qreal P =  d->m_P;
111     const qreal deltaLambda = coordinates.longitude() - viewport->centerLongitude();
112     const qreal phi = coordinates.latitude();
113     const qreal phi1 = viewport->centerLatitude();
114 
115     qreal cosC = qSin( phi1 ) * qSin( phi ) + qCos( phi1 ) * qCos( phi ) * qCos( deltaLambda );
116 
117     // Don't display placemarks that are below 10km altitude and
118     // are on the Earth's backside (where cosC < 1/P)
119     if (cosC < 1/P && coordinates.altitude() < 10000) {
120         globeHidesPoint = true;
121         return false;
122     }
123 
124     // Let (x, y) be the position on the screen of the placemark ..
125     // First determine the position in unit coordinates:
126     qreal k = (P - 1) / (P - cosC); // scale factor
127     x = ( qCos( phi ) * qSin( deltaLambda ) ) * k;
128     y = ( qCos( phi1 ) * qSin( phi ) - qSin( phi1 ) * qCos( phi ) * qCos( deltaLambda ) ) * k;
129 
130     // Transform to screen coordinates
131     qreal pixelAltitude = (coordinates.altitude() + EARTH_RADIUS) * d->m_altitudeToPixel;
132     x *= pixelAltitude;
133     y *= pixelAltitude;
134 
135 
136     // Don't display satellites that are on the Earth's backside:
137     if (cosC < 1/P && x*x+y*y < viewport->radius() * viewport->radius()) {
138         globeHidesPoint = true;
139         return false;
140     }
141     // The remaining placemarks are definitely not on the Earth's backside
142     globeHidesPoint = false;
143 
144     x += viewport->width() / 2;
145     y = viewport->height() / 2 - y;
146 
147     // Skip placemarks that are outside the screen area
148     return !(x < 0 || x >= viewport->width() || y < 0 || y >= viewport->height());
149 }
150 
screenCoordinates(const GeoDataCoordinates & coordinates,const ViewportParams * viewport,qreal * x,qreal & y,int & pointRepeatNum,const QSizeF & size,bool & globeHidesPoint) const151 bool VerticalPerspectiveProjection::screenCoordinates( const GeoDataCoordinates &coordinates,
152                                              const ViewportParams *viewport,
153                                              qreal *x, qreal &y,
154                                              int &pointRepeatNum,
155                                              const QSizeF& size,
156                                              bool &globeHidesPoint ) const
157 {
158     pointRepeatNum = 0;
159     globeHidesPoint = false;
160 
161     bool visible = screenCoordinates( coordinates, viewport, *x, y, globeHidesPoint );
162 
163     // Skip placemarks that are outside the screen area
164     if ( *x + size.width() / 2.0 < 0.0 || *x >= viewport->width() + size.width() / 2.0
165          || y + size.height() / 2.0 < 0.0 || y >= viewport->height() + size.height() / 2.0 )
166     {
167         return false;
168     }
169 
170     // This projection doesn't have any repetitions,
171     // so the number of screen points referring to the geopoint is one.
172     pointRepeatNum = 1;
173     return visible;
174 }
175 
176 
geoCoordinates(const int x,const int y,const ViewportParams * viewport,qreal & lon,qreal & lat,GeoDataCoordinates::Unit unit) const177 bool VerticalPerspectiveProjection::geoCoordinates( const int x, const int y,
178                                           const ViewportParams *viewport,
179                                           qreal& lon, qreal& lat,
180                                           GeoDataCoordinates::Unit unit ) const
181 {
182     Q_D(const VerticalPerspectiveProjection);
183     d->calculateConstants(viewport->radius());
184     const qreal P = d->m_P;
185     const qreal rx = ( - viewport->width()  / 2 + x );
186     const qreal ry = (   viewport->height() / 2 - y );
187     const qreal p2 = rx*rx + ry*ry;
188 
189     if (p2 == 0) {
190         lon = viewport->centerLongitude();
191         lat = viewport->centerLatitude();
192         return true;
193     }
194 
195     const qreal pP = p2*d->m_pPfactor;
196 
197     if ( pP > 1) return false;
198 
199     const qreal p = qSqrt(p2);
200     const qreal fract = d->m_perspectiveRadius*(P-1)/p;
201     const qreal c = qAsin((P-qSqrt(1-pP))/(fract+1/fract));
202     const qreal sinc = qSin(c);
203 
204     const qreal centerLon = viewport->centerLongitude();
205     const qreal centerLat = viewport->centerLatitude();
206     lon = centerLon + qAtan2(rx*sinc, (p*qCos(centerLat)*qCos(c) - ry*qSin(centerLat)*sinc));
207 
208     while ( lon < -M_PI ) lon += 2 * M_PI;
209     while ( lon >  M_PI ) lon -= 2 * M_PI;
210 
211     lat = qAsin(qCos(c)*qSin(centerLat) + (ry*sinc*qCos(centerLat))/p);
212 
213     if ( unit == GeoDataCoordinates::Degree ) {
214         lon *= RAD2DEG;
215         lat *= RAD2DEG;
216     }
217 
218     return true;
219 }
220 
221 }
222