diff --git a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp index 1ae22f481..9bc6774fd 100644 --- a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp @@ -1,174 +1,175 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #include "AbstractGeoPolygonGraphicsItem.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoPainter.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoDataTypes.h" #include "OsmPlacemarkData.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include namespace Marble { QPixmapCache AbstractGeoPolygonGraphicsItem::m_textureCache = QPixmapCache(); AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon) : GeoGraphicsItem(placemark), m_polygon(polygon), m_ring(0) { } AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring) : GeoGraphicsItem(placemark), m_polygon(0), m_ring(ring) { } AbstractGeoPolygonGraphicsItem::~AbstractGeoPolygonGraphicsItem() { } const GeoDataLatLonAltBox& AbstractGeoPolygonGraphicsItem::latLonAltBox() const { if( m_polygon ) { return m_polygon->latLonAltBox(); } return m_ring->latLonAltBox(); } void AbstractGeoPolygonGraphicsItem::paint( GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel) { Q_UNUSED(layer); Q_UNUSED(tileZoomLevel); painter->save(); configurePainter(painter, viewport); if ( m_polygon ) { bool innerResolved = false; + for(auto const & ring : m_polygon->innerBoundaries()) { if (viewport->resolves(ring.latLonAltBox(), 4)) { innerResolved = true; break; } } if (innerResolved) { painter->drawPolygon(*m_polygon); } else { painter->drawPolygon(m_polygon->outerBoundary()); } } else if ( m_ring ) { painter->drawPolygon( *m_ring ); } painter->restore(); } QPen AbstractGeoPolygonGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams *viewport) { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); // "style-less" polygons: a 1px black solid line } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); if (polyStyle.outline()) { // polygons without outline: Qt::NoPen (not drawn) const GeoDataLineStyle& lineStyle = style->lineStyle(); currentPen.setColor(lineStyle.paintedColor()); currentPen.setWidthF(lineStyle.width()); currentPen.setCapStyle(lineStyle.capStyle()); currentPen.setStyle(lineStyle.penStyle()); if (painter->pen().color() != currentPen.color()) { painter->setPen(currentPen); } } if (!polyStyle.fill()) { painter->setBrush(QColor(Qt::transparent)); } else { const QColor paintedColor = polyStyle.paintedColor(); if (painter->brush().color() != paintedColor || painter->brush().style() != polyStyle.brushStyle()) { if (!polyStyle.texturePath().isEmpty() || !polyStyle.textureImage().isNull()) { GeoDataCoordinates coords = latLonAltBox().center(); qreal x, y; viewport->screenCoordinates(coords, x, y); QBrush brush(texture(polyStyle.texturePath(), polyStyle.textureImage(), paintedColor)); painter->setBrush(brush); painter->setBrushOrigin(QPoint(x,y)); } else { painter->setBrush(QBrush(paintedColor, polyStyle.brushStyle())); } } } } return currentPen; } int AbstractGeoPolygonGraphicsItem::extractElevation(const GeoDataPlacemark &placemark) { int elevation = 0; const OsmPlacemarkData &osmData = placemark.osmData(); const auto tagIter = osmData.findTag(QStringLiteral("ele")); if (tagIter != osmData.tagsEnd()) { elevation = tagIter.value().toInt(); } return elevation; } QPixmap AbstractGeoPolygonGraphicsItem::texture(const QString &texturePath, const QImage &textureImage, const QColor &color) { QString const key = QString("%1/%2").arg(color.rgba()).arg(texturePath); QPixmap texture; if (!m_textureCache.find(key, texture)) { if (textureImage.hasAlphaChannel()) { - QImage image = QImage (textureImage.size(), QImage::Format_ARGB32_Premultiplied); + QImage image (textureImage.size(), QImage::Format_ARGB32_Premultiplied); image.fill(color); QPainter imagePainter(&image); imagePainter.drawImage(0, 0, textureImage); imagePainter.end(); texture = QPixmap::fromImage(image); } else { texture = QPixmap::fromImage(textureImage); } m_textureCache.insert(key, texture); } return texture; } } diff --git a/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp index 7df35dc6a..d516f63eb 100644 --- a/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp @@ -1,524 +1,528 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #include "BuildingGeoPolygonGraphicsItem.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include "GeoDataTypes.h" #include "GeoDataPlacemark.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoDataPolyStyle.h" #include "OsmPlacemarkData.h" #include "GeoPainter.h" #include #include namespace Marble { BuildingGeoPolygonGraphicsItem::BuildingGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon) : AbstractGeoPolygonGraphicsItem(placemark, polygon) , m_buildingHeight(extractBuildingHeight(*placemark)) , m_buildingLabel(extractBuildingLabel(*placemark)) , m_entries(extractNamedEntries(*placemark)) , m_hasInnerBoundaries(false) { setZValue(this->zValue() + m_buildingHeight); Q_ASSERT(m_buildingHeight > 0.0); QStringList paintLayers; paintLayers << QStringLiteral("Polygon/Building/frame") << QStringLiteral("Polygon/Building/roof"); setPaintLayers(paintLayers); } BuildingGeoPolygonGraphicsItem::BuildingGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing* ring) : AbstractGeoPolygonGraphicsItem(placemark, ring) , m_buildingHeight(extractBuildingHeight(*placemark)) , m_buildingLabel(extractBuildingLabel(*placemark)) , m_entries(extractNamedEntries(*placemark)) { setZValue(this->zValue() + m_buildingHeight); Q_ASSERT(m_buildingHeight > 0.0); QStringList paintLayers; paintLayers << QStringLiteral("Polygon/Building/frame") << QStringLiteral("Polygon/Building/roof"); setPaintLayers(paintLayers); } void BuildingGeoPolygonGraphicsItem::initializeBuildingPainting(const GeoPainter* painter, const ViewportParams *viewport, bool &drawAccurate3D, bool &isCameraAboveBuilding ) const { drawAccurate3D = false; isCameraAboveBuilding = false; auto const screen = QApplication::screens().first(); double const physicalSize = 1.0; // mm int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM)); QPointF offsetAtCorner = buildingOffset(QPointF(0, 0), viewport, &isCameraAboveBuilding); qreal maxOffset = qMax( qAbs( offsetAtCorner.x() ), qAbs( offsetAtCorner.y() ) ); drawAccurate3D = painter->mapQuality() == HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize; } void BuildingGeoPolygonGraphicsItem::updatePolygons( const ViewportParams *viewport, QVector& outerPolygons, QVector& innerPolygons, bool &hasInnerBoundaries ) { // Since subtracting one fully contained polygon from another results in a single // polygon with a "connecting line" between the inner and outer part we need // to first paint the inner area with no pen and then the outlines with the correct pen. hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().isEmpty() : false; if (polygon()) { if (hasInnerBoundaries) { screenPolygons(viewport, polygon(), innerPolygons, outerPolygons); } else { viewport->screenCoordinates(polygon()->outerBoundary(), outerPolygons); } } else if (ring()) { viewport->screenCoordinates(*ring(), outerPolygons); } } QPointF BuildingGeoPolygonGraphicsItem::centroid(const QPolygonF &polygon, double &area) { auto centroid = QPointF(0.0, 0.0); area = 0.0; for (auto i=0, n=polygon.size(); i 0.0); qreal const buildingFactor = m_buildingHeight / EARTH_RADIUS; qreal const cameraHeightPixel = viewport->width() * cameraFactor; qreal buildingHeightPixel = viewport->radius() * buildingFactor; qreal const cameraDistance = cameraHeightPixel-buildingHeightPixel; if (isCameraAboveBuilding) { *isCameraAboveBuilding = cameraDistance > 0; } qreal const cc = cameraDistance * cameraHeightPixel; qreal const cb = cameraDistance * buildingHeightPixel; // The following lines calculate the same result, but are potentially slower due // to using more trigonometric method calls // qreal const alpha1 = atan2(offsetX, cameraHeightPixel); // qreal const alpha2 = atan2(offsetX, cameraHeightPixel-buildingHeightPixel); // qreal const shiftX = 2 * (cameraHeightPixel-buildingHeightPixel) * sin(0.5*(alpha2-alpha1)); qreal const offsetX = point.x() - viewport->width() / 2.0; qreal const offsetY = point.y() - viewport->height() / 2.0; qreal const shiftX = offsetX * cb / (cc + offsetX); qreal const shiftY = offsetY * cb / (cc + offsetY); return QPointF(shiftX, shiftY); } double BuildingGeoPolygonGraphicsItem::extractBuildingHeight(const GeoDataPlacemark &placemark) { double height = 8.0; const OsmPlacemarkData &osmData = placemark.osmData(); QHash::const_iterator tagIter; if ((tagIter = osmData.findTag(QStringLiteral("height"))) != osmData.tagsEnd()) { /** @todo Also parse non-SI units, see https://wiki.openstreetmap.org/wiki/Key:height#Height_of_buildings */ QString const heightValue = QString(tagIter.value()).remove(QStringLiteral(" meters")).remove(QStringLiteral(" m")); bool extracted = false; double extractedHeight = heightValue.toDouble(&extracted); if (extracted) { height = extractedHeight; } } else if ((tagIter = osmData.findTag(QStringLiteral("building:levels"))) != osmData.tagsEnd()) { int const levels = tagIter.value().toInt(); int const skipLevels = osmData.tagValue(QStringLiteral("building:min_level")).toInt(); /** @todo Is 35 as an upper bound for the number of levels sane? */ height = 3.0 * qBound(1, 1+levels-skipLevels, 35); } return qBound(1.0, height, 1000.0); } QString BuildingGeoPolygonGraphicsItem::extractBuildingLabel(const GeoDataPlacemark &placemark) { const OsmPlacemarkData &osmData = placemark.osmData(); auto tagIter = osmData.findTag(QStringLiteral("addr:housename")); if (tagIter != osmData.tagsEnd()) { return tagIter.value(); } tagIter = osmData.findTag(QStringLiteral("addr:housenumber")); if (tagIter != osmData.tagsEnd()) { return tagIter.value(); } return QString(); } QVector BuildingGeoPolygonGraphicsItem::extractNamedEntries(const GeoDataPlacemark &placemark) { QVector entries; const auto end = placemark.osmData().nodeReferencesEnd(); for (auto iter = placemark.osmData().nodeReferencesBegin(); iter != end; ++iter) { const auto tagIter = iter.value().findTag(QStringLiteral("addr:housenumber")); if (tagIter != iter.value().tagsEnd()) { NamedEntry entry; entry.point = iter.key(); entry.label = tagIter.value(); entries.push_back(entry); } } return entries; } void BuildingGeoPolygonGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel) { // Just display flat buildings for tile level 17 if (tileZoomLevel == 17) { if (layer.endsWith(QLatin1String("/frame"))) { AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel ); } return; } // For level 18, 19 .. render 3D buildings in perspective if (layer.endsWith(QLatin1String("/frame"))) { Q_ASSERT(m_cachedOuterPolygons.isEmpty()); Q_ASSERT(m_cachedInnerPolygons.isEmpty()); Q_ASSERT(m_cachedOuterRoofPolygons.isEmpty()); Q_ASSERT(m_cachedInnerRoofPolygons.isEmpty()); updatePolygons(viewport, m_cachedOuterPolygons, m_cachedInnerPolygons, m_hasInnerBoundaries); if (m_cachedOuterPolygons.isEmpty()) { return; } paintFrame(painter, viewport); } else if (layer.endsWith(QLatin1String("/roof"))) { if (m_cachedOuterPolygons.isEmpty()) { return; } paintRoof(painter, viewport); qDeleteAll(m_cachedOuterPolygons); qDeleteAll(m_cachedOuterRoofPolygons); qDeleteAll(m_cachedInnerRoofPolygons); m_cachedOuterPolygons.clear(); m_cachedInnerPolygons.clear(); m_cachedOuterRoofPolygons.clear(); m_cachedInnerRoofPolygons.clear(); } else { mDebug() << "Didn't expect to have to paint layer " << layer << ", ignoring it."; } } void BuildingGeoPolygonGraphicsItem::paintRoof(GeoPainter* painter, const ViewportParams* viewport) { bool drawAccurate3D; bool isCameraAboveBuilding; initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding); if (!isCameraAboveBuilding) { return; // do not render roof if we look inside the building } painter->save(); QPen const currentPen = configurePainter(painter, viewport); qreal maxSize(0.0); QPointF roofCenter; // first paint the area (and the outline if there are no inner boundaries) double maxArea = 0.0; if ( drawAccurate3D) { if (m_hasInnerBoundaries) { painter->setPen(Qt::NoPen); QVector fillPolygons = painter->createFillPolygons( m_cachedOuterRoofPolygons, m_cachedInnerRoofPolygons ); foreach( const QPolygonF* fillPolygon, fillPolygons ) { painter->drawPolygon(*fillPolygon); } painter->setPen(currentPen); foreach( const QPolygonF* outerRoof, m_cachedOuterRoofPolygons ) { painter->drawPolyline( *outerRoof ); } foreach( const QPolygonF* innerRoof, m_cachedInnerRoofPolygons ) { painter->drawPolyline( *innerRoof ); } } else { foreach( const QPolygonF* outerRoof, m_cachedOuterRoofPolygons ) { painter->drawPolygon( *outerRoof ); } } } else { QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().center(), viewport); painter->translate(offset); if (m_hasInnerBoundaries) { painter->setPen(Qt::NoPen); QVector fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons, m_cachedInnerPolygons ); foreach( const QPolygonF* fillPolygon, fillPolygons ) { painter->drawPolygon(*fillPolygon); } painter->setPen(currentPen); foreach( const QPolygonF* outerPolygon, m_cachedOuterPolygons ) { painter->drawPolyline( *outerPolygon ); } foreach( const QPolygonF* innerPolygon, m_cachedInnerPolygons ) { painter->drawPolyline( *innerPolygon ); } } else { foreach( const QPolygonF* outerPolygon, m_cachedOuterPolygons ) { painter->drawPolygon( *outerPolygon ); } } painter->translate(-offset); } for (int i = 0; i < m_cachedOuterPolygons.size(); ++i) { QPolygonF* outerPolygon = m_cachedOuterPolygons[i]; QRectF const boundingRect = outerPolygon->boundingRect(); // Label position calculation if (!m_buildingLabel.isEmpty() || !m_entries.isEmpty()) { QSizeF const polygonSize = boundingRect.size(); qreal size = polygonSize.width() * polygonSize.height(); if (size > maxSize) { maxSize = size; double area; roofCenter = centroid(*outerPolygon, area); maxArea = qMax(area, maxArea); roofCenter += buildingOffset(roofCenter, viewport); } } // Draw the housenumber labels if (drawAccurate3D && !m_buildingLabel.isEmpty() && !roofCenter.isNull() && !m_cachedOuterRoofPolygons.isEmpty()) { QPolygonF * outerRoof = m_cachedOuterRoofPolygons[i]; double const w2 = 0.5 * painter->fontMetrics().width(m_buildingLabel); double const ascent = painter->fontMetrics().ascent(); double const descent = painter->fontMetrics().descent(); double const a2 = 0.5 * painter->fontMetrics().ascent(); QPointF const textPosition = roofCenter - QPointF(w2, -a2); if (outerRoof->containsPoint(textPosition + QPointF(-2, -ascent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(-2, descent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, descent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, -ascent), Qt::OddEvenFill) ) { painter->drawText(textPosition, m_buildingLabel); } } ++i; } // Render additional housenumbers at building entries if (!m_entries.isEmpty() && maxArea > 1600 * m_entries.size()) { QBrush brush = painter->brush(); QColor const brushColor = brush.color(); QColor lighterColor = brushColor.lighter(110); lighterColor.setAlphaF(0.9); brush.setColor(lighterColor); painter->setBrush(brush); foreach(const auto &entry, m_entries) { qreal x, y; viewport->screenCoordinates(entry.point, x, y); QPointF point(x, y); point += buildingOffset(point, viewport); auto const width = painter->fontMetrics().width(entry.label); auto const height = painter->fontMetrics().height(); QRectF rectangle(point, QSizeF(qMax(1.2*width, 1.1*height), 1.2*height)); rectangle.moveCenter(point); painter->drawRoundedRect(rectangle, 3, 3); painter->drawText(rectangle, Qt::AlignCenter, entry.label); } brush.setColor(brushColor); painter->setBrush(brush); } painter->restore(); } void BuildingGeoPolygonGraphicsItem::paintFrame(GeoPainter *painter, const ViewportParams *viewport) { // TODO: how does this match the Q_ASSERT in the constructor? if (m_buildingHeight == 0.0) { return; } if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4)) || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) { return; } painter->save(); bool drawAccurate3D; bool isCameraAboveBuilding; initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding); configureFramePainter(painter); if ( drawAccurate3D && isCameraAboveBuilding ) { foreach(QPolygonF* outline, m_cachedOuterPolygons) { if (outline->isEmpty()) { continue; } // draw the building sides int const size = outline->size(); QPolygonF * outerRoof = new QPolygonF; outerRoof->reserve(outline->size()); QPointF & a = (*outline)[0]; QPointF shiftA = a + buildingOffset(a, viewport); outerRoof->append(shiftA); for (int i=1; i= 0; if (!backface) { - QPolygonF buildingSide = QPolygonF() << a << shiftA << shiftB << b; + QPolygonF buildingSide; + buildingSide.reserve(4); + buildingSide << a << shiftA << shiftB << b; painter->drawPolygon(buildingSide); } a = b; shiftA = shiftB; outerRoof->append(shiftA); } m_cachedOuterRoofPolygons.append(outerRoof); } foreach(QPolygonF* outline, m_cachedInnerPolygons) { if (outline->isEmpty()) { continue; } // draw the building sides int const size = outline->size(); QPolygonF * innerRoof = new QPolygonF; innerRoof->reserve(outline->size()); QPointF & a = (*outline)[0]; QPointF shiftA = a + buildingOffset(a, viewport); innerRoof->append(shiftA); for (int i=1; i= 0; if (backface) { - QPolygonF buildingSide = QPolygonF() << a << shiftA << shiftB << b; + QPolygonF buildingSide; + buildingSide.reserve(4); + buildingSide << a << shiftA << shiftB << b; painter->drawPolygon(buildingSide); } a = b; shiftA = shiftB; innerRoof->append(shiftA); } m_cachedInnerRoofPolygons.append(innerRoof); } } else { // don't draw the building sides - just draw the base frame instead QVector fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons, m_cachedInnerPolygons ); foreach( QPolygonF* fillPolygon, fillPolygons ) { painter->drawPolygon(*fillPolygon); } } painter->restore(); } void BuildingGeoPolygonGraphicsItem::screenPolygons(const ViewportParams *viewport, const GeoDataPolygon *polygon, QVector &innerPolygons, QVector &outerPolygons ) { Q_ASSERT(polygon); viewport->screenCoordinates( polygon->outerBoundary(), outerPolygons ); QVector const & innerBoundaries = polygon->innerBoundaries(); foreach (const GeoDataLinearRing &innerBoundary, innerBoundaries) { QVector innerPolygonsPerBoundary; viewport->screenCoordinates(innerBoundary, innerPolygonsPerBoundary); innerPolygons.reserve(innerPolygons.size() + innerPolygonsPerBoundary.size()); foreach( QPolygonF* innerPolygonPerBoundary, innerPolygonsPerBoundary ) { innerPolygons << innerPolygonPerBoundary; } } } void BuildingGeoPolygonGraphicsItem::configureFramePainter(GeoPainter *painter) const { GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); const QColor transparentColor(Qt::transparent); painter->setPen(Qt::NoPen); if (!polyStyle.fill()) { painter->setBrush(transparentColor); } else { const QColor paintedColor = polyStyle.paintedColor(); painter->setBrush(paintedColor.darker(150)); } } } } diff --git a/src/lib/marble/projections/AzimuthalProjection.cpp b/src/lib/marble/projections/AzimuthalProjection.cpp index a4986278d..ee94c9b5e 100644 --- a/src/lib/marble/projections/AzimuthalProjection.cpp +++ b/src/lib/marble/projections/AzimuthalProjection.cpp @@ -1,676 +1,681 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2014 Gábor Péterffy // // Local #include "AzimuthalProjection.h" #include "AzimuthalProjection_p.h" #include "AbstractProjection_p.h" // Marble #include "GeoDataLinearRing.h" #include "GeoDataLineString.h" #include "GeoDataCoordinates.h" #include "GeoDataLatLonAltBox.h" #include "ViewportParams.h" #include namespace Marble { qreal AzimuthalProjection::maxValidLat() const { return +90.0 * DEG2RAD; } qreal AzimuthalProjection::minValidLat() const { return -90.0 * DEG2RAD; } bool AzimuthalProjection::isClippedToSphere() const { return true; } qreal AzimuthalProjection::clippingRadius() const { return 1; } bool AzimuthalProjection::screenCoordinates( const GeoDataLineString &lineString, const ViewportParams *viewport, QVector &polygons ) const { Q_D( const AzimuthalProjection ); // Compare bounding box size of the line string with the angularResolution // Immediately return if the latLonAltBox is smaller. if ( !viewport->resolves( lineString.latLonAltBox() ) ) { // mDebug() << "Object too small to be resolved"; return false; } d->lineStringToPolygon( lineString, viewport, polygons ); return true; } bool AzimuthalProjection::mapCoversViewport( const ViewportParams *viewport ) const { qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius(); qint64 width = viewport->width(); qint64 height = viewport->height(); // This first test is a quick one that will catch all really big // radii and prevent overflow in the real test. if ( radius > width + height ) return true; // This is the real test. The 4 is because we are really // comparing to width/2 and height/2. if ( 4 * radius * radius >= width * width + height * height ) return true; return false; } GeoDataLatLonAltBox AzimuthalProjection::latLonAltBox( const QRect& screenRect, const ViewportParams *viewport ) const { // For the case where the whole viewport gets covered there is a // pretty dirty and generic detection algorithm: GeoDataLatLonAltBox latLonAltBox = AbstractProjection::latLonAltBox( screenRect, viewport ); // If the whole globe is visible we can easily calculate // analytically the lon-/lat- range. qreal pitch = GeoDataCoordinates::normalizeLat( viewport->planetAxis().pitch() ); if ( 2.0 * viewport->radius() <= viewport->height() && 2.0 * viewport->radius() <= viewport->width() ) { // Unless the planetaxis is in the screen plane the allowed longitude range // covers full -180 deg to +180 deg: if ( pitch > 0.0 && pitch < +M_PI ) { latLonAltBox.setWest( -M_PI ); latLonAltBox.setEast( +M_PI ); latLonAltBox.setNorth( +fabs( M_PI / 2.0 - fabs( pitch ) ) ); latLonAltBox.setSouth( -M_PI / 2.0 ); } if ( pitch < 0.0 && pitch > -M_PI ) { latLonAltBox.setWest( -M_PI ); latLonAltBox.setEast( +M_PI ); latLonAltBox.setNorth( +M_PI / 2.0 ); latLonAltBox.setSouth( -fabs( M_PI / 2.0 - fabs( pitch ) ) ); } // Last but not least we deal with the rare case where the // globe is fully visible and pitch = 0.0 or pitch = -M_PI or // pitch = +M_PI if ( pitch == 0.0 || pitch == -M_PI || pitch == +M_PI ) { qreal yaw = viewport->planetAxis().yaw(); latLonAltBox.setWest( GeoDataCoordinates::normalizeLon( yaw - M_PI / 2.0 ) ); latLonAltBox.setEast( GeoDataCoordinates::normalizeLon( yaw + M_PI / 2.0 ) ); latLonAltBox.setNorth( +M_PI / 2.0 ); latLonAltBox.setSouth( -M_PI / 2.0 ); } return latLonAltBox; } // Now we check whether maxLat (e.g. the north pole) gets displayed // inside the viewport to get more accurate values for east and west. // We need a point on the screen at maxLat that definitely gets displayed: qreal averageLongitude = ( latLonAltBox.west() + latLonAltBox.east() ) / 2.0; GeoDataCoordinates maxLatPoint( averageLongitude, maxLat(), 0.0, GeoDataCoordinates::Radian ); GeoDataCoordinates minLatPoint( averageLongitude, minLat(), 0.0, GeoDataCoordinates::Radian ); qreal dummyX, dummyY; // not needed bool dummyVal; if ( screenCoordinates( maxLatPoint, viewport, dummyX, dummyY, dummyVal ) || screenCoordinates( minLatPoint, viewport, dummyX, dummyY, dummyVal ) ) { latLonAltBox.setWest( -M_PI ); latLonAltBox.setEast( +M_PI ); } return latLonAltBox; } QPainterPath AzimuthalProjection::mapShape( const ViewportParams *viewport ) const { int radius = viewport->radius() * viewport->currentProjection()->clippingRadius(); int imgWidth = viewport->width(); int imgHeight = viewport->height(); QPainterPath fullRect; fullRect.addRect( 0 , 0 , imgWidth, imgHeight ); // If the globe covers the whole image, then the projected region represents // all of the image. // Otherwise the active region has got the shape of the visible globe. if ( !viewport->mapCoversViewport() ) { QPainterPath mapShape; mapShape.addEllipse( imgWidth / 2 - radius, imgHeight / 2 - radius, 2 * radius, 2 * radius ); return mapShape.intersected( fullRect ); } return fullRect; } AzimuthalProjection::AzimuthalProjection(AzimuthalProjectionPrivate * dd) : AbstractProjection( dd ) { } AzimuthalProjection::~AzimuthalProjection() { } void AzimuthalProjectionPrivate::tessellateLineSegment( const GeoDataCoordinates &aCoords, qreal ax, qreal ay, const GeoDataCoordinates &bCoords, qreal bx, qreal by, QVector &polygons, const ViewportParams *viewport, TessellationFlags f, bool allowLatePolygonCut ) const { // We take the manhattan length as a distance approximation // that can be too big by a factor of sqrt(2) qreal distance = fabs((bx - ax)) + fabs((by - ay)); #ifdef SAFE_DISTANCE // Interpolate additional nodes if the line segment that connects the // current or previous nodes might cross the viewport. // The latter can pretty safely be excluded for most projections if both points // are located on the same side relative to the viewport boundaries and if they are // located more than half the line segment distance away from the viewport. const qreal safeDistance = - 0.5 * distance; if ( !( bx < safeDistance && ax < safeDistance ) || !( by < safeDistance && ay < safeDistance ) || !( bx + safeDistance > viewport->width() && ax + safeDistance > viewport->width() ) || !( by + safeDistance > viewport->height() && ay + safeDistance > viewport->height() ) ) { #endif int maxTessellationFactor = viewport->radius() < 20000 ? 10 : 20; int const finalTessellationPrecision = qBound(2, viewport->radius()/200, maxTessellationFactor) * tessellationPrecision; // Let the line segment follow the spherical surface // if the distance between the previous point and the current point // on screen is too big if ( distance > finalTessellationPrecision ) { const int tessellatedNodes = qMin( distance / finalTessellationPrecision, maxTessellationNodes ); processTessellation( aCoords, bCoords, tessellatedNodes, polygons, viewport, f, allowLatePolygonCut); } else { crossHorizon( bCoords, polygons, viewport, allowLatePolygonCut ); } #ifdef SAFE_DISTANCE } #endif } void AzimuthalProjectionPrivate::processTessellation( const GeoDataCoordinates &previousCoords, const GeoDataCoordinates ¤tCoords, int tessellatedNodes, QVector &polygons, const ViewportParams *viewport, TessellationFlags f, bool allowLatePolygonCut ) const { const bool clampToGround = f.testFlag( FollowGround ); const bool followLatitudeCircle = f.testFlag( RespectLatitudeCircle ) && previousCoords.latitude() == currentCoords.latitude(); // Calculate steps for tessellation: lonDiff and altDiff qreal lonDiff = 0.0; if ( followLatitudeCircle ) { const int previousSign = previousCoords.longitude() > 0 ? 1 : -1; const int currentSign = currentCoords.longitude() > 0 ? 1 : -1; lonDiff = currentCoords.longitude() - previousCoords.longitude(); if ( previousSign != currentSign && fabs(previousCoords.longitude()) + fabs(currentCoords.longitude()) > M_PI ) { if ( previousSign > currentSign ) { // going eastwards -> lonDiff += 2 * M_PI ; } else { // going westwards -> lonDiff -= 2 * M_PI; } } } const qreal altDiff = currentCoords.altitude() - previousCoords.altitude(); // Create the tessellation nodes. GeoDataCoordinates previousTessellatedCoords = previousCoords; for ( int i = 1; i <= tessellatedNodes; ++i ) { const qreal t = (qreal)(i) / (qreal)( tessellatedNodes + 1 ); // interpolate the altitude, too const qreal altitude = clampToGround ? 0 : altDiff * t + previousCoords.altitude(); qreal lon = 0.0; qreal lat = 0.0; if ( followLatitudeCircle ) { // To tessellate along latitude circles use the // linear interpolation of the longitude. lon = lonDiff * t + previousCoords.longitude(); lat = previousTessellatedCoords.latitude(); } else { // To tessellate along great circles use the // normalized linear interpolation ("NLERP") for latitude and longitude. const Quaternion itpos = Quaternion::nlerp( previousCoords.quaternion(), currentCoords.quaternion(), t ); itpos. getSpherical( lon, lat ); } const GeoDataCoordinates currentTessellatedCoords( lon, lat, altitude ); crossHorizon( currentTessellatedCoords, polygons, viewport, allowLatePolygonCut ); previousTessellatedCoords = currentTessellatedCoords; } // For the clampToGround case add the "current" coordinate after adding all other nodes. GeoDataCoordinates currentModifiedCoords( currentCoords ); if ( clampToGround ) { currentModifiedCoords.setAltitude( 0.0 ); } crossHorizon( currentModifiedCoords, polygons, viewport, allowLatePolygonCut ); } void AzimuthalProjectionPrivate::crossHorizon( const GeoDataCoordinates & bCoord, QVector &polygons, const ViewportParams *viewport, bool allowLatePolygonCut ) const { qreal x, y; bool globeHidesPoint; Q_Q( const AbstractProjection ); q->screenCoordinates( bCoord, viewport, x, y, globeHidesPoint ); if( !globeHidesPoint ) { *polygons.last() << QPointF( x, y ); } else { if ( allowLatePolygonCut && !polygons.last()->isEmpty() ) { QPolygonF *path = new QPolygonF; polygons.append( path ); } } } bool AzimuthalProjectionPrivate::lineStringToPolygon( const GeoDataLineString &lineString, const ViewportParams *viewport, QVector &polygons ) const { Q_Q( const AzimuthalProjection ); const TessellationFlags f = lineString.tessellationFlags(); + bool const tessellate = lineString.tessellate(); const bool noFilter = f.testFlag(PreventNodeFiltering); qreal x = 0; qreal y = 0; bool globeHidesPoint = false; qreal previousX = -1.0; qreal previousY = -1.0; bool previousGlobeHidesPoint = false; qreal horizonX = -1.0; qreal horizonY = -1.0; - polygons.append( new QPolygonF ); + QPolygonF * polygon = new QPolygonF; + if (!tessellate) { + polygon->reserve(lineString.size()); + } + polygons.append( polygon ); GeoDataLineString::ConstIterator itCoords = lineString.constBegin(); GeoDataLineString::ConstIterator itPreviousCoords = lineString.constBegin(); // Some projections display the earth in a way so that there is a // foreside and a backside. // The horizon is the line (usually a circle) which separates both // sides from each other and resembles the map shape. GeoDataCoordinates horizonCoords; // A horizon pair is a pair of two subsequent horizon crossings: // The first one describes the point where a line string disappears behind the horizon. // and where horizonPair is set to true. // The second one describes the point where the line string reappears. // In this case the two points are connected and horizonPair is set to false again. bool horizonPair = false; GeoDataCoordinates horizonDisappearCoords; // If the first horizon crossing in a line string describes the appearance of // a line string then we call it a "horizon orphan" and horizonOrphan is set to true. // In this case once the last horizon crossing in the line string is reached // it needs to be connected to the orphan. bool horizonOrphan = false; GeoDataCoordinates horizonOrphanCoords; GeoDataLineString::ConstIterator itBegin = lineString.constBegin(); GeoDataLineString::ConstIterator itEnd = lineString.constEnd(); bool processingLastNode = false; // We use a while loop to be able to cover linestrings as well as linear rings: // Linear rings require to tessellate the path from the last node to the first node // which isn't really convenient to achieve with a for loop ... const bool isLong = lineString.size() > 10; const int maximumDetail = levelForResolution(viewport->angularResolution()); // The first node of optimized linestrings has a non-zero detail value. const bool hasDetail = itBegin->detail() != 0; while ( itCoords != itEnd ) { // Optimization for line strings with a big amount of nodes bool skipNode = (hasDetail ? itCoords->detail() > maximumDetail : itCoords != itBegin && isLong && !processingLastNode && !viewport->resolves( *itPreviousCoords, *itCoords ) ); if ( !skipNode || noFilter) { q->screenCoordinates( *itCoords, viewport, x, y, globeHidesPoint ); // Initializing variables that store the values of the previous iteration if ( !processingLastNode && itCoords == itBegin ) { previousGlobeHidesPoint = globeHidesPoint; itPreviousCoords = itCoords; previousX = x; previousY = y; } // Check for the "horizon case" (which is present e.g. for the spherical projection const bool isAtHorizon = ( globeHidesPoint || previousGlobeHidesPoint ) && ( globeHidesPoint != previousGlobeHidesPoint ); if ( isAtHorizon ) { // Handle the "horizon case" horizonCoords = findHorizon( *itPreviousCoords, *itCoords, viewport, f ); if ( lineString.isClosed() ) { if ( horizonPair ) { horizonToPolygon( viewport, horizonDisappearCoords, horizonCoords, polygons.last() ); horizonPair = false; } else { if ( globeHidesPoint ) { horizonDisappearCoords = horizonCoords; horizonPair = true; } else { horizonOrphanCoords = horizonCoords; horizonOrphan = true; } } } q->screenCoordinates( horizonCoords, viewport, horizonX, horizonY ); // If the line appears on the visible half we need // to add an interpolated point at the horizon as the previous point. if ( previousGlobeHidesPoint ) { *polygons.last() << QPointF( horizonX, horizonY ); } } // This if-clause contains the section that tessellates the line // segments of a linestring. If you are about to learn how the code of // this class works you can safely ignore this section for a start. if ( lineString.tessellate() /* && ( isVisible || previousIsVisible ) */ ) { if ( !isAtHorizon ) { tessellateLineSegment( *itPreviousCoords, previousX, previousY, *itCoords, x, y, polygons, viewport, f, !lineString.isClosed() ); } else { // Connect the interpolated point at the horizon with the // current or previous point in the line. if ( previousGlobeHidesPoint ) { tessellateLineSegment( horizonCoords, horizonX, horizonY, *itCoords, x, y, polygons, viewport, f, !lineString.isClosed() ); } else { tessellateLineSegment( *itPreviousCoords, previousX, previousY, horizonCoords, horizonX, horizonY, polygons, viewport, f, !lineString.isClosed() ); } } } else { if ( !globeHidesPoint ) { *polygons.last() << QPointF( x, y ); } else { if ( !previousGlobeHidesPoint && isAtHorizon ) { *polygons.last() << QPointF( horizonX, horizonY ); } } } if ( globeHidesPoint ) { if ( !previousGlobeHidesPoint && !lineString.isClosed() ) { polygons.append( new QPolygonF ); } } previousGlobeHidesPoint = globeHidesPoint; itPreviousCoords = itCoords; previousX = x; previousY = y; } // Here we modify the condition to be able to process the // first node after the last node in a LinearRing. if ( processingLastNode ) { break; } ++itCoords; if ( itCoords == itEnd && lineString.isClosed() ) { itCoords = itBegin; processingLastNode = true; } } // In case of horizon crossings, make sure that we always get a // polygon closed correctly. if ( horizonOrphan && lineString.isClosed() ) { horizonToPolygon( viewport, horizonCoords, horizonOrphanCoords, polygons.last() ); } if ( polygons.last()->size() <= 1 ){ delete polygons.last(); polygons.pop_back(); // Clean up "unused" empty polygon instances } return polygons.isEmpty(); } void AzimuthalProjectionPrivate::horizonToPolygon( const ViewportParams *viewport, const GeoDataCoordinates & disappearCoords, const GeoDataCoordinates & reappearCoords, QPolygonF * polygon ) const { qreal x, y; const qreal imageHalfWidth = viewport->width() / 2; const qreal imageHalfHeight = viewport->height() / 2; bool dummyGlobeHidesPoint = false; Q_Q( const AzimuthalProjection ); // Calculate the angle of the position vectors of both coordinates q->screenCoordinates( disappearCoords, viewport, x, y, dummyGlobeHidesPoint ); qreal alpha = atan2( y - imageHalfHeight, x - imageHalfWidth ); q->screenCoordinates( reappearCoords, viewport, x, y, dummyGlobeHidesPoint ); qreal beta = atan2( y - imageHalfHeight, x - imageHalfWidth ); // Calculate the difference between both qreal diff = GeoDataCoordinates::normalizeLon( beta - alpha ); qreal sgndiff = diff < 0 ? -1 : 1; const qreal arcradius = q->clippingRadius() * viewport->radius(); const int itEnd = fabs(diff * RAD2DEG); // Create a polygon that resembles an arc between the two position vectors polygon->reserve(polygon->size() + itEnd); for ( int it = 1; it <= itEnd; ++it ) { const qreal angle = alpha + DEG2RAD * sgndiff * it; const qreal itx = imageHalfWidth + arcradius * cos( angle ); const qreal ity = imageHalfHeight + arcradius * sin( angle ); *polygon << QPointF( itx, ity ); } } GeoDataCoordinates AzimuthalProjectionPrivate::findHorizon( const GeoDataCoordinates & previousCoords, const GeoDataCoordinates & currentCoords, const ViewportParams *viewport, TessellationFlags f) const { bool currentHide = globeHidesPoint( currentCoords, viewport ) ; return doFindHorizon(previousCoords, currentCoords, viewport, f, currentHide, 0); } GeoDataCoordinates AzimuthalProjectionPrivate::doFindHorizon( const GeoDataCoordinates & previousCoords, const GeoDataCoordinates & currentCoords, const ViewportParams *viewport, TessellationFlags f, bool currentHide, int recursionCounter ) const { if ( recursionCounter > 20 ) { return currentHide ? previousCoords : currentCoords; } ++recursionCounter; bool followLatitudeCircle = false; // Calculate steps for tessellation: lonDiff and altDiff qreal lonDiff = 0.0; qreal previousLongitude = 0.0; qreal previousLatitude = 0.0; if ( f.testFlag( RespectLatitudeCircle ) ) { previousCoords.geoCoordinates( previousLongitude, previousLatitude ); qreal previousSign = previousLongitude > 0 ? 1 : -1; qreal currentLongitude = 0.0; qreal currentLatitude = 0.0; currentCoords.geoCoordinates( currentLongitude, currentLatitude ); qreal currentSign = currentLongitude > 0 ? 1 : -1; if ( previousLatitude == currentLatitude ) { followLatitudeCircle = true; lonDiff = currentLongitude - previousLongitude; if ( previousSign != currentSign && fabs(previousLongitude) + fabs(currentLongitude) > M_PI ) { if ( previousSign > currentSign ) { // going eastwards -> lonDiff += 2 * M_PI ; } else { // going westwards -> lonDiff -= 2 * M_PI; } } } else { // mDebug() << "Don't FollowLatitudeCircle"; } } qreal lon = 0.0; qreal lat = 0.0; qreal altDiff = currentCoords.altitude() - previousCoords.altitude(); if ( followLatitudeCircle ) { // To tessellate along latitude circles use the // linear interpolation of the longitude. lon = lonDiff * 0.5 + previousLongitude; lat = previousLatitude; } else { // To tessellate along great circles use the // normalized linear interpolation ("NLERP") for latitude and longitude. const Quaternion itpos = Quaternion::nlerp( previousCoords.quaternion(), currentCoords.quaternion(), 0.5 ); itpos. getSpherical( lon, lat ); } qreal altitude = previousCoords.altitude() + 0.5 * altDiff; GeoDataCoordinates horizonCoords( lon, lat, altitude ); bool horizonHide = globeHidesPoint( horizonCoords, viewport ); if ( horizonHide != currentHide ) { return doFindHorizon(horizonCoords, currentCoords, viewport, f, currentHide, recursionCounter); } return doFindHorizon(previousCoords, horizonCoords, viewport, f, horizonHide, recursionCounter); } bool AzimuthalProjectionPrivate::globeHidesPoint( const GeoDataCoordinates &coordinates, const ViewportParams *viewport ) const { bool globeHidesPoint; qreal dummyX, dummyY; Q_Q( const AzimuthalProjection ); q->screenCoordinates(coordinates, viewport, dummyX, dummyY, globeHidesPoint); return globeHidesPoint; } } diff --git a/src/lib/marble/projections/CylindricalProjection.cpp b/src/lib/marble/projections/CylindricalProjection.cpp index cd8644295..aad8805a7 100644 --- a/src/lib/marble/projections/CylindricalProjection.cpp +++ b/src/lib/marble/projections/CylindricalProjection.cpp @@ -1,492 +1,496 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Inge Wallin // Copyright 2007-2012 Torsten Rahn // Copyright 2012 Cezar Mocan // // Local #include "CylindricalProjection.h" #include "CylindricalProjection_p.h" // Marble #include "GeoDataLinearRing.h" #include "GeoDataLineString.h" #include "GeoDataCoordinates.h" #include "GeoDataLatLonAltBox.h" #include "ViewportParams.h" #include // Maximum amount of nodes that are created automatically between actual nodes. static const int maxTessellationNodes = 200; namespace Marble { CylindricalProjection::CylindricalProjection() : AbstractProjection( new CylindricalProjectionPrivate( this ) ) { } CylindricalProjection::CylindricalProjection( CylindricalProjectionPrivate* dd ) : AbstractProjection( dd ) { } CylindricalProjection::~CylindricalProjection() { } CylindricalProjectionPrivate::CylindricalProjectionPrivate( CylindricalProjection * parent ) : AbstractProjectionPrivate( parent ), q_ptr( parent ) { } QPainterPath CylindricalProjection::mapShape( const ViewportParams *viewport ) const { // Convenience variables int width = viewport->width(); int height = viewport->height(); qreal yTop; qreal yBottom; qreal xDummy; // Get the top and bottom coordinates of the projected map. screenCoordinates( 0.0, maxLat(), viewport, xDummy, yTop ); screenCoordinates( 0.0, minLat(), viewport, xDummy, yBottom ); // Don't let the map area be outside the image if ( yTop < 0 ) yTop = 0; if ( yBottom > height ) yBottom = height; QPainterPath mapShape; mapShape.addRect( 0, yTop, width, yBottom - yTop ); return mapShape; } bool CylindricalProjection::screenCoordinates( const GeoDataLineString &lineString, const ViewportParams *viewport, QVector &polygons ) const { Q_D( const CylindricalProjection ); // Compare bounding box size of the line string with the angularResolution // Immediately return if the latLonAltBox is smaller. if ( !viewport->resolves( lineString.latLonAltBox() ) ) { // mDebug() << "Object too small to be resolved"; return false; } QVector subPolygons; d->lineStringToPolygon( lineString, viewport, subPolygons ); polygons << subPolygons; return polygons.isEmpty(); } int CylindricalProjectionPrivate::tessellateLineSegment( const GeoDataCoordinates &aCoords, qreal ax, qreal ay, const GeoDataCoordinates &bCoords, qreal bx, qreal by, QVector &polygons, const ViewportParams *viewport, TessellationFlags f, int mirrorCount, qreal repeatDistance) const { // We take the manhattan length as a distance approximation // that can be too big by a factor of sqrt(2) qreal distance = fabs((bx - ax)) + fabs((by - ay)); #ifdef SAFE_DISTANCE // Interpolate additional nodes if the line segment that connects the // current or previous nodes might cross the viewport. // The latter can pretty safely be excluded for most projections if both points // are located on the same side relative to the viewport boundaries and if they are // located more than half the line segment distance away from the viewport. const qreal safeDistance = - 0.5 * distance; if ( !( bx < safeDistance && ax < safeDistance ) || !( by < safeDistance && ay < safeDistance ) || !( bx + safeDistance > viewport->width() && ax + safeDistance > viewport->width() ) || !( by + safeDistance > viewport->height() && ay + safeDistance > viewport->height() ) ) { #endif int maxTessellationFactor = viewport->radius() < 20000 ? 10 : 20; int const finalTessellationPrecision = qBound(2, viewport->radius()/200, maxTessellationFactor) * tessellationPrecision; // Let the line segment follow the spherical surface // if the distance between the previous point and the current point // on screen is too big if ( distance > finalTessellationPrecision ) { const int tessellatedNodes = qMin( distance / finalTessellationPrecision, maxTessellationNodes ); mirrorCount = processTessellation( aCoords, bCoords, tessellatedNodes, polygons, viewport, f, mirrorCount, repeatDistance ); } else { mirrorCount = crossDateLine( aCoords, bCoords, bx, by, polygons, mirrorCount, repeatDistance ); } #ifdef SAFE_DISTANCE } #endif return mirrorCount; } int CylindricalProjectionPrivate::processTessellation( const GeoDataCoordinates &previousCoords, const GeoDataCoordinates ¤tCoords, int tessellatedNodes, QVector &polygons, const ViewportParams *viewport, TessellationFlags f, int mirrorCount, qreal repeatDistance) const { const bool clampToGround = f.testFlag( FollowGround ); const bool followLatitudeCircle = f.testFlag( RespectLatitudeCircle ) && previousCoords.latitude() == currentCoords.latitude(); // Calculate steps for tessellation: lonDiff and altDiff qreal lonDiff = 0.0; if ( followLatitudeCircle ) { const int previousSign = previousCoords.longitude() > 0 ? 1 : -1; const int currentSign = currentCoords.longitude() > 0 ? 1 : -1; lonDiff = currentCoords.longitude() - previousCoords.longitude(); if ( previousSign != currentSign && fabs(previousCoords.longitude()) + fabs(currentCoords.longitude()) > M_PI ) { if ( previousSign > currentSign ) { // going eastwards -> lonDiff += 2 * M_PI ; } else { // going westwards -> lonDiff -= 2 * M_PI; } } if ( fabs( lonDiff ) == 2 * M_PI ) { return mirrorCount; } } const qreal altDiff = currentCoords.altitude() - previousCoords.altitude(); // Create the tessellation nodes. GeoDataCoordinates previousTessellatedCoords = previousCoords; for ( int i = 1; i <= tessellatedNodes; ++i ) { const qreal t = (qreal)(i) / (qreal)( tessellatedNodes + 1 ); // interpolate the altitude, too const qreal altitude = clampToGround ? 0 : altDiff * t + previousCoords.altitude(); qreal lon = 0.0; qreal lat = 0.0; if ( followLatitudeCircle ) { // To tessellate along latitude circles use the // linear interpolation of the longitude. lon = lonDiff * t + previousCoords.longitude(); lat = previousTessellatedCoords.latitude(); } else { // To tessellate along great circles use the // normalized linear interpolation ("NLERP") for latitude and longitude. const Quaternion itpos = Quaternion::nlerp( previousCoords.quaternion(), currentCoords.quaternion(), t ); itpos. getSpherical( lon, lat ); } const GeoDataCoordinates currentTessellatedCoords( lon, lat, altitude ); Q_Q(const CylindricalProjection); qreal bx, by; q->screenCoordinates( currentTessellatedCoords, viewport, bx, by ); mirrorCount = crossDateLine( previousTessellatedCoords, currentTessellatedCoords, bx, by, polygons, mirrorCount, repeatDistance ); previousTessellatedCoords = currentTessellatedCoords; } // For the clampToGround case add the "current" coordinate after adding all other nodes. GeoDataCoordinates currentModifiedCoords( currentCoords ); if ( clampToGround ) { currentModifiedCoords.setAltitude( 0.0 ); } Q_Q(const CylindricalProjection); qreal bx, by; q->screenCoordinates( currentModifiedCoords, viewport, bx, by ); mirrorCount = crossDateLine( previousTessellatedCoords, currentModifiedCoords, bx, by, polygons, mirrorCount, repeatDistance ); return mirrorCount; } int CylindricalProjectionPrivate::crossDateLine( const GeoDataCoordinates & aCoord, const GeoDataCoordinates & bCoord, qreal bx, qreal by, QVector &polygons, int mirrorCount, qreal repeatDistance ) { qreal aLon = aCoord.longitude(); qreal aSign = aLon > 0 ? 1 : -1; qreal bLon = bCoord.longitude(); qreal bSign = bLon > 0 ? 1 : -1; qreal delta = 0; if( aSign != bSign && fabs(aLon) + fabs(bLon) > M_PI ) { int sign = aSign > bSign ? 1 : -1; mirrorCount += sign; } delta = repeatDistance * mirrorCount; *polygons.last() << QPointF( bx + delta, by ); return mirrorCount; } bool CylindricalProjectionPrivate::lineStringToPolygon( const GeoDataLineString &lineString, const ViewportParams *viewport, QVector &polygons ) const { const TessellationFlags f = lineString.tessellationFlags(); + bool const tessellate = lineString.tessellate(); const bool noFilter = f.testFlag(PreventNodeFiltering); qreal x = 0; qreal y = 0; qreal previousX = -1.0; qreal previousY = -1.0; int mirrorCount = 0; qreal distance = repeatDistance( viewport ); - polygons.append( new QPolygonF ); + QPolygonF * polygon = new QPolygonF; + if (!tessellate) { + polygon->reserve(lineString.size()); + } + polygons.append( polygon ); GeoDataLineString::ConstIterator itCoords = lineString.constBegin(); GeoDataLineString::ConstIterator itPreviousCoords = lineString.constBegin(); GeoDataLineString::ConstIterator itBegin = lineString.constBegin(); GeoDataLineString::ConstIterator itEnd = lineString.constEnd(); bool processingLastNode = false; // We use a while loop to be able to cover linestrings as well as linear rings: // Linear rings require to tessellate the path from the last node to the first node // which isn't really convenient to achieve with a for loop ... const bool isLong = lineString.size() > 10; const int maximumDetail = levelForResolution(viewport->angularResolution()); // The first node of optimized linestrings has a non-zero detail value. const bool hasDetail = itBegin->detail() != 0; bool isStraight = lineString.latLonAltBox().height() == 0 || lineString.latLonAltBox().width() == 0; Q_Q( const CylindricalProjection ); - bool const tesselate = lineString.tessellate(); bool const isClosed = lineString.isClosed(); while ( itCoords != itEnd ) { // Optimization for line strings with a big amount of nodes bool skipNode = (hasDetail ? itCoords->detail() > maximumDetail : isLong && !processingLastNode && itCoords != itBegin && !viewport->resolves( *itPreviousCoords, *itCoords ) ); if ( !skipNode || noFilter) { q->screenCoordinates( *itCoords, viewport, x, y ); // Initializing variables that store the values of the previous iteration if ( !processingLastNode && itCoords == itBegin ) { itPreviousCoords = itCoords; previousX = x; previousY = y; } // This if-clause contains the section that tessellates the line // segments of a linestring. If you are about to learn how the code of // this class works you can safely ignore this section for a start. - if ( tesselate && !isStraight) { + if ( tessellate && !isStraight) { mirrorCount = tessellateLineSegment( *itPreviousCoords, previousX, previousY, *itCoords, x, y, polygons, viewport, f, mirrorCount, distance ); } else { // special case for polys which cross dateline but have no Tesselation Flag // the expected rendering is a screen coordinates straight line between // points, but in projections with repeatX things are not smooth mirrorCount = crossDateLine( *itPreviousCoords, *itCoords, x, y, polygons, mirrorCount, distance ); } itPreviousCoords = itCoords; previousX = x; previousY = y; } // Here we modify the condition to be able to process the // first node after the last node in a LinearRing. if ( processingLastNode ) { break; } ++itCoords; if (isClosed && itCoords == itEnd) { itCoords = itBegin; processingLastNode = true; } } // Closing e.g. in the Antarctica case. // This code makes the assumption that // - the first node is located at 180 E // - and the last node is located at 180 W // TODO: add a similar pattern in the crossDateLine() code. /* GeoDataLatLonAltBox box = lineString.latLonAltBox(); if( lineString.isClosed() && box.width() == 2*M_PI ) { QPolygonF *poly = polygons.last(); if( box.containsPole( NorthPole ) ) { qreal topMargin = 0.0; qreal dummy = 0.0; q_ptr->screenCoordinates(0.0, q_ptr->maxLat(), viewport, topMargin, dummy ); poly->push_back( QPointF( poly->last().x(), topMargin ) ); poly->push_back( QPointF( poly->first().x(), topMargin ) ); } else { qreal bottomMargin = 0.0; qreal dummy = 0.0; q_ptr->screenCoordinates(0.0, q_ptr->minLat(), viewport, bottomMargin, dummy ); poly->push_back( QPointF( poly->last().x(), bottomMargin ) ); poly->push_back( QPointF( poly->first().x(), bottomMargin ) ); } } */ repeatPolygons( viewport, polygons ); return polygons.isEmpty(); } void CylindricalProjectionPrivate::translatePolygons( const QVector &polygons, QVector &translatedPolygons, qreal xOffset ) { // mDebug() << "Translation: " << xOffset; translatedPolygons.reserve(polygons.size()); QVector::const_iterator itPolygon = polygons.constBegin(); QVector::const_iterator itEnd = polygons.constEnd(); for( ; itPolygon != itEnd; ++itPolygon ) { QPolygonF * polygon = new QPolygonF; *polygon = **itPolygon; polygon->translate( xOffset, 0 ); translatedPolygons.append( polygon ); } } void CylindricalProjectionPrivate::repeatPolygons( const ViewportParams *viewport, QVector &polygons ) const { Q_Q( const CylindricalProjection ); qreal xEast = 0; qreal xWest = 0; qreal y = 0; // Choose a latitude that is inside the viewport. qreal centerLatitude = viewport->viewLatLonAltBox().center().latitude(); GeoDataCoordinates westCoords( -M_PI, centerLatitude ); GeoDataCoordinates eastCoords( +M_PI, centerLatitude ); q->screenCoordinates( westCoords, viewport, xWest, y ); q->screenCoordinates( eastCoords, viewport, xEast, y ); if ( xWest <= 0 && xEast >= viewport->width() - 1 ) { // mDebug() << "No repeats"; return; } qreal repeatXInterval = xEast - xWest; qreal repeatsLeft = 0; qreal repeatsRight = 0; if ( xWest > 0 ) { repeatsLeft = (int)( xWest / repeatXInterval ) + 1; } if ( xEast < viewport->width() ) { repeatsRight = (int)( ( viewport->width() - xEast ) / repeatXInterval ) + 1; } QVector repeatedPolygons; QVector translatedPolygons; qreal xOffset = 0; qreal it = repeatsLeft; while ( it > 0 ) { xOffset = -it * repeatXInterval; translatePolygons( polygons, translatedPolygons, xOffset ); repeatedPolygons << translatedPolygons; translatedPolygons.clear(); --it; } repeatedPolygons << polygons; it = 1; while ( it <= repeatsRight ) { xOffset = +it * repeatXInterval; translatePolygons( polygons, translatedPolygons, xOffset ); repeatedPolygons << translatedPolygons; translatedPolygons.clear(); ++it; } polygons = repeatedPolygons; // mDebug() << Q_FUNC_INFO << "Coordinates: " << xWest << xEast // << "Repeats: " << repeatsLeft << repeatsRight; } qreal CylindricalProjectionPrivate::repeatDistance( const ViewportParams *viewport ) const { // Choose a latitude that is inside the viewport. qreal centerLatitude = viewport->viewLatLonAltBox().center().latitude(); GeoDataCoordinates westCoords( -M_PI, centerLatitude ); GeoDataCoordinates eastCoords( +M_PI, centerLatitude ); qreal xWest, xEast, dummyY; Q_Q( const AbstractProjection ); q->screenCoordinates( westCoords, viewport, xWest, dummyY ); q->screenCoordinates( eastCoords, viewport, xEast, dummyY ); return xEast - xWest; } }