8#include "AbstractProjection_p.h"
9#include "AzimuthalProjection_p.h"
12#include "GeoDataCoordinates.h"
13#include "GeoDataLatLonAltBox.h"
14#include "GeoDataLineString.h"
15#include "GeoDataLinearRing.h"
18#include <QPainterPath>
28qreal AzimuthalProjection::clippingRadius()
const
33bool AzimuthalProjection::screenCoordinates(
const GeoDataLineString &lineString,
const ViewportParams *viewport,
QList<QPolygonF *> &polygons)
const
35 Q_D(
const AzimuthalProjection);
38 if (!viewport->resolves(lineString.latLonAltBox())) {
43 d->lineStringToPolygon(lineString, viewport, polygons);
49 qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
50 qint64 width = viewport->width();
51 qint64 height = viewport->height();
55 if (radius > width + height)
60 if (4 * radius * radius >= width * width + height * height)
76 if (2.0 * viewport->radius() <= viewport->height() && 2.0 * viewport->radius() <= viewport->width()) {
79 if (pitch > 0.0 && pitch < +M_PI) {
85 if (pitch < 0.0 && pitch > -M_PI) {
95 if (pitch == 0.0 || pitch == -M_PI || pitch == +M_PI) {
96 qreal yaw = viewport->planetAxis().yaw();
115 qreal dummyX, dummyY;
118 if (screenCoordinates(maxLatPoint, viewport, dummyX, dummyY, dummyVal) || screenCoordinates(minLatPoint, viewport, dummyX, dummyY, dummyVal)) {
128 int radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
129 int imgWidth = viewport->width();
130 int imgHeight = viewport->height();
133 fullRect.
addRect(0, 0, imgWidth, imgHeight);
139 if (!viewport->mapCoversViewport()) {
141 mapShape.
addEllipse(imgWidth / 2 - radius, imgHeight / 2 - radius, 2 * radius, 2 * radius);
148AzimuthalProjection::AzimuthalProjection(AzimuthalProjectionPrivate *dd)
153AzimuthalProjection::~AzimuthalProjection() =
default;
155void AzimuthalProjectionPrivate::tessellateLineSegment(
const GeoDataCoordinates &aCoords,
158 const GeoDataCoordinates &bCoords,
162 const ViewportParams *viewport,
164 bool allowLatePolygonCut)
const
168 qreal distance = fabs((bx - ax)) + fabs((by - ay));
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())) {
180 int maxTessellationFactor = viewport->radius() < 20000 ? 10 : 20;
181 int const finalTessellationPrecision = qBound(2, viewport->radius() / 200, maxTessellationFactor) * tessellationPrecision;
187 if (distance > finalTessellationPrecision) {
188 const int tessellatedNodes = qMin<int>(distance / finalTessellationPrecision, maxTessellationNodes);
190 processTessellation(aCoords, bCoords, tessellatedNodes, polygons, viewport, f, allowLatePolygonCut);
192 crossHorizon(bCoords, polygons, viewport, allowLatePolygonCut);
199void AzimuthalProjectionPrivate::processTessellation(
const GeoDataCoordinates &previousCoords,
200 const GeoDataCoordinates ¤tCoords,
201 int tessellatedNodes,
203 const ViewportParams *viewport,
205 bool allowLatePolygonCut)
const
207 const bool clampToGround = f.testFlag(FollowGround);
208 const bool followLatitudeCircle = f.testFlag(RespectLatitudeCircle) && previousCoords.latitude() == currentCoords.latitude();
212 if (followLatitudeCircle) {
213 const int previousSign = previousCoords.longitude() > 0 ? 1 : -1;
214 const int currentSign = currentCoords.longitude() > 0 ? 1 : -1;
216 lonDiff = currentCoords.longitude() - previousCoords.longitude();
217 if (previousSign != currentSign && fabs(previousCoords.longitude()) + fabs(currentCoords.longitude()) > M_PI) {
218 if (previousSign > currentSign) {
229 GeoDataCoordinates previousTessellatedCoords = previousCoords;
230 for (
int i = 1; i <= tessellatedNodes; ++i) {
231 const qreal t = (qreal)(i) / (qreal)(tessellatedNodes + 1);
233 GeoDataCoordinates currentTessellatedCoords;
235 if (followLatitudeCircle) {
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();
243 currentTessellatedCoords = GeoDataCoordinates(lon, lat, altitude);
247 currentTessellatedCoords = previousCoords.nlerp(currentCoords, t);
251 currentTessellatedCoords.setAltitude(0);
254 crossHorizon(currentTessellatedCoords, polygons, viewport, allowLatePolygonCut);
255 previousTessellatedCoords = currentTessellatedCoords;
259 GeoDataCoordinates currentModifiedCoords(currentCoords);
261 currentModifiedCoords.setAltitude(0.0);
263 crossHorizon(currentModifiedCoords, polygons, viewport, allowLatePolygonCut);
266void AzimuthalProjectionPrivate::crossHorizon(
const GeoDataCoordinates &bCoord,
268 const ViewportParams *viewport,
269 bool allowLatePolygonCut)
const
272 bool globeHidesPoint;
274 Q_Q(
const AbstractProjection);
276 q->screenCoordinates(bCoord, viewport, x, y, globeHidesPoint);
278 if (!globeHidesPoint) {
281 if (allowLatePolygonCut && !polygons.
last()->isEmpty()) {
288bool AzimuthalProjectionPrivate::lineStringToPolygon(
const GeoDataLineString &lineString,
const ViewportParams *viewport,
QList<QPolygonF *> &polygons)
const
290 Q_Q(
const AzimuthalProjection);
292 const TessellationFlags f = lineString.tessellationFlags();
293 bool const tessellate = lineString.tessellate();
294 const bool noFilter = f.testFlag(PreventNodeFiltering);
298 bool globeHidesPoint =
false;
300 qreal previousX = -1.0;
301 qreal previousY = -1.0;
302 bool previousGlobeHidesPoint =
false;
304 qreal horizonX = -1.0;
305 qreal horizonY = -1.0;
309 polygon->reserve(lineString.size());
313 GeoDataLineString::ConstIterator itCoords = lineString.constBegin();
314 GeoDataLineString::ConstIterator itPreviousCoords = lineString.constBegin();
320 GeoDataCoordinates horizonCoords;
327 bool horizonPair =
false;
328 GeoDataCoordinates horizonDisappearCoords;
334 bool horizonOrphan =
false;
335 GeoDataCoordinates horizonOrphanCoords;
337 GeoDataLineString::ConstIterator itBegin = lineString.constBegin();
338 GeoDataLineString::ConstIterator itEnd = lineString.constEnd();
340 bool processingLastNode =
false;
346 const bool isLong = lineString.size() > 10;
347 const int maximumDetail = levelForResolution(viewport->angularResolution());
349 const bool hasDetail = itBegin->detail() != 0;
351 while (itCoords != itEnd) {
353 bool skipNode = (hasDetail ? itCoords->detail() > maximumDetail
354 : itCoords != itBegin && isLong && !processingLastNode && !viewport->resolves(*itPreviousCoords, *itCoords));
356 if (!skipNode || noFilter) {
357 q->screenCoordinates(*itCoords, viewport, x, y, globeHidesPoint);
360 if (!processingLastNode && itCoords == itBegin) {
361 previousGlobeHidesPoint = globeHidesPoint;
362 itPreviousCoords = itCoords;
368 const bool isAtHorizon = (globeHidesPoint || previousGlobeHidesPoint) && (globeHidesPoint != previousGlobeHidesPoint);
372 horizonCoords = findHorizon(*itPreviousCoords, *itCoords, viewport, f);
374 if (lineString.isClosed()) {
376 horizonToPolygon(viewport, horizonDisappearCoords, horizonCoords, polygons.
last());
379 if (globeHidesPoint) {
380 horizonDisappearCoords = horizonCoords;
383 horizonOrphanCoords = horizonCoords;
384 horizonOrphan =
true;
389 q->screenCoordinates(horizonCoords, viewport, horizonX, horizonY);
393 if (previousGlobeHidesPoint) {
402 if (lineString.tessellate() ) {
404 tessellateLineSegment(*itPreviousCoords, previousX, previousY, *itCoords, x, y, polygons, viewport, f, !lineString.isClosed());
409 if (previousGlobeHidesPoint) {
410 tessellateLineSegment(horizonCoords, horizonX, horizonY, *itCoords, x, y, polygons, viewport, f, !lineString.isClosed());
412 tessellateLineSegment(*itPreviousCoords,
421 !lineString.isClosed());
425 if (!globeHidesPoint) {
428 if (!previousGlobeHidesPoint && isAtHorizon) {
434 if (globeHidesPoint) {
435 if (!previousGlobeHidesPoint && !lineString.isClosed()) {
440 previousGlobeHidesPoint = globeHidesPoint;
441 itPreviousCoords = itCoords;
449 if (processingLastNode) {
454 if (itCoords == itEnd && lineString.isClosed()) {
456 processingLastNode =
true;
462 if (horizonOrphan && lineString.isClosed()) {
463 horizonToPolygon(viewport, horizonCoords, horizonOrphanCoords, polygons.
last());
466 if (polygons.
last()->size() <= 1) {
467 delete polygons.
last();
474void AzimuthalProjectionPrivate::horizonToPolygon(
const ViewportParams *viewport,
475 const GeoDataCoordinates &disappearCoords,
476 const GeoDataCoordinates &reappearCoords,
481 const qreal imageHalfWidth = viewport->width() / 2;
482 const qreal imageHalfHeight = viewport->height() / 2;
484 bool dummyGlobeHidesPoint =
false;
486 Q_Q(
const AzimuthalProjection);
488 q->screenCoordinates(disappearCoords, viewport, x, y, dummyGlobeHidesPoint);
489 qreal alpha = atan2(y - imageHalfHeight, x - imageHalfWidth);
491 q->screenCoordinates(reappearCoords, viewport, x, y, dummyGlobeHidesPoint);
492 qreal beta = atan2(y - imageHalfHeight, x - imageHalfWidth);
497 qreal sgndiff = diff < 0 ? -1 : 1;
499 const qreal arcradius = q->clippingRadius() * viewport->radius();
500 const int itEnd = fabs(diff * RAD2DEG);
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);
512GeoDataCoordinates AzimuthalProjectionPrivate::findHorizon(
const GeoDataCoordinates &previousCoords,
513 const GeoDataCoordinates ¤tCoords,
514 const ViewportParams *viewport,
515 TessellationFlags f)
const
517 bool currentHide = globeHidesPoint(currentCoords, viewport);
519 return doFindHorizon(previousCoords, currentCoords, viewport, f, currentHide, 0);
522GeoDataCoordinates AzimuthalProjectionPrivate::doFindHorizon(
const GeoDataCoordinates &previousCoords,
523 const GeoDataCoordinates ¤tCoords,
524 const ViewportParams *viewport,
527 int recursionCounter)
const
529 if (recursionCounter > 20) {
530 return currentHide ? previousCoords : currentCoords;
534 bool followLatitudeCircle =
false;
538 qreal previousLongitude = 0.0;
539 qreal previousLatitude = 0.0;
541 if (f.testFlag(RespectLatitudeCircle)) {
542 previousCoords.geoCoordinates(previousLongitude, previousLatitude);
543 qreal previousSign = previousLongitude > 0 ? 1 : -1;
545 qreal currentLongitude = 0.0;
546 qreal currentLatitude = 0.0;
547 currentCoords.geoCoordinates(currentLongitude, currentLatitude);
548 qreal currentSign = currentLongitude > 0 ? 1 : -1;
550 if (previousLatitude == currentLatitude) {
551 followLatitudeCircle =
true;
553 lonDiff = currentLongitude - previousLongitude;
554 if (previousSign != currentSign && fabs(previousLongitude) + fabs(currentLongitude) > M_PI) {
555 if (previousSign > currentSign) {
569 GeoDataCoordinates horizonCoords;
571 if (followLatitudeCircle) {
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;
579 horizonCoords = GeoDataCoordinates(lon, lat, altitude);
583 horizonCoords = previousCoords.nlerp(currentCoords, 0.5);
586 bool horizonHide = globeHidesPoint(horizonCoords, viewport);
588 if (horizonHide != currentHide) {
589 return doFindHorizon(horizonCoords, currentCoords, viewport, f, currentHide, recursionCounter);
592 return doFindHorizon(previousCoords, horizonCoords, viewport, f, horizonHide, recursionCounter);
595bool AzimuthalProjectionPrivate::globeHidesPoint(
const GeoDataCoordinates &coordinates,
const ViewportParams *viewport)
const
597 bool globeHidesPoint;
598 qreal dummyX, dummyY;
600 Q_Q(
const AzimuthalProjection);
601 q->screenCoordinates(coordinates, viewport, dummyX, dummyY, globeHidesPoint);
602 return globeHidesPoint;
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
void reserve(qsizetype size)
qsizetype size() const const
void addEllipse(const QPointF ¢er, qreal rx, qreal ry)
void addRect(const QRectF &rectangle)
QPainterPath intersected(const QPainterPath &p) const const