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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2014 Gábor Péterffy <peterffy95@gmail.org>
0004 //
0005 
0006 // Local
0007 #include "AzimuthalProjection.h"
0008 #include "AzimuthalProjection_p.h"
0009 #include "AbstractProjection_p.h"
0010 
0011 // Marble
0012 #include "GeoDataLinearRing.h"
0013 #include "GeoDataLineString.h"
0014 #include "GeoDataCoordinates.h"
0015 #include "GeoDataLatLonAltBox.h"
0016 #include "ViewportParams.h"
0017 
0018 #include <QPainterPath>
0019 
0020 
0021 namespace Marble {
0022 
0023 bool AzimuthalProjection::isClippedToSphere() const
0024 {
0025     return true;
0026 }
0027 
0028 qreal AzimuthalProjection::clippingRadius() const
0029 {
0030     return 1;
0031 }
0032 
0033 bool AzimuthalProjection::screenCoordinates( const GeoDataLineString &lineString,
0034                                                   const ViewportParams *viewport,
0035                                                   QVector<QPolygonF *> &polygons ) const
0036 {
0037 
0038     Q_D( const AzimuthalProjection );
0039     // Compare bounding box size of the line string with the angularResolution
0040     // Immediately return if the latLonAltBox is smaller.
0041     if ( !viewport->resolves( lineString.latLonAltBox() ) ) {
0042 //      mDebug() << "Object too small to be resolved";
0043         return false;
0044     }
0045 
0046     d->lineStringToPolygon( lineString, viewport, polygons );
0047     return true;
0048 }
0049 
0050 bool AzimuthalProjection::mapCoversViewport( const ViewportParams *viewport ) const
0051 {
0052         qint64  radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
0053         qint64  width  = viewport->width();
0054         qint64  height = viewport->height();
0055 
0056         // This first test is a quick one that will catch all really big
0057         // radii and prevent overflow in the real test.
0058         if ( radius > width + height )
0059             return true;
0060 
0061         // This is the real test.  The 4 is because we are really
0062         // comparing to width/2 and height/2.
0063         if ( 4 * radius * radius >= width * width + height * height )
0064             return true;
0065 
0066         return false;
0067 }
0068 
0069 GeoDataLatLonAltBox AzimuthalProjection::latLonAltBox( const QRect& screenRect,
0070                                                        const ViewportParams *viewport ) const
0071 {
0072     // For the case where the whole viewport gets covered there is a
0073     // pretty dirty and generic detection algorithm:
0074     GeoDataLatLonAltBox latLonAltBox = AbstractProjection::latLonAltBox( screenRect, viewport );
0075 
0076     // If the whole globe is visible we can easily calculate
0077     // analytically the lon-/lat- range.
0078     qreal pitch = GeoDataCoordinates::normalizeLat( viewport->planetAxis().pitch() );
0079 
0080     if ( 2.0 * viewport->radius() <= viewport->height()
0081          &&  2.0 * viewport->radius() <= viewport->width() )
0082     {
0083         // Unless the planetaxis is in the screen plane the allowed longitude range
0084         // covers full -180 deg to +180 deg:
0085         if ( pitch > 0.0 && pitch < +M_PI ) {
0086             latLonAltBox.setWest(  -M_PI );
0087             latLonAltBox.setEast(  +M_PI );
0088             latLonAltBox.setNorth( +fabs( M_PI / 2.0 - fabs( pitch ) ) );
0089             latLonAltBox.setSouth( -M_PI / 2.0 );
0090         }
0091         if ( pitch < 0.0 && pitch > -M_PI ) {
0092             latLonAltBox.setWest(  -M_PI );
0093             latLonAltBox.setEast(  +M_PI );
0094             latLonAltBox.setNorth( +M_PI / 2.0 );
0095             latLonAltBox.setSouth( -fabs( M_PI / 2.0 - fabs( pitch ) ) );
0096         }
0097 
0098         // Last but not least we deal with the rare case where the
0099         // globe is fully visible and pitch = 0.0 or pitch = -M_PI or
0100         // pitch = +M_PI
0101         if ( pitch == 0.0 || pitch == -M_PI || pitch == +M_PI ) {
0102             qreal yaw = viewport->planetAxis().yaw();
0103             latLonAltBox.setWest( GeoDataCoordinates::normalizeLon( yaw - M_PI / 2.0 ) );
0104             latLonAltBox.setEast( GeoDataCoordinates::normalizeLon( yaw + M_PI / 2.0 ) );
0105             latLonAltBox.setNorth( +M_PI / 2.0 );
0106             latLonAltBox.setSouth( -M_PI / 2.0 );
0107         }
0108 
0109         return latLonAltBox;
0110     }
0111 
0112     // Now we check whether maxLat (e.g. the north pole) gets displayed
0113     // inside the viewport to get more accurate values for east and west.
0114 
0115     // We need a point on the screen at maxLat that definitely gets displayed:
0116     qreal averageLongitude = ( latLonAltBox.west() + latLonAltBox.east() ) / 2.0;
0117 
0118     GeoDataCoordinates maxLatPoint( averageLongitude, maxLat(), 0.0, GeoDataCoordinates::Radian );
0119     GeoDataCoordinates minLatPoint( averageLongitude, minLat(), 0.0, GeoDataCoordinates::Radian );
0120 
0121     qreal dummyX, dummyY; // not needed
0122     bool dummyVal;
0123 
0124     if ( screenCoordinates( maxLatPoint, viewport, dummyX, dummyY, dummyVal ) ||
0125          screenCoordinates( minLatPoint, viewport, dummyX, dummyY, dummyVal ) ) {
0126         latLonAltBox.setWest( -M_PI );
0127         latLonAltBox.setEast( +M_PI );
0128     }
0129 
0130     return latLonAltBox;
0131 }
0132 
0133 QPainterPath AzimuthalProjection::mapShape( const ViewportParams *viewport ) const
0134 {
0135     int  radius    = viewport->radius() * viewport->currentProjection()->clippingRadius();
0136     int  imgWidth  = viewport->width();
0137     int  imgHeight = viewport->height();
0138 
0139     QPainterPath fullRect;
0140     fullRect.addRect(  0 , 0 , imgWidth, imgHeight );
0141 
0142     // If the globe covers the whole image, then the projected region represents
0143     // all of the image.
0144     // Otherwise the active region has got the shape of the visible globe.
0145 
0146     if ( !viewport->mapCoversViewport() ) {
0147         QPainterPath mapShape;
0148         mapShape.addEllipse(
0149             imgWidth  / 2 - radius,
0150             imgHeight / 2 - radius,
0151             2 * radius,
0152             2 * radius );
0153         return mapShape.intersected( fullRect );
0154     }
0155 
0156     return fullRect;
0157 }
0158 
0159 AzimuthalProjection::AzimuthalProjection(AzimuthalProjectionPrivate * dd) :
0160     AbstractProjection( dd )
0161 {
0162 }
0163 
0164 AzimuthalProjection::~AzimuthalProjection()
0165 {
0166 }
0167 
0168 void AzimuthalProjectionPrivate::tessellateLineSegment( const GeoDataCoordinates &aCoords,
0169                                                 qreal ax, qreal ay,
0170                                                 const GeoDataCoordinates &bCoords,
0171                                                 qreal bx, qreal by,
0172                                                 QVector<QPolygonF*> &polygons,
0173                                                 const ViewportParams *viewport,
0174                                                 TessellationFlags f,
0175                                                 bool allowLatePolygonCut ) const
0176 {
0177     // We take the manhattan length as a distance approximation
0178     // that can be too big by a factor of sqrt(2)
0179     qreal distance = fabs((bx - ax)) + fabs((by - ay));
0180 #ifdef SAFE_DISTANCE
0181     // Interpolate additional nodes if the line segment that connects the
0182     // current or previous nodes might cross the viewport.
0183     // The latter can pretty safely be excluded for most projections if both points
0184     // are located on the same side relative to the viewport boundaries and if they are
0185     // located more than half the line segment distance away from the viewport.
0186     const qreal safeDistance = - 0.5 * distance;
0187     if (   !( bx < safeDistance && ax < safeDistance )
0188         || !( by < safeDistance && ay < safeDistance )
0189         || !( bx + safeDistance > viewport->width()
0190             && ax + safeDistance > viewport->width() )
0191         || !( by + safeDistance > viewport->height()
0192             && ay + safeDistance > viewport->height() )
0193     )
0194     {
0195 #endif
0196         int maxTessellationFactor = viewport->radius() < 20000 ? 10 : 20;
0197         int const finalTessellationPrecision = qBound(2, viewport->radius()/200, maxTessellationFactor) * tessellationPrecision;
0198 
0199         // Let the line segment follow the spherical surface
0200         // if the distance between the previous point and the current point
0201         // on screen is too big
0202 
0203         if ( distance > finalTessellationPrecision ) {
0204             const int tessellatedNodes = qMin<int>( distance / finalTessellationPrecision, maxTessellationNodes );
0205 
0206             processTessellation( aCoords, bCoords,
0207                                  tessellatedNodes,
0208                                  polygons,
0209                                  viewport,
0210                                  f,
0211                                  allowLatePolygonCut);
0212         }
0213         else {
0214             crossHorizon( bCoords, polygons, viewport, allowLatePolygonCut );
0215         }
0216 #ifdef SAFE_DISTANCE
0217     }
0218 #endif
0219 }
0220 
0221 
0222 void AzimuthalProjectionPrivate::processTessellation( const GeoDataCoordinates &previousCoords,
0223                                                     const GeoDataCoordinates &currentCoords,
0224                                                     int tessellatedNodes,
0225                                                     QVector<QPolygonF*> &polygons,
0226                                                     const ViewportParams *viewport,
0227                                                     TessellationFlags f,
0228                                                     bool allowLatePolygonCut ) const
0229 {
0230 
0231     const bool clampToGround = f.testFlag( FollowGround );
0232     const bool followLatitudeCircle = f.testFlag( RespectLatitudeCircle )
0233                                       && previousCoords.latitude() == currentCoords.latitude();
0234 
0235     // Calculate steps for tessellation: lonDiff and altDiff
0236     qreal lonDiff = 0.0;
0237     if ( followLatitudeCircle ) {
0238         const int previousSign = previousCoords.longitude() > 0 ? 1 : -1;
0239         const int currentSign = currentCoords.longitude() > 0 ? 1 : -1;
0240 
0241         lonDiff = currentCoords.longitude() - previousCoords.longitude();
0242         if ( previousSign != currentSign
0243              && fabs(previousCoords.longitude()) + fabs(currentCoords.longitude()) > M_PI ) {
0244             if ( previousSign > currentSign ) {
0245                 // going eastwards ->
0246                 lonDiff += 2 * M_PI ;
0247             } else {
0248                 // going westwards ->
0249                 lonDiff -= 2 * M_PI;
0250             }
0251         }
0252     }
0253 
0254     // Create the tessellation nodes.
0255     GeoDataCoordinates previousTessellatedCoords = previousCoords;
0256     for ( int i = 1; i <= tessellatedNodes; ++i ) {
0257         const qreal t = (qreal)(i) / (qreal)( tessellatedNodes + 1 );
0258 
0259         GeoDataCoordinates currentTessellatedCoords;
0260 
0261         if ( followLatitudeCircle ) {
0262             // To tessellate along latitude circles use the
0263             // linear interpolation of the longitude.
0264             const qreal altDiff = currentCoords.altitude() - previousCoords.altitude();
0265             const qreal altitude = altDiff * t + previousCoords.altitude();
0266             const qreal lon = lonDiff * t + previousCoords.longitude();
0267             const qreal lat = previousTessellatedCoords.latitude();
0268 
0269             currentTessellatedCoords = GeoDataCoordinates(lon, lat, altitude);
0270         }
0271         else {
0272             // To tessellate along great circles use the
0273             // normalized linear interpolation ("NLERP") for latitude and longitude.
0274             currentTessellatedCoords = previousCoords.nlerp(currentCoords, t);
0275         }
0276 
0277         if (clampToGround) {
0278             currentTessellatedCoords.setAltitude(0);
0279         }
0280 
0281         crossHorizon( currentTessellatedCoords, polygons, viewport, allowLatePolygonCut );
0282         previousTessellatedCoords = currentTessellatedCoords;
0283     }
0284 
0285     // For the clampToGround case add the "current" coordinate after adding all other nodes.
0286     GeoDataCoordinates currentModifiedCoords( currentCoords );
0287     if ( clampToGround ) {
0288         currentModifiedCoords.setAltitude( 0.0 );
0289     }
0290     crossHorizon( currentModifiedCoords, polygons, viewport, allowLatePolygonCut );
0291 }
0292 
0293 void AzimuthalProjectionPrivate::crossHorizon( const GeoDataCoordinates & bCoord,
0294                                               QVector<QPolygonF*> &polygons,
0295                                               const ViewportParams *viewport,
0296                                               bool allowLatePolygonCut
0297                                              ) const
0298 {
0299     qreal x, y;
0300     bool globeHidesPoint;
0301 
0302     Q_Q( const AbstractProjection );
0303 
0304     q->screenCoordinates( bCoord, viewport, x, y, globeHidesPoint );
0305 
0306     if( !globeHidesPoint ) {
0307         *polygons.last() << QPointF( x, y );
0308     }
0309     else {
0310         if ( allowLatePolygonCut && !polygons.last()->isEmpty() ) {
0311             QPolygonF *path = new QPolygonF;
0312             polygons.append( path );
0313         }
0314     }
0315 }
0316 
0317 bool AzimuthalProjectionPrivate::lineStringToPolygon( const GeoDataLineString &lineString,
0318                                               const ViewportParams *viewport,
0319                                               QVector<QPolygonF *> &polygons ) const
0320 {
0321     Q_Q( const AzimuthalProjection );
0322 
0323     const TessellationFlags f = lineString.tessellationFlags();
0324     bool const tessellate = lineString.tessellate();
0325     const bool noFilter = f.testFlag(PreventNodeFiltering);
0326 
0327 
0328     qreal x = 0;
0329     qreal y = 0;
0330     bool globeHidesPoint = false;
0331 
0332     qreal previousX = -1.0;
0333     qreal previousY = -1.0;
0334     bool previousGlobeHidesPoint = false;
0335 
0336     qreal horizonX = -1.0;
0337     qreal horizonY = -1.0;
0338 
0339     QPolygonF * polygon = new QPolygonF;
0340     if (!tessellate) {
0341         polygon->reserve(lineString.size());
0342     }
0343     polygons.append( polygon );
0344 
0345     GeoDataLineString::ConstIterator itCoords = lineString.constBegin();
0346     GeoDataLineString::ConstIterator itPreviousCoords = lineString.constBegin();
0347 
0348     // Some projections display the earth in a way so that there is a
0349     // foreside and a backside.
0350     // The horizon is the line (usually a circle) which separates both
0351     // sides from each other and resembles the map shape.
0352     GeoDataCoordinates horizonCoords;
0353 
0354     // A horizon pair is a pair of two subsequent horizon crossings:
0355     // The first one describes the point where a line string disappears behind the horizon.
0356     // and where horizonPair is set to true.
0357     // The second one describes the point where the line string reappears.
0358     // In this case the two points are connected and horizonPair is set to false again.
0359     bool horizonPair = false;
0360     GeoDataCoordinates horizonDisappearCoords;
0361 
0362     // If the first horizon crossing in a line string describes the appearance of
0363     // a line string then we call it a "horizon orphan" and horizonOrphan is set to true.
0364     // In this case once the last horizon crossing in the line string is reached
0365     // it needs to be connected to the orphan.
0366     bool horizonOrphan = false;
0367     GeoDataCoordinates horizonOrphanCoords;
0368 
0369     GeoDataLineString::ConstIterator itBegin = lineString.constBegin();
0370     GeoDataLineString::ConstIterator itEnd = lineString.constEnd();
0371 
0372     bool processingLastNode = false;
0373 
0374     // We use a while loop to be able to cover linestrings as well as linear rings:
0375     // Linear rings require to tessellate the path from the last node to the first node
0376     // which isn't really convenient to achieve with a for loop ...
0377 
0378     const bool isLong = lineString.size() > 10;
0379     const int maximumDetail = levelForResolution(viewport->angularResolution());
0380     // The first node of optimized linestrings has a non-zero detail value.
0381     const bool hasDetail = itBegin->detail() != 0;
0382 
0383     while ( itCoords != itEnd )
0384     {
0385         // Optimization for line strings with a big amount of nodes
0386         bool skipNode = (hasDetail ? itCoords->detail() > maximumDetail
0387                 : itCoords != itBegin && isLong && !processingLastNode &&
0388                 !viewport->resolves( *itPreviousCoords, *itCoords ) );
0389 
0390         if ( !skipNode || noFilter) {
0391 
0392             q->screenCoordinates( *itCoords, viewport, x, y, globeHidesPoint );
0393 
0394             // Initializing variables that store the values of the previous iteration
0395             if ( !processingLastNode && itCoords == itBegin ) {
0396                 previousGlobeHidesPoint = globeHidesPoint;
0397                 itPreviousCoords = itCoords;
0398                 previousX = x;
0399                 previousY = y;
0400             }
0401 
0402             // Check for the "horizon case" (which is present e.g. for the spherical projection
0403             const bool isAtHorizon = ( globeHidesPoint || previousGlobeHidesPoint ) &&
0404                                      ( globeHidesPoint !=  previousGlobeHidesPoint );
0405 
0406             if ( isAtHorizon ) {
0407                 // Handle the "horizon case"
0408                 horizonCoords = findHorizon( *itPreviousCoords, *itCoords, viewport, f );
0409 
0410                 if ( lineString.isClosed() ) {
0411                     if ( horizonPair ) {
0412                         horizonToPolygon( viewport, horizonDisappearCoords, horizonCoords, polygons.last() );
0413                         horizonPair = false;
0414                     }
0415                     else {
0416                         if ( globeHidesPoint ) {
0417                             horizonDisappearCoords = horizonCoords;
0418                             horizonPair = true;
0419                         }
0420                         else {
0421                             horizonOrphanCoords = horizonCoords;
0422                             horizonOrphan = true;
0423                         }
0424                     }
0425                 }
0426 
0427                 q->screenCoordinates( horizonCoords, viewport, horizonX, horizonY );
0428 
0429                 // If the line appears on the visible half we need
0430                 // to add an interpolated point at the horizon as the previous point.
0431                 if ( previousGlobeHidesPoint ) {
0432                     *polygons.last() << QPointF( horizonX, horizonY );
0433                 }
0434             }
0435 
0436             // This if-clause contains the section that tessellates the line
0437             // segments of a linestring. If you are about to learn how the code of
0438             // this class works you can safely ignore this section for a start.
0439 
0440             if ( lineString.tessellate() /* && ( isVisible || previousIsVisible ) */ ) {
0441 
0442                 if ( !isAtHorizon ) {
0443 
0444                     tessellateLineSegment( *itPreviousCoords, previousX, previousY,
0445                                            *itCoords, x, y,
0446                                            polygons, viewport,
0447                                            f, !lineString.isClosed() );
0448 
0449                 }
0450                 else {
0451                     // Connect the interpolated  point at the horizon with the
0452                     // current or previous point in the line.
0453                     if ( previousGlobeHidesPoint ) {
0454                         tessellateLineSegment( horizonCoords, horizonX, horizonY,
0455                                                *itCoords, x, y,
0456                                                polygons, viewport,
0457                                                f, !lineString.isClosed() );
0458                     }
0459                     else {
0460                         tessellateLineSegment( *itPreviousCoords, previousX, previousY,
0461                                                horizonCoords, horizonX, horizonY,
0462                                                polygons, viewport,
0463                                                f, !lineString.isClosed() );
0464                     }
0465                 }
0466             }
0467             else {
0468                 if ( !globeHidesPoint ) {
0469                     *polygons.last() << QPointF( x, y );
0470                 }
0471                 else {
0472                     if ( !previousGlobeHidesPoint && isAtHorizon ) {
0473                         *polygons.last() << QPointF( horizonX, horizonY );
0474                     }
0475                 }
0476             }
0477 
0478             if ( globeHidesPoint ) {
0479                 if (   !previousGlobeHidesPoint
0480                     && !lineString.isClosed()
0481                     ) {
0482                     polygons.append( new QPolygonF );
0483                 }
0484             }
0485 
0486             previousGlobeHidesPoint = globeHidesPoint;
0487             itPreviousCoords = itCoords;
0488             previousX = x;
0489             previousY = y;
0490         }
0491 
0492         // Here we modify the condition to be able to process the
0493         // first node after the last node in a LinearRing.
0494 
0495         if ( processingLastNode ) {
0496             break;
0497         }
0498         ++itCoords;
0499 
0500         if ( itCoords == itEnd  && lineString.isClosed() ) {
0501             itCoords = itBegin;
0502             processingLastNode = true;
0503         }
0504     }
0505 
0506     // In case of horizon crossings, make sure that we always get a
0507     // polygon closed correctly.
0508     if ( horizonOrphan && lineString.isClosed() ) {
0509         horizonToPolygon( viewport, horizonCoords, horizonOrphanCoords, polygons.last() );
0510     }
0511 
0512     if ( polygons.last()->size() <= 1 ){
0513         delete polygons.last();
0514         polygons.pop_back(); // Clean up "unused" empty polygon instances
0515     }
0516 
0517     return polygons.isEmpty();
0518 }
0519 
0520 void AzimuthalProjectionPrivate::horizonToPolygon( const ViewportParams *viewport,
0521                                            const GeoDataCoordinates & disappearCoords,
0522                                            const GeoDataCoordinates & reappearCoords,
0523                                            QPolygonF * polygon ) const
0524 {
0525     qreal x, y;
0526 
0527     const qreal imageHalfWidth  = viewport->width() / 2;
0528     const qreal imageHalfHeight = viewport->height() / 2;
0529 
0530     bool dummyGlobeHidesPoint = false;
0531 
0532     Q_Q( const AzimuthalProjection );
0533     // Calculate the angle of the position vectors of both coordinates
0534     q->screenCoordinates( disappearCoords, viewport, x, y, dummyGlobeHidesPoint );
0535     qreal alpha = atan2( y - imageHalfHeight,
0536                          x - imageHalfWidth );
0537 
0538     q->screenCoordinates( reappearCoords, viewport, x, y, dummyGlobeHidesPoint );
0539     qreal beta =  atan2( y - imageHalfHeight,
0540                          x - imageHalfWidth );
0541 
0542     // Calculate the difference between both
0543     qreal diff = GeoDataCoordinates::normalizeLon( beta - alpha );
0544 
0545     qreal sgndiff = diff < 0 ? -1 : 1;
0546 
0547     const qreal arcradius = q->clippingRadius() * viewport->radius();
0548     const int itEnd = fabs(diff * RAD2DEG);
0549 
0550     // Create a polygon that resembles an arc between the two position vectors
0551     polygon->reserve(polygon->size() + itEnd);
0552     for ( int it = 1; it <= itEnd; ++it ) {
0553         const qreal angle = alpha + DEG2RAD * sgndiff * it;
0554         const qreal itx = imageHalfWidth  +  arcradius * cos( angle );
0555         const qreal ity = imageHalfHeight +  arcradius * sin( angle );
0556         *polygon << QPointF( itx, ity );
0557     }
0558 }
0559 
0560 
0561 GeoDataCoordinates AzimuthalProjectionPrivate::findHorizon( const GeoDataCoordinates & previousCoords,
0562                                                     const GeoDataCoordinates & currentCoords,
0563                                                     const ViewportParams *viewport,
0564                                                     TessellationFlags f) const
0565 {
0566     bool currentHide = globeHidesPoint( currentCoords, viewport ) ;
0567 
0568     return doFindHorizon(previousCoords, currentCoords, viewport, f, currentHide, 0);
0569 }
0570 
0571 
0572 GeoDataCoordinates AzimuthalProjectionPrivate::doFindHorizon( const GeoDataCoordinates & previousCoords,
0573                                                     const GeoDataCoordinates & currentCoords,
0574                                                     const ViewportParams *viewport,
0575                                                     TessellationFlags f,
0576                                                     bool currentHide,
0577                                                     int recursionCounter ) const
0578 {
0579     if ( recursionCounter > 20 ) {
0580         return currentHide ? previousCoords : currentCoords;
0581     }
0582     ++recursionCounter;
0583 
0584     bool followLatitudeCircle = false;
0585 
0586     // Calculate steps for tessellation: lonDiff and altDiff
0587     qreal lonDiff = 0.0;
0588     qreal previousLongitude = 0.0;
0589     qreal previousLatitude = 0.0;
0590 
0591     if ( f.testFlag( RespectLatitudeCircle ) ) {
0592         previousCoords.geoCoordinates( previousLongitude, previousLatitude );
0593         qreal previousSign = previousLongitude > 0 ? 1 : -1;
0594 
0595         qreal currentLongitude = 0.0;
0596         qreal currentLatitude = 0.0;
0597         currentCoords.geoCoordinates( currentLongitude, currentLatitude );
0598         qreal currentSign = currentLongitude > 0 ? 1 : -1;
0599 
0600         if ( previousLatitude == currentLatitude ) {
0601             followLatitudeCircle = true;
0602 
0603             lonDiff = currentLongitude - previousLongitude;
0604             if ( previousSign != currentSign
0605                  && fabs(previousLongitude) + fabs(currentLongitude) > M_PI ) {
0606                 if ( previousSign > currentSign ) {
0607                     // going eastwards ->
0608                     lonDiff += 2 * M_PI ;
0609                 } else {
0610                     // going westwards ->
0611                     lonDiff -= 2 * M_PI;
0612                 }
0613             }
0614 
0615         }
0616         else {
0617 //            mDebug() << "Don't FollowLatitudeCircle";
0618         }
0619     }
0620 
0621     GeoDataCoordinates horizonCoords;
0622 
0623     if ( followLatitudeCircle ) {
0624         // To tessellate along latitude circles use the
0625         // linear interpolation of the longitude.
0626         const qreal altDiff = currentCoords.altitude() - previousCoords.altitude();
0627         const qreal altitude = previousCoords.altitude() + 0.5 * altDiff;
0628         const qreal lon = lonDiff * 0.5 + previousLongitude;
0629         const qreal lat = previousLatitude;
0630 
0631         horizonCoords = GeoDataCoordinates(lon, lat, altitude);
0632     }
0633     else {
0634         // To tessellate along great circles use the
0635         // normalized linear interpolation ("NLERP") for latitude and longitude.
0636         horizonCoords = previousCoords.nlerp(currentCoords, 0.5);
0637     }
0638 
0639     bool horizonHide = globeHidesPoint( horizonCoords, viewport );
0640 
0641     if ( horizonHide != currentHide ) {
0642         return doFindHorizon(horizonCoords, currentCoords, viewport, f, currentHide, recursionCounter);
0643     }
0644 
0645     return doFindHorizon(previousCoords, horizonCoords, viewport, f, horizonHide, recursionCounter);
0646 }
0647 
0648 
0649 bool AzimuthalProjectionPrivate::globeHidesPoint( const GeoDataCoordinates &coordinates,
0650                                           const ViewportParams *viewport ) const
0651 {
0652     bool globeHidesPoint;
0653     qreal dummyX, dummyY;
0654 
0655     Q_Q( const AzimuthalProjection );
0656     q->screenCoordinates(coordinates, viewport, dummyX, dummyY, globeHidesPoint);
0657     return globeHidesPoint;
0658 }
0659 
0660 
0661 }
0662 
0663