8#include "MergedLayerDecorator.h"
10#include "blendings/Blending.h"
11#include "blendings/BlendingFactory.h"
12#include "SunLocator.h"
13#include "MarbleMath.h"
14#include "MarbleDebug.h"
15#include "GeoDataGroundOverlay.h"
16#include "GeoSceneTextureTileDataset.h"
18#include "StackedTile.h"
19#include "TileLoaderHelper.h"
20#include "TextureTile.h"
21#include "TileLoader.h"
22#include "RenderState.h"
24#include "GeoDataCoordinates.h"
28#include <QPainterPath>
32class Q_DECL_HIDDEN MergedLayerDecorator::Private
35 Private( TileLoader *tileLoader,
const SunLocator *sunLocator );
37 static int maxDivisor(
int maximum,
int fullLength );
42 void paintSunShading(
QImage *tileImage,
const TileId &
id )
const;
43 void paintTileId(
QImage *tileImage,
const TileId &
id )
const;
45 void detectMaxTileLevel();
48 TileLoader *
const m_tileLoader;
49 const SunLocator *
const m_sunLocator;
50 BlendingFactory m_blendingFactory;
55 int m_levelZeroColumns;
57 bool m_showSunShading;
58 bool m_showCityLights;
62MergedLayerDecorator::Private::Private( TileLoader *tileLoader,
const SunLocator *sunLocator ) :
63 m_tileLoader( tileLoader ),
64 m_sunLocator( sunLocator ),
65 m_blendingFactory( sunLocator ),
69 m_levelZeroColumns( 0 ),
71 m_showSunShading( false ),
72 m_showCityLights( false ),
77MergedLayerDecorator::MergedLayerDecorator( TileLoader *
const tileLoader,
78 const SunLocator* sunLocator )
79 : d( new Private( tileLoader, sunLocator ) )
83MergedLayerDecorator::~MergedLayerDecorator()
90 if ( textureLayers.
count() > 0 ) {
91 const GeoSceneTileDataset *
const firstTexture = textureLayers.
at( 0 );
92 d->m_levelZeroColumns = firstTexture->levelZeroColumns();
93 d->m_levelZeroRows = firstTexture->levelZeroRows();
94 d->m_blendingFactory.setLevelZeroLayout( d->m_levelZeroColumns, d->m_levelZeroRows );
95 d->m_themeId =
QLatin1String(
"maps/") + firstTexture->sourceDir();
98 d->m_textureLayers = textureLayers;
100 d->detectMaxTileLevel();
105 d->m_groundOverlays = groundOverlays;
109int MergedLayerDecorator::textureLayersSize()
const
111 return d->m_textureLayers.size();
114int MergedLayerDecorator::maximumTileLevel()
const
116 return d->m_maxTileLevel;
119int MergedLayerDecorator::tileColumnCount(
int level )
const
121 Q_ASSERT( !d->m_textureLayers.isEmpty() );
123 const int levelZeroColumns = d->m_textureLayers.at( 0 )->levelZeroColumns();
125 return TileLoaderHelper::levelToColumn( levelZeroColumns, level );
128int MergedLayerDecorator::tileRowCount(
int level )
const
130 Q_ASSERT( !d->m_textureLayers.isEmpty() );
132 const int levelZeroRows = d->m_textureLayers.at( 0 )->levelZeroRows();
134 return TileLoaderHelper::levelToRow( levelZeroRows, level );
139 Q_ASSERT( !d->m_textureLayers.isEmpty() );
141 return d->m_textureLayers.at(0)->tileProjection();
144QSize MergedLayerDecorator::tileSize()
const
146 Q_ASSERT( !d->m_textureLayers.isEmpty() );
148 return d->m_textureLayers.at( 0 )->tileSize();
153 Q_ASSERT( !tiles.isEmpty() );
155 const TileId firstId = tiles.first()->id();
156 const TileId id( 0, firstId.zoomLevel(), firstId.x(), firstId.y() );
163 const bool withConversion = tiles.count() > 1 || m_showSunShading || m_showTileId || !m_groundOverlays.
isEmpty();
169 const Blending *
const blending = tile->blending();
172 mDebug() <<
"blending";
174 if ( resultImage.
isNull() ) {
178 blending->blend( &resultImage, tile.data() );
181 mDebug() <<
"no blending defined => copying top over bottom image";
182 if ( withConversion ) {
185 resultImage = tile->image()->
copy();
190 renderGroundOverlays( &resultImage, tiles );
192 if ( m_showSunShading && !m_showCityLights ) {
193 paintSunShading( &resultImage,
id );
196 if ( m_showTileId ) {
197 paintTileId( &resultImage,
id );
207 const TileId tileId = tiles.first()->id();
209 const GeoDataLatLonBox tileLatLonBox = findRelevantTextureLayers(tileId).first()->tileProjection()->geoCoordinates(tileId);
212 for (
int i = 0; i < m_groundOverlays.size(); ++i ) {
214 const GeoDataGroundOverlay* overlay = m_groundOverlays.at( i );
225 const qreal pixelToLat = tileLatLonBox.
height() / tileImage->
height();
226 const qreal pixelToLon = tileLatLonBox.
width() / tileImage->
width();
228 const qreal latToPixel = overlay->icon().
height() / overlayLatLonBox.
height();
229 const qreal lonToPixel = overlay->icon().
width() / overlayLatLonBox.
width();
231 const qreal global_height = tileImage->
height()
232 * TileLoaderHelper::levelToRow( m_levelZeroRows, tileId.zoomLevel() );
233 const qreal pixel2Rad = M_PI / global_height;
234 const qreal rad2Pixel = global_height / M_PI;
236 qreal latPixelPosition = rad2Pixel/2 *
gdInv(tileLatLonBox.
north());
237 const bool isMercatorTileProjection = (m_textureLayers.at( 0 )->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator);
239 for (
int y = 0; y < tileImage->
height(); ++y ) {
240 QRgb *scanLine = ( QRgb* ) ( tileImage->
scanLine( y ) );
242 const qreal lat = isMercatorTileProjection
243 ? gd(2 * (latPixelPosition - y) * pixel2Rad )
244 : tileLatLonBox.north() - y * pixelToLat;
246 for (
int x = 0; x < tileImage->
width(); ++x, ++scanLine ) {
252 if (overlay->latLonBox().
rotation() != 0) {
255 rotatedCoords = coords.rotateAround(overlayLatLonBox.
center(), -overlay->latLonBox().
rotation());
260 if ( overlay->latLonBox().contains( rotatedCoords ) ) {
265 if ( px >= 0 && px < overlay->icon().width() && py >= 0 && py < overlay->icon().
height() ) {
266 int alpha = qAlpha( overlay->icon().
pixel( px, py ) );
269 QRgb result = ImageF::pixelF( overlay->icon(), px, py );
277 *scanLine = qRgb( ( alpha * qRed(result) + (255 - alpha) * qRed(*scanLine) ) / 255,
278 ( alpha * qGreen(result) + (255 - alpha) * qGreen(*scanLine) ) / 255,
279 ( alpha * qBlue(result) + (255 - alpha) * qBlue(*scanLine) ) / 255 );
289StackedTile *MergedLayerDecorator::loadTile(
const TileId &stackedTileId )
295 for (
const GeoSceneTextureTileDataset *layer: textureLayers ) {
296 const TileId tileId( layer->sourceDir(), stackedTileId.zoomLevel(),
297 stackedTileId.x(), stackedTileId.y() );
299 mDebug() << layer->sourceDir() << tileId << layer->tileSize() << layer->fileFormat();
302 const Blending *blending = d->m_blendingFactory.findBlending( layer->blending() );
303 if ( blending ==
nullptr && !layer->blending().isEmpty() ) {
304 mDebug() <<
"could not find blending" << layer->blending();
307 const GeoSceneTextureTileDataset *
const textureLayer =
static_cast<const GeoSceneTextureTileDataset *
>( layer );
308 const QImage tileImage = d->m_tileLoader->loadTileImage( textureLayer, tileId,
DownloadBrowse );
316 return d->createTile( tiles );
319RenderState MergedLayerDecorator::renderState(
const TileId &stackedTileId )
const
321 QString const nameTemplate =
"Tile %1/%2/%3";
322 RenderState state( nameTemplate.
arg( stackedTileId.zoomLevel() )
323 .
arg( stackedTileId.x() )
324 .
arg( stackedTileId.y() ) );
326 for (
const GeoSceneTextureTileDataset *layer: textureLayers ) {
327 const TileId tileId( layer->sourceDir(), stackedTileId.zoomLevel(),
328 stackedTileId.x(), stackedTileId.y() );
330 switch ( TileLoader::tileStatus( layer, tileId ) ) {
331 case TileLoader::Available:
334 case TileLoader::Expired:
337 case TileLoader::Missing:
342 state.addChild( RenderState( layer->name(), tileStatus ) );
348bool MergedLayerDecorator::hasTextureLayer()
const
350 return !d->m_textureLayers.isEmpty();
355 Q_ASSERT( !tileImage.
isNull() );
357 d->detectMaxTileLevel();
361 for (
int i = 0; i < tiles.
count(); ++ i) {
362 if ( tiles[i]->
id() == tileId ) {
363 const Blending *blending = tiles[i]->blending();
369 return d->createTile( tiles );
372void MergedLayerDecorator::downloadStackedTile(
const TileId &
id,
DownloadUsage usage )
376 for (
const GeoSceneTextureTileDataset *textureLayer: textureLayers ) {
377 if (textureLayer->tileLevels().isEmpty() || textureLayer->tileLevels().contains(
id.zoomLevel())) {
378 if ( TileLoader::tileStatus( textureLayer,
id ) != TileLoader::Available || usage ==
DownloadBrowse ) {
379 d->m_tileLoader->downloadTile( textureLayer,
id, usage );
385void MergedLayerDecorator::setShowSunShading(
bool show )
387 d->m_showSunShading = show;
390bool MergedLayerDecorator::showSunShading()
const
392 return d->m_showSunShading;
395void MergedLayerDecorator::setShowCityLights(
bool show )
397 d->m_showCityLights = show;
400bool MergedLayerDecorator::showCityLights()
const
402 return d->m_showCityLights;
405void MergedLayerDecorator::setShowTileId(
bool visible )
407 d->m_showTileId = visible;
410void MergedLayerDecorator::Private::paintSunShading(
QImage *tileImage,
const TileId &
id )
const
412 if ( tileImage->
depth() != 32 )
417 const qreal global_width = tileImage->
width()
418 * TileLoaderHelper::levelToColumn( m_levelZeroColumns,
id.zoomLevel() );
419 const qreal global_height = tileImage->
height()
420 * TileLoaderHelper::levelToRow( m_levelZeroRows,
id.zoomLevel() );
421 const qreal lon_scale = 2*M_PI / global_width;
422 const qreal lat_scale = -M_PI / global_height;
423 const int tileHeight = tileImage->
height();
424 const int tileWidth = tileImage->
width();
427 const int n = maxDivisor( 30, tileWidth );
428 const int ipRight = n * (int)( tileWidth / n );
430 for (
int cur_y = 0; cur_y < tileHeight; ++cur_y ) {
431 const qreal lat = lat_scale * (
id.y() * tileHeight + cur_y ) - 0.5*M_PI;
432 const qreal a = sin( (lat+DEG2RAD * m_sunLocator->getLat() )/2.0 );
433 const qreal c = cos(lat)*cos( -DEG2RAD * m_sunLocator->getLat() );
435 QRgb* scanline = (QRgb*)tileImage->
scanLine( cur_y );
437 qreal lastShade = -10.0;
441 while ( cur_x < tileWidth ) {
443 const bool interpolate = ( cur_x != 0 && cur_x < ipRight && cur_x + n < tileWidth );
448 const int check = cur_x + n;
449 const qreal checklon = lon_scale * (
id.x() * tileWidth + check );
450 shade = m_sunLocator->shading( checklon, a, c );
454 if ( shade == lastShade && shade == 1.0 ) {
459 if ( shade == lastShade && shade == 0.0 ) {
460 for (
int t = 0; t < n; ++t ) {
461 SunLocator::shadePixel(*scanline, shade);
467 for (
int t = 0; t < n ; ++t ) {
468 const qreal lon = lon_scale * (
id.x() * tileWidth + cur_x );
469 shade = m_sunLocator->shading( lon, a, c );
470 SunLocator::shadePixel(*scanline, shade);
478 if ( cur_x < tileWidth ) {
479 const qreal lon = lon_scale * (
id.x() * tileWidth + cur_x );
480 shade = m_sunLocator->shading( lon, a, c );
481 SunLocator::shadePixel(*scanline, shade);
491void MergedLayerDecorator::Private::paintTileId(
QImage *tileImage,
const TileId &
id )
const
502 if ( ( (qreal)(
id.x())/2 ==
id.x()/2 && (qreal)(
id.y())/2 ==
id.y()/2 )
503 || ( (qreal)(
id.x())/2 !=
id.x()/2 && (qreal)(
id.y())/2 !=
id.y()/2 )
514 int strokeWidth = 10;
515 QPen testPen( foreground );
516 testPen.setWidth( strokeWidth );
519 painter.setPen( testPen );
520 painter.drawRect( strokeWidth / 2, strokeWidth / 2,
521 tileImage->
width() - strokeWidth,
522 tileImage->
height() - strokeWidth );
523 QFont testFont(QStringLiteral(
"Sans Serif"), 12);
525 painter.setFont( testFont );
527 QPen outlinepen( foreground );
528 outlinepen.setWidthF( 6 );
530 painter.setPen( outlinepen );
531 painter.setBrush( background );
535 QPointF baseline1( ( tileImage->
width() - testFm.boundingRect(filename).width() ) / 2,
536 ( tileImage->
height() * 0.25) );
537 outlinepath.
addText( baseline1, testFont,
QString(
"level: %1" ).arg(
id.zoomLevel()) );
539 QPointF baseline2( ( tileImage->
width() - testFm.boundingRect(filename).width() ) / 2,
540 tileImage->
height() * 0.50 );
541 outlinepath.
addText( baseline2, testFont, filename );
543 QPointF baseline3( ( tileImage->
width() - testFm.boundingRect(filename).width() ) / 2,
544 tileImage->
height() * 0.75 );
545 outlinepath.
addText( baseline3, testFont, m_themeId );
547 painter.drawPath( outlinepath );
550 painter.drawPath( outlinepath );
553void MergedLayerDecorator::Private::detectMaxTileLevel()
555 if ( m_textureLayers.isEmpty() ) {
560 m_maxTileLevel = TileLoader::maximumTileLevel( *m_textureLayers.at( 0 ) );
567 for (
const GeoSceneTextureTileDataset *candidate: m_textureLayers ) {
568 Q_ASSERT( candidate );
570 if ( !candidate->hasMaximumTileLevel() ||
571 candidate->maximumTileLevel() >= stackedTileId.zoomLevel() ) {
573 if (candidate->latLonBox().isNull()) {
577 const GeoDataLatLonBox bbox = candidate->tileProjection()->geoCoordinates(stackedTileId);
579 if (candidate->latLonBox().intersects(bbox)) {
580 result.
append( candidate );
591int MergedLayerDecorator::Private::maxDivisor(
int maximum,
int fullLength )
597 int nEvalMin = fullLength;
598 for (
int it = 1; it <= maximum; ++it ) {
602 int nEval = fullLength / it + fullLength % it;
603 if ( nEval < nEvalMin ) {
A 3d point representation.
static qreal normalizeLon(qreal lon, GeoDataCoordinates::Unit=GeoDataCoordinates::Radian)
normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian).
bool isGloballyVisible() const
Return whether this feature is visible or not in the context of its parenting.
A class that defines a 2D bounding box for geographic data.
qreal north(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the northern boundary of the bounding box.
qreal height(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the height of the latitude interval.
GeoDataLatLonBox toCircumscribedRectangle() const
qreal width(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the width of the longitude interval.
qreal west(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the western boundary of the bounding box.
qreal south(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the southern boundary of the bounding box.
qreal rotation(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the rotation of the bounding box.
virtual GeoDataCoordinates center() const
returns the center of this box
A base class for projections between tile indizes and geo coordinates in Marble.
A single tile that consists of a stack of Tile layers.
QVector< QSharedPointer< TextureTile > > tiles() const
Returns the stack of Tiles.
A class that resembles an image tile (extends Tile).
KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount=0.0)
Binds a QML item to a specific geodetic location in screen coordinates.
qreal gdInv(qreal x)
This method is a fast Mac Laurin power series approximation of the.
DownloadUsage
This enum is used to describe the type of download.
@ DownloadBrowse
Browsing mode, normal operation of Marble, like a web browser.
@ WaitingForData
Rendering is based on no or partial data, more data was requested (e.g. pending network queries)
@ Complete
All data is there and up to date.
@ WaitingForUpdate
Rendering is based on complete, but outdated data, data update was requested.
void setNamedColor(QLatin1StringView name)
QImage copy(const QRect &rectangle) const const
bool isNull() const const
QRgb pixel(const QPoint &position) const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
void addText(const QPointF &point, const QFont &font, const QString &text)
QString arg(Args &&... args) const const