Marble

PlacemarkLayout.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <[email protected]>
4 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <[email protected]>
5 // SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <[email protected]>
6 //
7 
8 #include "PlacemarkLayout.h"
9 
10 #include <QAbstractItemModel>
11 #include <QList>
12 #include <QPoint>
13 #include <QVectorIterator>
14 #include <QFont>
15 #include <QFontMetrics>
16 #include <QItemSelectionModel>
17 #include <qmath.h>
18 
19 #include "GeoDataLatLonAltBox.h"
20 #include "GeoDataLatLonBox.h"
21 #include "GeoDataStyle.h"
22 #include "GeoDataIconStyle.h"
23 #include "GeoDataLabelStyle.h"
24 #include "OsmPlacemarkData.h"
25 
26 #include "MarbleDebug.h"
27 #include "MarbleGlobal.h"
28 #include "PlacemarkLayer.h"
29 #include "MarbleClock.h"
30 #include "MarblePlacemarkModel.h"
31 #include "MarbleDirs.h"
32 #include "ViewportParams.h"
33 #include "TileId.h"
34 #include "TileCoordsPyramid.h"
35 #include "VisiblePlacemark.h"
36 #include "MathHelper.h"
37 #include <StyleBuilder.h>
38 
39 namespace
40 { //Helper function that checks for available room for the label
41  bool hasRoomFor(const QVector<Marble::VisiblePlacemark*> & placemarks, const QRectF &boundingBox)
42  {
43  // Check if there is another label or symbol that overlaps.
46  beforeIt != beforeItEnd; ++beforeIt )
47  {
48  if ( boundingBox.intersects( (*beforeIt)->boundingBox() ) ) {
49  return false;
50  }
51  }
52  return true;
53  }
54 }
55 
56 namespace Marble
57 {
58 
59 QSet<GeoDataPlacemark::GeoDataVisualCategory> acceptedVisualCategories()
60 {
62 
63  visualCategories
64  << GeoDataPlacemark::SmallCity
65  << GeoDataPlacemark::SmallCountyCapital
66  << GeoDataPlacemark::SmallStateCapital
67  << GeoDataPlacemark::SmallNationCapital
68  << GeoDataPlacemark::MediumCity
69  << GeoDataPlacemark::MediumCountyCapital
70  << GeoDataPlacemark::MediumStateCapital
71  << GeoDataPlacemark::MediumNationCapital
72  << GeoDataPlacemark::BigCity
73  << GeoDataPlacemark::BigCountyCapital
74  << GeoDataPlacemark::BigStateCapital
75  << GeoDataPlacemark::BigNationCapital
76  << GeoDataPlacemark::LargeCity
77  << GeoDataPlacemark::LargeCountyCapital
78  << GeoDataPlacemark::LargeStateCapital
79  << GeoDataPlacemark::LargeNationCapital
80  << GeoDataPlacemark::Nation
81  << GeoDataPlacemark::Mountain
82  << GeoDataPlacemark::Volcano
83  << GeoDataPlacemark::Mons
84  << GeoDataPlacemark::Valley
85  << GeoDataPlacemark::Continent
86  << GeoDataPlacemark::Ocean
87  << GeoDataPlacemark::OtherTerrain
88  << GeoDataPlacemark::Crater
89  << GeoDataPlacemark::Mare
90  << GeoDataPlacemark::GeographicPole
91  << GeoDataPlacemark::MagneticPole
92  << GeoDataPlacemark::ShipWreck
93  << GeoDataPlacemark::PlaceSuburb
94  << GeoDataPlacemark::PlaceHamlet
95  << GeoDataPlacemark::PlaceLocality;
96 
97  return visualCategories;
98 }
99 
100 
102  QItemSelectionModel *selectionModel,
103  MarbleClock *clock,
104  const StyleBuilder *styleBuilder,
105  QObject* parent )
106  : QObject( parent ),
107  m_placemarkModel(placemarkModel),
108  m_selectionModel( selectionModel ),
109  m_clock( clock ),
110  m_acceptedVisualCategories( acceptedVisualCategories() ),
111  m_showPlaces( false ),
112  m_showCities( false ),
113  m_showTerrain( false ),
114  m_showOtherPlaces( false ),
115  m_showLandingSites( false ),
116  m_showCraters( false ),
117  m_showMaria( false ),
118  m_maxLabelHeight(maxLabelHeight()),
119  m_styleResetRequested( true ),
120  m_styleBuilder(styleBuilder),
121  m_lastPlacemarkAvailable(false)
122 {
123  Q_ASSERT(m_placemarkModel);
124 
125  connect( m_selectionModel, SIGNAL( selectionChanged( QItemSelection,
126  QItemSelection) ),
127  this, SLOT(requestStyleReset()) );
128 
129  connect( m_placemarkModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
130  this, SLOT(resetCacheData()) );
131  connect( m_placemarkModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
132  this, SLOT(addPlacemarks(QModelIndex,int,int)) );
133  connect( m_placemarkModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
134  this, SLOT(removePlacemarks(QModelIndex,int,int)) );
135  connect( m_placemarkModel, SIGNAL(modelReset()),
136  this, SLOT(resetCacheData()) );
137 }
138 
140 {
141  styleReset();
142 }
143 
144 void PlacemarkLayout::setShowPlaces( bool show )
145 {
146  m_showPlaces = show;
147 }
148 
149 void PlacemarkLayout::setShowCities( bool show )
150 {
151  m_showCities = show;
152 }
153 
154 void PlacemarkLayout::setShowTerrain( bool show )
155 {
156  m_showTerrain = show;
157 }
158 
159 void PlacemarkLayout::setShowOtherPlaces( bool show )
160 {
161  m_showOtherPlaces = show;
162 }
163 
164 void PlacemarkLayout::setShowLandingSites( bool show )
165 {
166  m_showLandingSites = show;
167 }
168 
169 void PlacemarkLayout::setShowCraters( bool show )
170 {
171  m_showCraters = show;
172 }
173 
174 void PlacemarkLayout::setShowMaria( bool show )
175 {
176  m_showMaria = show;
177 }
178 
179 void PlacemarkLayout::requestStyleReset()
180 {
181  mDebug() << "Style reset requested.";
182  m_styleResetRequested = true;
183 }
184 
185 void PlacemarkLayout::styleReset()
186 {
187  clearCache();
188  m_maxLabelHeight = maxLabelHeight();
189  m_styleResetRequested = false;
190 }
191 
192 void PlacemarkLayout::clearCache()
193 {
194  m_paintOrder.clear();
195  m_lastPlacemarkAvailable = false;
196  m_lastPlacemarkLabelRect = QRectF();
197  m_lastPlacemarkSymbolRect = QRectF();
198  m_labelArea = 0;
199  qDeleteAll( m_visiblePlacemarks );
200  m_visiblePlacemarks.clear();
201 }
202 
204 {
205  if ( m_styleResetRequested ) {
206  styleReset();
207  }
208 
210 
211  for( VisiblePlacemark* mark: m_paintOrder ) {
212  if ( mark->labelRect().contains( curpos ) || mark->symbolRect().contains( curpos ) ) {
213  ret.append( mark->placemark() );
214  }
215  }
216 
217  return ret;
218 }
219 
220 int PlacemarkLayout::maxLabelHeight()
221 {
222  QFont const standardFont(QStringLiteral("Sans Serif"));
223  return QFontMetrics(standardFont).height();
224 }
225 
226 /// feed an internal QMap of placemarks with TileId as key when model changes
227 void PlacemarkLayout::addPlacemarks( const QModelIndex& parent, int first, int last )
228 {
229  Q_ASSERT( first < m_placemarkModel->rowCount() );
230  Q_ASSERT( last < m_placemarkModel->rowCount() );
231  for( int i=first; i<=last; ++i ) {
232  QModelIndex index = m_placemarkModel->index( i, 0, parent );
233  Q_ASSERT( index.isValid() );
234  auto const object = qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole));
235  if (const GeoDataPlacemark *placemark = geodata_cast<GeoDataPlacemark>(object)) {
236  const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
237  if ( !coordinates.isValid() ) {
238  continue;
239  }
240 
241  if (placemark->hasOsmData()) {
242  qint64 const osmId = placemark->osmData().id();
243  if (osmId > 0) {
244  if (m_osmIds.contains(osmId)) {
245  continue; // placemark is already shown
246  }
247  m_osmIds << osmId;
248  }
249  }
250 
251  int zoomLevel = placemark->zoomLevel();
252  TileId key = TileId::fromCoordinates( coordinates, zoomLevel );
253  m_placemarkCache[key].append( placemark );
254  }
255  }
256  emit repaintNeeded();
257 }
258 
259 void PlacemarkLayout::removePlacemarks( const QModelIndex& parent, int first, int last )
260 {
261  Q_ASSERT( first < m_placemarkModel->rowCount() );
262  Q_ASSERT( last < m_placemarkModel->rowCount() );
263  for( int i=first; i<=last; ++i ) {
264  QModelIndex index = m_placemarkModel->index( i, 0, parent );
265  Q_ASSERT( index.isValid() );
266  const GeoDataPlacemark *placemark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>( index.data( MarblePlacemarkModel::ObjectPointerRole ) ));
267  const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
268  if ( !coordinates.isValid() ) {
269  continue;
270  }
271 
272  int zoomLevel = placemark->zoomLevel();
273  TileId key = TileId::fromCoordinates( coordinates, zoomLevel );
274  delete m_visiblePlacemarks[placemark];
275  m_visiblePlacemarks.remove(placemark);
276  m_placemarkCache[key].removeAll( placemark );
277  if (placemark->hasOsmData()) {
278  qint64 const osmId = placemark->osmData().id();
279  if (osmId > 0) {
280  m_osmIds.remove(osmId);
281  }
282  }
283  }
284  emit repaintNeeded();
285 }
286 
287 void PlacemarkLayout::resetCacheData()
288 {
289  const int rowCount = m_placemarkModel->rowCount();
290 
291  m_osmIds.clear();
292  m_placemarkCache.clear();
293  qDeleteAll(m_visiblePlacemarks);
294  m_visiblePlacemarks.clear();
295  requestStyleReset();
296  addPlacemarks( m_placemarkModel->index( 0, 0 ), 0, rowCount );
297  emit repaintNeeded();
298 }
299 
300 QSet<TileId> PlacemarkLayout::visibleTiles(const ViewportParams &viewport, int zoomLevel)
301 {
302  /*
303  * rely on m_placemarkCache to find the placemarks for the tiles which
304  * matter. The top level tiles have the more popular placemarks,
305  * the bottom level tiles have the smaller ones, and we only get the ones
306  * matching our latLonAltBox.
307  */
308 
309  qreal north, south, east, west;
310  viewport.viewLatLonAltBox().boundaries(north, south, east, west);
311  QSet<TileId> tileIdSet;
312  QVector<GeoDataLatLonBox> geoRects;
313  if( west <= east ) {
314  geoRects << GeoDataLatLonBox(north, south, east, west);
315  } else {
316  geoRects << GeoDataLatLonBox(north, south, M_PI, west);
317  geoRects << GeoDataLatLonBox(north, south, east, -M_PI);
318  }
319 
320  for (const GeoDataLatLonBox &geoRect : geoRects) {
321  TileId key;
322  QRect rect;
323 
324  key = TileId::fromCoordinates(GeoDataCoordinates(geoRect.west(), north), zoomLevel);
325  rect.setLeft( key.x() );
326  rect.setTop( key.y() );
327 
328  key = TileId::fromCoordinates(GeoDataCoordinates(geoRect.east(), south), zoomLevel);
329  rect.setRight( key.x() );
330  rect.setBottom( key.y() );
331 
332  TileCoordsPyramid pyramid(0, zoomLevel );
333  pyramid.setBottomLevelCoords( rect );
334 
335  for ( int level = pyramid.topLevel(); level <= pyramid.bottomLevel(); ++level ) {
336  QRect const coords = pyramid.coords( level );
337  int x1, y1, x2, y2;
338  coords.getCoords( &x1, &y1, &x2, &y2 );
339  for ( int x = x1; x <= x2; ++x ) {
340  for ( int y = y1; y <= y2; ++y ) {
341  TileId const tileId( 0, level, x, y );
342  tileIdSet.insert(tileId);
343  }
344  }
345  }
346  }
347 
348  return tileIdSet;
349 }
350 
352 {
353  m_runtimeTrace.clear();
354  if ( m_placemarkModel->rowCount() <= 0 ) {
355  clearCache();
357  }
358 
359  if ( m_styleResetRequested ) {
360  styleReset();
361  }
362 
363  if ( m_maxLabelHeight == 0 ) {
364  clearCache();
366  }
367 
368  QList<const GeoDataPlacemark*> placemarkList;
369  int currentMaxLabelHeight;
370  do {
371  currentMaxLabelHeight = m_maxLabelHeight;
372  const int secnumber = viewport->height() / m_maxLabelHeight + 1;
373  m_rowsection.clear();
374  m_rowsection.resize(secnumber);
375 
376  m_paintOrder.clear();
377  m_lastPlacemarkAvailable = false;
378  m_lastPlacemarkLabelRect = QRectF();
379  m_lastPlacemarkSymbolRect = QRectF();
380  m_labelArea = 0;
381 
382  // First handle the selected placemarks as they have the highest priority.
383 
384  const QModelIndexList selectedIndexes = m_selectionModel->selection().indexes();
385  auto const viewLatLonAltBox = viewport->viewLatLonAltBox();
386 
387  for ( int i = 0; i < selectedIndexes.count(); ++i ) {
388  const QModelIndex index = selectedIndexes.at( i );
389  const GeoDataPlacemark *placemark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data( MarblePlacemarkModel::ObjectPointerRole ) ));
390  const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
391 
392  if ( !coordinates.isValid() ) {
393  continue;
394  }
395 
396  qreal x = 0;
397  qreal y = 0;
398 
399  if ( !viewLatLonAltBox.contains( coordinates ) ||
400  ! viewport->screenCoordinates( coordinates, x, y ))
401  {
402  continue;
403  }
404 
405  if( layoutPlacemark( placemark, coordinates, x, y, true) ) {
406  // Make sure not to draw more placemarks on the screen than
407  // specified by placemarksOnScreenLimit().
408  if ( placemarksOnScreenLimit( viewport->size() ) )
409  break;
410  }
411 
412  }
413 
414  // Now handle all other placemarks...
415 
416  const QItemSelection selection = m_selectionModel->selection();
417 
418  placemarkList.clear();
419  for (const TileId &tileId: visibleTiles(*viewport, tileLevel)) {
420  placemarkList += m_placemarkCache.value( tileId );
421  }
422  std::sort(placemarkList.begin(), placemarkList.end(), GeoDataPlacemark::placemarkLayoutOrderCompare);
423 
424  for ( const GeoDataPlacemark *placemark: placemarkList ) {
425  const GeoDataCoordinates coordinates = placemarkIconCoordinates( placemark );
426  if ( !coordinates.isValid() ) {
427  continue;
428  }
429 
430  int zoomLevel = placemark->zoomLevel();
431  if ( zoomLevel > 20 ) {
432  break;
433  }
434 
435  qreal x = 0;
436  qreal y = 0;
437 
438  if ( !viewLatLonAltBox.contains( coordinates ) ||
439  ! viewport->screenCoordinates( coordinates, x, y )) {
440  continue;
441  }
442 
443  if ( !placemark->isGloballyVisible() ) {
444  continue;
445  }
446 
447  const GeoDataPlacemark::GeoDataVisualCategory visualCategory = placemark->visualCategory();
448 
449  // Skip city marks if we're not showing cities.
450  if ( !m_showCities
451  && visualCategory >= GeoDataPlacemark::SmallCity
452  && visualCategory <= GeoDataPlacemark::Nation )
453  continue;
454 
455  // Skip terrain marks if we're not showing terrain.
456  if ( !m_showTerrain
457  && visualCategory >= GeoDataPlacemark::Mountain
458  && visualCategory <= GeoDataPlacemark::OtherTerrain )
459  continue;
460 
461  // Skip other places if we're not showing other places.
462  if ( !m_showOtherPlaces
463  && visualCategory >= GeoDataPlacemark::GeographicPole
464  && visualCategory <= GeoDataPlacemark::Observatory )
465  continue;
466 
467  // Skip landing sites if we're not showing landing sites.
468  if ( !m_showLandingSites
469  && visualCategory >= GeoDataPlacemark::MannedLandingSite
470  && visualCategory <= GeoDataPlacemark::UnmannedHardLandingSite )
471  continue;
472 
473  // Skip craters if we're not showing craters.
474  if ( !m_showCraters
475  && visualCategory == GeoDataPlacemark::Crater )
476  continue;
477 
478  // Skip maria if we're not showing maria.
479  if ( !m_showMaria
480  && visualCategory == GeoDataPlacemark::Mare )
481  continue;
482 
483  if ( !m_showPlaces
484  && visualCategory >= GeoDataPlacemark::GeographicPole
485  && visualCategory <= GeoDataPlacemark::Observatory )
486  continue;
487 
488  // We handled selected placemarks already, so we skip them here...
489  // Assuming that only a small amount of places is selected
490  // we check for the selected state after all other filters
491  bool isSelected = false;
492  for ( const QModelIndex &index: selection.indexes() ) {
493  const GeoDataPlacemark *mark = static_cast<GeoDataPlacemark*>(qvariant_cast<GeoDataObject*>(index.data( MarblePlacemarkModel::ObjectPointerRole ) ));
494  if (mark == placemark ) {
495  isSelected = true;
496  break;
497  }
498  }
499  if ( isSelected )
500  continue;
501 
502  if( layoutPlacemark( placemark, coordinates, x, y, isSelected ) ) {
503  // Make sure not to draw more placemarks on the screen than
504  // specified by placemarksOnScreenLimit().
505  if ( placemarksOnScreenLimit( viewport->size() ) )
506  break;
507  }
508  }
509 
510  if (m_visiblePlacemarks.size() > qMax(100, 4 * m_paintOrder.size())) {
511  auto const extendedBox = viewLatLonAltBox.scaled(2.0, 2.0);
513  for (auto placemark: m_visiblePlacemarks) {
514  if (!extendedBox.contains(placemark->coordinates())) {
515  outdated << placemark;
516  }
517  }
518  for (auto placemark: outdated) {
519  delete m_visiblePlacemarks.take(placemark->placemark());
520  }
521  }
522  } while (currentMaxLabelHeight != m_maxLabelHeight);
523 
524  m_runtimeTrace = QStringLiteral("Placemarks: %1 Drawn: %2").arg(placemarkList.count()).arg(m_paintOrder.size());
525  return m_paintOrder;
526 }
527 
528 QString PlacemarkLayout::runtimeTrace() const
529 {
530  return m_runtimeTrace;
531 }
532 
533 QList<VisiblePlacemark *> PlacemarkLayout::visiblePlacemarks() const
534 {
535  return m_visiblePlacemarks.values();
536 }
537 
538 bool PlacemarkLayout::hasPlacemarkAt(const QPoint &pos)
539 {
540  if ( m_styleResetRequested ) {
541  styleReset();
542  }
543 
544  if (m_lastPlacemarkAvailable &&
545  (m_lastPlacemarkLabelRect.contains(pos) || m_lastPlacemarkSymbolRect.contains(pos))) {
546  return true;
547  }
548 
549  for(VisiblePlacemark* mark: m_paintOrder) {
550  if (mark->labelRect().contains(pos) || mark->symbolRect().contains(pos)) {
551  m_lastPlacemarkLabelRect = mark->labelRect();
552  m_lastPlacemarkSymbolRect = mark->symbolRect();
553  m_lastPlacemarkAvailable = true;
554  return true;
555  }
556  }
557 
558  return false;
559 }
560 
561 bool PlacemarkLayout::layoutPlacemark( const GeoDataPlacemark *placemark, const GeoDataCoordinates &coordinates, qreal x, qreal y, bool selected )
562 {
563  // Find the corresponding visible placemark
564  VisiblePlacemark *mark = m_visiblePlacemarks.value( placemark );
565  if ( !mark ) {
566  // If there is no visible placemark yet for this index,
567  // create a new one...
568  StyleParameters parameters;
569  // @todo: Set / adjust to tile level
570  parameters.placemark = placemark;
571 
572  auto style = m_styleBuilder->createStyle(parameters);
573  mark = new VisiblePlacemark(placemark, coordinates, style);
574  m_visiblePlacemarks.insert( placemark, mark );
575  connect( mark, SIGNAL(updateNeeded()), this, SIGNAL(repaintNeeded()) );
576  }
577  GeoDataStyle::ConstPtr style = mark->style();
578 
579  // Choose Section
580 
581  QPointF hotSpot = mark->hotSpot();
582  mark->setSelected(selected);
583  mark->setSymbolPosition(QPointF(x - hotSpot.x(), y - hotSpot.y()));
584 
585  // Find out whether the area around the placemark is covered already.
586  // If there's not enough space free don't add a VisiblePlacemark here.
587 
588  const QString labelText = placemark->displayName();
589  QRectF labelRect;
590  if (!labelText.isEmpty()) {
591  labelRect = roomForLabel(style, x, y, labelText, mark);
592  }
593  if (labelRect.isEmpty() && mark->symbolPixmap().isNull()) {
594  return false;
595  }
596  if (!mark->symbolPixmap().isNull() && !hasRoomForPixmap(y, mark)) {
597  return false;
598  }
599 
600  mark->setLabelRect( labelRect );
601 
602  // Add the current placemark to the matching row and its
603  // direct neighbors.
604  int idx = y / m_maxLabelHeight;
605  if ( idx - 1 >= 0 ) {
606  m_rowsection[ idx - 1 ].append( mark );
607  }
608  m_rowsection[ idx ].append( mark );
609  if ( idx + 1 < m_rowsection.size() ) {
610  m_rowsection[ idx + 1 ].append( mark );
611  }
612 
613  m_paintOrder.append( mark );
614  QRectF const boundingBox = mark->boundingBox();
615  Q_ASSERT(!boundingBox.isEmpty());
616  m_labelArea += boundingBox.width() * boundingBox.height();
617  m_maxLabelHeight = qMax(m_maxLabelHeight, qCeil(boundingBox.height()));
618  return true;
619 }
620 
621 GeoDataCoordinates PlacemarkLayout::placemarkIconCoordinates( const GeoDataPlacemark *placemark ) const
622 {
623  GeoDataCoordinates coordinates = placemark->coordinate( m_clock->dateTime());
624  if (!m_acceptedVisualCategories.contains(placemark->visualCategory())) {
625  StyleParameters parameters;
626  parameters.placemark = placemark;
627  auto style = m_styleBuilder->createStyle(parameters);
628  if (style->iconStyle().scaledIcon().isNull()) {
629  return GeoDataCoordinates();
630  }
631  }
632 
633  return coordinates;
634 }
635 
636 QRectF PlacemarkLayout::roomForLabel( const GeoDataStyle::ConstPtr &style,
637  const qreal x, const qreal y,
638  const QString &labelText,
639  const VisiblePlacemark* placemark) const
640 {
641  QFont labelFont = style->labelStyle().scaledFont();
642  int textHeight = QFontMetrics( labelFont ).height();
643 
644  int textWidth;
645  if ( style->labelStyle().glow() ) {
646  labelFont.setWeight( 75 ); // Needed to calculate the correct pixmap size;
647  textWidth = ( QFontMetrics( labelFont ).horizontalAdvance( labelText )
648  + qRound( 2 * s_labelOutlineWidth ) );
649  } else {
650  textWidth = ( QFontMetrics( labelFont ).horizontalAdvance( labelText ) );
651  }
652 
653  const QVector<VisiblePlacemark*> currentsec = m_rowsection.at( y / m_maxLabelHeight );
654  QRectF const symbolRect = placemark->symbolRect();
655 
656  if ( style->labelStyle().alignment() == GeoDataLabelStyle::Corner ) {
657  const int symbolWidth = style->iconStyle().scaledIcon().size().width();
658 
659  // Check the four possible positions by going through all of them
660  for( int i=0; i<4; ++i ) {
661  const qreal xPos = ( i/2 == 0 ) ? x + symbolWidth / 2 + 1 :
662  x - symbolWidth / 2 - 1 - textWidth;
663  const qreal yPos = ( i%2 == 0 ) ? y :
664  y - textHeight;
665  const QRectF labelRect = QRectF( xPos, yPos, textWidth, textHeight );
666 
667  if (hasRoomFor(currentsec, labelRect.united(symbolRect))) {
668  // claim the place immediately if it hasn't been used yet
669  return labelRect;
670  }
671  }
672  }
673  else if ( style->labelStyle().alignment() == GeoDataLabelStyle::Center ) {
674  int const offsetY = style->iconStyle().scaledIcon().height() / 2.0;
675  QRectF labelRect = QRectF( x - textWidth / 2, y - offsetY - textHeight,
676  textWidth, textHeight );
677 
678  if (hasRoomFor(currentsec, labelRect.united(symbolRect))) {
679  // claim the place immediately if it hasn't been used yet
680  return labelRect;
681  }
682  }
683  else if (style->labelStyle().alignment() == GeoDataLabelStyle::Right)
684  {
685  const int symbolWidth = style->iconStyle().scaledIcon().width();
686  const qreal startY = y - textHeight/2;
687  const qreal xPos = x + symbolWidth / 2 + 1;
688 
689  // Check up to seven vertical positions (center, +3, -3 from center)
690  for(int i=0; i<7; ++i)
691  {
692  const qreal increase = (i/2) * (textHeight + 1); //intentional integer arithmetics
693  const qreal direction = (i%2 == 0 ? 1 : -1);
694  const qreal yPos = startY + increase*direction;
695 
696  const QRectF labelRect = QRectF(xPos, yPos, textWidth, textHeight);
697 
698  if (hasRoomFor(currentsec, labelRect.united(symbolRect)))
699  {
700  return labelRect;
701  }
702  }
703  }
704 
705  // At this point there is no space left for the rectangle anymore.
706  return QRectF();
707 }
708 
709 bool PlacemarkLayout::hasRoomForPixmap(const qreal y, const VisiblePlacemark *placemark) const
710 {
711  const QVector<VisiblePlacemark*> currentsec = m_rowsection.at(y / m_maxLabelHeight);
712  return hasRoomFor(currentsec, placemark->symbolRect());
713 }
714 
715 bool PlacemarkLayout::placemarksOnScreenLimit( const QSize &screenSize ) const
716 {
717  int ratio = ( m_labelArea * 100 ) / ( screenSize.width() * screenSize.height() );
718  return ratio >= 40;
719 }
720 
721 }
722 
723 #include "moc_PlacemarkLayout.cpp"
A 3d point representation.
QVector< VisiblePlacemark * > generateLayout(const ViewportParams *viewport, int tileLevel)
bool remove(const T &value)
A class which represents the visible place marks on a map.
virtual int rowCount(const QModelIndex &parent) const const=0
const QItemSelection selection() const const
int count(const T &value) const const
void clear()
int horizontalAdvance(const QString &text, int len) const const
void append(const T &value)
~PlacemarkLayout() override
Destroys the place mark painter.
QVector::const_iterator constEnd() const const
void addPlacemarks(const QModelIndex &index, int first, int last)
feed an internal QMap of placemarks with TileId as key when model changes
int width() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setRight(int x)
@ ObjectPointerRole
The pointer to a specific object.
QVariant data(int role) const const
QModelIndexList indexes() const const
A public class that controls what is visible in the viewport of a Marble map.
GeoDataVisualCategory visualCategory() const
Return the symbol index of the placemark.
int height() const const
void setBottom(int y)
void getCoords(int *x1, int *y1, int *x2, int *y2) const const
bool contains(const QRectF &rectangle) const const
bool isEmpty() const const
int zoomLevel() const
Return the popularity index of the placemark.
GeoDataVisualCategory
A categorization of a placemark as defined by ...FIXME.
Binds a QML item to a specific geodetic location in screen coordinates.
bool isValid() const
Returns.
bool isEmpty() const const
bool isValid() const const
QRectF united(const QRectF &rectangle) const const
bool contains(const T &value) const const
a class representing a point of interest on the map
QVector< const GeoDataFeature * > whichPlacemarkAt(const QPoint &pos)
Returns a list of model indexes that are at position pos.
bool intersects(const QRectF &rectangle) const const
void setTop(int y)
qreal x() const const
qreal y() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void clear()
qreal width() const const
OsmPlacemarkData & osmData()
Quick, safe accessor to the placemark's OsmPlacemarkData stored within it's ExtendedData.
bool isGloballyVisible() const
Return whether this feature is visible or not in the context of its parenting.
void setWeight(int weight)
void clear()
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
QSet::iterator insert(const T &value)
QList::iterator begin()
QSizeF size() const const
PlacemarkLayout(QAbstractItemModel *placemarkModel, QItemSelectionModel *selectionModel, MarbleClock *clock, const StyleBuilder *styleBuilder, QObject *parent=nullptr)
Creates a new place mark layout.
QVector::const_iterator constBegin() const const
QList::iterator end()
bool screenCoordinates(const qreal lon, const qreal lat, qreal &x, qreal &y) const
Get the screen coordinates corresponding to geographical coordinates in the map.
void setLeft(int x)
int height() const const
QObject * parent() const const
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
qreal width() const const
qreal height() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Sep 27 2023 04:09:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.