File indexing completed on 2024-05-05 03:49:52
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org> 0004 // SPDX-FileCopyrightText: 2007-2012 Torsten Rahn <rahn@kde.org> 0005 // 0006 0007 0008 // Local 0009 #include "MercatorProjection.h" 0010 0011 #include "MarbleDebug.h" 0012 0013 // Marble 0014 #include "ViewportParams.h" 0015 #include "GeoDataLatLonAltBox.h" 0016 0017 #include "MathHelper.h" 0018 #include "GeoDataPoint.h" 0019 #include "MarbleMath.h" 0020 0021 #include <QIcon> 0022 0023 using namespace Marble; 0024 0025 MercatorProjection::MercatorProjection() 0026 : CylindricalProjection(), 0027 m_lastCenterLat(200.0), 0028 m_lastCenterLatInv(0.0) 0029 { 0030 setMinLat( minValidLat() ); 0031 setMaxLat( maxValidLat() ); 0032 } 0033 0034 MercatorProjection::~MercatorProjection() 0035 { 0036 } 0037 0038 QString MercatorProjection::name() const 0039 { 0040 return QObject::tr( "Mercator" ); 0041 } 0042 0043 QString MercatorProjection::description() const 0044 { 0045 return QObject::tr( "<p><b>Mercator Projection</b></p><p>Applications: popular standard map projection for navigation.</p>" ); 0046 } 0047 0048 QIcon MercatorProjection::icon() const 0049 { 0050 return QIcon(QStringLiteral(":/icons/map-mercator.png")); 0051 } 0052 0053 qreal MercatorProjection::maxValidLat() const 0054 { 0055 // This is the max value where gd( lat ) is defined. 0056 return +85.05113 * DEG2RAD; 0057 } 0058 0059 qreal MercatorProjection::minValidLat() const 0060 { 0061 // This is the min value where gd( lat ) is defined. 0062 return -85.05113 * DEG2RAD; 0063 } 0064 0065 bool MercatorProjection::screenCoordinates( const GeoDataCoordinates &geopoint, 0066 const ViewportParams *viewport, 0067 qreal &x, qreal &y, bool &globeHidesPoint ) const 0068 { 0069 globeHidesPoint = false; 0070 qreal lon; 0071 qreal originalLat; 0072 0073 geopoint.geoCoordinates( lon, originalLat ); 0074 qreal const lat = qBound(minLat(), originalLat, maxLat()); 0075 const bool isLatValid = lat == originalLat; 0076 0077 // Convenience variables 0078 int radius = viewport->radius(); 0079 qreal width = (qreal)(viewport->width()); 0080 qreal height = (qreal)(viewport->height()); 0081 0082 qreal rad2Pixel = 2 * radius / M_PI; 0083 0084 const qreal centerLon = viewport->centerLongitude(); 0085 const qreal centerLat = viewport->centerLatitude(); 0086 if (centerLat != m_lastCenterLat) { 0087 m_lastCenterLatInv = gdInv(centerLat); 0088 m_lastCenterLat = centerLat; 0089 } 0090 0091 // Let (x, y) be the position on the screen of the placemark.. 0092 x = ( width / 2 + rad2Pixel * ( lon - centerLon ) ); 0093 y = ( height / 2 - rad2Pixel * ( gdInv( lat ) - m_lastCenterLatInv ) ); 0094 0095 // Return true if the calculated point is inside the screen area, 0096 // otherwise return false. 0097 return isLatValid && ( ( 0 <= y && y < height ) 0098 && ( ( 0 <= x && x < width ) 0099 || ( 0 <= x - 4 * radius && x - 4 * radius < width ) 0100 || ( 0 <= x + 4 * radius && x + 4 * radius < width ) ) ); 0101 } 0102 0103 bool MercatorProjection::screenCoordinates( const GeoDataCoordinates &coordinates, 0104 const ViewportParams *viewport, 0105 qreal *x, qreal &y, int &pointRepeatNum, 0106 const QSizeF& size, 0107 bool &globeHidesPoint ) const 0108 { 0109 pointRepeatNum = 0; 0110 // On flat projections the observer's view onto the point won't be 0111 // obscured by the target planet itself. 0112 globeHidesPoint = false; 0113 0114 // Convenience variables 0115 int radius = viewport->radius(); 0116 qreal width = (qreal)(viewport->width()); 0117 qreal height = (qreal)(viewport->height()); 0118 0119 // Let (itX, y) be the first guess for one possible position on screen.. 0120 qreal itX; 0121 bool visible = screenCoordinates( coordinates, viewport, itX, y); 0122 0123 // Make sure that the requested point is within the visible y range: 0124 if ( 0 <= y + size.height() / 2.0 && y < height + size.height() / 2.0 ) { 0125 // For the repetition case the same geopoint gets displayed on 0126 // the map many times.across the longitude. 0127 0128 int xRepeatDistance = 4 * radius; 0129 0130 // Finding the leftmost positive x value 0131 if ( itX + size.width() / 2.0 >= xRepeatDistance ) { 0132 const int repeatNum = (int)( ( itX + size.width() / 2.0 ) / xRepeatDistance ); 0133 itX = itX - repeatNum * xRepeatDistance; 0134 } 0135 if ( itX + size.width() / 2.0 < 0 ) { 0136 itX += xRepeatDistance; 0137 } 0138 // the requested point is out of the visible x range: 0139 if ( itX > width + size.width() / 2.0 ) { 0140 return false; 0141 } 0142 0143 // Now iterate through all visible x screen coordinates for the point 0144 // from left to right. 0145 int itNum = 0; 0146 while ( itX - size.width() / 2.0 < width ) { 0147 *x = itX; 0148 ++x; 0149 ++itNum; 0150 itX += xRepeatDistance; 0151 } 0152 0153 pointRepeatNum = itNum; 0154 0155 return visible; 0156 } 0157 0158 // the requested point is out of the visible y range: 0159 return false; 0160 } 0161 0162 0163 bool MercatorProjection::geoCoordinates( const int x, const int y, 0164 const ViewportParams *viewport, 0165 qreal& lon, qreal& lat, 0166 GeoDataCoordinates::Unit unit ) const 0167 { 0168 const int radius = viewport->radius(); 0169 Q_ASSERT( radius > 0 ); 0170 0171 // Calculate translation of center point 0172 const qreal centerLon = viewport->centerLongitude(); 0173 const qreal centerLat = viewport->centerLatitude(); 0174 0175 // Calculate how many pixel are being represented per radians. 0176 const float rad2Pixel = (qreal)( 2 * radius )/M_PI; 0177 const qreal pixel2Rad = M_PI / (2 * radius); 0178 0179 { 0180 const int halfImageWidth = viewport->width() / 2; 0181 const int xPixels = x - halfImageWidth; 0182 lon = xPixels * pixel2Rad + centerLon; 0183 0184 while ( lon > M_PI ) lon -= 2*M_PI; 0185 while ( lon < -M_PI ) lon += 2*M_PI; 0186 0187 if ( unit == GeoDataCoordinates::Degree ) { 0188 lon *= RAD2DEG; 0189 } 0190 } 0191 0192 { 0193 const int halfImageHeight = viewport->height() / 2; 0194 const int yCenterOffset = (int)( asinh( tan( centerLat ) ) * rad2Pixel ); 0195 const int yTop = halfImageHeight - 2 * radius + yCenterOffset; 0196 const int yBottom = yTop + 4 * radius; 0197 if ( y >= yTop && y < yBottom ) { 0198 lat = gd( ( ( halfImageHeight + yCenterOffset ) - y) 0199 * pixel2Rad ); 0200 0201 if ( unit == GeoDataCoordinates::Degree ) { 0202 lat *= RAD2DEG; 0203 } 0204 0205 return true; // lat successfully calculated 0206 } 0207 } 0208 0209 return false; // lat unchanged 0210 } 0211 0212 0213 GeoDataLatLonAltBox MercatorProjection::latLonAltBox( const QRect& screenRect, 0214 const ViewportParams *viewport ) const 0215 { 0216 qreal west; 0217 qreal north = 85*DEG2RAD; 0218 geoCoordinates( screenRect.left(), screenRect.top(), viewport, west, north, GeoDataCoordinates::Radian ); 0219 0220 qreal east; 0221 qreal south = -85*DEG2RAD; 0222 geoCoordinates( screenRect.right(), screenRect.bottom(), viewport, east, south, GeoDataCoordinates::Radian ); 0223 0224 // For the case where the whole viewport gets covered there is a 0225 // pretty dirty and generic detection algorithm: 0226 GeoDataLatLonAltBox latLonAltBox; 0227 latLonAltBox.setNorth( north, GeoDataCoordinates::Radian ); 0228 latLonAltBox.setSouth( south, GeoDataCoordinates::Radian ); 0229 latLonAltBox.setWest( west, GeoDataCoordinates::Radian ); 0230 latLonAltBox.setEast( east, GeoDataCoordinates::Radian ); 0231 latLonAltBox.setMinAltitude( -100000000.0 ); 0232 latLonAltBox.setMaxAltitude( 100000000000000.0 ); 0233 0234 // The remaining algorithm should be pretty generic for all kinds of 0235 // flat projections: 0236 0237 // If the whole globe is visible we can easily calculate 0238 // analytically the lon-/lat- range. 0239 // qreal pitch = GeoDataPoint::normalizeLat( viewport->planetAxis().pitch() ); 0240 0241 int xRepeatDistance = 4 * viewport->radius(); 0242 if ( viewport->width() >= xRepeatDistance ) { 0243 latLonAltBox.setWest( -M_PI ); 0244 latLonAltBox.setEast( +M_PI ); 0245 } 0246 0247 return latLonAltBox; 0248 } 0249 0250 0251 bool MercatorProjection::mapCoversViewport( const ViewportParams *viewport ) const 0252 { 0253 int radius = viewport->radius(); 0254 int height = viewport->height(); 0255 0256 // Calculate translation of center point 0257 const qreal centerLat = viewport->centerLatitude(); 0258 0259 // Calculate how many pixel are being represented per radians. 0260 const float rad2Pixel = (float)( 2 * radius )/M_PI; 0261 0262 int yCenterOffset = (int)( asinh( tan( centerLat ) ) * rad2Pixel ); 0263 int yTop = height / 2 - 2 * radius + yCenterOffset; 0264 int yBottom = yTop + 4 * radius; 0265 0266 return !(yTop >= 0 || yBottom < height); 0267 }