File indexing completed on 2024-04-14 03:48:11

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
0004 // SPDX-FileCopyrightText: 2008 Jens-Michael Hoffmann <jensmh@gmx.de>
0005 // SPDX-FileCopyrightText: 2010-2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0006 //
0007 
0008 
0009 #include "ViewportParams.h"
0010 
0011 #include <QRect>
0012 
0013 #include <QPainterPath>
0014 #include <QRegion>
0015 
0016 #include "MarbleDebug.h"
0017 #include "GeoDataLatLonAltBox.h"
0018 #include "SphericalProjection.h"
0019 #include "EquirectProjection.h"
0020 #include "MercatorProjection.h"
0021 #include "GnomonicProjection.h"
0022 #include "LambertAzimuthalProjection.h"
0023 #include "AzimuthalEquidistantProjection.h"
0024 #include "StereographicProjection.h"
0025 #include "VerticalPerspectiveProjection.h"
0026 
0027 
0028 namespace Marble
0029 {
0030 
0031 class ViewportParamsPrivate
0032 {
0033 public:
0034     ViewportParamsPrivate( Projection projection,
0035                            qreal centerLongitude, qreal centerLatitude,
0036                            int radius,
0037                            const QSize &size );
0038 
0039     static const AbstractProjection *abstractProjection( Projection projection );
0040 
0041     // These two go together.  m_currentProjection points to one of
0042     // the static Projection classes at the bottom.
0043     Projection           m_projection;
0044     const AbstractProjection *m_currentProjection;
0045 
0046     // Parameters that determine the painting
0047     qreal                m_centerLongitude;
0048     qreal                m_centerLatitude;
0049     qreal                m_heading;
0050     Quaternion           m_planetAxis;   // Position, coded in a quaternion
0051     matrix               m_planetAxisMatrix;
0052     int                  m_radius;       // Zoom level (pixels / globe radius)
0053     qreal                m_angularResolution;
0054 
0055     QSize                m_size;         // width, height
0056 
0057 
0058     bool                 m_dirtyBox;
0059     GeoDataLatLonAltBox  m_viewLatLonAltBox;
0060 
0061     static const SphericalProjection  s_sphericalProjection;
0062     static const EquirectProjection   s_equirectProjection;
0063     static const MercatorProjection   s_mercatorProjection;
0064     static const GnomonicProjection   s_gnomonicProjection;
0065     static const StereographicProjection   s_stereographicProjection;
0066     static const LambertAzimuthalProjection   s_lambertAzimuthalProjection;
0067     static const AzimuthalEquidistantProjection   s_azimuthalEquidistantProjection;
0068     static const VerticalPerspectiveProjection   s_verticalPerspectiveProjection;
0069 
0070     GeoDataCoordinates   m_focusPoint;
0071 };
0072 
0073 const SphericalProjection  ViewportParamsPrivate::s_sphericalProjection;
0074 const EquirectProjection   ViewportParamsPrivate::s_equirectProjection;
0075 const MercatorProjection   ViewportParamsPrivate::s_mercatorProjection;
0076 const GnomonicProjection   ViewportParamsPrivate::s_gnomonicProjection;
0077 const StereographicProjection   ViewportParamsPrivate::s_stereographicProjection;
0078 const LambertAzimuthalProjection   ViewportParamsPrivate::s_lambertAzimuthalProjection;
0079 const AzimuthalEquidistantProjection   ViewportParamsPrivate::s_azimuthalEquidistantProjection;
0080 const VerticalPerspectiveProjection   ViewportParamsPrivate::s_verticalPerspectiveProjection;
0081 
0082 ViewportParamsPrivate::ViewportParamsPrivate( Projection projection,
0083                                               qreal centerLongitude, qreal centerLatitude,
0084                                               int radius,
0085                                               const QSize &size )
0086     : m_projection( projection ),
0087       m_currentProjection( abstractProjection( projection ) ),
0088       m_centerLongitude( centerLongitude ),
0089       m_centerLatitude( centerLatitude ),
0090       m_heading( 0 ),
0091       m_planetAxis(),
0092       m_planetAxisMatrix(),
0093       m_radius( radius ),
0094       m_angularResolution(4.0 / abs(m_radius)),
0095       m_size( size ),
0096       m_dirtyBox( true ),
0097       m_viewLatLonAltBox()
0098 {
0099 }
0100 
0101 const AbstractProjection *ViewportParamsPrivate::abstractProjection(Projection projection)
0102 {
0103     switch ( projection ) {
0104     case Spherical:
0105         return &s_sphericalProjection;
0106     case Equirectangular:
0107         return &s_equirectProjection;
0108     case Mercator:
0109         return &s_mercatorProjection;
0110     case Gnomonic:
0111         return &s_gnomonicProjection;
0112     case Stereographic:
0113         return &s_stereographicProjection;
0114     case LambertAzimuthal:
0115         return &s_lambertAzimuthalProjection;
0116     case AzimuthalEquidistant:
0117         return &s_azimuthalEquidistantProjection;
0118     case VerticalPerspective:
0119         return &s_verticalPerspectiveProjection;
0120     }
0121 
0122     return nullptr;
0123 }
0124 
0125 
0126 ViewportParams::ViewportParams()
0127     : d( new ViewportParamsPrivate( Spherical, 0, 0, 2000, QSize( 100, 100 ) ) )
0128 {
0129     centerOn( d->m_centerLongitude, d->m_centerLatitude );
0130 }
0131 
0132 ViewportParams::ViewportParams( Projection projection,
0133                                 qreal centerLongitude, qreal centerLatitude,
0134                                 int radius,
0135                                 const QSize &size )
0136     : d( new ViewportParamsPrivate( projection, centerLongitude, centerLatitude, radius, size ) )
0137 {
0138     centerOn( d->m_centerLongitude, d->m_centerLatitude );
0139 }
0140 
0141 ViewportParams::~ViewportParams()
0142 {
0143     delete d;
0144 }
0145 
0146 
0147 // ================================================================
0148 //                    Getters and setters
0149 
0150 
0151 Projection ViewportParams::projection() const
0152 {
0153     return d->m_projection;
0154 }
0155 
0156 const AbstractProjection *ViewportParams::currentProjection() const
0157 {
0158     return d->m_currentProjection;
0159 }
0160 
0161 void ViewportParams::setProjection(Projection newProjection)
0162 {
0163     d->m_projection = newProjection;
0164     d->m_currentProjection = ViewportParamsPrivate::abstractProjection( newProjection );
0165 
0166     // We now need to reset the planetAxis to make sure
0167     // that it's a valid axis orientation!
0168     // So this line is important (although it might look odd) ! :
0169     centerOn( d->m_centerLongitude, d->m_centerLatitude );
0170 }
0171 
0172 int ViewportParams::polarity() const
0173 {
0174     // For mercator this just gives the extreme latitudes
0175     // instead of the actual poles but it works fine as well:
0176     GeoDataCoordinates northPole( 0.0, +currentProjection()->maxLat() );
0177     GeoDataCoordinates southPole( 0.0, -currentProjection()->maxLat() );
0178 
0179     bool globeHidesN, globeHidesS;
0180     qreal x;
0181     qreal yN, yS;
0182 
0183     currentProjection()->screenCoordinates( northPole, this,
0184                                           x, yN, globeHidesN );
0185     currentProjection()->screenCoordinates( southPole, this,
0186                                           x, yS, globeHidesS );
0187 
0188     int polarity = 0;
0189 
0190     // case of the flat map:
0191     if ( !globeHidesN && !globeHidesS ) {
0192         if ( yN < yS ) {
0193             polarity = +1;
0194         }
0195         if ( yS < yN ) {
0196             polarity = -1;
0197         }
0198     }
0199     else {
0200         if ( !globeHidesN && yN < height() / 2 ) {
0201             polarity = +1;
0202         }
0203         if ( !globeHidesN && yN > height() / 2 ) {
0204             polarity = -1;
0205         }
0206         if ( !globeHidesS && yS > height() / 2 ) {
0207             polarity = +1;
0208         }
0209         if ( !globeHidesS && yS < height() / 2 ) {
0210             polarity = -1;
0211         }
0212     }
0213 
0214     return polarity;
0215 }
0216 
0217 int ViewportParams::radius() const
0218 {
0219     return d->m_radius;
0220 }
0221 
0222 void ViewportParams::setRadius(int newRadius)
0223 {
0224     if ( newRadius > 0 ) {
0225         d->m_dirtyBox = true;
0226 
0227         d->m_radius = newRadius;
0228         d->m_angularResolution = 4.0 / d->m_radius;
0229     }
0230 }
0231 
0232 void ViewportParams::centerOn( qreal lon, qreal lat )
0233 {
0234     if ( !d->m_currentProjection->traversablePoles() ) {
0235         if ( lat > d->m_currentProjection->maxLat() )
0236             lat = d->m_currentProjection->maxLat();
0237 
0238         if ( lat < d->m_currentProjection->minLat() )
0239             lat = d->m_currentProjection->minLat();
0240     } else {
0241         while ( lat > M_PI )
0242             lat -= 2 * M_PI;
0243         while ( lat < -M_PI )
0244             lat += 2 * M_PI;
0245     }
0246 
0247     while ( lon > M_PI )
0248         lon -= 2 * M_PI;
0249     while ( lon < -M_PI )
0250         lon += 2 * M_PI;
0251 
0252     d->m_centerLongitude = lon;
0253     d->m_centerLatitude = lat;
0254 
0255     const Quaternion roll = Quaternion::fromEuler( 0, 0, d->m_heading );
0256     const Quaternion quat = Quaternion::fromEuler( -lat, lon, 0.0 );
0257 
0258     d->m_planetAxis = quat * roll;
0259     d->m_planetAxis.normalize();
0260 
0261     d->m_dirtyBox = true;
0262     d->m_planetAxis.inverse().toMatrix( d->m_planetAxisMatrix );
0263     d->m_planetAxis.normalize();
0264 }
0265 
0266 void ViewportParams::setHeading( qreal heading )
0267 {
0268     d->m_heading = heading;
0269 
0270     const Quaternion roll = Quaternion::fromEuler( 0, 0, heading );
0271 
0272     const qreal centerLat = centerLatitude();
0273     const qreal centerLon = centerLongitude();
0274 
0275     const Quaternion quat = Quaternion::fromEuler( -centerLat, centerLon, 0 );
0276 
0277     d->m_planetAxis = quat * roll;
0278     d->m_planetAxis.normalize();
0279 
0280     d->m_dirtyBox = true;
0281     d->m_planetAxis.inverse().toMatrix( d->m_planetAxisMatrix );
0282     d->m_planetAxis.normalize();
0283 }
0284 
0285 qreal ViewportParams::heading() const
0286 {
0287     return d->m_heading;
0288 }
0289 
0290 Quaternion ViewportParams::planetAxis() const
0291 {
0292     return d->m_planetAxis;
0293 }
0294 
0295 const matrix &ViewportParams::planetAxisMatrix() const
0296 {
0297     return d->m_planetAxisMatrix;
0298 }
0299 
0300 int ViewportParams::width()  const
0301 {
0302     return d->m_size.width();
0303 }
0304 
0305 int ViewportParams::height() const
0306 {
0307     return d->m_size.height();
0308 }
0309 
0310 QSize ViewportParams::size() const
0311 {
0312     return d->m_size;
0313 }
0314 
0315 
0316 void ViewportParams::setWidth(int newWidth)
0317 {
0318     setSize( QSize( newWidth, height() ) );
0319 }
0320 
0321 void ViewportParams::setHeight(int newHeight)
0322 {
0323     setSize( QSize( width(), newHeight ) );
0324 }
0325 
0326 void ViewportParams::setSize(const QSize& newSize)
0327 {
0328     if ( newSize == d->m_size )
0329         return;
0330 
0331     d->m_dirtyBox = true;
0332 
0333     d->m_size = newSize;
0334 }
0335 
0336 // ================================================================
0337 //                        Other functions
0338 
0339 qreal ViewportParams::centerLongitude() const
0340 {
0341     return d->m_centerLongitude;
0342 }
0343 
0344 qreal ViewportParams::centerLatitude() const
0345 {
0346     return d->m_centerLatitude;
0347 }
0348 
0349 const GeoDataLatLonAltBox& ViewportParams::viewLatLonAltBox() const
0350 {
0351     if (d->m_dirtyBox) {
0352         d->m_viewLatLonAltBox = d->m_currentProjection->latLonAltBox( QRect( QPoint( 0, 0 ), 
0353                         d->m_size ),
0354                         this );
0355         d->m_dirtyBox = false;
0356     }
0357 
0358     return d->m_viewLatLonAltBox;
0359 }
0360 
0361 GeoDataLatLonAltBox ViewportParams::latLonAltBox( const QRect &screenRect ) const
0362 {
0363     return d->m_currentProjection->latLonAltBox( screenRect, this );
0364 }
0365 
0366 qreal ViewportParams::angularResolution() const
0367 {
0368     // We essentially divide the diameter by 180 deg and
0369     // take half of the result as a guess for the angle per pixel resolution. 
0370     // d->m_angularResolution = 0.25 * M_PI / fabs( (qreal)(d->m_radius);
0371     return d->m_angularResolution;
0372 }
0373 
0374 bool ViewportParams::resolves ( const GeoDataLatLonBox &latLonBox, qreal pixel ) const
0375 {
0376     return latLonBox.width() + latLonBox.height() > pixel * d->m_angularResolution;
0377 }
0378 
0379 
0380 bool ViewportParams::resolves ( const GeoDataLatLonAltBox &latLonAltBox, qreal pixel, qreal altitude ) const
0381 {
0382     return    latLonAltBox.width() + latLonAltBox.height() > pixel * d->m_angularResolution
0383            || latLonAltBox.maxAltitude() - latLonAltBox.minAltitude() > altitude;
0384 }
0385 
0386 bool ViewportParams::resolves ( const GeoDataCoordinates &coord1, 
0387                                 const GeoDataCoordinates &coord2 ) const
0388 {
0389     qreal lon1, lat1;
0390     coord1.geoCoordinates( lon1, lat1 );
0391 
0392     qreal lon2, lat2;
0393     coord2.geoCoordinates( lon2, lat2 );
0394 
0395     // We take the manhattan length as an approximation for the distance
0396     return ( fabs( lon2 - lon1 ) + fabs( lat2 - lat1 ) > d->m_angularResolution );
0397 }
0398 
0399 
0400 bool ViewportParams::screenCoordinates( const qreal lon, const qreal lat,
0401                         qreal &x, qreal &y ) const
0402 {
0403     return d->m_currentProjection->screenCoordinates( lon, lat, this, x, y );
0404 }
0405 
0406 bool ViewportParams::screenCoordinates( const GeoDataCoordinates &geopoint,
0407                         qreal &x, qreal &y,
0408                         bool &globeHidesPoint ) const
0409 {
0410     return d->m_currentProjection->screenCoordinates( geopoint, this, x, y, globeHidesPoint );
0411 }
0412 
0413 bool ViewportParams::screenCoordinates( const GeoDataCoordinates &geopoint,
0414                         qreal &x, qreal &y ) const
0415 {
0416     return d->m_currentProjection->screenCoordinates( geopoint, this, x, y );
0417 }
0418 
0419 bool ViewportParams::screenCoordinates( const GeoDataCoordinates &coordinates,
0420                         qreal *x, qreal &y, int &pointRepeatNum,
0421                         const QSizeF& size,
0422                         bool &globeHidesPoint ) const
0423 {
0424     return d->m_currentProjection->screenCoordinates( coordinates, this, x, y, pointRepeatNum, size, globeHidesPoint );
0425 }
0426 
0427 
0428 bool ViewportParams::screenCoordinates( const GeoDataLineString &lineString,
0429                         QVector<QPolygonF*> &polygons ) const
0430 {
0431     return d->m_currentProjection->screenCoordinates( lineString, this, polygons );
0432 }
0433 
0434 bool ViewportParams::geoCoordinates( const int x, const int y,
0435                      qreal &lon, qreal &lat,
0436                      GeoDataCoordinates::Unit unit ) const
0437 {
0438     return d->m_currentProjection->geoCoordinates( x, y, this, lon, lat, unit );
0439 }
0440 
0441 bool  ViewportParams::mapCoversViewport() const
0442 {
0443     return d->m_currentProjection->mapCoversViewport( this );
0444 }
0445 
0446 QPainterPath ViewportParams::mapShape() const
0447 {
0448     return d->m_currentProjection->mapShape( this );
0449 }
0450 
0451 QRegion ViewportParams::mapRegion() const
0452 {
0453     return d->m_currentProjection->mapRegion( this );
0454 }
0455 
0456 GeoDataCoordinates ViewportParams::focusPoint() const
0457 {
0458     if (d->m_focusPoint.isValid()) {
0459         return d->m_focusPoint;
0460     }
0461     else {
0462        const qreal lon = d->m_centerLongitude;
0463        const qreal lat = d->m_centerLatitude;
0464 
0465        return GeoDataCoordinates(lon, lat, 0.0, GeoDataCoordinates::Radian);
0466     }
0467 
0468 }
0469 
0470 void ViewportParams::setFocusPoint(const GeoDataCoordinates &focusPoint)
0471 {
0472     d->m_focusPoint = focusPoint;
0473 }
0474 
0475 void ViewportParams::resetFocusPoint()
0476 {
0477     d->m_focusPoint = GeoDataCoordinates();
0478 }
0479 
0480 }