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