Marble

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

KDE's Doxygen guidelines are available online.