1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4 // SPDX-FileCopyrightText: 2007-2008 Inge Wallin <ingwa@kde.org>
5 // SPDX-FileCopyrightText: 2011-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
6 //
7 
8 #include "PlacemarkLayer.h"
9 
10 #include <QPoint>
11 
12 #include "MarbleDebug.h"
13 #include "AbstractProjection.h"
14 #include "GeoDataStyle.h"
15 #include "GeoPainter.h"
16 #include "GeoDataLatLonAltBox.h"
17 #include "ViewportParams.h"
18 #include "VisiblePlacemark.h"
19 #include "RenderState.h"
20 #include "osm/OsmPlacemarkData.h"
21 
22 #define BATCH_RENDERING
23 
24 using namespace Marble;
25 
26 bool PlacemarkLayer::m_useXWorkaround = false;
27 
PlacemarkLayer(QAbstractItemModel * placemarkModel,QItemSelectionModel * selectionModel,MarbleClock * clock,const StyleBuilder * styleBuilder,QObject * parent)28 PlacemarkLayer::PlacemarkLayer(QAbstractItemModel *placemarkModel,
29                                 QItemSelectionModel *selectionModel,
30                                 MarbleClock *clock, const StyleBuilder *styleBuilder,
31                                 QObject *parent ) :
32     QObject( parent ),
33     m_layout( placemarkModel, selectionModel, clock, styleBuilder ),
34     m_debugModeEnabled(false),
35     m_levelTagDebugModeEnabled(false),
36     m_tileLevel(0),
37     m_debugLevelTag(0)
38 {
39     m_useXWorkaround = testXBug();
40     mDebug() << "Use workaround: " << ( m_useXWorkaround ? "1" : "0" );
41 
42     connect( &m_layout, SIGNAL(repaintNeeded()), SIGNAL(repaintNeeded()) );
43 }
44 
~PlacemarkLayer()45 PlacemarkLayer::~PlacemarkLayer()
46 {
47 }
48 
renderPosition() const49 QStringList PlacemarkLayer::renderPosition() const
50 {
51     return QStringList(QStringLiteral("PLACEMARKS"));
52 }
53 
zValue() const54 qreal PlacemarkLayer::zValue() const
55 {
56     return 2.0;
57 }
58 
render(GeoPainter * geoPainter,ViewportParams * viewport,const QString & renderPos,GeoSceneLayer * layer)59 bool PlacemarkLayer::render( GeoPainter *geoPainter, ViewportParams *viewport,
60                                const QString &renderPos, GeoSceneLayer *layer )
61 {
62     Q_UNUSED( renderPos )
63     Q_UNUSED( layer )
64 
65     QVector<VisiblePlacemark*> visiblePlacemarks = m_layout.generateLayout( viewport, m_tileLevel );
66     // draw placemarks less important first
67     QVector<VisiblePlacemark*>::const_iterator visit = visiblePlacemarks.constEnd();
68     QVector<VisiblePlacemark*>::const_iterator itEnd = visiblePlacemarks.constBegin();
69 
70     QPainter *const painter = geoPainter;
71 
72     bool const repeatableX = viewport->currentProjection()->repeatableX();
73     int const radius4 = 4 * viewport->radius();
74 
75 #ifdef BATCH_RENDERING
76     QHash <QString, Fragment> hash;
77 #endif
78 
79     while ( visit != itEnd ) {
80         --visit;
81 
82         VisiblePlacemark *const mark = *visit;
83         if (m_levelTagDebugModeEnabled) {
84             if (mark->placemark()->hasOsmData()) {
85                 QHash<QString, QString>::const_iterator tagIter = mark->placemark()->osmData().findTag(QStringLiteral("level"));
86                 if (tagIter != mark->placemark()->osmData().tagsEnd()) {
87                     const int val = tagIter.value().toInt();
88                     if (val != m_debugLevelTag) {
89                         continue;
90                     }
91                 }
92             }
93         }
94 
95         // Intentionally converting positions from floating point to pixel aligned screen grid below
96         QRect labelRect( mark->labelRect().toRect() );
97         QPoint symbolPos( mark->symbolPosition().toPoint());
98 
99         // when the map is such zoomed out that a given place
100         // appears many times, we draw one placemark at each
101         if (repeatableX) {
102             const int symbolX = symbolPos.x();
103             const int textX =   labelRect.x();
104 
105             for (int i = symbolX % radius4, width = viewport->width(); i <= width; i += radius4) {
106                 labelRect.moveLeft(i - symbolX + textX);
107                 symbolPos.setX(i);
108 
109                 if (!mark->symbolPixmap().isNull()) {
110 #ifdef BATCH_RENDERING
111                     QRect symbolRect = mark->symbolPixmap().rect();
112                     QPainter::PixmapFragment pixmapFragment = QPainter::PixmapFragment::create(QPointF(symbolPos+symbolRect.center()),QRectF(symbolRect));
113 
114                     auto iter = hash.find(mark->symbolId());
115                     if (iter == hash.end()) {
116                         Fragment fragment;
117                         fragment.pixmap = mark->symbolPixmap();
118                         fragment.fragments << pixmapFragment;
119                         hash.insert(mark->symbolId(), fragment);
120                     } else {
121                         auto & fragment = iter.value();
122                         fragment.fragments << pixmapFragment;
123                     }
124 #else
125                     painter->drawPixmap( symbolPos, mark->symbolPixmap() );
126 #endif
127                 }
128                 if (!mark->labelPixmap().isNull()) {
129                     painter->drawPixmap( labelRect, mark->labelPixmap() );
130                 }
131             }
132         } else { // simple case, one draw per placemark
133 
134             if (!mark->symbolPixmap().isNull()) {
135 #ifdef BATCH_RENDERING
136                 QRect symbolRect = mark->symbolPixmap().rect();
137                 QPainter::PixmapFragment pixmapFragment = QPainter::PixmapFragment::create(QPointF(symbolPos+symbolRect.center()),QRectF(symbolRect));
138 
139                 auto iter = hash.find(mark->symbolId());
140                 if (iter == hash.end()) {
141                     Fragment fragment;
142                     fragment.pixmap = mark->symbolPixmap();
143                     fragment.fragments << pixmapFragment;
144                     hash.insert(mark->symbolId(), fragment);
145                 }
146                 else {
147                     auto & fragment = iter.value();
148                     fragment.fragments << pixmapFragment;
149                 }
150 #else
151                 painter->drawPixmap( symbolPos, mark->symbolPixmap() );
152 #endif
153             }
154             if (!mark->labelPixmap().isNull()) {
155                 painter->drawPixmap( labelRect, mark->labelPixmap() );
156             }
157         }
158     }
159 
160 #ifdef BATCH_RENDERING
161     for (auto iter = hash.begin(), end = hash.end(); iter != end; ++iter) {
162         auto const & fragment = iter.value();
163         if (m_debugModeEnabled) {
164             QPixmap debugPixmap(fragment.pixmap.size());
165             QColor backgroundColor;
166             QString idStr = iter.key().section('/', -1);
167             if (idStr.length() > 2) {
168               idStr.remove("shop_");
169               backgroundColor = QColor(
170                           (10 * (int)(idStr[0].toLatin1()))%255,
171                           (10 * (int)(idStr[1].toLatin1()))%255,
172                           (10 * (int)(idStr[2].toLatin1()))%255 );
173             }
174             else {
175               backgroundColor = QColor((quint64)(&iter.key()));
176             }
177             debugPixmap.fill(backgroundColor);
178             QPainter pixpainter;
179             pixpainter.begin(&debugPixmap);
180             pixpainter.drawPixmap(0, 0, fragment.pixmap);
181             pixpainter.end();
182             iter.value().pixmap = debugPixmap;
183         }
184         painter->drawPixmapFragments(fragment.fragments.data(), fragment.fragments.size(), fragment.pixmap);
185     }
186 #endif
187 
188     if (m_debugModeEnabled) {
189         renderDebug(geoPainter, viewport, visiblePlacemarks);
190     }
191 
192     return true;
193 }
194 
renderState() const195 RenderState PlacemarkLayer::renderState() const
196 {
197     return RenderState(QStringLiteral("Placemarks"));
198 }
199 
runtimeTrace() const200 QString PlacemarkLayer::runtimeTrace() const
201 {
202     return m_layout.runtimeTrace();
203 }
204 
whichPlacemarkAt(const QPoint & pos)205 QVector<const GeoDataFeature *> PlacemarkLayer::whichPlacemarkAt( const QPoint &pos )
206 {
207     return m_layout.whichPlacemarkAt( pos );
208 }
209 
hasPlacemarkAt(const QPoint & pos)210 bool PlacemarkLayer::hasPlacemarkAt(const QPoint &pos)
211 {
212     return m_layout.hasPlacemarkAt(pos);
213 }
214 
isDebugModeEnabled() const215 bool PlacemarkLayer::isDebugModeEnabled() const
216 {
217     return m_debugModeEnabled;
218 }
219 
setDebugModeEnabled(bool enabled)220 void PlacemarkLayer::setDebugModeEnabled(bool enabled)
221 {
222     m_debugModeEnabled = enabled;
223 }
224 
setShowPlaces(bool show)225 void PlacemarkLayer::setShowPlaces( bool show )
226 {
227     m_layout.setShowPlaces( show );
228 }
229 
setShowCities(bool show)230 void PlacemarkLayer::setShowCities( bool show )
231 {
232     m_layout.setShowCities( show );
233 }
234 
setShowTerrain(bool show)235 void PlacemarkLayer::setShowTerrain( bool show )
236 {
237     m_layout.setShowTerrain( show );
238 }
239 
setShowOtherPlaces(bool show)240 void PlacemarkLayer::setShowOtherPlaces( bool show )
241 {
242     m_layout.setShowOtherPlaces( show );
243 }
244 
setShowLandingSites(bool show)245 void PlacemarkLayer::setShowLandingSites( bool show )
246 {
247     m_layout.setShowLandingSites( show );
248 }
249 
setShowCraters(bool show)250 void PlacemarkLayer::setShowCraters( bool show )
251 {
252     m_layout.setShowCraters( show );
253 }
254 
setShowMaria(bool show)255 void PlacemarkLayer::setShowMaria( bool show )
256 {
257     m_layout.setShowMaria( show );
258 }
259 
requestStyleReset()260 void PlacemarkLayer::requestStyleReset()
261 {
262     m_layout.requestStyleReset();
263 }
264 
setTileLevel(int tileLevel)265 void PlacemarkLayer::setTileLevel(int tileLevel)
266 {
267     m_tileLevel = tileLevel;
268 }
269 
270 
271 // Test if there a bug in the X server which makes
272 // text fully transparent if it gets written on
273 // QPixmaps that were initialized by filling them
274 // with Qt::transparent
275 
testXBug()276 bool PlacemarkLayer::testXBug()
277 {
278     QString  testchar( "K" );
279     QFont    font( "Sans Serif", 10 );
280 
281     int fontheight = QFontMetrics( font ).height();
282     int fontwidth  = QFontMetrics( font ).width(testchar);
283     int fontascent = QFontMetrics( font ).ascent();
284 
285     QPixmap  pixmap( fontwidth, fontheight );
286     pixmap.fill( Qt::transparent );
287 
288     QPainter textpainter;
289     textpainter.begin( &pixmap );
290     textpainter.setPen( QColor( 0, 0, 0, 255 ) );
291     textpainter.setFont( font );
292     textpainter.drawText( 0, fontascent, testchar );
293     textpainter.end();
294 
295     QImage image = pixmap.toImage();
296 
297     for ( int x = 0; x < fontwidth; ++x ) {
298         for ( int y = 0; y < fontheight; ++y ) {
299             if ( qAlpha( image.pixel( x, y ) ) > 0 )
300                 return false;
301         }
302     }
303 
304     return true;
305 }
306 
renderDebug(GeoPainter * painter,ViewportParams * viewport,const QVector<VisiblePlacemark * > & placemarks) const307 void PlacemarkLayer::renderDebug(GeoPainter *painter, ViewportParams *viewport, const QVector<VisiblePlacemark *> &placemarks) const
308 {
309     painter->save();
310     painter->setFont(QFont(QStringLiteral("Sans Serif"), 7));
311     painter->setBrush(QBrush(Qt::NoBrush));
312     auto const latLonAltBox = viewport->viewLatLonAltBox();
313 
314     using Placemarks = QSet<VisiblePlacemark *>;
315     Placemarks const hidden = Placemarks::fromList(m_layout.visiblePlacemarks()).subtract(Placemarks::fromList(placemarks.toList()));
316 
317     for (auto placemark: hidden) {
318         bool const inside = latLonAltBox.contains(placemark->coordinates());
319         painter->setPen(QPen(QColor(inside ? Qt::red : Qt::darkYellow)));
320         painter->drawRect(placemark->boundingBox());
321     }
322 
323     painter->setPen(QPen(QColor(Qt::blue)));
324     for (auto placemark: placemarks) {
325         painter->drawRect(placemark->boundingBox());
326     }
327 
328     painter->setPen(QPen(QColor(Qt::green)));
329     for (auto placemark: placemarks) {
330         painter->drawRect(placemark->labelRect());
331         painter->drawRect(placemark->symbolRect());
332     }
333 
334     auto const height = painter->fontMetrics().height();
335     painter->setPen(QPen(QColor(Qt::black)));
336     for (auto placemark: placemarks) {
337         QPoint position = placemark->symbolRect().bottomLeft().toPoint() + QPoint(0, qRound(0.8 * height));
338         auto const popularity = placemark->placemark()->popularity();
339         painter->drawText(position, QStringLiteral("p: %1").arg(popularity));
340         position -= QPoint(0, placemark->symbolRect().height() + height);
341         auto const zoomLevel = placemark->placemark()->zoomLevel();
342         painter->drawText(position, QStringLiteral("z: %1").arg(zoomLevel));
343     }
344 
345     painter->restore();
346 }
347 
setLevelTagDebugModeEnabled(bool enabled)348 void PlacemarkLayer::setLevelTagDebugModeEnabled(bool enabled)
349 {
350     if (m_levelTagDebugModeEnabled != enabled) {
351         m_levelTagDebugModeEnabled = enabled;
352         emit repaintNeeded();
353     }
354 }
355 
levelTagDebugModeEnabled() const356 bool PlacemarkLayer::levelTagDebugModeEnabled() const
357 {
358     return m_levelTagDebugModeEnabled;
359 }
360 
setDebugLevelTag(int level)361 void PlacemarkLayer::setDebugLevelTag(int level)
362 {
363     if (m_debugLevelTag != level) {
364         m_debugLevelTag = level;
365         emit repaintNeeded();
366     }
367 }
368 
369 #include "moc_PlacemarkLayer.cpp"
370 
371