File indexing completed on 2025-01-05 03:59:18
0001 // SPDX-FileCopyrightText: 2008 David Roberts <dvdr18@gmail.com> 0002 // SPDX-FileCopyrightText: 2009 Jens-Michael Hoffmann <jensmh@gmx.de> 0003 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de> 0004 // 0005 // SPDX-License-Identifier: LGPL-2.1-or-later 0006 0007 0008 #include "MergedLayerDecorator.h" 0009 0010 #include <QPointer> 0011 #include <QPainter> 0012 #include <QPainterPath> 0013 0014 #include "Blending.h" 0015 #include "BlendingFactory.h" 0016 #include "SunLocator.h" 0017 #include "MarbleMath.h" 0018 #include "GeoDataGroundOverlay.h" 0019 #include "GeoSceneTextureTileDataset.h" 0020 #include "ImageF.h" 0021 #include "StackedTile.h" 0022 #include "TileLoaderHelper.h" 0023 #include "TextureTile.h" 0024 #include "TileLoader.h" 0025 #include "RenderState.h" 0026 #include "GeoDataCoordinates.h" 0027 0028 #include "digikam_debug.h" 0029 0030 using namespace Marble; 0031 0032 class Q_DECL_HIDDEN MergedLayerDecorator::Private 0033 { 0034 public: 0035 Private( TileLoader *tileLoader, const SunLocator *sunLocator ); 0036 0037 static int maxDivisor( int maximum, int fullLength ); 0038 0039 StackedTile *createTile( const QVector<QSharedPointer<TextureTile> > &tiles ) const; 0040 0041 void renderGroundOverlays( QImage *tileImage, const QVector<QSharedPointer<TextureTile> > &tiles ) const; 0042 void paintSunShading( QImage *tileImage, const TileId &id ) const; 0043 void paintTileId( QImage *tileImage, const TileId &id ) const; 0044 0045 void detectMaxTileLevel(); 0046 QVector<const GeoSceneTextureTileDataset *> findRelevantTextureLayers( const TileId &stackedTileId ) const; 0047 0048 TileLoader *const m_tileLoader; 0049 const SunLocator *const m_sunLocator; 0050 BlendingFactory m_blendingFactory; 0051 QVector<const GeoSceneTextureTileDataset *> m_textureLayers; 0052 QList<const GeoDataGroundOverlay *> m_groundOverlays; 0053 int m_maxTileLevel; 0054 QString m_themeId; 0055 int m_levelZeroColumns; 0056 int m_levelZeroRows; 0057 bool m_showSunShading; 0058 bool m_showCityLights; 0059 bool m_showTileId; 0060 }; 0061 0062 MergedLayerDecorator::Private::Private( TileLoader *tileLoader, const SunLocator *sunLocator ) : 0063 m_tileLoader( tileLoader ), 0064 m_sunLocator( sunLocator ), 0065 m_blendingFactory( sunLocator ), 0066 m_textureLayers(), 0067 m_maxTileLevel( 0 ), 0068 m_themeId(), 0069 m_levelZeroColumns( 0 ), 0070 m_levelZeroRows( 0 ), 0071 m_showSunShading( false ), 0072 m_showCityLights( false ), 0073 m_showTileId( false ) 0074 { 0075 } 0076 0077 MergedLayerDecorator::MergedLayerDecorator( TileLoader * const tileLoader, 0078 const SunLocator* sunLocator ) 0079 : d( new Private( tileLoader, sunLocator ) ) 0080 { 0081 } 0082 0083 MergedLayerDecorator::~MergedLayerDecorator() 0084 { 0085 delete d; 0086 } 0087 0088 void MergedLayerDecorator::setTextureLayers( const QVector<const GeoSceneTextureTileDataset *> &textureLayers ) 0089 { 0090 if ( textureLayers.count() > 0 ) { 0091 const GeoSceneTileDataset *const firstTexture = textureLayers.at( 0 ); 0092 d->m_levelZeroColumns = firstTexture->levelZeroColumns(); 0093 d->m_levelZeroRows = firstTexture->levelZeroRows(); 0094 d->m_blendingFactory.setLevelZeroLayout( d->m_levelZeroColumns, d->m_levelZeroRows ); 0095 d->m_themeId = QLatin1String("maps/") + firstTexture->sourceDir(); 0096 } 0097 0098 d->m_textureLayers = textureLayers; 0099 0100 d->detectMaxTileLevel(); 0101 } 0102 0103 void MergedLayerDecorator::updateGroundOverlays(const QList<const GeoDataGroundOverlay *> &groundOverlays ) 0104 { 0105 d->m_groundOverlays = groundOverlays; 0106 } 0107 0108 0109 int MergedLayerDecorator::textureLayersSize() const 0110 { 0111 return d->m_textureLayers.size(); 0112 } 0113 0114 int MergedLayerDecorator::maximumTileLevel() const 0115 { 0116 return d->m_maxTileLevel; 0117 } 0118 0119 int MergedLayerDecorator::tileColumnCount( int level ) const 0120 { 0121 Q_ASSERT( !d->m_textureLayers.isEmpty() ); 0122 0123 const int levelZeroColumns = d->m_textureLayers.at( 0 )->levelZeroColumns(); 0124 0125 return TileLoaderHelper::levelToColumn( levelZeroColumns, level ); 0126 } 0127 0128 int MergedLayerDecorator::tileRowCount( int level ) const 0129 { 0130 Q_ASSERT( !d->m_textureLayers.isEmpty() ); 0131 0132 const int levelZeroRows = d->m_textureLayers.at( 0 )->levelZeroRows(); 0133 0134 return TileLoaderHelper::levelToRow( levelZeroRows, level ); 0135 } 0136 0137 const GeoSceneAbstractTileProjection *MergedLayerDecorator::tileProjection() const 0138 { 0139 Q_ASSERT( !d->m_textureLayers.isEmpty() ); 0140 0141 return d->m_textureLayers.at(0)->tileProjection(); 0142 } 0143 0144 QSize MergedLayerDecorator::tileSize() const 0145 { 0146 Q_ASSERT( !d->m_textureLayers.isEmpty() ); 0147 0148 return d->m_textureLayers.at( 0 )->tileSize(); 0149 } 0150 0151 StackedTile *MergedLayerDecorator::Private::createTile( const QVector<QSharedPointer<TextureTile> > &tiles ) const 0152 { 0153 Q_ASSERT( !tiles.isEmpty() ); 0154 0155 const TileId firstId = tiles.first()->id(); 0156 const TileId id( 0, firstId.zoomLevel(), firstId.x(), firstId.y() ); 0157 0158 // Image for blending all the texture tiles on it 0159 QImage resultImage; 0160 0161 // if there are more than one active texture layers, we have to convert the 0162 // result tile into QImage::Format_ARGB32_Premultiplied to make blending possible 0163 const bool withConversion = tiles.count() > 1 || m_showSunShading || m_showTileId || !m_groundOverlays.isEmpty(); 0164 for ( const QSharedPointer<TextureTile> &tile: tiles ) { 0165 0166 // Image blending. If there are several images in the same tile (like clouds 0167 // or hillshading images over the map) blend them all into only one image 0168 0169 const Blending *const blending = tile->blending(); 0170 if ( blending ) { 0171 0172 qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO << "blending"; 0173 0174 if ( resultImage.isNull() ) { 0175 resultImage = QImage( tile->image()->size(), QImage::Format_ARGB32_Premultiplied ); 0176 } 0177 0178 blending->blend( &resultImage, tile.data() ); 0179 } 0180 else { 0181 qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO << "no blending defined => copying top over bottom image"; 0182 if ( withConversion ) { 0183 resultImage = tile->image()->convertToFormat( QImage::Format_ARGB32_Premultiplied ); 0184 } else { 0185 resultImage = tile->image()->copy(); 0186 } 0187 } 0188 } 0189 0190 renderGroundOverlays( &resultImage, tiles ); 0191 0192 if ( m_showSunShading && !m_showCityLights ) { 0193 paintSunShading( &resultImage, id ); 0194 } 0195 0196 if ( m_showTileId ) { 0197 paintTileId( &resultImage, id ); 0198 } 0199 0200 return new StackedTile( id, resultImage, tiles ); 0201 } 0202 0203 void MergedLayerDecorator::Private::renderGroundOverlays( QImage *tileImage, const QVector<QSharedPointer<TextureTile> > &tiles ) const 0204 { 0205 0206 /* All tiles are covering the same area. Pick one. */ 0207 const TileId tileId = tiles.first()->id(); 0208 0209 const GeoDataLatLonBox tileLatLonBox = findRelevantTextureLayers(tileId).first()->tileProjection()->geoCoordinates(tileId); 0210 0211 /* Map the ground overlay to the image. */ 0212 for ( int i = 0; i < m_groundOverlays.size(); ++i ) { 0213 0214 const GeoDataGroundOverlay* overlay = m_groundOverlays.at( i ); 0215 if ( !overlay->isGloballyVisible() ) { 0216 continue; 0217 } 0218 0219 const GeoDataLatLonBox overlayLatLonBox = overlay->latLonBox(); 0220 0221 if ( !tileLatLonBox.intersects( overlayLatLonBox.toCircumscribedRectangle() ) ) { 0222 continue; 0223 } 0224 0225 const qreal pixelToLat = tileLatLonBox.height() / tileImage->height(); 0226 const qreal pixelToLon = tileLatLonBox.width() / tileImage->width(); 0227 0228 const qreal latToPixel = overlay->icon().height() / overlayLatLonBox.height(); 0229 const qreal lonToPixel = overlay->icon().width() / overlayLatLonBox.width(); 0230 0231 const qreal global_height = tileImage->height() 0232 * TileLoaderHelper::levelToRow( m_levelZeroRows, tileId.zoomLevel() ); 0233 const qreal pixel2Rad = M_PI / global_height; 0234 const qreal rad2Pixel = global_height / M_PI; 0235 0236 qreal latPixelPosition = rad2Pixel/2 * gdInv(tileLatLonBox.north()); 0237 const bool isMercatorTileProjection = (m_textureLayers.at( 0 )->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator); 0238 0239 for ( int y = 0; y < tileImage->height(); ++y ) { 0240 QRgb *scanLine = ( QRgb* ) ( tileImage->scanLine( y ) ); 0241 0242 const qreal lat = isMercatorTileProjection 0243 ? gd(2 * (latPixelPosition - y) * pixel2Rad ) 0244 : tileLatLonBox.north() - y * pixelToLat; 0245 0246 for ( int x = 0; x < tileImage->width(); ++x, ++scanLine ) { 0247 qreal lon = GeoDataCoordinates::normalizeLon( tileLatLonBox.west() + x * pixelToLon ); 0248 0249 GeoDataCoordinates coords(lon, lat); 0250 GeoDataCoordinates rotatedCoords(coords); 0251 0252 if (overlay->latLonBox().rotation() != 0) { 0253 // Possible TODO: Make this faster by creating the axisMatrix beforehand 0254 // and just call Quaternion::rotateAroundAxis(const matrix &m) here. 0255 rotatedCoords = coords.rotateAround(overlayLatLonBox.center(), -overlay->latLonBox().rotation()); 0256 } 0257 0258 // TODO: The rotated latLonBox is bigger. We need to take this into account. 0259 // (Currently the GroundOverlay sometimes gets clipped because of that) 0260 if ( overlay->latLonBox().contains( rotatedCoords ) ) { 0261 0262 qreal px = GeoDataLatLonBox::width( rotatedCoords.longitude(), overlayLatLonBox.west() ) * lonToPixel; 0263 qreal py = (qreal)( overlay->icon().height() ) - ( GeoDataLatLonBox::height( rotatedCoords.latitude(), overlayLatLonBox.south() ) * latToPixel ) - 1; 0264 0265 if ( px >= 0 && px < overlay->icon().width() && py >= 0 && py < overlay->icon().height() ) { 0266 int alpha = qAlpha( overlay->icon().pixel( px, py ) ); 0267 if ( alpha != 0 ) 0268 { 0269 QRgb result = ImageF::pixelF( overlay->icon(), px, py ); 0270 0271 if (alpha == 255) 0272 { 0273 *scanLine = result; 0274 } 0275 else 0276 { 0277 *scanLine = qRgb( ( alpha * qRed(result) + (255 - alpha) * qRed(*scanLine) ) / 255, 0278 ( alpha * qGreen(result) + (255 - alpha) * qGreen(*scanLine) ) / 255, 0279 ( alpha * qBlue(result) + (255 - alpha) * qBlue(*scanLine) ) / 255 ); 0280 } 0281 } 0282 } 0283 } 0284 } 0285 } 0286 } 0287 } 0288 0289 StackedTile *MergedLayerDecorator::loadTile( const TileId &stackedTileId ) 0290 { 0291 const QVector<const GeoSceneTextureTileDataset *> textureLayers = d->findRelevantTextureLayers( stackedTileId ); 0292 QVector<QSharedPointer<TextureTile> > tiles; 0293 tiles.reserve(textureLayers.size()); 0294 0295 for ( const GeoSceneTextureTileDataset *layer: textureLayers ) { 0296 const TileId tileId( layer->sourceDir(), stackedTileId.zoomLevel(), 0297 stackedTileId.x(), stackedTileId.y() ); 0298 0299 qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO << layer->sourceDir() << tileId << layer->tileSize() << layer->fileFormat(); 0300 0301 // Blending (how to merge the images into an only image) 0302 const Blending *blending = d->m_blendingFactory.findBlending( layer->blending() ); 0303 if ( blending == nullptr && !layer->blending().isEmpty() ) { 0304 qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO << "could not find blending" << layer->blending(); 0305 } 0306 0307 const GeoSceneTextureTileDataset *const textureLayer = static_cast<const GeoSceneTextureTileDataset *>( layer ); 0308 const QImage tileImage = d->m_tileLoader->loadTileImage( textureLayer, tileId, DownloadBrowse ); 0309 0310 QSharedPointer<TextureTile> tile( new TextureTile( tileId, tileImage, blending ) ); 0311 tiles.append( tile ); 0312 } 0313 0314 Q_ASSERT( !tiles.isEmpty() ); 0315 0316 return d->createTile( tiles ); 0317 } 0318 0319 RenderState MergedLayerDecorator::renderState( const TileId &stackedTileId ) const 0320 { 0321 QString const nameTemplate = QString::fromUtf8("Tile %1/%2/%3"); 0322 RenderState state( nameTemplate.arg( stackedTileId.zoomLevel() ) 0323 .arg( stackedTileId.x() ) 0324 .arg( stackedTileId.y() ) ); 0325 const QVector<const GeoSceneTextureTileDataset *> textureLayers = d->findRelevantTextureLayers( stackedTileId ); 0326 for ( const GeoSceneTextureTileDataset *layer: textureLayers ) { 0327 const TileId tileId( layer->sourceDir(), stackedTileId.zoomLevel(), 0328 stackedTileId.x(), stackedTileId.y() ); 0329 RenderStatus tileStatus = Complete; 0330 switch ( TileLoader::tileStatus( layer, tileId ) ) { 0331 case TileLoader::Available: 0332 tileStatus = Complete; 0333 break; 0334 case TileLoader::Expired: 0335 tileStatus = WaitingForUpdate; 0336 break; 0337 case TileLoader::Missing: 0338 tileStatus = WaitingForData; 0339 break; 0340 } 0341 0342 state.addChild( RenderState( layer->name(), tileStatus ) ); 0343 } 0344 0345 return state; 0346 } 0347 0348 bool MergedLayerDecorator::hasTextureLayer() const 0349 { 0350 return !d->m_textureLayers.isEmpty(); 0351 } 0352 0353 StackedTile *MergedLayerDecorator::updateTile( const StackedTile &stackedTile, const TileId &tileId, const QImage &tileImage ) 0354 { 0355 Q_ASSERT( !tileImage.isNull() ); 0356 0357 d->detectMaxTileLevel(); 0358 0359 QVector<QSharedPointer<TextureTile> > tiles = stackedTile.tiles(); 0360 0361 for ( int i = 0; i < tiles.count(); ++ i) { 0362 if ( tiles[i]->id() == tileId ) { 0363 const Blending *blending = tiles[i]->blending(); 0364 0365 tiles[i] = QSharedPointer<TextureTile>( new TextureTile( tileId, tileImage, blending ) ); 0366 } 0367 } 0368 0369 return d->createTile( tiles ); 0370 } 0371 0372 void MergedLayerDecorator::downloadStackedTile( const TileId &id, DownloadUsage usage ) 0373 { 0374 const QVector<const GeoSceneTextureTileDataset *> textureLayers = d->findRelevantTextureLayers( id ); 0375 0376 for ( const GeoSceneTextureTileDataset *textureLayer: textureLayers ) { 0377 if (textureLayer->tileLevels().isEmpty() || textureLayer->tileLevels().contains(id.zoomLevel())) { 0378 if ( TileLoader::tileStatus( textureLayer, id ) != TileLoader::Available || usage == DownloadBrowse ) { 0379 d->m_tileLoader->downloadTile( textureLayer, id, usage ); 0380 } 0381 } 0382 } 0383 } 0384 0385 void MergedLayerDecorator::setShowSunShading( bool show ) 0386 { 0387 d->m_showSunShading = show; 0388 } 0389 0390 bool MergedLayerDecorator::showSunShading() const 0391 { 0392 return d->m_showSunShading; 0393 } 0394 0395 void MergedLayerDecorator::setShowCityLights( bool show ) 0396 { 0397 d->m_showCityLights = show; 0398 } 0399 0400 bool MergedLayerDecorator::showCityLights() const 0401 { 0402 return d->m_showCityLights; 0403 } 0404 0405 void MergedLayerDecorator::setShowTileId( bool visible ) 0406 { 0407 d->m_showTileId = visible; 0408 } 0409 0410 void MergedLayerDecorator::Private::paintSunShading( QImage *tileImage, const TileId &id ) const 0411 { 0412 if ( tileImage->depth() != 32 ) 0413 return; 0414 0415 // TODO add support for 8-bit maps? 0416 // add sun shading 0417 const qreal global_width = tileImage->width() 0418 * TileLoaderHelper::levelToColumn( m_levelZeroColumns, id.zoomLevel() ); 0419 const qreal global_height = tileImage->height() 0420 * TileLoaderHelper::levelToRow( m_levelZeroRows, id.zoomLevel() ); 0421 const qreal lon_scale = 2*M_PI / global_width; 0422 const qreal lat_scale = -M_PI / global_height; 0423 const int tileHeight = tileImage->height(); 0424 const int tileWidth = tileImage->width(); 0425 0426 // First we determine the supporting point interval for the interpolation. 0427 const int n = maxDivisor( 30, tileWidth ); 0428 const int ipRight = n * (int)( tileWidth / n ); 0429 0430 for ( int cur_y = 0; cur_y < tileHeight; ++cur_y ) { 0431 const qreal lat = lat_scale * ( id.y() * tileHeight + cur_y ) - 0.5*M_PI; 0432 const qreal a = sin( (lat+DEG2RAD * m_sunLocator->getLat() )/2.0 ); 0433 const qreal c = cos(lat)*cos( -DEG2RAD * m_sunLocator->getLat() ); 0434 0435 QRgb* scanline = (QRgb*)tileImage->scanLine( cur_y ); 0436 0437 qreal lastShade = -10.0; 0438 0439 int cur_x = 0; 0440 0441 while ( cur_x < tileWidth ) { 0442 0443 const bool interpolate = ( cur_x != 0 && cur_x < ipRight && cur_x + n < tileWidth ); 0444 0445 qreal shade = 0; 0446 0447 if ( interpolate ) { 0448 const int check = cur_x + n; 0449 const qreal checklon = lon_scale * ( id.x() * tileWidth + check ); 0450 shade = m_sunLocator->shading( checklon, a, c ); 0451 0452 // if the shading didn't change across the interpolation 0453 // interval move on and don't change anything. 0454 if ( shade == lastShade && shade == 1.0 ) { 0455 scanline += n; 0456 cur_x += n; 0457 continue; 0458 } 0459 if ( shade == lastShade && shade == 0.0 ) { 0460 for ( int t = 0; t < n; ++t ) { 0461 SunLocator::shadePixel(*scanline, shade); 0462 ++scanline; 0463 } 0464 cur_x += n; 0465 continue; 0466 } 0467 for ( int t = 0; t < n ; ++t ) { 0468 const qreal lon = lon_scale * ( id.x() * tileWidth + cur_x ); 0469 shade = m_sunLocator->shading( lon, a, c ); 0470 SunLocator::shadePixel(*scanline, shade); 0471 ++scanline; 0472 ++cur_x; 0473 } 0474 } 0475 0476 else { 0477 // Make sure we don't exceed the image memory 0478 if ( cur_x < tileWidth ) { 0479 const qreal lon = lon_scale * ( id.x() * tileWidth + cur_x ); 0480 shade = m_sunLocator->shading( lon, a, c ); 0481 SunLocator::shadePixel(*scanline, shade); 0482 ++scanline; 0483 ++cur_x; 0484 } 0485 } 0486 lastShade = shade; 0487 } 0488 } 0489 } 0490 0491 void MergedLayerDecorator::Private::paintTileId( QImage *tileImage, const TileId &id ) const 0492 { 0493 QString filename = QString::fromUtf8( "%1_%2.jpg" ) 0494 .arg(id.x(), tileDigits, 10, QLatin1Char('0')) 0495 .arg(id.y(), tileDigits, 10, QLatin1Char('0')); 0496 0497 QPainter painter( tileImage ); 0498 0499 QColor foreground; 0500 QColor background; 0501 0502 if ( ( (qreal)(id.x())/2 == id.x()/2 && (qreal)(id.y())/2 == id.y()/2 ) 0503 || ( (qreal)(id.x())/2 != id.x()/2 && (qreal)(id.y())/2 != id.y()/2 ) 0504 ) 0505 { 0506 foreground.setNamedColor( QString::fromUtf8("#FFFFFF") ); 0507 background.setNamedColor( QString::fromUtf8("#000000") ); 0508 } 0509 else { 0510 foreground.setNamedColor( QString::fromUtf8("#000000") ); 0511 background.setNamedColor( QString::fromUtf8("#FFFFFF") ); 0512 } 0513 0514 int strokeWidth = 10; 0515 QPen testPen( foreground ); 0516 testPen.setWidth( strokeWidth ); 0517 testPen.setJoinStyle( Qt::MiterJoin ); 0518 0519 painter.setPen( testPen ); 0520 painter.drawRect( strokeWidth / 2, strokeWidth / 2, 0521 tileImage->width() - strokeWidth, 0522 tileImage->height() - strokeWidth ); 0523 QFont testFont(QStringLiteral("Sans Serif"), 12); 0524 QFontMetrics testFm( testFont ); 0525 painter.setFont( testFont ); 0526 0527 QPen outlinepen( foreground ); 0528 outlinepen.setWidthF( 6 ); 0529 0530 painter.setPen( outlinepen ); 0531 painter.setBrush( background ); 0532 0533 QPainterPath outlinepath; 0534 0535 QPointF baseline1( ( tileImage->width() - testFm.boundingRect(filename).width() ) / 2, 0536 ( tileImage->height() * 0.25) ); 0537 outlinepath.addText( baseline1, testFont, QString::fromUtf8( "level: %1" ).arg(id.zoomLevel()) ); 0538 0539 QPointF baseline2( ( tileImage->width() - testFm.boundingRect(filename).width() ) / 2, 0540 tileImage->height() * 0.50 ); 0541 outlinepath.addText( baseline2, testFont, filename ); 0542 0543 QPointF baseline3( ( tileImage->width() - testFm.boundingRect(filename).width() ) / 2, 0544 tileImage->height() * 0.75 ); 0545 outlinepath.addText( baseline3, testFont, m_themeId ); 0546 0547 painter.drawPath( outlinepath ); 0548 0549 painter.setPen( Qt::NoPen ); 0550 painter.drawPath( outlinepath ); 0551 } 0552 0553 void MergedLayerDecorator::Private::detectMaxTileLevel() 0554 { 0555 if ( m_textureLayers.isEmpty() ) { 0556 m_maxTileLevel = -1; 0557 return; 0558 } 0559 0560 m_maxTileLevel = TileLoader::maximumTileLevel( *m_textureLayers.at( 0 ) ); 0561 } 0562 0563 QVector<const GeoSceneTextureTileDataset *> MergedLayerDecorator::Private::findRelevantTextureLayers( const TileId &stackedTileId ) const 0564 { 0565 QVector<const GeoSceneTextureTileDataset *> result; 0566 0567 for ( const GeoSceneTextureTileDataset *candidate: m_textureLayers ) { 0568 Q_ASSERT( candidate ); 0569 // check, if layer provides tiles for the current level 0570 if ( !candidate->hasMaximumTileLevel() || 0571 candidate->maximumTileLevel() >= stackedTileId.zoomLevel() ) { 0572 //check if the tile intersects with texture bounds 0573 if (candidate->latLonBox().isNull()) { 0574 result.append(candidate); 0575 } 0576 else { 0577 const GeoDataLatLonBox bbox = candidate->tileProjection()->geoCoordinates(stackedTileId); 0578 0579 if (candidate->latLonBox().intersects(bbox)) { 0580 result.append( candidate ); 0581 } 0582 } 0583 } 0584 } 0585 0586 return result; 0587 } 0588 0589 // TODO: This should likely go into a math class in the future ... 0590 0591 int MergedLayerDecorator::Private::maxDivisor( int maximum, int fullLength ) 0592 { 0593 // Find the optimal interpolation interval n for the 0594 // current image canvas width 0595 int best = 2; 0596 0597 int nEvalMin = fullLength; 0598 for ( int it = 1; it <= maximum; ++it ) { 0599 // The optimum is the interval which results in the least amount 0600 // supporting points taking into account the rest which can't 0601 // get used for interpolation. 0602 int nEval = fullLength / it + fullLength % it; 0603 if ( nEval < nEvalMin ) { 0604 nEvalMin = nEval; 0605 best = it; 0606 } 0607 } 0608 return best; 0609 }