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 }