File indexing completed on 2025-01-05 03:59:19
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 #include "MarbleGlobal.h" 0026 #include "PlacemarkLayer.h" 0027 #include "MarbleClock.h" 0028 #include "MarblePlacemarkModel.h" 0029 #include "MarbleDirs.h" 0030 #include "ViewportParams.h" 0031 #include "TileId.h" 0032 #include "TileCoordsPyramid.h" 0033 #include "VisiblePlacemark.h" 0034 #include "MathHelper.h" 0035 #include "StyleBuilder.h" 0036 0037 #include "digikam_debug.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 qCDebug(DIGIKAM_MARBLE_LOG) << "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 Q_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 Q_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 Q_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( QFont::Bold ); // 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"