File indexing completed on 2025-01-05 03:59:32
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 // qCDebug(DIGIKAM_MARBLE_LOG) << "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 ¤tCoords, 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 // qCDebug(DIGIKAM_MARBLE_LOG) << "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