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 }