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 <QList>
11#include <QSortFilterProxyModel>
12#include <QTimer>
13#include <qmath.h>
14
15#include "EquirectScanlineTextureMapper.h"
16#include "GenericScanlineTextureMapper.h"
17#include "GeoDataGroundOverlay.h"
18#include "GeoPainter.h"
19#include "GeoSceneGroup.h"
20#include "GeoSceneTextureTileDataset.h"
21#include "GeoSceneTypes.h"
22#include "MarbleDebug.h"
23#include "MarbleDirs.h"
24#include "MarblePlacemarkModel.h"
25#include "MercatorScanlineTextureMapper.h"
26#include "MergedLayerDecorator.h"
27#include "SphericalScanlineTextureMapper.h"
28#include "StackedTile.h"
29#include "StackedTileLoader.h"
30#include "SunLocator.h"
31#include "TextureColorizer.h"
32#include "TileLoader.h"
33#include "TileScalingTextureMapper.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)), m_parent, SLOT(addGroundOverlays(QModelIndex, int, int)));
107
108 connect(&m_groundOverlayModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), m_parent, SLOT(removeGroundOverlays(QModelIndex, int, int)));
109
110 connect(&m_groundOverlayModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), m_parent, SLOT(resetGroundOverlaysCache()));
111
112 connect(&m_groundOverlayModel, SIGNAL(modelReset()), m_parent, SLOT(resetGroundOverlaysCache()));
113
114 updateGroundOverlays();
115}
116
117void TextureLayer::Private::requestDelayedRepaint()
118{
119 if (m_texmapper) {
120 m_texmapper->setRepaintNeeded();
121 }
122
123 if (!m_repaintTimer.isActive()) {
124 m_repaintTimer.start();
125 }
126}
127
128void TextureLayer::Private::updateTextureLayers()
129{
131
132 for (const GeoSceneTextureTileDataset *candidate : std::as_const(m_textures)) {
133 bool enabled = true;
134 if (m_textureLayerSettings) {
135 const bool propertyExists = m_textureLayerSettings->propertyValue(candidate->name(), enabled);
136 enabled |= !propertyExists; // if property doesn't exist, enable texture nevertheless
137 }
138 if (enabled) {
139 result.append(candidate);
140 mDebug() << "enabling texture" << candidate->name();
141 } else {
142 mDebug() << "disabling texture" << candidate->name();
143 }
144 }
145
146 updateGroundOverlays();
147
148 m_layerDecorator.setTextureLayers(result);
149 m_tileLoader.clear();
150
151 m_tileZoomLevel = -1;
152 m_parent->setNeedsUpdate();
153}
154
155void TextureLayer::Private::updateTile(const TileId &tileId, const QImage &tileImage)
156{
157 if (tileImage.isNull())
158 return; // keep tiles in cache to improve performance
159
160 m_tileLoader.updateTile(tileId, tileImage);
161
162 requestDelayedRepaint();
163}
164
165bool TextureLayer::Private::drawOrderLessThan(const GeoDataGroundOverlay *o1, const GeoDataGroundOverlay *o2)
166{
167 return o1->drawOrder() < o2->drawOrder();
168}
169
170void TextureLayer::Private::addGroundOverlays(const QModelIndex &parent, int first, int last)
171{
172 for (int i = first; i <= last; ++i) {
173 QModelIndex index = m_groundOverlayModel.index(i, 0, parent);
174 const GeoDataGroundOverlay *overlay =
175 static_cast<GeoDataGroundOverlay *>(qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole)));
176
177 if (overlay->icon().isNull()) {
178 continue;
179 }
180
181 int pos = std::lower_bound(m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan) - m_groundOverlayCache.begin();
182 m_groundOverlayCache.insert(pos, overlay);
183 }
184
185 updateGroundOverlays();
186
187 m_parent->reset();
188}
189
190void TextureLayer::Private::removeGroundOverlays(const QModelIndex &parent, int first, int last)
191{
192 for (int i = first; i <= last; ++i) {
193 QModelIndex index = m_groundOverlayModel.index(i, 0, parent);
194 const GeoDataGroundOverlay *overlay =
195 static_cast<GeoDataGroundOverlay *>(qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole)));
196
197 int pos = std::lower_bound(m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan) - m_groundOverlayCache.begin();
198 if (pos >= 0 && pos < m_groundOverlayCache.size()) {
199 m_groundOverlayCache.removeAt(pos);
200 }
201 }
202
203 updateGroundOverlays();
204
205 m_parent->reset();
206}
207
208void TextureLayer::Private::resetGroundOverlaysCache()
209{
210 m_groundOverlayCache.clear();
211
212 updateGroundOverlays();
213
214 m_parent->reset();
215}
216
217void TextureLayer::Private::updateGroundOverlays()
218{
219 if (!m_texcolorizer) {
220 m_layerDecorator.updateGroundOverlays(m_groundOverlayCache);
221 } else {
222 m_layerDecorator.updateGroundOverlays(QList<const GeoDataGroundOverlay *>());
223 }
224}
225
226void TextureLayer::Private::addCustomTextures()
227{
228 m_textures.reserve(m_textures.size() + m_customTextures.size());
229 for (GeoSceneTextureTileDataset *t : std::as_const(m_customTextures)) {
230 m_textures.append(t);
231 }
232}
233
234TextureLayer::TextureLayer(HttpDownloadManager *downloadManager,
235 PluginManager *pluginManager,
236 const SunLocator *sunLocator,
237 QAbstractItemModel *groundOverlayModel)
238 : TileLayer()
239 , d(new Private(downloadManager, pluginManager, sunLocator, groundOverlayModel, this))
240{
241 connect(&d->m_loader, SIGNAL(tileCompleted(TileId, QImage)), this, SLOT(updateTile(TileId, QImage)));
242
243 // Repaint timer
244 d->m_repaintTimer.setSingleShot(true);
245 d->m_repaintTimer.setInterval(REPAINT_SCHEDULING_INTERVAL);
246 connect(&d->m_repaintTimer, SIGNAL(timeout()), this, SIGNAL(repaintNeeded()));
247}
248
249TextureLayer::~TextureLayer()
250{
251 qDeleteAll(d->m_customTextures);
252 delete d->m_texmapper;
253 delete d->m_texcolorizer;
254 delete d;
255}
256
257void TextureLayer::addSeaDocument(const GeoDataDocument *seaDocument)
258{
259 if (d->m_texcolorizer) {
260 d->m_texcolorizer->addSeaDocument(seaDocument);
261 reset();
262 }
263}
264
265void TextureLayer::addLandDocument(const GeoDataDocument *landDocument)
266{
267 if (d->m_texcolorizer) {
268 d->m_texcolorizer->addLandDocument(landDocument);
269 reset();
270 }
271}
272
273int TextureLayer::layerCount() const
274{
275 return d->m_layerDecorator.textureLayersSize();
276}
277
278bool TextureLayer::showSunShading() const
279{
280 return d->m_layerDecorator.showSunShading();
281}
282
283bool TextureLayer::showCityLights() const
284{
285 return d->m_layerDecorator.showCityLights();
286}
287
288bool TextureLayer::render(GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer)
289{
290 Q_UNUSED(renderPos);
291 Q_UNUSED(layer);
292 d->m_runtimeTrace = QStringLiteral("Texture Cache: %1 ").arg(d->m_tileLoader.tileCount());
293 d->m_renderState = RenderState(QStringLiteral("Texture Tiles"));
294
295 // Timers cannot be stopped from another thread (e.g. from QtQuick RenderThread).
297 // Stop repaint timer if it is already running
298 if (d->m_repaintTimer.isActive()) {
299 d->m_repaintTimer.stop();
300 }
301 }
302
303 if (d->m_textures.isEmpty())
304 return false;
305
306 if (d->m_layerDecorator.textureLayersSize() == 0)
307 return false;
308
309 if (!d->m_texmapper)
310 return false;
311
312 if (d->m_centerCoordinates.longitude() != viewport->centerLongitude() || d->m_centerCoordinates.latitude() != viewport->centerLatitude()) {
313 d->m_centerCoordinates.setLongitude(viewport->centerLongitude());
314 d->m_centerCoordinates.setLatitude(viewport->centerLatitude());
315 d->m_texmapper->setRepaintNeeded();
316 }
317
318 // choose the smaller dimension for selecting the tile level, leading to higher-resolution results
319 const int levelZeroWidth = d->m_layerDecorator.tileSize().width() * d->m_layerDecorator.tileColumnCount(0);
320 const int levelZeroHight = d->m_layerDecorator.tileSize().height() * d->m_layerDecorator.tileRowCount(0);
321 const int levelZeroMinDimension = qMin(levelZeroWidth, levelZeroHight);
322
323 // limit to 1 as dirty fix for invalid entry linearLevel
324 const qreal linearLevel = qMax<qreal>(1.0, viewport->radius() * 4.0 / levelZeroMinDimension);
325
326 // As our tile resolution doubles with each level we calculate
327 // the tile level from tilesize and the globe radius via log(2)
328 const qreal tileLevelF = qLn(linearLevel) / qLn(2.0) * 1.00001; // snap to the sharper tile level a tiny bit earlier
329 // to work around rounding errors when the radius
330 // roughly equals the global texture width
331
332 const int tileLevel = qMin<int>(d->m_layerDecorator.maximumTileLevel(), tileLevelF);
333
334 if (tileLevel != d->m_tileZoomLevel) {
335 d->m_tileZoomLevel = tileLevel;
336 Q_EMIT tileLevelChanged(d->m_tileZoomLevel);
337 }
338
339 const QRect dirtyRect = QRect(QPoint(0, 0), viewport->size());
340 d->m_texmapper->mapTexture(painter, viewport, d->m_tileZoomLevel, dirtyRect, d->m_texcolorizer);
341 d->m_renderState.addChild(d->m_tileLoader.renderState());
342 return true;
343}
344
345QString TextureLayer::runtimeTrace() const
346{
347 return d->m_runtimeTrace;
348}
349
350void TextureLayer::setShowRelief(bool show)
351{
352 if (d->m_texcolorizer) {
353 d->m_texcolorizer->setShowRelief(show);
354 }
355}
356
357void TextureLayer::setShowSunShading(bool show)
358{
359 disconnect(d->m_sunLocator, SIGNAL(positionChanged(qreal, qreal)), this, SLOT(reset()));
360
361 if (show) {
362 connect(d->m_sunLocator, SIGNAL(positionChanged(qreal, qreal)), this, SLOT(reset()));
363 }
364
365 d->m_layerDecorator.setShowSunShading(show);
366
367 reset();
368}
369
370void TextureLayer::setShowCityLights(bool show)
371{
372 d->m_layerDecorator.setShowCityLights(show);
373
374 reset();
375}
376
377void TextureLayer::setShowTileId(bool show)
378{
379 d->m_layerDecorator.setShowTileId(show);
380
381 reset();
382}
383
384void TextureLayer::setProjection(Projection projection)
385{
386 if (d->m_textures.isEmpty()) {
387 return;
388 }
389
390 // FIXME: replace this with an approach based on the factory method pattern.
391 delete d->m_texmapper;
392
393 switch (projection) {
394 case Spherical:
395 d->m_texmapper = new SphericalScanlineTextureMapper(&d->m_tileLoader);
396 break;
397 case Equirectangular:
398 d->m_texmapper = new EquirectScanlineTextureMapper(&d->m_tileLoader);
399 break;
400 case Mercator:
401 if (d->m_textures.at(0)->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator) {
402 d->m_texmapper = new TileScalingTextureMapper(&d->m_tileLoader);
403 } else {
404 d->m_texmapper = new MercatorScanlineTextureMapper(&d->m_tileLoader);
405 }
406 break;
407 case Gnomonic:
408 case Stereographic:
409 case LambertAzimuthal:
412 d->m_texmapper = new GenericScanlineTextureMapper(&d->m_tileLoader);
413 break;
414 default:
415 d->m_texmapper = nullptr;
416 }
417 Q_ASSERT(d->m_texmapper);
418}
419
420void TextureLayer::setNeedsUpdate()
421{
422 if (d->m_texmapper) {
423 d->m_texmapper->setRepaintNeeded();
424 }
425
426 Q_EMIT repaintNeeded();
427}
428
429void TextureLayer::setVolatileCacheLimit(quint64 kilobytes)
430{
431 d->m_tileLoader.setVolatileCacheLimit(kilobytes);
432}
433
434void TextureLayer::reset()
435{
436 d->m_tileLoader.clear();
437 setNeedsUpdate();
438}
439
440void TextureLayer::reload()
441{
442 for (const TileId &id : d->m_tileLoader.visibleTiles()) {
443 // it's debatable here, whether DownloadBulk or DownloadBrowse should be used
444 // but since "reload" or "refresh" seems to be a common action of a browser and it
445 // allows for more connections (in our model), use "DownloadBrowse"
446 d->m_layerDecorator.downloadStackedTile(id, DownloadBrowse);
447 }
448}
449
450void TextureLayer::downloadStackedTile(const TileId &stackedTileId)
451{
452 d->m_layerDecorator.downloadStackedTile(stackedTileId, DownloadBulk);
453}
454
455void TextureLayer::setMapTheme(const QList<const GeoSceneTextureTileDataset *> &textures,
456 const GeoSceneGroup *textureLayerSettings,
457 const QString &seaFile,
458 const QString &landFile)
459{
460 delete d->m_texcolorizer;
461 d->m_texcolorizer = nullptr;
462
463 if (QFileInfo(seaFile).isReadable() || QFileInfo(landFile).isReadable()) {
464 d->m_texcolorizer = new TextureColorizer(seaFile, landFile);
465 }
466
467 d->m_textures = textures;
468 d->addCustomTextures();
469 d->m_textureLayerSettings = textureLayerSettings;
470
471 if (d->m_textureLayerSettings) {
472 connect(d->m_textureLayerSettings, SIGNAL(valueChanged(QString, bool)), this, SLOT(updateTextureLayers()));
473 }
474
475 d->updateTextureLayers();
476}
477
478int TextureLayer::tileZoomLevel() const
479{
480 return d->m_tileZoomLevel;
481}
482
483QSize TextureLayer::tileSize() const
484{
485 return d->m_layerDecorator.tileSize();
486}
487
488const GeoSceneAbstractTileProjection *TextureLayer::tileProjection() const
489{
490 return d->m_layerDecorator.tileProjection();
491}
492
493int TextureLayer::tileColumnCount(int level) const
494{
495 return d->m_layerDecorator.tileColumnCount(level);
496}
497
498int TextureLayer::tileRowCount(int level) const
499{
500 return d->m_layerDecorator.tileRowCount(level);
501}
502
503quint64 TextureLayer::volatileCacheLimit() const
504{
505 return d->m_tileLoader.volatileCacheLimit();
506}
507
508int TextureLayer::preferredRadiusCeil(int radius) const
509{
510 if (!d->m_layerDecorator.hasTextureLayer()) {
511 return radius;
512 }
513 const int tileWidth = d->m_layerDecorator.tileSize().width();
514 const int levelZeroColumns = d->m_layerDecorator.tileColumnCount(0);
515 const qreal linearLevel = 4.0 * (qreal)(radius) / (qreal)(tileWidth * levelZeroColumns);
516 const qreal tileLevelF = qLn(linearLevel) / qLn(2.0);
517 const int tileLevel = qCeil(tileLevelF);
518
519 if (tileLevel < 0)
520 return (tileWidth * levelZeroColumns / 4) >> (-tileLevel);
521
522 return (tileWidth * levelZeroColumns / 4) << tileLevel;
523}
524
525int TextureLayer::preferredRadiusFloor(int radius) const
526{
527 if (!d->m_layerDecorator.hasTextureLayer()) {
528 return radius;
529 }
530 const int tileWidth = d->m_layerDecorator.tileSize().width();
531 const int levelZeroColumns = d->m_layerDecorator.tileColumnCount(0);
532 const qreal linearLevel = 4.0 * (qreal)(radius) / (qreal)(tileWidth * levelZeroColumns);
533 const qreal tileLevelF = qLn(linearLevel) / qLn(2.0);
534 const int tileLevel = qFloor(tileLevelF);
535
536 if (tileLevel < 0)
537 return (tileWidth * levelZeroColumns / 4) >> (-tileLevel);
538
539 return (tileWidth * levelZeroColumns / 4) << tileLevel;
540}
541
542RenderState TextureLayer::renderState() const
543{
544 return d->m_renderState;
545}
546
547QString TextureLayer::addTextureLayer(GeoSceneTextureTileDataset *texture)
548{
549 if (!texture)
550 return {}; // Not a sane call
551
552 QString sourceDir = texture->sourceDir();
553 if (!d->m_customTextures.contains(sourceDir)) { // Add if not present. For update, remove the old texture first.
554 d->m_customTextures.insert(sourceDir, texture);
555 d->m_textures.append(texture);
556 d->updateTextureLayers();
557 }
558 return sourceDir;
559}
560
561void TextureLayer::removeTextureLayer(const QString &key)
562{
563 if (d->m_customTextures.contains(key)) {
564 GeoSceneTextureTileDataset *texture = d->m_customTextures.value(key);
565 d->m_customTextures.remove(key);
566 d->m_textures.remove(d->m_textures.indexOf(texture));
567 delete texture;
568 d->updateTextureLayers();
569 }
570}
571
572}
573
574#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
int width() const const
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 Fri Nov 8 2024 12:02:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.