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