diff --git a/src/lib/marble/DownloadRegion.cpp b/src/lib/marble/DownloadRegion.cpp --- a/src/lib/marble/DownloadRegion.cpp +++ b/src/lib/marble/DownloadRegion.cpp @@ -56,10 +56,10 @@ 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 ) diff --git a/src/lib/marble/MapThemeManager.cpp b/src/lib/marble/MapThemeManager.cpp --- a/src/lib/marble/MapThemeManager.cpp +++ b/src/lib/marble/MapThemeManager.cpp @@ -563,7 +563,7 @@ texture->setSourceDir( sourceDir ); texture->setFileFormat( extension ); texture->setInstallMap( fileName ); - texture->setProjection(GeoSceneTileDataset::Equirectangular); + texture->setTileProjection(GeoSceneAbstractTileProjection::Equirectangular); layer->addDataset(texture); diff --git a/src/lib/marble/MapWizard.cpp b/src/lib/marble/MapWizard.cpp --- a/src/lib/marble/MapWizard.cpp +++ b/src/lib/marble/MapWizard.cpp @@ -789,7 +789,7 @@ 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 ) @@ -803,7 +803,7 @@ 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 ) @@ -813,7 +813,7 @@ 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; diff --git a/src/lib/marble/MergedLayerDecorator.h b/src/lib/marble/MergedLayerDecorator.h --- a/src/lib/marble/MergedLayerDecorator.h +++ b/src/lib/marble/MergedLayerDecorator.h @@ -57,7 +57,7 @@ int tileRowCount( int level ) const; - GeoSceneTextureTileDataset::Projection tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; QSize tileSize() const; diff --git a/src/lib/marble/MergedLayerDecorator.cpp b/src/lib/marble/MergedLayerDecorator.cpp --- a/src/lib/marble/MergedLayerDecorator.cpp +++ b/src/lib/marble/MergedLayerDecorator.cpp @@ -145,11 +145,11 @@ 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 @@ -217,7 +217,8 @@ /* 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 ) { @@ -245,13 +246,14 @@ 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 { @@ -583,10 +585,16 @@ 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 ); + } } } } diff --git a/src/lib/marble/ScanlineTextureMapperContext.h b/src/lib/marble/ScanlineTextureMapperContext.h --- a/src/lib/marble/ScanlineTextureMapperContext.h +++ b/src/lib/marble/ScanlineTextureMapperContext.h @@ -73,7 +73,7 @@ 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; @@ -123,9 +123,9 @@ 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 diff --git a/src/lib/marble/ScanlineTextureMapperContext.cpp b/src/lib/marble/ScanlineTextureMapperContext.cpp --- a/src/lib/marble/ScanlineTextureMapperContext.cpp +++ b/src/lib/marble/ScanlineTextureMapperContext.cpp @@ -24,7 +24,7 @@ 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 ) ), diff --git a/src/lib/marble/ServerLayout.cpp b/src/lib/marble/ServerLayout.cpp --- a/src/lib/marble/ServerLayout.cpp +++ b/src/lib/marble/ServerLayout.cpp @@ -95,7 +95,8 @@ 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 ); @@ -123,7 +124,8 @@ 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" ); @@ -160,10 +162,10 @@ 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"; } diff --git a/src/lib/marble/StackedTileLoader.h b/src/lib/marble/StackedTileLoader.h --- a/src/lib/marble/StackedTileLoader.h +++ b/src/lib/marble/StackedTileLoader.h @@ -74,7 +74,7 @@ int tileRowCount( int level ) const; - GeoSceneTextureTileDataset::Projection tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; QSize tileSize() const; diff --git a/src/lib/marble/StackedTileLoader.cpp b/src/lib/marble/StackedTileLoader.cpp --- a/src/lib/marble/StackedTileLoader.cpp +++ b/src/lib/marble/StackedTileLoader.cpp @@ -77,9 +77,9 @@ 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 diff --git a/src/lib/marble/TileId.h b/src/lib/marble/TileId.h --- a/src/lib/marble/TileId.h +++ b/src/lib/marble/TileId.h @@ -21,8 +21,6 @@ namespace Marble { class GeoDataCoordinates; -class GeoSceneTileDataset; -class GeoDataLatLonBox; class MARBLE_EXPORT TileId { @@ -39,14 +37,8 @@ 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; diff --git a/src/lib/marble/TileId.cpp b/src/lib/marble/TileId.cpp --- a/src/lib/marble/TileId.cpp +++ b/src/lib/marble/TileId.cpp @@ -12,9 +12,7 @@ // Own #include "TileId.h" -#include "MarbleMath.h" -#include "GeoDataLatLonBox.h" -#include "GeoSceneTileDataset.h" +#include "GeoDataCoordinates.h" #include @@ -36,32 +34,6 @@ { } -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 ) { @@ -89,36 +61,6 @@ 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 diff --git a/src/lib/marble/VectorTileModel.cpp b/src/lib/marble/VectorTileModel.cpp --- a/src/lib/marble/VectorTileModel.cpp +++ b/src/lib/marble/VectorTileModel.cpp @@ -115,27 +115,29 @@ 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 ); } @@ -197,7 +199,8 @@ 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); } diff --git a/src/lib/marble/geodata/CMakeLists.txt b/src/lib/marble/geodata/CMakeLists.txt --- a/src/lib/marble/geodata/CMakeLists.txt +++ b/src/lib/marble/geodata/CMakeLists.txt @@ -77,6 +77,9 @@ ) 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 diff --git a/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp b/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp --- a/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp +++ b/src/lib/marble/geodata/handlers/dgml/DgmlProjectionTagHandler.cpp @@ -47,15 +47,16 @@ // 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.h b/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.h new file mode 100644 --- /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/GeoSceneAbstractTileProjection.cpp b/src/lib/marble/geodata/scene/GeoSceneAbstractTileProjection.cpp new file mode 100644 --- /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/GeoSceneEquirectTileProjection.h b/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.h new file mode 100644 --- /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/GeoSceneEquirectTileProjection.cpp b/src/lib/marble/geodata/scene/GeoSceneEquirectTileProjection.cpp new file mode 100644 --- /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/GeoSceneMercatorTileProjection.h b/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.h new file mode 100644 --- /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/GeoSceneMercatorTileProjection.cpp b/src/lib/marble/geodata/scene/GeoSceneMercatorTileProjection.cpp new file mode 100644 --- /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/GeoSceneTileDataset.h b/src/lib/marble/geodata/scene/GeoSceneTileDataset.h --- a/src/lib/marble/geodata/scene/GeoSceneTileDataset.h +++ b/src/lib/marble/geodata/scene/GeoSceneTileDataset.h @@ -20,6 +20,7 @@ #include #include "GeoSceneAbstractDataset.h" +#include "GeoSceneAbstractTileProjection.h" #include "MarbleGlobal.h" class QStringList; @@ -45,7 +46,6 @@ { public: enum StorageLayout { Marble, OpenStreetMap, TileMapService }; - enum Projection { Equirectangular, Mercator }; explicit GeoSceneTileDataset( const QString& name ); ~GeoSceneTileDataset(); @@ -84,8 +84,10 @@ 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 ); @@ -120,7 +122,7 @@ 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 diff --git a/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp b/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp --- a/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp +++ b/src/lib/marble/geodata/scene/GeoSceneTileDataset.cpp @@ -14,6 +14,8 @@ #include "GeoSceneTileDataset.h" #include "GeoSceneTypes.h" +#include "GeoSceneEquirectTileProjection.h" +#include "GeoSceneMercatorTileProjection.h" #include "DownloadPolicy.h" #include "MarbleDebug.h" @@ -37,17 +39,20 @@ 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 @@ -104,6 +109,7 @@ void GeoSceneTileDataset::setLevelZeroColumns( const int columns ) { m_levelZeroColumns = columns; + m_tileProjection->setLevelZeroColumns(m_levelZeroColumns); } int GeoSceneTileDataset::levelZeroRows() const @@ -114,6 +120,7 @@ void GeoSceneTileDataset::setLevelZeroRows( const int rows ) { m_levelZeroRows = rows; + m_tileProjection->setLevelZeroRows(m_levelZeroRows); } int GeoSceneTileDataset::maximumTileLevel() const @@ -208,14 +215,31 @@ } } -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 diff --git a/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp b/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp --- a/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp +++ b/src/lib/marble/geodata/writers/dgml/DgmlTextureTagWriter.cpp @@ -89,9 +89,10 @@ } 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(); diff --git a/src/lib/marble/layers/TextureLayer.h b/src/lib/marble/layers/TextureLayer.h --- a/src/lib/marble/layers/TextureLayer.h +++ b/src/lib/marble/layers/TextureLayer.h @@ -79,7 +79,7 @@ QSize tileSize() const; - GeoSceneTileDataset::Projection tileProjection() const; + GeoSceneAbstractTileProjection::Type tileProjectionType() const; int tileColumnCount( int level ) const; int tileRowCount( int level ) const; diff --git a/src/lib/marble/layers/TextureLayer.cpp b/src/lib/marble/layers/TextureLayer.cpp --- a/src/lib/marble/layers/TextureLayer.cpp +++ b/src/lib/marble/layers/TextureLayer.cpp @@ -412,7 +412,7 @@ 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 ); @@ -499,9 +499,9 @@ 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -109,6 +109,7 @@ 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} ) diff --git a/tests/TestGeoSceneWriter.cpp b/tests/TestGeoSceneWriter.cpp --- a/tests/TestGeoSceneWriter.cpp +++ b/tests/TestGeoSceneWriter.cpp @@ -219,7 +219,7 @@ 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 ); diff --git a/tests/TestTileProjection.cpp b/tests/TestTileProjection.cpp new file mode 100644 --- /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/TileIterator.cpp b/tools/vectorosm-tilecreator/TileIterator.cpp --- a/tools/vectorosm-tilecreator/TileIterator.cpp +++ b/tools/vectorosm-tilecreator/TileIterator.cpp @@ -9,8 +9,8 @@ // #include "TileIterator.h" -#include "TileId.h" -#include "VectorTileModel.h" + +#include "GeoSceneMercatorTileProjection.h" #include @@ -45,13 +45,14 @@ 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(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 diff --git a/tools/vectorosm-tilecreator/VectorClipper.h b/tools/vectorosm-tilecreator/VectorClipper.h --- a/tools/vectorosm-tilecreator/VectorClipper.h +++ b/tools/vectorosm-tilecreator/VectorClipper.h @@ -15,6 +15,7 @@ #include "OsmPlacemarkData.h" #include +#include namespace Marble { @@ -29,6 +30,9 @@ private: void copyTags(const GeoDataPlacemark &source, GeoDataPlacemark &target) const; void copyTags(const OsmPlacemarkData &originalPlacemarkData, OsmPlacemarkData& targetOsmData) const; + +private: + GeoSceneMercatorTileProjection m_tileProjection; }; } diff --git a/tools/vectorosm-tilecreator/VectorClipper.cpp b/tools/vectorosm-tilecreator/VectorClipper.cpp --- a/tools/vectorosm-tilecreator/VectorClipper.cpp +++ b/tools/vectorosm-tilecreator/VectorClipper.cpp @@ -168,13 +168,8 @@ 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);