File indexing completed on 2024-04-14 03:47:59

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
0004 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org>
0005 // SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0006 //
0007 
0008 #include "PlacemarkLayout.h"
0009 
0010 #include <QAbstractItemModel>
0011 #include <QList>
0012 #include <QPoint>
0013 #include <QVectorIterator>
0014 #include <QFont>
0015 #include <QFontMetrics>
0016 #include <QItemSelectionModel>
0017 #include <qmath.h>
0018 
0019 #include "GeoDataLatLonAltBox.h"
0020 #include "GeoDataLatLonBox.h"
0021 #include "GeoDataStyle.h"
0022 #include "GeoDataIconStyle.h"
0023 #include "GeoDataLabelStyle.h"
0024 #include "OsmPlacemarkData.h"
0025 
0026 #include "MarbleDebug.h"
0027 #include "MarbleGlobal.h"
0028 #include "PlacemarkLayer.h"
0029 #include "MarbleClock.h"
0030 #include "MarblePlacemarkModel.h"
0031 #include "MarbleDirs.h"
0032 #include "ViewportParams.h"
0033 #include "TileId.h"
0034 #include "TileCoordsPyramid.h"
0035 #include "VisiblePlacemark.h"
0036 #include "MathHelper.h"
0037 #include <StyleBuilder.h>
0038 
0039 namespace
0040 {   //Helper function that checks for available room for the label
0041     bool hasRoomFor(const QVector<Marble::VisiblePlacemark*> & placemarks, const QRectF &boundingBox)
0042     {
0043         // Check if there is another label or symbol that overlaps.
0044         QVector<Marble::VisiblePlacemark*>::const_iterator beforeItEnd = placemarks.constEnd();
0045         for ( QVector<Marble::VisiblePlacemark*>::ConstIterator beforeIt = placemarks.constBegin();
0046               beforeIt != beforeItEnd; ++beforeIt )
0047         {
0048             if ( boundingBox.intersects( (*beforeIt)->boundingBox() ) ) {
0049                 return false;
0050             }
0051         }
0052         return true;
0053     }
0054 }
0055 
0056 namespace Marble
0057 {
0058 
0059 QSet<GeoDataPlacemark::GeoDataVisualCategory> acceptedVisualCategories()
0060 {
0061     QSet<GeoDataPlacemark::GeoDataVisualCategory> visualCategories;
0062 
0063     visualCategories
0064         << GeoDataPlacemark::SmallCity
0065         << GeoDataPlacemark::SmallCountyCapital
0066         << GeoDataPlacemark::SmallStateCapital
0067         << GeoDataPlacemark::SmallNationCapital
0068         << GeoDataPlacemark::MediumCity
0069         << GeoDataPlacemark::MediumCountyCapital
0070         << GeoDataPlacemark::MediumStateCapital
0071         << GeoDataPlacemark::MediumNationCapital
0072         << GeoDataPlacemark::BigCity
0073         << GeoDataPlacemark::BigCountyCapital
0074         << GeoDataPlacemark::BigStateCapital
0075         << GeoDataPlacemark::BigNationCapital
0076         << GeoDataPlacemark::LargeCity
0077         << GeoDataPlacemark::LargeCountyCapital
0078         << GeoDataPlacemark::LargeStateCapital
0079         << GeoDataPlacemark::LargeNationCapital
0080         << GeoDataPlacemark::Nation
0081         << GeoDataPlacemark::Mountain
0082         << GeoDataPlacemark::Volcano
0083         << GeoDataPlacemark::Mons
0084         << GeoDataPlacemark::Valley
0085         << GeoDataPlacemark::Continent
0086         << GeoDataPlacemark::Ocean
0087         << GeoDataPlacemark::OtherTerrain
0088         << GeoDataPlacemark::Crater
0089         << GeoDataPlacemark::Mare
0090         << GeoDataPlacemark::GeographicPole
0091         << GeoDataPlacemark::MagneticPole
0092         << GeoDataPlacemark::ShipWreck
0093         << GeoDataPlacemark::PlaceSuburb
0094         << GeoDataPlacemark::PlaceHamlet
0095         << GeoDataPlacemark::PlaceLocality;
0096 
0097     return visualCategories;
0098 }
0099 
0100 
0101 PlacemarkLayout::PlacemarkLayout( QAbstractItemModel  *placemarkModel,
0102                                   QItemSelectionModel *selectionModel,
0103                                   MarbleClock *clock,
0104                                   const StyleBuilder *styleBuilder,
0105                                   QObject* parent )
0106     : QObject( parent ),
0107       m_placemarkModel(placemarkModel),
0108       m_selectionModel( selectionModel ),
0109       m_clock( clock ),
0110       m_acceptedVisualCategories( acceptedVisualCategories() ),
0111       m_showPlaces( false ),
0112       m_showCities( false ),
0113       m_showTerrain( false ),
0114       m_showOtherPlaces( false ),
0115       m_showLandingSites( false ),
0116       m_showCraters( false ),
0117       m_showMaria( false ),
0118       m_maxLabelHeight(maxLabelHeight()),
0119       m_styleResetRequested( true ),
0120       m_styleBuilder(styleBuilder),
0121       m_lastPlacemarkAvailable(false)
0122 {
0123     Q_ASSERT(m_placemarkModel);
0124 
0125     connect( m_selectionModel,  SIGNAL( selectionChanged( QItemSelection,
0126                                                            QItemSelection) ),
0127              this,               SLOT(requestStyleReset()) );
0128 
0129     connect( m_placemarkModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0130              this, SLOT(resetCacheData()) );
0131     connect( m_placemarkModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
0132              this, SLOT(addPlacemarks(QModelIndex,int,int)) );
0133     connect( m_placemarkModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
0134              this, SLOT(removePlacemarks(QModelIndex,int,int)) );
0135     connect( m_placemarkModel, SIGNAL(modelReset()),
0136              this, SLOT(resetCacheData()) );
0137 }
0138 
0139 PlacemarkLayout::~PlacemarkLayout()
0140 {
0141     styleReset();
0142 }
0143 
0144 void PlacemarkLayout::setShowPlaces( bool show )
0145 {
0146     m_showPlaces = show;
0147 }
0148 
0149 void PlacemarkLayout::setShowCities( bool show )
0150 {
0151     m_showCities = show;
0152 }
0153 
0154 void PlacemarkLayout::setShowTerrain( bool show )
0155 {
0156     m_showTerrain = show;
0157 }
0158 
0159 void PlacemarkLayout::setShowOtherPlaces( bool show )
0160 {
0161     m_showOtherPlaces = show;
0162 }
0163 
0164 void PlacemarkLayout::setShowLandingSites( bool show )
0165 {
0166     m_showLandingSites = show;
0167 }
0168 
0169 void PlacemarkLayout::setShowCraters( bool show )
0170 {
0171     m_showCraters = show;
0172 }
0173 
0174 void PlacemarkLayout::setShowMaria( bool show )
0175 {
0176     m_showMaria = show;
0177 }
0178 
0179 void PlacemarkLayout::requestStyleReset()
0180 {
0181     mDebug() << "Style reset requested.";
0182     m_styleResetRequested = true;
0183 }
0184 
0185 void PlacemarkLayout::styleReset()
0186 {
0187     clearCache();
0188     m_maxLabelHeight = maxLabelHeight();
0189     m_styleResetRequested = false;
0190 }
0191 
0192 void PlacemarkLayout::clearCache()
0193 {
0194     m_paintOrder.clear();
0195     m_lastPlacemarkAvailable = false;
0196     m_lastPlacemarkLabelRect = QRectF();
0197     m_lastPlacemarkSymbolRect = QRectF();
0198     m_labelArea = 0;
0199     qDeleteAll( m_visiblePlacemarks );
0200     m_visiblePlacemarks.clear();
0201 }
0202 
0203 QVector<const GeoDataFeature*> PlacemarkLayout::whichPlacemarkAt( const QPoint& curpos )
0204 {
0205     if ( m_styleResetRequested ) {
0206         styleReset();
0207     }
0208 
0209     QVector<const GeoDataFeature*> ret;
0210 
0211     for( VisiblePlacemark* mark: m_paintOrder ) {
0212         if ( mark->labelRect().contains( curpos ) || mark->symbolRect().contains( curpos ) ) {
0213             ret.append( mark->placemark() );
0214         }
0215     }
0216 
0217     return ret;
0218 }
0219 
0220 int PlacemarkLayout::maxLabelHeight()
0221 {
0222     QFont const standardFont(QStringLiteral("Sans Serif"));
0223     return QFontMetrics(standardFont).height();
0224 }
0225 
0226 /// feed an internal QMap of placemarks with TileId as key when model changes
0227 void PlacemarkLayout::addPlacemarks( const QModelIndex& parent, int first, int last )
0228 {
0229     Q_ASSERT( first < m_placemarkModel->rowCount() );
0230     Q_ASSERT( last < m_placemarkModel->rowCount() );
0231     for( int i=first; i<=last; ++i ) {
0232         QModelIndex index = m_placemarkModel->index( i, 0, parent );
0233         Q_ASSERT( index.isValid() );
0234         auto const object = qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole));
0235         if (const GeoDataPlacemark *placemark = geodata_cast<GeoDataPlacemark>(object)) {
0236             const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
0237             if ( !coordinates.isValid() ) {
0238                 continue;
0239             }
0240 
0241             if (placemark->hasOsmData()) {
0242                 qint64 const osmId = placemark->osmData().id();
0243                 if (osmId > 0) {
0244                     if (m_osmIds.contains(osmId)) {
0245                         continue; // placemark is already shown
0246                     }
0247                     m_osmIds << osmId;
0248                 }
0249             }
0250 
0251             int zoomLevel = placemark->zoomLevel();
0252             TileId key = TileId::fromCoordinates( coordinates, zoomLevel );
0253             m_placemarkCache[key].append( placemark );
0254         }
0255     }
0256     emit repaintNeeded();
0257 }
0258 
0259 void PlacemarkLayout::removePlacemarks( const QModelIndex& parent, int first, int last )
0260 {
0261     Q_ASSERT( first < m_placemarkModel->rowCount() );
0262     Q_ASSERT( last < m_placemarkModel->rowCount() );
0263     for( int i=first; i<=last; ++i ) {
0264         QModelIndex index = m_placemarkModel->index( i, 0, parent );
0265         Q_ASSERT( index.isValid() );
0266         const GeoDataPlacemark *placemark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>( index.data( MarblePlacemarkModel::ObjectPointerRole ) ));
0267         const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
0268         if ( !coordinates.isValid() ) {
0269             continue;
0270         }
0271 
0272         int zoomLevel = placemark->zoomLevel();
0273         TileId key = TileId::fromCoordinates( coordinates, zoomLevel );
0274         delete m_visiblePlacemarks[placemark];
0275         m_visiblePlacemarks.remove(placemark);
0276         m_placemarkCache[key].removeAll( placemark );
0277         if (placemark->hasOsmData()) {
0278             qint64 const osmId = placemark->osmData().id();
0279             if (osmId > 0) {
0280                 m_osmIds.remove(osmId);
0281             }
0282         }
0283     }
0284     emit repaintNeeded();
0285 }
0286 
0287 void PlacemarkLayout::resetCacheData()
0288 {
0289     const int rowCount = m_placemarkModel->rowCount();
0290 
0291     m_osmIds.clear();
0292     m_placemarkCache.clear();
0293     qDeleteAll(m_visiblePlacemarks);
0294     m_visiblePlacemarks.clear();
0295     requestStyleReset();
0296     addPlacemarks( m_placemarkModel->index( 0, 0 ), 0, rowCount );
0297     emit repaintNeeded();
0298 }
0299 
0300 QSet<TileId> PlacemarkLayout::visibleTiles(const ViewportParams &viewport, int zoomLevel)
0301 {
0302     /*
0303      * rely on m_placemarkCache to find the placemarks for the tiles which
0304      * matter. The top level tiles have the more popular placemarks,
0305      * the bottom level tiles have the smaller ones, and we only get the ones
0306      * matching our latLonAltBox.
0307      */
0308 
0309     qreal north, south, east, west;
0310     viewport.viewLatLonAltBox().boundaries(north, south, east, west);
0311     QSet<TileId> tileIdSet;
0312     QVector<GeoDataLatLonBox> geoRects;
0313     if( west <= east ) {
0314         geoRects << GeoDataLatLonBox(north, south, east, west);
0315     } else {
0316         geoRects << GeoDataLatLonBox(north, south, M_PI, west);
0317         geoRects << GeoDataLatLonBox(north, south, east, -M_PI);
0318     }
0319 
0320     for (const GeoDataLatLonBox &geoRect : geoRects) {
0321         TileId key;
0322         QRect rect;
0323 
0324         key = TileId::fromCoordinates(GeoDataCoordinates(geoRect.west(), north), zoomLevel);
0325         rect.setLeft( key.x() );
0326         rect.setTop( key.y() );
0327 
0328         key = TileId::fromCoordinates(GeoDataCoordinates(geoRect.east(), south), zoomLevel);
0329         rect.setRight( key.x() );
0330         rect.setBottom( key.y() );
0331 
0332         TileCoordsPyramid pyramid(0, zoomLevel );
0333         pyramid.setBottomLevelCoords( rect );
0334 
0335         for ( int level = pyramid.topLevel(); level <= pyramid.bottomLevel(); ++level ) {
0336         QRect const coords = pyramid.coords( level );
0337         int x1, y1, x2, y2;
0338         coords.getCoords( &x1, &y1, &x2, &y2 );
0339             for ( int x = x1; x <= x2; ++x ) {
0340                 for ( int y = y1; y <= y2; ++y ) {
0341                     TileId const tileId( 0, level, x, y );
0342                     tileIdSet.insert(tileId);
0343                 }
0344             }
0345         }
0346     }
0347 
0348     return tileIdSet;
0349 }
0350 
0351 QVector<VisiblePlacemark *> PlacemarkLayout::generateLayout( const ViewportParams *viewport, int tileLevel )
0352 {
0353     m_runtimeTrace.clear();
0354     if ( m_placemarkModel->rowCount() <= 0 ) {
0355         clearCache();
0356         return QVector<VisiblePlacemark *>();
0357     }
0358 
0359     if ( m_styleResetRequested ) {
0360         styleReset();
0361     }
0362 
0363     if ( m_maxLabelHeight == 0 ) {
0364         clearCache();
0365         return QVector<VisiblePlacemark *>();
0366     }
0367 
0368     QList<const GeoDataPlacemark*> placemarkList;
0369     int currentMaxLabelHeight;
0370     do {
0371         currentMaxLabelHeight = m_maxLabelHeight;
0372         const int secnumber = viewport->height() / m_maxLabelHeight + 1;
0373         m_rowsection.clear();
0374         m_rowsection.resize(secnumber);
0375 
0376         m_paintOrder.clear();
0377         m_lastPlacemarkAvailable = false;
0378         m_lastPlacemarkLabelRect = QRectF();
0379         m_lastPlacemarkSymbolRect = QRectF();
0380         m_labelArea = 0;
0381 
0382         // First handle the selected placemarks as they have the highest priority.
0383 
0384         const QModelIndexList selectedIndexes = m_selectionModel->selection().indexes();
0385         auto const viewLatLonAltBox = viewport->viewLatLonAltBox();
0386 
0387         for ( int i = 0; i < selectedIndexes.count(); ++i ) {
0388             const QModelIndex index = selectedIndexes.at( i );
0389             const GeoDataPlacemark *placemark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data( MarblePlacemarkModel::ObjectPointerRole ) ));
0390             const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
0391 
0392             if ( !coordinates.isValid() ) {
0393                 continue;
0394             }
0395 
0396             qreal x = 0;
0397             qreal y = 0;
0398 
0399             if ( !viewLatLonAltBox.contains( coordinates ) ||
0400                  ! viewport->screenCoordinates( coordinates, x, y ))
0401                 {
0402                     continue;
0403                 }
0404 
0405             if( layoutPlacemark( placemark, coordinates, x, y, true) ) {
0406                 // Make sure not to draw more placemarks on the screen than
0407                 // specified by placemarksOnScreenLimit().
0408                 if ( placemarksOnScreenLimit( viewport->size() ) )
0409                     break;
0410             }
0411 
0412         }
0413 
0414         // Now handle all other placemarks...
0415 
0416         const QItemSelection selection = m_selectionModel->selection();
0417 
0418         placemarkList.clear();
0419         for (const TileId &tileId: visibleTiles(*viewport, tileLevel)) {
0420             placemarkList += m_placemarkCache.value( tileId );
0421         }
0422         std::sort(placemarkList.begin(), placemarkList.end(), GeoDataPlacemark::placemarkLayoutOrderCompare);
0423 
0424         for ( const GeoDataPlacemark *placemark: placemarkList ) {
0425             const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
0426             if ( !coordinates.isValid() ) {
0427                 continue;
0428             }
0429 
0430             int zoomLevel = placemark->zoomLevel();
0431             if ( zoomLevel > 20 ) {
0432                 break;
0433             }
0434 
0435             qreal x = 0;
0436             qreal y = 0;
0437 
0438             if ( !viewLatLonAltBox.contains( coordinates ) ||
0439                  ! viewport->screenCoordinates( coordinates, x, y )) {
0440                     continue;
0441                 }
0442 
0443             if ( !placemark->isGloballyVisible() ) {
0444                 continue;
0445             }
0446 
0447             const GeoDataPlacemark::GeoDataVisualCategory visualCategory = placemark->visualCategory();
0448 
0449             // Skip city marks if we're not showing cities.
0450             if ( !m_showCities
0451                  && visualCategory >= GeoDataPlacemark::SmallCity
0452                  && visualCategory <= GeoDataPlacemark::Nation )
0453                 continue;
0454 
0455             // Skip terrain marks if we're not showing terrain.
0456             if ( !m_showTerrain
0457                  && visualCategory >= GeoDataPlacemark::Mountain
0458                  && visualCategory <= GeoDataPlacemark::OtherTerrain )
0459                 continue;
0460 
0461             // Skip other places if we're not showing other places.
0462             if ( !m_showOtherPlaces
0463                  && visualCategory >= GeoDataPlacemark::GeographicPole
0464                  && visualCategory <= GeoDataPlacemark::Observatory )
0465                 continue;
0466 
0467             // Skip landing sites if we're not showing landing sites.
0468             if ( !m_showLandingSites
0469                  && visualCategory >= GeoDataPlacemark::MannedLandingSite
0470                  && visualCategory <= GeoDataPlacemark::UnmannedHardLandingSite )
0471                 continue;
0472 
0473             // Skip craters if we're not showing craters.
0474             if ( !m_showCraters
0475                  && visualCategory == GeoDataPlacemark::Crater )
0476                 continue;
0477 
0478             // Skip maria if we're not showing maria.
0479             if ( !m_showMaria
0480                  && visualCategory == GeoDataPlacemark::Mare )
0481                 continue;
0482 
0483             if ( !m_showPlaces
0484                  && visualCategory >= GeoDataPlacemark::GeographicPole
0485                  && visualCategory <= GeoDataPlacemark::Observatory )
0486                 continue;
0487 
0488             // We handled selected placemarks already, so we skip them here...
0489             // Assuming that only a small amount of places is selected
0490             // we check for the selected state after all other filters
0491             bool isSelected = false;
0492             for ( const QModelIndex &index: selection.indexes() ) {
0493                 const GeoDataPlacemark *mark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data( MarblePlacemarkModel::ObjectPointerRole ) ));
0494                 if (mark == placemark ) {
0495                     isSelected = true;
0496                     break;
0497                 }
0498             }
0499             if ( isSelected )
0500                 continue;
0501 
0502             if( layoutPlacemark( placemark, coordinates, x, y, isSelected ) ) {
0503                 // Make sure not to draw more placemarks on the screen than
0504                 // specified by placemarksOnScreenLimit().
0505                 if ( placemarksOnScreenLimit( viewport->size() ) )
0506                     break;
0507             }
0508         }
0509 
0510         if (m_visiblePlacemarks.size() > qMax(100, 4 * m_paintOrder.size())) {
0511             auto const extendedBox = viewLatLonAltBox.scaled(2.0, 2.0);
0512             QVector<VisiblePlacemark*> outdated;
0513             for (auto placemark: m_visiblePlacemarks) {
0514                 if (!extendedBox.contains(placemark->coordinates())) {
0515                     outdated << placemark;
0516                 }
0517             }
0518             for (auto placemark: outdated) {
0519                 delete m_visiblePlacemarks.take(placemark->placemark());
0520             }
0521         }
0522     } while (currentMaxLabelHeight != m_maxLabelHeight);
0523 
0524     m_runtimeTrace = QStringLiteral("Placemarks: %1 Drawn: %2").arg(placemarkList.count()).arg(m_paintOrder.size());
0525     return m_paintOrder;
0526 }
0527 
0528 QString PlacemarkLayout::runtimeTrace() const
0529 {
0530     return m_runtimeTrace;
0531 }
0532 
0533 QList<VisiblePlacemark *> PlacemarkLayout::visiblePlacemarks() const
0534 {
0535     return m_visiblePlacemarks.values();
0536 }
0537 
0538 bool PlacemarkLayout::hasPlacemarkAt(const QPoint &pos)
0539 {
0540     if ( m_styleResetRequested ) {
0541         styleReset();
0542     }
0543 
0544     if (m_lastPlacemarkAvailable &&
0545             (m_lastPlacemarkLabelRect.contains(pos) || m_lastPlacemarkSymbolRect.contains(pos))) {
0546         return true;
0547     }
0548 
0549     for(VisiblePlacemark* mark: m_paintOrder) {
0550         if (mark->labelRect().contains(pos) || mark->symbolRect().contains(pos)) {
0551             m_lastPlacemarkLabelRect = mark->labelRect();
0552             m_lastPlacemarkSymbolRect = mark->symbolRect();
0553             m_lastPlacemarkAvailable = true;
0554             return true;
0555         }
0556     }
0557 
0558     return false;
0559 }
0560 
0561 bool PlacemarkLayout::layoutPlacemark( const GeoDataPlacemark *placemark, const GeoDataCoordinates &coordinates, qreal x, qreal y, bool selected )
0562 {
0563     // Find the corresponding visible placemark
0564     VisiblePlacemark *mark = m_visiblePlacemarks.value( placemark );
0565     if ( !mark ) {
0566         // If there is no visible placemark yet for this index,
0567         // create a new one...
0568         StyleParameters parameters;
0569         // @todo: Set / adjust to tile level
0570         parameters.placemark = placemark;
0571 
0572         auto style = m_styleBuilder->createStyle(parameters);
0573         mark = new VisiblePlacemark(placemark, coordinates, style);
0574         m_visiblePlacemarks.insert( placemark, mark );
0575         connect( mark, SIGNAL(updateNeeded()), this, SIGNAL(repaintNeeded()) );
0576     }
0577     GeoDataStyle::ConstPtr style = mark->style();
0578 
0579     // Choose Section
0580 
0581     QPointF hotSpot = mark->hotSpot();
0582     mark->setSelected(selected);
0583     mark->setSymbolPosition(QPointF(x - hotSpot.x(), y - hotSpot.y()));
0584 
0585     // Find out whether the area around the placemark is covered already.
0586     // If there's not enough space free don't add a VisiblePlacemark here.
0587 
0588     const QString labelText = placemark->displayName();
0589     QRectF labelRect;
0590     if (!labelText.isEmpty()) {
0591         labelRect = roomForLabel(style, x, y, labelText, mark);
0592     }
0593     if (labelRect.isEmpty() && mark->symbolPixmap().isNull()) {
0594         return false;
0595     }
0596     if (!mark->symbolPixmap().isNull() && !hasRoomForPixmap(y, mark)) {
0597         return false;
0598     }
0599 
0600     mark->setLabelRect( labelRect );
0601 
0602     // Add the current placemark to the matching row and its
0603     // direct neighbors.
0604     int idx = y / m_maxLabelHeight;
0605     if ( idx - 1 >= 0 ) {
0606         m_rowsection[ idx - 1 ].append( mark );
0607     }
0608     m_rowsection[ idx ].append( mark );
0609     if ( idx + 1 < m_rowsection.size() ) {
0610         m_rowsection[ idx + 1 ].append( mark );
0611     }
0612 
0613     m_paintOrder.append( mark );
0614     QRectF const boundingBox = mark->boundingBox();
0615     Q_ASSERT(!boundingBox.isEmpty());
0616     m_labelArea += boundingBox.width() * boundingBox.height();
0617     m_maxLabelHeight = qMax(m_maxLabelHeight, qCeil(boundingBox.height()));
0618     return true;
0619 }
0620 
0621 GeoDataCoordinates PlacemarkLayout::placemarkIconCoordinates( const GeoDataPlacemark *placemark ) const
0622 {
0623     GeoDataCoordinates coordinates = placemark->coordinate( m_clock->dateTime());
0624     if (!m_acceptedVisualCategories.contains(placemark->visualCategory())) {
0625         StyleParameters parameters;
0626         parameters.placemark = placemark;
0627         auto style = m_styleBuilder->createStyle(parameters);
0628         if (style->iconStyle().scaledIcon().isNull()) {
0629             return GeoDataCoordinates();
0630         }
0631     }
0632 
0633     return coordinates;
0634 }
0635 
0636 QRectF PlacemarkLayout::roomForLabel( const GeoDataStyle::ConstPtr &style,
0637                                       const qreal x, const qreal y,
0638                                       const QString &labelText,
0639                                       const VisiblePlacemark* placemark) const
0640 {
0641     QFont labelFont = style->labelStyle().scaledFont();
0642     int textHeight = QFontMetrics( labelFont ).height();
0643 
0644     int textWidth;
0645     if ( style->labelStyle().glow() ) {
0646         labelFont.setWeight( 75 ); // Needed to calculate the correct pixmap size;
0647         textWidth = ( QFontMetrics( labelFont ).horizontalAdvance( labelText )
0648             + qRound( 2 * s_labelOutlineWidth ) );
0649     } else {
0650         textWidth = ( QFontMetrics( labelFont ).horizontalAdvance( labelText ) );
0651     }
0652 
0653     const QVector<VisiblePlacemark*> currentsec = m_rowsection.at( y / m_maxLabelHeight );
0654     QRectF const symbolRect = placemark->symbolRect();
0655 
0656     if ( style->labelStyle().alignment() == GeoDataLabelStyle::Corner ) {
0657         const int symbolWidth = style->iconStyle().scaledIcon().size().width();
0658 
0659         // Check the four possible positions by going through all of them
0660         for( int i=0; i<4; ++i ) {
0661             const qreal xPos = ( i/2 == 0 ) ? x + symbolWidth / 2 + 1 :
0662                                               x - symbolWidth / 2 - 1 - textWidth;
0663             const qreal yPos = ( i%2 == 0 ) ? y :
0664                                               y - textHeight;
0665             const QRectF labelRect = QRectF( xPos, yPos, textWidth, textHeight );
0666 
0667             if (hasRoomFor(currentsec, labelRect.united(symbolRect))) {
0668                 // claim the place immediately if it hasn't been used yet
0669                 return labelRect;
0670             }
0671         }
0672     }
0673     else if ( style->labelStyle().alignment() == GeoDataLabelStyle::Center ) {
0674         int const offsetY = style->iconStyle().scaledIcon().height() / 2.0;
0675         QRectF  labelRect = QRectF( x - textWidth / 2, y - offsetY - textHeight,
0676                           textWidth, textHeight );
0677 
0678         if (hasRoomFor(currentsec, labelRect.united(symbolRect))) {
0679             // claim the place immediately if it hasn't been used yet 
0680             return labelRect;
0681         }
0682     }
0683     else if (style->labelStyle().alignment() == GeoDataLabelStyle::Right)
0684     {
0685         const int symbolWidth = style->iconStyle().scaledIcon().width();
0686         const qreal startY = y - textHeight/2;
0687         const qreal xPos = x + symbolWidth / 2 + 1;
0688 
0689         // Check up to seven vertical positions (center, +3, -3 from center)
0690         for(int i=0; i<7; ++i)
0691         {
0692             const qreal increase = (i/2) * (textHeight + 1); //intentional integer arithmetics
0693             const qreal direction = (i%2 == 0 ? 1 : -1);
0694             const qreal yPos = startY + increase*direction;
0695 
0696             const QRectF labelRect = QRectF(xPos, yPos, textWidth, textHeight);
0697 
0698             if (hasRoomFor(currentsec, labelRect.united(symbolRect)))
0699             {
0700                 return labelRect;
0701             }
0702         }
0703     }
0704 
0705     // At this point there is no space left for the rectangle anymore.
0706     return QRectF();
0707 }
0708 
0709 bool PlacemarkLayout::hasRoomForPixmap(const qreal y, const VisiblePlacemark *placemark) const
0710 {
0711     const QVector<VisiblePlacemark*> currentsec = m_rowsection.at(y / m_maxLabelHeight);
0712     return hasRoomFor(currentsec, placemark->symbolRect());
0713 }
0714 
0715 bool PlacemarkLayout::placemarksOnScreenLimit( const QSize &screenSize ) const
0716 {
0717     int ratio = ( m_labelArea * 100 ) / ( screenSize.width() * screenSize.height() );
0718     return ratio >= 40;
0719 }
0720 
0721 }
0722 
0723 #include "moc_PlacemarkLayout.cpp"