1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2014 Gábor Péterffy <peterffy95@gmail.org>
4 //
5
6 // Local
7 #include "AzimuthalProjection.h"
8 #include "AzimuthalProjection_p.h"
9 #include "AbstractProjection_p.h"
10
11 // Marble
12 #include "GeoDataLinearRing.h"
13 #include "GeoDataLineString.h"
14 #include "GeoDataCoordinates.h"
15 #include "GeoDataLatLonAltBox.h"
16 #include "ViewportParams.h"
17
18 #include <QPainterPath>
19
20
21 namespace Marble {
22
isClippedToSphere() const23 bool AzimuthalProjection::isClippedToSphere() const
24 {
25 return true;
26 }
27
clippingRadius() const28 qreal AzimuthalProjection::clippingRadius() const
29 {
30 return 1;
31 }
32
screenCoordinates(const GeoDataLineString & lineString,const ViewportParams * viewport,QVector<QPolygonF * > & polygons) const33 bool AzimuthalProjection::screenCoordinates( const GeoDataLineString &lineString,
34 const ViewportParams *viewport,
35 QVector<QPolygonF *> &polygons ) const
36 {
37
38 Q_D( const AzimuthalProjection );
39 // Compare bounding box size of the line string with the angularResolution
40 // Immediately return if the latLonAltBox is smaller.
41 if ( !viewport->resolves( lineString.latLonAltBox() ) ) {
42 // mDebug() << "Object too small to be resolved";
43 return false;
44 }
45
46 d->lineStringToPolygon( lineString, viewport, polygons );
47 return true;
48 }
49
mapCoversViewport(const ViewportParams * viewport) const50 bool AzimuthalProjection::mapCoversViewport( const ViewportParams *viewport ) const
51 {
52 qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
53 qint64 width = viewport->width();
54 qint64 height = viewport->height();
55
56 // This first test is a quick one that will catch all really big
57 // radii and prevent overflow in the real test.
58 if ( radius > width + height )
59 return true;
60
61 // This is the real test. The 4 is because we are really
62 // comparing to width/2 and height/2.
63 if ( 4 * radius * radius >= width * width + height * height )
64 return true;
65
66 return false;
67 }
68
latLonAltBox(const QRect & screenRect,const ViewportParams * viewport) const69 GeoDataLatLonAltBox AzimuthalProjection::latLonAltBox( const QRect& screenRect,
70 const ViewportParams *viewport ) const
71 {
72 // For the case where the whole viewport gets covered there is a
73 // pretty dirty and generic detection algorithm:
74 GeoDataLatLonAltBox latLonAltBox = AbstractProjection::latLonAltBox( screenRect, viewport );
75
76 // If the whole globe is visible we can easily calculate
77 // analytically the lon-/lat- range.
78 qreal pitch = GeoDataCoordinates::normalizeLat( viewport->planetAxis().pitch() );
79
80 if ( 2.0 * viewport->radius() <= viewport->height()
81 && 2.0 * viewport->radius() <= viewport->width() )
82 {
83 // Unless the planetaxis is in the screen plane the allowed longitude range
84 // covers full -180 deg to +180 deg:
85 if ( pitch > 0.0 && pitch < +M_PI ) {
86 latLonAltBox.setWest( -M_PI );
87 latLonAltBox.setEast( +M_PI );
88 latLonAltBox.setNorth( +fabs( M_PI / 2.0 - fabs( pitch ) ) );
89 latLonAltBox.setSouth( -M_PI / 2.0 );
90 }
91 if ( pitch < 0.0 && pitch > -M_PI ) {
92 latLonAltBox.setWest( -M_PI );
93 latLonAltBox.setEast( +M_PI );
94 latLonAltBox.setNorth( +M_PI / 2.0 );
95 latLonAltBox.setSouth( -fabs( M_PI / 2.0 - fabs( pitch ) ) );
96 }
97
98 // Last but not least we deal with the rare case where the
99 // globe is fully visible and pitch = 0.0 or pitch = -M_PI or
100 // pitch = +M_PI
101 if ( pitch == 0.0 || pitch == -M_PI || pitch == +M_PI ) {
102 qreal yaw = viewport->planetAxis().yaw();
103 latLonAltBox.setWest( GeoDataCoordinates::normalizeLon( yaw - M_PI / 2.0 ) );
104 latLonAltBox.setEast( GeoDataCoordinates::normalizeLon( yaw + M_PI / 2.0 ) );
105 latLonAltBox.setNorth( +M_PI / 2.0 );
106 latLonAltBox.setSouth( -M_PI / 2.0 );
107 }
108
109 return latLonAltBox;
110 }
111
112 // Now we check whether maxLat (e.g. the north pole) gets displayed
113 // inside the viewport to get more accurate values for east and west.
114
115 // We need a point on the screen at maxLat that definitely gets displayed:
116 qreal averageLongitude = ( latLonAltBox.west() + latLonAltBox.east() ) / 2.0;
117
118 GeoDataCoordinates maxLatPoint( averageLongitude, maxLat(), 0.0, GeoDataCoordinates::Radian );
119 GeoDataCoordinates minLatPoint( averageLongitude, minLat(), 0.0, GeoDataCoordinates::Radian );
120
121 qreal dummyX, dummyY; // not needed
122 bool dummyVal;
123
124 if ( screenCoordinates( maxLatPoint, viewport, dummyX, dummyY, dummyVal ) ||
125 screenCoordinates( minLatPoint, viewport, dummyX, dummyY, dummyVal ) ) {
126 latLonAltBox.setWest( -M_PI );
127 latLonAltBox.setEast( +M_PI );
128 }
129
130 return latLonAltBox;
131 }
132
mapShape(const ViewportParams * viewport) const133 QPainterPath AzimuthalProjection::mapShape( const ViewportParams *viewport ) const
134 {
135 int radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
136 int imgWidth = viewport->width();
137 int imgHeight = viewport->height();
138
139 QPainterPath fullRect;
140 fullRect.addRect( 0 , 0 , imgWidth, imgHeight );
141
142 // If the globe covers the whole image, then the projected region represents
143 // all of the image.
144 // Otherwise the active region has got the shape of the visible globe.
145
146 if ( !viewport->mapCoversViewport() ) {
147 QPainterPath mapShape;
148 mapShape.addEllipse(
149 imgWidth / 2 - radius,
150 imgHeight / 2 - radius,
151 2 * radius,
152 2 * radius );
153 return mapShape.intersected( fullRect );
154 }
155
156 return fullRect;
157 }
158
AzimuthalProjection(AzimuthalProjectionPrivate * dd)159 AzimuthalProjection::AzimuthalProjection(AzimuthalProjectionPrivate * dd) :
160 AbstractProjection( dd )
161 {
162 }
163
~AzimuthalProjection()164 AzimuthalProjection::~AzimuthalProjection()
165 {
166 }
167
tessellateLineSegment(const GeoDataCoordinates & aCoords,qreal ax,qreal ay,const GeoDataCoordinates & bCoords,qreal bx,qreal by,QVector<QPolygonF * > & polygons,const ViewportParams * viewport,TessellationFlags f,bool allowLatePolygonCut) const168 void AzimuthalProjectionPrivate::tessellateLineSegment( const GeoDataCoordinates &aCoords,
169 qreal ax, qreal ay,
170 const GeoDataCoordinates &bCoords,
171 qreal bx, qreal by,
172 QVector<QPolygonF*> &polygons,
173 const ViewportParams *viewport,
174 TessellationFlags f,
175 bool allowLatePolygonCut ) const
176 {
177 // We take the manhattan length as a distance approximation
178 // that can be too big by a factor of sqrt(2)
179 qreal distance = fabs((bx - ax)) + fabs((by - ay));
180 #ifdef SAFE_DISTANCE
181 // Interpolate additional nodes if the line segment that connects the
182 // current or previous nodes might cross the viewport.
183 // The latter can pretty safely be excluded for most projections if both points
184 // are located on the same side relative to the viewport boundaries and if they are
185 // located more than half the line segment distance away from the viewport.
186 const qreal safeDistance = - 0.5 * distance;
187 if ( !( bx < safeDistance && ax < safeDistance )
188 || !( by < safeDistance && ay < safeDistance )
189 || !( bx + safeDistance > viewport->width()
190 && ax + safeDistance > viewport->width() )
191 || !( by + safeDistance > viewport->height()
192 && ay + safeDistance > viewport->height() )
193 )
194 {
195 #endif
196 int maxTessellationFactor = viewport->radius() < 20000 ? 10 : 20;
197 int const finalTessellationPrecision = qBound(2, viewport->radius()/200, maxTessellationFactor) * tessellationPrecision;
198
199 // Let the line segment follow the spherical surface
200 // if the distance between the previous point and the current point
201 // on screen is too big
202
203 if ( distance > finalTessellationPrecision ) {
204 const int tessellatedNodes = qMin<int>( distance / finalTessellationPrecision, maxTessellationNodes );
205
206 processTessellation( aCoords, bCoords,
207 tessellatedNodes,
208 polygons,
209 viewport,
210 f,
211 allowLatePolygonCut);
212 }
213 else {
214 crossHorizon( bCoords, polygons, viewport, allowLatePolygonCut );
215 }
216 #ifdef SAFE_DISTANCE
217 }
218 #endif
219 }
220
221
processTessellation(const GeoDataCoordinates & previousCoords,const GeoDataCoordinates & currentCoords,int tessellatedNodes,QVector<QPolygonF * > & polygons,const ViewportParams * viewport,TessellationFlags f,bool allowLatePolygonCut) const222 void AzimuthalProjectionPrivate::processTessellation( const GeoDataCoordinates &previousCoords,
223 const GeoDataCoordinates ¤tCoords,
224 int tessellatedNodes,
225 QVector<QPolygonF*> &polygons,
226 const ViewportParams *viewport,
227 TessellationFlags f,
228 bool allowLatePolygonCut ) const
229 {
230
231 const bool clampToGround = f.testFlag( FollowGround );
232 const bool followLatitudeCircle = f.testFlag( RespectLatitudeCircle )
233 && previousCoords.latitude() == currentCoords.latitude();
234
235 // Calculate steps for tessellation: lonDiff and altDiff
236 qreal lonDiff = 0.0;
237 if ( followLatitudeCircle ) {
238 const int previousSign = previousCoords.longitude() > 0 ? 1 : -1;
239 const int currentSign = currentCoords.longitude() > 0 ? 1 : -1;
240
241 lonDiff = currentCoords.longitude() - previousCoords.longitude();
242 if ( previousSign != currentSign
243 && fabs(previousCoords.longitude()) + fabs(currentCoords.longitude()) > M_PI ) {
244 if ( previousSign > currentSign ) {
245 // going eastwards ->
246 lonDiff += 2 * M_PI ;
247 } else {
248 // going westwards ->
249 lonDiff -= 2 * M_PI;
250 }
251 }
252 }
253
254 // Create the tessellation nodes.
255 GeoDataCoordinates previousTessellatedCoords = previousCoords;
256 for ( int i = 1; i <= tessellatedNodes; ++i ) {
257 const qreal t = (qreal)(i) / (qreal)( tessellatedNodes + 1 );
258
259 GeoDataCoordinates currentTessellatedCoords;
260
261 if ( followLatitudeCircle ) {
262 // To tessellate along latitude circles use the
263 // linear interpolation of the longitude.
264 const qreal altDiff = currentCoords.altitude() - previousCoords.altitude();
265 const qreal altitude = altDiff * t + previousCoords.altitude();
266 const qreal lon = lonDiff * t + previousCoords.longitude();
267 const qreal lat = previousTessellatedCoords.latitude();
268
269 currentTessellatedCoords = GeoDataCoordinates(lon, lat, altitude);
270 }
271 else {
272 // To tessellate along great circles use the
273 // normalized linear interpolation ("NLERP") for latitude and longitude.
274 currentTessellatedCoords = previousCoords.nlerp(currentCoords, t);
275 }
276
277 if (clampToGround) {
278 currentTessellatedCoords.setAltitude(0);
279 }
280
281 crossHorizon( currentTessellatedCoords, polygons, viewport, allowLatePolygonCut );
282 previousTessellatedCoords = currentTessellatedCoords;
283 }
284
285 // For the clampToGround case add the "current" coordinate after adding all other nodes.
286 GeoDataCoordinates currentModifiedCoords( currentCoords );
287 if ( clampToGround ) {
288 currentModifiedCoords.setAltitude( 0.0 );
289 }
290 crossHorizon( currentModifiedCoords, polygons, viewport, allowLatePolygonCut );
291 }
292
crossHorizon(const GeoDataCoordinates & bCoord,QVector<QPolygonF * > & polygons,const ViewportParams * viewport,bool allowLatePolygonCut) const293 void AzimuthalProjectionPrivate::crossHorizon( const GeoDataCoordinates & bCoord,
294 QVector<QPolygonF*> &polygons,
295 const ViewportParams *viewport,
296 bool allowLatePolygonCut
297 ) const
298 {
299 qreal x, y;
300 bool globeHidesPoint;
301
302 Q_Q( const AbstractProjection );
303
304 q->screenCoordinates( bCoord, viewport, x, y, globeHidesPoint );
305
306 if( !globeHidesPoint ) {
307 *polygons.last() << QPointF( x, y );
308 }
309 else {
310 if ( allowLatePolygonCut && !polygons.last()->isEmpty() ) {
311 QPolygonF *path = new QPolygonF;
312 polygons.append( path );
313 }
314 }
315 }
316
lineStringToPolygon(const GeoDataLineString & lineString,const ViewportParams * viewport,QVector<QPolygonF * > & polygons) const317 bool AzimuthalProjectionPrivate::lineStringToPolygon( const GeoDataLineString &lineString,
318 const ViewportParams *viewport,
319 QVector<QPolygonF *> &polygons ) const
320 {
321 Q_Q( const AzimuthalProjection );
322
323 const TessellationFlags f = lineString.tessellationFlags();
324 bool const tessellate = lineString.tessellate();
325 const bool noFilter = f.testFlag(PreventNodeFiltering);
326
327
328 qreal x = 0;
329 qreal y = 0;
330 bool globeHidesPoint = false;
331
332 qreal previousX = -1.0;
333 qreal previousY = -1.0;
334 bool previousGlobeHidesPoint = false;
335
336 qreal horizonX = -1.0;
337 qreal horizonY = -1.0;
338
339 QPolygonF * polygon = new QPolygonF;
340 if (!tessellate) {
341 polygon->reserve(lineString.size());
342 }
343 polygons.append( polygon );
344
345 GeoDataLineString::ConstIterator itCoords = lineString.constBegin();
346 GeoDataLineString::ConstIterator itPreviousCoords = lineString.constBegin();
347
348 // Some projections display the earth in a way so that there is a
349 // foreside and a backside.
350 // The horizon is the line (usually a circle) which separates both
351 // sides from each other and resembles the map shape.
352 GeoDataCoordinates horizonCoords;
353
354 // A horizon pair is a pair of two subsequent horizon crossings:
355 // The first one describes the point where a line string disappears behind the horizon.
356 // and where horizonPair is set to true.
357 // The second one describes the point where the line string reappears.
358 // In this case the two points are connected and horizonPair is set to false again.
359 bool horizonPair = false;
360 GeoDataCoordinates horizonDisappearCoords;
361
362 // If the first horizon crossing in a line string describes the appearance of
363 // a line string then we call it a "horizon orphan" and horizonOrphan is set to true.
364 // In this case once the last horizon crossing in the line string is reached
365 // it needs to be connected to the orphan.
366 bool horizonOrphan = false;
367 GeoDataCoordinates horizonOrphanCoords;
368
369 GeoDataLineString::ConstIterator itBegin = lineString.constBegin();
370 GeoDataLineString::ConstIterator itEnd = lineString.constEnd();
371
372 bool processingLastNode = false;
373
374 // We use a while loop to be able to cover linestrings as well as linear rings:
375 // Linear rings require to tessellate the path from the last node to the first node
376 // which isn't really convenient to achieve with a for loop ...
377
378 const bool isLong = lineString.size() > 10;
379 const int maximumDetail = levelForResolution(viewport->angularResolution());
380 // The first node of optimized linestrings has a non-zero detail value.
381 const bool hasDetail = itBegin->detail() != 0;
382
383 while ( itCoords != itEnd )
384 {
385 // Optimization for line strings with a big amount of nodes
386 bool skipNode = (hasDetail ? itCoords->detail() > maximumDetail
387 : itCoords != itBegin && isLong && !processingLastNode &&
388 !viewport->resolves( *itPreviousCoords, *itCoords ) );
389
390 if ( !skipNode || noFilter) {
391
392 q->screenCoordinates( *itCoords, viewport, x, y, globeHidesPoint );
393
394 // Initializing variables that store the values of the previous iteration
395 if ( !processingLastNode && itCoords == itBegin ) {
396 previousGlobeHidesPoint = globeHidesPoint;
397 itPreviousCoords = itCoords;
398 previousX = x;
399 previousY = y;
400 }
401
402 // Check for the "horizon case" (which is present e.g. for the spherical projection
403 const bool isAtHorizon = ( globeHidesPoint || previousGlobeHidesPoint ) &&
404 ( globeHidesPoint != previousGlobeHidesPoint );
405
406 if ( isAtHorizon ) {
407 // Handle the "horizon case"
408 horizonCoords = findHorizon( *itPreviousCoords, *itCoords, viewport, f );
409
410 if ( lineString.isClosed() ) {
411 if ( horizonPair ) {
412 horizonToPolygon( viewport, horizonDisappearCoords, horizonCoords, polygons.last() );
413 horizonPair = false;
414 }
415 else {
416 if ( globeHidesPoint ) {
417 horizonDisappearCoords = horizonCoords;
418 horizonPair = true;
419 }
420 else {
421 horizonOrphanCoords = horizonCoords;
422 horizonOrphan = true;
423 }
424 }
425 }
426
427 q->screenCoordinates( horizonCoords, viewport, horizonX, horizonY );
428
429 // If the line appears on the visible half we need
430 // to add an interpolated point at the horizon as the previous point.
431 if ( previousGlobeHidesPoint ) {
432 *polygons.last() << QPointF( horizonX, horizonY );
433 }
434 }
435
436 // This if-clause contains the section that tessellates the line
437 // segments of a linestring. If you are about to learn how the code of
438 // this class works you can safely ignore this section for a start.
439
440 if ( lineString.tessellate() /* && ( isVisible || previousIsVisible ) */ ) {
441
442 if ( !isAtHorizon ) {
443
444 tessellateLineSegment( *itPreviousCoords, previousX, previousY,
445 *itCoords, x, y,
446 polygons, viewport,
447 f, !lineString.isClosed() );
448
449 }
450 else {
451 // Connect the interpolated point at the horizon with the
452 // current or previous point in the line.
453 if ( previousGlobeHidesPoint ) {
454 tessellateLineSegment( horizonCoords, horizonX, horizonY,
455 *itCoords, x, y,
456 polygons, viewport,
457 f, !lineString.isClosed() );
458 }
459 else {
460 tessellateLineSegment( *itPreviousCoords, previousX, previousY,
461 horizonCoords, horizonX, horizonY,
462 polygons, viewport,
463 f, !lineString.isClosed() );
464 }
465 }
466 }
467 else {
468 if ( !globeHidesPoint ) {
469 *polygons.last() << QPointF( x, y );
470 }
471 else {
472 if ( !previousGlobeHidesPoint && isAtHorizon ) {
473 *polygons.last() << QPointF( horizonX, horizonY );
474 }
475 }
476 }
477
478 if ( globeHidesPoint ) {
479 if ( !previousGlobeHidesPoint
480 && !lineString.isClosed()
481 ) {
482 polygons.append( new QPolygonF );
483 }
484 }
485
486 previousGlobeHidesPoint = globeHidesPoint;
487 itPreviousCoords = itCoords;
488 previousX = x;
489 previousY = y;
490 }
491
492 // Here we modify the condition to be able to process the
493 // first node after the last node in a LinearRing.
494
495 if ( processingLastNode ) {
496 break;
497 }
498 ++itCoords;
499
500 if ( itCoords == itEnd && lineString.isClosed() ) {
501 itCoords = itBegin;
502 processingLastNode = true;
503 }
504 }
505
506 // In case of horizon crossings, make sure that we always get a
507 // polygon closed correctly.
508 if ( horizonOrphan && lineString.isClosed() ) {
509 horizonToPolygon( viewport, horizonCoords, horizonOrphanCoords, polygons.last() );
510 }
511
512 if ( polygons.last()->size() <= 1 ){
513 delete polygons.last();
514 polygons.pop_back(); // Clean up "unused" empty polygon instances
515 }
516
517 return polygons.isEmpty();
518 }
519
horizonToPolygon(const ViewportParams * viewport,const GeoDataCoordinates & disappearCoords,const GeoDataCoordinates & reappearCoords,QPolygonF * polygon) const520 void AzimuthalProjectionPrivate::horizonToPolygon( const ViewportParams *viewport,
521 const GeoDataCoordinates & disappearCoords,
522 const GeoDataCoordinates & reappearCoords,
523 QPolygonF * polygon ) const
524 {
525 qreal x, y;
526
527 const qreal imageHalfWidth = viewport->width() / 2;
528 const qreal imageHalfHeight = viewport->height() / 2;
529
530 bool dummyGlobeHidesPoint = false;
531
532 Q_Q( const AzimuthalProjection );
533 // Calculate the angle of the position vectors of both coordinates
534 q->screenCoordinates( disappearCoords, viewport, x, y, dummyGlobeHidesPoint );
535 qreal alpha = atan2( y - imageHalfHeight,
536 x - imageHalfWidth );
537
538 q->screenCoordinates( reappearCoords, viewport, x, y, dummyGlobeHidesPoint );
539 qreal beta = atan2( y - imageHalfHeight,
540 x - imageHalfWidth );
541
542 // Calculate the difference between both
543 qreal diff = GeoDataCoordinates::normalizeLon( beta - alpha );
544
545 qreal sgndiff = diff < 0 ? -1 : 1;
546
547 const qreal arcradius = q->clippingRadius() * viewport->radius();
548 const int itEnd = fabs(diff * RAD2DEG);
549
550 // Create a polygon that resembles an arc between the two position vectors
551 polygon->reserve(polygon->size() + itEnd);
552 for ( int it = 1; it <= itEnd; ++it ) {
553 const qreal angle = alpha + DEG2RAD * sgndiff * it;
554 const qreal itx = imageHalfWidth + arcradius * cos( angle );
555 const qreal ity = imageHalfHeight + arcradius * sin( angle );
556 *polygon << QPointF( itx, ity );
557 }
558 }
559
560
findHorizon(const GeoDataCoordinates & previousCoords,const GeoDataCoordinates & currentCoords,const ViewportParams * viewport,TessellationFlags f) const561 GeoDataCoordinates AzimuthalProjectionPrivate::findHorizon( const GeoDataCoordinates & previousCoords,
562 const GeoDataCoordinates & currentCoords,
563 const ViewportParams *viewport,
564 TessellationFlags f) const
565 {
566 bool currentHide = globeHidesPoint( currentCoords, viewport ) ;
567
568 return doFindHorizon(previousCoords, currentCoords, viewport, f, currentHide, 0);
569 }
570
571
doFindHorizon(const GeoDataCoordinates & previousCoords,const GeoDataCoordinates & currentCoords,const ViewportParams * viewport,TessellationFlags f,bool currentHide,int recursionCounter) const572 GeoDataCoordinates AzimuthalProjectionPrivate::doFindHorizon( const GeoDataCoordinates & previousCoords,
573 const GeoDataCoordinates & currentCoords,
574 const ViewportParams *viewport,
575 TessellationFlags f,
576 bool currentHide,
577 int recursionCounter ) const
578 {
579 if ( recursionCounter > 20 ) {
580 return currentHide ? previousCoords : currentCoords;
581 }
582 ++recursionCounter;
583
584 bool followLatitudeCircle = false;
585
586 // Calculate steps for tessellation: lonDiff and altDiff
587 qreal lonDiff = 0.0;
588 qreal previousLongitude = 0.0;
589 qreal previousLatitude = 0.0;
590
591 if ( f.testFlag( RespectLatitudeCircle ) ) {
592 previousCoords.geoCoordinates( previousLongitude, previousLatitude );
593 qreal previousSign = previousLongitude > 0 ? 1 : -1;
594
595 qreal currentLongitude = 0.0;
596 qreal currentLatitude = 0.0;
597 currentCoords.geoCoordinates( currentLongitude, currentLatitude );
598 qreal currentSign = currentLongitude > 0 ? 1 : -1;
599
600 if ( previousLatitude == currentLatitude ) {
601 followLatitudeCircle = true;
602
603 lonDiff = currentLongitude - previousLongitude;
604 if ( previousSign != currentSign
605 && fabs(previousLongitude) + fabs(currentLongitude) > M_PI ) {
606 if ( previousSign > currentSign ) {
607 // going eastwards ->
608 lonDiff += 2 * M_PI ;
609 } else {
610 // going westwards ->
611 lonDiff -= 2 * M_PI;
612 }
613 }
614
615 }
616 else {
617 // mDebug() << "Don't FollowLatitudeCircle";
618 }
619 }
620
621 GeoDataCoordinates horizonCoords;
622
623 if ( followLatitudeCircle ) {
624 // To tessellate along latitude circles use the
625 // linear interpolation of the longitude.
626 const qreal altDiff = currentCoords.altitude() - previousCoords.altitude();
627 const qreal altitude = previousCoords.altitude() + 0.5 * altDiff;
628 const qreal lon = lonDiff * 0.5 + previousLongitude;
629 const qreal lat = previousLatitude;
630
631 horizonCoords = GeoDataCoordinates(lon, lat, altitude);
632 }
633 else {
634 // To tessellate along great circles use the
635 // normalized linear interpolation ("NLERP") for latitude and longitude.
636 horizonCoords = previousCoords.nlerp(currentCoords, 0.5);
637 }
638
639 bool horizonHide = globeHidesPoint( horizonCoords, viewport );
640
641 if ( horizonHide != currentHide ) {
642 return doFindHorizon(horizonCoords, currentCoords, viewport, f, currentHide, recursionCounter);
643 }
644
645 return doFindHorizon(previousCoords, horizonCoords, viewport, f, horizonHide, recursionCounter);
646 }
647
648
globeHidesPoint(const GeoDataCoordinates & coordinates,const ViewportParams * viewport) const649 bool AzimuthalProjectionPrivate::globeHidesPoint( const GeoDataCoordinates &coordinates,
650 const ViewportParams *viewport ) const
651 {
652 bool globeHidesPoint;
653 qreal dummyX, dummyY;
654
655 Q_Q( const AzimuthalProjection );
656 q->screenCoordinates(coordinates, viewport, dummyX, dummyY, globeHidesPoint);
657 return globeHidesPoint;
658 }
659
660
661 }
662
663
664