Marble

TextureLayer.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <[email protected]>
4 // SPDX-FileCopyrightText: 2007 Inge Wallin <[email protected]>
5 // SPDX-FileCopyrightText: 2008, 2009, 2010 Jens-Michael Hoffmann <[email protected]>
6 // SPDX-FileCopyrightText: 2010-2012 Bernhard Beschow <[email protected]>//
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 
36 namespace Marble
37 {
38 
39 const int REPAINT_SCHEDULING_INTERVAL = 1000;
40 
41 class Q_DECL_HIDDEN TextureLayer::Private
42 {
43 public:
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 
63 public:
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 
84 TextureLayer::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 
121 void 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 
132 void 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 
159 void 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 
169 bool TextureLayer::Private::drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 )
170 {
171  return o1->drawOrder() < o2->drawOrder();
172 }
173 
174 void 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 
193 void 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 
210 void TextureLayer::Private::resetGroundOverlaysCache()
211 {
212  m_groundOverlayCache.clear();
213 
214  updateGroundOverlays();
215 
216  m_parent->reset();
217 }
218 
219 void 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 
229 void 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 
238 TextureLayer::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 
255 TextureLayer::~TextureLayer()
256 {
257  qDeleteAll(d->m_customTextures);
258  delete d->m_texmapper;
259  delete d->m_texcolorizer;
260  delete d;
261 }
262 
263 void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument )
264 {
265  if( d->m_texcolorizer ) {
266  d->m_texcolorizer->addSeaDocument( seaDocument );
267  reset();
268  }
269 }
270 
271 void TextureLayer::addLandDocument( const GeoDataDocument *landDocument )
272 {
273  if( d->m_texcolorizer ) {
274  d->m_texcolorizer->addLandDocument( landDocument );
275  reset();
276  }
277 }
278 
279 int TextureLayer::layerCount() const
280 {
281  return d->m_layerDecorator.textureLayersSize();
282 }
283 
284 bool TextureLayer::showSunShading() const
285 {
286  return d->m_layerDecorator.showSunShading();
287 }
288 
289 bool TextureLayer::showCityLights() const
290 {
291  return d->m_layerDecorator.showCityLights();
292 }
293 
294 bool 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).
303  if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
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 
353 QString TextureLayer::runtimeTrace() const
354 {
355  return d->m_runtimeTrace;
356 }
357 
358 void TextureLayer::setShowRelief( bool show )
359 {
360  if ( d->m_texcolorizer ) {
361  d->m_texcolorizer->setShowRelief( show );
362  }
363 }
364 
365 void 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 
380 void TextureLayer::setShowCityLights( bool show )
381 {
382  d->m_layerDecorator.setShowCityLights( show );
383 
384  reset();
385 }
386 
387 void TextureLayer::setShowTileId( bool show )
388 {
389  d->m_layerDecorator.setShowTileId( show );
390 
391  reset();
392 }
393 
394 void 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:
421  case VerticalPerspective:
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 
430 void TextureLayer::setNeedsUpdate()
431 {
432  if ( d->m_texmapper ) {
433  d->m_texmapper->setRepaintNeeded();
434  }
435 
436  emit repaintNeeded();
437 }
438 
439 void TextureLayer::setVolatileCacheLimit( quint64 kilobytes )
440 {
441  d->m_tileLoader.setVolatileCacheLimit( kilobytes );
442 }
443 
444 void TextureLayer::reset()
445 {
446  d->m_tileLoader.clear();
447  setNeedsUpdate();
448 }
449 
450 void 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 
460 void TextureLayer::downloadStackedTile( const TileId &stackedTileId )
461 {
462  d->m_layerDecorator.downloadStackedTile( stackedTileId, DownloadBulk );
463 }
464 
465 void 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 
486 int TextureLayer::tileZoomLevel() const
487 {
488  return d->m_tileZoomLevel;
489 }
490 
491 QSize TextureLayer::tileSize() const
492 {
493  return d->m_layerDecorator.tileSize();
494 }
495 
496 const GeoSceneAbstractTileProjection *TextureLayer::tileProjection() const
497 {
498  return d->m_layerDecorator.tileProjection();
499 }
500 
501 int TextureLayer::tileColumnCount( int level ) const
502 {
503  return d->m_layerDecorator.tileColumnCount( level );
504 }
505 
506 int TextureLayer::tileRowCount( int level ) const
507 {
508  return d->m_layerDecorator.tileRowCount( level );
509 }
510 
511 quint64 TextureLayer::volatileCacheLimit() const
512 {
513  return d->m_tileLoader.volatileCacheLimit();
514 }
515 
516 int 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 
533 int 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 
550 RenderState TextureLayer::renderState() const
551 {
552  return d->m_renderState;
553 }
554 
555 QString 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 
570 void 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"
@ DownloadBrowse
Browsing mode, normal operation of Marble, like a web browser.
Definition: MarbleGlobal.h:155
@ VerticalPerspective
Vertical perspective projection.
Definition: MarbleGlobal.h:49
@ AzimuthalEquidistant
Azimuthal Equidistant projection.
Definition: MarbleGlobal.h:48
@ PopularityIndexRole
The popularity index.
void append(const T &value)
AscendingOrder
int width() const const
@ ObjectPointerRole
The pointer to a specific object.
QVariant data(int role) const const
@ Stereographic
Stereographic projection.
Definition: MarbleGlobal.h:46
@ Spherical
Spherical projection ("Orthographic")
Definition: MarbleGlobal.h:42
bool isNull() const const
QCoreApplication * instance()
QThread * currentThread()
@ LambertAzimuthal
Lambert Azimuthal Equal-Area projection.
Definition: MarbleGlobal.h:47
Binds a QML item to a specific geodetic location in screen coordinates.
QString & remove(int position, int n)
Projection
This enum is used to choose the projection shown in the view.
Definition: MarbleGlobal.h:41
QString & insert(int position, QChar ch)
KGuiItem reset()
@ DownloadBulk
Bulk download, for example "File/Download region".
Definition: MarbleGlobal.h:154
@ Mercator
Mercator projection.
Definition: MarbleGlobal.h:44
@ Gnomonic
Gnomonic projection.
Definition: MarbleGlobal.h:45
QString & append(QChar ch)
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
@ Equirectangular
Flat projection ("plate carree")
Definition: MarbleGlobal.h:43
Q_INVOKABLE void setProjection(uint proj)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Oct 4 2023 04:09:43 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.