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"