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