diff --git a/src/lib/marble/DownloadRegion.cpp b/src/lib/marble/DownloadRegion.cpp index d36075f7f..ad8305c32 100644 --- a/src/lib/marble/DownloadRegion.cpp +++ b/src/lib/marble/DownloadRegion.cpp @@ -1,256 +1,256 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2012 Dennis Nienhüser // #include "DownloadRegion.h" #include "MarbleModel.h" #include "MarbleMap.h" #include "MarbleMath.h" #include "MarbleDebug.h" #include "TextureLayer.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataLineString.h" #include "TileCoordsPyramid.h" namespace Marble { class DownloadRegionPrivate { public: MarbleModel* m_marbleModel; QPair m_tileLevelRange; int m_visibleTileLevel; DownloadRegionPrivate(); int rad2PixelX( qreal const lon, const TextureLayer *textureLayer ) const; int rad2PixelY( qreal const lat, const TextureLayer *textureLayer ) const; }; DownloadRegionPrivate::DownloadRegionPrivate() : m_marbleModel( 0 ), m_tileLevelRange( 0, 0 ), m_visibleTileLevel( 0 ) { // nothing to do } // copied from AbstractScanlineTextureMapper and slightly adjusted int DownloadRegionPrivate::rad2PixelX( qreal const lon, const TextureLayer *textureLayer ) const { qreal const globalWidth = textureLayer->tileSize().width() * textureLayer->tileColumnCount( m_visibleTileLevel ); return static_cast( globalWidth * 0.5 + lon * ( globalWidth / ( 2.0 * M_PI ) ) ); } // copied from AbstractScanlineTextureMapper and slightly adjusted int DownloadRegionPrivate::rad2PixelY( qreal const lat, const TextureLayer *textureLayer ) const { qreal const globalHeight = textureLayer->tileSize().height() * textureLayer->tileRowCount( m_visibleTileLevel ); qreal const normGlobalHeight = globalHeight / M_PI; - switch ( textureLayer->tileProjection() ) { - case GeoSceneTileDataset::Equirectangular: + switch (textureLayer->tileProjectionType()) { + case GeoSceneAbstractTileProjection::Equirectangular: return static_cast( globalHeight * 0.5 - lat * normGlobalHeight ); - case GeoSceneTileDataset::Mercator: + case GeoSceneAbstractTileProjection::Mercator: if ( fabs( lat ) < 1.4835 ) return static_cast( globalHeight * 0.5 - gdInv( lat ) * 0.5 * normGlobalHeight ); if ( lat >= +1.4835 ) return static_cast( globalHeight * 0.5 - 3.1309587 * 0.5 * normGlobalHeight ); if ( lat <= -1.4835 ) return static_cast( globalHeight * 0.5 + 3.1309587 * 0.5 * normGlobalHeight ); } // Dummy value to avoid a warning. return 0; } DownloadRegion::DownloadRegion( QObject* parent ) : QObject( parent ), d( new DownloadRegionPrivate ) { // nothing to do } void DownloadRegion::setMarbleModel( MarbleModel* model ) { d->m_marbleModel = model; } DownloadRegion::~DownloadRegion() { delete d; } void DownloadRegion::setTileLevelRange( const int minimumTileLevel, const int maximumTileLevel ) { Q_ASSERT( minimumTileLevel >= 0 ); Q_ASSERT( maximumTileLevel >= 0 ); Q_ASSERT( minimumTileLevel <= maximumTileLevel ); d->m_tileLevelRange.first = minimumTileLevel; d->m_tileLevelRange.second = maximumTileLevel; } QVector DownloadRegion::region( const TextureLayer *textureLayer, const GeoDataLatLonAltBox &downloadRegion ) const { Q_ASSERT( textureLayer ); int const westX = d->rad2PixelX( downloadRegion.west(), textureLayer ); int const northY = d->rad2PixelY( downloadRegion.north(), textureLayer ); int const eastX = d->rad2PixelX( downloadRegion.east(), textureLayer ); int const southY = d->rad2PixelY( downloadRegion.south(), textureLayer ); // FIXME: remove this stuff mDebug() << "DownloadRegionDialog downloadRegion:" << "north:" << downloadRegion.north() << "south:" << downloadRegion.south() << "east:" << downloadRegion.east() << "west:" << downloadRegion.west(); mDebug() << "north/west (x/y):" << westX << northY; mDebug() << "south/east (x/y):" << eastX << southY; int const tileWidth = textureLayer->tileSize().width(); int const tileHeight = textureLayer->tileSize().height(); mDebug() << "DownloadRegionDialog downloadRegion: tileSize:" << tileWidth << tileHeight; int const visibleLevelX1 = qMin( westX, eastX ); int const visibleLevelY1 = qMin( northY, southY ); int const visibleLevelX2 = qMax( westX, eastX ); int const visibleLevelY2 = qMax( northY, southY ); mDebug() << "visible level pixel coords (level/x1/y1/x2/y2):" << d->m_visibleTileLevel << visibleLevelX1 << visibleLevelY1 << visibleLevelX2 << visibleLevelY2; int bottomLevelX1, bottomLevelY1, bottomLevelX2, bottomLevelY2; // the pixel coords calculated above are referring to the visible tile level, // if the bottom level is a different level, we have to take it into account if ( d->m_visibleTileLevel > d->m_tileLevelRange.second ) { int const deltaLevel = d->m_visibleTileLevel - d->m_tileLevelRange.second; bottomLevelX1 = visibleLevelX1 >> deltaLevel; bottomLevelY1 = visibleLevelY1 >> deltaLevel; bottomLevelX2 = visibleLevelX2 >> deltaLevel; bottomLevelY2 = visibleLevelY2 >> deltaLevel; } else if ( d->m_visibleTileLevel < d->m_tileLevelRange.second ) { int const deltaLevel = d->m_tileLevelRange.second - d->m_visibleTileLevel; bottomLevelX1 = visibleLevelX1 << deltaLevel; bottomLevelY1 = visibleLevelY1 << deltaLevel; bottomLevelX2 = visibleLevelX2 << deltaLevel; bottomLevelY2 = visibleLevelY2 << deltaLevel; } else { bottomLevelX1 = visibleLevelX1; bottomLevelY1 = visibleLevelY1; bottomLevelX2 = visibleLevelX2; bottomLevelY2 = visibleLevelY2; } mDebug() << "bottom level pixel coords (level/x1/y1/x2/y2):" << d->m_tileLevelRange.second << bottomLevelX1 << bottomLevelY1 << bottomLevelX2 << bottomLevelY2; TileCoordsPyramid coordsPyramid( d->m_tileLevelRange.first, d->m_tileLevelRange.second ); QRect bottomLevelTileCoords; bottomLevelTileCoords.setCoords ( bottomLevelX1 / tileWidth, bottomLevelY1 / tileHeight, bottomLevelX2 / tileWidth + ( bottomLevelX2 % tileWidth > 0 ? 1 : 0 ), bottomLevelY2 / tileHeight + ( bottomLevelY2 % tileHeight > 0 ? 1 : 0 )); mDebug() << "bottom level tile coords: (x1/y1/size):" << bottomLevelTileCoords; coordsPyramid.setBottomLevelCoords( bottomLevelTileCoords ); mDebug() << "tiles count:" << coordsPyramid.tilesCount( ); QVector pyramid; pyramid << coordsPyramid; return pyramid; } void DownloadRegion::setVisibleTileLevel(const int tileLevel) { d->m_visibleTileLevel = tileLevel; } QVector DownloadRegion::fromPath( const TextureLayer *textureLayer, qreal offset, const GeoDataLineString &waypoints ) const { if ( !d->m_marbleModel ) { return QVector(); } int const topLevel = d->m_tileLevelRange.first; int const bottomLevel = d->m_tileLevelRange.second; TileCoordsPyramid coordsPyramid( topLevel, bottomLevel ); int const tileWidth = textureLayer->tileSize().width(); int const tileHeight = textureLayer->tileSize().height(); qreal radius = d->m_marbleModel->planetRadius(); QVector pyramid; qreal radianOffset = offset / radius; for( int i = 1; i < waypoints.size(); ++i ) { GeoDataCoordinates position = waypoints[i]; qreal lonCenter = position.longitude(); qreal latCenter = position.latitude(); // coordinates of the of the vertices of the square(topleft and bottomright) at an offset distance from the waypoint qreal latNorth = asin( sin( latCenter ) * cos( radianOffset ) + cos( latCenter ) * sin( radianOffset ) * cos( 7*M_PI/4 ) ); qreal dlonWest = atan2( sin( 7*M_PI/4 ) * sin( radianOffset ) * cos( latCenter ), cos( radianOffset ) - sin( latCenter ) * sin( latNorth ) ); qreal lonWest = fmod( lonCenter - dlonWest + M_PI, 2*M_PI ) - M_PI; qreal latSouth = asin( sin( latCenter ) * cos( radianOffset ) + cos( latCenter ) * sin( radianOffset ) * cos( 3*M_PI/4 ) ); qreal dlonEast = atan2( sin( 3*M_PI/4 ) * sin( radianOffset ) * cos( latCenter ), cos( radianOffset ) - sin( latCenter ) * sin( latSouth ) ); qreal lonEast = fmod( lonCenter - dlonEast+M_PI, 2*M_PI ) - M_PI; int const northY = d->rad2PixelY( latNorth, textureLayer ); int const southY = d->rad2PixelY( latSouth, textureLayer ); int const eastX = d->rad2PixelX( lonEast, textureLayer ); int const westX = d->rad2PixelX( lonWest, textureLayer ); int const west = qMin( westX, eastX ); int const north = qMin( northY, southY ); int const east = qMax( westX, eastX ); int const south = qMax( northY, southY ); int bottomLevelTileX1 = 0; int bottomLevelTileY1 = 0; int bottomLevelTileX2 = 0; int bottomLevelTileY2 = 0; if ( d->m_visibleTileLevel > d->m_tileLevelRange.second ) { int const deltaLevel = d->m_visibleTileLevel - d->m_tileLevelRange.second; bottomLevelTileX1 = west >> deltaLevel; bottomLevelTileY1 = north >> deltaLevel; bottomLevelTileX2 = east >> deltaLevel; bottomLevelTileY2 = south >> deltaLevel; } else if ( d->m_visibleTileLevel < bottomLevel ) { int const deltaLevel = bottomLevel - d->m_visibleTileLevel; bottomLevelTileX1 = west << deltaLevel; bottomLevelTileY1 = north << deltaLevel; bottomLevelTileX2 = east << deltaLevel; bottomLevelTileY2 = south << deltaLevel; } else { bottomLevelTileX1 = west; bottomLevelTileY1 = north; bottomLevelTileX2 = east; bottomLevelTileY2 = south; } QRect waypointRegion; //square region around the waypoint waypointRegion.setCoords( bottomLevelTileX1/tileWidth, bottomLevelTileY1/tileHeight, bottomLevelTileX2/tileWidth, bottomLevelTileY2/tileHeight ); coordsPyramid.setBottomLevelCoords( waypointRegion ); pyramid << coordsPyramid; } return pyramid; } } #include "moc_DownloadRegion.cpp" diff --git a/src/lib/marble/MapThemeManager.cpp b/src/lib/marble/MapThemeManager.cpp index 512abb688..c2ab59229 100644 --- a/src/lib/marble/MapThemeManager.cpp +++ b/src/lib/marble/MapThemeManager.cpp @@ -1,599 +1,599 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008 Torsten Rahn // Copyright 2008 Jens-Michael Hoffmann // // Own #include "MapThemeManager.h" // Qt #include #include #include #include #include #include #include #include // Local dir #include "GeoDataPhotoOverlay.h" #include "GeoSceneDocument.h" #include "GeoSceneMap.h" #include "GeoSceneHead.h" #include "GeoSceneIcon.h" #include "GeoSceneParser.h" #include "GeoSceneLayer.h" #include "GeoSceneTileDataset.h" #include "GeoSceneTextureTileDataset.h" #include "GeoSceneProperty.h" #include "GeoSceneZoom.h" #include "GeoSceneSettings.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "Planet.h" #include "PlanetFactory.h" // Std #include namespace { static const QString mapDirName = "maps"; static const int columnRelativePath = 1; } namespace Marble { class Q_DECL_HIDDEN MapThemeManager::Private { public: Private( MapThemeManager *parent ); ~Private(); void directoryChanged( const QString& path ); void fileChanged( const QString & path ); /** * @brief Updates the map theme model on request. * * This method should usually get invoked on startup or * by a QFileSystemWatcher instance. */ void updateMapThemeModel(); void watchPaths(); /** * @brief Adds directory paths and .dgml file paths to the given QStringList. */ static void addMapThemePaths( const QString& mapPathName, QStringList& result ); /** * @brief Helper method for findMapThemes(). Searches for .dgml files below * given directory path. */ static QStringList findMapThemes( const QString& basePath ); /** * @brief Searches for .dgml files below local and system map directory. */ static QStringList findMapThemes(); static GeoSceneDocument* loadMapThemeFile( const QString& mapThemeId ); /** * @brief Helper method for updateMapThemeModel(). */ static QList createMapThemeRow( const QString& mapThemeID ); /** * @brief Deletes any directory with its contents. * @param directory Path to directory * WARNING: Please do not raise this method's visibility in future, keep it private. */ static bool deleteDirectory( const QString &directory ); MapThemeManager *const q; QStandardItemModel m_mapThemeModel; QStandardItemModel m_celestialList; QFileSystemWatcher m_fileSystemWatcher; bool m_isInitialized; private: /** * @brief Returns all directory paths and .dgml file paths below local and * system map directory. */ static QStringList pathsToWatch(); }; MapThemeManager::Private::Private( MapThemeManager *parent ) : q( parent ), m_mapThemeModel( 0, 3 ), m_celestialList(), m_fileSystemWatcher(), m_isInitialized( false ) { } MapThemeManager::Private::~Private() { } MapThemeManager::MapThemeManager( QObject *parent ) : QObject( parent ), d( new Private( this ) ) { d->watchPaths(); connect( &d->m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); connect( &d->m_fileSystemWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString))); } MapThemeManager::~MapThemeManager() { delete d; } QStringList MapThemeManager::mapThemeIds() const { QStringList result; if ( !d->m_isInitialized ) { d->updateMapThemeModel(); d->m_isInitialized = true; } const int mapThemeIdCount = d->m_mapThemeModel.rowCount(); result.reserve(mapThemeIdCount); for (int i = 0; i < mapThemeIdCount; ++i) { const QString id = d->m_mapThemeModel.data( d->m_mapThemeModel.index( i, 0 ), Qt::UserRole + 1 ).toString(); result << id; } return result; } GeoSceneDocument* MapThemeManager::loadMapTheme( const QString& mapThemeStringID ) { if ( mapThemeStringID.isEmpty() ) return 0; return Private::loadMapThemeFile( mapThemeStringID ); } void MapThemeManager::deleteMapTheme( const QString &mapThemeId ) { const QString dgmlPath = MarbleDirs::localPath() + QLatin1String("/maps/") + mapThemeId; QFileInfo dgmlFile(dgmlPath); QString themeDir = dgmlFile.dir().absolutePath(); Private::deleteDirectory( themeDir ); } bool MapThemeManager::Private::deleteDirectory( const QString& directory ) { QDir dir( directory ); bool result = true; if ( dir.exists() ) { Q_FOREACH( const QFileInfo &info, dir.entryInfoList( QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst ) ) { if ( info.isDir() ) { result = deleteDirectory( info.absoluteFilePath() ); } else { result = QFile::remove( info.absoluteFilePath() ); } if ( !result ) { return result; } } result = dir.rmdir( directory ); if( !result ) { return result; } } return result; } GeoSceneDocument* MapThemeManager::Private::loadMapThemeFile( const QString& mapThemeStringID ) { const QString mapThemePath = mapDirName + QLatin1Char('/') + mapThemeStringID; const QString dgmlPath = MarbleDirs::path( mapThemePath ); // Check whether file exists QFile file( dgmlPath ); if ( !file.exists() ) { qWarning() << "Map theme file does not exist:" << dgmlPath; return 0; } // Open file in right mode const bool fileReadable = file.open( QIODevice::ReadOnly ); if ( !fileReadable ) { qWarning() << "Map theme file not readable:" << dgmlPath; return 0; } GeoSceneParser parser( GeoScene_DGML ); if ( !parser.read( &file )) { qWarning() << "Map theme file not well-formed:" << dgmlPath; return 0; } mDebug() << "Map theme file successfully loaded:" << dgmlPath; // Get result document GeoSceneDocument* document = static_cast( parser.releaseDocument() ); Q_ASSERT( document ); return document; } QStringList MapThemeManager::Private::pathsToWatch() { QStringList result; const QString localMapPathName = MarbleDirs::localPath() + QLatin1Char('/') + mapDirName; const QString systemMapPathName = MarbleDirs::systemPath() + QLatin1Char('/') + mapDirName; if( !QDir().exists( localMapPathName ) ) { QDir().mkpath( localMapPathName ); } result << localMapPathName; result << systemMapPathName; addMapThemePaths( localMapPathName, result ); addMapThemePaths( systemMapPathName, result ); return result; } QStringList MapThemeManager::Private::findMapThemes( const QString& basePath ) { const QString mapPathName = basePath + QLatin1Char('/') + mapDirName; QDir paths = QDir( mapPathName ); QStringList mapPaths = paths.entryList( QStringList( "*" ), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot ); QStringList mapDirs; for ( int planet = 0; planet < mapPaths.size(); ++planet ) { QDir themeDir = QDir(mapPathName + QLatin1Char('/') + mapPaths.at(planet)); QStringList themeMapPaths = themeDir.entryList( QStringList( "*" ), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot ); for ( int theme = 0; theme < themeMapPaths.size(); ++theme ) { mapDirs << mapPathName + QLatin1Char('/') + mapPaths.at(planet) + QLatin1Char('/') + themeMapPaths.at( theme ); } } QStringList mapFiles; QStringListIterator it( mapDirs ); while ( it.hasNext() ) { QString themeDir = it.next() + QLatin1Char('/'); QString themeDirName = QDir(themeDir).path().section(QLatin1Char('/'), -2, -1); QStringList tmp = QDir( themeDir ).entryList( QStringList( "*.dgml" ), QDir::Files | QDir::NoSymLinks ); if ( !tmp.isEmpty() ) { QStringListIterator k( tmp ); while ( k.hasNext() ) { QString themeXml = k.next(); mapFiles << themeDirName + QLatin1Char('/') + themeXml; } } } return mapFiles; } QStringList MapThemeManager::Private::findMapThemes() { QStringList mapFilesLocal = findMapThemes( MarbleDirs::localPath() ); QStringList mapFilesSystem = findMapThemes( MarbleDirs::systemPath() ); QStringList allMapFiles( mapFilesLocal ); allMapFiles << mapFilesSystem; // remove duplicate entries allMapFiles.sort(); for ( int i = 1; i < allMapFiles.size(); ++i ) { if ( allMapFiles.at(i) == allMapFiles.at( i-1 ) ) { allMapFiles.removeAt( i ); --i; } } return allMapFiles; } QStandardItemModel* MapThemeManager::mapThemeModel() { if ( !d->m_isInitialized ) { d->updateMapThemeModel(); d->m_isInitialized = true; } return &d->m_mapThemeModel; } QStandardItemModel *MapThemeManager::celestialBodiesModel() { if ( !d->m_isInitialized ) { d->updateMapThemeModel(); d->m_isInitialized = true; } return &d->m_celestialList; } QList MapThemeManager::Private::createMapThemeRow( QString const& mapThemeID ) { QList itemList; QScopedPointer mapTheme( loadMapThemeFile( mapThemeID ) ); if ( !mapTheme || !mapTheme->head()->visible() ) { return itemList; } QPixmap themeIconPixmap; QString relativePath = mapDirName + QLatin1Char('/') + mapTheme->head()->target() + QLatin1Char('/') + mapTheme->head()->theme() + QLatin1Char('/') + mapTheme->head()->icon()->pixmap(); themeIconPixmap.load( MarbleDirs::path( relativePath ) ); if ( themeIconPixmap.isNull() ) { relativePath = "svg/application-x-marble-gray.png"; themeIconPixmap.load( MarbleDirs::path( relativePath ) ); } else { // Make sure we don't keep excessively large previews in memory // TODO: Scale the icon down to the default icon size in MarbleSelectView. // For now maxIconSize already equals what's expected by the listview. QSize maxIconSize( 136, 136 ); if ( themeIconPixmap.size() != maxIconSize ) { mDebug() << "Smooth scaling theme icon"; themeIconPixmap = themeIconPixmap.scaled( maxIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } } QIcon mapThemeIcon = QIcon( themeIconPixmap ); QString name = mapTheme->head()->name(); const QString translatedDescription = QCoreApplication::translate("DGML", mapTheme->head()->description().toUtf8().constData()); const QString toolTip = QLatin1String(" ") + translatedDescription + QLatin1String(" "); QStandardItem *item = new QStandardItem( name ); item->setData(QCoreApplication::translate("DGML", name.toUtf8().constData()), Qt::DisplayRole); item->setData( mapThemeIcon, Qt::DecorationRole ); item->setData(toolTip, Qt::ToolTipRole); item->setData( mapThemeID, Qt::UserRole + 1 ); item->setData(translatedDescription, Qt::UserRole + 2); itemList << item; return itemList; } void MapThemeManager::Private::updateMapThemeModel() { mDebug() << "updateMapThemeModel"; m_mapThemeModel.clear(); m_mapThemeModel.setHeaderData(0, Qt::Horizontal, QObject::tr("Name")); QStringList stringlist = findMapThemes(); QStringListIterator it( stringlist ); while ( it.hasNext() ) { QString mapThemeID = it.next(); QList itemList = createMapThemeRow( mapThemeID ); if ( !itemList.empty() ) { m_mapThemeModel.appendRow( itemList ); } } foreach ( const QString &mapThemeId, stringlist ) { const QString celestialBodyId = mapThemeId.section(QLatin1Char('/'), 0, 0); QString celestialBodyName = PlanetFactory::localizedName( celestialBodyId ); QList matchingItems = m_celestialList.findItems( celestialBodyId, Qt::MatchExactly, 1 ); if ( matchingItems.isEmpty() ) { m_celestialList.appendRow( QList() << new QStandardItem( celestialBodyName ) << new QStandardItem( celestialBodyId ) ); } } } void MapThemeManager::Private::watchPaths() { QStringList const paths = pathsToWatch(); QStringList const files = m_fileSystemWatcher.files(); QStringList const directories = m_fileSystemWatcher.directories(); // Check each resource to add that it is not being watched already, // otherwise some qWarning appears foreach( const QString &resource, paths ) { if ( !directories.contains( resource ) && !files.contains( resource ) ) { m_fileSystemWatcher.addPath( resource ); } } } void MapThemeManager::Private::directoryChanged( const QString& path ) { mDebug() << "directoryChanged:" << path; watchPaths(); mDebug() << "Emitting themesChanged()"; updateMapThemeModel(); emit q->themesChanged(); } void MapThemeManager::Private::fileChanged( const QString& path ) { mDebug() << "fileChanged:" << path; // 1. if the file does not (anymore) exist, it got deleted and we // have to delete the corresponding item from the model // 2. if the file exists it is changed and we have to replace // the item with a new one. const QString mapThemeId = path.section(QLatin1Char('/'), -3); mDebug() << "mapThemeId:" << mapThemeId; QList matchingItems = m_mapThemeModel.findItems( mapThemeId, Qt::MatchFixedString | Qt::MatchCaseSensitive, columnRelativePath ); mDebug() << "matchingItems:" << matchingItems.size(); Q_ASSERT( matchingItems.size() <= 1 ); int insertAtRow = 0; if ( matchingItems.size() == 1 ) { const int row = matchingItems.front()->row(); insertAtRow = row; QList toBeDeleted = m_mapThemeModel.takeRow( row ); while ( !toBeDeleted.isEmpty() ) { delete toBeDeleted.takeFirst(); } } QFileInfo fileInfo( path ); if ( fileInfo.exists() ) { QList newMapThemeRow = createMapThemeRow( mapThemeId ); if ( !newMapThemeRow.empty() ) { m_mapThemeModel.insertRow( insertAtRow, newMapThemeRow ); } } emit q->themesChanged(); } // // // // void MapThemeManager::Private::addMapThemePaths( const QString& mapPathName, QStringList& result ) { QDir mapPath( mapPathName ); QStringList orbDirNames = mapPath.entryList( QStringList( "*" ), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot ); QStringListIterator itOrb( orbDirNames ); while ( itOrb.hasNext() ) { const QString orbPathName = mapPathName + QLatin1Char('/') + itOrb.next(); result << orbPathName; QDir orbPath( orbPathName ); QStringList themeDirNames = orbPath.entryList( QStringList( "*" ), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot ); QStringListIterator itThemeDir( themeDirNames ); while ( itThemeDir.hasNext() ) { const QString themePathName = orbPathName + QLatin1Char('/') + itThemeDir.next(); result << themePathName; QDir themePath( themePathName ); QStringList themeFileNames = themePath.entryList( QStringList( "*.dgml" ), QDir::Files | QDir::NoSymLinks ); QStringListIterator itThemeFile( themeFileNames ); while ( itThemeFile.hasNext() ) { const QString themeFilePathName = themePathName + QLatin1Char('/') + itThemeFile.next(); result << themeFilePathName; } } } } GeoSceneDocument *MapThemeManager::createMapThemeFromOverlay( const GeoDataPhotoOverlay *overlayData ) { GeoSceneDocument * document = new GeoSceneDocument(); document->head()->setDescription( overlayData->description() ); document->head()->setName( overlayData->name() ); document->head()->setTheme( "photo" ); document->head()->setTarget( "panorama" ); document->head()->setRadius(36000); document->head()->setVisible(true); document->head()->zoom()->setMaximum(3500); document->head()->zoom()->setMinimum(900); document->head()->zoom()->setDiscrete(false); GeoSceneLayer * layer = new GeoSceneLayer( "photo" ); layer->setBackend("texture"); GeoSceneTextureTileDataset * texture = new GeoSceneTextureTileDataset( "map" ); texture->setExpire(std::numeric_limits::max()); QString fileName = overlayData->absoluteIconFile(); QFileInfo fileInfo( fileName ); fileName = fileInfo.fileName(); QString sourceDir = fileInfo.absoluteDir().path(); QString extension = fileInfo.suffix(); texture->setSourceDir( sourceDir ); texture->setFileFormat( extension ); texture->setInstallMap( fileName ); - texture->setProjection(GeoSceneTileDataset::Equirectangular); + texture->setTileProjection(GeoSceneAbstractTileProjection::Equirectangular); layer->addDataset(texture); document->map()->addLayer(layer); GeoSceneSettings *settings = document->settings(); GeoSceneProperty *gridProperty = new GeoSceneProperty( "coordinate-grid" ); gridProperty->setValue( false ); gridProperty->setAvailable( false ); settings->addProperty( gridProperty ); GeoSceneProperty *overviewmap = new GeoSceneProperty( "overviewmap" ); overviewmap->setValue( false ); overviewmap->setAvailable( false ); settings->addProperty( overviewmap ); GeoSceneProperty *compass = new GeoSceneProperty( "compass" ); compass->setValue( false ); compass->setAvailable( false ); settings->addProperty( compass ); GeoSceneProperty *scalebar = new GeoSceneProperty( "scalebar" ); scalebar->setValue( true ); scalebar->setAvailable( true ); settings->addProperty( scalebar ); return document; } } #include "moc_MapThemeManager.cpp" diff --git a/src/lib/marble/MapWizard.cpp b/src/lib/marble/MapWizard.cpp index 83727e961..549c5116f 100644 --- a/src/lib/marble/MapWizard.cpp +++ b/src/lib/marble/MapWizard.cpp @@ -1,1122 +1,1122 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Utku Aydın // #include "MapWizard.h" #include "ui_MapWizard.h" #include "MarbleGlobal.h" #include "MarbleDirs.h" #include "MarbleDebug.h" #include "ServerLayout.h" #include "GeoSceneDocument.h" #include "GeoSceneHead.h" #include "GeoSceneIcon.h" #include "GeoSceneZoom.h" #include "GeoSceneMap.h" #include "GeoSceneLayer.h" #include "GeoSceneTileDataset.h" #include "GeoSceneSettings.h" #include "GeoSceneProperty.h" #include "GeoSceneGeodata.h" #include "GeoSceneLegend.h" #include "GeoSceneSection.h" #include "GeoSceneItem.h" #include "GeoSceneVector.h" #include "GeoWriter.h" #include "DgmlElementDictionary.h" #include "MarbleWidget.h" #include "MarbleNavigator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Marble { class MapWizardPrivate { public: MapWizardPrivate() : m_serverCapabilitiesValid( false ), mapProviderType() {} void pageEntered( int id ); Ui::MapWizard uiWidget; QString mapTheme; QNetworkAccessManager xmlAccessManager; QNetworkAccessManager legendAccessManager; QNetworkAccessManager levelZeroAccessManager; QStringList wmsServerList; QMap wmsFetchedMaps; QStringList staticUrlServerList; bool m_serverCapabilitiesValid; enum mapType { NoMap, StaticImageMap, WmsMap, StaticUrlMap }; mapType mapProviderType; QByteArray levelZero; QImage previewImage; QString format; QStringList wmsLegends; QString sourceImage; }; class PreviewDialog : public QDialog { Q_OBJECT public: PreviewDialog( QWidget* parent, const QString& mapThemeId ); virtual void closeEvent(QCloseEvent* e ); private: bool deleteTheme( const QString& directory ); QString m_mapThemeId; }; PreviewDialog::PreviewDialog( QWidget* parent, const QString& mapThemeId ) : QDialog( parent ), m_mapThemeId( mapThemeId ) { QGridLayout *layout = new QGridLayout(); MarbleWidget *widget = new MarbleWidget(); MarbleNavigator *navigator = new MarbleNavigator(); connect( navigator, SIGNAL(goHome()), widget, SLOT(goHome()) ); connect( navigator, SIGNAL(moveUp()), widget, SLOT(moveUp()) ); connect( navigator, SIGNAL(moveDown()), widget, SLOT(moveDown()) ); connect( navigator, SIGNAL(moveLeft()), widget, SLOT(moveLeft()) ); connect( navigator, SIGNAL(moveRight()), widget, SLOT(moveRight()) ); connect( navigator, SIGNAL(zoomIn()), widget, SLOT(zoomIn()) ); connect( navigator, SIGNAL(zoomOut()), widget, SLOT(zoomOut()) ); connect( navigator, SIGNAL(zoomChanged(int)), widget, SLOT(setZoom(int)) ); widget->setMapThemeId( m_mapThemeId ); widget->setZoom( 1000 ); layout->addWidget( navigator, 1, 1 ); layout->addWidget( widget, 1, 2 ); layout->setMargin( 0 ); layout->setSpacing( 0 ); this->setLayout( layout ); this->setMinimumSize( 640, 480 ); this->setWindowTitle( tr( "Preview Map" ) ); } void PreviewDialog::closeEvent(QCloseEvent* e) { const QString dgmlPath = MarbleDirs::localPath() + QLatin1String("/maps/") + m_mapThemeId; const QString directory = dgmlPath.left(dgmlPath.lastIndexOf(QLatin1Char('/'))); this->deleteTheme( directory ); QDialog::closeEvent( e ); } bool PreviewDialog::deleteTheme( const QString &directory ) { QDir dir(directory); bool result = true; if (dir.exists(directory)) { Q_FOREACH(const QFileInfo& info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) { if (info.isDir()) { result = deleteTheme(info.absoluteFilePath()); } else { result = QFile::remove(info.absoluteFilePath()); } if (!result) { return result; } } result = dir.rmdir(directory); } return result; } void MapWizardPrivate::pageEntered( int id ) { if ( id == 1 ) { m_serverCapabilitiesValid = false; } else if ( id == 2 || id == 4 ) { levelZero.clear(); uiWidget.comboBoxStaticUrlServer->clear(); uiWidget.comboBoxStaticUrlServer->addItems( staticUrlServerList ); uiWidget.comboBoxStaticUrlServer->addItem( "http://" ); } else if ( id == 5 ) { if ( mapProviderType == MapWizardPrivate::StaticImageMap ) { previewImage = QImage( uiWidget.lineEditSource->text() ).scaled( 136, 136, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } else { previewImage = QImage::fromData( levelZero ).scaled( 136, 136, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } uiWidget.labelPreview->setPixmap( QPixmap::fromImage( previewImage ) ); } else if ( id == 7 ) { uiWidget.labelThumbnail->setPixmap( QPixmap::fromImage( previewImage ) ); } } MapWizard::MapWizard( QWidget* parent ) : QWizard( parent ), d( new MapWizardPrivate ) { d->uiWidget.setupUi( this ); connect( this, SIGNAL(currentIdChanged(int)), this, SLOT(pageEntered(int)) ); connect( &( d->xmlAccessManager ), SIGNAL(finished(QNetworkReply*)), this, SLOT(parseServerCapabilities(QNetworkReply*)) ); connect( &( d->legendAccessManager ), SIGNAL(finished(QNetworkReply*)), this, SLOT(createWmsLegend(QNetworkReply*)) ); connect( &( d->levelZeroAccessManager ), SIGNAL(finished(QNetworkReply*)), this, SLOT(createLevelZero(QNetworkReply*)) ); connect( d->uiWidget.pushButtonSource, SIGNAL(clicked(bool)), this, SLOT(querySourceImage()) ); connect( d->uiWidget.pushButtonPreview, SIGNAL(clicked(bool)), this, SLOT(queryPreviewImage()) ); connect( d->uiWidget.pushButtonLegend_2, SIGNAL(clicked(bool)), this, SLOT(queryLegendImage()) ); connect( d->uiWidget.comboBoxWmsServer, SIGNAL(currentIndexChanged(QString)), d->uiWidget.lineEditWmsUrl, SLOT(setText(QString)) ); connect( d->uiWidget.listWidgetWmsMaps, SIGNAL(itemSelectionChanged()), this, SLOT(autoFillDetails()) ); connect( d->uiWidget.lineEditTitle, SIGNAL(textChanged(QString)), d->uiWidget.labelSumMName, SLOT(setText(QString)) ); connect( d->uiWidget.lineEditTheme, SIGNAL(textChanged(QString)), d->uiWidget.labelSumMTheme, SLOT(setText(QString)) ); connect( d->uiWidget.pushButtonPreviewMap, SIGNAL(clicked(bool)), this, SLOT(showPreview()) ); } MapWizard::~MapWizard() { delete d; } void MapWizard::queryServerCapabilities() { QUrl url( d->uiWidget.lineEditWmsUrl->text() ); QUrlQuery urlQuery; urlQuery.addQueryItem( "service", "WMS" ); urlQuery.addQueryItem( "request", "GetCapabilities" ); url.setQuery(urlQuery); QNetworkRequest request; request.setUrl( url ); d->xmlAccessManager.get( request ); } void MapWizard::parseServerCapabilities( QNetworkReply* reply ) { button( MapWizard::NextButton )->setEnabled( true ); QString result( reply->readAll() ); QDomDocument xml; if( !xml.setContent( result ) ) { QMessageBox::critical( this, tr( "Error while parsing" ), tr( "Wizard cannot parse server's response" ) ); return; } if( xml.documentElement().firstChildElement().tagName().isNull() ) { QMessageBox::critical( this, tr( "Error while parsing" ), tr( "Server is not a Web Map Server." ) ); return; } QDomElement firstLayer = xml.documentElement().firstChildElement( "Capability" ).firstChildElement( "Layer" ); QDomNodeList layers = firstLayer.elementsByTagName( "Layer" ); d->uiWidget.listWidgetWmsMaps->clear(); d->wmsFetchedMaps.clear(); for( int i = 0; i < layers.size(); ++i ) { QString theme = layers.at( i ).firstChildElement( "Name" ).text(); QString title = layers.at( i ).firstChildElement( "Title" ).text(); QDomElement legendUrl = layers.at( i ).firstChildElement( "Style" ).firstChildElement( "LegendURL" ); d->wmsFetchedMaps[ theme ] = title; d->wmsLegends.clear(); if( legendUrl.isNull() ) { d->wmsLegends.append( QString() ); } else { d->wmsLegends.append( legendUrl.firstChildElement( "OnlineResource" ).attribute( "xlink:href" ) ); } } d->uiWidget.listWidgetWmsMaps->addItems( d->wmsFetchedMaps.values() ); QDomElement format = xml.documentElement().firstChildElement( "Capability" ).firstChildElement( "Request" ) .firstChildElement( "GetMap" ).firstChildElement( "Format" ); d->format = format.text().right(format.text().length() - format.text().indexOf(QLatin1Char('/')) - 1).toLower(); if (d->format == QLatin1String("jpeg")) { d->format = "jpg"; } if( !d->wmsFetchedMaps.isEmpty() && !d->wmsServerList.contains( d->uiWidget.lineEditWmsUrl->text() ) ) { d->wmsServerList.append( d->uiWidget.lineEditWmsUrl->text() ); setWmsServers( d->wmsServerList ); } d->m_serverCapabilitiesValid = true; next(); } void MapWizard::createWmsLegend( QNetworkReply* reply ) { QByteArray result( reply->readAll() ); QDir map(MarbleDirs::localPath() + QLatin1String("/maps/earth/") + d->mapTheme); if( !map.exists( "legend" ) ) { map.mkdir( "legend" ); } QFile image(map.absolutePath() + QLatin1String("/legend/legend.png")); image.open( QIODevice::ReadWrite ); image.write( result ); image.close(); const QString legendHtml = createLegendHtml(); createLegendFile( legendHtml ); } void MapWizard::setWmsServers( const QStringList& uris ) { d->wmsServerList = uris; d->uiWidget.comboBoxWmsServer->clear(); d->uiWidget.comboBoxWmsServer->addItems( d->wmsServerList ); d->uiWidget.comboBoxWmsServer->addItem( tr( "Custom" ), "http://" ); } QStringList MapWizard::wmsServers() const { return d->wmsServerList; } QStringList MapWizard::staticUrlServers() const { return d->staticUrlServerList; } void MapWizard::setStaticUrlServers( const QStringList& uris ) { d->staticUrlServerList = uris; } void MapWizard::autoFillDetails() { QString selected = d->uiWidget.listWidgetWmsMaps->currentItem()->text(); d->uiWidget.lineEditTitle->setText( selected ); d->uiWidget.lineEditTheme->setText( d->wmsFetchedMaps.key( selected ) ); } bool MapWizard::createFiles( const GeoSceneDocument* document ) { // Create directories QDir maps(MarbleDirs::localPath() + QLatin1String("/maps/earth/")); if( !maps.exists( document->head()->theme() ) ) { maps.mkdir( document->head()->theme() ); if( d->mapProviderType == MapWizardPrivate::StaticImageMap ) { // Source image QFile sourceImage( d->sourceImage ); d->format = d->sourceImage.right(d->sourceImage.length() - d->sourceImage.lastIndexOf(QLatin1Char('.')) - 1).toLower(); sourceImage.copy( QString( "%1/%2/%2.%3" ).arg( maps.absolutePath() ) .arg( document->head()->theme() ) .arg( d->format ) ); } else if( d->mapProviderType == MapWizardPrivate::WmsMap ) { maps.mkdir( QString( "%1/0/" ).arg( document->head()->theme() ) ); maps.mkdir( QString( "%1/0/0" ).arg( document->head()->theme() ) ); const QString path = QString( "%1/%2/0/0/0.%3" ).arg( maps.absolutePath() ) .arg( document->head()->theme() ) .arg( d->format ); QFile baseTile( path ); baseTile.open( QFile::WriteOnly ); baseTile.write( d->levelZero ); } else if( d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { maps.mkdir( QString( "%1/0/" ).arg( document->head()->theme() ) ); maps.mkdir( QString( "%1/0/0" ).arg( document->head()->theme() ) ); const QString path = QString( "%1/%2/0/0/0.%3" ).arg( maps.absolutePath() ) .arg( document->head()->theme() ) .arg( d->format ); QFile baseTile( path ); baseTile.open( QFile::WriteOnly ); baseTile.write( d->levelZero ); } // Preview image QString pixmapPath = QString( "%1/%2/%3" ).arg( maps.absolutePath() ) .arg( document->head()->theme() ) .arg( document->head()->icon()->pixmap() ); d->previewImage.save( pixmapPath ); // DGML QFile file( QString( "%1/%2/%2.dgml" ).arg( maps.absolutePath() ) .arg( document->head()->theme() ) ); file.open( QIODevice::ReadWrite ); GeoWriter geoWriter; geoWriter.setDocumentType( dgml::dgmlTag_nameSpace20 ); geoWriter.write( &file, document ); file.close(); return true; } else return false; } QString MapWizard::createLegendHtml( const QString& image ) { QString htmlOutput; QXmlStreamWriter stream( &htmlOutput ); stream.writeStartDocument(); stream.writeStartElement( "html" ); stream.writeStartElement( "head" ); stream.writeTextElement( "title", "Marble: Legend" ); stream.writeStartElement( "link" ); stream.writeAttribute( "href", "legend.css" ); stream.writeAttribute( "rel", "stylesheet" ); stream.writeAttribute( "type", "text/css" ); stream.writeEndElement(); stream.writeStartElement( "body" ); stream.writeStartElement( "img" ); stream.writeAttribute( "src", image ); stream.writeEndElement(); stream.writeComment( " ##customLegendEntries:all## " ); stream.writeEndElement(); stream.writeEndElement(); return htmlOutput; } void MapWizard::createLegendFile( const QString& legendHtml ) { QDir map(MarbleDirs::localPath() + QLatin1String("/maps/earth/") + d->mapTheme); QFile html(map.absolutePath() + QLatin1String("/legend.html")); html.open( QIODevice::ReadWrite ); html.write( legendHtml.toLatin1().data() ); html.close(); } void MapWizard::downloadLegend( const QString& url ) { QUrl downloadUrl( url ); QNetworkRequest request( downloadUrl ); d->legendAccessManager.get( request ); } void MapWizard::downloadLevelZero() { if( d->mapProviderType == MapWizardPrivate::WmsMap ) { QString selected = d->uiWidget.listWidgetWmsMaps->currentItem()->text(); QUrl finalDownloadUrl( d->uiWidget.lineEditWmsUrl->text() ); QUrlQuery downloadUrl; downloadUrl.addQueryItem( "request", "GetMap" ); downloadUrl.addQueryItem( "version", "1.1.1" ); downloadUrl.addQueryItem( "layers", d->wmsFetchedMaps.key( selected ) ); downloadUrl.addQueryItem( "srs", "EPSG:4326" ); downloadUrl.addQueryItem( "width", "400" ); downloadUrl.addQueryItem( "height", "200" ); downloadUrl.addQueryItem( "bbox", "-180,-90,180,90" ); downloadUrl.addQueryItem( "format", "image/jpeg" ); downloadUrl.addQueryItem( "styles", "" ); finalDownloadUrl.setQuery( downloadUrl ); QNetworkRequest request( finalDownloadUrl ); d->levelZeroAccessManager.get( request ); } else if( d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { QString server = d->uiWidget.comboBoxStaticUrlServer->currentText(); QUrl downloadUrl; server.replace(server.indexOf(QLatin1String("{x}")), 3, QString::number(0)); server.replace(server.indexOf(QLatin1String("{y}")), 3, QString::number(0)); server.replace(server.indexOf(QLatin1String("{zoomLevel}")), 11, QString::number(0)); downloadUrl.setUrl( server ); QNetworkRequest request( downloadUrl ); d->levelZeroAccessManager.get( request ); } } void MapWizard::createLevelZero( QNetworkReply* reply ) { button( MapWizard::NextButton )->setEnabled( true ); d->levelZero = reply->readAll(); QImage testImage = QImage::fromData( d->levelZero ); if ( d->levelZero.isNull() ) { QMessageBox::information( this, tr( "Base Tile" ), tr( "The base tile could not be downloaded." ) ); return; } if ( testImage.isNull() ) { QMessageBox::information( this, tr( "Base Tile" ), tr( "The base tile could not be downloaded successfully. The server replied:\n\n%1" ).arg( QString( d->levelZero ) ) ); d->levelZero.clear(); return; } QBuffer testBuffer( &d->levelZero ); d->format = QImageReader( &testBuffer ).format(); if ( d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { const QString url = d->uiWidget.comboBoxStaticUrlServer->currentText(); d->staticUrlServerList.removeAll( url ); d->staticUrlServerList.prepend( url ); } next(); } void MapWizard::createLegend() { QDir map(MarbleDirs::localPath() + QLatin1String("/maps/earth/") + d->mapTheme); if( !map.exists( "legend" ) ) { map.mkdir( "legend" ); } QFile image; image.setFileName( d->uiWidget.lineEditLegend_2->text() ); image.copy(map.absolutePath() + QLatin1String("/legend/legend.png")); const QString legendHtml = createLegendHtml(); createLegendFile( legendHtml ); } void MapWizard::querySourceImage() { d->uiWidget.lineEditSource->setText( QFileDialog::getOpenFileName() ); } void MapWizard::queryPreviewImage() { QString fileName = QFileDialog::getOpenFileName(); d->previewImage = QImage( fileName ); QPixmap preview = QPixmap::fromImage( d->previewImage ); d->uiWidget.labelThumbnail->setPixmap( preview ); d->uiWidget.labelThumbnail->resize( preview.width(), preview.height() ); } void MapWizard::queryLegendImage() { QString fileName = QFileDialog::getOpenFileName(); d->uiWidget.lineEditLegend_2->setText( fileName ); const QString legendHtml = createLegendHtml( d->uiWidget.lineEditLegend_2->text() ); d->uiWidget.textBrowserLegend->setHtml( legendHtml ); } QString MapWizard::createArchive( QWidget *parent, const QString& mapId ) { QStringList splitMapId( mapId.split(QLatin1Char('/')) ); QString body = splitMapId[0]; QString theme = splitMapId[1]; QDir themeDir; QStringList tarArgs; tarArgs.append( "--create" ); tarArgs.append( "--gzip" ); tarArgs.append( "--file" ); tarArgs.append( QString( "%1/%2.tar.gz" ).arg( QDir::tempPath() ).arg( theme ) ); tarArgs.append( "--directory" ); if( QFile::exists( QString( "%1/maps/%2" ).arg( MarbleDirs::localPath() ).arg( mapId ) ) ) { tarArgs.append( QString( "%1/maps/" ).arg( MarbleDirs::localPath() ) ); themeDir.cd( QString( "%1/maps/%2/%3" ).arg( MarbleDirs::localPath() ).arg( body ).arg( theme ) ); } else if( QFile::exists( QString( "%1/maps/%2" ).arg( MarbleDirs::systemPath() ).arg( mapId ) ) ) { tarArgs.append( QString( "%1/maps/" ).arg( MarbleDirs::systemPath() ) ); themeDir.cd( QString( "%1/maps/%2/%3" ).arg( MarbleDirs::systemPath() ).arg( body ).arg( theme ) ); } if( QFile::exists( QString( "%1/%2.dgml" ).arg( themeDir.absolutePath() ).arg( theme ) ) ) { tarArgs.append( QString( "%1/%2/%2.dgml" ).arg( body ).arg( theme ) ); } if( QFile::exists( QString( "%1/legend.html" ).arg( themeDir.absolutePath() ) ) ) { tarArgs.append( QString( "%1/%2/legend.html" ).arg( body ).arg( theme ) ); } if( QFile::exists( QString( "%1/legend" ).arg( themeDir.absolutePath() ) ) ) { tarArgs.append( QString( "%1/%2/legend" ).arg( body ).arg( theme ) ); } if( QFile::exists( QString( "%1/0/000000" ).arg( themeDir.absolutePath() ) ) ) { tarArgs.append( QString( "%1/%2/0/000000" ).arg( body ).arg( theme ) ); } QStringList previewFilters; previewFilters << "preview.*"; QStringList preview = themeDir.entryList( previewFilters ); if( !preview.isEmpty() ) { tarArgs.append( QString( "%1/%2/%3" ).arg( body ).arg( theme ).arg( preview[0] ) ); } QStringList sourceImgFilters; sourceImgFilters << theme + QLatin1String(".jpg") << theme + QLatin1String(".png") << theme + QLatin1String(".jpeg"); QStringList sourceImg = themeDir.entryList( sourceImgFilters ); if( !sourceImg.isEmpty() ) { tarArgs.append( QString( "%1/%2/%3" ).arg( body ).arg( theme ).arg( sourceImg[0] ) ); } QProcess archiver; switch( archiver.execute( "tar", tarArgs ) ) { case -2: QMessageBox::critical( parent, tr( "Archiving failed" ), tr( "Archiving process cannot be started." ) ); break; case -1: QMessageBox::critical( parent, tr( "Archiving failed" ), tr( "Archiving process crashed." ) ); break; case 0: mDebug() << "Archived the theme successfully."; break; } archiver.waitForFinished(); return QString( "%1/%2.tar.gz" ).arg( QDir::tempPath() ).arg( theme ); } void MapWizard::deleteArchive( const QString& mapId ) { QStringList splitMapId( mapId.split(QLatin1Char('/')) ); QString theme = splitMapId[1]; QFile::remove( QString( "%1/%2.tar.gz" ).arg( QDir::tempPath() ).arg( theme ) ); } bool MapWizard::validateCurrentPage() { if ( currentId() == 1 && !d->m_serverCapabilitiesValid ) { queryServerCapabilities(); button( MapWizard::NextButton )->setEnabled( false ); return false; } if ( ( currentId() == 2 || currentId() == 4 ) && d->levelZero.isNull() ) { downloadLevelZero(); button( MapWizard::NextButton )->setEnabled( false ); return false; } if ( currentId() == 3 ) { d->sourceImage = d->uiWidget.lineEditSource->text(); if ( d->sourceImage.isEmpty() ) { QMessageBox::information( this, tr( "Source Image" ), tr( "Please specify a source image." ) ); d->uiWidget.lineEditSource->setFocus(); return false; } if ( !QFileInfo( d->sourceImage ).exists() ) { QMessageBox::information( this, tr( "Source Image" ), tr( "The source image you specified does not exist. Please specify a different one." ) ); d->uiWidget.lineEditSource->setFocus(); d->uiWidget.lineEditSource->selectAll(); return false; } if ( QImage( d->sourceImage ).isNull() ) { QMessageBox::information( this, tr( "Source Image" ), tr( "The source image you specified does not seem to be an image. Please specify a different image file." ) ); d->uiWidget.lineEditSource->setFocus(); d->uiWidget.lineEditSource->selectAll(); return false; } } if ( currentId() == 5 ) { if ( d->uiWidget.lineEditTitle->text().isEmpty() ) { QMessageBox::information( this, tr( "Map Title" ), tr( "Please specify a map title." ) ); d->uiWidget.lineEditTitle->setFocus(); return false; } d->mapTheme = d->uiWidget.lineEditTheme->text(); if ( d->mapTheme.isEmpty() ) { QMessageBox::information( this, tr( "Map Name" ), tr( "Please specify a map name." ) ); d->uiWidget.lineEditTheme->setFocus(); return false; } const QDir destinationDir( QString( "%1/maps/earth/%2" ).arg( MarbleDirs::localPath() ).arg( d->mapTheme ) ); if ( destinationDir.exists() ) { QMessageBox::information( this, tr( "Map Name" ), tr( "Please specify another map name, since there is already a map named \"%1\"." ).arg( d->mapTheme ) ); d->uiWidget.lineEditTheme->setFocus(); d->uiWidget.lineEditTheme->selectAll(); return false; } if ( d->previewImage.isNull() ) { QMessageBox::information( this, tr( "Preview Image" ), tr( "Please specify a preview image." ) ); d->uiWidget.pushButtonPreview->setFocus(); return false; } } return QWizard::validateCurrentPage(); } int MapWizard::nextId() const { switch( currentId() ) { case 0: if( d->uiWidget.radioButtonWms->isChecked() ) { d->mapProviderType = MapWizardPrivate::WmsMap; return 1; } else if( d->uiWidget.radioButtonBitmap->isChecked() ) { d->mapProviderType = MapWizardPrivate::StaticImageMap; return 3; } else if( d->uiWidget.radioButtonStaticUrl->isChecked() ) { d->mapProviderType = MapWizardPrivate::StaticUrlMap; return 4; } break; case 2: // WMS return 5; break; case 3: // Static Image return 5; break; case 7: // Finish return -1; break; default: break; } return currentId() + 1; } GeoSceneDocument* MapWizard::createDocument() { GeoSceneDocument *document = new GeoSceneDocument; GeoSceneHead *head = document->head(); head->setName( d->uiWidget.lineEditTitle->text() ); head->setTheme( d->uiWidget.lineEditTheme->text() ); head->setTarget( "earth" ); head->setDescription( d->uiWidget.textEditDesc->document()->toHtml() ); head->setVisible( true ); GeoSceneIcon *icon = head->icon(); icon->setPixmap( QString( "preview.png" ) ); GeoSceneZoom *zoom = head->zoom(); zoom->setMinimum( 900 ); zoom->setMaximum( 3500 ); zoom->setDiscrete( false ); GeoSceneTileDataset *texture = new GeoSceneTileDataset( "map" ); texture->setExpire( 31536000 ); texture->setSourceDir(QLatin1String("earth/") + document->head()->theme()); if( d->mapProviderType == MapWizardPrivate::WmsMap ) { texture->setFileFormat( d->format ); QString layer = d->wmsFetchedMaps.key( d->uiWidget.listWidgetWmsMaps->currentItem()->text() ); QUrl downloadUrl = QUrl( d->uiWidget.lineEditWmsUrl->text() ); QUrlQuery urlQuery; urlQuery.addQueryItem( "layers", layer ); downloadUrl.setQuery( urlQuery ); texture->addDownloadUrl( downloadUrl ); texture->setMaximumTileLevel( 20 ); texture->setLevelZeroRows( 1 ); texture->setLevelZeroColumns( 1 ); texture->setServerLayout( new WmsServerLayout( texture ) ); - texture->setProjection( GeoSceneTileDataset::Equirectangular ); + texture->setTileProjection(GeoSceneAbstractTileProjection::Equirectangular); } else if( d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { texture->setFileFormat( d->format ); QUrl downloadUrl = QUrl( d->uiWidget.comboBoxStaticUrlServer->currentText() ); texture->addDownloadPolicy( DownloadBrowse, 20 ); texture->addDownloadPolicy( DownloadBulk, 2 ); texture->addDownloadUrl( downloadUrl ); texture->setMaximumTileLevel( 20 ); texture->setLevelZeroRows( 1 ); texture->setLevelZeroColumns( 1 ); texture->setServerLayout( new CustomServerLayout( texture ) ); - texture->setProjection( GeoSceneTileDataset::Mercator ); + texture->setTileProjection(GeoSceneAbstractTileProjection::Mercator); } else if( d->mapProviderType == MapWizardPrivate::StaticImageMap ) { QString image = d->uiWidget.lineEditSource->text(); d->format = image.right(image.length() - image.lastIndexOf(QLatin1Char('.')) - 1).toLower(); texture->setFileFormat( d->format.toUpper() ); texture->setInstallMap(document->head()->theme() + QLatin1Char('.') + d->format); texture->setServerLayout( new MarbleServerLayout( texture ) ); - texture->setProjection( GeoSceneTileDataset::Equirectangular ); + texture->setTileProjection(GeoSceneAbstractTileProjection::Equirectangular); int imageWidth = QImage( image ).width(); int tileSize = c_defaultTileSize; float approxMaxTileLevel = log( imageWidth / ( 2.0 * tileSize ) ) / log( 2.0 ); int maxTileLevel = 0; if ( approxMaxTileLevel == int( approxMaxTileLevel ) ) { maxTileLevel = static_cast( approxMaxTileLevel ); } else { maxTileLevel = static_cast( approxMaxTileLevel + 1 ); } texture->setMaximumTileLevel( maxTileLevel ); } GeoSceneLayer *layer = new GeoSceneLayer( d->uiWidget.lineEditTheme->text() ); layer->setBackend( "texture" ); layer->addDataset( texture ); GeoSceneLayer* secondLayer = new GeoSceneLayer( "standardplaces" ); secondLayer->setBackend( "geodata" ); GeoSceneGeodata* cityplacemarks = new GeoSceneGeodata( "cityplacemarks" ); cityplacemarks->setSourceFile( "cityplacemarks.kml" ); secondLayer->addDataset( cityplacemarks ); GeoSceneGeodata* baseplacemarks = new GeoSceneGeodata( "baseplacemarks" ); baseplacemarks->setSourceFile( "baseplacemarks.kml" ); secondLayer->addDataset( baseplacemarks ); GeoSceneGeodata* elevplacemarks = new GeoSceneGeodata( "elevplacemarks" ); elevplacemarks->setSourceFile( "elevplacemarks.kml" ); secondLayer->addDataset( elevplacemarks ); GeoSceneGeodata* observatoryplacemarks = new GeoSceneGeodata( "observatoryplacemarks" ); observatoryplacemarks->setSourceFile( "observatoryplacemarks.kml" ); secondLayer->addDataset( observatoryplacemarks ); GeoSceneGeodata* otherplacemarks = new GeoSceneGeodata( "otherplacemarks" ); otherplacemarks->setSourceFile( "otherplacemarks.kml" ); secondLayer->addDataset( otherplacemarks ); GeoSceneGeodata* boundaryplacemarks = new GeoSceneGeodata( "boundaryplacemarks" ); boundaryplacemarks->setSourceFile( "boundaryplacemarks.kml" ); secondLayer->addDataset( boundaryplacemarks ); GeoSceneMap *map = document->map(); map->addLayer( layer ); map->addLayer( secondLayer ); GeoSceneSettings *settings = document->settings(); GeoSceneLegend *legend = document->legend(); if( d->uiWidget.checkBoxCoord->checkState() == Qt::Checked ) { GeoSceneProperty *coorGrid = new GeoSceneProperty( "coordinate-grid" ); coorGrid->setDefaultValue( true ); coorGrid->setAvailable( true ); settings->addProperty( coorGrid ); GeoSceneSection *coorSection = new GeoSceneSection( "coordinate-grid" ); coorSection->setHeading( "Coordinate Grid" ); coorSection->setCheckable( true ); coorSection->setConnectTo( "coordinate-grid" ); coorSection->setSpacing( 12 ); legend->addSection( coorSection ); } if( d->uiWidget.checkBoxInterest->checkState() == Qt::Checked ) { GeoSceneProperty *poiProperty = new GeoSceneProperty( "otherplaces" ); poiProperty->setDefaultValue( true ); poiProperty->setAvailable( true ); settings->addProperty( poiProperty ); GeoSceneSection *poiSection = new GeoSceneSection( "otherplaces" ); poiSection->setHeading( "Places of Interest" ); poiSection->setCheckable( true ); poiSection->setConnectTo( "otherplaces" ); poiSection->setSpacing( 12 ); GeoSceneItem *geoPole = new GeoSceneItem( "geographic-pole" ); GeoSceneIcon *geoPoleIcon = geoPole->icon(); geoPole->setText( tr("Geographic Pole") ); geoPoleIcon->setPixmap( "bitmaps/pole_1.png" ); poiSection->addItem( geoPole ); GeoSceneItem *magPole = new GeoSceneItem( "magnetic-pole" ); GeoSceneIcon *magPoleIcon = magPole->icon(); magPole->setText( tr("Magnetic Pole") ); magPoleIcon->setPixmap( "bitmaps/pole_2.png" ); poiSection->addItem( magPole ); GeoSceneItem *airport = new GeoSceneItem( "airport" ); GeoSceneIcon *airportIcon = airport->icon(); airport->setText( tr("Airport") ); airportIcon->setPixmap( "bitmaps/airport.png" ); poiSection->addItem( airport ); GeoSceneItem *shipwreck = new GeoSceneItem( "shipwreck" ); GeoSceneIcon *shipwreckIcon = shipwreck->icon(); shipwreck->setText( tr("Shipwreck") ); shipwreckIcon->setPixmap( "bitmaps/shipwreck.png" ); poiSection->addItem( shipwreck ); GeoSceneItem *observatory = new GeoSceneItem( "observatory" ); GeoSceneIcon *observatoryIcon = observatory->icon(); observatory->setText( tr("Observatory") ); observatoryIcon->setPixmap( "bitmaps/observatory.png" ); poiSection->addItem( observatory ); legend->addSection( poiSection ); } if( d->uiWidget.checkBoxTer->checkState() == Qt::Checked ) { GeoSceneProperty *terrainProperty = new GeoSceneProperty( "terrain" ); terrainProperty->setDefaultValue( true ); terrainProperty->setAvailable( true ); settings->addProperty( terrainProperty ); GeoSceneSection *terrainSection = new GeoSceneSection( "terrain" ); terrainSection->setHeading( "Terrain" ); terrainSection->setCheckable( true ); terrainSection->setConnectTo( "terrain" ); terrainSection->setSpacing( 12 ); GeoSceneItem *mountain = new GeoSceneItem( "mountain" ); GeoSceneIcon *mountainIcon = mountain->icon(); mountain->setText( tr("Mountain") ); mountainIcon->setPixmap( "bitmaps/mountain_1.png" ); terrainSection->addItem( mountain ); GeoSceneItem *volcano = new GeoSceneItem( "volcano" ); GeoSceneIcon *volcanoIcon = volcano->icon(); volcano->setText( tr("Volcano") ); volcanoIcon->setPixmap( "bitmaps/volcano_1.png" ); terrainSection->addItem( volcano ); legend->addSection( terrainSection ); } if( d->uiWidget.checkBoxPop->checkState() == Qt::Checked ) { GeoSceneProperty *placesProperty = new GeoSceneProperty( "places" ); placesProperty->setDefaultValue( true ); placesProperty->setAvailable( true ); settings->addProperty( placesProperty ); GeoSceneProperty *citiesProperty = new GeoSceneProperty( "cities" ); citiesProperty->setDefaultValue( true ); citiesProperty->setAvailable( true ); settings->addProperty( citiesProperty ); } if( d->uiWidget.checkBoxBorder->checkState() == Qt::Checked ) { GeoSceneSection *bordersSection = new GeoSceneSection( "borders" ); bordersSection->setHeading( "Boundaries" ); bordersSection->setCheckable( true ); bordersSection->setConnectTo( "borders" ); bordersSection->setSpacing( 12 ); GeoSceneItem *internationalBoundary = new GeoSceneItem( "international-boundary" ); GeoSceneIcon *internationalBoundaryIcon = internationalBoundary->icon(); internationalBoundary->setText( tr("International") ); internationalBoundaryIcon->setPixmap( "bitmaps/border_1.png" ); bordersSection->addItem( internationalBoundary ); GeoSceneItem *stateBoundary = new GeoSceneItem( "state" ); GeoSceneIcon *stateBoundaryIcon = stateBoundary->icon(); stateBoundary->setText( tr("State") ); stateBoundaryIcon->setPixmap( "bitmaps/border_2.png" ); bordersSection->addItem( stateBoundary ); GeoSceneProperty *bordersProperty = new GeoSceneProperty( "borders" ); bordersProperty->setDefaultValue( false ); bordersProperty->setAvailable( true ); settings->addProperty( bordersProperty ); GeoSceneProperty *intBoundariesProperty = new GeoSceneProperty( "international-boundaries" ); intBoundariesProperty->setDefaultValue( false ); intBoundariesProperty->setAvailable( true ); settings->addProperty( intBoundariesProperty ); GeoSceneProperty *stateBounderiesProperty = new GeoSceneProperty( "state-boundaries" ); stateBounderiesProperty->setDefaultValue( false ); stateBounderiesProperty->setAvailable( true ); settings->addProperty( stateBounderiesProperty ); legend->addSection( bordersSection ); GeoSceneLayer* mwdbii = new GeoSceneLayer( "mwdbii" ); mwdbii->setBackend( "vector" ); mwdbii->setRole( "polyline" ); GeoSceneVector* vector = new GeoSceneVector( "pdiffborder" ); vector->setFeature( "border" ); vector->setFileFormat( "PNT" ); vector->setSourceFile( "earth/mwdbii/PDIFFBORDER.PNT" ); vector->pen().setColor( "#ffe300" ); mwdbii->addDataset( vector ); map->addLayer( mwdbii ); } GeoSceneProperty *overviewmap = new GeoSceneProperty( "overviewmap" ); overviewmap->setDefaultValue( true ); overviewmap->setAvailable( true ); settings->addProperty( overviewmap ); GeoSceneProperty *compass = new GeoSceneProperty( "compass" ); compass->setDefaultValue( true ); compass->setAvailable( true ); settings->addProperty( compass ); GeoSceneProperty *scalebar = new GeoSceneProperty( "scalebar" ); scalebar->setDefaultValue( true ); scalebar->setAvailable( true ); settings->addProperty( scalebar ); return document; } void MapWizard::accept() { Q_ASSERT( d->mapProviderType != MapWizardPrivate::NoMap ); Q_ASSERT( d->format == d->format.toLower() ); Q_ASSERT( !d->mapTheme.isEmpty() ); if ( d->mapProviderType == MapWizardPrivate::StaticImageMap ) { d->sourceImage = d->uiWidget.lineEditSource->text(); Q_ASSERT( !d->sourceImage.isEmpty() ); Q_ASSERT( QFile( d->sourceImage ).exists() ); } else if ( d->mapProviderType == MapWizardPrivate::WmsMap ) { Q_ASSERT( !d->wmsFetchedMaps.isEmpty() ); Q_ASSERT( !d->levelZero.isNull() ); Q_ASSERT( !QImage::fromData( d->levelZero ).isNull() ); } else if ( d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { Q_ASSERT( !d->levelZero.isNull() ); Q_ASSERT( !QImage::fromData( d->levelZero ).isNull() ); } QSharedPointer document( createDocument() ); Q_ASSERT( !document->head()->description().isEmpty() ); Q_ASSERT( !document->head()->name().isEmpty() ); if( createFiles( document.data() ) ) { if( d->mapProviderType == MapWizardPrivate::WmsMap ) { if( d->wmsLegends.isEmpty() && d->wmsLegends.at( d->uiWidget.listWidgetWmsMaps->currentRow() ).isEmpty() ) { downloadLegend( d->wmsLegends.at( d->uiWidget.listWidgetWmsMaps->currentRow() ) ); } } else if( d->mapProviderType == MapWizardPrivate::StaticImageMap || d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { createLegend(); } QDialog::accept(); d->uiWidget.lineEditTitle->clear(); d->uiWidget.lineEditTheme->clear(); d->uiWidget.textEditDesc->clear(); d->uiWidget.labelPreview->clear(); d->uiWidget.lineEditSource->clear(); QTimer::singleShot( 0, this, SLOT(restart()) ); } else { QMessageBox::critical( this, tr( "Problem while creating files" ), tr( "Check if a theme with the same name exists." ) ); return; } } void MapWizard::showPreview() { QSharedPointer document( createDocument() ); if( createFiles( document.data() ) ) { if( d->mapProviderType == MapWizardPrivate::WmsMap ) { if( d->wmsLegends.isEmpty() && d->wmsLegends.at( d->uiWidget.listWidgetWmsMaps->currentRow() ).isEmpty() ) { downloadLegend( d->wmsLegends.at( d->uiWidget.listWidgetWmsMaps->currentRow() ) ); } } else if( d->mapProviderType == MapWizardPrivate::StaticImageMap || d->mapProviderType == MapWizardPrivate::StaticUrlMap ) { createLegend(); } } QPointer previewDialog = new PreviewDialog( this, document.data()->head()->mapThemeId() ); previewDialog->exec(); delete previewDialog; } } #include "moc_MapWizard.cpp" #include "MapWizard.moc" // needed for Q_OBJECT here in source diff --git a/src/lib/marble/MergedLayerDecorator.cpp b/src/lib/marble/MergedLayerDecorator.cpp index 46d4619e2..f6daf8bf1 100644 --- a/src/lib/marble/MergedLayerDecorator.cpp +++ b/src/lib/marble/MergedLayerDecorator.cpp @@ -1,618 +1,626 @@ // Copyright 2008 David Roberts // Copyright 2009 Jens-Michael Hoffmann // Copyright 2011 Bernhard Beschow // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library. If not, see . #include "MergedLayerDecorator.h" #include "blendings/Blending.h" #include "blendings/BlendingFactory.h" #include "SunLocator.h" #include "MarbleMath.h" #include "MarbleDebug.h" #include "GeoDataGroundOverlay.h" #include "GeoSceneTextureTileDataset.h" #include "ImageF.h" #include "StackedTile.h" #include "TileLoaderHelper.h" #include "TextureTile.h" #include "TileLoader.h" #include "RenderState.h" #include "GeoDataCoordinates.h" #include #include using namespace Marble; class Q_DECL_HIDDEN MergedLayerDecorator::Private { public: Private( TileLoader *tileLoader, const SunLocator *sunLocator ); static int maxDivisor( int maximum, int fullLength ); StackedTile *createTile( const QVector > &tiles ) const; void renderGroundOverlays( QImage *tileImage, const QVector > &tiles ) const; void paintSunShading( QImage *tileImage, const TileId &id ) const; void paintTileId( QImage *tileImage, const TileId &id ) const; void detectMaxTileLevel(); QVector findRelevantTextureLayers( const TileId &stackedTileId ) const; TileLoader *const m_tileLoader; const SunLocator *const m_sunLocator; BlendingFactory m_blendingFactory; QVector m_textureLayers; QList m_groundOverlays; int m_maxTileLevel; QString m_themeId; int m_levelZeroColumns; int m_levelZeroRows; bool m_showSunShading; bool m_showCityLights; bool m_showTileId; }; MergedLayerDecorator::Private::Private( TileLoader *tileLoader, const SunLocator *sunLocator ) : m_tileLoader( tileLoader ), m_sunLocator( sunLocator ), m_blendingFactory( sunLocator ), m_textureLayers(), m_maxTileLevel( 0 ), m_themeId(), m_levelZeroColumns( 0 ), m_levelZeroRows( 0 ), m_showSunShading( false ), m_showCityLights( false ), m_showTileId( false ) { } MergedLayerDecorator::MergedLayerDecorator( TileLoader * const tileLoader, const SunLocator* sunLocator ) : d( new Private( tileLoader, sunLocator ) ) { } MergedLayerDecorator::~MergedLayerDecorator() { delete d; } void MergedLayerDecorator::setTextureLayers( const QVector &textureLayers ) { mDebug() << Q_FUNC_INFO; if ( textureLayers.count() > 0 ) { const GeoSceneTileDataset *const firstTexture = textureLayers.at( 0 ); d->m_levelZeroColumns = firstTexture->levelZeroColumns(); d->m_levelZeroRows = firstTexture->levelZeroRows(); d->m_blendingFactory.setLevelZeroLayout( d->m_levelZeroColumns, d->m_levelZeroRows ); d->m_themeId = QLatin1String("maps/") + firstTexture->sourceDir(); } d->m_textureLayers = textureLayers; d->detectMaxTileLevel(); } void MergedLayerDecorator::updateGroundOverlays(const QList &groundOverlays ) { d->m_groundOverlays = groundOverlays; } int MergedLayerDecorator::textureLayersSize() const { return d->m_textureLayers.size(); } int MergedLayerDecorator::maximumTileLevel() const { return d->m_maxTileLevel; } int MergedLayerDecorator::tileColumnCount( int level ) const { Q_ASSERT( !d->m_textureLayers.isEmpty() ); const int levelZeroColumns = d->m_textureLayers.at( 0 )->levelZeroColumns(); return TileLoaderHelper::levelToColumn( levelZeroColumns, level ); } int MergedLayerDecorator::tileRowCount( int level ) const { Q_ASSERT( !d->m_textureLayers.isEmpty() ); const int levelZeroRows = d->m_textureLayers.at( 0 )->levelZeroRows(); return TileLoaderHelper::levelToRow( levelZeroRows, level ); } -GeoSceneTileDataset::Projection MergedLayerDecorator::tileProjection() const +GeoSceneAbstractTileProjection::Type MergedLayerDecorator::tileProjectionType() const { Q_ASSERT( !d->m_textureLayers.isEmpty() ); - return d->m_textureLayers.at( 0 )->projection(); + return d->m_textureLayers.at( 0 )->tileProjectionType(); } QSize MergedLayerDecorator::tileSize() const { Q_ASSERT( !d->m_textureLayers.isEmpty() ); return d->m_textureLayers.at( 0 )->tileSize(); } StackedTile *MergedLayerDecorator::Private::createTile( const QVector > &tiles ) const { Q_ASSERT( !tiles.isEmpty() ); const TileId firstId = tiles.first()->id(); const TileId id( 0, firstId.zoomLevel(), firstId.x(), firstId.y() ); // Image for blending all the texture tiles on it QImage resultImage; // if there are more than one active texture layers, we have to convert the // result tile into QImage::Format_ARGB32_Premultiplied to make blending possible const bool withConversion = tiles.count() > 1 || m_showSunShading || m_showTileId || !m_groundOverlays.isEmpty(); foreach ( const QSharedPointer &tile, tiles ) { // Image blending. If there are several images in the same tile (like clouds // or hillshading images over the map) blend them all into only one image const Blending *const blending = tile->blending(); if ( blending ) { mDebug() << Q_FUNC_INFO << "blending"; if ( resultImage.isNull() ) { resultImage = QImage( tile->image()->size(), QImage::Format_ARGB32_Premultiplied ); } blending->blend( &resultImage, tile.data() ); } else { mDebug() << Q_FUNC_INFO << "no blending defined => copying top over bottom image"; if ( withConversion ) { resultImage = tile->image()->convertToFormat( QImage::Format_ARGB32_Premultiplied ); } else { resultImage = tile->image()->copy(); } } } renderGroundOverlays( &resultImage, tiles ); if ( m_showSunShading && !m_showCityLights ) { paintSunShading( &resultImage, id ); } if ( m_showTileId ) { paintTileId( &resultImage, id ); } return new StackedTile( id, resultImage, tiles ); } void MergedLayerDecorator::Private::renderGroundOverlays( QImage *tileImage, const QVector > &tiles ) const { /* All tiles are covering the same area. Pick one. */ const TileId tileId = tiles.first()->id(); - GeoDataLatLonBox tileLatLonBox = tileId.toLatLonBox( findRelevantTextureLayers( tileId ).first() ); + GeoDataLatLonBox tileLatLonBox; + findRelevantTextureLayers(tileId).first()->tileProjection()->geoCoordinates(tileId, tileLatLonBox); /* Map the ground overlay to the image. */ for ( int i = 0; i < m_groundOverlays.size(); ++i ) { const GeoDataGroundOverlay* overlay = m_groundOverlays.at( i ); if ( !overlay->isGloballyVisible() ) { continue; } const GeoDataLatLonBox overlayLatLonBox = overlay->latLonBox(); if ( !tileLatLonBox.intersects( overlayLatLonBox.toCircumscribedRectangle() ) ) { continue; } const qreal pixelToLat = tileLatLonBox.height() / tileImage->height(); const qreal pixelToLon = tileLatLonBox.width() / tileImage->width(); const qreal latToPixel = overlay->icon().height() / overlayLatLonBox.height(); const qreal lonToPixel = overlay->icon().width() / overlayLatLonBox.width(); const qreal global_height = tileImage->height() * TileLoaderHelper::levelToRow( m_levelZeroRows, tileId.zoomLevel() ); const qreal pixel2Rad = M_PI / global_height; const qreal rad2Pixel = global_height / M_PI; qreal latPixelPosition = rad2Pixel/2 * gdInv(tileLatLonBox.north()); + const bool isMercatorTileProjection = (m_textureLayers.at( 0 )->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator); for ( int y = 0; y < tileImage->height(); ++y ) { QRgb *scanLine = ( QRgb* ) ( tileImage->scanLine( y ) ); qreal lat = 0; - if (m_textureLayers.at( 0 )->projection() == GeoSceneTileDataset::Mercator) { + if (isMercatorTileProjection) { lat = gd(2 * (latPixelPosition - y) * pixel2Rad ); } else { lat = tileLatLonBox.north() - y * pixelToLat; } for ( int x = 0; x < tileImage->width(); ++x, ++scanLine ) { qreal lon = GeoDataCoordinates::normalizeLon( tileLatLonBox.west() + x * pixelToLon ); GeoDataCoordinates coords(lon, lat); GeoDataCoordinates rotatedCoords(coords); if (overlay->latLonBox().rotation() != 0) { // Possible TODO: Make this faster by creating the axisMatrix beforehand // and just call Quaternion::rotateAroundAxis(const matrix &m) here. rotatedCoords = coords.rotateAround(overlayLatLonBox.center(), -overlay->latLonBox().rotation()); } // TODO: The rotated latLonBox is bigger. We need to take this into account. // (Currently the GroundOverlay sometimes gets clipped because of that) if ( overlay->latLonBox().contains( rotatedCoords ) ) { qreal px = GeoDataLatLonBox::width( rotatedCoords.longitude(), overlayLatLonBox.west() ) * lonToPixel; qreal py = (qreal)( overlay->icon().height() ) - ( GeoDataLatLonBox::height( rotatedCoords.latitude(), overlayLatLonBox.south() ) * latToPixel ) - 1; if ( px >= 0 && px < overlay->icon().width() && py >= 0 && py < overlay->icon().height() ) { int alpha = qAlpha( overlay->icon().pixel( px, py ) ); if ( alpha != 0 ) { QRgb result = ImageF::pixelF( overlay->icon(), px, py ); if (alpha == 255) { *scanLine = result; } else { *scanLine = qRgb( ( alpha * qRed(result) + (255 - alpha) * qRed(*scanLine) ) / 255, ( alpha * qGreen(result) + (255 - alpha) * qGreen(*scanLine) ) / 255, ( alpha * qBlue(result) + (255 - alpha) * qBlue(*scanLine) ) / 255 ); } } } } } } } } StackedTile *MergedLayerDecorator::loadTile( const TileId &stackedTileId ) { const QVector textureLayers = d->findRelevantTextureLayers( stackedTileId ); QVector > tiles; tiles.reserve(textureLayers.size()); foreach ( const GeoSceneTextureTileDataset *layer, textureLayers ) { const TileId tileId( layer->sourceDir(), stackedTileId.zoomLevel(), stackedTileId.x(), stackedTileId.y() ); mDebug() << Q_FUNC_INFO << layer->sourceDir() << tileId << layer->tileSize() << layer->fileFormat(); // Blending (how to merge the images into an only image) const Blending *blending = d->m_blendingFactory.findBlending( layer->blending() ); if ( blending == 0 && !layer->blending().isEmpty() ) { mDebug() << Q_FUNC_INFO << "could not find blending" << layer->blending(); } const GeoSceneTextureTileDataset *const textureLayer = static_cast( layer ); const QImage tileImage = d->m_tileLoader->loadTileImage( textureLayer, tileId, DownloadBrowse ); QSharedPointer tile( new TextureTile( tileId, tileImage, blending ) ); tiles.append( tile ); } Q_ASSERT( !tiles.isEmpty() ); return d->createTile( tiles ); } RenderState MergedLayerDecorator::renderState( const TileId &stackedTileId ) const { QString const nameTemplate = "Tile %1/%2/%3"; RenderState state( nameTemplate.arg( stackedTileId.zoomLevel() ) .arg( stackedTileId.x() ) .arg( stackedTileId.y() ) ); const QVector textureLayers = d->findRelevantTextureLayers( stackedTileId ); foreach ( const GeoSceneTextureTileDataset *layer, textureLayers ) { const TileId tileId( layer->sourceDir(), stackedTileId.zoomLevel(), stackedTileId.x(), stackedTileId.y() ); RenderStatus tileStatus = Complete; switch ( TileLoader::tileStatus( layer, tileId ) ) { case TileLoader::Available: tileStatus = Complete; break; case TileLoader::Expired: tileStatus = WaitingForUpdate; break; case TileLoader::Missing: tileStatus = WaitingForData; break; } state.addChild( RenderState( layer->name(), tileStatus ) ); } return state; } bool MergedLayerDecorator::hasTextureLayer() const { return !d->m_textureLayers.isEmpty(); } StackedTile *MergedLayerDecorator::updateTile( const StackedTile &stackedTile, const TileId &tileId, const QImage &tileImage ) { Q_ASSERT( !tileImage.isNull() ); d->detectMaxTileLevel(); QVector > tiles = stackedTile.tiles(); for ( int i = 0; i < tiles.count(); ++ i) { if ( tiles[i]->id() == tileId ) { const Blending *blending = tiles[i]->blending(); tiles[i] = QSharedPointer( new TextureTile( tileId, tileImage, blending ) ); } } return d->createTile( tiles ); } void MergedLayerDecorator::downloadStackedTile( const TileId &id, DownloadUsage usage ) { const QVector textureLayers = d->findRelevantTextureLayers( id ); foreach ( const GeoSceneTextureTileDataset *textureLayer, textureLayers ) { if ( TileLoader::tileStatus( textureLayer, id ) != TileLoader::Available || usage == DownloadBrowse ) { d->m_tileLoader->downloadTile( textureLayer, id, usage ); } } } void MergedLayerDecorator::setShowSunShading( bool show ) { d->m_showSunShading = show; } bool MergedLayerDecorator::showSunShading() const { return d->m_showSunShading; } void MergedLayerDecorator::setShowCityLights( bool show ) { d->m_showCityLights = show; } bool MergedLayerDecorator::showCityLights() const { return d->m_showCityLights; } void MergedLayerDecorator::setShowTileId( bool visible ) { d->m_showTileId = visible; } void MergedLayerDecorator::Private::paintSunShading( QImage *tileImage, const TileId &id ) const { if ( tileImage->depth() != 32 ) return; // TODO add support for 8-bit maps? // add sun shading const qreal global_width = tileImage->width() * TileLoaderHelper::levelToColumn( m_levelZeroColumns, id.zoomLevel() ); const qreal global_height = tileImage->height() * TileLoaderHelper::levelToRow( m_levelZeroRows, id.zoomLevel() ); const qreal lon_scale = 2*M_PI / global_width; const qreal lat_scale = -M_PI / global_height; const int tileHeight = tileImage->height(); const int tileWidth = tileImage->width(); // First we determine the supporting point interval for the interpolation. const int n = maxDivisor( 30, tileWidth ); const int ipRight = n * (int)( tileWidth / n ); for ( int cur_y = 0; cur_y < tileHeight; ++cur_y ) { const qreal lat = lat_scale * ( id.y() * tileHeight + cur_y ) - 0.5*M_PI; const qreal a = sin( (lat+DEG2RAD * m_sunLocator->getLat() )/2.0 ); const qreal c = cos(lat)*cos( -DEG2RAD * m_sunLocator->getLat() ); QRgb* scanline = (QRgb*)tileImage->scanLine( cur_y ); qreal lastShade = -10.0; int cur_x = 0; while ( cur_x < tileWidth ) { const bool interpolate = ( cur_x != 0 && cur_x < ipRight && cur_x + n < tileWidth ); qreal shade = 0; if ( interpolate ) { const int check = cur_x + n; const qreal checklon = lon_scale * ( id.x() * tileWidth + check ); shade = m_sunLocator->shading( checklon, a, c ); // if the shading didn't change across the interpolation // interval move on and don't change anything. if ( shade == lastShade && shade == 1.0 ) { scanline += n; cur_x += n; continue; } if ( shade == lastShade && shade == 0.0 ) { for ( int t = 0; t < n; ++t ) { m_sunLocator->shadePixel( *scanline, shade ); ++scanline; } cur_x += n; continue; } for ( int t = 0; t < n ; ++t ) { const qreal lon = lon_scale * ( id.x() * tileWidth + cur_x ); shade = m_sunLocator->shading( lon, a, c ); m_sunLocator->shadePixel( *scanline, shade ); ++scanline; ++cur_x; } } else { // Make sure we don't exceed the image memory if ( cur_x < tileWidth ) { const qreal lon = lon_scale * ( id.x() * tileWidth + cur_x ); shade = m_sunLocator->shading( lon, a, c ); m_sunLocator->shadePixel( *scanline, shade ); ++scanline; ++cur_x; } } lastShade = shade; } } } void MergedLayerDecorator::Private::paintTileId( QImage *tileImage, const TileId &id ) const { QString filename = QString( "%1_%2.jpg" ) .arg(id.x(), tileDigits, 10, QLatin1Char('0')) .arg(id.y(), tileDigits, 10, QLatin1Char('0')); QPainter painter( tileImage ); QColor foreground; QColor background; if ( ( (qreal)(id.x())/2 == id.x()/2 && (qreal)(id.y())/2 == id.y()/2 ) || ( (qreal)(id.x())/2 != id.x()/2 && (qreal)(id.y())/2 != id.y()/2 ) ) { foreground.setNamedColor( "#FFFFFF" ); background.setNamedColor( "#000000" ); } else { foreground.setNamedColor( "#000000" ); background.setNamedColor( "#FFFFFF" ); } int strokeWidth = 10; QPen testPen( foreground ); testPen.setWidth( strokeWidth ); testPen.setJoinStyle( Qt::MiterJoin ); painter.setPen( testPen ); painter.drawRect( strokeWidth / 2, strokeWidth / 2, tileImage->width() - strokeWidth, tileImage->height() - strokeWidth ); QFont testFont(QStringLiteral("Sans Serif"), 12); QFontMetrics testFm( testFont ); painter.setFont( testFont ); QPen outlinepen( foreground ); outlinepen.setWidthF( 6 ); painter.setPen( outlinepen ); painter.setBrush( background ); QPainterPath outlinepath; QPointF baseline1( ( tileImage->width() - testFm.boundingRect(filename).width() ) / 2, ( tileImage->height() * 0.25) ); outlinepath.addText( baseline1, testFont, QString( "level: %1" ).arg(id.zoomLevel()) ); QPointF baseline2( ( tileImage->width() - testFm.boundingRect(filename).width() ) / 2, tileImage->height() * 0.50 ); outlinepath.addText( baseline2, testFont, filename ); QPointF baseline3( ( tileImage->width() - testFm.boundingRect(filename).width() ) / 2, tileImage->height() * 0.75 ); outlinepath.addText( baseline3, testFont, m_themeId ); painter.drawPath( outlinepath ); painter.setPen( Qt::NoPen ); painter.drawPath( outlinepath ); } void MergedLayerDecorator::Private::detectMaxTileLevel() { if ( m_textureLayers.isEmpty() ) { m_maxTileLevel = -1; return; } m_maxTileLevel = TileLoader::maximumTileLevel( *m_textureLayers.at( 0 ) ); } QVector MergedLayerDecorator::Private::findRelevantTextureLayers( const TileId &stackedTileId ) const { QVector result; foreach ( const GeoSceneTextureTileDataset *candidate, m_textureLayers ) { Q_ASSERT( candidate ); // check, if layer provides tiles for the current level if ( !candidate->hasMaximumTileLevel() || candidate->maximumTileLevel() >= stackedTileId.zoomLevel() ) { //check if the tile intersects with texture bounds - if ( candidate->latLonBox().isNull() - || candidate->latLonBox().intersects( stackedTileId.toLatLonBox( candidate ) ) ) - { - result.append( candidate ); + if (candidate->latLonBox().isNull()) { + result.append(candidate); + } + else { + GeoDataLatLonBox bbox; + candidate->tileProjection()->geoCoordinates(stackedTileId, bbox); + + if (candidate->latLonBox().intersects(bbox)) { + result.append( candidate ); + } } } } return result; } // TODO: This should likely go into a math class in the future ... int MergedLayerDecorator::Private::maxDivisor( int maximum, int fullLength ) { // Find the optimal interpolation interval n for the // current image canvas width int best = 2; int nEvalMin = fullLength; for ( int it = 1; it <= maximum; ++it ) { // The optimum is the interval which results in the least amount // supporting points taking into account the rest which can't // get used for interpolation. int nEval = fullLength / it + fullLength % it; if ( nEval < nEvalMin ) { nEvalMin = nEval; best = it; } } return best; } diff --git a/src/lib/marble/MergedLayerDecorator.h b/src/lib/marble/MergedLayerDecorator.h index b4c9c61c9..582555774 100644 --- a/src/lib/marble/MergedLayerDecorator.h +++ b/src/lib/marble/MergedLayerDecorator.h @@ -1,91 +1,91 @@ // Copyright 2008 David Roberts // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library. If not, see . #ifndef MARBLE_MERGEDLAYERDECORATOR_H #define MARBLE_MERGEDLAYERDECORATOR_H #include #include #include "GeoSceneTextureTileDataset.h" class QImage; class QString; class QSize; namespace Marble { class GeoDataGroundOverlay; class SunLocator; class StackedTile; class Tile; class TileId; class TileLoader; class RenderState; class MergedLayerDecorator { public: MergedLayerDecorator( TileLoader * const tileLoader, const SunLocator* sunLocator ); virtual ~MergedLayerDecorator(); void setTextureLayers( const QVector &textureLayers ); void updateGroundOverlays( const QList &groundOverlays ); int textureLayersSize() const; /** * Returns the highest level in which some tiles are theoretically * available for the current texture layers. */ int maximumTileLevel() const; int tileColumnCount( int level ) const; int tileRowCount( int level ) const; - GeoSceneTextureTileDataset::Projection tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; QSize tileSize() const; StackedTile *loadTile( const TileId &id ); StackedTile *updateTile( const StackedTile &stackedTile, const TileId &tileId, const QImage &tileImage ); void downloadStackedTile( const TileId &id, DownloadUsage usage ); void setShowSunShading( bool show ); bool showSunShading() const; void setShowCityLights( bool show ); bool showCityLights() const; void setShowTileId(bool show); RenderState renderState( const TileId &stackedTileId ) const; bool hasTextureLayer() const; protected: Q_DISABLE_COPY( MergedLayerDecorator ) class Private; Private *const d; }; } #endif diff --git a/src/lib/marble/ScanlineTextureMapperContext.cpp b/src/lib/marble/ScanlineTextureMapperContext.cpp index eeaae47db..8ab9e1fd5 100644 --- a/src/lib/marble/ScanlineTextureMapperContext.cpp +++ b/src/lib/marble/ScanlineTextureMapperContext.cpp @@ -1,506 +1,506 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Carlos Licea // Copyright 2011 Bernhard Beschow #include "MarbleDebug.h" #include "StackedTile.h" #include "StackedTileLoader.h" #include "TileId.h" #include "ViewParams.h" #include "ViewportParams.h" using namespace Marble; ScanlineTextureMapperContext::ScanlineTextureMapperContext( StackedTileLoader * const tileLoader, int tileLevel ) : m_tileLoader( tileLoader ), - m_textureProjection( tileLoader->tileProjection() ), // cache texture projection + m_textureProjection(tileLoader->tileProjectionType()), // cache texture projection m_tileSize( tileLoader->tileSize() ), // cache tile size m_tileLevel( tileLevel ), m_globalWidth( m_tileSize.width() * m_tileLoader->tileColumnCount( m_tileLevel ) ), m_globalHeight( m_tileSize.height() * m_tileLoader->tileRowCount( m_tileLevel ) ), m_normGlobalWidth( m_globalWidth / ( 2 * M_PI ) ), m_normGlobalHeight( m_globalHeight / M_PI ), m_tile( 0 ), m_tilePosX( 65535 ), m_tilePosY( 65535 ), m_toTileCoordinatesLon( 0.5 * m_globalWidth - m_tilePosX ), m_toTileCoordinatesLat( 0.5 * m_globalHeight - m_tilePosY ), m_prevLat( 0.0 ), m_prevLon( 0.0 ), m_prevPixelX( 0.0 ), m_prevPixelY( 0.0 ) { } void ScanlineTextureMapperContext::pixelValueF( const qreal lon, const qreal lat, QRgb* const scanLine ) { // The same method using integers performs about 33% faster. // However we need the qreal version to create the high quality mode. // Convert the lon and lat coordinates of the position on the scanline // measured in radian to the pixel position of the requested // coordinate on the current tile. m_prevPixelX = rad2PixelX( lon ); m_prevPixelY = rad2PixelY( lat ); qreal posX = m_toTileCoordinatesLon + m_prevPixelX; qreal posY = m_toTileCoordinatesLat + m_prevPixelY; // Most of the time while moving along the scanLine we'll stay on the // same tile. However at the tile border we might "fall off". If that // happens we need to find out the next tile that needs to be loaded. if ( posX >= (qreal)( m_tileSize.width() ) || posX < 0.0 || posY >= (qreal)( m_tileSize.height() ) || posY < 0.0 ) { nextTile( posX, posY ); } if ( m_tile ) { *scanLine = m_tile->pixelF( posX, posY ); } else { *scanLine = 0; } m_prevLon = lon; m_prevLat = lat; // preparing for interpolation } void ScanlineTextureMapperContext::pixelValue( const qreal lon, const qreal lat, QRgb* const scanLine ) { // The same method using integers performs about 33% faster. // However we need the qreal version to create the high quality mode. // Convert the lon and lat coordinates of the position on the scanline // measured in radian to the pixel position of the requested // coordinate on the current tile. m_prevPixelX = rad2PixelX( lon ); m_prevPixelY = rad2PixelY( lat ); int iPosX = (int)( m_toTileCoordinatesLon + m_prevPixelX ); int iPosY = (int)( m_toTileCoordinatesLat + m_prevPixelY ); // Most of the time while moving along the scanLine we'll stay on the // same tile. However at the tile border we might "fall off". If that // happens we need to find out the next tile that needs to be loaded. if ( iPosX >= m_tileSize.width() || iPosX < 0 || iPosY >= m_tileSize.height() || iPosY < 0 ) { nextTile( iPosX, iPosY ); } if ( m_tile ) { *scanLine = m_tile->pixel( iPosX, iPosY ); } else { *scanLine = 0; } m_prevLon = lon; m_prevLat = lat; // preparing for interpolation } // This method interpolates color values for skipped pixels in a scanline. // // While moving along the scanline we don't move from pixel to pixel but // leave out n pixels each time and calculate the exact position and // color value for the new pixel. The pixel values in between get // approximated through linear interpolation across the direct connecting // line on the original tiles directly. // This method will do by far most of the calculations for the // texturemapping, so we move towards integer math to improve speed. void ScanlineTextureMapperContext::pixelValueApproxF( const qreal lon, const qreal lat, QRgb *scanLine, const int n ) { // stepLon/Lat: Distance between two subsequent approximated positions qreal stepLat = lat - m_prevLat; qreal stepLon = lon - m_prevLon; // As long as the distance is smaller than 180 deg we can assume that // we didn't cross the dateline. const qreal nInverse = 1.0 / (qreal)(n); if ( fabs(stepLon) < M_PI ) { const qreal itStepLon = ( rad2PixelX( lon ) - m_prevPixelX ) * nInverse; const qreal itStepLat = ( rad2PixelY( lat ) - m_prevPixelY ) * nInverse; // To improve speed we unroll // AbstractScanlineTextureMapper::pixelValue(...) here and // calculate the performance critical issues via integers qreal itLon = m_prevPixelX + m_toTileCoordinatesLon; qreal itLat = m_prevPixelY + m_toTileCoordinatesLat; const int tileWidth = m_tileSize.width(); const int tileHeight = m_tileSize.height(); // int oldR = 0; // int oldG = 0; // int oldB = 0; QRgb oldRgb = qRgb( 0, 0, 0 ); qreal oldPosX = -1; qreal oldPosY = 0; const bool alwaysCheckTileRange = isOutOfTileRangeF( itLon, itLat, itStepLon, itStepLat, n ); for ( int j=1; j < n; ++j ) { qreal posX = itLon + itStepLon * j; qreal posY = itLat + itStepLat * j; if ( alwaysCheckTileRange ) if ( posX >= tileWidth || posX < 0.0 || posY >= tileHeight || posY < 0.0 ) { nextTile( posX, posY ); itLon = m_prevPixelX + m_toTileCoordinatesLon; itLat = m_prevPixelY + m_toTileCoordinatesLat; posX = qBound ( 0.0, (itLon + itStepLon * j), tileWidth-1.0 ); posY = qBound ( 0.0, (itLat + itStepLat * j), tileHeight-1.0 ); oldPosX = -1; } *scanLine = m_tile->pixelF( posX, posY ); // Just perform bilinear interpolation if there's a color change compared to the // last pixel that was evaluated. This speeds up things greatly for maps like OSM if ( *scanLine != oldRgb ) { if ( oldPosX != -1 ) { *(scanLine - 1) = m_tile->pixelF( oldPosX, oldPosY, *(scanLine - 1) ); oldPosX = -1; } oldRgb = m_tile->pixelF( posX, posY, *scanLine ); *scanLine = oldRgb; } else { oldPosX = posX; oldPosY = posY; } // if ( needsFilter( *scanLine, oldR, oldB, oldG ) ) { // *scanLine = m_tile->pixelF( posX, posY ); // } ++scanLine; } } // For the case where we cross the dateline between (lon, lat) and // (prevlon, prevlat) we need a more sophisticated calculation. // However as this will happen rather rarely, we use // pixelValue(...) directly to make the code more readable. else { stepLon = ( TWOPI - fabs(stepLon) ) * nInverse; stepLat = stepLat * nInverse; // We need to distinguish two cases: // crossing the dateline from east to west ... if ( m_prevLon < lon ) { for ( int j = 1; j < n; ++j ) { m_prevLat += stepLat; m_prevLon -= stepLon; if ( m_prevLon <= -M_PI ) m_prevLon += TWOPI; pixelValueF( m_prevLon, m_prevLat, scanLine ); ++scanLine; } } // ... and vice versa: from west to east. else { qreal curStepLon = lon - n * stepLon; for ( int j = 1; j < n; ++j ) { m_prevLat += stepLat; curStepLon += stepLon; qreal evalLon = curStepLon; if ( curStepLon <= -M_PI ) evalLon += TWOPI; pixelValueF( evalLon, m_prevLat, scanLine ); ++scanLine; } } } } bool ScanlineTextureMapperContext::isOutOfTileRangeF( const qreal itLon, const qreal itLat, const qreal itStepLon, const qreal itStepLat, const int n ) const { const qreal minIPosX = itLon + itStepLon; const qreal minIPosY = itLat + itStepLat; const qreal maxIPosX = itLon + itStepLon * ( n - 1 ); const qreal maxIPosY = itLat + itStepLat * ( n - 1 ); return ( maxIPosX >= m_tileSize.width() || maxIPosX < 0 || maxIPosY >= m_tileSize.height() || maxIPosY < 0 || minIPosX >= m_tileSize.width() || minIPosX < 0 || minIPosY >= m_tileSize.height() || minIPosY < 0 ); } void ScanlineTextureMapperContext::pixelValueApprox( const qreal lon, const qreal lat, QRgb *scanLine, const int n ) { // stepLon/Lat: Distance between two subsequent approximated positions qreal stepLat = lat - m_prevLat; qreal stepLon = lon - m_prevLon; // As long as the distance is smaller than 180 deg we can assume that // we didn't cross the dateline. const qreal nInverse = 1.0 / (qreal)(n); if ( fabs(stepLon) < M_PI ) { const int itStepLon = (int)( ( rad2PixelX( lon ) - m_prevPixelX ) * nInverse * 128.0 ); const int itStepLat = (int)( ( rad2PixelY( lat ) - m_prevPixelY ) * nInverse * 128.0 ); // To improve speed we unroll // AbstractScanlineTextureMapper::pixelValue(...) here and // calculate the performance critical issues via integers int itLon = (int)( ( m_prevPixelX + m_toTileCoordinatesLon ) * 128.0 ); int itLat = (int)( ( m_prevPixelY + m_toTileCoordinatesLat ) * 128.0 ); const int tileWidth = m_tileSize.width(); const int tileHeight = m_tileSize.height(); const bool alwaysCheckTileRange = isOutOfTileRange( itLon, itLat, itStepLon, itStepLat, n ); if ( !alwaysCheckTileRange ) { int iPosXf = itLon; int iPosYf = itLat; for ( int j = 1; j < n; ++j ) { iPosXf += itStepLon; iPosYf += itStepLat; *scanLine = m_tile->pixel( iPosXf >> 7, iPosYf >> 7 ); ++scanLine; } } else { for ( int j = 1; j < n; ++j ) { int iPosX = ( itLon + itStepLon * j ) >> 7; int iPosY = ( itLat + itStepLat * j ) >> 7; if ( iPosX >= tileWidth || iPosX < 0 || iPosY >= tileHeight || iPosY < 0 ) { nextTile( iPosX, iPosY ); itLon = (int)( ( m_prevPixelX + m_toTileCoordinatesLon ) * 128.0 ); itLat = (int)( ( m_prevPixelY + m_toTileCoordinatesLat ) * 128.0 ); iPosX = ( itLon + itStepLon * j ) >> 7; iPosY = ( itLat + itStepLat * j ) >> 7; } *scanLine = m_tile->pixel( iPosX, iPosY ); ++scanLine; } } } // For the case where we cross the dateline between (lon, lat) and // (prevlon, prevlat) we need a more sophisticated calculation. // However as this will happen rather rarely, we use // pixelValue(...) directly to make the code more readable. else { stepLon = ( TWOPI - fabs(stepLon) ) * nInverse; stepLat = stepLat * nInverse; // We need to distinguish two cases: // crossing the dateline from east to west ... if ( m_prevLon < lon ) { for ( int j = 1; j < n; ++j ) { m_prevLat += stepLat; m_prevLon -= stepLon; if ( m_prevLon <= -M_PI ) m_prevLon += TWOPI; pixelValue( m_prevLon, m_prevLat, scanLine ); ++scanLine; } } // ... and vice versa: from west to east. else { qreal curStepLon = lon - n * stepLon; for ( int j = 1; j < n; ++j ) { m_prevLat += stepLat; curStepLon += stepLon; qreal evalLon = curStepLon; if ( curStepLon <= -M_PI ) evalLon += TWOPI; pixelValue( evalLon, m_prevLat, scanLine ); ++scanLine; } } } } bool ScanlineTextureMapperContext::isOutOfTileRange( const int itLon, const int itLat, const int itStepLon, const int itStepLat, const int n ) const { const int minIPosX = ( itLon + itStepLon ) >> 7; const int minIPosY = ( itLat + itStepLat ) >> 7; const int maxIPosX = ( itLon + itStepLon * ( n - 1 ) ) >> 7; const int maxIPosY = ( itLat + itStepLat * ( n - 1 ) ) >> 7; return ( maxIPosX >= m_tileSize.width() || maxIPosX < 0 || maxIPosY >= m_tileSize.height() || maxIPosY < 0 || minIPosX >= m_tileSize.width() || minIPosX < 0 || minIPosY >= m_tileSize.height() || minIPosY < 0 ); } int ScanlineTextureMapperContext::interpolationStep( const ViewportParams *viewport, MapQuality mapQuality ) { if ( mapQuality == PrintQuality ) { return 1; // Don't interpolate for print quality. } if ( ! viewport->mapCoversViewport() ) { return 8; } // Find the optimal interpolation interval m_nBest for the // current image canvas width const int width = viewport->width(); int nBest = 2; int nEvalMin = width - 1; for ( int it = 1; it < 48; ++it ) { int nEval = ( width - 1 ) / it + ( width - 1 ) % it; if ( nEval < nEvalMin ) { nEvalMin = nEval; nBest = it; } } return nBest; } QImage::Format ScanlineTextureMapperContext::optimalCanvasImageFormat( const ViewportParams *viewport ) { // If the globe covers fully the screen then we can use the faster // RGB32 as there are no translucent areas involved. QImage::Format imageFormat = ( viewport->mapCoversViewport() ) ? QImage::Format_RGB32 : QImage::Format_ARGB32_Premultiplied; return imageFormat; } void ScanlineTextureMapperContext::nextTile( int &posX, int &posY ) { // Move from tile coordinates to global texture coordinates // ( with origin in upper left corner, measured in pixel) int lon = posX + m_tilePosX; if ( lon >= m_globalWidth ) lon -= m_globalWidth; else if ( lon < 0 ) lon += m_globalWidth; int lat = posY + m_tilePosY; if ( lat >= m_globalHeight ) lat -= m_globalHeight; else if ( lat < 0 ) lat += m_globalHeight; // tileCol counts the tile columns left from the current tile. // tileRow counts the tile rows on the top from the current tile. const int tileCol = lon / m_tileSize.width(); const int tileRow = lat / m_tileSize.height(); m_tile = m_tileLoader->loadTile( TileId( 0, m_tileLevel, tileCol, tileRow ) ); // Update position variables: // m_tilePosX/Y stores the position of the tiles in // global texture coordinates // ( origin upper left, measured in pixels ) m_tilePosX = tileCol * m_tileSize.width(); m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX); posX = lon - m_tilePosX; m_tilePosY = tileRow * m_tileSize.height(); m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY); posY = lat - m_tilePosY; } void ScanlineTextureMapperContext::nextTile( qreal &posX, qreal &posY ) { // Move from tile coordinates to global texture coordinates // ( with origin in upper left corner, measured in pixel) int lon = (int)(posX + m_tilePosX); if ( lon >= m_globalWidth ) lon -= m_globalWidth; else if ( lon < 0 ) lon += m_globalWidth; int lat = (int)(posY + m_tilePosY); if ( lat >= m_globalHeight ) lat -= m_globalHeight; else if ( lat < 0 ) lat += m_globalHeight; // tileCol counts the tile columns left from the current tile. // tileRow counts the tile rows on the top from the current tile. const int tileCol = lon / m_tileSize.width(); const int tileRow = lat / m_tileSize.height(); m_tile = m_tileLoader->loadTile( TileId( 0, m_tileLevel, tileCol, tileRow ) ); // Update position variables: // m_tilePosX/Y stores the position of the tiles in // global texture coordinates // ( origin upper left, measured in pixels ) m_tilePosX = tileCol * m_tileSize.width(); m_toTileCoordinatesLon = (qreal)(0.5 * m_globalWidth - m_tilePosX); posX = lon - m_tilePosX; m_tilePosY = tileRow * m_tileSize.height(); m_toTileCoordinatesLat = (qreal)(0.5 * m_globalHeight - m_tilePosY); posY = lat - m_tilePosY; } diff --git a/src/lib/marble/ScanlineTextureMapperContext.h b/src/lib/marble/ScanlineTextureMapperContext.h index e187b618a..bf35749f1 100644 --- a/src/lib/marble/ScanlineTextureMapperContext.h +++ b/src/lib/marble/ScanlineTextureMapperContext.h @@ -1,154 +1,154 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Andrew Manson // Copyright 2011 Bernhard Beschow #include #include "GeoSceneTileDataset.h" #include "MarbleMath.h" #include "MathHelper.h" namespace Marble { class StackedTile; class StackedTileLoader; class ViewportParams; class ScanlineTextureMapperContext { public: ScanlineTextureMapperContext( StackedTileLoader * const tileLoader, int tileLevel ); void pixelValueF( const qreal lon, const qreal lat, QRgb* const scanLine ); void pixelValue( const qreal lon, const qreal lat, QRgb* const scanLine ); void pixelValueApproxF( const qreal lon, const qreal lat, QRgb *scanLine, const int n ); void pixelValueApprox( const qreal lon, const qreal lat, QRgb *scanLine, const int n ); static int interpolationStep( const ViewportParams *viewport, MapQuality mapQuality ); static QImage::Format optimalCanvasImageFormat( const ViewportParams *viewport ); int globalWidth() const; int globalHeight() const; private: // method for fast integer calculation void nextTile( int& posx, int& posy ); // method for precise interpolation void nextTile( qreal& posx, qreal& posy ); // Converts Radian to global texture coordinates // ( with origin in center, measured in pixel) qreal rad2PixelX( const qreal lon ) const; qreal rad2PixelY( const qreal lat ) const; // Checks whether the pixelValueApprox method will make use of more than // one tile bool isOutOfTileRange( const int itLon, const int itLat, const int itStepLon, const int itStepLat, const int n ) const; bool isOutOfTileRangeF( const qreal itLon, const qreal itLat, const qreal itStepLon, const qreal itStepLat, const int n ) const; private: StackedTileLoader *const m_tileLoader; - GeoSceneTileDataset::Projection const m_textureProjection; + GeoSceneAbstractTileProjection::Type const m_textureProjection; /// size of the tiles of of the current texture layer QSize const m_tileSize; int const m_tileLevel; int const m_globalWidth; int const m_globalHeight; qreal const m_normGlobalWidth; qreal const m_normGlobalHeight; const StackedTile *m_tile; // Coordinate transformations: // Position of the tile in global Texture Coordinates // ( with origin in upper left corner, measured in pixel) int m_tilePosX; int m_tilePosY; // Converts global texture coordinates // ( with origin in center, measured in pixel) // to tile coordinates ( measured in pixel ) qreal m_toTileCoordinatesLon; qreal m_toTileCoordinatesLat; // Previous coordinates qreal m_prevLat; qreal m_prevLon; qreal m_prevPixelX; qreal m_prevPixelY; }; inline int ScanlineTextureMapperContext::globalWidth() const { return m_globalWidth; } inline int ScanlineTextureMapperContext::globalHeight() const { return m_globalHeight; } inline qreal ScanlineTextureMapperContext::rad2PixelX( const qreal lon ) const { return lon * m_normGlobalWidth; } inline qreal ScanlineTextureMapperContext::rad2PixelY( const qreal lat ) const { switch ( m_textureProjection ) { - case GeoSceneTileDataset::Equirectangular: + case GeoSceneAbstractTileProjection::Equirectangular: return -lat * m_normGlobalHeight; - case GeoSceneTileDataset::Mercator: + case GeoSceneAbstractTileProjection::Mercator: if ( fabs( lat ) < 1.4835 ) { // We develop the inverse Gudermannian into a MacLaurin Series: // In spite of the many elements needed to get decent // accuracy this is still faster by far than calculating the // trigonometric expression: // return - asinh( tan( lat ) ) * 0.5 * m_normGlobalHeight; // We are using the Horner Scheme as a polynom representation return - gdInv( lat ) * 0.5 * m_normGlobalHeight; } if ( lat >= +1.4835 ) // asinh( tan (1.4835)) => 3.1309587 return - 3.1309587 * 0.5 * m_normGlobalHeight; if ( lat <= -1.4835 ) // asinh( tan( -1.4835 )) => −3.1309587 return 3.1309587 * 0.5 * m_normGlobalHeight; } // Dummy value to avoid a warning. return 0.0; } } #endif diff --git a/src/lib/marble/ServerLayout.cpp b/src/lib/marble/ServerLayout.cpp index fa6c2ec86..91b09883d 100644 --- a/src/lib/marble/ServerLayout.cpp +++ b/src/lib/marble/ServerLayout.cpp @@ -1,237 +1,239 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2010,2011 Bernhard Beschow // // Own #include "ServerLayout.h" #include "GeoSceneTileDataset.h" #include "GeoDataLatLonBox.h" #include "MarbleGlobal.h" #include "TileId.h" #include #include namespace Marble { ServerLayout::ServerLayout( GeoSceneTileDataset *textureLayer ) : m_textureLayer( textureLayer ) { } ServerLayout::~ServerLayout() { } MarbleServerLayout::MarbleServerLayout( GeoSceneTileDataset *textureLayer ) : ServerLayout( textureLayer ) { } QUrl MarbleServerLayout::downloadUrl( const QUrl &prototypeUrl, const TileId &id ) const { const QString path = QString( "%1/%2/%3/%3_%4.%5" ) .arg( prototypeUrl.path() ) .arg( id.zoomLevel() ) .arg(id.y(), tileDigits, 10, QLatin1Char('0')) .arg(id.x(), tileDigits, 10, QLatin1Char('0')) .arg( m_textureLayer->fileFormat().toLower() ); QUrl url = prototypeUrl; url.setPath( path ); return url; } QString MarbleServerLayout::name() const { return "Marble"; } QString ServerLayout::sourceDir() const { return m_textureLayer ? m_textureLayer->sourceDir() : QString(); } OsmServerLayout::OsmServerLayout( GeoSceneTileDataset *textureLayer ) : ServerLayout( textureLayer ) { } QUrl OsmServerLayout::downloadUrl( const QUrl &prototypeUrl, const TileId &id ) const { const QString suffix = m_textureLayer->fileFormat().toLower(); const QString path = QString( "%1/%2/%3.%4" ).arg( id.zoomLevel() ) .arg( id.x() ) .arg( id.y() ) .arg( suffix ); QUrl url = prototypeUrl; url.setPath( url.path() + path ); return url; } QString OsmServerLayout::name() const { return "OpenStreetMap"; } CustomServerLayout::CustomServerLayout( GeoSceneTileDataset *texture ) : ServerLayout( texture ) { } QUrl CustomServerLayout::downloadUrl( const QUrl &prototypeUrl, const TileId &id ) const { - const GeoDataLatLonBox bbox = id.toLatLonBox( m_textureLayer ); + GeoDataLatLonBox bbox; + m_textureLayer->tileProjection()->geoCoordinates(id, bbox); QString urlStr = prototypeUrl.toString( QUrl::DecodeReserved ); urlStr.replace( "{zoomLevel}", QString::number( id.zoomLevel() ) ); urlStr.replace( "{x}", QString::number( id.x() ) ); urlStr.replace( "{y}", QString::number( id.y() ) ); urlStr.replace( "{west}", QString::number( bbox.west( GeoDataCoordinates::Degree ), 'f', 12 ) ); urlStr.replace( "{south}", QString::number( bbox.south( GeoDataCoordinates::Degree ), 'f', 12 ) ); urlStr.replace( "{east}", QString::number( bbox.east( GeoDataCoordinates::Degree ), 'f', 12 ) ); urlStr.replace( "{north}", QString::number( bbox.north( GeoDataCoordinates::Degree ), 'f', 12 ) ); return QUrl( urlStr ); } QString CustomServerLayout::name() const { return "Custom"; } WmsServerLayout::WmsServerLayout( GeoSceneTileDataset *texture ) : ServerLayout( texture ) { } QUrl WmsServerLayout::downloadUrl( const QUrl &prototypeUrl, const Marble::TileId &tileId ) const { - GeoDataLatLonBox box = tileId.toLatLonBox( m_textureLayer ); + GeoDataLatLonBox box; + m_textureLayer->tileProjection()->geoCoordinates(tileId, box); QUrlQuery url(prototypeUrl.query()); url.addQueryItem( "service", "WMS" ); url.addQueryItem( "request", "GetMap" ); url.addQueryItem( "version", "1.1.1" ); if ( !url.hasQueryItem( "styles" ) ) url.addQueryItem( "styles", "" ); if ( !url.hasQueryItem( "format" ) ) { if (m_textureLayer->fileFormat().toLower() == QLatin1String("jpg")) url.addQueryItem( "format", "image/jpeg" ); else url.addQueryItem("format", QLatin1String("image/") + m_textureLayer->fileFormat().toLower()); } if ( !url.hasQueryItem( "srs" ) ) { url.addQueryItem( "srs", epsgCode() ); } if ( !url.hasQueryItem( "layers" ) ) url.addQueryItem( "layers", m_textureLayer->name() ); url.addQueryItem( "width", QString::number( m_textureLayer->tileSize().width() ) ); url.addQueryItem( "height", QString::number( m_textureLayer->tileSize().height() ) ); url.addQueryItem( "bbox", QString( "%1,%2,%3,%4" ).arg( QString::number( box.west( GeoDataCoordinates::Degree ), 'f', 12 ) ) .arg( QString::number( box.south( GeoDataCoordinates::Degree ), 'f', 12 ) ) .arg( QString::number( box.east( GeoDataCoordinates::Degree ), 'f', 12 ) ) .arg( QString::number( box.north( GeoDataCoordinates::Degree ), 'f', 12 ) ) ); QUrl finalUrl = prototypeUrl; finalUrl.setQuery(url); return finalUrl; } QString WmsServerLayout::name() const { return "WebMapService"; } QString WmsServerLayout::epsgCode() const { - switch ( m_textureLayer->projection() ) { - case GeoSceneTileDataset::Equirectangular: + switch (m_textureLayer->tileProjectionType()) { + case GeoSceneAbstractTileProjection::Equirectangular: return "EPSG:4326"; - case GeoSceneTileDataset::Mercator: + case GeoSceneAbstractTileProjection::Mercator: return "EPSG:3785"; } Q_ASSERT( false ); // not reached return QString(); } QuadTreeServerLayout::QuadTreeServerLayout( GeoSceneTileDataset *textureLayer ) : ServerLayout( textureLayer ) { } QUrl QuadTreeServerLayout::downloadUrl( const QUrl &prototypeUrl, const Marble::TileId &id ) const { QString urlStr = prototypeUrl.toString( QUrl::DecodeReserved ); urlStr.replace( "{quadIndex}", encodeQuadTree( id ) ); return QUrl( urlStr ); } QString QuadTreeServerLayout::name() const { return "QuadTree"; } QString QuadTreeServerLayout::encodeQuadTree( const Marble::TileId &id ) { QString tileNum; for ( int i = id.zoomLevel(); i >= 0; i-- ) { const int tileX = (id.x() >> i) % 2; const int tileY = (id.y() >> i) % 2; const int num = ( 2 * tileY ) + tileX; tileNum += QString::number( num ); } return tileNum; } TmsServerLayout::TmsServerLayout(GeoSceneTileDataset *textureLayer ) : ServerLayout( textureLayer ) { } QUrl TmsServerLayout::downloadUrl( const QUrl &prototypeUrl, const TileId &id ) const { const QString suffix = m_textureLayer->fileFormat().toLower(); // y coordinate in TMS start at the bottom of the map (South) and go upwards, // opposed to OSM which start at the top. // // http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification int y_frombottom = ( 1< * Copyright 2007 Inge Wallin * Copyright 2008, 2009, 2010 Jens-Michael Hoffmann * Copyright 2010-2012 Bernhard Beschow * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "StackedTileLoader.h" #include "MarbleDebug.h" #include "MergedLayerDecorator.h" #include "StackedTile.h" #include "TileLoader.h" #include "TileLoaderHelper.h" #include "MarbleGlobal.h" #include #include #include #include namespace Marble { class StackedTileLoaderPrivate { public: explicit StackedTileLoaderPrivate( MergedLayerDecorator *mergedLayerDecorator ) : m_layerDecorator( mergedLayerDecorator ) { m_tileCache.setMaxCost( 20000 * 1024 ); // Cache size measured in bytes } MergedLayerDecorator *const m_layerDecorator; QHash m_tilesOnDisplay; QCache m_tileCache; QReadWriteLock m_cacheLock; }; StackedTileLoader::StackedTileLoader( MergedLayerDecorator *mergedLayerDecorator, QObject *parent ) : QObject( parent ), d( new StackedTileLoaderPrivate( mergedLayerDecorator ) ) { } StackedTileLoader::~StackedTileLoader() { qDeleteAll( d->m_tilesOnDisplay ); delete d; } int StackedTileLoader::tileColumnCount( int level ) const { return d->m_layerDecorator->tileColumnCount( level ); } int StackedTileLoader::tileRowCount( int level ) const { return d->m_layerDecorator->tileRowCount( level ); } -GeoSceneTileDataset::Projection StackedTileLoader::tileProjection() const +GeoSceneAbstractTileProjection::Type StackedTileLoader::tileProjectionType() const { - return d->m_layerDecorator->tileProjection(); + return d->m_layerDecorator->tileProjectionType(); } QSize StackedTileLoader::tileSize() const { return d->m_layerDecorator->tileSize(); } void StackedTileLoader::resetTilehash() { QHash::const_iterator it = d->m_tilesOnDisplay.constBegin(); QHash::const_iterator const end = d->m_tilesOnDisplay.constEnd(); for (; it != end; ++it ) { Q_ASSERT( it.value()->used() && "contained in m_tilesOnDisplay should imply used()" ); it.value()->setUsed( false ); } } void StackedTileLoader::cleanupTilehash() { // Make sure that tiles which haven't been used during the last // rendering of the map at all get removed from the tile hash. QHashIterator it( d->m_tilesOnDisplay ); while ( it.hasNext() ) { it.next(); if ( !it.value()->used() ) { // If insert call result is false then the cache is too small to store the tile // but the item will get deleted nevertheless and the pointer we have // doesn't get set to zero (so don't delete it in this case or it will crash!) d->m_tileCache.insert( it.key(), it.value(), it.value()->byteCount() ); d->m_tilesOnDisplay.remove( it.key() ); } } } const StackedTile* StackedTileLoader::loadTile( TileId const & stackedTileId ) { // check if the tile is in the hash d->m_cacheLock.lockForRead(); StackedTile * stackedTile = d->m_tilesOnDisplay.value( stackedTileId, 0 ); d->m_cacheLock.unlock(); if ( stackedTile ) { stackedTile->setUsed( true ); return stackedTile; } // here ends the performance critical section of this method d->m_cacheLock.lockForWrite(); // has another thread loaded our tile due to a race condition? stackedTile = d->m_tilesOnDisplay.value( stackedTileId, 0 ); if ( stackedTile ) { Q_ASSERT( stackedTile->used() && "other thread should have marked tile as used" ); d->m_cacheLock.unlock(); return stackedTile; } // the tile was not in the hash so check if it is in the cache stackedTile = d->m_tileCache.take( stackedTileId ); if ( stackedTile ) { Q_ASSERT( !stackedTile->used() && "tiles in m_tileCache are invisible and should thus be marked as unused" ); stackedTile->setUsed( true ); d->m_tilesOnDisplay[ stackedTileId ] = stackedTile; d->m_cacheLock.unlock(); return stackedTile; } // tile (valid) has not been found in hash or cache, so load it from disk // and place it in the hash from where it will get transferred to the cache mDebug() << "load tile from disk:" << stackedTileId; stackedTile = d->m_layerDecorator->loadTile( stackedTileId ); Q_ASSERT( stackedTile ); stackedTile->setUsed( true ); d->m_tilesOnDisplay[ stackedTileId ] = stackedTile; d->m_cacheLock.unlock(); emit tileLoaded( stackedTileId ); return stackedTile; } quint64 StackedTileLoader::volatileCacheLimit() const { return d->m_tileCache.maxCost() / 1024; } QList StackedTileLoader::visibleTiles() const { return d->m_tilesOnDisplay.keys(); } int StackedTileLoader::tileCount() const { return d->m_tileCache.count() + d->m_tilesOnDisplay.count(); } void StackedTileLoader::setVolatileCacheLimit( quint64 kiloBytes ) { mDebug() << QString("Setting tile cache to %1 kilobytes.").arg( kiloBytes ); d->m_tileCache.setMaxCost( kiloBytes * 1024 ); } void StackedTileLoader::updateTile( TileId const &tileId, QImage const &tileImage ) { const TileId stackedTileId( 0, tileId.zoomLevel(), tileId.x(), tileId.y() ); StackedTile * displayedTile = d->m_tilesOnDisplay.take( stackedTileId ); if ( displayedTile ) { Q_ASSERT( !d->m_tileCache.contains( stackedTileId ) ); StackedTile *const stackedTile = d->m_layerDecorator->updateTile( *displayedTile, tileId, tileImage ); stackedTile->setUsed( true ); d->m_tilesOnDisplay.insert( stackedTileId, stackedTile ); delete displayedTile; displayedTile = 0; emit tileLoaded( stackedTileId ); } else { d->m_tileCache.remove( stackedTileId ); } } RenderState StackedTileLoader::renderState() const { RenderState renderState( "Stacked Tiles" ); QHash::const_iterator it = d->m_tilesOnDisplay.constBegin(); QHash::const_iterator const end = d->m_tilesOnDisplay.constEnd(); for (; it != end; ++it ) { renderState.addChild( d->m_layerDecorator->renderState( it.key() ) ); } return renderState; } void StackedTileLoader::clear() { mDebug() << Q_FUNC_INFO; qDeleteAll( d->m_tilesOnDisplay ); d->m_tilesOnDisplay.clear(); d->m_tileCache.clear(); // clear the tile cache in physical memory emit cleared(); } } #include "moc_StackedTileLoader.cpp" diff --git a/src/lib/marble/StackedTileLoader.h b/src/lib/marble/StackedTileLoader.h index f5b1e143a..ddef3a458 100644 --- a/src/lib/marble/StackedTileLoader.h +++ b/src/lib/marble/StackedTileLoader.h @@ -1,149 +1,149 @@ /* * This file is part of the Marble Virtual Globe. * * Copyright 2005-2007 Torsten Rahn * Copyright 2007 Inge Wallin * Copyright 2009 Jens-Michael Hoffmann * Copyright 2010-2012 Bernhard Beschow * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MARBLE_STACKEDTILELOADER_H #define MARBLE_STACKEDTILELOADER_H #include #include "GeoSceneTextureTileDataset.h" #include "RenderState.h" class QImage; class QString; class QSize; namespace Marble { class MergedLayerDecorator; class StackedTile; class TileId; class StackedTileLoaderPrivate; /** * @short Tile loading from a quad tree * * This class loads tiles into memory. For faster access * we keep the tileIDs and their respective pointers to * the tiles in a hashtable. * The class also contains convenience methods to remove entries * from the hashtable and to return more detailed properties * about each tile level and their tiles. * * @author Torsten Rahn **/ class StackedTileLoader : public QObject { Q_OBJECT public: /** * Creates a new tile loader. * * @param downloadManager The download manager that shall be used to fetch * the tiles from a remote resource. */ explicit StackedTileLoader( MergedLayerDecorator *mergedLayerDecorator, QObject *parent = 0 ); virtual ~StackedTileLoader(); int tileColumnCount( int level ) const; int tileRowCount( int level ) const; - GeoSceneTextureTileDataset::Projection tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; QSize tileSize() const; /** * Loads a tile and returns it. * * @param stackedTileId The Id of the requested tile, containing the x and y coordinate * and the zoom level. */ const StackedTile* loadTile( TileId const &stackedTileId ); /** * Resets the internal tile hash. */ void resetTilehash(); /** * Cleans up the internal tile hash. * * Removes all superfluous tiles from the hash. */ void cleanupTilehash(); /** * @brief Returns the limit of the volatile (in RAM) cache. * @return the cache limit in kilobytes */ quint64 volatileCacheLimit() const; /** * @brief Reloads the tiles that are currently displayed. */ QList visibleTiles() const; /** * @brief Return the number of tiles in the cache. * @return number of tiles in cache */ int tileCount() const; /** * @brief Set the limit of the volatile (in RAM) cache. * @param bytes The limit in kilobytes. */ void setVolatileCacheLimit( quint64 kiloBytes ); /** * Effectively triggers a reload of all tiles that are currently in use * and clears the tile cache in physical memory. */ void clear(); /** */ void updateTile(TileId const & tileId, QImage const &tileImage ); RenderState renderState() const; Q_SIGNALS: void tileLoaded( TileId const &tileId ); void cleared(); private: Q_DISABLE_COPY( StackedTileLoader ) friend class StackedTileLoaderPrivate; StackedTileLoaderPrivate* const d; }; } #endif diff --git a/src/lib/marble/TileId.cpp b/src/lib/marble/TileId.cpp index a494288ec..228a1f07b 100644 --- a/src/lib/marble/TileId.cpp +++ b/src/lib/marble/TileId.cpp @@ -1,132 +1,74 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008, 2010 Jens-Michael Hoffmann // Copyright 2012 Bernhard Beschow // // Own #include "TileId.h" -#include "MarbleMath.h" -#include "GeoDataLatLonBox.h" -#include "GeoSceneTileDataset.h" +#include "GeoDataCoordinates.h" #include namespace Marble { TileId::TileId( QString const & mapThemeId, int zoomLevel, int tileX, int tileY ) : m_mapThemeIdHash( qHash( mapThemeId )), m_zoomLevel( zoomLevel ), m_tileX( tileX ), m_tileY( tileY ) { } TileId::TileId( uint mapThemeIdHash, int zoomLevel, int tileX, int tileY ) : m_mapThemeIdHash( mapThemeIdHash ), m_zoomLevel( zoomLevel ), m_tileX( tileX ), m_tileY( tileY ) { } TileId::TileId() : m_mapThemeIdHash( 0 ), m_zoomLevel( 0 ), m_tileX( 0 ), m_tileY( 0 ) { } -GeoDataLatLonBox TileId::toLatLonBox( const GeoSceneTileDataset *textureLayer ) const -{ - - qreal radius = ( 1 << zoomLevel() ) * textureLayer->levelZeroColumns() / 2.0; - - qreal lonLeft = ( x() - radius ) / radius * M_PI; - qreal lonRight = ( x() - radius + 1 ) / radius * M_PI; - - radius = ( 1 << zoomLevel() ) * textureLayer->levelZeroRows() / 2.0; - qreal latTop = 0; - qreal latBottom = 0; - - switch ( textureLayer->projection() ) { - case GeoSceneTileDataset::Equirectangular: - latTop = ( radius - y() ) / radius * M_PI / 2.0; - latBottom = ( radius - y() - 1 ) / radius * M_PI / 2.0; - break; - case GeoSceneTileDataset::Mercator: - latTop = atan( sinh( ( radius - y() ) / radius * M_PI ) ); - latBottom = atan( sinh( ( radius - y() - 1 ) / radius * M_PI ) ); - break; - } - - return GeoDataLatLonBox( latTop, latBottom, lonRight, lonLeft ); -} - TileId TileId::fromCoordinates(const GeoDataCoordinates &coords, int zoomLevel) { if ( zoomLevel < 0 ) { return TileId(); } const int maxLat = 90 * 1000000; const int maxLon = 180 * 1000000; int lat = GeoDataCoordinates::normalizeLat( coords.latitude( GeoDataCoordinates::Degree ), GeoDataCoordinates::Degree ) * 1000000; int lon = GeoDataCoordinates::normalizeLon( coords.longitude( GeoDataCoordinates::Degree ), GeoDataCoordinates::Degree ) * 1000000; int x = 0; int y = 0; for( int i=0; i> i; if( lat <= ( maxLat - deltaLat )) { y += 1<<(zoomLevel-i-1); lat += deltaLat; } const int deltaLon = maxLon >> i; if( lon >= ( maxLon - deltaLon )) { x += 1<<(zoomLevel-i-1); } else { lon += deltaLon; } } return TileId(0, zoomLevel, x, y); } -unsigned int TileId::lon2tileX( qreal lon, unsigned int maxTileX ) -{ - return (unsigned int)floor(0.5 * (lon / M_PI + 1.0) * maxTileX); -} - -unsigned int TileId::lat2tileY( qreal latitude, unsigned int maxTileY ) -{ - // We need to calculate the tile position from the latitude - // projected using the Mercator projection. This requires the inverse Gudermannian - // function which is only defined between -85°S and 85°N. Therefore in order to - // prevent undefined results we need to restrict our calculation: - qreal maxAbsLat = 85.0 * DEG2RAD; - qreal lat = (qAbs(latitude) > maxAbsLat) ? latitude/qAbs(latitude) * maxAbsLat : latitude; - return (unsigned int)floor(0.5 * (1.0 - gdInv(lat) / M_PI) * maxTileY); -} - - -qreal TileId::tileX2lon( unsigned int x, unsigned int maxTileX ) -{ - return ( (2*M_PI * x) / maxTileX - M_PI ); -} - -qreal TileId::tileY2lat( unsigned int y, unsigned int maxTileY ) -{ - return gd(M_PI * (1.0 - (2.0 * y) / maxTileY)); -} - - - - } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<( QDebug dbg, const Marble::TileId &id ) { return dbg << QString( "Marble::TileId(%1, %2, %3, %4)" ).arg( id.mapThemeIdHash() ) .arg( id.zoomLevel() ) .arg( id.x() ) .arg( id.y() ); } #endif diff --git a/src/lib/marble/TileId.h b/src/lib/marble/TileId.h index 04fcab0da..2e2721c40 100644 --- a/src/lib/marble/TileId.h +++ b/src/lib/marble/TileId.h @@ -1,123 +1,115 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008, 2010 Jens-Michael Hoffmann // Copyright 2012 Bernhard Beschow // #ifndef MARBLE_TILEID_H #define MARBLE_TILEID_H #include "marble_export.h" #include class QString; namespace Marble { class GeoDataCoordinates; -class GeoSceneTileDataset; -class GeoDataLatLonBox; class MARBLE_EXPORT TileId { public: TileId( QString const & mapThemeId, int zoomLevel, int tileX, int tileY ); TileId( uint mapThemeIdHash, int zoomLevel, int tileX, int tileY ); TileId(); int zoomLevel() const; int x() const; int y() const; uint mapThemeIdHash() const; bool operator==( TileId const& rhs ) const; bool operator<( TileId const& rhs ) const; - GeoDataLatLonBox toLatLonBox( const GeoSceneTileDataset *textureLayer ) const; static TileId fromCoordinates( const GeoDataCoordinates& coords, int zoomLevel ); - static unsigned int lon2tileX( qreal lon, unsigned int maxTileX ); - static unsigned int lat2tileY( qreal lat, unsigned int maxTileY ); - static qreal tileX2lon( unsigned int x, unsigned int maxTileX ); - static qreal tileY2lat( unsigned int y, unsigned int maxTileY ); - private: uint m_mapThemeIdHash; int m_zoomLevel; int m_tileX; int m_tileY; }; uint qHash( TileId const& ); // inline definitions inline int TileId::zoomLevel() const { return m_zoomLevel; } inline int TileId::x() const { return m_tileX; } inline int TileId::y() const { return m_tileY; } inline uint TileId::mapThemeIdHash() const { return m_mapThemeIdHash; } inline bool TileId::operator==( TileId const& rhs ) const { return m_zoomLevel == rhs.m_zoomLevel && m_tileX == rhs.m_tileX && m_tileY == rhs.m_tileY && m_mapThemeIdHash == rhs.m_mapThemeIdHash; } inline bool TileId::operator<( TileId const& rhs ) const { if (m_zoomLevel < rhs.m_zoomLevel) return true; else if (m_zoomLevel == rhs.m_zoomLevel && m_tileX < rhs.m_tileX) return true; else if (m_zoomLevel == rhs.m_zoomLevel && m_tileX == rhs.m_tileX && m_tileY < rhs.m_tileY) return true; else if (m_zoomLevel == rhs.m_zoomLevel && m_tileX == rhs.m_tileX && m_tileY == rhs.m_tileY && m_mapThemeIdHash < rhs.m_mapThemeIdHash) return true; return false; } inline uint qHash( TileId const& tid ) { const quint64 tmp = (( quint64 )( tid.zoomLevel() ) << 36 ) + (( quint64 )( tid.x() ) << 18 ) + ( quint64 )( tid.y() ); return ::qHash( tmp ) ^ tid.mapThemeIdHash(); } } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<( QDebug, const Marble::TileId & ); #endif #endif diff --git a/src/lib/marble/VectorTileModel.cpp b/src/lib/marble/VectorTileModel.cpp index 4e4c7b43d..5ef5d989c 100644 --- a/src/lib/marble/VectorTileModel.cpp +++ b/src/lib/marble/VectorTileModel.cpp @@ -1,240 +1,243 @@ /* This file is part of the Marble Virtual Globe. This program is free software licensed under the GNU LGPL. You can find a copy of this license in LICENSE.txt in the top directory of the source code. Copyright 2012 Ander Pijoan Copyright 2013 Bernhard Beschow */ #include "VectorTileModel.h" #include "GeoDataDocument.h" #include "GeoDataLatLonBox.h" #include "GeoDataTreeModel.h" #include "GeoDataTypes.h" #include "GeoSceneVectorTileDataset.h" #include "MarbleGlobal.h" #include "MarbleDebug.h" #include "MathHelper.h" #include "MarbleMath.h" #include "TileId.h" #include "TileLoader.h" #include #include namespace Marble { TileRunner::TileRunner( TileLoader *loader, const GeoSceneVectorTileDataset *texture, const TileId &id ) : m_loader( loader ), m_texture( texture ), m_id( id ) { } void TileRunner::run() { GeoDataDocument *const document = m_loader->loadTileVectorData( m_texture, m_id, DownloadBrowse ); emit documentLoaded( m_id, document ); } VectorTileModel::CacheDocument::CacheDocument(GeoDataDocument *doc, VectorTileModel *vectorTileModel, const GeoDataLatLonBox &boundingBox) : m_document( doc ), m_vectorTileModel(vectorTileModel), m_boundingBox(boundingBox) { // nothing to do } VectorTileModel::CacheDocument::~CacheDocument() { m_vectorTileModel->removeTile(m_document); } VectorTileModel::VectorTileModel( TileLoader *loader, const GeoSceneVectorTileDataset *layer, GeoDataTreeModel *treeModel, QThreadPool *threadPool ) : m_loader( loader ), m_layer( layer ), m_treeModel( treeModel ), m_threadPool( threadPool ), m_tileLoadLevel( -1 ), m_tileZoomLevel(-1), m_deleteDocumentsLater(false) { connect(this, SIGNAL(tileAdded(GeoDataDocument*)), treeModel, SLOT(addDocument(GeoDataDocument*)) ); connect(this, SIGNAL(tileRemoved(GeoDataDocument*)), treeModel, SLOT(removeDocument(GeoDataDocument*)) ); connect(treeModel, SIGNAL(removed(GeoDataObject*)), this, SLOT(cleanupTile(GeoDataObject*)) ); } void VectorTileModel::setViewport( const GeoDataLatLonBox &latLonBox, int radius ) { // choose the smaller dimension for selecting the tile level, leading to higher-resolution results const int levelZeroWidth = m_layer->tileSize().width() * m_layer->levelZeroColumns(); const int levelZeroHight = m_layer->tileSize().height() * m_layer->levelZeroRows(); const int levelZeroMinDimension = qMin( levelZeroWidth, levelZeroHight ); qreal linearLevel = ( 4.0 * (qreal)( radius ) / (qreal)( levelZeroMinDimension ) ); if ( linearLevel < 1.0 ) linearLevel = 1.0; // Dirty fix for invalid entry linearLevel // As our tile resolution doubles with each level we calculate // the tile level from tilesize and the globe radius via log(2) qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ); int tileZoomLevel = (int)( tileLevelF * 1.00001 ); // snap to the sharper tile level a tiny bit earlier // to work around rounding errors when the radius // roughly equals the global texture width m_tileZoomLevel = tileZoomLevel; // Determine available tile levels in the layer and thereby // select the tileZoomLevel that is actually used: QVector tileLevels = m_layer->tileLevels(); if (tileLevels.isEmpty() /* || tileZoomLevel < tileLevels.first() */) { // if there is no (matching) tile level then show nothing // and bail out. m_documents.clear(); return; } int tileLevel = tileLevels.first(); for (int i=1, n=tileLevels.size(); i tileZoomLevel) { break; } tileLevel = tileLevels[i]; } tileZoomLevel = tileLevel; // if zoom level has changed, empty vectortile cache if ( tileZoomLevel != m_tileLoadLevel ) { m_tileLoadLevel = tileZoomLevel; m_deleteDocumentsLater = true; } - const unsigned int maxTileX = ( 1 << tileZoomLevel ) * m_layer->levelZeroColumns(); - const unsigned int maxTileY = ( 1 << tileZoomLevel ) * m_layer->levelZeroRows(); - /** LOGIC FOR DOWNLOADING ALL THE TILES THAT ARE INSIDE THE SCREEN AT THE CURRENT ZOOM LEVEL **/ // New tiles X and Y for moved screen coordinates // More info: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Subtiles // More info: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#C.2FC.2B.2B - // Sometimes the formula returns wrong huge values, x and y have to be between 0 and 2^ZoomLevel - unsigned int westX = qBound( 0, TileId::lon2tileX( latLonBox.west(), maxTileX ), maxTileX); - unsigned int northY = qBound( 0, TileId::lat2tileY( latLonBox.north(), maxTileY ), maxTileY); - unsigned int eastX = qBound( 0, TileId::lon2tileX( latLonBox.east(), maxTileX ), maxTileX); - unsigned int southY = qBound( 0, TileId::lat2tileY( latLonBox.south(), maxTileY ), maxTileY ); + int westX; + int northY; + int eastX; + int southY; + m_layer->tileProjection()->tileIndexes(latLonBox, tileZoomLevel, westX, northY, eastX, southY); // Download tiles and send them to VectorTileLayer // When changing zoom, download everything inside the screen + // TODO: hardcodes assumption about tiles indexing also ends at dateline + // TODO: what about crossing things in y direction? if ( !latLonBox.crossesDateLine() ) { queryTiles( tileZoomLevel, westX, northY, eastX, southY ); } // When only moving screen, just download the new tiles else { + // TODO: maxTileX (calculation knowledge) should be a property of tileProjection or m_layer + const unsigned int maxTileX = (1 << tileZoomLevel) * m_layer->levelZeroColumns() - 1; + queryTiles( tileZoomLevel, 0, northY, eastX, southY ); queryTiles( tileZoomLevel, westX, northY, maxTileX, southY ); } removeTilesOutOfView(latLonBox); } void VectorTileModel::removeTilesOutOfView(const GeoDataLatLonBox &boundingBox) { GeoDataLatLonBox const extendedViewport = boundingBox.scaled(2.0, 2.0); for (auto iter = m_documents.begin(); iter != m_documents.end();) { bool const isOutOfView = !extendedViewport.intersects(iter.value()->m_boundingBox); if (isOutOfView) { iter = m_documents.erase(iter); } else { ++iter; } } } QString VectorTileModel::name() const { return m_layer->name(); } void VectorTileModel::removeTile(GeoDataDocument *document) { emit tileRemoved(document); } int VectorTileModel::tileZoomLevel() const { return m_tileZoomLevel; } int VectorTileModel::cachedDocuments() const { return m_documents.size(); } void VectorTileModel::updateTile( const TileId &id, GeoDataDocument *document ) { m_pendingDocuments.removeAll(id); if (!document) { return; } if ( m_tileLoadLevel != id.zoomLevel() ) { delete document; return; } document->setName(QString("%1/%2/%3").arg(id.zoomLevel()).arg(id.x()).arg(id.y())); m_garbageQueue << document; if (m_documents.contains(id)) { m_documents.remove(id); } if (m_deleteDocumentsLater) { m_deleteDocumentsLater = false; m_documents.clear(); } - GeoDataLatLonBox const boundingBox = id.toLatLonBox(m_layer); + GeoDataLatLonBox boundingBox; + m_layer->tileProjection()->geoCoordinates(id, boundingBox); m_documents[id] = QSharedPointer(new CacheDocument(document, this, boundingBox)); emit tileAdded(document); } void VectorTileModel::clear() { m_documents.clear(); } void VectorTileModel::queryTiles( int tileZoomLevel, unsigned int minTileX, unsigned int minTileY, unsigned int maxTileX, unsigned int maxTileY ) { // Download all the tiles inside the given indexes for ( unsigned int x = minTileX; x <= maxTileX; ++x ) { for ( unsigned int y = minTileY; y <= maxTileY; ++y ) { const TileId tileId = TileId( 0, tileZoomLevel, x, y ); if ( !m_documents.contains( tileId ) && !m_pendingDocuments.contains( tileId ) ) { m_pendingDocuments << tileId; TileRunner *job = new TileRunner( m_loader, m_layer, tileId ); connect( job, SIGNAL(documentLoaded(TileId,GeoDataDocument*)), this, SLOT(updateTile(TileId,GeoDataDocument*)) ); m_threadPool->start( job ); } } } } void VectorTileModel::cleanupTile(GeoDataObject *object) { if (object->nodeType() == GeoDataTypes::GeoDataDocumentType) { GeoDataDocument* document = static_cast(object); if (m_garbageQueue.contains(document)) { m_garbageQueue.removeAll(document); delete document; } } } } #include "moc_VectorTileModel.cpp" diff --git a/src/lib/marble/geodata/CMakeLists.txt b/src/lib/marble/geodata/CMakeLists.txt index 187d54842..add79b2c6 100644 --- a/src/lib/marble/geodata/CMakeLists.txt +++ b/src/lib/marble/geodata/CMakeLists.txt @@ -1,493 +1,496 @@ SET ( geodata_data_SRCS geodata/data/LonLatParser.cpp geodata/data/GeoDataRegion.cpp geodata/data/GeoDataUpdate.cpp geodata/data/GeoDataViewVolume.cpp geodata/data/GeoDataHotSpot.cpp geodata/data/GeoDataAlias.cpp geodata/data/GeoDataImagePyramid.cpp geodata/data/GeoDataGeometry.cpp geodata/data/GeoDataPoint.cpp geodata/data/GeoDataPhotoOverlay.cpp geodata/data/GeoDataTimePrimitive.cpp geodata/data/GeoDataVec2.cpp geodata/data/GeoDataBalloonStyle.cpp geodata/data/GeoDataNetworkLink.cpp geodata/data/GeoDataLineStyle.cpp geodata/data/GeoDataScreenOverlay.cpp geodata/data/GeoDataContainer.cpp geodata/data/GeoDataItemIcon.cpp geodata/data/GeoDataTour.cpp geodata/data/GeoDataOverlay.cpp geodata/data/GeoDataListStyle.cpp geodata/data/GeoDataFlyTo.cpp geodata/data/GeoDataMultiTrack.cpp geodata/data/GeoDataSnippet.cpp geodata/data/GeoDataStyle.cpp geodata/data/GeoDataLinearRing.cpp geodata/data/GeoDataFolder.cpp geodata/data/GeoDataDocument.cpp geodata/data/GeoDataLatLonAltBox.cpp geodata/data/GeoDataStyleSelector.cpp geodata/data/GeoDataLod.cpp geodata/data/GeoDataColorStyle.cpp geodata/data/GeoDataData.cpp geodata/data/GeoDataLocation.cpp geodata/data/GeoDataPolygon.cpp geodata/data/GeoDataLineString.cpp geodata/data/GeoDataOrientation.cpp geodata/data/GeoDataLookAt.cpp geodata/data/GeoDataPlacemark.cpp geodata/data/GeoDataPlaylist.cpp geodata/data/GeoDataPolyStyle.cpp geodata/data/GeoDataModel.cpp geodata/data/GeoDataLink.cpp geodata/data/GeoDataIconStyle.cpp geodata/data/GeoDataAbstractView.cpp geodata/data/GeoDataStyleMap.cpp geodata/data/GeoDataExtendedData.cpp geodata/data/GeoDataLabelStyle.cpp geodata/data/GeoDataTimeSpan.cpp geodata/data/GeoDataMultiGeometry.cpp geodata/data/GeoDataSimpleArrayData.cpp geodata/data/GeoDataObject.cpp geodata/data/GeoDataLatLonQuad.cpp geodata/data/GeoDataCoordinates.cpp geodata/data/GeoDataTrack.cpp geodata/data/GeoDataNetworkLinkControl.cpp geodata/data/GeoDataFeature.cpp geodata/data/GeoDataCamera.cpp geodata/data/GeoDataTimeStamp.cpp geodata/data/GeoDataGroundOverlay.cpp geodata/data/GeoDataLatLonBox.cpp geodata/data/GeoDataScale.cpp geodata/data/GeoDataResourceMap.cpp geodata/data/GeoDataTourControl.cpp geodata/data/GeoDataAccuracy.cpp geodata/data/GeoDataWait.cpp geodata/data/GeoDataSoundCue.cpp geodata/data/GeoDataAnimatedUpdate.cpp geodata/data/GeoDataSchema.cpp geodata/data/GeoDataSimpleField.cpp geodata/data/GeoDataChange.cpp geodata/data/GeoDataCreate.cpp geodata/data/GeoDataDelete.cpp geodata/data/GeoDataSchemaData.cpp geodata/data/GeoDataSimpleData.cpp ) SET ( geodata_scene_SRCS + geodata/scene/GeoSceneAbstractTileProjection.cpp + geodata/scene/GeoSceneMercatorTileProjection.cpp + geodata/scene/GeoSceneEquirectTileProjection.cpp geodata/scene/GeoSceneIcon.cpp geodata/scene/GeoSceneTileDataset.cpp geodata/scene/GeoSceneVectorTileDataset.cpp geodata/scene/GeoSceneGeodata.cpp geodata/scene/GeoSceneGroup.cpp geodata/scene/GeoSceneZoom.cpp geodata/scene/GeoSceneLegend.cpp geodata/scene/GeoSceneTextureTileDataset.cpp geodata/scene/GeoSceneAbstractDataset.cpp geodata/scene/GeoSceneItem.cpp geodata/scene/GeoSceneLicense.cpp geodata/scene/GeoSceneSection.cpp geodata/scene/GeoSceneFilter.cpp geodata/scene/GeoSceneHead.cpp geodata/scene/GeoSceneVector.cpp geodata/scene/GeoSceneSettings.cpp geodata/scene/GeoSceneDocument.cpp geodata/scene/GeoSceneMap.cpp geodata/scene/GeoSceneProperty.cpp geodata/scene/GeoSceneLayer.cpp geodata/scene/GeoScenePalette.cpp ) # handlers and writers sources SET ( geodata_handlers_dgml_SRCS geodata/handlers/dgml/DgmlFilterTagHandler.h geodata/handlers/dgml/DgmlHeadingTagHandler.h geodata/handlers/dgml/DgmlDocumentTagHandler.cpp geodata/handlers/dgml/DgmlMinimumTagHandler.cpp geodata/handlers/dgml/DgmlHeadTagHandler.h geodata/handlers/dgml/DgmlLayerTagHandler.cpp geodata/handlers/dgml/DgmlMinimumTagHandler.h geodata/handlers/dgml/DgmlTargetTagHandler.cpp geodata/handlers/dgml/DgmlSourceFileTagHandler.h geodata/handlers/dgml/DgmlSourceFileTagHandler.cpp geodata/handlers/dgml/DgmlGeodataTagHandler.h geodata/handlers/dgml/DgmlDownloadPolicyTagHandler.h geodata/handlers/dgml/DgmlProjectionTagHandler.cpp geodata/handlers/dgml/DgmlLegendTagHandler.cpp geodata/handlers/dgml/DgmlTargetTagHandler.h geodata/handlers/dgml/DgmlAuxillaryDictionary.cpp geodata/handlers/dgml/DgmlGeodataTagHandler.cpp geodata/handlers/dgml/DgmlBlendingTagHandler.h geodata/handlers/dgml/DgmlSourceDirTagHandler.cpp geodata/handlers/dgml/DgmlValueTagHandler.h geodata/handlers/dgml/DgmlMapTagHandler.h geodata/handlers/dgml/DgmlPropertyTagHandler.h geodata/handlers/dgml/DgmlAttributeDictionary.h geodata/handlers/dgml/DgmlThemeTagHandler.cpp geodata/handlers/dgml/DgmlInstallMapTagHandler.h geodata/handlers/dgml/DgmlPropertyTagHandler.cpp geodata/handlers/dgml/DgmlAvailableTagHandler.h geodata/handlers/dgml/DgmlMaximumTagHandler.h geodata/handlers/dgml/DgmlHeadTagHandler.cpp geodata/handlers/dgml/DgmlLegendTagHandler.h geodata/handlers/dgml/DgmlProjectionTagHandler.h geodata/handlers/dgml/DgmlAttributeDictionary.cpp geodata/handlers/dgml/DgmlInstallMapTagHandler.cpp geodata/handlers/dgml/DgmlVectorTagHandler.h geodata/handlers/dgml/DgmlDiscreteTagHandler.cpp geodata/handlers/dgml/DgmlNameTagHandler.cpp geodata/handlers/dgml/DgmlLicenseTagHandler.h geodata/handlers/dgml/DgmlAvailableTagHandler.cpp geodata/handlers/dgml/DgmlStorageLayoutTagHandler.h geodata/handlers/dgml/DgmlThemeTagHandler.h geodata/handlers/dgml/DgmlSettingsTagHandler.h geodata/handlers/dgml/DgmlDescriptionTagHandler.h geodata/handlers/dgml/DgmlBrushTagHandler.cpp geodata/handlers/dgml/DgmlSectionTagHandler.h geodata/handlers/dgml/DgmlTextureTagHandler.h geodata/handlers/dgml/DgmlDownloadUrlTagHandler.cpp geodata/handlers/dgml/DgmlBlendingTagHandler.cpp geodata/handlers/dgml/DgmlBrushTagHandler.h geodata/handlers/dgml/DgmlZoomTagHandler.h geodata/handlers/dgml/DgmlVectorTagHandler.cpp geodata/handlers/dgml/DgmlDiscreteTagHandler.h geodata/handlers/dgml/DgmlMapTagHandler.cpp geodata/handlers/dgml/DgmlVectortileTagHandler.cpp geodata/handlers/dgml/DgmlVisibleTagHandler.cpp geodata/handlers/dgml/DgmlHeadingTagHandler.cpp geodata/handlers/dgml/DgmlTileSizeTagHandler.h geodata/handlers/dgml/DgmlPenTagHandler.h geodata/handlers/dgml/DgmlZoomTagHandler.cpp geodata/handlers/dgml/DgmlTileSizeTagHandler.cpp geodata/handlers/dgml/DgmlTextureTagHandler.cpp geodata/handlers/dgml/DgmlPenTagHandler.cpp geodata/handlers/dgml/DgmlDescriptionTagHandler.cpp geodata/handlers/dgml/DgmlElementDictionary.h geodata/handlers/dgml/DgmlIconTagHandler.cpp geodata/handlers/dgml/DgmlValueTagHandler.cpp geodata/handlers/dgml/DgmlLicenseTagHandler.cpp geodata/handlers/dgml/DgmlTextTagHandler.cpp geodata/handlers/dgml/DgmlMaximumTagHandler.cpp geodata/handlers/dgml/DgmlPaletteTagHandler.cpp geodata/handlers/dgml/DgmlGroupTagHandler.cpp geodata/handlers/dgml/DgmlDownloadUrlTagHandler.h geodata/handlers/dgml/DgmlItemTagHandler.h geodata/handlers/dgml/DgmlSourceDirTagHandler.h geodata/handlers/dgml/DgmlGroupTagHandler.h geodata/handlers/dgml/DgmlStorageLayoutTagHandler.cpp geodata/handlers/dgml/DgmlTextTagHandler.h geodata/handlers/dgml/DgmlFilterTagHandler.cpp geodata/handlers/dgml/DgmlVisibleTagHandler.h geodata/handlers/dgml/DgmlSectionTagHandler.cpp geodata/handlers/dgml/DgmlDownloadPolicyTagHandler.cpp geodata/handlers/dgml/DgmlIconTagHandler.h geodata/handlers/dgml/DgmlNameTagHandler.h geodata/handlers/dgml/DgmlDocumentTagHandler.h geodata/handlers/dgml/DgmlPaletteTagHandler.h geodata/handlers/dgml/DgmlVectortileTagHandler.h geodata/handlers/dgml/DgmlElementDictionary.cpp geodata/handlers/dgml/DgmlItemTagHandler.cpp geodata/handlers/dgml/DgmlSettingsTagHandler.cpp geodata/handlers/dgml/DgmlAuxillaryDictionary.h geodata/handlers/dgml/DgmlLayerTagHandler.h geodata/handlers/dgml/DgmlRenderOrderTagHandler.cpp geodata/handlers/dgml/DgmlRenderOrderTagHandler.h ) SET ( geodata_writers_dgml_SRCS geodata/writers/dgml/DgmlSectionTagWriter.h geodata/writers/dgml/DgmlSettingsTagWriter.cpp geodata/writers/dgml/DgmlTagWriter.h geodata/writers/dgml/DgmlItemTagWriter.h geodata/writers/dgml/DgmlSettingsTagWriter.h geodata/writers/dgml/DgmlMapTagWriter.cpp geodata/writers/dgml/DgmlSectionTagWriter.cpp geodata/writers/dgml/DgmlGeodataTagWriter.h geodata/writers/dgml/DgmlVectorTagWriter.h geodata/writers/dgml/DgmlGeodataTagWriter.cpp geodata/writers/dgml/DgmlLayerTagWriter.cpp geodata/writers/dgml/DgmlDocumentTagWriter.cpp geodata/writers/dgml/DgmlTextureTagWriter.h geodata/writers/dgml/DgmlMapTagWriter.h geodata/writers/dgml/DgmlVectorTagWriter.cpp geodata/writers/dgml/DgmlDocumentTagWriter.h geodata/writers/dgml/DgmlHeadTagWriter.cpp geodata/writers/dgml/DgmlLegendTagWriter.cpp geodata/writers/dgml/DgmlLegendTagWriter.h geodata/writers/dgml/DgmlHeadTagWriter.h geodata/writers/dgml/DgmlTextureTagWriter.cpp geodata/writers/dgml/DgmlTagWriter.cpp geodata/writers/dgml/DgmlLayerTagWriter.h geodata/writers/dgml/DgmlItemTagWriter.cpp ) SET( geodata_graphicsitem_SRCS geodata/graphicsitem/GeoLineStringGraphicsItem.cpp geodata/graphicsitem/GeoPhotoGraphicsItem.cpp geodata/graphicsitem/GeoPointGraphicsItem.cpp geodata/graphicsitem/GeoPolygonGraphicsItem.cpp geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp geodata/graphicsitem/GeoTrackGraphicsItem.cpp geodata/graphicsitem/ScreenOverlayGraphicsItem.cpp ) SET ( geodata_handlers_kml_SRCS geodata/handlers/kml/KmlAltitudeModeTagHandler.cpp geodata/handlers/kml/KmlAltitudeTagHandler.cpp geodata/handlers/kml/KmlAnimatedUpdateTagHandler.cpp geodata/handlers/kml/KmlAreaTagHandler.cpp geodata/handlers/kml/KmlBalloonStyleTagHandler.cpp geodata/handlers/kml/KmlBeginTagHandler.cpp geodata/handlers/kml/KmlBgColorTagHandler.cpp geodata/handlers/kml/KmlCameraTagHandler.cpp geodata/handlers/kml/KmlChangeTagHandler.cpp geodata/handlers/kml/KmlColorModeTagHandler.cpp geodata/handlers/kml/KmlColorTagHandler.cpp geodata/handlers/kml/KmlCoordinatesTagHandler.cpp geodata/handlers/kml/KmlCountrycodeTagHandler.cpp geodata/handlers/kml/KmlCountryNameCodeTagHandler.cpp geodata/handlers/kml/KmlDataTagHandler.cpp geodata/handlers/kml/KmlDescriptionTagHandler.cpp geodata/handlers/kml/KmlDisplayModeTagHandler.cpp geodata/handlers/kml/KmlDisplayNameTagHandler.cpp geodata/handlers/kml/KmlDocumentTagHandler.cpp geodata/handlers/kml/KmlDurationTagHandler.cpp geodata/handlers/kml/KmldrawOrderTagHandler.cpp geodata/handlers/kml/KmlEastTagHandler.cpp geodata/handlers/kml/KmlElementDictionary.cpp geodata/handlers/kml/KmlEndTagHandler.cpp geodata/handlers/kml/KmlExtendedDataTagHandler.cpp geodata/handlers/kml/KmlExtrudeTagHandler.cpp geodata/handlers/kml/KmlFillTagHandler.cpp geodata/handlers/kml/KmlFlyToModeTagHandler.cpp geodata/handlers/kml/KmlFlyToTagHandler.cpp geodata/handlers/kml/KmlFlyToViewTagHandler.cpp geodata/handlers/kml/KmlFolderTagHandler.cpp geodata/handlers/kml/KmlGroundOverlayTagHandler.cpp geodata/handlers/kml/KmlGxAltitudeModeTagHandler.cpp geodata/handlers/kml/KmlGxTimeSpanTagHandler.cpp geodata/handlers/kml/KmlGxTimeStampTagHandler.cpp geodata/handlers/kml/KmlHeadingTagHandler.cpp geodata/handlers/kml/KmlHotSpotTagHandler.cpp geodata/handlers/kml/KmlHrefTagHandler.cpp geodata/handlers/kml/KmlHttpQueryTagHandler.cpp geodata/handlers/kml/KmlIconStyleTagHandler.cpp geodata/handlers/kml/KmlIconTagHandler.cpp geodata/handlers/kml/KmlInnerBoundaryIsTagHandler.cpp geodata/handlers/kml/KmlItemIconTagHandler.cpp geodata/handlers/kml/KmlKeyTagHandler.cpp geodata/handlers/kml/KmlLabelStyleTagHandler.cpp geodata/handlers/kml/KmlLatitudeTagHandler.cpp geodata/handlers/kml/KmlLatLonAltBoxTagHandler.cpp geodata/handlers/kml/KmlLatLonBoxTagHandler.cpp geodata/handlers/kml/KmlLatLonQuadTagHandler.cpp geodata/handlers/kml/KmlLinearRingTagHandler.cpp geodata/handlers/kml/KmlLineStringTagHandler.cpp geodata/handlers/kml/KmlLineStyleTagHandler.cpp geodata/handlers/kml/KmlLinkTagHandler.cpp geodata/handlers/kml/KmlListItemTypeTagHandler.cpp geodata/handlers/kml/KmlListStyleTagHandler.cpp geodata/handlers/kml/KmlLodTagHandler.cpp geodata/handlers/kml/KmlLongitudeTagHandler.cpp geodata/handlers/kml/KmlLookAtTagHandler.cpp geodata/handlers/kml/KmlMarblePlacemarkTagHandler.cpp geodata/handlers/kml/KmlMaxAltitudeTagHandler.cpp geodata/handlers/kml/KmlMaxFadeExtentTagHandler.cpp geodata/handlers/kml/KmlMaxLodPixelsTagHandler.cpp geodata/handlers/kml/KmlMinAltitudeTagHandler.cpp geodata/handlers/kml/KmlMinFadeExtentTagHandler.cpp geodata/handlers/kml/KmlMinLodPixelsTagHandler.cpp geodata/handlers/kml/KmlModelTagHandler.cpp geodata/handlers/kml/KmlMultiGeometryTagHandler.cpp geodata/handlers/kml/KmlMultiTrackTagHandler.cpp geodata/handlers/kml/KmlNameTagHandler.cpp geodata/handlers/kml/KmlNetworkLinkTagHandler.cpp geodata/handlers/kml/KmlNorthTagHandler.cpp geodata/handlers/kml/KmlObjectTagHandler.cpp geodata/handlers/kml/KmlOpenTagHandler.cpp geodata/handlers/kml/KmlOuterBoundaryIsTagHandler.cpp geodata/handlers/kml/KmlOutlineTagHandler.cpp geodata/handlers/kml/KmlOverlayXYTagHandler.cpp geodata/handlers/kml/KmlPairTagHandler.cpp geodata/handlers/kml/KmlPhotoOverlayTagHandler.cpp geodata/handlers/kml/KmlPlacemarkTagHandler.cpp geodata/handlers/kml/KmlPlaylistTagHandler.cpp geodata/handlers/kml/KmlPointTagHandler.cpp geodata/handlers/kml/KmlPolygonTagHandler.cpp geodata/handlers/kml/KmlPolyStyleTagHandler.cpp geodata/handlers/kml/KmlPopTagHandler.cpp geodata/handlers/kml/KmlRangeTagHandler.cpp geodata/handlers/kml/KmlRefreshIntervalTagHandler.cpp geodata/handlers/kml/KmlRefreshModeTagHandler.cpp geodata/handlers/kml/KmlRefreshVisibilityTagHandler.cpp geodata/handlers/kml/KmlRegionTagHandler.cpp geodata/handlers/kml/KmlRoleTagHandler.cpp geodata/handlers/kml/KmlRollTagHandler.cpp geodata/handlers/kml/KmlRotationTagHandler.cpp geodata/handlers/kml/KmlRotationXYTagHandler.cpp geodata/handlers/kml/Kml_scaleTagHandler.cpp geodata/handlers/kml/KmlSchemaDataTagHandler.cpp geodata/handlers/kml/KmlSchemaTagHandler.cpp geodata/handlers/kml/KmlScreenOverlayTagHandler.cpp geodata/handlers/kml/KmlScreenXYTagHandler.cpp geodata/handlers/kml/KmlSimpleArrayDataTagHandler.cpp geodata/handlers/kml/KmlSimpleDataTagHandler.cpp geodata/handlers/kml/KmlSimpleFieldTagHandler.cpp geodata/handlers/kml/KmlSizeTagHandler.cpp geodata/handlers/kml/KmlSouthTagHandler.cpp geodata/handlers/kml/KmlStateTagHandler.cpp geodata/handlers/kml/KmlStyleMapTagHandler.cpp geodata/handlers/kml/KmlStyleTagHandler.cpp geodata/handlers/kml/KmlStyleUrlTagHandler.cpp geodata/handlers/kml/KmlTessellateTagHandler.cpp geodata/handlers/kml/KmlTextColorTagHandler.cpp geodata/handlers/kml/KmlTextTagHandler.cpp geodata/handlers/kml/KmlTiltTagHandler.cpp geodata/handlers/kml/KmlTimeSpanTagHandler.cpp geodata/handlers/kml/KmlTimeStampTagHandler.cpp geodata/handlers/kml/KmlTourTagHandler.cpp geodata/handlers/kml/KmlTourControlTagHandler.cpp geodata/handlers/kml/KmlTrackTagHandler.cpp geodata/handlers/kml/KmlValueTagHandler.cpp geodata/handlers/kml/KmlViewBoundScaleTagHandler.cpp geodata/handlers/kml/KmlVisibilityTagHandler.cpp geodata/handlers/kml/KmlWaitTagHandler.cpp geodata/handlers/kml/KmlWestTagHandler.cpp geodata/handlers/kml/KmlWhenTagHandler.cpp geodata/handlers/kml/KmlWidthTagHandler.cpp geodata/handlers/kml/KmlViewFormatTagHandler.cpp geodata/handlers/kml/KmlViewRefreshModeTagHandler.cpp geodata/handlers/kml/KmlViewRefreshTimeTagHandler.cpp geodata/handlers/kml/KmlViewVolumeTagHandler.cpp geodata/handlers/kml/KmlLeftFovTagHandler.cpp geodata/handlers/kml/KmlRightFovTagHandler.cpp geodata/handlers/kml/KmlBottomFovTagHandler.cpp geodata/handlers/kml/KmlTopFovTagHandler.cpp geodata/handlers/kml/KmlNearTagHandler.cpp geodata/handlers/kml/KmlImagePyramidTagHandler.cpp geodata/handlers/kml/KmlTileSizeTagHandler.cpp geodata/handlers/kml/KmlMaxHeightTagHandler.cpp geodata/handlers/kml/KmlMaxWidthTagHandler.cpp geodata/handlers/kml/KmlGridOriginTagHandler.cpp geodata/handlers/kml/KmlShapeTagHandler.cpp geodata/handlers/kml/KmlMinRefreshPeriodTagHandler.cpp geodata/handlers/kml/KmlMaxSessionLengthTagHandler.cpp geodata/handlers/kml/KmlCookieTagHandler.cpp geodata/handlers/kml/KmlMessageTagHandler.cpp geodata/handlers/kml/KmlLinkNameTagHandler.cpp geodata/handlers/kml/KmlLinkDescriptionTagHandler.cpp geodata/handlers/kml/KmlLinkSnippetTagHandler.cpp geodata/handlers/kml/KmlSnippetTagHandler.cpp geodata/handlers/kml/KmlExpiresTagHandler.cpp geodata/handlers/kml/KmlUpdateTagHandler.cpp geodata/handlers/kml/KmlNetworkLinkControlTagHandler.cpp geodata/handlers/kml/KmlplayModeTagHandler.cpp geodata/handlers/kml/KmlOrientationTagHandler.cpp geodata/handlers/kml/KmlScaleTagHandler.cpp geodata/handlers/kml/KmlXTagHandler.cpp geodata/handlers/kml/KmlYTagHandler.cpp geodata/handlers/kml/KmlZTagHandler.cpp geodata/handlers/kml/KmlLocationTagHandler.cpp geodata/handlers/kml/KmlResourceMapTagHandler.cpp geodata/handlers/kml/KmlAliasTagHandler.cpp geodata/handlers/kml/KmlSourceHrefTagHandler.cpp geodata/handlers/kml/KmlTargetHrefTagHandler.cpp geodata/handlers/kml/KmlSoundCueTagHandler.cpp geodata/handlers/kml/KmldelayedStartTagHandler.cpp geodata/handlers/kml/KmlBalloonVisibilityTagHandler.cpp geodata/handlers/kml/KmlCreateTagHandler.cpp geodata/handlers/kml/KmlDeleteTagHandler.cpp geodata/handlers/kml/KmlOsmPlacemarkDataTagHandler.cpp geodata/handlers/kml/KmlTagTagHandler.cpp geodata/handlers/kml/KmlMemberTagHandler.cpp geodata/handlers/kml/KmlNdTagHandler.cpp ) SET ( geodata_writers_kml_SRCS geodata/writers/kml/KmlAnimatedUpdateTagWriter.cpp geodata/writers/kml/KmlBalloonStyleTagWriter.cpp geodata/writers/kml/KmlCameraTagWriter.cpp geodata/writers/kml/KmlColorStyleTagWriter.cpp geodata/writers/kml/KmlDataTagWriter.cpp geodata/writers/kml/KmlDocumentTagWriter.cpp geodata/writers/kml/KmlExtendedDataTagWriter.cpp geodata/writers/kml/KmlFeatureTagWriter.cpp geodata/writers/kml/KmlFolderTagWriter.cpp geodata/writers/kml/KmlFlyToTagWriter.cpp geodata/writers/kml/KmlGroundOverlayWriter.cpp geodata/writers/kml/KmlIconStyleTagWriter.cpp geodata/writers/kml/KmlLatLonAltBoxWriter.cpp geodata/writers/kml/KmlLatLonBoxWriter.cpp geodata/writers/kml/KmlLatLonQuadWriter.cpp geodata/writers/kml/KmlLabelStyleTagWriter.cpp geodata/writers/kml/KmlLinearRingTagWriter.cpp geodata/writers/kml/KmlLineStringTagWriter.cpp geodata/writers/kml/KmlLineStyleTagWriter.cpp geodata/writers/kml/KmlLinkTagWriter.cpp geodata/writers/kml/KmlListStyleTagWriter.cpp geodata/writers/kml/KmlLodTagWriter.cpp geodata/writers/kml/KmlLookAtTagWriter.cpp geodata/writers/kml/KmlModelTagWriter.cpp geodata/writers/kml/KmlMultiGeometryTagWriter.cpp geodata/writers/kml/KmlMultiTrackTagWriter.cpp geodata/writers/kml/KmlNetworkLinkTagWriter.cpp geodata/writers/kml/KmlNetworkLinkControlTagWriter.cpp geodata/writers/kml/KmlObjectTagWriter.cpp geodata/writers/kml/KmlOverlayTagWriter.cpp geodata/writers/kml/KmlPhotoOverlayWriter.cpp geodata/writers/kml/KmlPlacemarkTagWriter.cpp geodata/writers/kml/KmlPlaylistTagWriter.cpp geodata/writers/kml/KmlPointTagWriter.cpp geodata/writers/kml/KmlPolygonTagWriter.cpp geodata/writers/kml/KmlPolyStyleTagWriter.cpp geodata/writers/kml/KmlRegionTagWriter.cpp geodata/writers/kml/KmlSchemaTagWriter.cpp geodata/writers/kml/KmlSchemaDataTagWriter.cpp geodata/writers/kml/KmlSimpleDataTagWriter.cpp geodata/writers/kml/KmlSimpleFieldTagWriter.cpp geodata/writers/kml/KmlScreenOverlayWriter.cpp geodata/writers/kml/KmlStyleMapTagWriter.cpp geodata/writers/kml/KmlStyleTagWriter.cpp geodata/writers/kml/KmlTagWriter.cpp geodata/writers/kml/KmlTimeSpanWriter.cpp geodata/writers/kml/KmlTimeStampTagWriter.cpp geodata/writers/kml/KmlTourTagWriter.cpp geodata/writers/kml/KmlTrackWriter.cpp geodata/writers/kml/KmlUpdateTagWriter.cpp geodata/writers/kml/KmlOsmPlacemarkDataTagWriter.cpp ) # writer and the parser sources SET ( geodata_parser_SRCS geodata/parser/GeoDataParser.cpp geodata/parser/GeoDataTypes.cpp geodata/parser/GeoDocument.cpp geodata/parser/GeoParser.cpp geodata/parser/GeoSceneParser.cpp geodata/parser/GeoSceneTypes.cpp geodata/parser/GeoTagHandler.cpp ) SET( geodata_writer_SRCS geodata/writer/GeoTagWriter.cpp geodata/writer/GeoWriter.cpp geodata/writer/GeoWriterBackend.cpp geodata/writer/GeoDataDocumentWriter.cpp ) SET( geodata_SRCS ${geodata_data_SRCS} ${geodata_graphicsitem_SRCS} ${geodata_scene_SRCS} ${geodata_parser_SRCS} ${geodata_writer_SRCS} ${geodata_handlers_kml_SRCS} ${geodata_handlers_dgml_SRCS} ${geodata_writers_kml_SRCS} ${geodata_writers_dgml_SRCS} ) #add_subdirectory(geodata/data/tests) diff --git a/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp b/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp index 7aef84200..e38bd7d20 100644 --- a/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp +++ b/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp @@ -1,64 +1,65 @@ /* Copyright (C) 2008 Jens-Michael Hoffmann This file is part of the KDE project This library is free software you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License aint with this library see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "DgmlProjectionTagHandler.h" // Marble #include "DgmlAttributeDictionary.h" #include "DgmlElementDictionary.h" #include "GeoParser.h" #include "GeoSceneTileDataset.h" namespace Marble { namespace dgml { DGML_DEFINE_TAG_HANDLER(Projection) GeoNode* DgmlProjectionTagHandler::parse( GeoParser& parser ) const { // Check whether the tag is valid Q_ASSERT(parser.isStartElement() && parser.isValidElement(QLatin1String(dgmlTag_Projection))); // Checking for parent item GeoStackItem parentItem = parser.parentElement(); if ( !parentItem.represents( dgmlTag_Texture ) && !parentItem.represents( dgmlTag_Vectortile)) return 0; // Attribute name, default to "Equirectangular" const QString nameStr = parser.attribute( dgmlAttr_name ).trimmed(); if ( !nameStr.isEmpty() ) { - GeoSceneTileDataset::Projection projection = GeoSceneTileDataset::Equirectangular; - if (nameStr == QLatin1String("Equirectangular")) - projection = GeoSceneTileDataset::Equirectangular; - else if (nameStr == QLatin1String("Mercator")) - projection = GeoSceneTileDataset::Mercator; - else + GeoSceneAbstractTileProjection::Type tileProjectionType = GeoSceneAbstractTileProjection::Equirectangular; + if (nameStr == QLatin1String("Equirectangular")) { + tileProjectionType = GeoSceneAbstractTileProjection::Equirectangular; + } else if (nameStr == QLatin1String("Mercator")) { + tileProjectionType = GeoSceneAbstractTileProjection::Mercator; + } else { parser.raiseWarning( QString( "Value not allowed for attribute name: %1" ).arg( nameStr )); + } - parentItem.nodeAs()->setProjection( projection ); + parentItem.nodeAs()->setTileProjection(tileProjectionType); } return 0; } } } diff --git a/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.cpp b/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.cpp new file mode 100644 index 000000000..48f6e304d --- /dev/null +++ b/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.cpp @@ -0,0 +1,72 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "GeoSceneAbstractTileProjection.h" + +namespace Marble +{ + +class GeoSceneAbstractTileProjectionPrivate +{ +public: + GeoSceneAbstractTileProjectionPrivate(); + +public: + int levelZeroColumns; + int levelZeroRows; +}; + +GeoSceneAbstractTileProjectionPrivate::GeoSceneAbstractTileProjectionPrivate() + : levelZeroColumns(1) + , levelZeroRows(1) +{ +} + +GeoSceneAbstractTileProjection::GeoSceneAbstractTileProjection() + : d_ptr(new GeoSceneAbstractTileProjectionPrivate()) +{ +} + +GeoSceneAbstractTileProjection::~GeoSceneAbstractTileProjection() +{ +} + +int GeoSceneAbstractTileProjection::levelZeroColumns() const +{ + return d_ptr->levelZeroColumns; +} + +void GeoSceneAbstractTileProjection::setLevelZeroColumns(int levelZeroColumns) +{ + d_ptr->levelZeroColumns = levelZeroColumns; +} + +int GeoSceneAbstractTileProjection::levelZeroRows() const +{ + return d_ptr->levelZeroRows; +} + +void GeoSceneAbstractTileProjection::setLevelZeroRows(int levelZeroRows) +{ + d_ptr->levelZeroRows = levelZeroRows; +} + +} diff --git a/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.h b/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.h new file mode 100644 index 000000000..d909b4752 --- /dev/null +++ b/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.h @@ -0,0 +1,145 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MARBLE_GEOSCENEABSTRACTTILEPROJECTION_H +#define MARBLE_GEOSCENEABSTRACTTILEPROJECTION_H + +#include "geodata_export.h" +#include + +#include + +namespace Marble +{ + +class GeoSceneAbstractTileProjectionPrivate; + +class GeoDataLatLonBox; + +/** + * @short A base class for projections between tile indizes and geo coordinates in Marble. + * + * For map tiling with indizes in x and y dimensions and 1 or multiple zoomlevels. + * The lowest zoomlevel is 0. + */ +class GEODATA_EXPORT GeoSceneAbstractTileProjection +{ +public: + enum Type { Equirectangular, Mercator }; + + /** + * @brief Construct a new GeoSceneAbstractTileProjection. + */ + GeoSceneAbstractTileProjection(); + + virtual ~GeoSceneAbstractTileProjection(); + +public: + virtual GeoSceneAbstractTileProjection::Type type() const = 0; + + /** + * @return the number of tiles on level 0 in x dimension + */ + int levelZeroColumns() const; + /** + * @brief Sets the number of tiles on level 0 in x dimension + * + * @param levelZeroColumns new number of tiles on level 0 in x dimension + * + * Default value of the levelZeroColumns property is 1. + */ + void setLevelZeroColumns(int levelZeroColumns); + + /** + * @return the number of tiles on level 0 in y dimension + */ + int levelZeroRows() const; + /** + * @brief Sets the number of tiles on level 0 in y dimension + * + * @param levelZeroColumns new number of tiles on level 0 in y dimension + * + * Default value of the levelZeroRows property is 1. + */ + void setLevelZeroRows(int levelZeroRows); + + /** + * @brief Get the tile indexes which cover the given geographical box. + * If @p latLonBox or @p zoomLevel have values out-of-bounds, the behaviour is undefined. + * + * @param latLonBox the geo coordinates of the requested tiles + * @param zoomLevel the zoomlevel of the requested tiles + * @param westX the x index of the tiles covering the western boundary is returned through this parameter + * @param northY the y index of the tiles covering the northern boundary is returned through this parameter + * @param eastX the x index of the tiles covering the eastern boundary is returned through this parameter + * @param southY the y index of the tiles covering the southern boundary is returned through this parameter + */ + virtual void tileIndexes(const GeoDataLatLonBox& latLonBox, int zoomLevel, + int& westX, int& northY, int& eastX, int& southY) const = 0; + + /** + * @brief Get the north-west geo coordinates corresponding to a tile. + * If @p x, @p y or @p zoomLevel have values out-of-bounds, the behaviour is undefined. + * + * @param zoomLevel the zoomlevel of the tile + * @param x the x index of the tile + * @param y the y index of the tile + * @param westernTileEdgeLon the longitude angle in radians of the western tile edge tis returned through this parameter + * @param northernTileEdgeLat the latitude angle in radians of the northern tile edge is returned through this parameter + */ + virtual void geoCoordinates(int zoomLevel, + int x, int y, + qreal& westernTileEdgeLon, qreal& northernTileEdgeLat) const = 0; + + /** + * @brief Get the boundary geo coordinates corresponding to a tile. + * If @p x, @p y or @p zoomLevel have values out-of-bounds, the behaviour is undefined. + * + * @param zoomLevel the zoomlevel of the tile + * @param x the x index of the tile + * @param y the y index of the tile + * @param latLonBox the boundary geo coordinates are set to this GeoDataLatLonBox + */ + virtual void geoCoordinates(int zoomLevel, + int x, int y, + GeoDataLatLonBox& latLonBox) const = 0; + + /** + * @brief Get the boundary geo coordinates corresponding to a tile. + * If @p tildId has values out-of-bounds, the behaviour is undefined. + * + * @param tileId the id of the tile + * @param latLonBox the boundary geo coordinates are set to this GeoDataLatLonBox + */ + void geoCoordinates(const TileId& tileId, + GeoDataLatLonBox& latLonBox) const + { + geoCoordinates(tileId.zoomLevel(), tileId.x(), tileId.y(), latLonBox); + } + + private: + Q_DISABLE_COPY(GeoSceneAbstractTileProjection) + const QScopedPointer d_ptr; +}; + +} + +#endif diff --git a/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.cpp b/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.cpp new file mode 100644 index 000000000..182b1fc13 --- /dev/null +++ b/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.cpp @@ -0,0 +1,161 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "GeoSceneEquirectTileProjection.h" + +#include +#include + +namespace Marble +{ + +GeoSceneEquirectTileProjection::GeoSceneEquirectTileProjection() +{ +} + + +GeoSceneEquirectTileProjection::~GeoSceneEquirectTileProjection() +{ +} + +GeoSceneAbstractTileProjection::Type GeoSceneEquirectTileProjection::type() const +{ + return Equirectangular; +} + + +static inline +unsigned int lowerBoundTileIndex(qreal baseTileIndex) +{ + const qreal floorBaseTileIndex = floor(baseTileIndex); + unsigned int tileIndex = static_cast(floorBaseTileIndex); + return (baseTileIndex == floorBaseTileIndex) ? tileIndex-1 : tileIndex; +} + +static inline +unsigned int upperBoundTileIndex(qreal baseTileIndex) +{ + return (unsigned int)floor(baseTileIndex); +} + +static inline +qreal baseTileXFromLon(qreal lon, unsigned int tileCount) +{ + return 0.5 * (lon / M_PI + 1.0) * tileCount; +} + +static inline +qreal baseTileYFromLat(qreal lat, unsigned int tileCount) +{ + return (0.5 - lat / M_PI) * tileCount; +} + + +// on tile borders selects the tile to the east +static inline +unsigned int eastBoundTileXFromLon(qreal lon, unsigned int tileCount) +{ + // special casing tile-map end + if (lon == M_PI) { + return 0; + } + return upperBoundTileIndex(baseTileXFromLon(lon, tileCount)); +} + +// on tile borders selects the tile to the west +static inline +unsigned int westBoundTileXFromLon(qreal lon, unsigned int tileCount) +{ + // special casing tile-map end + if (lon == -M_PI) { + return tileCount-1; + } + return lowerBoundTileIndex(baseTileXFromLon(lon, tileCount)); +} + +// on tile borders selects the tile to the south +static inline +unsigned int southBoundTileYFromLat(qreal lat, unsigned int tileCount) +{ + // special casing tile-map end + if (lat == -M_PI*0.5) { + return 0; + } + return upperBoundTileIndex(baseTileYFromLat(lat, tileCount)); +} + +// on tile borders selects the tile to the north +static inline +unsigned int northBoundTileYFromLat(qreal lat, unsigned int tileCount) +{ + // special casing tile-map end + if (lat == M_PI*0.5) { + return tileCount-1; + } + return lowerBoundTileIndex(baseTileYFromLat(lat, tileCount)); +} + + +void GeoSceneEquirectTileProjection::tileIndexes(const GeoDataLatLonBox& latLonBox, int zoomLevel, + int& westX, int& northY, int& eastX, int& southY) const +{ + const unsigned int xTileCount = (1 << zoomLevel) * levelZeroColumns(); + + westX = eastBoundTileXFromLon(latLonBox.west(), xTileCount); + eastX = westBoundTileXFromLon(latLonBox.east(), xTileCount); + + const unsigned int yTileCount = (1 << zoomLevel) * levelZeroRows(); + + northY = southBoundTileYFromLat(latLonBox.north(), yTileCount); + southY = northBoundTileYFromLat(latLonBox.south(), yTileCount); +} + +void GeoSceneEquirectTileProjection::geoCoordinates(int zoomLevel, + int x, int y, + qreal& westernTileEdgeLon, qreal& northernTileEdgeLat) const +{ + qreal radius = (1 << zoomLevel) * levelZeroColumns() / 2.0; + + westernTileEdgeLon = (x - radius ) / radius * M_PI; + + radius = (1 << zoomLevel) * levelZeroRows() / 2.0; + + northernTileEdgeLat = (radius - y) / radius * M_PI / 2.0; +} + +void GeoSceneEquirectTileProjection::geoCoordinates(int zoomLevel, + int x, int y, + GeoDataLatLonBox& latLonBox) const +{ + qreal radius = (1 << zoomLevel) * levelZeroColumns() / 2.0; + + qreal lonLeft = (x - radius ) / radius * M_PI; + qreal lonRight = (x - radius + 1 ) / radius * M_PI; + + radius = (1 << zoomLevel) * levelZeroRows() / 2.0; + + qreal latTop = (radius - y) / radius * M_PI / 2.0; + qreal latBottom = (radius - y - 1) / radius * M_PI / 2.0; + + latLonBox.setBoundaries(latTop, latBottom, lonRight, lonLeft); +} + +} diff --git a/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.h b/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.h new file mode 100644 index 000000000..cbd2bbdc0 --- /dev/null +++ b/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.h @@ -0,0 +1,85 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MARBLE_GEOSCENEEQUIRECTTILEPROJECTION_H +#define MARBLE_GEOSCENEEQUIRECTTILEPROJECTION_H + +#include "GeoSceneAbstractTileProjection.h" + +namespace Marble +{ + +/** + * Convertes the x and y indizes of tiles to and from geo coordinates. + * For tiles of maps in Equirectangular projection. + * + * Tiles do have the same width and the same height per zoomlevel. + * The number of tiles per dimension is twice that of the previous lower zoomlevel. + * The indexing is done in x dimension eastwards, with the first tiles beginning at -180 degree + * and an x value of 0 and the last tiles ending at +180 degree, + * in y dimension southwards with the first tiles beginning at +90 degree and a y value of 0 + * and the last tiles ending at -90 degree. + */ +class GEODATA_EXPORT GeoSceneEquirectTileProjection : public GeoSceneAbstractTileProjection +{ +public: + /** + * @brief Construct a new GeoSceneEquirectTileProjection. + */ + GeoSceneEquirectTileProjection(); + + ~GeoSceneEquirectTileProjection() override; + +public: + /** + * @copydoc + */ + GeoSceneAbstractTileProjection::Type type() const override; + + /** + * @copydoc + */ + void tileIndexes(const GeoDataLatLonBox& latLonBox, int zoomLevel, + int& westX, int& northY, int& eastX, int& southY) const override; + + /** + * @copydoc + */ + void geoCoordinates(int zoomLevel, + int x, int y, + qreal& westernTileEdgeLon, qreal& northernTileEdgeLat) const override; + + /** + * @copydoc + */ + void geoCoordinates(int zoomLevel, + int x, int y, + GeoDataLatLonBox& latLonBox) const override; + + using GeoSceneAbstractTileProjection::geoCoordinates; + +private: + Q_DISABLE_COPY(GeoSceneEquirectTileProjection) +}; + +} + +#endif diff --git a/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.cpp b/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.cpp new file mode 100644 index 000000000..2a107b7a2 --- /dev/null +++ b/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.cpp @@ -0,0 +1,182 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "GeoSceneMercatorTileProjection.h" + +#include +#include + + +namespace Marble +{ + +GeoSceneMercatorTileProjection::GeoSceneMercatorTileProjection() +{ +} + + +GeoSceneMercatorTileProjection::~GeoSceneMercatorTileProjection() +{ +} + +GeoSceneAbstractTileProjection::Type GeoSceneMercatorTileProjection::type() const +{ + return Mercator; +} + + +static inline +unsigned int lowerBoundTileIndex(qreal baseTileIndex) +{ + const qreal floorBaseTileIndex = floor(baseTileIndex); + unsigned int tileIndex = static_cast(floorBaseTileIndex); + return (baseTileIndex == floorBaseTileIndex) ? tileIndex-1 : tileIndex; +} + +static inline +unsigned int upperBoundTileIndex(qreal baseTileIndex) +{ + return (unsigned int)floor(baseTileIndex); +} + +static inline +qreal baseTileXFromLon(qreal lon, unsigned int tileCount) +{ + return 0.5 * (lon / M_PI + 1.0) * tileCount; +} + +static inline +qreal baseTileYFromLat(qreal latitude, unsigned int tileCount) +{ + // We need to calculate the tile position from the latitude + // projected using the Mercator projection. This requires the inverse Gudermannian + // function which is only defined between -85°S and 85°N. Therefore in order to + // prevent undefined results we need to restrict our calculation. + // Using 85.0 instead of some more correct 85.05113, to avoid running into NaN issues. + qreal maxAbsLat = 85.0 * DEG2RAD; + qreal lat = (qAbs(latitude) > maxAbsLat) ? latitude/qAbs(latitude) * maxAbsLat : latitude; + return (0.5 * (1.0 - gdInv(lat) / M_PI) * tileCount); +} + +// on tile borders selects the tile to the east +static inline +unsigned int eastBoundTileXFromLon(qreal lon, unsigned int tileCount) +{ + // special casing tile-map end + if (lon == M_PI) { + return 0; + } + return upperBoundTileIndex(baseTileXFromLon(lon, tileCount)); +} + +// on tile borders selects the tile to the west +static inline +unsigned int westBoundTileXFromLon(qreal lon, unsigned int tileCount) +{ + // special casing tile-map end + if (lon == -M_PI) { + return tileCount-1; + } + return lowerBoundTileIndex(baseTileXFromLon(lon, tileCount)); +} + +// on tile borders selects the tile to the south +static inline +unsigned int southBoundTileYFromLat(qreal lat, unsigned int tileCount) +{ + // special casing tile-map end + if (lat == -M_PI*0.5) { + // calculate with normal lat value + lat = M_PI * 0.5; + } + return upperBoundTileIndex(baseTileYFromLat(lat, tileCount)); +} + +// on tile borders selects the tile to the north +static inline +unsigned int northBoundTileYFromLat(qreal lat, unsigned int tileCount) +{ + // special casing tile-map end + if (lat == M_PI*0.5) { + // calculate with normal lat value + lat = - M_PI * 0.5; + } + return lowerBoundTileIndex(baseTileYFromLat(lat, tileCount)); +} + + +static inline +qreal lonFromTileX(unsigned int x, unsigned int tileCount) +{ + return ( (2*M_PI * x) / tileCount - M_PI ); +} + +static inline +qreal latFromTileY(unsigned int y, unsigned int tileCount) +{ + return gd(M_PI * (1.0 - (2.0 * y) / tileCount)); +} + + +void GeoSceneMercatorTileProjection::tileIndexes(const GeoDataLatLonBox& latLonBox, int zoomLevel, + int& westX, int& northY, int& eastX, int& southY) const +{ + const unsigned int xTileCount = (1 << zoomLevel) * levelZeroColumns(); + + westX = eastBoundTileXFromLon(latLonBox.west(), xTileCount); + eastX = westBoundTileXFromLon(latLonBox.east(), xTileCount); + + const unsigned int yTileCount = (1 << zoomLevel) * levelZeroRows(); + + northY = southBoundTileYFromLat(latLonBox.north(), yTileCount); + southY = northBoundTileYFromLat(latLonBox.south(), yTileCount); +} + +void GeoSceneMercatorTileProjection::geoCoordinates(int zoomLevel, + int x, int y, + qreal& westernTileEdgeLon, qreal& northernTileEdgeLat) const +{ + const unsigned int xTileCount = (1 << zoomLevel) * levelZeroColumns(); + westernTileEdgeLon = lonFromTileX(x, xTileCount); + + const unsigned int yTileCount = (1 << zoomLevel) * levelZeroRows(); + northernTileEdgeLat = latFromTileY(y, yTileCount); +} + + +void GeoSceneMercatorTileProjection::geoCoordinates(int zoomLevel, + int x, int y, + GeoDataLatLonBox& latLonBox) const +{ + const unsigned int xTileCount = (1 << zoomLevel) * levelZeroColumns(); + + const qreal west = lonFromTileX(x, xTileCount); + const qreal east = lonFromTileX(x + 1, xTileCount); + + const unsigned int yTileCount = (1 << zoomLevel) * levelZeroRows(); + + const qreal north = latFromTileY(y, yTileCount); + const qreal south = latFromTileY(y + 1, yTileCount); + + latLonBox.setBoundaries(north, south, east, west); +} + +} diff --git a/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.h b/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.h new file mode 100644 index 000000000..8dfe4ce9a --- /dev/null +++ b/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.h @@ -0,0 +1,89 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MARBLE_GEOSCENEMERCATORTILEPROJECTION_H +#define MARBLE_GEOSCENEMERCATORTILEPROJECTION_H + +#include "GeoSceneAbstractTileProjection.h" + +namespace Marble +{ + +/** + * Convertes the x and y indizes of tiles to and from geo coordinates. + * For tiles of maps in Mercator projection. + * + * Tiles do have the same width and the same height per zoomlevel. + * The number of tiles per dimension is twice that of the previous lower zoomlevel. + * The indexing is done in x dimension eastwards, with the first tiles beginning at -180 degree + * and an x value of 0 and the last tiles ending at +180 degree, + * in y dimension southwards with the first tiles beginning at +85.05113 degree and a y value of 0 + * and the last tiles ending at -85.05113 degree. + * + * NOTE: The method @c tileIndexes() handles any latitude value >= +85.0 degree as + * exactly +85.0 degree and any latitude value <= -85.0 as exactly -85.0 degree. + * So for higher zoomlevels the outermost tiles will be masked by that and not included in any results. + */ +class GEODATA_EXPORT GeoSceneMercatorTileProjection : public GeoSceneAbstractTileProjection +{ +public: + /** + * @brief Construct a new GeoSceneMercatorTileProjection. + */ + GeoSceneMercatorTileProjection(); + + ~GeoSceneMercatorTileProjection() override; + +public: + /** + * @copydoc + */ + GeoSceneAbstractTileProjection::Type type() const override; + + /** + * @copydoc + */ + void tileIndexes(const GeoDataLatLonBox& latLonBox, int zoomLevel, + int& westX, int& northY, int& eastX, int& southY) const override; + + /** + * @copydoc + */ + void geoCoordinates(int zoomLevel, + int x, int y, + qreal& westernTileEdgeLon, qreal& northernTileEdgeLat) const override; + + /** + * @copydoc + */ + void geoCoordinates(int zoomLevel, + int x, int y, + GeoDataLatLonBox& latLonBox) const override; + + using GeoSceneAbstractTileProjection::geoCoordinates; + +private: + Q_DISABLE_COPY(GeoSceneMercatorTileProjection) +}; + +} + +#endif diff --git a/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp b/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp index 8b1767bf2..4edd31bd7 100644 --- a/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp +++ b/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp @@ -1,319 +1,343 @@ /* This file is part of the Marble Virtual Globe. This program is free software licensed under the GNU LGPL. You can find a copy of this license in LICENSE.txt in the top directory of the source code. Copyright (C) 2008 Torsten Rahn Copyright (C) 2008 Jens-Michael Hoffmann Copyright 2012 Ander Pijoan */ #include "GeoSceneTileDataset.h" #include "GeoSceneTypes.h" +#include "GeoSceneEquirectTileProjection.h" +#include "GeoSceneMercatorTileProjection.h" #include "DownloadPolicy.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "ServerLayout.h" #include "TileId.h" #include #include namespace Marble { GeoSceneTileDataset::GeoSceneTileDataset( const QString& name ) : GeoSceneAbstractDataset( name ), m_sourceDir(), m_installMap(), m_storageLayoutMode(Marble), m_serverLayout( new MarbleServerLayout( this ) ), m_levelZeroColumns( defaultLevelZeroColumns ), m_levelZeroRows( defaultLevelZeroRows ), m_minimumTileLevel(0), m_maximumTileLevel( -1 ), - m_projection( Equirectangular ), + m_tileProjection(new GeoSceneEquirectTileProjection()), m_blending(), m_downloadUrls(), m_nextUrl( m_downloadUrls.constEnd() ) { + m_tileProjection->setLevelZeroColumns(m_levelZeroColumns); + m_tileProjection->setLevelZeroRows(m_levelZeroRows); } GeoSceneTileDataset::~GeoSceneTileDataset() { qDeleteAll( m_downloadPolicies ); delete m_serverLayout; + delete m_tileProjection; } const char* GeoSceneTileDataset::nodeType() const { return GeoSceneTypes::GeoSceneTileDatasetType; } QString GeoSceneTileDataset::sourceDir() const { return m_sourceDir; } void GeoSceneTileDataset::setSourceDir( const QString& sourceDir ) { m_sourceDir = sourceDir; } QString GeoSceneTileDataset::installMap() const { return m_installMap; } void GeoSceneTileDataset::setInstallMap( const QString& installMap ) { m_installMap = installMap; } GeoSceneTileDataset::StorageLayout GeoSceneTileDataset::storageLayout() const { return m_storageLayoutMode; } void GeoSceneTileDataset::setStorageLayout( const StorageLayout layout ) { m_storageLayoutMode = layout; } void GeoSceneTileDataset::setServerLayout( const ServerLayout *layout ) { delete m_serverLayout; m_serverLayout = layout; } const ServerLayout* GeoSceneTileDataset::serverLayout() const { return m_serverLayout; } int GeoSceneTileDataset::levelZeroColumns() const { return m_levelZeroColumns; } void GeoSceneTileDataset::setLevelZeroColumns( const int columns ) { m_levelZeroColumns = columns; + m_tileProjection->setLevelZeroColumns(m_levelZeroColumns); } int GeoSceneTileDataset::levelZeroRows() const { return m_levelZeroRows; } void GeoSceneTileDataset::setLevelZeroRows( const int rows ) { m_levelZeroRows = rows; + m_tileProjection->setLevelZeroRows(m_levelZeroRows); } int GeoSceneTileDataset::maximumTileLevel() const { return m_maximumTileLevel; } void GeoSceneTileDataset::setMaximumTileLevel( const int maximumTileLevel ) { m_maximumTileLevel = maximumTileLevel; } int GeoSceneTileDataset::minimumTileLevel() const { return m_minimumTileLevel; } void GeoSceneTileDataset::setMinimumTileLevel(int level) { m_minimumTileLevel = level; } void GeoSceneTileDataset::setTileLevels(const QString &tileLevels) { if (tileLevels.isEmpty()) { m_tileLevels.clear(); return; } const QStringList values = tileLevels.split(QLatin1Char(',')); foreach(const QString &value, values) { bool canParse(false); int const tileLevel = value.trimmed().toInt(&canParse); if (canParse && tileLevel >= 0 && tileLevel < 100) { m_tileLevels << tileLevel; } else { mDebug() << "Cannot parse tile level part " << value << " in " << tileLevels << ", ignoring it."; } } if (!m_tileLevels.isEmpty()) { qSort(m_tileLevels); m_minimumTileLevel = m_tileLevels.first(); m_maximumTileLevel = m_tileLevels.last(); } } QVector GeoSceneTileDataset::tileLevels() const { return m_tileLevels; } QVector GeoSceneTileDataset::downloadUrls() const { return m_downloadUrls; } const QSize GeoSceneTileDataset::tileSize() const { if ( m_tileSize.isEmpty() ) { const TileId id( 0, 0, 0, 0 ); QString const fileName = relativeTileFileName( id ); QFileInfo const dirInfo( fileName ); QString const path = dirInfo.isAbsolute() ? fileName : MarbleDirs::path( fileName ); QImage testTile( path ); if ( testTile.isNull() ) { mDebug() << "Tile size is missing in dgml and no base tile found in " << themeStr(); mDebug() << "Using default tile size " << c_defaultTileSize; m_tileSize = QSize( c_defaultTileSize, c_defaultTileSize ); } else { m_tileSize = testTile.size(); } if ( m_tileSize.isEmpty() ) { mDebug() << "Tile width or height cannot be 0. Falling back to default tile size."; m_tileSize = QSize( c_defaultTileSize, c_defaultTileSize ); } } Q_ASSERT( !m_tileSize.isEmpty() ); return m_tileSize; } void GeoSceneTileDataset::setTileSize( const QSize &tileSize ) { if ( tileSize.isEmpty() ) { mDebug() << "Ignoring invalid tile size " << tileSize; } else { m_tileSize = tileSize; } } -GeoSceneTileDataset::Projection GeoSceneTileDataset::projection() const +void GeoSceneTileDataset::setTileProjection(GeoSceneAbstractTileProjection::Type projectionType) { - return m_projection; + if (m_tileProjection->type() == projectionType) { + return; + } + + delete m_tileProjection; + if (projectionType == GeoSceneAbstractTileProjection::Mercator) { + m_tileProjection = new GeoSceneMercatorTileProjection(); + } else { + m_tileProjection = new GeoSceneEquirectTileProjection(); + } + + m_tileProjection->setLevelZeroColumns(m_levelZeroColumns); + m_tileProjection->setLevelZeroRows(m_levelZeroRows); +} + +const GeoSceneAbstractTileProjection * GeoSceneTileDataset::tileProjection() const +{ + return m_tileProjection; } -void GeoSceneTileDataset::setProjection( const Projection projection ) +GeoSceneAbstractTileProjection::Type GeoSceneTileDataset::tileProjectionType() const { - m_projection = projection; + return m_tileProjection->type(); } // Even though this method changes the internal state, it may be const // because the compiler is forced to invoke this method for different TileIds. QUrl GeoSceneTileDataset::downloadUrl( const TileId &id ) const { // default download url if ( m_downloadUrls.empty() ) { QUrl const defaultUrl = QUrl(QLatin1String("https://maps.kde.org/") + m_serverLayout->sourceDir()); mDebug() << "No download URL specified for tiles stored in " << m_sourceDir << ", falling back to " << defaultUrl.toString(); return m_serverLayout->downloadUrl(defaultUrl, id); } if ( m_nextUrl == m_downloadUrls.constEnd() ) m_nextUrl = m_downloadUrls.constBegin(); const QUrl url = m_serverLayout->downloadUrl( *m_nextUrl, id ); ++m_nextUrl; return url; } void GeoSceneTileDataset::addDownloadUrl( const QUrl & url ) { m_downloadUrls.append( url ); // FIXME: this could be done only once m_nextUrl = m_downloadUrls.constBegin(); } QString GeoSceneTileDataset::relativeTileFileName( const TileId &id ) const { const QString suffix = fileFormat().toLower(); QString relFileName; switch ( m_storageLayoutMode ) { default: mDebug() << Q_FUNC_INFO << "Invalid storage layout mode! Falling back to default."; case GeoSceneTileDataset::Marble: relFileName = QString( "%1/%2/%3/%3_%4.%5" ) .arg( themeStr() ) .arg( id.zoomLevel() ) .arg(id.y(), tileDigits, 10, QLatin1Char('0')) .arg(id.x(), tileDigits, 10, QLatin1Char('0')) .arg( suffix ); break; case GeoSceneTileDataset::OpenStreetMap: relFileName = QString( "%1/%2/%3/%4.%5" ) .arg( themeStr() ) .arg( id.zoomLevel() ) .arg( id.x() ) .arg( id.y() ) .arg( suffix ); break; case GeoSceneTileDataset::TileMapService: relFileName = QString( "%1/%2/%3/%4.%5" ) .arg( themeStr() ) .arg( id.zoomLevel() ) .arg( id.x() ) .arg( ( 1< GeoSceneTileDataset::downloadPolicies() const { return m_downloadPolicies; } void GeoSceneTileDataset::addDownloadPolicy( const DownloadUsage usage, const int maximumConnections ) { DownloadPolicy * const policy = new DownloadPolicy( DownloadPolicyKey( hostNames(), usage )); policy->setMaximumConnections( maximumConnections ); m_downloadPolicies.append( policy ); mDebug() << "added download policy" << hostNames() << usage << maximumConnections; } QStringList GeoSceneTileDataset::hostNames() const { QStringList result; result.reserve(m_downloadUrls.size()); QVector::const_iterator pos = m_downloadUrls.constBegin(); QVector::const_iterator const end = m_downloadUrls.constEnd(); for (; pos != end; ++pos ) result.append( (*pos).host() ); return result; } } diff --git a/src/lib/marble/geodata/scene/GeoSceneTileDataset.h b/src/lib/marble/geodata/scene/GeoSceneTileDataset.h index 586d806d3..d838c50db 100644 --- a/src/lib/marble/geodata/scene/GeoSceneTileDataset.h +++ b/src/lib/marble/geodata/scene/GeoSceneTileDataset.h @@ -1,150 +1,152 @@ /* This file is part of the Marble Virtual Globe. This program is free software licensed under the GNU LGPL. You can find a copy of this license in LICENSE.txt in the top directory of the source code. Copyright (C) 2008 Torsten Rahn Copyright (C) 2008 Jens-Michael Hoffmann Copyright 2012 Ander Pijoan */ #ifndef MARBLE_GEOSCENETILEDATASET_H #define MARBLE_GEOSCENETILEDATASET_H #include #include #include #include "GeoSceneAbstractDataset.h" +#include "GeoSceneAbstractTileProjection.h" #include "MarbleGlobal.h" class QStringList; class QUrl; /** * @short Tiled dataset stored in a layer. TextureTile and VectorTile layes inherit from this class. */ /* In order to make Marble able to manage vector tiles, * now there is GeoSceneTileDataset and then GeoSceneTextureTileDataset * (for the tag in dgml) or GeoSceneVectorTileDataset * (for ) are created, which inherit from this class */ namespace Marble { class DownloadPolicy; class ServerLayout; class TileId; class GEODATA_EXPORT GeoSceneTileDataset : public GeoSceneAbstractDataset { public: enum StorageLayout { Marble, OpenStreetMap, TileMapService }; - enum Projection { Equirectangular, Mercator }; explicit GeoSceneTileDataset( const QString& name ); ~GeoSceneTileDataset(); virtual const char* nodeType() const; QString sourceDir() const; void setSourceDir( const QString& sourceDir ); QString installMap() const; void setInstallMap( const QString& installMap ); StorageLayout storageLayout() const; void setStorageLayout( const StorageLayout ); void setServerLayout( const ServerLayout * ); const ServerLayout *serverLayout() const; int levelZeroColumns() const; void setLevelZeroColumns( const int ); int levelZeroRows() const; void setLevelZeroRows( const int ); bool hasMaximumTileLevel() const; int maximumTileLevel() const; void setMaximumTileLevel( const int ); int minimumTileLevel() const; void setMinimumTileLevel(int level); void setTileLevels(const QString &tileLevels); QVector tileLevels() const; QVector downloadUrls() const; const QSize tileSize() const; void setTileSize( const QSize &tileSize ); - Projection projection() const; - void setProjection( const Projection ); + void setTileProjection(GeoSceneAbstractTileProjection::Type projectionType); + + const GeoSceneAbstractTileProjection * tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; QString blending() const; void setBlending( const QString &name ); /** * Creates a download URL for the given tile id. * * It implements the round robin for the tile servers. * On each invocation the next url is returned. */ QUrl downloadUrl( const TileId & ) const; void addDownloadUrl( const QUrl & ); QString relativeTileFileName( const TileId & ) const; QString themeStr() const; QList downloadPolicies() const; void addDownloadPolicy( const DownloadUsage usage, const int maximumConnections ); private: Q_DISABLE_COPY( GeoSceneTileDataset ) QStringList hostNames() const; QString m_sourceDir; QString m_installMap; StorageLayout m_storageLayoutMode; const ServerLayout *m_serverLayout; int m_levelZeroColumns; int m_levelZeroRows; int m_minimumTileLevel; int m_maximumTileLevel; QVector m_tileLevels; mutable QSize m_tileSize; - Projection m_projection; + GeoSceneAbstractTileProjection *m_tileProjection; QString m_blending; /// List of Urls which are used in a round robin fashion QVector m_downloadUrls; /// Points to next Url for the round robin algorithm mutable QVector::const_iterator m_nextUrl; QList m_downloadPolicies; }; inline bool GeoSceneTileDataset::hasMaximumTileLevel() const { return m_maximumTileLevel != -1; } inline QString GeoSceneTileDataset::blending() const { return m_blending; } inline void GeoSceneTileDataset::setBlending( const QString &name ) { m_blending = name; } } #endif diff --git a/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp b/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp index 8bc786c71..3e82a5732 100644 --- a/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp +++ b/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp @@ -1,103 +1,104 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Utku Aydın // #include "DgmlTextureTagWriter.h" #include "GeoSceneTypes.h" #include "GeoWriter.h" #include "GeoSceneTileDataset.h" #include "DownloadPolicy.h" #include "DgmlElementDictionary.h" #include "ServerLayout.h" #include namespace Marble { static GeoTagWriterRegistrar s_writerTexture( GeoTagWriter::QualifiedName( GeoSceneTypes::GeoSceneTileDatasetType, dgml::dgmlTag_nameSpace20 ), new DgmlTextureTagWriter() ); bool DgmlTextureTagWriter::write(const GeoNode *node, GeoWriter& writer) const { const GeoSceneTileDataset *texture = static_cast( node ); writer.writeStartElement( dgml::dgmlTag_Texture ); writer.writeAttribute( "name", texture->name() ); writer.writeAttribute( "expire", QString::number( texture->expire() ) ); writer.writeStartElement( dgml::dgmlTag_SourceDir ); writer.writeAttribute( "format", texture->fileFormat() ); if( texture->expire() ) { writer.writeAttribute( "expire", QString::number( texture->expire() ) ); } writer.writeCharacters( texture->sourceDir() ); writer.writeEndElement(); writer.writeOptionalElement( dgml::dgmlTag_InstallMap, texture->installMap() ); writer.writeStartElement( dgml::dgmlTag_StorageLayout ); if( texture->hasMaximumTileLevel() ) { writer.writeAttribute( "maximumTileLevel", QString::number( texture->maximumTileLevel() ) ); writer.writeAttribute( "levelZeroColumns", QString::number( texture->levelZeroColumns() ) ); writer.writeAttribute( "levelZeroRows", QString::number( texture->levelZeroRows() ) ); writer.writeAttribute( "mode", texture->serverLayout()->name() ); } writer.writeEndElement(); if ( texture->downloadUrls().size() > 0 ) { for( int i = 0; i < texture->downloadUrls().size(); ++i ) { QString protocol = texture->downloadUrls().at(i).toString().left(texture->downloadUrls().at(i).toString().indexOf(QLatin1Char(':'))); QString host = QString( texture->downloadUrls().at(i).host() ); QString path = QString( texture->downloadUrls().at(i).path() ); QString query = texture->downloadUrls().at(i).query(QUrl::FullyEncoded); writer.writeStartElement( dgml::dgmlTag_DownloadUrl ); writer.writeAttribute( "protocol", protocol ); writer.writeAttribute( "host", host ); writer.writeAttribute( "path", path ); writer.writeAttribute( "query", query ); writer.writeEndElement(); } } foreach( const DownloadPolicy *policy, texture->downloadPolicies() ) { writer.writeStartElement( dgml::dgmlTag_DownloadPolicy ); if( policy->key().usage() == DownloadBrowse ) { writer.writeAttribute( "usage", "Browse" ); writer.writeAttribute( "maximumConnections", QString::number( policy->maximumConnections() ) ); } else if( policy->key().usage() == DownloadBulk ) { writer.writeAttribute( "usage", "Bulk" ); writer.writeAttribute( "maximumConnections", QString::number( policy->maximumConnections() ) ); } writer.writeEndElement(); } writer.writeStartElement( dgml::dgmlTag_Projection ); - if( texture->projection() == GeoSceneTileDataset::Mercator ) { + const GeoSceneAbstractTileProjection::Type tileProjectionType = texture->tileProjectionType(); + if (tileProjectionType == GeoSceneAbstractTileProjection::Mercator) { writer.writeAttribute( "name", "Mercator" ); - } else if ( texture->projection() == GeoSceneTileDataset::Equirectangular ) { + } else if (tileProjectionType == GeoSceneAbstractTileProjection::Equirectangular) { writer.writeAttribute( "name", "Equirectangular" ); } writer.writeEndElement(); writer.writeEndElement(); return true; } } diff --git a/src/lib/marble/layers/TextureLayer.cpp b/src/lib/marble/layers/TextureLayer.cpp index d038b1d57..1b83f365e 100644 --- a/src/lib/marble/layers/TextureLayer.cpp +++ b/src/lib/marble/layers/TextureLayer.cpp @@ -1,590 +1,590 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2007 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2008, 2009, 2010 Jens-Michael Hoffmann // Copyright 2010-2012 Bernhard Beschow // #include "TextureLayer.h" #include #include #include #include #include "SphericalScanlineTextureMapper.h" #include "EquirectScanlineTextureMapper.h" #include "MercatorScanlineTextureMapper.h" #include "GenericScanlineTextureMapper.h" #include "TileScalingTextureMapper.h" #include "GeoDataGroundOverlay.h" #include "GeoPainter.h" #include "GeoSceneGroup.h" #include "GeoSceneTypes.h" #include "MergedLayerDecorator.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "MarblePlacemarkModel.h" #include "StackedTile.h" #include "StackedTileLoader.h" #include "SunLocator.h" #include "TextureColorizer.h" #include "TileLoader.h" #include "ViewportParams.h" namespace Marble { const int REPAINT_SCHEDULING_INTERVAL = 1000; class Q_DECL_HIDDEN TextureLayer::Private { public: Private( HttpDownloadManager *downloadManager, PluginManager* pluginManager, const SunLocator *sunLocator, QAbstractItemModel *groundOverlayModel, TextureLayer *parent ); void requestDelayedRepaint(); void updateTextureLayers(); void updateTile( const TileId &tileId, const QImage &tileImage ); void addGroundOverlays( const QModelIndex& parent, int first, int last ); void removeGroundOverlays( const QModelIndex& parent, int first, int last ); void resetGroundOverlaysCache(); void updateGroundOverlays(); void addCustomTextures(); static bool drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 ); public: TextureLayer *const m_parent; const SunLocator *const m_sunLocator; TileLoader m_loader; MergedLayerDecorator m_layerDecorator; StackedTileLoader m_tileLoader; GeoDataCoordinates m_centerCoordinates; int m_tileZoomLevel; TextureMapperInterface *m_texmapper; TextureColorizer *m_texcolorizer; QVector m_textures; const GeoSceneGroup *m_textureLayerSettings; QString m_runtimeTrace; QSortFilterProxyModel m_groundOverlayModel; QList m_groundOverlayCache; QMap m_customTextures; // For scheduling repaints QTimer m_repaintTimer; RenderState m_renderState; }; TextureLayer::Private::Private( HttpDownloadManager *downloadManager, PluginManager* pluginManager, const SunLocator *sunLocator, QAbstractItemModel *groundOverlayModel, TextureLayer *parent ) : m_parent( parent ) , m_sunLocator( sunLocator ) , m_loader( downloadManager, pluginManager ) , m_layerDecorator( &m_loader, sunLocator ) , m_tileLoader( &m_layerDecorator ) , m_centerCoordinates() , m_tileZoomLevel( -1 ) , m_texmapper( 0 ) , m_texcolorizer( 0 ) , m_textureLayerSettings( 0 ) , m_repaintTimer() { m_groundOverlayModel.setSourceModel( groundOverlayModel ); m_groundOverlayModel.setDynamicSortFilter( true ); m_groundOverlayModel.setSortRole ( MarblePlacemarkModel::PopularityIndexRole ); m_groundOverlayModel.sort (0, Qt::AscendingOrder ); connect( &m_groundOverlayModel, SIGNAL(rowsInserted(QModelIndex,int,int)), m_parent, SLOT(addGroundOverlays(QModelIndex,int,int)) ); connect( &m_groundOverlayModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), m_parent, SLOT(removeGroundOverlays(QModelIndex,int,int)) ); connect( &m_groundOverlayModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), m_parent, SLOT(resetGroundOverlaysCache()) ); connect( &m_groundOverlayModel, SIGNAL(modelReset()), m_parent, SLOT(resetGroundOverlaysCache()) ); updateGroundOverlays(); } void TextureLayer::Private::requestDelayedRepaint() { if ( m_texmapper ) { m_texmapper->setRepaintNeeded(); } if ( !m_repaintTimer.isActive() ) { m_repaintTimer.start(); } } void TextureLayer::Private::updateTextureLayers() { QVector result; foreach ( const GeoSceneTextureTileDataset *candidate, m_textures ) { bool enabled = true; if ( m_textureLayerSettings ) { const bool propertyExists = m_textureLayerSettings->propertyValue( candidate->name(), enabled ); enabled |= !propertyExists; // if property doesn't exist, enable texture nevertheless } if ( enabled ) { result.append( candidate ); mDebug() << "enabling texture" << candidate->name(); } else { mDebug() << "disabling texture" << candidate->name(); } } updateGroundOverlays(); m_layerDecorator.setTextureLayers( result ); m_tileLoader.clear(); m_tileZoomLevel = -1; m_parent->setNeedsUpdate(); } void TextureLayer::Private::updateTile( const TileId &tileId, const QImage &tileImage ) { if ( tileImage.isNull() ) return; // keep tiles in cache to improve performance m_tileLoader.updateTile( tileId, tileImage ); requestDelayedRepaint(); } bool TextureLayer::Private::drawOrderLessThan( const GeoDataGroundOverlay* o1, const GeoDataGroundOverlay* o2 ) { return o1->drawOrder() < o2->drawOrder(); } void TextureLayer::Private::addGroundOverlays( const QModelIndex& parent, int first, int last ) { for ( int i = first; i <= last; ++i ) { QModelIndex index = m_groundOverlayModel.index( i, 0, parent ); const GeoDataGroundOverlay *overlay = static_cast( qvariant_cast( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) ); if ( overlay->icon().isNull() ) { continue; } int pos = qLowerBound( m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan ) - m_groundOverlayCache.begin(); m_groundOverlayCache.insert( pos, overlay ); } updateGroundOverlays(); m_parent->reset(); } void TextureLayer::Private::removeGroundOverlays( const QModelIndex& parent, int first, int last ) { for ( int i = first; i <= last; ++i ) { QModelIndex index = m_groundOverlayModel.index( i, 0, parent ); const GeoDataGroundOverlay *overlay = static_cast( qvariant_cast( index.data( MarblePlacemarkModel::ObjectPointerRole ) ) ); int pos = qLowerBound( m_groundOverlayCache.begin(), m_groundOverlayCache.end(), overlay, drawOrderLessThan ) - m_groundOverlayCache.begin(); if (pos >= 0 && pos < m_groundOverlayCache.size() ) { m_groundOverlayCache.removeAt( pos ); } } updateGroundOverlays(); m_parent->reset(); } void TextureLayer::Private::resetGroundOverlaysCache() { m_groundOverlayCache.clear(); updateGroundOverlays(); m_parent->reset(); } void TextureLayer::Private::updateGroundOverlays() { if ( !m_texcolorizer ) { m_layerDecorator.updateGroundOverlays( m_groundOverlayCache ); } else { m_layerDecorator.updateGroundOverlays( QList() ); } } void TextureLayer::Private::addCustomTextures() { m_textures.reserve(m_textures.size() + m_customTextures.size()); foreach (GeoSceneTextureTileDataset *t, m_customTextures) { m_textures.append(t); } } TextureLayer::TextureLayer( HttpDownloadManager *downloadManager, PluginManager* pluginManager, const SunLocator *sunLocator, QAbstractItemModel *groundOverlayModel ) : QObject() , d( new Private( downloadManager, pluginManager, sunLocator, groundOverlayModel, this ) ) { connect( &d->m_loader, SIGNAL(tileCompleted(TileId,QImage)), this, SLOT(updateTile(TileId,QImage)) ); // Repaint timer d->m_repaintTimer.setSingleShot( true ); d->m_repaintTimer.setInterval( REPAINT_SCHEDULING_INTERVAL ); connect( &d->m_repaintTimer, SIGNAL(timeout()), this, SIGNAL(repaintNeeded()) ); } TextureLayer::~TextureLayer() { qDeleteAll(d->m_customTextures); delete d->m_texmapper; delete d->m_texcolorizer; delete d; } QStringList TextureLayer::renderPosition() const { return QStringList(QStringLiteral("SURFACE")); } void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument ) { if( d->m_texcolorizer ) { d->m_texcolorizer->addSeaDocument( seaDocument ); reset(); } } void TextureLayer::addLandDocument( const GeoDataDocument *landDocument ) { if( d->m_texcolorizer ) { d->m_texcolorizer->addLandDocument( landDocument ); reset(); } } int TextureLayer::textureLayerCount() const { return d->m_layerDecorator.textureLayersSize(); } bool TextureLayer::showSunShading() const { return d->m_layerDecorator.showSunShading(); } bool TextureLayer::showCityLights() const { return d->m_layerDecorator.showCityLights(); } bool TextureLayer::render( GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer ) { Q_UNUSED( renderPos ); Q_UNUSED( layer ); d->m_renderState = RenderState(QStringLiteral("Texture Tiles")); // Stop repaint timer if it is already running d->m_repaintTimer.stop(); if ( d->m_textures.isEmpty() ) return false; if ( d->m_layerDecorator.textureLayersSize() == 0 ) return false; if ( !d->m_texmapper ) return false; if ( d->m_centerCoordinates.longitude() != viewport->centerLongitude() || d->m_centerCoordinates.latitude() != viewport->centerLatitude() ) { d->m_centerCoordinates.setLongitude( viewport->centerLongitude() ); d->m_centerCoordinates.setLatitude( viewport->centerLatitude() ); d->m_texmapper->setRepaintNeeded(); } // choose the smaller dimension for selecting the tile level, leading to higher-resolution results const int levelZeroWidth = d->m_layerDecorator.tileSize().width() * d->m_layerDecorator.tileColumnCount( 0 ); const int levelZeroHight = d->m_layerDecorator.tileSize().height() * d->m_layerDecorator.tileRowCount( 0 ); const int levelZeroMinDimension = qMin( levelZeroWidth, levelZeroHight ); // limit to 1 as dirty fix for invalid entry linearLevel const qreal linearLevel = qMax( 1.0, viewport->radius() * 4.0 / levelZeroMinDimension ); // As our tile resolution doubles with each level we calculate // the tile level from tilesize and the globe radius via log(2) const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ) * 1.00001; // snap to the sharper tile level a tiny bit earlier // to work around rounding errors when the radius // roughly equals the global texture width const int tileLevel = qMin( d->m_layerDecorator.maximumTileLevel(), tileLevelF ); if ( tileLevel != d->m_tileZoomLevel ) { d->m_tileZoomLevel = tileLevel; emit tileLevelChanged( d->m_tileZoomLevel ); } const QRect dirtyRect = QRect( QPoint( 0, 0), viewport->size() ); d->m_texmapper->mapTexture( painter, viewport, d->m_tileZoomLevel, dirtyRect, d->m_texcolorizer ); d->m_renderState.addChild( d->m_tileLoader.renderState() ); d->m_runtimeTrace = QStringLiteral("Texture Cache: %1 ").arg(d->m_tileLoader.tileCount()); return true; } QString TextureLayer::runtimeTrace() const { return d->m_runtimeTrace; } void TextureLayer::setShowRelief( bool show ) { if ( d->m_texcolorizer ) { d->m_texcolorizer->setShowRelief( show ); } } void TextureLayer::setShowSunShading( bool show ) { disconnect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)), this, SLOT(reset()) ); if ( show ) { connect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)), this, SLOT(reset()) ); } d->m_layerDecorator.setShowSunShading( show ); reset(); } void TextureLayer::setShowCityLights( bool show ) { d->m_layerDecorator.setShowCityLights( show ); reset(); } void TextureLayer::setShowTileId( bool show ) { d->m_layerDecorator.setShowTileId( show ); reset(); } void TextureLayer::setProjection( Projection projection ) { if ( d->m_textures.isEmpty() ) { return; } // FIXME: replace this with an approach based on the factory method pattern. delete d->m_texmapper; switch( projection ) { case Spherical: d->m_texmapper = new SphericalScanlineTextureMapper( &d->m_tileLoader ); break; case Equirectangular: d->m_texmapper = new EquirectScanlineTextureMapper( &d->m_tileLoader ); break; case Mercator: - if ( d->m_textures.at(0)->projection() == GeoSceneTileDataset::Mercator ) { + if (d->m_textures.at(0)->tileProjectionType() == GeoSceneAbstractTileProjection::Mercator) { d->m_texmapper = new TileScalingTextureMapper( &d->m_tileLoader ); } else { d->m_texmapper = new MercatorScanlineTextureMapper( &d->m_tileLoader ); } break; case Gnomonic: case Stereographic: case LambertAzimuthal: case AzimuthalEquidistant: case VerticalPerspective: d->m_texmapper = new GenericScanlineTextureMapper( &d->m_tileLoader ); break; default: d->m_texmapper = 0; } Q_ASSERT( d->m_texmapper ); } void TextureLayer::setNeedsUpdate() { if ( d->m_texmapper ) { d->m_texmapper->setRepaintNeeded(); } emit repaintNeeded(); } void TextureLayer::setVolatileCacheLimit( quint64 kilobytes ) { d->m_tileLoader.setVolatileCacheLimit( kilobytes ); } void TextureLayer::reset() { mDebug() << Q_FUNC_INFO; d->m_tileLoader.clear(); setNeedsUpdate(); } void TextureLayer::reload() { foreach ( const TileId &id, d->m_tileLoader.visibleTiles() ) { // it's debatable here, whether DownloadBulk or DownloadBrowse should be used // but since "reload" or "refresh" seems to be a common action of a browser and it // allows for more connections (in our model), use "DownloadBrowse" d->m_layerDecorator.downloadStackedTile( id, DownloadBrowse ); } } void TextureLayer::downloadStackedTile( const TileId &stackedTileId ) { d->m_layerDecorator.downloadStackedTile( stackedTileId, DownloadBulk ); } void TextureLayer::setMapTheme( const QVector &textures, const GeoSceneGroup *textureLayerSettings, const QString &seaFile, const QString &landFile ) { delete d->m_texcolorizer; d->m_texcolorizer = 0; if ( QFileInfo( seaFile ).isReadable() || QFileInfo( landFile ).isReadable() ) { d->m_texcolorizer = new TextureColorizer( seaFile, landFile ); } d->m_textures = textures; d->addCustomTextures(); d->m_textureLayerSettings = textureLayerSettings; if ( d->m_textureLayerSettings ) { connect( d->m_textureLayerSettings, SIGNAL(valueChanged(QString,bool)), this, SLOT(updateTextureLayers()) ); } d->updateTextureLayers(); } int TextureLayer::tileZoomLevel() const { return d->m_tileZoomLevel; } QSize TextureLayer::tileSize() const { return d->m_layerDecorator.tileSize(); } -GeoSceneTileDataset::Projection TextureLayer::tileProjection() const +GeoSceneAbstractTileProjection::Type TextureLayer::tileProjectionType() const { - return d->m_layerDecorator.tileProjection(); + return d->m_layerDecorator.tileProjectionType(); } int TextureLayer::tileColumnCount( int level ) const { return d->m_layerDecorator.tileColumnCount( level ); } int TextureLayer::tileRowCount( int level ) const { return d->m_layerDecorator.tileRowCount( level ); } qint64 TextureLayer::volatileCacheLimit() const { return d->m_tileLoader.volatileCacheLimit(); } int TextureLayer::preferredRadiusCeil( int radius ) const { if (!d->m_layerDecorator.hasTextureLayer()) { return radius; } const int tileWidth = d->m_layerDecorator.tileSize().width(); const int levelZeroColumns = d->m_layerDecorator.tileColumnCount( 0 ); const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns ); const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ); const int tileLevel = qCeil( tileLevelF ); if ( tileLevel < 0 ) return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel); return ( tileWidth * levelZeroColumns / 4 ) << tileLevel; } int TextureLayer::preferredRadiusFloor( int radius ) const { if (!d->m_layerDecorator.hasTextureLayer()) { return radius; } const int tileWidth = d->m_layerDecorator.tileSize().width(); const int levelZeroColumns = d->m_layerDecorator.tileColumnCount( 0 ); const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns ); const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ); const int tileLevel = qFloor( tileLevelF ); if ( tileLevel < 0 ) return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel); return ( tileWidth * levelZeroColumns / 4 ) << tileLevel; } RenderState TextureLayer::renderState() const { return d->m_renderState; } QString TextureLayer::addTextureLayer(GeoSceneTextureTileDataset* texture) { if (!texture) return QString(); //Not a sane call QString sourceDir = texture->sourceDir(); if (!d->m_customTextures.contains(sourceDir)) { // Add if not present. For update, remove the old texture first. d->m_customTextures.insert(sourceDir, texture); d->m_textures.append(texture); d->updateTextureLayers(); } return sourceDir; } void TextureLayer::removeTextureLayer(const QString &key) { if (d->m_customTextures.contains(key)) { GeoSceneTextureTileDataset *texture = d->m_customTextures.value(key); d->m_customTextures.remove(key); d->m_textures.remove(d->m_textures.indexOf(texture)); delete texture; d->updateTextureLayers(); } } } #include "moc_TextureLayer.cpp" diff --git a/src/lib/marble/layers/TextureLayer.h b/src/lib/marble/layers/TextureLayer.h index 691286880..caba1c36d 100644 --- a/src/lib/marble/layers/TextureLayer.h +++ b/src/lib/marble/layers/TextureLayer.h @@ -1,146 +1,146 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2010,2011 Bernhard Beschow // #ifndef MARBLE_MARBLETEXTURELAYER_H #define MARBLE_MARBLETEXTURELAYER_H #include "LayerInterface.h" #include #include "MarbleGlobal.h" #include "GeoSceneTileDataset.h" class QAbstractItemModel; class QImage; class QSize; namespace Marble { class GeoPainter; class GeoDataDocument; class GeoSceneGroup; class GeoSceneTextureTileDataset; class HttpDownloadManager; class SunLocator; class ViewportParams; class PluginManager; class MARBLE_EXPORT TextureLayer : public QObject, public LayerInterface { Q_OBJECT public: TextureLayer( HttpDownloadManager *downloadManager, PluginManager* pluginManager, const SunLocator *sunLocator, QAbstractItemModel *groundOverlayModel ); ~TextureLayer(); QStringList renderPosition() const; void addSeaDocument( const GeoDataDocument *seaDocument ); void addLandDocument( const GeoDataDocument *landDocument ); int textureLayerCount() const; /** * @brief Adds texture sublayer, taking ownership of the object's memory * Does nothing if a texture with the same source directory was already * added with this method. * @return returned string is the key for the texture that can be later used to remove it */ QString addTextureLayer(GeoSceneTextureTileDataset *texture); /** * @brief Removes texture sublayer identified by a key. * Deletes the texture object. Does nothing if key is not found. * @param A key to identify the texture, returned from addTextureLayer */ void removeTextureLayer(const QString &key); bool showSunShading() const; bool showCityLights() const; /** * @brief Return the current tile zoom level. For example for OpenStreetMap * possible values are 1..18, for BlueMarble 0..6. */ int tileZoomLevel() const; QSize tileSize() const; - GeoSceneTileDataset::Projection tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; int tileColumnCount( int level ) const; int tileRowCount( int level ) const; qint64 volatileCacheLimit() const; int preferredRadiusCeil( int radius ) const; int preferredRadiusFloor( int radius ) const; RenderState renderState() const; virtual QString runtimeTrace() const; virtual bool render( GeoPainter *painter, ViewportParams *viewport, const QString &renderPos = QLatin1String("NONE"), GeoSceneLayer *layer = 0 ); public Q_SLOTS: void setShowRelief( bool show ); void setShowSunShading( bool show ); void setShowCityLights( bool show ); void setShowTileId( bool show ); /** * @brief Set the Projection used for the map * @param projection projection type (e.g. Spherical, Equirectangular, Mercator) */ void setProjection( Projection projection ); void setNeedsUpdate(); void setMapTheme( const QVector &textures, const GeoSceneGroup *textureLayerSettings, const QString &seaFile, const QString &landFile ); void setVolatileCacheLimit( quint64 kilobytes ); void reset(); void reload(); void downloadStackedTile( const TileId &stackedTileId ); Q_SIGNALS: void tileLevelChanged( int ); void repaintNeeded(); private: Q_PRIVATE_SLOT( d, void requestDelayedRepaint() ) Q_PRIVATE_SLOT( d, void updateTextureLayers() ) Q_PRIVATE_SLOT( d, void updateTile( const TileId &tileId, const QImage &tileImage ) ) Q_PRIVATE_SLOT( d, void addGroundOverlays( const QModelIndex& parent, int first, int last ) ) Q_PRIVATE_SLOT( d, void removeGroundOverlays( const QModelIndex& parent, int first, int last ) ) Q_PRIVATE_SLOT( d, void resetGroundOverlaysCache() ) private: class Private; Private *const d; }; } #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d0db4d67c..32c034c92 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,118 +1,119 @@ ############################################################# ADD_DEFINITIONS(-DTEST_DATA_DIR="${TEST_DATA_DIR}") ADD_DEFINITIONS(-DMARBLE_SRC_DIR="${CMAKE_SOURCE_DIR}") ############################################################# # libraries # because of htonl IF (WIN32) SET(PLATFORM_LIBRARIES wsock32) ENDIF (WIN32) # Since the tests are not actually installed, but rather # run directly from the build/src/tests dir we need to # ensure the marble libs can be found. IF (APPLE) # For Mac OS X, the executable must be at the root of the bundle's executable folder SET (CMAKE_INSTALL_NAME_DIR @executable_path/../lib) ENDIF (APPLE) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) ############################################################# # includes ############################################################# # Make the current test source directory available in #define TESTSRCDIR # use this for accessing TESTSRCDIR/data ############################################################# add_definitions( -DTESTSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}" ) ############################################################# ############################################################# # Configure the test scripts and put them in the project root # directory. ############################################################# #make sure that the cmake and ctest stuff is available if( CMAKE_COMMAND AND CMAKE_CTEST_COMMAND ) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/CTestNightlyScript.cmake.in" "${PROJECT_BINARY_DIR}/CTestNightlyScript.cmake" ) else( CMAKE_COMMAND AND CMAKE_CTEST_COMMAND ) message( STATUS "Both the CTest and CMake command are needed to create test scripts" ) message( STATUS "Test Scripts have not been created" ) endif( CMAKE_COMMAND AND CMAKE_CTEST_COMMAND ) ############################ # Drop in New Tests ############################ marble_add_test( MarbleWidgetSpeedTest ) add_definitions( -DDGML_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/maps/earth" ) marble_add_test( TestGeoSceneWriter ) marble_add_test( LocaleTest ) # Check MarbleLocale functionality marble_add_test( QuaternionTest ) # Check Quaternion arithmetic marble_add_test( TileIdTest ) # Check TileId arithmetic marble_add_test( ViewportParamsTest ) marble_add_test( PluginManagerTest ) # Check plugin loading marble_add_test( MarbleRunnerManagerTest ) # Check RunnerManager signals marble_add_test( BookmarkManagerTest ) marble_add_test( PlacemarkPositionProviderPluginTest ) marble_add_test( PositionTrackingTest ) marble_add_test( MercatorProjectionTest ) # Check Screen coordinates marble_add_test( GnomonicProjectionTest ) marble_add_test( StereographicProjectionTest ) marble_add_test( MarbleMapTest ) # Check map theme and centering marble_add_test( MarbleWidgetTest ) # Check map theme, mouse move, repaint and multiple widgets marble_add_test( MapViewWidgetTest ) # Check mapview signals marble_add_test( TestGeoPainter ) # no tests! marble_add_test( GeoUriParserTest ) marble_add_test( BillboardGraphicsItemTest ) marble_add_test( ScreenGraphicsItemTest ) marble_add_test( FrameGraphicsItemTest ) marble_add_test( RenderPluginTest ) marble_add_test( AbstractDataPluginModelTest ) marble_add_test( AbstractDataPluginTest ) marble_add_test( AbstractFloatItemTest ) marble_add_test( RenderPluginModelTest ) marble_add_test( GeoDataTreeModelTest ) marble_add_test( RouteRequestTest ) ## GeoData Classes tests marble_add_test( TestCamera ) marble_add_test( TestNetworkLink ) marble_add_test( TestLatLonQuad ) marble_add_test( TestGeoData ) # Check parent, nodetype marble_add_test( TestGeoDataCoordinates ) # Check coordinates specifics marble_add_test( TestGeoDataLatLonAltBox ) # Check boxen specifics marble_add_test( TestGeoDataGeometry ) # Check geometry specifics marble_add_test( TestGeoDataTrack ) # Check track specifics marble_add_test( TestGxTimeSpan ) marble_add_test( TestGxTimeStamp ) marble_add_test( TestBalloonStyle ) # Check BalloonStyle marble_add_test( TestListStyle ) # Check ListStyle marble_add_test( TestTour ) marble_add_test( TestGroundOverlay ) # Check GroundOverlay specifics marble_add_test( TestModel ) marble_add_test( TestTimeStamp ) marble_add_test( TestTimeSpan ) marble_add_test( TestEquality ) marble_add_test( TestFeatureDetach ) marble_add_test( TestGeometryDetach ) +marble_add_test( TestTileProjection ) qt_add_resources(TestGeoDataCopy_SRCS TestGeoDataCopy.qrc) # Check copy operations on CoW classes marble_add_test( TestGeoDataCopy ${TestGeoDataCopy_SRCS} ) add_definitions( -DCITIES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/placemarks/cityplacemarks.kml" ) marble_add_test( TestGeoDataWriter ) # Check parsing, writing, reloading and comparing kml files marble_add_test( TestGeoDataPack ) # Check pack and unpack to file diff --git a/tests/TestGeoSceneWriter.cpp b/tests/TestGeoSceneWriter.cpp index 4649107d1..d161d5bf5 100644 --- a/tests/TestGeoSceneWriter.cpp +++ b/tests/TestGeoSceneWriter.cpp @@ -1,326 +1,326 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Utku Aydın // #include #include #include #include #include #include "GeoSceneDocument.h" #include "GeoSceneHead.h" #include "GeoSceneZoom.h" #include "GeoSceneIcon.h" #include "GeoSceneMap.h" #include "GeoSceneLayer.h" #include "GeoSceneTileDataset.h" #include "GeoSceneGeodata.h" #include "GeoSceneSettings.h" #include "GeoSceneProperty.h" #include "GeoSceneLegend.h" #include "GeoSceneSection.h" #include "GeoSceneItem.h" #include "GeoSceneVector.h" #include "DgmlElementDictionary.h" #include "GeoWriter.h" #include "GeoSceneParser.h" using namespace Marble; class TestGeoSceneWriter : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void saveFile_data(); void saveFile(); void saveAndLoad_data(); void saveAndLoad(); void saveAndCompare_data(); void saveAndCompare(); void writeHeadTag(); private: QDir dgmlPath; QMap > parsers; }; Q_DECLARE_METATYPE( QSharedPointer ) void TestGeoSceneWriter::initTestCase() { QStringList dgmlFilters; dgmlFilters << "*.dgml"; dgmlPath = QDir( DGML_PATH ); dgmlPath.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); foreach( const QString &dgmlDirname, dgmlPath.entryList() ) { qDebug() << dgmlDirname; QDir dataDir(dgmlPath.absoluteFilePath(dgmlDirname)); dataDir.setNameFilters( dgmlFilters ); //check there are dgml files in the data dir if( dataDir.count() == 0 ) { continue; } //test the loading of each file in the data dir foreach( const QString &filename, dataDir.entryList(dgmlFilters, QDir::Files) ){ //Add example files QFile file( dataDir.filePath(filename)); //Verify file existence QVERIFY( file.exists() ); //Make the parsers GeoSceneParser* parser = new GeoSceneParser( GeoScene_DGML ); QSharedPointerparserPointer ( parser ); //Open the files and verify QVERIFY( file.open( QIODevice::ReadOnly ) ); //Parser and verify QVERIFY2(parser->read(&file), filename.toLatin1().constData()); parsers.insert( dataDir.filePath(filename), parserPointer ); //close files file.close(); } } } void TestGeoSceneWriter::saveFile_data() { QTest::addColumn >( "parser" ); QMap >::iterator itpoint = parsers.begin(); QMap >::iterator const endpoint = parsers.end(); for (; itpoint != endpoint; ++itpoint ) { QTest::newRow(itpoint.key().toLatin1().constData()) << itpoint.value(); } } void TestGeoSceneWriter::saveFile() { QFETCH( QSharedPointer, parser ); //attempt to save a file using the GeoWriter QTemporaryFile tempFile; GeoWriter writer; writer.setDocumentType( dgml::dgmlTag_nameSpace20 ); // Open file in right mode QVERIFY( tempFile.open() ); QVERIFY( writer.write( &tempFile, (dynamic_cast(parser->activeDocument() ) ) ) ); } void TestGeoSceneWriter::saveAndLoad_data() { QTest::addColumn >( "parser" ); QMap >::iterator itpoint = parsers.begin(); QMap >::iterator const endpoint = parsers.end(); for (; itpoint != endpoint; ++itpoint ) { QTest::newRow(itpoint.key().toLatin1().constData()) << itpoint.value(); } } void TestGeoSceneWriter::saveAndLoad() { //Save the file and then verify loading it again QFETCH( QSharedPointer, parser ); QTemporaryFile tempFile; GeoWriter writer; writer.setDocumentType( dgml::dgmlTag_nameSpace20 ); // Open file in right mode QVERIFY( tempFile.open() ); QVERIFY( writer.write( &tempFile, (dynamic_cast(parser->activeDocument() ) ) ) ); GeoSceneParser resultParser( GeoScene_DGML ); tempFile.reset(); QVERIFY( resultParser.read( &tempFile ) ); } void TestGeoSceneWriter::saveAndCompare_data() { QTest::addColumn >("parser"); QTest::addColumn("original"); QMap >::iterator itpoint = parsers.begin(); QMap >::iterator const endpoint = parsers.end(); for (; itpoint != endpoint; ++itpoint ) { QTest::newRow(itpoint.key().toLatin1().constData()) << itpoint.value() << itpoint.key(); } } void TestGeoSceneWriter::saveAndCompare() { //save the file and compare it to the original QFETCH( QSharedPointer, parser ); QFETCH( QString, original ); //attempt to save a file using the GeoWriter QTemporaryFile tempFile; GeoWriter writer; //FIXME: a better way to do this? writer.setDocumentType( dgml::dgmlTag_nameSpace20 ); // Open file in right mode QVERIFY( tempFile.open() ); QVERIFY( writer.write( &tempFile, (dynamic_cast(parser->activeDocument() ) ) ) ); QFile file( original ); QVERIFY( file.open( QIODevice::ReadOnly ) ); QVERIFY( tempFile.reset() ); QTextStream oldFile( &file ); QTextStream newFile( &tempFile ); QVERIFY( newFile.readAll().simplified().compare( oldFile.readAll().simplified() ) ); } void TestGeoSceneWriter::writeHeadTag() { GeoSceneDocument *document = new GeoSceneDocument; GeoSceneHead* head = document->head(); head->setName( "Test Map" ); head->setTheme( "testmap" ); head->setTarget( "earth" ); GeoSceneIcon* icon = document->head()->icon(); icon->setPixmap( "preview.jpg" ); GeoSceneZoom* zoom = document->head()->zoom(); zoom->setMaximum( 1000 ); zoom->setMaximum( 500 ); zoom->setDiscrete( true ); GeoSceneTileDataset* texture = new GeoSceneTileDataset( "map" ); texture->setSourceDir( "earth/testmap" ); texture->setFileFormat( "png" ); - texture->setProjection( GeoSceneTileDataset::Equirectangular ); + texture->setTileProjection(GeoSceneAbstractTileProjection::Equirectangular); texture->addDownloadUrl( QUrl( "http://download.kde.org/marble/map/{x}/{y}/{zoomLevel}" ) ); texture->addDownloadUrl( QUrl( "http://download.google.com/marble/map/{x}/{y}/{zoomLevel}" ) ); texture->addDownloadPolicy( DownloadBrowse, 20 ); texture->addDownloadPolicy( DownloadBulk, 20 ); texture->setMaximumTileLevel( 15 ); texture->setLevelZeroColumns( 2 ); texture->setLevelZeroRows( 2 ); GeoSceneGeodata* geodata = new GeoSceneGeodata( "cityplacemarks" ); geodata->setSourceFile( "baseplacemarks.kml" ); GeoSceneLayer* layer = new GeoSceneLayer( "testmap" ); layer->setBackend( "texture" ); layer->addDataset( texture ); GeoSceneLayer* secondLayer = new GeoSceneLayer( "standardplaces" ); secondLayer->setBackend( "geodata" ); secondLayer->addDataset( geodata ); GeoSceneLayer* thirdLayer = new GeoSceneLayer( "mwdbii" ); thirdLayer->setBackend( "vector" ); thirdLayer->setRole( "polyline" ); GeoSceneVector* vector = new GeoSceneVector( "pdiffborder" ); vector->setFeature( "border" ); vector->setFileFormat( "PNT" ); vector->setSourceFile( "earth/mwdbii/PDIFFBORDER.PNT" ); vector->pen().setColor( "#ffe300" ); thirdLayer->addDataset( vector ); GeoSceneMap* map = document->map(); map->addLayer( layer ); map->addLayer( secondLayer ); map->addLayer( thirdLayer ); GeoSceneSettings *settings = document->settings(); GeoSceneProperty *coorGrid = new GeoSceneProperty( "coordinate-grid" ); coorGrid->setValue( true ); coorGrid->setAvailable( true ); settings->addProperty( coorGrid ); GeoSceneProperty *overviewmap = new GeoSceneProperty( "overviewmap" ); overviewmap->setValue( true ); overviewmap->setAvailable( true ); settings->addProperty( overviewmap ); GeoSceneProperty *compass = new GeoSceneProperty( "compass" ); compass->setValue( true ); compass->setAvailable( true ); settings->addProperty( compass ); GeoSceneProperty *scalebar = new GeoSceneProperty( "scalebar" ); scalebar->setValue( true ); scalebar->setAvailable( true ); settings->addProperty( scalebar ); GeoSceneLegend* legend = document->legend(); GeoSceneSection* section = new GeoSceneSection( "areas" ); section->setHeading( "Areas" ); legend->addSection( section ); GeoSceneItem* sportsPitch = new GeoSceneItem( "sports_pitch" ); sportsPitch->setText( "Sports pitch" ); GeoSceneIcon* sportsPitchIcon = sportsPitch->icon(); sportsPitchIcon->setPixmap( "maps/earth/testmap/legend/sports_pitch.png" ); section->addItem( sportsPitch ); GeoSceneItem* sportsCentre = new GeoSceneItem( "sports_centre" ); sportsCentre->setText( "Sports centre" ); GeoSceneIcon* sportsCentreIcon = sportsCentre->icon(); sportsCentreIcon->setColor( "#00FF00" ); section->addItem( sportsCentre ); QTemporaryFile tempFile; tempFile.open(); GeoWriter writer; writer.setDocumentType( "http://edu.kde.org/marble/dgml/2.0" ); QVERIFY( writer.write( &tempFile, document ) ); //Parser and verify GeoSceneParser parser( GeoScene_DGML ); tempFile.reset(); QVERIFY( parser.read( &tempFile ) ); GeoSceneDocument *document2 = static_cast( parser.activeDocument() ); QTemporaryFile tempFile2; tempFile2.open(); GeoWriter writer2; writer2.setDocumentType( "http://edu.kde.org/marble/dgml/2.0" ); QVERIFY( writer2.write( &tempFile2, document2 ) ); tempFile.reset(); QTextStream file( &tempFile ); tempFile2.reset(); QTextStream file2( &tempFile2 ); QVERIFY( file.readAll().simplified().compare( file2.readAll().simplified() ) ); delete document; } QTEST_MAIN( TestGeoSceneWriter ) #include "TestGeoSceneWriter.moc" diff --git a/tests/TestTileProjection.cpp b/tests/TestTileProjection.cpp new file mode 100644 index 000000000..19f27bd5b --- /dev/null +++ b/tests/TestTileProjection.cpp @@ -0,0 +1,540 @@ +/* + Copyright 2016 Friedrich W. H. Kossebau + + This file is part of the KDE project + + This library is free software you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + aint with this library see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "TestUtils.h" + +#include +#include +#include +#include + + +namespace Marble +{ + +class TileProjectionTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testTypeEquirect(); + void testTypeMercator(); + + void testLevelZeroColumnsRowsEquirect(); + void testLevelZeroColumnsRowsMercator(); + + void testTileIndexesEquirect_data(); + void testTileIndexesEquirect(); + void testTileIndexesMercator_data(); + void testTileIndexesMercator(); + + void testGeoCoordinatesEquirect_data(); + void testGeoCoordinatesEquirect(); + void testGeoCoordinatesMercator_data(); + void testGeoCoordinatesMercator(); + +private: + void testLevelZeroColumnsRows(GeoSceneAbstractTileProjection& projection); +}; + + +void TileProjectionTest::testLevelZeroColumnsRows(GeoSceneAbstractTileProjection& projection) +{ + // test default + QCOMPARE(projection.levelZeroColumns(), 1); + QCOMPARE(projection.levelZeroRows(), 1); + + // test setting a different value + const int levelZeroColumns = 4; + const int levelZeroRows = 6; + + projection.setLevelZeroColumns(levelZeroColumns); + projection.setLevelZeroRows(levelZeroRows); + + QCOMPARE(projection.levelZeroColumns(), levelZeroColumns); + QCOMPARE(projection.levelZeroRows(), levelZeroRows); +} + +void TileProjectionTest::testLevelZeroColumnsRowsEquirect() +{ + GeoSceneEquirectTileProjection projection; + testLevelZeroColumnsRows(projection); +} + +void TileProjectionTest::testLevelZeroColumnsRowsMercator() +{ + GeoSceneMercatorTileProjection projection; + testLevelZeroColumnsRows(projection); +} + +void TileProjectionTest::testTypeEquirect() +{ + GeoSceneEquirectTileProjection projection; + QCOMPARE(projection.type(), GeoSceneAbstractTileProjection::Equirectangular); +} + +void TileProjectionTest::testTypeMercator() +{ + GeoSceneMercatorTileProjection projection; + QCOMPARE(projection.type(), GeoSceneAbstractTileProjection::Mercator); +} + + +void TileProjectionTest::testTileIndexesEquirect_data() +{ + QTest::addColumn("westLon"); + QTest::addColumn("northLat"); + QTest::addColumn("eastLon"); + QTest::addColumn("southLat"); + QTest::addColumn("zoomLevel"); + QTest::addColumn("expectedTileXWest"); + QTest::addColumn("expectedTileYNorth"); + QTest::addColumn("expectedTileXEast"); + QTest::addColumn("expectedTileYSouth"); + + // zoomlevel zero: 1 tile + // bounds matching the tile map + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 0 + << 0 << 0 << 0 << 0; + // bounds inside the 1 tile + addRow() << qreal(-M_PI*0.5) << qreal(+M_PI * 0.25) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 0 + << 0 << 0 << 0 << 0; + // bounds west and north on tile map borders, with normal border values + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 0 + << 0 << 0 << 0 << 0; + // bounds west and north on tile map borders, with border values from other map border sides + addRow() << qreal(+M_PI) << qreal(-M_PI * 0.5) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 0 + << 0 << 0 << 0 << 0; + + // zoomlevel 1: 2 tiles per dimension + // bounds matching the tile map + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 1 + << 0 << 0 << 1 << 1; + // bounds inside the 4 tiles + addRow() << qreal(-M_PI*0.5) << qreal(+M_PI * 0.25) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 1 + << 0 << 0 << 1 << 1; + // bounds matching the most north-west tile, with normal border values + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(0) << qreal(0) + << 1 + << 0 << 0 << 0 << 0; + // bounds matching the most north-west tile, with border values from other map border sides + addRow() << qreal(+M_PI) << qreal(-M_PI * 0.5) + << qreal(0) << qreal(0) + << 1 + << 0 << 0 << 0 << 0; + // bounds matching the most south-east tile, with normal border values + addRow() << qreal(0) << qreal(0) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 1 + << 1 << 1 << 1 << 1; + // bounds matching the most south-east tile, with border values from other map border sides + addRow() << qreal(0) << qreal(0) + << qreal(-M_PI) << qreal(+M_PI * 0.5) + << 1 + << 1 << 1 << 1 << 1; + + // zoomlevel 9: 2^8==512 tiles per dimension + // bounds matching the tile map + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 9 + << 0 << 0 << 511 << 511; + // bounds inside the outer tiles + addRow() << qreal(-M_PI*(511/512.0)) << qreal(+M_PI * 0.5 * (511/512.0)) + << qreal(+M_PI*(511/512.0)) << qreal(-M_PI * 0.5 * (511/512.0)) + << 9 + << 0 << 0 << 511 << 511; + // bounds matching the most north-west tile, with normal border values + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(-M_PI*(255/256.0)) << qreal(+M_PI * 0.5 *(255/256.0)) + << 9 + << 0 << 0 << 0 << 0; + // bounds matching the most north-west tile, with border values from other map border sides + addRow() << qreal(+M_PI) << qreal(-M_PI * 0.5) + << qreal(-M_PI*(255/256.0)) << qreal(+M_PI * 0.5 *(255/256.0)) + << 9 + << 0 << 0 << 0 << 0; + // bounds matching the most south-east tile, with normal border values + addRow() << qreal(+M_PI*(255/256.0)) << qreal(-M_PI * 0.5 *(255/256.0)) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 9 + << 511 << 511 << 511 << 511; + // bounds matching the most south-east tile, with border values from other map border sides + addRow() << qreal(+M_PI*(255/256.0)) << qreal(-M_PI * 0.5 *(255/256.0)) + << qreal(-M_PI) << qreal(+M_PI * 0.5) + << 9 + << 511 << 511 << 511 << 511; +} + + +void TileProjectionTest::testTileIndexesEquirect() +{ + QFETCH(qreal, westLon); + QFETCH(qreal, northLat); + QFETCH(qreal, eastLon); + QFETCH(qreal, southLat); + QFETCH(int, zoomLevel); + QFETCH(int, expectedTileXWest); + QFETCH(int, expectedTileYNorth); + QFETCH(int, expectedTileXEast); + QFETCH(int, expectedTileYSouth); + + GeoDataLatLonBox latLonBox(northLat, southLat, eastLon, westLon); + + GeoSceneEquirectTileProjection projection; + + int tileXWest; + int tileYNorth; + int tileXEast; + int tileYSouth; + + projection.tileIndexes(latLonBox, zoomLevel, tileXWest, tileYNorth, tileXEast, tileYSouth); + + QCOMPARE(tileXWest, expectedTileXWest); + QCOMPARE(tileYNorth, expectedTileYNorth); + QCOMPARE(tileXEast, expectedTileXEast); + QCOMPARE(tileYSouth, expectedTileYSouth); +} + + +void TileProjectionTest::testTileIndexesMercator_data() +{ + QTest::addColumn("westLon"); + QTest::addColumn("northLat"); + QTest::addColumn("eastLon"); + QTest::addColumn("southLat"); + QTest::addColumn("zoomLevel"); + QTest::addColumn("expectedTileXWest"); + QTest::addColumn("expectedTileYNorth"); + QTest::addColumn("expectedTileXEast"); + QTest::addColumn("expectedTileYSouth"); + + // zoomlevel zero: 1 tile + // bounds matching the tile map up to 90 degree latitude + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 0 + << 0 << 0 << 0 << 0; + // bounds matching the tile map with 85 degree latitude limit + addRow() << qreal(-M_PI) << qreal(85.0 * DEG2RAD) + << qreal(+M_PI) << qreal(-85.0 * DEG2RAD) + << 0 + << 0 << 0 << 0 << 0; + // bounds inside the 1 tile + addRow() << qreal(-M_PI*0.5) << qreal(+M_PI * 0.25) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 0 + << 0 << 0 << 0 << 0; + // bounds west and north on tile map borders, with normal border values + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 0 + << 0 << 0 << 0 << 0; + // bounds west and north on tile map borders, with border values from other map border sides + addRow() << qreal(+M_PI) << qreal(-M_PI * 0.5) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 0 + << 0 << 0 << 0 << 0; + + // zoomlevel 1: 2 tiles per dimension + // bounds matching the tile map up to 90 degree latitude + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 1 + << 0 << 0 << 1 << 1; + // bounds matching the tile map with 85 degree latitude limit + addRow() << qreal(-M_PI) << qreal(85.0 * DEG2RAD) + << qreal(+M_PI) << qreal(-85.0 * DEG2RAD) + << 1 + << 0 << 0 << 1 << 1; + // bounds inside the 4 tiles + addRow() << qreal(-M_PI*0.5) << qreal(+M_PI * 0.25) + << qreal(+M_PI*0.5) << qreal(-M_PI * 0.25) + << 1 + << 0 << 0 << 1 << 1; + // bounds matching the most north-west tile, with normal border values + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(0) << qreal(0) + << 1 + << 0 << 0 << 0 << 0; + // bounds matching the most north-west tile, with border values from other map border sides + addRow() << qreal(+M_PI) << qreal(-M_PI * 0.5) + << qreal(0) << qreal(0) + << 1 + << 0 << 0 << 0 << 0; + // bounds matching the most south-east tile, with normal border values + addRow() << qreal(0) << qreal(0) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 1 + << 1 << 1 << 1 << 1; + // bounds matching the most south-east tile, with border values from other map border sides + addRow() << qreal(0) << qreal(0) + << qreal(-M_PI) << qreal(+M_PI * 0.5) + << 1 + << 1 << 1 << 1 << 1; + + // zoomlevel 9: 2^8==512 tiles per dimension + // GeoSceneMercatorTileProjection bounds latitude value at +/- 85.0 degree (so not at 85.05113), + // which results in some tiles missed at the outer sides. + // bounds matching the tile map up to 90 degree latitude + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 9 + << 0 << 5 << 511 << 506; + // bounds matching the tile map with 85 degree latitude limit + addRow() << qreal(-M_PI) << qreal(85.0 * DEG2RAD) + << qreal(+M_PI) << qreal(-85.0 * DEG2RAD) + << 9 + << 0 << 5 << 511 << 506; + // bounds inside the outer tiles + addRow() << qreal(-M_PI*(511/512.0)) << qreal(+M_PI * 0.5 * (511/512.0)) + << qreal(+M_PI*(511/512.0)) << qreal(-M_PI * 0.5 * (511/512.0)) + << 9 + << 0 << 5 << 511 << 506; + // bounds matching the most north-west tile, with normal border values + addRow() << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(-M_PI*(255/256.0)) << qreal(+M_PI * 0.5 *(255/256.0)) + << 9 + << 0 << 5 << 0 << 5; + // bounds matching the most north-west tile, with border values from other map border sides + addRow() << qreal(+M_PI) << qreal(-M_PI * 0.5) + << qreal(-M_PI*(255/256.0)) << qreal(+M_PI * 0.5 *(255/256.0)) + << 9 + << 0 << 5 << 0 << 5; + // bounds matching the most south-east tile, with normal border values + addRow() << qreal(+M_PI*(255/256.0)) << qreal(-M_PI * 0.5 *(255/256.0)) + << qreal(+M_PI) << qreal(-M_PI * 0.5) + << 9 + << 511 << 506 << 511 << 506; + // bounds matching the most south-east tile, with border values from other map border sides + addRow() << qreal(+M_PI*(255/256.0)) << qreal(-M_PI * 0.5 *(255/256.0)) + << qreal(-M_PI) << qreal(+M_PI * 0.5) + << 9 + << 511 << 506 << 511 << 506; +} + + +void TileProjectionTest::testTileIndexesMercator() +{ + QFETCH(qreal, westLon); + QFETCH(qreal, northLat); + QFETCH(qreal, eastLon); + QFETCH(qreal, southLat); + QFETCH(int, zoomLevel); + QFETCH(int, expectedTileXWest); + QFETCH(int, expectedTileYNorth); + QFETCH(int, expectedTileXEast); + QFETCH(int, expectedTileYSouth); + + GeoDataLatLonBox latLonBox(northLat, southLat, eastLon, westLon); + + GeoSceneMercatorTileProjection projection; + + int tileXWest; + int tileYNorth; + int tileXEast; + int tileYSouth; + + projection.tileIndexes(latLonBox, zoomLevel, tileXWest, tileYNorth, tileXEast, tileYSouth); + + QCOMPARE(tileXWest, expectedTileXWest); + QCOMPARE(tileYNorth, expectedTileYNorth); + QCOMPARE(tileXEast, expectedTileXEast); + QCOMPARE(tileYSouth, expectedTileYSouth); +} + + +void TileProjectionTest::testGeoCoordinatesEquirect_data() +{ + QTest::addColumn("tileX"); + QTest::addColumn("tileY"); + QTest::addColumn("zoomLevel"); + QTest::addColumn("expectedWesternTileEdgeLon"); + QTest::addColumn("expectedNorthernTileEdgeLat"); + QTest::addColumn("expectedEasternTileEdgeLon"); + QTest::addColumn("expectedSouthernTileEdgeLat"); + + // zoomlevel zero: 1 tile + addRow() << 0 << 0 << 0 << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(-M_PI * 0.5); + + // zoomlevel 1: 2 tiles per dimension + addRow() << 0 << 0 << 1 << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(0) << qreal(0); + addRow() << 0 << 1 << 1 << qreal(-M_PI) << qreal(0) + << qreal(0) << qreal(-M_PI * 0.5); + addRow() << 1 << 0 << 1 << qreal(0) << qreal(+M_PI * 0.5) + << qreal(+M_PI) << qreal(0); + addRow() << 1 << 1 << 1 << qreal(0) << qreal(0) + << qreal(+M_PI) << qreal(-M_PI * 0.5); + + // zoomlevel 9: 2^8==512 tiles per dimension + addRow() << 0 << 0 << 9 << qreal(-M_PI) << qreal(+M_PI * 0.5) + << qreal(-M_PI * (255/256.0)) << qreal(+M_PI * 0.5 * (255/256.0)); + addRow() << 0 << 256 << 9 << qreal(-M_PI) << qreal(0) + << qreal(-M_PI * (255/256.0)) << qreal(-M_PI * 0.5 * (1/256.0)); + addRow() << 256 << 0 << 9 << qreal(0) << qreal(+M_PI * 0.5) + << qreal(M_PI * (1/256.0)) << qreal(+M_PI * 0.5 * (255/256.0)); + addRow() << 511 << 511 << 9 << qreal(M_PI * (255/256.0)) << qreal(-M_PI * 0.5 * (255/256.0)) + << qreal(+M_PI) << qreal(-M_PI * 0.5); +} + + +void TileProjectionTest::testGeoCoordinatesEquirect() +{ + QFETCH(int, tileX); + QFETCH(int, tileY); + QFETCH(int, zoomLevel); + QFETCH(qreal, expectedWesternTileEdgeLon); + QFETCH(qreal, expectedNorthernTileEdgeLat); + QFETCH(qreal, expectedEasternTileEdgeLon); + QFETCH(qreal, expectedSouthernTileEdgeLat); + + GeoSceneEquirectTileProjection projection; + + qreal westernTileEdgeLon; + qreal northernTileEdgeLat; + + // method variant with zoomLevel, tileX, tileY + projection.geoCoordinates(zoomLevel, tileX, tileY, westernTileEdgeLon, northernTileEdgeLat); + + QCOMPARE(westernTileEdgeLon, expectedWesternTileEdgeLon); + QCOMPARE(northernTileEdgeLat, expectedNorthernTileEdgeLat); + + // method variants with GeoDataLatLonBox + GeoDataLatLonBox latLonBox; + + projection.geoCoordinates(zoomLevel, tileX, tileY, latLonBox); + + QCOMPARE(latLonBox.west(), expectedWesternTileEdgeLon); + QCOMPARE(latLonBox.north(), expectedNorthernTileEdgeLat); + QCOMPARE(latLonBox.east(), expectedEasternTileEdgeLon); + QCOMPARE(latLonBox.south(), expectedSouthernTileEdgeLat); + + TileId tileId(QStringLiteral("testmap"), zoomLevel, tileX, tileY); + GeoDataLatLonBox latLonBox2; + + projection.geoCoordinates(tileId, latLonBox2); + + QCOMPARE(latLonBox2.west(), expectedWesternTileEdgeLon); + QCOMPARE(latLonBox2.north(), expectedNorthernTileEdgeLat); + QCOMPARE(latLonBox2.east(), expectedEasternTileEdgeLon); + QCOMPARE(latLonBox2.south(), expectedSouthernTileEdgeLat); +} + +void TileProjectionTest::testGeoCoordinatesMercator_data() +{ + QTest::addColumn("tileX"); + QTest::addColumn("tileY"); + QTest::addColumn("zoomLevel"); + QTest::addColumn("expectedWesternTileEdgeLon"); + QTest::addColumn("expectedNorthernTileEdgeLat"); + QTest::addColumn("expectedEasternTileEdgeLon"); + QTest::addColumn("expectedSouthernTileEdgeLat"); + + const qreal absMaxLat = DEG2RAD * 85.05113; + + // zoomlevel zero: 1 tile + addRow() << 0 << 0 << 0 << qreal(-M_PI) << qreal(+absMaxLat) + << qreal(+M_PI) << qreal(-absMaxLat); + + // zoomlevel 1: 2 tiles per dimension + addRow() << 0 << 0 << 1 << qreal(-M_PI) << qreal(+absMaxLat) + << qreal(0) << qreal(0); + addRow() << 0 << 1 << 1 << qreal(-M_PI) << qreal(0) + << qreal(0) << qreal(-absMaxLat); + addRow() << 1 << 0 << 1 << qreal(0) << qreal(+absMaxLat) + << qreal(+M_PI) << qreal(0); + addRow() << 1 << 1 << 1 << qreal(0) << qreal(0) + << qreal(+M_PI) << qreal(-absMaxLat); + + // zoomlevel 9: 2^8==512 tiles per dimension + addRow() << 0 << 0 << 9 << qreal(-M_PI) << qreal(+absMaxLat) + << qreal(-M_PI * (255/256.0)) << qreal(+1.48336); + addRow() << 0 << 256 << 9 << qreal(-M_PI) << qreal(0) + << qreal(-M_PI * (255/256.0)) << qreal(-0.0122715); + addRow() << 256 << 0 << 9 << qreal(0) << qreal(+absMaxLat) + << qreal(M_PI * (1/256.0)) << qreal(+1.48336); + addRow() << 511 << 511 << 9 << qreal(M_PI * (255/256.0)) << qreal(-1.48336) + << qreal(+M_PI) << qreal(-absMaxLat); +} + + +void TileProjectionTest::testGeoCoordinatesMercator() +{ + QFETCH(int, tileX); + QFETCH(int, tileY); + QFETCH(int, zoomLevel); + QFETCH(qreal, expectedWesternTileEdgeLon); + QFETCH(qreal, expectedNorthernTileEdgeLat); + QFETCH(qreal, expectedEasternTileEdgeLon); + QFETCH(qreal, expectedSouthernTileEdgeLat); + + GeoSceneMercatorTileProjection projection; + + qreal westernTileEdgeLon; + qreal northernTileEdgeLat; + + // method variant with zoomLevel, tileX, tileY + projection.geoCoordinates(zoomLevel, tileX, tileY, westernTileEdgeLon, northernTileEdgeLat); + + QCOMPARE(westernTileEdgeLon, expectedWesternTileEdgeLon); + QFUZZYCOMPARE(northernTileEdgeLat, expectedNorthernTileEdgeLat, 0.00001); + + // method variants with GeoDataLatLonBox + GeoDataLatLonBox latLonBox; + + projection.geoCoordinates(zoomLevel, tileX, tileY, latLonBox); + + QCOMPARE(latLonBox.west(), expectedWesternTileEdgeLon); + QFUZZYCOMPARE(latLonBox.north(), expectedNorthernTileEdgeLat, 0.00001); + QCOMPARE(latLonBox.east(), expectedEasternTileEdgeLon); + QFUZZYCOMPARE(latLonBox.south(), expectedSouthernTileEdgeLat, 0.00001); + + TileId tileId(QStringLiteral("testmap"), zoomLevel, tileX, tileY); + GeoDataLatLonBox latLonBox2; + + projection.geoCoordinates(tileId, latLonBox2); + + QCOMPARE(latLonBox2.west(), expectedWesternTileEdgeLon); + QFUZZYCOMPARE(latLonBox2.north(), expectedNorthernTileEdgeLat, 0.00001); + QCOMPARE(latLonBox2.east(), expectedEasternTileEdgeLon); + QFUZZYCOMPARE(latLonBox2.south(), expectedSouthernTileEdgeLat, 0.00001); +} + +} // namespace Marble + +QTEST_MAIN(Marble::TileProjectionTest) + +#include "TestTileProjection.moc" diff --git a/tools/vectorosm-tilecreator/TileDirectory.cpp b/tools/vectorosm-tilecreator/TileDirectory.cpp index 1edcf5e3b..c7141db41 100644 --- a/tools/vectorosm-tilecreator/TileDirectory.cpp +++ b/tools/vectorosm-tilecreator/TileDirectory.cpp @@ -1,482 +1,487 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Dennis Nienhüser // #include "TileDirectory.h" #include "TileIterator.h" #include #include "MarbleZipReader.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace Marble { TileDirectory::TileDirectory(TileType tileType, const QString &cacheDir, ParsingRunnerManager &manager, QString const &extension, int maxZoomLevel) : m_cacheDir(cacheDir), m_baseDir(), m_manager(manager), m_zoomLevel(-1), m_tileX(-1), m_tileY(-1), m_tagZoomLevel(-1), m_extension(extension), m_tileType(tileType), m_landmassFile("land-polygons-split-4326.zip"), m_maxZoomLevel(maxZoomLevel) { if (m_tileType == Landmass) { m_zoomLevel = 7; m_baseDir = QString("%1/landmass/%2").arg(cacheDir).arg(m_zoomLevel); QString const landmassDir = QString("%1/land-polygons-split-4326").arg(cacheDir); m_inputFile = QString("%1/land_polygons.shp").arg(landmassDir); auto const landmassZip = QString("%1/%2").arg(m_cacheDir).arg(m_landmassFile); if (!QFileInfo(landmassZip).exists()) { QString const url = QString("http://data.openstreetmapdata.com/%1").arg(m_landmassFile); download(url, landmassZip); } if (!QFileInfo(landmassDir).exists()) { MarbleZipReader unzip(landmassZip); if (!unzip.extractAll(m_cacheDir)) { qWarning() << "Failed to extract" << landmassZip << "to" << m_cacheDir; } } } else { m_zoomLevel = 10; m_baseDir = QString("%1/osm/%2").arg(cacheDir).arg(m_zoomLevel); } } TileId TileDirectory::tileFor(int zoomLevel, int tileX, int tileY) const { int const zoomDiff = zoomLevel - m_zoomLevel; int const x = tileX >> zoomDiff; int const y = tileY >> zoomDiff; return TileId(QString(), m_zoomLevel, x, y); } QSharedPointer TileDirectory::load(int zoomLevel, int tileX, int tileY) { auto const tile = tileFor(zoomLevel, tileX, tileY); if (tile.x() == m_tileX && tile.y() == m_tileY) { return m_landmass; } m_tileX = tile.x(); m_tileY = tile.y(); QString const filename = QString("%1/%2/%3.%4").arg(m_baseDir).arg(tile.x()).arg(tile.y()).arg(m_extension); m_landmass = open(filename, m_manager); return m_landmass; } void TileDirectory::setInputFile(const QString &filename) { m_inputFile = filename; if (m_tileType == OpenStreetMap) { QUrl url = QUrl(filename); if (url.scheme().isEmpty()) { // local file m_boundingBox = boundingBox(m_inputFile); } else { // remote file: check if already downloaded QFileInfo cacheFile = QString("%1/%2").arg(m_cacheDir).arg(url.fileName()); if (!cacheFile.exists()) { download(filename, cacheFile.absoluteFilePath()); } m_inputFile = cacheFile.absoluteFilePath(); QString polyFile = QUrl(filename).fileName(); polyFile.remove("-latest.osm.pbf").append(".poly"); QString poly = QString("%1/%2").arg(url.adjusted(QUrl::RemoveFilename).toString()).arg(polyFile); QString const polyTarget = QString("%1/%2").arg(m_cacheDir).arg(polyFile); if (!QFileInfo(polyTarget).exists()) { download(poly, polyTarget); } setBoundingPolygon(polyTarget); } } } GeoDataDocument* TileDirectory::clip(int zoomLevel, int tileX, int tileY) { QSharedPointer oldMap = m_landmass; load(zoomLevel, tileX, tileY); if (!m_clipper || oldMap != m_landmass || m_tagZoomLevel != zoomLevel) { setTagZoomLevel(zoomLevel); GeoDataDocument* input = m_tagsFilter ? m_tagsFilter->accepted() : m_landmass.data(); if (input) { m_clipper = QSharedPointer(new VectorClipper(input, m_maxZoomLevel)); } } return m_clipper ? m_clipper->clipTo(zoomLevel, tileX, tileY) : nullptr; } QString TileDirectory::name() const { return QString("%1/%2/%3").arg(m_zoomLevel).arg(m_tileX).arg(m_tileY); } QSharedPointer TileDirectory::open(const QString &filename, ParsingRunnerManager &manager) { // Timeout is set to 10 min. If the file is reaaally huge, set it to something bigger. GeoDataDocument* map = manager.openFile(filename, DocumentRole::ConversionDocument, 600000); if(map == nullptr) { qWarning() << "File" << filename << "couldn't be loaded."; } QSharedPointer result = QSharedPointer(map); return result; } QStringList TileDirectory::tagsFilteredIn(int zoomLevel) const { QStringList tags; tags << "highway=motorway" << "highway=motorway_link"; tags << "highway=trunk" << "highway=trunk_link"; tags << "highway=primary" << "highway=primary_link"; tags << "highway=secondary" << "highway=secondary_link"; if (zoomLevel >= 13) { tags << "highway=tertiary" << "highway=tertiary_link"; tags << "highway=unclassified"; tags << "public_transport=station"; tags << "railway=light_rail"; tags << "railway=monorail"; tags << "railway=narrow_gauge"; tags << "railway=preserved"; tags << "railway=rail"; tags << "railway=subway"; tags << "railway=tram"; tags << "natural=scrub"; tags << "natural=heath"; tags << "natural=grassland"; tags << "natural=glacier"; tags << "natural=beach"; tags << "natural=coastline"; tags << "natural=water"; tags << "natural=wood"; tags << "leisure=stadium"; tags << "tourism=alpine_hut"; tags << "waterway=river"; tags << "waterway=stream"; tags << "waterway=canal"; tags << "place=suburb"; tags << "place=village"; tags << "natural=peak"; } if (zoomLevel <= 13) { tags << "landuse=commercial"; tags << "landuse=farmland"; tags << "landuse=farmyard"; tags << "landuse=forest"; tags << "landuse=industrial"; tags << "landuse=meadow"; tags << "landuse=military"; tags << "landuse=recreation_ground"; tags << "landuse=residential"; tags << "landuse=retail"; } if (zoomLevel >= 15) { tags << "highway=residential"; tags << "highway=track"; tags << "landuse=*"; tags << "leisure=pitch"; tags << "leisure=swimming_area"; tags << "place=hamlet"; tags << "place=isolated_dwelling"; tags << "man_made=beacon"; tags << "man_made=bridge"; tags << "man_made=campanile"; tags << "man_made=chimney"; tags << "man_made=communications_tower"; tags << "man_made=cross"; tags << "man_made=gasometer"; tags << "man_made=lighthouse"; tags << "man_made=tower"; tags << "man_made=water_tower"; tags << "man_made=windmill"; } tags << "leisure=nature_reserve"; tags << "leisure=park"; tags << "place=city"; tags << "place=town"; tags << "place=locality"; tags << "boundary=administrative"; tags << "boundary=political"; tags << "boundary=national_park"; tags << "boundary=protected_area"; return tags; } void TileDirectory::setTagZoomLevel(int zoomLevel) { m_tagZoomLevel = zoomLevel; if (m_tileType == OpenStreetMap) { if (m_tagZoomLevel < 17) { QStringList const tags = tagsFilteredIn(m_tagZoomLevel); m_tagsFilter = QSharedPointer(new TagsFilter(m_landmass.data(), tags)); } else { m_tagsFilter.clear(); } } } void TileDirectory::download(const QString &url, const QString &target) { m_download = QSharedPointer(new Download); m_download->target = target; m_download->reply = m_downloadManager.get(QNetworkRequest(QUrl(url))); connect(m_download->reply, SIGNAL(downloadProgress(qint64,qint64)), m_download.data(), SLOT(updateProgress(qint64,qint64))); connect(m_download->reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateProgress())); QEventLoop loop; connect(m_download->reply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); cout << endl; } void TileDirectory::printProgress(double progress, int barWidth) { int const position = barWidth * progress; cout << " [" << string(position, '=') << ">"; cout << string(barWidth-position, ' ') << "] " << std::right << setw(3) << int(progress * 100.0) << "%"; } GeoDataLatLonBox TileDirectory::boundingBox() const { return m_boundingBox; } void TileDirectory::setBoundingBox(const GeoDataLatLonBox &boundingBox) { m_boundingBox = boundingBox; } void TileDirectory::setBoundingPolygon(const QString &file) { m_boundingPolygon.clear(); QFile input(file); QString country = "Unknown"; if ( input.open( QFile::ReadOnly ) ) { QTextStream stream( &input ); country = stream.readLine(); double lat( 0.0 ), lon( 0.0 ); GeoDataLinearRing box; while ( !stream.atEnd() ) { bool inside = true; QString line = stream.readLine().trimmed(); QStringList entries = line.split( QLatin1Char( ' ' ), QString::SkipEmptyParts ); if ( entries.size() == 1 ) { if (entries.first() == QLatin1String("END") && inside) { inside = false; if (!box.isEmpty()) { m_boundingPolygon << box; box = GeoDataLinearRing(); } } else if (entries.first() == QLatin1String("END") && !inside) { qDebug() << "END not expected here"; } else if ( entries.first().startsWith( QLatin1String( "!" ) ) ) { qDebug() << "Warning: Negative polygons not supported, skipping"; } else { //int number = entries.first().toInt(); inside = true; } } else if ( entries.size() == 2 ) { lon = entries.first().toDouble(); lat = entries.last().toDouble(); GeoDataCoordinates point( lon, lat, 0.0, GeoDataCoordinates::Degree ); box << point; } else { qDebug() << "Warning: Ignoring line in" << file << "with" << entries.size() << "fields:" << line; } } } if (!m_boundingPolygon.isEmpty()) { m_boundingBox = GeoDataLatLonBox::fromLineString(m_boundingPolygon.first()); for (int i=1, n=m_boundingPolygon.size(); i map; QSharedPointer clipper; TileIterator iter(m_boundingBox, m_zoomLevel); qint64 count = 0; foreach(auto const &tileId, iter) { ++count; QString const outputDir = QString("%1/%2").arg(m_baseDir).arg(tileId.x()); QString const outputFile = QString("%1/%2.o5m").arg(outputDir).arg(tileId.y()); if (QFileInfo(outputFile).exists()) { continue; } printProgress(count / double(iter.total())); cout << " Creating " << (m_tileType == OpenStreetMap ? "osm" : "landmass"); cout << " cache tile " << count << "/" << iter.total() << " ("; cout << m_zoomLevel << "/" << tileId.x() << "/" << tileId.y() << ") \r"; cout.flush(); QDir().mkpath(outputDir); if (m_tileType == OpenStreetMap) { QString const output = QString("-o=%1").arg(outputFile); - int const N = pow(2, m_zoomLevel); - double const minLon = TileId::tileX2lon(tileId.x(), N) * RAD2DEG; - double const maxLon = TileId::tileX2lon(tileId.x()+1, N) * RAD2DEG; - double const maxLat = TileId::tileY2lat(tileId.y(), N) * RAD2DEG; - double const minLat = TileId::tileY2lat(tileId.y()+1, N) * RAD2DEG; + + GeoDataLatLonBox tileBoundary; + m_tileProjection.geoCoordinates(m_zoomLevel, tileId.x(), tileId.y(), tileBoundary); + + double const minLon = tileBoundary.west(GeoDataCoordinates::Degree); + double const maxLon = tileBoundary.east(GeoDataCoordinates::Degree); + double const maxLat = tileBoundary.north(GeoDataCoordinates::Degree); + double const minLat = tileBoundary.south(GeoDataCoordinates::Degree); QString const bbox = QString("-b=%1,%2,%3,%4").arg(minLon).arg(minLat).arg(maxLon).arg(maxLat); QProcess osmconvert; osmconvert.start("osmconvert", QStringList() << "--drop-author" << "--drop-version" << "--complete-ways" << "--complex-ways" << bbox << output << m_inputFile); osmconvert.waitForFinished(10*60*1000); if (osmconvert.exitCode() != 0) { qWarning() << osmconvert.readAllStandardError(); qWarning() << "osmconvert failed: " << osmconvert.errorString(); } } else { if (!map) { map = open(m_inputFile, m_manager); clipper = QSharedPointer(new VectorClipper(map.data(), m_zoomLevel)); } auto tile = clipper->clipTo(m_zoomLevel, tileId.x(), tileId.y()); if (!GeoDataDocumentWriter::write(outputFile, *tile)) { qWarning() << "Failed to write tile" << outputFile; } } } printProgress(1.0); cout << " " << (m_tileType == OpenStreetMap ? "osm" : "landmass") << " cache tiles complete." << endl; } bool TileDirectory::contains(const TileId &tile) const { - int const N = pow(2, tile.zoomLevel()); - double const west = TileId::tileX2lon(tile.x(), N); - double const east = TileId::tileX2lon(tile.x()+1, N); - double const north = TileId::tileY2lat(tile.y(), N); - double const south = TileId::tileY2lat(tile.y()+1, N); + GeoDataLatLonBox tileBoundary; + m_tileProjection.geoCoordinates(m_zoomLevel, tile.x(), tile.y(), tileBoundary); + + double const west = tileBoundary.west(); + double const east = tileBoundary.east(); + double const north = tileBoundary.north(); + double const south = tileBoundary.south(); QVector bounds; bounds << GeoDataCoordinates(west, north); bounds << GeoDataCoordinates(east, north); bounds << GeoDataCoordinates(east, south); bounds << GeoDataCoordinates(west, south); if (m_boundingPolygon.isEmpty()) { foreach(auto const &coordinate, bounds) { if (m_boundingBox.contains(coordinate)) { return true; } } return false; } foreach(auto const &ring, m_boundingPolygon) { foreach(auto const &coordinate, bounds) { if (ring.contains(coordinate)) { return true; } } } return false; } void TileDirectory::updateProgress() { double const progress = m_download->total > 0 ? m_download->received / double(m_download->total) : 0.0; printProgress(progress); cout << " "; cout << std::fixed << std::setprecision(1) << m_download->received / 1000000.0 << '/'; cout << std::fixed << std::setprecision(1) << m_download->total / 1000000.0 << " MB"; cout << " Downloading " << m_download->reply->url().fileName().toStdString(); cout << " \r"; cout.flush(); } void TileDirectory::handleFinishedDownload(const QString &filename, const QString &id) { qDebug() << "File " << filename << "(" << id << ") has been downloaded."; } GeoDataLatLonBox TileDirectory::boundingBox(const QString &filename) const { QProcess osmconvert; osmconvert.start("osmconvert", QStringList() << "--out-statistics" << filename); osmconvert.waitForFinished(10*60*1000); QStringList const output = QString(osmconvert.readAllStandardOutput()).split('\n'); GeoDataLatLonBox boundingBox; foreach(QString const &line, output) { if (line.startsWith("lon min:")) { boundingBox.setWest(line.mid(8).toDouble(), GeoDataCoordinates::Degree); } else if (line.startsWith("lon max")) { boundingBox.setEast(line.mid(8).toDouble(), GeoDataCoordinates::Degree); } else if (line.startsWith("lat min:")) { boundingBox.setSouth(line.mid(8).toDouble(), GeoDataCoordinates::Degree); } else if (line.startsWith("lat max:")) { boundingBox.setNorth(line.mid(8).toDouble(), GeoDataCoordinates::Degree); } } return boundingBox; } void Download::updateProgress(qint64 received_, qint64 total_) { received = received_; total = total_; QString const tempFile = QString("%1.download").arg(target); if (!m_file.isOpen()) { m_file.setFileName(tempFile); m_file.open(QFile::WriteOnly); } m_file.write(reply->readAll()); if (reply->isFinished()) { m_file.flush(); m_file.close(); QFile::rename(tempFile, target); } } } #include "moc_TileDirectory.cpp" diff --git a/tools/vectorosm-tilecreator/TileDirectory.h b/tools/vectorosm-tilecreator/TileDirectory.h index 9f985e1b4..52de4d50c 100644 --- a/tools/vectorosm-tilecreator/TileDirectory.h +++ b/tools/vectorosm-tilecreator/TileDirectory.h @@ -1,109 +1,111 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Dennis Nienhüser // #ifndef MARBLE_TILEDIRECTORY_H #define MARBLE_TILEDIRECTORY_H #include "VectorClipper.h" #include "TagsFilter.h" #include #include #include +#include #include #include #include #include class QNetworkReply; namespace Marble { class Download : public QObject { Q_OBJECT public: QString target; QNetworkReply* reply; qint64 received; qint64 total; public Q_SLOTS: void updateProgress(qint64 received, qint64 total); private: QFile m_file; }; class TileDirectory : public QObject { Q_OBJECT public: enum TileType { Landmass, OpenStreetMap }; TileDirectory(TileType tileType, const QString &cacheDir, ParsingRunnerManager &manager, const QString &extension, int maxZoomLevel); QSharedPointer load(int zoomLevel, int tileX, int tileY); void setInputFile(const QString &filename); TileId tileFor(int zoomLevel, int tileX, int tileY) const; GeoDataDocument *clip(int zoomLevel, int tileX, int tileY); QString name() const; static QSharedPointer open(const QString &filename, ParsingRunnerManager &manager); GeoDataLatLonBox boundingBox(const QString &filename) const; GeoDataLatLonBox boundingBox() const; void setBoundingBox(const GeoDataLatLonBox &boundingBox); void setBoundingPolygon(const QString &filename); void createTiles() const; bool contains(const TileId &tile) const; static void printProgress(double progress, int barWidth=40); private Q_SLOTS: void updateProgress(); void handleFinishedDownload(const QString &filename, const QString &id); private: QStringList tagsFilteredIn(int zoomLevel) const; void setTagZoomLevel(int zoomLevel); void download(const QString &url, const QString &target); QString m_cacheDir; QString m_baseDir; ParsingRunnerManager &m_manager; QSharedPointer m_landmass; int m_zoomLevel; int m_tileX; int m_tileY; int m_tagZoomLevel; QString m_extension; QSharedPointer m_clipper; QSharedPointer m_tagsFilter; TileType m_tileType; QString m_inputFile; GeoDataLatLonBox m_boundingBox; QVector m_boundingPolygon; QNetworkAccessManager m_downloadManager; + GeoSceneMercatorTileProjection m_tileProjection; QString m_landmassFile; QSharedPointer m_download; int m_maxZoomLevel; }; } #endif diff --git a/tools/vectorosm-tilecreator/TileIterator.cpp b/tools/vectorosm-tilecreator/TileIterator.cpp index d3b9900e2..51bc85725 100644 --- a/tools/vectorosm-tilecreator/TileIterator.cpp +++ b/tools/vectorosm-tilecreator/TileIterator.cpp @@ -1,90 +1,91 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Dennis Nienhüser // #include "TileIterator.h" -#include "TileId.h" -#include "VectorTileModel.h" + +#include "GeoSceneMercatorTileProjection.h" #include namespace Marble { TileIterator::const_iterator const TileIterator::s_end = TileIterator(); const TileIterator &TileIterator::operator*() { return *this; } bool TileIterator::operator!=(const TileIterator::const_iterator &other) const { return m_state != other.m_state; } TileIterator::const_iterator &TileIterator::operator++() { if (m_state.x() >= m_bounds.right()) { m_state.setX(m_bounds.left()); if (m_state.y() < m_bounds.bottom()) { ++m_state.ry(); } else { *this = s_end; } } else { ++m_state.rx(); } return *this; } TileIterator::TileIterator(const GeoDataLatLonBox &latLonBox, int zoomLevel) { - qreal north, west, south, east; - latLonBox.boundaries(north, south, east, west); - unsigned int N = pow(2, zoomLevel); - m_bounds.setLeft(TileId::lon2tileX(west, N)); - m_bounds.setTop(TileId::lat2tileY(north, N)); - m_bounds.setRight(qMin(N-1, TileId::lon2tileX(east, N))); - m_bounds.setBottom(TileId::lat2tileY(south, N)); + int westX, northY, eastX, southY; + GeoSceneMercatorTileProjection tileProjection; + tileProjection.tileIndexes(latLonBox, zoomLevel, westX, northY, eastX, southY); + + m_bounds.setLeft(westX); + m_bounds.setTop(northY); + m_bounds.setRight(eastX); + m_bounds.setBottom(southY); } TileIterator::const_iterator TileIterator::begin() const { TileIterator iter = *this; iter.m_state = iter.m_bounds.topLeft(); return iter; } TileIterator::const_iterator TileIterator::end() const { return s_end; } int TileIterator::x() const { return m_state.x(); } int TileIterator::y() const { return m_state.y(); } int TileIterator::total() const { return m_bounds.width() * m_bounds.height(); } TileIterator::TileIterator() : m_state(-1, -1) { // nothing to do } } diff --git a/tools/vectorosm-tilecreator/VectorClipper.cpp b/tools/vectorosm-tilecreator/VectorClipper.cpp index 3298297f0..da290b81d 100644 --- a/tools/vectorosm-tilecreator/VectorClipper.cpp +++ b/tools/vectorosm-tilecreator/VectorClipper.cpp @@ -1,353 +1,348 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 David Kolozsvari // #include "VectorClipper.h" #include "BaseClipper.h" #include "TileId.h" #include "GeoDataTypes.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataPolygon.h" #include "GeoDataPlacemark.h" #include "OsmPlacemarkData.h" #include "OsmObjectManager.h" #include "TileCoordsPyramid.h" #include "clipper/clipper.hpp" #include #include #include namespace Marble { VectorClipper::VectorClipper(GeoDataDocument* document, int maxZoomLevel) : BaseFilter(document), m_maxZoomLevel(maxZoomLevel) { foreach(auto placemark, placemarks()) { // Select zoom level such that the placemark fits in a single tile int zoomLevel; qreal north, south, east, west; placemark->geometry()->latLonAltBox().boundaries(north, south, east, west); for (zoomLevel = maxZoomLevel; zoomLevel >= 0; --zoomLevel) { if (TileId::fromCoordinates(GeoDataCoordinates(west, north), zoomLevel) == TileId::fromCoordinates(GeoDataCoordinates(east, south), zoomLevel)) { break; } } TileId const key = TileId::fromCoordinates(GeoDataCoordinates(west, north), zoomLevel); m_items[key] << placemark; } } GeoDataDocument *VectorClipper::clipTo(const GeoDataLatLonBox &tileBoundary) { bool const useBaseClipper = false; if (useBaseClipper) { return clipToBaseClipper(tileBoundary); } GeoDataDocument* tile = new GeoDataDocument(); auto const clip = clipPath(tileBoundary); foreach (GeoDataPlacemark const * placemark, potentialIntersections(tileBoundary)) { GeoDataGeometry const * const geometry = placemark ? placemark->geometry() : nullptr; if (geometry && tileBoundary.intersects(geometry->latLonAltBox())) { if(geometry->nodeType() == GeoDataTypes::GeoDataPolygonType) { clipPolygon(placemark, clip, tile); } else if (geometry->nodeType() == GeoDataTypes::GeoDataLineStringType) { clipString(placemark, clip, tile); } else if (geometry->nodeType() == GeoDataTypes::GeoDataLinearRingType) { clipString(placemark, clip, tile); } else { tile->append(new GeoDataPlacemark(*placemark)); } } } return tile; } GeoDataDocument *VectorClipper::clipToBaseClipper(const GeoDataLatLonBox &tileBoundary) { GeoDataDocument* tile = new GeoDataDocument(); BaseClipper clipper; clipper.initClipRect(tileBoundary, 20); foreach (GeoDataPlacemark const * placemark, placemarks()) { if(placemark && placemark->geometry() && tileBoundary.intersects(placemark->geometry()->latLonAltBox())) { if( placemark->geometry()->nodeType() == GeoDataTypes::GeoDataPolygonType) { GeoDataPolygon const * marblePolygon = static_cast(placemark->geometry()); int index = -1; using PolygonPair = QPair; QVector newMarblePolygons; bool const isClockwise = marblePolygon->outerBoundary().isClockwise(); QPolygonF outerBoundaryPolygon = BaseClipper::toQPolygon(marblePolygon->outerBoundary(), !isClockwise); QVector outerBoundaries; clipper.clipPolyObject(outerBoundaryPolygon, outerBoundaries, true); // qDebug() << "Size(s) after:"; foreach(const QPolygonF& polygon, outerBoundaries) { // qDebug() << polygon.size(); PolygonPair newMarblePolygon = qMakePair(new GeoDataPlacemark(), polygon); GeoDataPolygon* geometry = new GeoDataPolygon(); geometry->setOuterBoundary(BaseClipper::toLineString(polygon, !isClockwise)); newMarblePolygon.first->setGeometry(geometry); copyTags(*placemark, *(newMarblePolygon.first)); OsmObjectManager::initializeOsmData(newMarblePolygon.first); placemark->osmData().memberReference(index); copyTags(placemark->osmData().memberReference(index), newMarblePolygon.first->osmData().memberReference(index)); newMarblePolygons.push_back(newMarblePolygon); } foreach (const GeoDataLinearRing& innerBoundary, marblePolygon->innerBoundaries()) { ++index; bool const isClockwise = innerBoundary.isClockwise(); QPolygonF innerBoundaryPolygon = BaseClipper::toQPolygon(innerBoundary, !isClockwise); QVector clippedPolygons; clipper.clipPolyObject(innerBoundaryPolygon, clippedPolygons, true); foreach (const QPolygonF& polygon, clippedPolygons) { bool isAdded = false; foreach (const PolygonPair& newMarblePolygon, newMarblePolygons) { if(!polygon.intersected(newMarblePolygon.second).isEmpty()) { GeoDataPolygon* geometry = static_cast(newMarblePolygon.first->geometry()); geometry->appendInnerBoundary(BaseClipper::toLineString(polygon, !isClockwise)); OsmObjectManager::initializeOsmData(newMarblePolygon.first); OsmPlacemarkData& innerRingData = newMarblePolygon.first->osmData().memberReference(geometry->innerBoundaries().size()-1); OsmPlacemarkData const & placemarkInnerRingData = placemark->osmData().memberReference(index); copyTags(placemarkInnerRingData, innerRingData); isAdded = true; } } if(!isAdded) { qDebug() << "Polygon not added. Why?"; } } } foreach (const PolygonPair& newMarblePolygon, newMarblePolygons) { tile->append(newMarblePolygon.first); } } else if (placemark->geometry()->nodeType() == GeoDataTypes::GeoDataLineStringType) { GeoDataLineString const * marbleWay = static_cast(placemark->geometry()); QVector clippedPolygons; QPolygonF way = BaseClipper::toQPolygon(*marbleWay); clipper.clipPolyObject(way, clippedPolygons, false); // qDebug() << "Size before:" << way.size(); // qDebug() << "Size(s) after:"; foreach(const QPolygonF& polygon, clippedPolygons) { // qDebug() << polygon.size(); GeoDataLineString* newMarbleWay = new GeoDataLineString(BaseClipper::toLineString(polygon)); GeoDataPlacemark* newPlacemark = new GeoDataPlacemark(); newPlacemark->setGeometry(newMarbleWay); copyTags(*placemark, *newPlacemark); tile->append(newPlacemark); } } else if (placemark->geometry()->nodeType() == GeoDataTypes::GeoDataLinearRingType) { GeoDataLinearRing const * marbleClosedWay = static_cast(placemark->geometry()); QVector clippedPolygons; QPolygonF closedWay = BaseClipper::toQPolygon(*marbleClosedWay); clipper.clipPolyObject(closedWay, clippedPolygons, true); // qDebug() << "Size(s) after:"; foreach(const QPolygonF& polygon, clippedPolygons) { // qDebug() << polygon.size(); GeoDataLinearRing* newMarbleWay = new GeoDataLinearRing(BaseClipper::toLineString(polygon)); GeoDataPlacemark* newPlacemark = new GeoDataPlacemark(); newPlacemark->setGeometry(newMarbleWay); copyTags(*placemark, *newPlacemark); tile->append(newPlacemark); } } else { tile->append(new GeoDataPlacemark(*placemark)); } } } return tile; } QVector VectorClipper::potentialIntersections(const GeoDataLatLonBox &box) const { qreal north, south, east, west; box.boundaries(north, south, east, west); TileId const topLeft = TileId::fromCoordinates(GeoDataCoordinates(west, north), m_maxZoomLevel); TileId const bottomRight = TileId::fromCoordinates(GeoDataCoordinates(east, south), m_maxZoomLevel); QRect rect; rect.setCoords(topLeft.x(), topLeft.y(), bottomRight.x(), bottomRight.y()); TileCoordsPyramid pyramid(0, m_maxZoomLevel); pyramid.setBottomLevelCoords(rect); QVector result; for (int level = pyramid.topLevel(), maxLevel = pyramid.bottomLevel(); level <= maxLevel; ++level) { int x1, y1, x2, y2; pyramid.coords(level).getCoords(&x1, &y1, &x2, &y2); for (int x = x1; x <= x2; ++x) { for (int y = y1; y <= y2; ++y) { result << m_items.value(TileId(0, level, x, y)); } } } return result; } GeoDataDocument *VectorClipper::clipTo(unsigned int zoomLevel, unsigned int tileX, unsigned int tileY) { - unsigned int N = pow(2, zoomLevel); GeoDataLatLonBox tileBoundary; - qreal north = TileId::tileY2lat(tileY, N); - qreal south = TileId::tileY2lat(tileY+1, N); - qreal west = TileId::tileX2lon(tileX, N); - qreal east = TileId::tileX2lon(tileX+1, N); - tileBoundary.setBoundaries(north, south, east, west); + m_tileProjection.geoCoordinates(zoomLevel, tileX, tileY, tileBoundary); GeoDataDocument *tile = clipTo(tileBoundary); QString tileName = QString("%1/%2/%3").arg(zoomLevel).arg(tileX).arg(tileY); tile->setName(tileName); return tile; } ClipperLib::Path VectorClipper::clipPath(const GeoDataLatLonBox &box) const { using namespace ClipperLib; Path path; int const steps = 20; double x = box.west() * m_scale; double const horizontalStep = (box.east() * m_scale - x) / (steps-1); double y = box.north() * m_scale; double const verticalStep = (box.south() * m_scale - y) / (steps-1); for (int i=0; i(placemark->geometry()); using namespace ClipperLib; Path path; foreach(auto const & node, polygon->outerBoundary()) { path << IntPoint(qRound64(node.longitude() * m_scale), qRound64(node.latitude() * m_scale)); } Clipper clipper; clipper.PreserveCollinear(true); clipper.AddPath(tileBoundary, ptClip, true); clipper.AddPath(path, ptSubject, true); Paths paths; clipper.Execute(ctIntersection, paths); foreach(const auto &path, paths) { GeoDataLinearRing outerRing; foreach(const auto &point, path) { outerRing << GeoDataCoordinates(double(point.X) / m_scale, double(point.Y) / m_scale); } GeoDataPlacemark* newPlacemark = new GeoDataPlacemark; GeoDataPolygon* newPolygon = new GeoDataPolygon; newPolygon->setOuterBoundary(outerRing); newPlacemark->setGeometry(newPolygon); int index = -1; OsmObjectManager::initializeOsmData(newPlacemark); copyTags(*placemark, *newPlacemark); copyTags(placemark->osmData().memberReference(index), newPlacemark->osmData().memberReference(index)); auto const & innerBoundaries = polygon->innerBoundaries(); for (index = 0; index < innerBoundaries.size(); ++index) { clipper.Clear(); clipper.AddPath(path, ptClip, true); Path innerPath; foreach(auto const & node, innerBoundaries.at(index)) { innerPath << IntPoint(qRound64(node.longitude() * m_scale), qRound64(node.latitude() * m_scale)); } clipper.AddPath(innerPath, ptSubject, true); Paths innerPaths; clipper.Execute(ctIntersection, innerPaths); foreach(auto const &innerPath, innerPaths) { GeoDataLinearRing innerRing; foreach(const auto &point, innerPath) { innerRing << GeoDataCoordinates(double(point.X) / m_scale, double(point.Y) / m_scale); } newPolygon->appendInnerBoundary(innerRing); OsmObjectManager::initializeOsmData(newPlacemark); copyTags(placemark->osmData().memberReference(index), newPlacemark->osmData().memberReference(newPolygon->innerBoundaries().size()-1)); } } document->append(newPlacemark); } } void VectorClipper::copyTags(const GeoDataPlacemark &source, GeoDataPlacemark &target) const { copyTags(source.osmData(), target.osmData()); } void VectorClipper::copyTags(const OsmPlacemarkData &originalPlacemarkData, OsmPlacemarkData &targetOsmData) const { for (auto iter=originalPlacemarkData.tagsBegin(), end=originalPlacemarkData.tagsEnd(); iter != end; ++iter) { targetOsmData.addTag(iter.key(), iter.value()); } } } diff --git a/tools/vectorosm-tilecreator/VectorClipper.h b/tools/vectorosm-tilecreator/VectorClipper.h index cd91614e1..fe7c6e31c 100644 --- a/tools/vectorosm-tilecreator/VectorClipper.h +++ b/tools/vectorosm-tilecreator/VectorClipper.h @@ -1,89 +1,91 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 David Kolozsvari // #ifndef TINYPLANETPROCESSOR_H #define TINYPLANETPROCESSOR_H #include "BaseFilter.h" #include "OsmPlacemarkData.h" #include #include "GeoDataPlacemark.h" #include +#include #include "clipper/clipper.hpp" namespace Marble { class GeoDataLinearRing; class VectorClipper : public BaseFilter { public: VectorClipper(GeoDataDocument* document, int maxZoomLevel); GeoDataDocument* clipTo(const GeoDataLatLonBox &box); GeoDataDocument* clipTo(unsigned int zoomLevel, unsigned int tileX, unsigned int tileY); private: GeoDataDocument* clipToBaseClipper(const GeoDataLatLonBox &box); QVector potentialIntersections(const GeoDataLatLonBox &box) const; ClipperLib::Path clipPath(const GeoDataLatLonBox &box) const; template void clipString(const GeoDataPlacemark *placemark, const ClipperLib::Path &tileBoundary, GeoDataDocument* document) { const T* ring = static_cast(placemark->geometry()); using namespace ClipperLib; Path path; foreach(auto const & node, *ring) { path << IntPoint(qRound64(node.longitude() * m_scale), qRound64(node.latitude() * m_scale)); } Clipper clipper; clipper.PreserveCollinear(true); bool const isClosed = ring->isClosed(); clipper.AddPath(tileBoundary, ptClip, true); clipper.AddPath(path, ptSubject, isClosed); PolyTree tree; clipper.Execute(ctIntersection, tree); Paths paths; if (isClosed) { ClosedPathsFromPolyTree(tree, paths); } else { OpenPathsFromPolyTree(tree, paths); } foreach(const auto &path, paths) { T* ring = new T; foreach(const auto &point, path) { *ring << GeoDataCoordinates(double(point.X) / m_scale, double(point.Y) / m_scale); } GeoDataPlacemark* newPlacemark = new GeoDataPlacemark(); newPlacemark->setGeometry(ring); copyTags(*placemark, *newPlacemark); document->append(newPlacemark); } } void clipPolygon(const GeoDataPlacemark *placemark, const ClipperLib::Path &tileBoundary, GeoDataDocument* document); void copyTags(const GeoDataPlacemark &source, GeoDataPlacemark &target) const; void copyTags(const OsmPlacemarkData &originalPlacemarkData, OsmPlacemarkData& targetOsmData) const; static qint64 const m_scale = 10000000000; QMap > m_items; int m_maxZoomLevel; + GeoSceneMercatorTileProjection m_tileProjection; }; } #endif // TINYPLANETPROCESSOR_H