File indexing completed on 2024-05-05 03:49:49
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org> 0004 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org> 0005 // SPDX-FileCopyrightText: 2008, 2009, 2010 Jens-Michael Hoffmann <jmho@c-xx.com> 0006 // SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>// 0007 0008 #include "TextureLayer.h" 0009 0010 #include <qmath.h> 0011 #include <QTimer> 0012 #include <QList> 0013 #include <QSortFilterProxyModel> 0014 0015 #include "SphericalScanlineTextureMapper.h" 0016 #include "EquirectScanlineTextureMapper.h" 0017 #include "MercatorScanlineTextureMapper.h" 0018 #include "GenericScanlineTextureMapper.h" 0019 #include "TileScalingTextureMapper.h" 0020 #include "GeoDataGroundOverlay.h" 0021 #include "GeoPainter.h" 0022 #include "GeoSceneGroup.h" 0023 #include "GeoSceneTextureTileDataset.h" 0024 #include "GeoSceneTypes.h" 0025 #include "MergedLayerDecorator.h" 0026 #include "MarbleDebug.h" 0027 #include "MarbleDirs.h" 0028 #include "MarblePlacemarkModel.h" 0029 #include "StackedTile.h" 0030 #include "StackedTileLoader.h" 0031 #include "SunLocator.h" 0032 #include "TextureColorizer.h" 0033 #include "TileLoader.h" 0034 #include "ViewportParams.h" 0035 0036 namespace Marble 0037 { 0038 0039 const int REPAINT_SCHEDULING_INTERVAL = 1000; 0040 0041 class Q_DECL_HIDDEN TextureLayer::Private 0042 { 0043 public: 0044 Private( HttpDownloadManager *downloadManager, 0045 PluginManager* pluginManager, 0046 const SunLocator *sunLocator, 0047 QAbstractItemModel *groundOverlayModel, 0048 TextureLayer *parent ); 0049 0050 void requestDelayedRepaint(); 0051 void updateTextureLayers(); 0052 void updateTile( const TileId &tileId, const QImage &tileImage ); 0053 0054 void addGroundOverlays( const QModelIndex& parent, int first, int last ); 0055 void removeGroundOverlays( const QModelIndex& parent, int first, int last ); 0056 void resetGroundOverlaysCache(); 0057 0058 void updateGroundOverlays(); 0059 void addCustomTextures(); 0060 0061 static bool drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 ); 0062 0063 public: 0064 TextureLayer *const m_parent; 0065 const SunLocator *const m_sunLocator; 0066 TileLoader m_loader; 0067 MergedLayerDecorator m_layerDecorator; 0068 StackedTileLoader m_tileLoader; 0069 GeoDataCoordinates m_centerCoordinates; 0070 int m_tileZoomLevel; 0071 TextureMapperInterface *m_texmapper; 0072 TextureColorizer *m_texcolorizer; 0073 QVector<const GeoSceneTextureTileDataset *> m_textures; 0074 const GeoSceneGroup *m_textureLayerSettings; 0075 QString m_runtimeTrace; 0076 QSortFilterProxyModel m_groundOverlayModel; 0077 QList<const GeoDataGroundOverlay *> m_groundOverlayCache; 0078 QMap<QString, GeoSceneTextureTileDataset *> m_customTextures; 0079 // For scheduling repaints 0080 QTimer m_repaintTimer; 0081 RenderState m_renderState; 0082 }; 0083 0084 TextureLayer::Private::Private( HttpDownloadManager *downloadManager, 0085 PluginManager* pluginManager, 0086 const SunLocator *sunLocator, 0087 QAbstractItemModel *groundOverlayModel, 0088 TextureLayer *parent ) 0089 : m_parent( parent ) 0090 , m_sunLocator( sunLocator ) 0091 , m_loader( downloadManager, pluginManager ) 0092 , m_layerDecorator( &m_loader, sunLocator ) 0093 , m_tileLoader( &m_layerDecorator ) 0094 , m_centerCoordinates() 0095 , m_tileZoomLevel( -1 ) 0096 , m_texmapper( nullptr ) 0097 , m_texcolorizer( nullptr ) 0098 , m_textureLayerSettings( nullptr ) 0099 , m_repaintTimer() 0100 { 0101 m_groundOverlayModel.setSourceModel( groundOverlayModel ); 0102 m_groundOverlayModel.setDynamicSortFilter( true ); 0103 m_groundOverlayModel.setSortRole ( MarblePlacemarkModel::PopularityIndexRole ); 0104 m_groundOverlayModel.sort (0, Qt::AscendingOrder ); 0105 0106 connect( &m_groundOverlayModel, SIGNAL(rowsInserted(QModelIndex,int,int)), 0107 m_parent, SLOT(addGroundOverlays(QModelIndex,int,int)) ); 0108 0109 connect( &m_groundOverlayModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), 0110 m_parent, SLOT(removeGroundOverlays(QModelIndex,int,int)) ); 0111 0112 connect( &m_groundOverlayModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 0113 m_parent, SLOT(resetGroundOverlaysCache()) ); 0114 0115 connect( &m_groundOverlayModel, SIGNAL(modelReset()), 0116 m_parent, SLOT(resetGroundOverlaysCache()) ); 0117 0118 updateGroundOverlays(); 0119 } 0120 0121 void TextureLayer::Private::requestDelayedRepaint() 0122 { 0123 if ( m_texmapper ) { 0124 m_texmapper->setRepaintNeeded(); 0125 } 0126 0127 if ( !m_repaintTimer.isActive() ) { 0128 m_repaintTimer.start(); 0129 } 0130 } 0131 0132 void TextureLayer::Private::updateTextureLayers() 0133 { 0134 QVector<GeoSceneTextureTileDataset const *> result; 0135 0136 for ( const GeoSceneTextureTileDataset *candidate: m_textures ) { 0137 bool enabled = true; 0138 if ( m_textureLayerSettings ) { 0139 const bool propertyExists = m_textureLayerSettings->propertyValue( candidate->name(), enabled ); 0140 enabled |= !propertyExists; // if property doesn't exist, enable texture nevertheless 0141 } 0142 if ( enabled ) { 0143 result.append( candidate ); 0144 mDebug() << "enabling texture" << candidate->name(); 0145 } else { 0146 mDebug() << "disabling texture" << candidate->name(); 0147 } 0148 } 0149 0150 updateGroundOverlays(); 0151 0152 m_layerDecorator.setTextureLayers( result ); 0153 m_tileLoader.clear(); 0154 0155 m_tileZoomLevel = -1; 0156 m_parent->setNeedsUpdate(); 0157 } 0158 0159 void TextureLayer::Private::updateTile( const TileId &tileId, const QImage &tileImage ) 0160 { 0161 if ( tileImage.isNull() ) 0162 return; // keep tiles in cache to improve performance 0163 0164 m_tileLoader.updateTile( tileId, tileImage ); 0165 0166 requestDelayedRepaint(); 0167 } 0168 0169 bool TextureLayer::Private::drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 ) 0170 { 0171 return o1->drawOrder() < o2->drawOrder(); 0172 } 0173 0174 void TextureLayer::Private::addGroundOverlays( const QModelIndex& parent, int first, int last ) 0175 { 0176 for ( int i = first; i <= last; ++i ) { 0177 QModelIndex index = m_groundOverlayModel.index( i, 0, parent ); 0178 const GeoDataGroundOverlay *overlay = static_cast<GeoDataGroundOverlay *>( qvariant_cast<GeoDataObject *>( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) ); 0179 0180 if ( overlay->icon().isNull() ) { 0181 continue; 0182 } 0183 0184 int pos = std::lower_bound( m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan ) - m_groundOverlayCache.begin(); 0185 m_groundOverlayCache.insert( pos, overlay ); 0186 } 0187 0188 updateGroundOverlays(); 0189 0190 m_parent->reset(); 0191 } 0192 0193 void TextureLayer::Private::removeGroundOverlays( const QModelIndex& parent, int first, int last ) 0194 { 0195 for ( int i = first; i <= last; ++i ) { 0196 QModelIndex index = m_groundOverlayModel.index( i, 0, parent ); 0197 const GeoDataGroundOverlay *overlay = static_cast<GeoDataGroundOverlay *>( qvariant_cast<GeoDataObject *>( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) ); 0198 0199 int pos = std::lower_bound( m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan ) - m_groundOverlayCache.begin(); 0200 if (pos >= 0 && pos < m_groundOverlayCache.size() ) { 0201 m_groundOverlayCache.removeAt( pos ); 0202 } 0203 } 0204 0205 updateGroundOverlays(); 0206 0207 m_parent->reset(); 0208 } 0209 0210 void TextureLayer::Private::resetGroundOverlaysCache() 0211 { 0212 m_groundOverlayCache.clear(); 0213 0214 updateGroundOverlays(); 0215 0216 m_parent->reset(); 0217 } 0218 0219 void TextureLayer::Private::updateGroundOverlays() 0220 { 0221 if ( !m_texcolorizer ) { 0222 m_layerDecorator.updateGroundOverlays( m_groundOverlayCache ); 0223 } 0224 else { 0225 m_layerDecorator.updateGroundOverlays( QList<const GeoDataGroundOverlay *>() ); 0226 } 0227 } 0228 0229 void TextureLayer::Private::addCustomTextures() 0230 { 0231 m_textures.reserve(m_textures.size() + m_customTextures.size()); 0232 for (GeoSceneTextureTileDataset *t: m_customTextures) 0233 { 0234 m_textures.append(t); 0235 } 0236 } 0237 0238 TextureLayer::TextureLayer( HttpDownloadManager *downloadManager, 0239 PluginManager* pluginManager, 0240 const SunLocator *sunLocator, 0241 QAbstractItemModel *groundOverlayModel ) 0242 : TileLayer() 0243 , d( new Private( downloadManager, pluginManager, sunLocator, groundOverlayModel, this ) ) 0244 { 0245 connect( &d->m_loader, SIGNAL(tileCompleted(TileId,QImage)), 0246 this, SLOT(updateTile(TileId,QImage)) ); 0247 0248 // Repaint timer 0249 d->m_repaintTimer.setSingleShot( true ); 0250 d->m_repaintTimer.setInterval( REPAINT_SCHEDULING_INTERVAL ); 0251 connect( &d->m_repaintTimer, SIGNAL(timeout()), 0252 this, SIGNAL(repaintNeeded()) ); 0253 } 0254 0255 TextureLayer::~TextureLayer() 0256 { 0257 qDeleteAll(d->m_customTextures); 0258 delete d->m_texmapper; 0259 delete d->m_texcolorizer; 0260 delete d; 0261 } 0262 0263 void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument ) 0264 { 0265 if( d->m_texcolorizer ) { 0266 d->m_texcolorizer->addSeaDocument( seaDocument ); 0267 reset(); 0268 } 0269 } 0270 0271 void TextureLayer::addLandDocument( const GeoDataDocument *landDocument ) 0272 { 0273 if( d->m_texcolorizer ) { 0274 d->m_texcolorizer->addLandDocument( landDocument ); 0275 reset(); 0276 } 0277 } 0278 0279 int TextureLayer::layerCount() const 0280 { 0281 return d->m_layerDecorator.textureLayersSize(); 0282 } 0283 0284 bool TextureLayer::showSunShading() const 0285 { 0286 return d->m_layerDecorator.showSunShading(); 0287 } 0288 0289 bool TextureLayer::showCityLights() const 0290 { 0291 return d->m_layerDecorator.showCityLights(); 0292 } 0293 0294 bool TextureLayer::render( GeoPainter *painter, ViewportParams *viewport, 0295 const QString &renderPos, GeoSceneLayer *layer ) 0296 { 0297 Q_UNUSED( renderPos ); 0298 Q_UNUSED( layer ); 0299 d->m_runtimeTrace = QStringLiteral("Texture Cache: %1 ").arg(d->m_tileLoader.tileCount()); 0300 d->m_renderState = RenderState(QStringLiteral("Texture Tiles")); 0301 0302 // Timers cannot be stopped from another thread (e.g. from QtQuick RenderThread). 0303 if (QThread::currentThread() == QCoreApplication::instance()->thread()) { 0304 // Stop repaint timer if it is already running 0305 if (d->m_repaintTimer.isActive()) { 0306 d->m_repaintTimer.stop(); 0307 } 0308 } 0309 0310 if ( d->m_textures.isEmpty() ) 0311 return false; 0312 0313 if ( d->m_layerDecorator.textureLayersSize() == 0 ) 0314 return false; 0315 0316 if ( !d->m_texmapper ) 0317 return false; 0318 0319 if ( d->m_centerCoordinates.longitude() != viewport->centerLongitude() || 0320 d->m_centerCoordinates.latitude() != viewport->centerLatitude() ) { 0321 d->m_centerCoordinates.setLongitude( viewport->centerLongitude() ); 0322 d->m_centerCoordinates.setLatitude( viewport->centerLatitude() ); 0323 d->m_texmapper->setRepaintNeeded(); 0324 } 0325 0326 // choose the smaller dimension for selecting the tile level, leading to higher-resolution results 0327 const int levelZeroWidth = d->m_layerDecorator.tileSize().width() * d->m_layerDecorator.tileColumnCount( 0 ); 0328 const int levelZeroHight = d->m_layerDecorator.tileSize().height() * d->m_layerDecorator.tileRowCount( 0 ); 0329 const int levelZeroMinDimension = qMin( levelZeroWidth, levelZeroHight ); 0330 0331 // limit to 1 as dirty fix for invalid entry linearLevel 0332 const qreal linearLevel = qMax<qreal>( 1.0, viewport->radius() * 4.0 / levelZeroMinDimension ); 0333 0334 // As our tile resolution doubles with each level we calculate 0335 // the tile level from tilesize and the globe radius via log(2) 0336 const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ) * 1.00001; // snap to the sharper tile level a tiny bit earlier 0337 // to work around rounding errors when the radius 0338 // roughly equals the global texture width 0339 0340 const int tileLevel = qMin<int>( d->m_layerDecorator.maximumTileLevel(), tileLevelF ); 0341 0342 if ( tileLevel != d->m_tileZoomLevel ) { 0343 d->m_tileZoomLevel = tileLevel; 0344 emit tileLevelChanged( d->m_tileZoomLevel ); 0345 } 0346 0347 const QRect dirtyRect = QRect( QPoint( 0, 0), viewport->size() ); 0348 d->m_texmapper->mapTexture( painter, viewport, d->m_tileZoomLevel, dirtyRect, d->m_texcolorizer ); 0349 d->m_renderState.addChild( d->m_tileLoader.renderState() ); 0350 return true; 0351 } 0352 0353 QString TextureLayer::runtimeTrace() const 0354 { 0355 return d->m_runtimeTrace; 0356 } 0357 0358 void TextureLayer::setShowRelief( bool show ) 0359 { 0360 if ( d->m_texcolorizer ) { 0361 d->m_texcolorizer->setShowRelief( show ); 0362 } 0363 } 0364 0365 void TextureLayer::setShowSunShading( bool show ) 0366 { 0367 disconnect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)), 0368 this, SLOT(reset()) ); 0369 0370 if ( show ) { 0371 connect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)), 0372 this, SLOT(reset()) ); 0373 } 0374 0375 d->m_layerDecorator.setShowSunShading( show ); 0376 0377 reset(); 0378 } 0379 0380 void TextureLayer::setShowCityLights( bool show ) 0381 { 0382 d->m_layerDecorator.setShowCityLights( show ); 0383 0384 reset(); 0385 } 0386 0387 void TextureLayer::setShowTileId( bool show ) 0388 { 0389 d->m_layerDecorator.setShowTileId( show ); 0390 0391 reset(); 0392 } 0393 0394 void TextureLayer::setProjection( Projection projection ) 0395 { 0396 if ( d->m_textures.isEmpty() ) { 0397 return; 0398 } 0399 0400 // FIXME: replace this with an approach based on the factory method pattern. 0401 delete d->m_texmapper; 0402 0403 switch( projection ) { 0404 case Spherical: 0405 d->m_texmapper = new SphericalScanlineTextureMapper( &d->m_tileLoader ); 0406 break; 0407 case Equirectangular: 0408 d->m_texmapper = new EquirectScanlineTextureMapper( &d->m_tileLoader ); 0409 break; 0410 case Mercator: 0411 if (d->m_textures.at(0)->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator) { 0412 d->m_texmapper = new TileScalingTextureMapper( &d->m_tileLoader ); 0413 } else { 0414 d->m_texmapper = new MercatorScanlineTextureMapper( &d->m_tileLoader ); 0415 } 0416 break; 0417 case Gnomonic: 0418 case Stereographic: 0419 case LambertAzimuthal: 0420 case AzimuthalEquidistant: 0421 case VerticalPerspective: 0422 d->m_texmapper = new GenericScanlineTextureMapper( &d->m_tileLoader ); 0423 break; 0424 default: 0425 d->m_texmapper = nullptr; 0426 } 0427 Q_ASSERT( d->m_texmapper ); 0428 } 0429 0430 void TextureLayer::setNeedsUpdate() 0431 { 0432 if ( d->m_texmapper ) { 0433 d->m_texmapper->setRepaintNeeded(); 0434 } 0435 0436 emit repaintNeeded(); 0437 } 0438 0439 void TextureLayer::setVolatileCacheLimit( quint64 kilobytes ) 0440 { 0441 d->m_tileLoader.setVolatileCacheLimit( kilobytes ); 0442 } 0443 0444 void TextureLayer::reset() 0445 { 0446 d->m_tileLoader.clear(); 0447 setNeedsUpdate(); 0448 } 0449 0450 void TextureLayer::reload() 0451 { 0452 for ( const TileId &id: d->m_tileLoader.visibleTiles() ) { 0453 // it's debatable here, whether DownloadBulk or DownloadBrowse should be used 0454 // but since "reload" or "refresh" seems to be a common action of a browser and it 0455 // allows for more connections (in our model), use "DownloadBrowse" 0456 d->m_layerDecorator.downloadStackedTile( id, DownloadBrowse ); 0457 } 0458 } 0459 0460 void TextureLayer::downloadStackedTile( const TileId &stackedTileId ) 0461 { 0462 d->m_layerDecorator.downloadStackedTile( stackedTileId, DownloadBulk ); 0463 } 0464 0465 void TextureLayer::setMapTheme( const QVector<const GeoSceneTextureTileDataset *> &textures, const GeoSceneGroup *textureLayerSettings, const QString &seaFile, const QString &landFile ) 0466 { 0467 delete d->m_texcolorizer; 0468 d->m_texcolorizer = nullptr; 0469 0470 if ( QFileInfo( seaFile ).isReadable() || QFileInfo( landFile ).isReadable() ) { 0471 d->m_texcolorizer = new TextureColorizer( seaFile, landFile ); 0472 } 0473 0474 d->m_textures = textures; 0475 d->addCustomTextures(); 0476 d->m_textureLayerSettings = textureLayerSettings; 0477 0478 if ( d->m_textureLayerSettings ) { 0479 connect( d->m_textureLayerSettings, SIGNAL(valueChanged(QString,bool)), 0480 this, SLOT(updateTextureLayers()) ); 0481 } 0482 0483 d->updateTextureLayers(); 0484 } 0485 0486 int TextureLayer::tileZoomLevel() const 0487 { 0488 return d->m_tileZoomLevel; 0489 } 0490 0491 QSize TextureLayer::tileSize() const 0492 { 0493 return d->m_layerDecorator.tileSize(); 0494 } 0495 0496 const GeoSceneAbstractTileProjection *TextureLayer::tileProjection() const 0497 { 0498 return d->m_layerDecorator.tileProjection(); 0499 } 0500 0501 int TextureLayer::tileColumnCount( int level ) const 0502 { 0503 return d->m_layerDecorator.tileColumnCount( level ); 0504 } 0505 0506 int TextureLayer::tileRowCount( int level ) const 0507 { 0508 return d->m_layerDecorator.tileRowCount( level ); 0509 } 0510 0511 quint64 TextureLayer::volatileCacheLimit() const 0512 { 0513 return d->m_tileLoader.volatileCacheLimit(); 0514 } 0515 0516 int TextureLayer::preferredRadiusCeil( int radius ) const 0517 { 0518 if (!d->m_layerDecorator.hasTextureLayer()) { 0519 return radius; 0520 } 0521 const int tileWidth = d->m_layerDecorator.tileSize().width(); 0522 const int levelZeroColumns = d->m_layerDecorator.tileColumnCount( 0 ); 0523 const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns ); 0524 const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ); 0525 const int tileLevel = qCeil( tileLevelF ); 0526 0527 if ( tileLevel < 0 ) 0528 return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel); 0529 0530 return ( tileWidth * levelZeroColumns / 4 ) << tileLevel; 0531 } 0532 0533 int TextureLayer::preferredRadiusFloor( int radius ) const 0534 { 0535 if (!d->m_layerDecorator.hasTextureLayer()) { 0536 return radius; 0537 } 0538 const int tileWidth = d->m_layerDecorator.tileSize().width(); 0539 const int levelZeroColumns = d->m_layerDecorator.tileColumnCount( 0 ); 0540 const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns ); 0541 const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ); 0542 const int tileLevel = qFloor( tileLevelF ); 0543 0544 if ( tileLevel < 0 ) 0545 return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel); 0546 0547 return ( tileWidth * levelZeroColumns / 4 ) << tileLevel; 0548 } 0549 0550 RenderState TextureLayer::renderState() const 0551 { 0552 return d->m_renderState; 0553 } 0554 0555 QString TextureLayer::addTextureLayer(GeoSceneTextureTileDataset* texture) 0556 { 0557 if (!texture) 0558 return QString(); //Not a sane call 0559 0560 QString sourceDir = texture->sourceDir(); 0561 if (!d->m_customTextures.contains(sourceDir)) 0562 { // Add if not present. For update, remove the old texture first. 0563 d->m_customTextures.insert(sourceDir, texture); 0564 d->m_textures.append(texture); 0565 d->updateTextureLayers(); 0566 } 0567 return sourceDir; 0568 } 0569 0570 void TextureLayer::removeTextureLayer(const QString &key) 0571 { 0572 if (d->m_customTextures.contains(key)) 0573 { 0574 GeoSceneTextureTileDataset *texture = d->m_customTextures.value(key); 0575 d->m_customTextures.remove(key); 0576 d->m_textures.remove(d->m_textures.indexOf(texture)); 0577 delete texture; 0578 d->updateTextureLayers(); 0579 } 0580 } 0581 0582 } 0583 0584 #include "moc_TextureLayer.cpp"