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