File indexing completed on 2024-05-05 03:49:52

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