1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
4 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
5 // SPDX-FileCopyrightText: 2008, 2009, 2010 Jens-Michael Hoffmann <jmho@c-xx.com>
6 // SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>//
7 
8 #include "TextureLayer.h"
9 
10 #include <qmath.h>
11 #include <QTimer>
12 #include <QList>
13 #include <QSortFilterProxyModel>
14 
15 #include "SphericalScanlineTextureMapper.h"
16 #include "EquirectScanlineTextureMapper.h"
17 #include "MercatorScanlineTextureMapper.h"
18 #include "GenericScanlineTextureMapper.h"
19 #include "TileScalingTextureMapper.h"
20 #include "GeoDataGroundOverlay.h"
21 #include "GeoPainter.h"
22 #include "GeoSceneGroup.h"
23 #include "GeoSceneTextureTileDataset.h"
24 #include "GeoSceneTypes.h"
25 #include "MergedLayerDecorator.h"
26 #include "MarbleDebug.h"
27 #include "MarbleDirs.h"
28 #include "MarblePlacemarkModel.h"
29 #include "StackedTile.h"
30 #include "StackedTileLoader.h"
31 #include "SunLocator.h"
32 #include "TextureColorizer.h"
33 #include "TileLoader.h"
34 #include "ViewportParams.h"
35 
36 namespace Marble
37 {
38 
39 const int REPAINT_SCHEDULING_INTERVAL = 1000;
40 
41 class Q_DECL_HIDDEN TextureLayer::Private
42 {
43 public:
44     Private( HttpDownloadManager *downloadManager,
45              PluginManager* pluginManager,
46              const SunLocator *sunLocator,
47              QAbstractItemModel *groundOverlayModel,
48              TextureLayer *parent );
49 
50     void requestDelayedRepaint();
51     void updateTextureLayers();
52     void updateTile( const TileId &tileId, const QImage &tileImage );
53 
54     void addGroundOverlays( const QModelIndex& parent, int first, int last );
55     void removeGroundOverlays( const QModelIndex& parent, int first, int last );
56     void resetGroundOverlaysCache();
57 
58     void updateGroundOverlays();
59     void addCustomTextures();
60 
61     static bool drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 );
62 
63 public:
64     TextureLayer  *const m_parent;
65     const SunLocator *const m_sunLocator;
66     TileLoader m_loader;
67     MergedLayerDecorator m_layerDecorator;
68     StackedTileLoader    m_tileLoader;
69     GeoDataCoordinates m_centerCoordinates;
70     int m_tileZoomLevel;
71     TextureMapperInterface *m_texmapper;
72     TextureColorizer *m_texcolorizer;
73     QVector<const GeoSceneTextureTileDataset *> m_textures;
74     const GeoSceneGroup *m_textureLayerSettings;
75     QString m_runtimeTrace;
76     QSortFilterProxyModel m_groundOverlayModel;
77     QList<const GeoDataGroundOverlay *> m_groundOverlayCache;
78     QMap<QString, GeoSceneTextureTileDataset *> m_customTextures;
79     // For scheduling repaints
80     QTimer           m_repaintTimer;
81     RenderState m_renderState;
82 };
83 
Private(HttpDownloadManager * downloadManager,PluginManager * pluginManager,const SunLocator * sunLocator,QAbstractItemModel * groundOverlayModel,TextureLayer * parent)84 TextureLayer::Private::Private( HttpDownloadManager *downloadManager,
85                                 PluginManager* pluginManager,
86                                 const SunLocator *sunLocator,
87                                 QAbstractItemModel *groundOverlayModel,
88                                 TextureLayer *parent )
89     : m_parent( parent )
90     , m_sunLocator( sunLocator )
91     , m_loader( downloadManager, pluginManager )
92     , m_layerDecorator( &m_loader, sunLocator )
93     , m_tileLoader( &m_layerDecorator )
94     , m_centerCoordinates()
95     , m_tileZoomLevel( -1 )
96     , m_texmapper( nullptr )
97     , m_texcolorizer( nullptr )
98     , m_textureLayerSettings( nullptr )
99     , m_repaintTimer()
100 {
101     m_groundOverlayModel.setSourceModel( groundOverlayModel );
102     m_groundOverlayModel.setDynamicSortFilter( true );
103     m_groundOverlayModel.setSortRole ( MarblePlacemarkModel::PopularityIndexRole );
104     m_groundOverlayModel.sort (0, Qt::AscendingOrder );
105 
106     connect( &m_groundOverlayModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
107              m_parent,              SLOT(addGroundOverlays(QModelIndex,int,int)) );
108 
109     connect( &m_groundOverlayModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
110              m_parent,              SLOT(removeGroundOverlays(QModelIndex,int,int)) );
111 
112     connect( &m_groundOverlayModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
113              m_parent,              SLOT(resetGroundOverlaysCache()) );
114 
115     connect( &m_groundOverlayModel, SIGNAL(modelReset()),
116              m_parent,              SLOT(resetGroundOverlaysCache()) );
117 
118     updateGroundOverlays();
119 }
120 
requestDelayedRepaint()121 void TextureLayer::Private::requestDelayedRepaint()
122 {
123     if ( m_texmapper ) {
124         m_texmapper->setRepaintNeeded();
125     }
126 
127     if ( !m_repaintTimer.isActive() ) {
128         m_repaintTimer.start();
129     }
130 }
131 
updateTextureLayers()132 void TextureLayer::Private::updateTextureLayers()
133 {
134     QVector<GeoSceneTextureTileDataset const *> result;
135 
136     for ( const GeoSceneTextureTileDataset *candidate: m_textures ) {
137         bool enabled = true;
138         if ( m_textureLayerSettings ) {
139             const bool propertyExists = m_textureLayerSettings->propertyValue( candidate->name(), enabled );
140             enabled |= !propertyExists; // if property doesn't exist, enable texture nevertheless
141         }
142         if ( enabled ) {
143             result.append( candidate );
144             mDebug() << "enabling texture" << candidate->name();
145         } else {
146             mDebug() << "disabling texture" << candidate->name();
147         }
148     }
149 
150     updateGroundOverlays();
151 
152     m_layerDecorator.setTextureLayers( result );
153     m_tileLoader.clear();
154 
155     m_tileZoomLevel = -1;
156     m_parent->setNeedsUpdate();
157 }
158 
updateTile(const TileId & tileId,const QImage & tileImage)159 void TextureLayer::Private::updateTile( const TileId &tileId, const QImage &tileImage )
160 {
161     if ( tileImage.isNull() )
162         return; // keep tiles in cache to improve performance
163 
164     m_tileLoader.updateTile( tileId, tileImage );
165 
166     requestDelayedRepaint();
167 }
168 
drawOrderLessThan(const GeoDataGroundOverlay * o1,const GeoDataGroundOverlay * o2)169 bool TextureLayer::Private::drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 )
170 {
171     return o1->drawOrder() < o2->drawOrder();
172 }
173 
addGroundOverlays(const QModelIndex & parent,int first,int last)174 void TextureLayer::Private::addGroundOverlays( const QModelIndex& parent, int first, int last )
175 {
176     for ( int i = first; i <= last; ++i ) {
177         QModelIndex index = m_groundOverlayModel.index( i, 0, parent );
178         const GeoDataGroundOverlay *overlay = static_cast<GeoDataGroundOverlay *>( qvariant_cast<GeoDataObject *>( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) );
179 
180         if ( overlay->icon().isNull() ) {
181             continue;
182         }
183 
184         int pos = std::lower_bound( m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan ) - m_groundOverlayCache.begin();
185         m_groundOverlayCache.insert( pos, overlay );
186     }
187 
188     updateGroundOverlays();
189 
190     m_parent->reset();
191 }
192 
removeGroundOverlays(const QModelIndex & parent,int first,int last)193 void TextureLayer::Private::removeGroundOverlays( const QModelIndex& parent, int first, int last )
194 {
195     for ( int i = first; i <= last; ++i ) {
196         QModelIndex index = m_groundOverlayModel.index( i, 0, parent );
197         const GeoDataGroundOverlay *overlay = static_cast<GeoDataGroundOverlay *>( qvariant_cast<GeoDataObject *>( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) );
198 
199         int pos = std::lower_bound( m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan ) - m_groundOverlayCache.begin();
200         if (pos >= 0 && pos < m_groundOverlayCache.size() ) {
201             m_groundOverlayCache.removeAt( pos );
202         }
203     }
204 
205     updateGroundOverlays();
206 
207     m_parent->reset();
208 }
209 
resetGroundOverlaysCache()210 void TextureLayer::Private::resetGroundOverlaysCache()
211 {
212     m_groundOverlayCache.clear();
213 
214     updateGroundOverlays();
215 
216     m_parent->reset();
217 }
218 
updateGroundOverlays()219 void TextureLayer::Private::updateGroundOverlays()
220 {
221     if ( !m_texcolorizer ) {
222         m_layerDecorator.updateGroundOverlays( m_groundOverlayCache );
223     }
224     else {
225         m_layerDecorator.updateGroundOverlays( QList<const GeoDataGroundOverlay *>() );
226     }
227 }
228 
addCustomTextures()229 void TextureLayer::Private::addCustomTextures()
230 {
231     m_textures.reserve(m_textures.size() + m_customTextures.size());
232     for (GeoSceneTextureTileDataset *t: m_customTextures)
233     {
234         m_textures.append(t);
235     }
236 }
237 
TextureLayer(HttpDownloadManager * downloadManager,PluginManager * pluginManager,const SunLocator * sunLocator,QAbstractItemModel * groundOverlayModel)238 TextureLayer::TextureLayer( HttpDownloadManager *downloadManager,
239                             PluginManager* pluginManager,
240                             const SunLocator *sunLocator,
241                             QAbstractItemModel *groundOverlayModel )
242     : QObject()
243     , d( new Private( downloadManager, pluginManager, sunLocator, groundOverlayModel, this ) )
244 {
245     connect( &d->m_loader, SIGNAL(tileCompleted(TileId,QImage)),
246              this, SLOT(updateTile(TileId,QImage)) );
247 
248     // Repaint timer
249     d->m_repaintTimer.setSingleShot( true );
250     d->m_repaintTimer.setInterval( REPAINT_SCHEDULING_INTERVAL );
251     connect( &d->m_repaintTimer, SIGNAL(timeout()),
252              this, SIGNAL(repaintNeeded()) );
253 }
254 
~TextureLayer()255 TextureLayer::~TextureLayer()
256 {
257     qDeleteAll(d->m_customTextures);
258     delete d->m_texmapper;
259     delete d->m_texcolorizer;
260     delete d;
261 }
262 
renderPosition() const263 QStringList TextureLayer::renderPosition() const
264 {
265     return QStringList(QStringLiteral("SURFACE"));
266 }
267 
addSeaDocument(const GeoDataDocument * seaDocument)268 void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument )
269 {
270     if( d->m_texcolorizer ) {
271         d->m_texcolorizer->addSeaDocument( seaDocument );
272         reset();
273     }
274 }
275 
addLandDocument(const GeoDataDocument * landDocument)276 void TextureLayer::addLandDocument( const GeoDataDocument *landDocument )
277 {
278     if( d->m_texcolorizer ) {
279         d->m_texcolorizer->addLandDocument( landDocument );
280         reset();
281     }
282 }
283 
textureLayerCount() const284 int TextureLayer::textureLayerCount() const
285 {
286     return d->m_layerDecorator.textureLayersSize();
287 }
288 
showSunShading() const289 bool TextureLayer::showSunShading() const
290 {
291     return d->m_layerDecorator.showSunShading();
292 }
293 
showCityLights() const294 bool TextureLayer::showCityLights() const
295 {
296     return d->m_layerDecorator.showCityLights();
297 }
298 
render(GeoPainter * painter,ViewportParams * viewport,const QString & renderPos,GeoSceneLayer * layer)299 bool TextureLayer::render( GeoPainter *painter, ViewportParams *viewport,
300                            const QString &renderPos, GeoSceneLayer *layer )
301 {
302     Q_UNUSED( renderPos );
303     Q_UNUSED( layer );
304     d->m_runtimeTrace = QStringLiteral("Texture Cache: %1 ").arg(d->m_tileLoader.tileCount());
305     d->m_renderState = RenderState(QStringLiteral("Texture Tiles"));
306 
307     // Stop repaint timer if it is already running
308     d->m_repaintTimer.stop();
309 
310     if ( d->m_textures.isEmpty() )
311         return false;
312 
313     if ( d->m_layerDecorator.textureLayersSize() == 0 )
314         return false;
315 
316     if ( !d->m_texmapper )
317         return false;
318 
319     if ( d->m_centerCoordinates.longitude() != viewport->centerLongitude() ||
320          d->m_centerCoordinates.latitude() != viewport->centerLatitude() ) {
321         d->m_centerCoordinates.setLongitude( viewport->centerLongitude() );
322         d->m_centerCoordinates.setLatitude( viewport->centerLatitude() );
323         d->m_texmapper->setRepaintNeeded();
324     }
325 
326     // choose the smaller dimension for selecting the tile level, leading to higher-resolution results
327     const int levelZeroWidth = d->m_layerDecorator.tileSize().width() * d->m_layerDecorator.tileColumnCount( 0 );
328     const int levelZeroHight = d->m_layerDecorator.tileSize().height() * d->m_layerDecorator.tileRowCount( 0 );
329     const int levelZeroMinDimension = qMin( levelZeroWidth, levelZeroHight );
330 
331     // limit to 1 as dirty fix for invalid entry linearLevel
332     const qreal linearLevel = qMax<qreal>( 1.0, viewport->radius() * 4.0 / levelZeroMinDimension );
333 
334     // As our tile resolution doubles with each level we calculate
335     // the tile level from tilesize and the globe radius via log(2)
336     const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ) * 1.00001;  // snap to the sharper tile level a tiny bit earlier
337                                                                          // to work around rounding errors when the radius
338                                                                          // roughly equals the global texture width
339 
340     const int tileLevel = qMin<int>( d->m_layerDecorator.maximumTileLevel(), tileLevelF );
341 
342     if ( tileLevel != d->m_tileZoomLevel ) {
343         d->m_tileZoomLevel = tileLevel;
344         emit tileLevelChanged( d->m_tileZoomLevel );
345     }
346 
347     const QRect dirtyRect = QRect( QPoint( 0, 0), viewport->size() );
348     d->m_texmapper->mapTexture( painter, viewport, d->m_tileZoomLevel, dirtyRect, d->m_texcolorizer );
349     d->m_renderState.addChild( d->m_tileLoader.renderState() );
350     return true;
351 }
352 
runtimeTrace() const353 QString TextureLayer::runtimeTrace() const
354 {
355     return d->m_runtimeTrace;
356 }
357 
setShowRelief(bool show)358 void TextureLayer::setShowRelief( bool show )
359 {
360     if ( d->m_texcolorizer ) {
361         d->m_texcolorizer->setShowRelief( show );
362     }
363 }
364 
setShowSunShading(bool show)365 void TextureLayer::setShowSunShading( bool show )
366 {
367     disconnect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)),
368                 this, SLOT(reset()) );
369 
370     if ( show ) {
371         connect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)),
372                  this,       SLOT(reset()) );
373     }
374 
375     d->m_layerDecorator.setShowSunShading( show );
376 
377     reset();
378 }
379 
setShowCityLights(bool show)380 void TextureLayer::setShowCityLights( bool show )
381 {
382     d->m_layerDecorator.setShowCityLights( show );
383 
384     reset();
385 }
386 
setShowTileId(bool show)387 void TextureLayer::setShowTileId( bool show )
388 {
389     d->m_layerDecorator.setShowTileId( show );
390 
391     reset();
392 }
393 
setProjection(Projection projection)394 void TextureLayer::setProjection( Projection projection )
395 {
396     if ( d->m_textures.isEmpty() ) {
397         return;
398     }
399 
400     // FIXME: replace this with an approach based on the factory method pattern.
401     delete d->m_texmapper;
402 
403     switch( projection ) {
404         case Spherical:
405             d->m_texmapper = new SphericalScanlineTextureMapper( &d->m_tileLoader );
406             break;
407         case Equirectangular:
408             d->m_texmapper = new EquirectScanlineTextureMapper( &d->m_tileLoader );
409             break;
410         case Mercator:
411             if (d->m_textures.at(0)->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator) {
412                 d->m_texmapper = new TileScalingTextureMapper( &d->m_tileLoader );
413             } else {
414                 d->m_texmapper = new MercatorScanlineTextureMapper( &d->m_tileLoader );
415             }
416             break;
417         case Gnomonic:
418         case Stereographic:
419         case LambertAzimuthal:
420         case AzimuthalEquidistant:
421         case VerticalPerspective:
422             d->m_texmapper = new GenericScanlineTextureMapper( &d->m_tileLoader );
423             break;
424         default:
425             d->m_texmapper = nullptr;
426     }
427     Q_ASSERT( d->m_texmapper );
428 }
429 
setNeedsUpdate()430 void TextureLayer::setNeedsUpdate()
431 {
432     if ( d->m_texmapper ) {
433         d->m_texmapper->setRepaintNeeded();
434     }
435 
436     emit repaintNeeded();
437 }
438 
setVolatileCacheLimit(quint64 kilobytes)439 void TextureLayer::setVolatileCacheLimit( quint64 kilobytes )
440 {
441     d->m_tileLoader.setVolatileCacheLimit( kilobytes );
442 }
443 
reset()444 void TextureLayer::reset()
445 {
446     d->m_tileLoader.clear();
447     setNeedsUpdate();
448 }
449 
reload()450 void TextureLayer::reload()
451 {
452     for ( const TileId &id: d->m_tileLoader.visibleTiles() ) {
453         // it's debatable here, whether DownloadBulk or DownloadBrowse should be used
454         // but since "reload" or "refresh" seems to be a common action of a browser and it
455         // allows for more connections (in our model), use "DownloadBrowse"
456         d->m_layerDecorator.downloadStackedTile( id, DownloadBrowse );
457     }
458 }
459 
downloadStackedTile(const TileId & stackedTileId)460 void TextureLayer::downloadStackedTile( const TileId &stackedTileId )
461 {
462     d->m_layerDecorator.downloadStackedTile( stackedTileId, DownloadBulk );
463 }
464 
setMapTheme(const QVector<const GeoSceneTextureTileDataset * > & textures,const GeoSceneGroup * textureLayerSettings,const QString & seaFile,const QString & landFile)465 void TextureLayer::setMapTheme( const QVector<const GeoSceneTextureTileDataset *> &textures, const GeoSceneGroup *textureLayerSettings, const QString &seaFile, const QString &landFile )
466 {
467     delete d->m_texcolorizer;
468     d->m_texcolorizer = nullptr;
469 
470     if ( QFileInfo( seaFile ).isReadable() || QFileInfo( landFile ).isReadable() ) {
471         d->m_texcolorizer = new TextureColorizer( seaFile, landFile );
472     }
473 
474     d->m_textures = textures;
475     d->addCustomTextures();
476     d->m_textureLayerSettings = textureLayerSettings;
477 
478     if ( d->m_textureLayerSettings ) {
479         connect( d->m_textureLayerSettings, SIGNAL(valueChanged(QString,bool)),
480                  this,                      SLOT(updateTextureLayers()) );
481     }
482 
483     d->updateTextureLayers();
484 }
485 
tileZoomLevel() const486 int TextureLayer::tileZoomLevel() const
487 {
488     return d->m_tileZoomLevel;
489 }
490 
tileSize() const491 QSize TextureLayer::tileSize() const
492 {
493     return d->m_layerDecorator.tileSize();
494 }
495 
tileProjection() const496 const GeoSceneAbstractTileProjection *TextureLayer::tileProjection() const
497 {
498     return d->m_layerDecorator.tileProjection();
499 }
500 
tileColumnCount(int level) const501 int TextureLayer::tileColumnCount( int level ) const
502 {
503     return d->m_layerDecorator.tileColumnCount( level );
504 }
505 
tileRowCount(int level) const506 int TextureLayer::tileRowCount( int level ) const
507 {
508     return d->m_layerDecorator.tileRowCount( level );
509 }
510 
volatileCacheLimit() const511 quint64 TextureLayer::volatileCacheLimit() const
512 {
513     return d->m_tileLoader.volatileCacheLimit();
514 }
515 
preferredRadiusCeil(int radius) const516 int TextureLayer::preferredRadiusCeil( int radius ) const
517 {
518     if (!d->m_layerDecorator.hasTextureLayer()) {
519         return radius;
520     }
521     const int tileWidth = d->m_layerDecorator.tileSize().width();
522     const int levelZeroColumns = d->m_layerDecorator.tileColumnCount( 0 );
523     const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns );
524     const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 );
525     const int tileLevel = qCeil( tileLevelF );
526 
527     if ( tileLevel < 0 )
528         return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel);
529 
530     return ( tileWidth * levelZeroColumns / 4 ) << tileLevel;
531 }
532 
preferredRadiusFloor(int radius) const533 int TextureLayer::preferredRadiusFloor( int radius ) const
534 {
535     if (!d->m_layerDecorator.hasTextureLayer()) {
536         return radius;
537     }
538     const int tileWidth = d->m_layerDecorator.tileSize().width();
539     const int levelZeroColumns = d->m_layerDecorator.tileColumnCount( 0 );
540     const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns );
541     const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 );
542     const int tileLevel = qFloor( tileLevelF );
543 
544     if ( tileLevel < 0 )
545         return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel);
546 
547     return ( tileWidth * levelZeroColumns / 4 ) << tileLevel;
548 }
549 
renderState() const550 RenderState TextureLayer::renderState() const
551 {
552     return d->m_renderState;
553 }
554 
addTextureLayer(GeoSceneTextureTileDataset * texture)555 QString TextureLayer::addTextureLayer(GeoSceneTextureTileDataset* texture)
556 {
557     if (!texture)
558         return QString(); //Not a sane call
559 
560     QString sourceDir = texture->sourceDir();
561     if (!d->m_customTextures.contains(sourceDir))
562     {   // Add if not present. For update, remove the old texture first.
563         d->m_customTextures.insert(sourceDir, texture);
564         d->m_textures.append(texture);
565         d->updateTextureLayers();
566     }
567     return sourceDir;
568 }
569 
removeTextureLayer(const QString & key)570 void TextureLayer::removeTextureLayer(const QString &key)
571 {
572     if (d->m_customTextures.contains(key))
573     {
574         GeoSceneTextureTileDataset *texture = d->m_customTextures.value(key);
575         d->m_customTextures.remove(key);
576         d->m_textures.remove(d->m_textures.indexOf(texture));
577         delete texture;
578         d->updateTextureLayers();
579     }
580 }
581 
582 }
583 
584 #include "moc_TextureLayer.cpp"
585