Marble

TextureLayer.cpp
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
36namespace Marble
37{
38
39const int REPAINT_SCHEDULING_INTERVAL = 1000;
40
41class Q_DECL_HIDDEN TextureLayer::Private
42{
43public:
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
63public:
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;
74 const GeoSceneGroup *m_textureLayerSettings;
75 QString m_runtimeTrace;
76 QSortFilterProxyModel m_groundOverlayModel;
77 QList<const GeoDataGroundOverlay *> m_groundOverlayCache;
79 // For scheduling repaints
80 QTimer m_repaintTimer;
81 RenderState m_renderState;
82};
83
84TextureLayer::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
121void 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
132void TextureLayer::Private::updateTextureLayers()
133{
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
159void 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
169bool TextureLayer::Private::drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 )
170{
171 return o1->drawOrder() < o2->drawOrder();
172}
173
174void 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
193void 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
210void TextureLayer::Private::resetGroundOverlaysCache()
211{
212 m_groundOverlayCache.clear();
213
214 updateGroundOverlays();
215
216 m_parent->reset();
217}
218
219void 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
229void 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
238TextureLayer::TextureLayer( HttpDownloadManager *downloadManager,
239 PluginManager* pluginManager,
240 const SunLocator *sunLocator,
241 QAbstractItemModel *groundOverlayModel )
242 : TileLayer()
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
255TextureLayer::~TextureLayer()
256{
257 qDeleteAll(d->m_customTextures);
258 delete d->m_texmapper;
259 delete d->m_texcolorizer;
260 delete d;
261}
262
263void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument )
264{
265 if( d->m_texcolorizer ) {
266 d->m_texcolorizer->addSeaDocument( seaDocument );
267 reset();
268 }
269}
270
271void TextureLayer::addLandDocument( const GeoDataDocument *landDocument )
272{
273 if( d->m_texcolorizer ) {
274 d->m_texcolorizer->addLandDocument( landDocument );
275 reset();
276 }
277}
278
279int TextureLayer::layerCount() const
280{
281 return d->m_layerDecorator.textureLayersSize();
282}
283
284bool TextureLayer::showSunShading() const
285{
286 return d->m_layerDecorator.showSunShading();
287}
288
289bool TextureLayer::showCityLights() const
290{
291 return d->m_layerDecorator.showCityLights();
292}
293
294bool TextureLayer::render( GeoPainter *painter, ViewportParams *viewport,
295 const QString &renderPos, GeoSceneLayer *layer )
296{
297 Q_UNUSED( renderPos );
298 Q_UNUSED( layer );
299 d->m_runtimeTrace = QStringLiteral("Texture Cache: %1 ").arg(d->m_tileLoader.tileCount());
300 d->m_renderState = RenderState(QStringLiteral("Texture Tiles"));
301
302 // Timers cannot be stopped from another thread (e.g. from QtQuick RenderThread).
304 // Stop repaint timer if it is already running
305 if (d->m_repaintTimer.isActive()) {
306 d->m_repaintTimer.stop();
307 }
308 }
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
353QString TextureLayer::runtimeTrace() const
354{
355 return d->m_runtimeTrace;
356}
357
358void TextureLayer::setShowRelief( bool show )
359{
360 if ( d->m_texcolorizer ) {
361 d->m_texcolorizer->setShowRelief( show );
362 }
363}
364
365void 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
380void TextureLayer::setShowCityLights( bool show )
381{
382 d->m_layerDecorator.setShowCityLights( show );
383
384 reset();
385}
386
387void TextureLayer::setShowTileId( bool show )
388{
389 d->m_layerDecorator.setShowTileId( show );
390
391 reset();
392}
393
394void 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:
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
430void TextureLayer::setNeedsUpdate()
431{
432 if ( d->m_texmapper ) {
433 d->m_texmapper->setRepaintNeeded();
434 }
435
436 emit repaintNeeded();
437}
438
439void TextureLayer::setVolatileCacheLimit( quint64 kilobytes )
440{
441 d->m_tileLoader.setVolatileCacheLimit( kilobytes );
442}
443
444void TextureLayer::reset()
445{
446 d->m_tileLoader.clear();
447 setNeedsUpdate();
448}
449
450void 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
460void TextureLayer::downloadStackedTile( const TileId &stackedTileId )
461{
462 d->m_layerDecorator.downloadStackedTile( stackedTileId, DownloadBulk );
463}
464
465void 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
486int TextureLayer::tileZoomLevel() const
487{
488 return d->m_tileZoomLevel;
489}
490
491QSize TextureLayer::tileSize() const
492{
493 return d->m_layerDecorator.tileSize();
494}
495
496const GeoSceneAbstractTileProjection *TextureLayer::tileProjection() const
497{
498 return d->m_layerDecorator.tileProjection();
499}
500
501int TextureLayer::tileColumnCount( int level ) const
502{
503 return d->m_layerDecorator.tileColumnCount( level );
504}
505
506int TextureLayer::tileRowCount( int level ) const
507{
508 return d->m_layerDecorator.tileRowCount( level );
509}
510
511quint64 TextureLayer::volatileCacheLimit() const
512{
513 return d->m_tileLoader.volatileCacheLimit();
514}
515
516int 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
533int 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
550RenderState TextureLayer::renderState() const
551{
552 return d->m_renderState;
553}
554
555QString 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
570void 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"
This file contains the headers for ViewportParams.
KGuiItem reset()
Binds a QML item to a specific geodetic location in screen coordinates.
@ Mercator
Mercator projection.
@ VerticalPerspective
Vertical perspective projection.
@ AzimuthalEquidistant
Azimuthal Equidistant projection.
@ Gnomonic
Gnomonic projection.
@ LambertAzimuthal
Lambert Azimuthal Equal-Area projection.
@ Equirectangular
Flat projection ("plate carree")
@ Spherical
Spherical projection ("Orthographic")
@ Stereographic
Stereographic projection.
QCoreApplication * instance()
bool isNull() const const
void append(QList< T > &&value)
QVariant data(int role) const const
QString & append(QChar ch)
QString & insert(qsizetype position, QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
AscendingOrder
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QThread * currentThread()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.