1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org>
4 // SPDX-FileCopyrightText: 2007-2012 Torsten Rahn <rahn@kde.org>
5 //
6 
7 
8 // Local
9 #include "MercatorProjection.h"
10 
11 #include "MarbleDebug.h"
12 
13 // Marble
14 #include "ViewportParams.h"
15 #include "GeoDataLatLonAltBox.h"
16 
17 #include "MathHelper.h"
18 #include "GeoDataPoint.h"
19 #include "MarbleMath.h"
20 
21 #include <QIcon>
22 
23 using namespace Marble;
24 
MercatorProjection()25 MercatorProjection::MercatorProjection()
26     : CylindricalProjection(),
27       m_lastCenterLat(200.0),
28       m_lastCenterLatInv(0.0)
29 {
30     setMinLat( minValidLat() );
31     setMaxLat( maxValidLat() );
32 }
33 
~MercatorProjection()34 MercatorProjection::~MercatorProjection()
35 {
36 }
37 
name() const38 QString MercatorProjection::name() const
39 {
40     return QObject::tr( "Mercator" );
41 }
42 
description() const43 QString MercatorProjection::description() const
44 {
45     return QObject::tr( "<p><b>Mercator Projection</b></p><p>Applications: popular standard map projection for navigation.</p>" );
46 }
47 
icon() const48 QIcon MercatorProjection::icon() const
49 {
50     return QIcon(QStringLiteral(":/icons/map-mercator.png"));
51 }
52 
maxValidLat() const53 qreal MercatorProjection::maxValidLat() const
54 {
55     // This is the max value where gd( lat ) is defined.
56     return +85.05113 * DEG2RAD;
57 }
58 
minValidLat() const59 qreal MercatorProjection::minValidLat() const
60 {
61     // This is the min value where gd( lat ) is defined.
62     return -85.05113 * DEG2RAD;
63 }
64 
screenCoordinates(const GeoDataCoordinates & geopoint,const ViewportParams * viewport,qreal & x,qreal & y,bool & globeHidesPoint) const65 bool MercatorProjection::screenCoordinates( const GeoDataCoordinates &geopoint,
66                                             const ViewportParams *viewport,
67                                             qreal &x, qreal &y, bool &globeHidesPoint ) const
68 {
69     globeHidesPoint = false;
70     qreal  lon;
71     qreal  originalLat;
72 
73     geopoint.geoCoordinates( lon, originalLat );
74     qreal const lat = qBound(minLat(), originalLat, maxLat());
75     const bool isLatValid = lat == originalLat;
76 
77     // Convenience variables
78     int  radius = viewport->radius();
79     qreal  width  = (qreal)(viewport->width());
80     qreal  height = (qreal)(viewport->height());
81 
82     qreal  rad2Pixel = 2 * radius / M_PI;
83 
84     const qreal centerLon = viewport->centerLongitude();
85     const qreal centerLat = viewport->centerLatitude();
86     if (centerLat != m_lastCenterLat) {
87         m_lastCenterLatInv = gdInv(centerLat);
88         m_lastCenterLat = centerLat;
89     }
90 
91     // Let (x, y) be the position on the screen of the placemark..
92     x = ( width  / 2 + rad2Pixel * ( lon - centerLon ) );
93     y = ( height / 2 - rad2Pixel * ( gdInv( lat ) - m_lastCenterLatInv ) );
94 
95     // Return true if the calculated point is inside the screen area,
96     // otherwise return false.
97     return isLatValid && ( ( 0 <= y && y < height )
98                   && ( ( 0 <= x && x < width )
99                   || ( 0 <= x - 4 * radius && x - 4 * radius < width )
100                   || ( 0 <= x + 4 * radius && x + 4 * radius < width ) ) );
101 }
102 
screenCoordinates(const GeoDataCoordinates & coordinates,const ViewportParams * viewport,qreal * x,qreal & y,int & pointRepeatNum,const QSizeF & size,bool & globeHidesPoint) const103 bool MercatorProjection::screenCoordinates( const GeoDataCoordinates &coordinates,
104                                             const ViewportParams *viewport,
105                                             qreal *x, qreal &y, int &pointRepeatNum,
106                                             const QSizeF& size,
107                                             bool &globeHidesPoint ) const
108 {
109     pointRepeatNum = 0;
110     // On flat projections the observer's view onto the point won't be
111     // obscured by the target planet itself.
112     globeHidesPoint = false;
113 
114     // Convenience variables
115     int  radius = viewport->radius();
116     qreal  width  = (qreal)(viewport->width());
117     qreal  height = (qreal)(viewport->height());
118 
119     // Let (itX, y) be the first guess for one possible position on screen..
120     qreal itX;
121     bool visible = screenCoordinates( coordinates, viewport, itX, y);
122 
123     // Make sure that the requested point is within the visible y range:
124     if ( 0 <= y + size.height() / 2.0 && y < height + size.height() / 2.0 ) {
125         // For the repetition case the same geopoint gets displayed on
126         // the map many times.across the longitude.
127 
128         int xRepeatDistance = 4 * radius;
129 
130         // Finding the leftmost positive x value
131         if ( itX + size.width() / 2.0 >= xRepeatDistance ) {
132             const int repeatNum = (int)( ( itX + size.width() / 2.0 ) / xRepeatDistance );
133             itX = itX - repeatNum * xRepeatDistance;
134         }
135         if ( itX + size.width() / 2.0 < 0 ) {
136             itX += xRepeatDistance;
137         }
138         // the requested point is out of the visible x range:
139         if ( itX > width + size.width() / 2.0 ) {
140             return false;
141         }
142 
143         // Now iterate through all visible x screen coordinates for the point
144         // from left to right.
145         int itNum = 0;
146         while ( itX - size.width() / 2.0 < width ) {
147             *x = itX;
148             ++x;
149             ++itNum;
150             itX += xRepeatDistance;
151         }
152 
153         pointRepeatNum = itNum;
154 
155         return visible;
156     }
157 
158     // the requested point is out of the visible y range:
159     return false;
160 }
161 
162 
geoCoordinates(const int x,const int y,const ViewportParams * viewport,qreal & lon,qreal & lat,GeoDataCoordinates::Unit unit) const163 bool MercatorProjection::geoCoordinates( const int x, const int y,
164                                          const ViewportParams *viewport,
165                                          qreal& lon, qreal& lat,
166                                          GeoDataCoordinates::Unit unit ) const
167 {
168     const int radius = viewport->radius();
169     Q_ASSERT( radius > 0 );
170 
171     // Calculate translation of center point
172     const qreal centerLon = viewport->centerLongitude();
173     const qreal centerLat = viewport->centerLatitude();
174 
175     // Calculate how many pixel are being represented per radians.
176     const float rad2Pixel = (qreal)( 2 * radius )/M_PI;
177     const qreal pixel2Rad = M_PI / (2 * radius);
178 
179     {
180         const int halfImageWidth = viewport->width() / 2;
181         const int xPixels = x - halfImageWidth;
182         lon = xPixels * pixel2Rad + centerLon;
183 
184         while ( lon > M_PI )  lon -= 2*M_PI;
185         while ( lon < -M_PI ) lon += 2*M_PI;
186 
187         if ( unit == GeoDataCoordinates::Degree ) {
188             lon *= RAD2DEG;
189         }
190     }
191 
192     {
193         const int halfImageHeight    = viewport->height() / 2;
194         const int yCenterOffset = (int)( asinh( tan( centerLat ) ) * rad2Pixel  );
195         const int yTop          = halfImageHeight - 2 * radius + yCenterOffset;
196         const int yBottom       = yTop + 4 * radius;
197         if ( y >= yTop && y < yBottom ) {
198             lat = gd( ( ( halfImageHeight + yCenterOffset ) - y)
199                               * pixel2Rad );
200 
201             if ( unit == GeoDataCoordinates::Degree ) {
202                 lat *= RAD2DEG;
203             }
204 
205             return true; // lat successfully calculated
206         }
207     }
208 
209     return false; // lat unchanged
210 }
211 
212 
latLonAltBox(const QRect & screenRect,const ViewportParams * viewport) const213 GeoDataLatLonAltBox MercatorProjection::latLonAltBox( const QRect& screenRect,
214                                                       const ViewportParams *viewport ) const
215 {
216     qreal west;
217     qreal north = 85*DEG2RAD;
218     geoCoordinates( screenRect.left(), screenRect.top(), viewport, west, north, GeoDataCoordinates::Radian );
219 
220     qreal east;
221     qreal south = -85*DEG2RAD;
222     geoCoordinates( screenRect.right(), screenRect.bottom(), viewport, east, south, GeoDataCoordinates::Radian );
223 
224     // For the case where the whole viewport gets covered there is a
225     // pretty dirty and generic detection algorithm:
226     GeoDataLatLonAltBox latLonAltBox;
227     latLonAltBox.setNorth( north, GeoDataCoordinates::Radian );
228     latLonAltBox.setSouth( south, GeoDataCoordinates::Radian );
229     latLonAltBox.setWest( west, GeoDataCoordinates::Radian );
230     latLonAltBox.setEast( east, GeoDataCoordinates::Radian );
231     latLonAltBox.setMinAltitude(      -100000000.0 );
232     latLonAltBox.setMaxAltitude( 100000000000000.0 );
233 
234     // The remaining algorithm should be pretty generic for all kinds of
235     // flat projections:
236 
237     // If the whole globe is visible we can easily calculate
238     // analytically the lon-/lat- range.
239     // qreal pitch = GeoDataPoint::normalizeLat( viewport->planetAxis().pitch() );
240 
241     int xRepeatDistance = 4 * viewport->radius();
242     if ( viewport->width() >= xRepeatDistance ) {
243         latLonAltBox.setWest( -M_PI );
244         latLonAltBox.setEast( +M_PI );
245     }
246 
247     return latLonAltBox;
248 }
249 
250 
mapCoversViewport(const ViewportParams * viewport) const251 bool MercatorProjection::mapCoversViewport( const ViewportParams *viewport ) const
252 {
253     int           radius = viewport->radius();
254     int           height = viewport->height();
255 
256     // Calculate translation of center point
257     const qreal centerLat = viewport->centerLatitude();
258 
259     // Calculate how many pixel are being represented per radians.
260     const float rad2Pixel = (float)( 2 * radius )/M_PI;
261 
262     int yCenterOffset = (int)( asinh( tan( centerLat ) ) * rad2Pixel  );
263     int yTop          = height / 2 - 2 * radius + yCenterOffset;
264     int yBottom       = yTop + 4 * radius;
265 
266     return !(yTop >= 0 || yBottom < height);
267 }
268