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"