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