# Marble

AzimuthalProjection.cpp
2//
3// SPDX-FileCopyrightText: 2014 Gábor Péterffy <peterffy95@gmail.org>
4//
5
6// Local
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
21namespace Marble {
22
24{
25 return true;
26}
27
29{
30 return 1;
31}
32
33bool 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
51{
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
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:
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
134{
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() ) {
149 imgWidth / 2 - radius,
150 imgHeight / 2 - radius,
153 return mapShape.intersected( fullRect );
154 }
155
156 return fullRect;
157}
158
159AzimuthalProjection::AzimuthalProjection(AzimuthalProjectionPrivate * dd) :
161{
162}
163
164AzimuthalProjection::~AzimuthalProjection()
165{
166}
167
168void 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
222void AzimuthalProjectionPrivate::processTessellation( const GeoDataCoordinates &previousCoords,
223 const GeoDataCoordinates &currentCoords,
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
293void 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
317bool 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
520void 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
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
561GeoDataCoordinates 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
572GeoDataCoordinates 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
649bool 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
This file contains the headers for AzimuthalProjection.
This file contains the headers for ViewportParams.
A base class for all projections in Marble.
qreal minLat() const
Returns the arbitrarily chosen minimum (southern) latitude.
qreal maxLat() const
Returns the arbitrarily chosen maximum (northern) latitude.
virtual GeoDataLatLonAltBox latLonAltBox(const QRect &screenRect, const ViewportParams *viewport) const
Returns a GeoDataLatLonAltBox bounding box of the given screenrect inside the given viewport.
bool isClippedToSphere() const override
Defines whether a projection is supposed to be clipped to a certain radius.
GeoDataLatLonAltBox latLonAltBox(const QRect &screenRect, const ViewportParams *viewport) const override
Returns a GeoDataLatLonAltBox bounding box of the given screenrect inside the given viewport.
bool mapCoversViewport(const ViewportParams *viewport) const override
Returns whether the projected data fully obstructs the current viewport.
QPainterPath mapShape(const ViewportParams *viewport) const override
Returns the shape/outline of a map projection.
A 3d point representation.
normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian).
normalize latitude to always be in -M_PI / 2.
A class that defines a 3D bounding box for geographic data.
Get the eastern boundary of the bounding box.
Get the western boundary of the bounding box.
A public class that controls what is visible in the viewport of a Marble map.
QString path(const QString &relativePath)
Binds a QML item to a specific geodetic location in screen coordinates.
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
void append(QList< T > &&value)
bool isEmpty() const const
T & last()
void pop_back()
void reserve(qsizetype size)
qsizetype size() const const
void addEllipse(const QPointF &center, qreal rx, qreal ry)