blob: 18d92b00a10f0a38430e1e37c7d7249f65cc0f57 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Copyright (C) 2014 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtLocation module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgeotiledmapscene_p.h"
#include "qgeotiledmapscene_p_p.h"
#include "qgeocameradata_p.h"
#include "qabstractgeotilecache_p.h"
#include "qgeotilespec_p.h"
#include <QtPositioning/private/qdoublevector3d_p.h>
#include <QtPositioning/private/qwebmercator_p.h>
#include <QtCore/private/qobject_p.h>
#include <QtQuick/QQuickWindow>
#include <QtGui/QVector3D>
#include <cmath>
#include <QtPositioning/private/qlocationutils_p.h>
#include <QtPositioning/private/qdoublematrix4x4_p.h>
#include <QtPositioning/private/qwebmercator_p.h>
static QVector3D toVector3D(const QDoubleVector3D& in)
{
return QVector3D(in.x(), in.y(), in.z());
}
QT_BEGIN_NAMESPACE
QGeoTiledMapScene::QGeoTiledMapScene(QObject *parent)
: QObject(*new QGeoTiledMapScenePrivate(),parent)
{
}
QGeoTiledMapScene::~QGeoTiledMapScene()
{
}
void QGeoTiledMapScene::setScreenSize(const QSize &size)
{
Q_D(QGeoTiledMapScene);
d->m_screenSize = size;
}
void QGeoTiledMapScene::updateSceneParameters()
{
Q_D(QGeoTiledMapScene);
d->m_intZoomLevel = static_cast<int>(std::floor(d->m_cameraData.zoomLevel()));
const float delta = d->m_cameraData.zoomLevel() - d->m_intZoomLevel;
d->m_linearScaling = qAbs(delta) > 0.05 || d->isTiltedOrRotated();
d->m_sideLength = 1 << d->m_intZoomLevel;
d->m_mapEdgeSize = std::pow(2.0, d->m_cameraData.zoomLevel()) * d->m_tileSize;
}
void QGeoTiledMapScene::setTileSize(int tileSize)
{
Q_D(QGeoTiledMapScene);
if (d->m_tileSize == tileSize)
return;
d->m_tileSize = tileSize;
updateSceneParameters();
}
void QGeoTiledMapScene::setCameraData(const QGeoCameraData &cameraData)
{
Q_D(QGeoTiledMapScene);
d->m_cameraData = cameraData;
updateSceneParameters();
}
void QGeoTiledMapScene::setVisibleArea(const QRectF &visibleArea)
{
Q_D(QGeoTiledMapScene);
if (d->m_visibleArea == visibleArea)
return;
d->m_visibleArea = visibleArea;
updateSceneParameters();
}
void QGeoTiledMapScene::setVisibleTiles(const QSet<QGeoTileSpec> &tiles)
{
Q_D(QGeoTiledMapScene);
d->setVisibleTiles(tiles);
}
const QSet<QGeoTileSpec> &QGeoTiledMapScene::visibleTiles() const
{
Q_D(const QGeoTiledMapScene);
return d->m_visibleTiles;
}
void QGeoTiledMapScene::addTile(const QGeoTileSpec &spec, QSharedPointer<QGeoTileTexture> texture)
{
Q_D(QGeoTiledMapScene);
d->addTile(spec, texture);
}
QSet<QGeoTileSpec> QGeoTiledMapScene::texturedTiles()
{
Q_D(QGeoTiledMapScene);
QSet<QGeoTileSpec> textured;
for (auto it = d->m_textures.cbegin(); it != d->m_textures.cend(); ++it)
textured += it.value()->spec;
return textured;
}
void QGeoTiledMapScene::clearTexturedTiles()
{
Q_D(QGeoTiledMapScene);
d->m_textures.clear();
d->m_dropTextures = true;
}
QGeoTiledMapScenePrivate::QGeoTiledMapScenePrivate()
: QObjectPrivate(),
m_tileSize(0),
#ifdef QT_LOCATION_DEBUG
m_scaleFactor(1.0),
#else
m_scaleFactor(10.0),
#endif
m_intZoomLevel(0),
m_sideLength(0),
m_minTileX(-1),
m_minTileY(-1),
m_maxTileX(-1),
m_maxTileY(-1),
m_tileXWrapsBelow(0),
m_linearScaling(false),
m_dropTextures(false)
{
}
QGeoTiledMapScenePrivate::~QGeoTiledMapScenePrivate()
{
}
bool QGeoTiledMapScenePrivate::buildGeometry(const QGeoTileSpec &spec, QSGImageNode *imageNode, bool &overzooming)
{
overzooming = false;
int x = spec.x();
if (x < m_tileXWrapsBelow)
x += m_sideLength;
if ((x < m_minTileX)
|| (m_maxTileX < x)
|| (spec.y() < m_minTileY)
|| (m_maxTileY < spec.y())
|| (spec.zoom() != m_intZoomLevel)) {
return false;
}
double edge = m_scaleFactor * m_tileSize;
double x1 = (x - m_minTileX);
double x2 = x1 + 1.0;
double y1 = (m_minTileY - spec.y());
double y2 = y1 - 1.0;
x1 *= edge;
x2 *= edge;
y1 *= edge;
y2 *= edge;
imageNode->setRect(QRectF(QPointF(x1, y2), QPointF(x2, y1)));
imageNode->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically);
// Calculate the texture mapping, in case we are magnifying some lower ZL tile
const auto it = m_textures.find(spec); // This should be always found, but apparently sometimes it isn't, possibly due to memory shortage
if (it != m_textures.end()) {
if (it.value()->spec.zoom() < spec.zoom()) {
// Currently only using lower ZL tiles for the overzoom.
const int tilesPerTexture = 1 << (spec.zoom() - it.value()->spec.zoom());
const int mappedSize = imageNode->texture()->textureSize().width() / tilesPerTexture;
const int x = (spec.x() % tilesPerTexture) * mappedSize;
const int y = (spec.y() % tilesPerTexture) * mappedSize;
imageNode->setSourceRect(QRectF(x, y, mappedSize, mappedSize));
overzooming = true;
} else {
imageNode->setSourceRect(QRectF(QPointF(0,0), imageNode->texture()->textureSize()));
}
} else {
qWarning() << "!! buildGeometry: tileSpec not present in m_textures !!";
imageNode->setSourceRect(QRectF(QPointF(0,0), imageNode->texture()->textureSize()));
}
return true;
}
void QGeoTiledMapScenePrivate::addTile(const QGeoTileSpec &spec, QSharedPointer<QGeoTileTexture> texture)
{
if (!m_visibleTiles.contains(spec)) // Don't add the geometry if it isn't visible
return;
if (m_textures.contains(spec))
m_updatedTextures.append(spec);
m_textures.insert(spec, texture);
}
void QGeoTiledMapScenePrivate::setVisibleTiles(const QSet<QGeoTileSpec> &visibleTiles)
{
// work out the tile bounds for the new scene
updateTileBounds(visibleTiles);
// set up the gl camera for the new scene
setupCamera();
QSet<QGeoTileSpec> toRemove = m_visibleTiles - visibleTiles;
if (!toRemove.isEmpty())
removeTiles(toRemove);
m_visibleTiles = visibleTiles;
}
void QGeoTiledMapScenePrivate::removeTiles(const QSet<QGeoTileSpec> &oldTiles)
{
typedef QSet<QGeoTileSpec>::const_iterator iter;
iter i = oldTiles.constBegin();
iter end = oldTiles.constEnd();
for (; i != end; ++i) {
QGeoTileSpec tile = *i;
m_textures.remove(tile);
}
}
void QGeoTiledMapScenePrivate::updateTileBounds(const QSet<QGeoTileSpec> &tiles)
{
if (tiles.isEmpty()) {
m_minTileX = -1;
m_minTileY = -1;
m_maxTileX = -1;
m_maxTileY = -1;
return;
}
typedef QSet<QGeoTileSpec>::const_iterator iter;
iter i = tiles.constBegin();
iter end = tiles.constEnd();
// determine whether the set of map tiles crosses the dateline.
// A gap in the tiles indicates dateline crossing
bool hasFarLeft = false;
bool hasFarRight = false;
bool hasMidLeft = false;
bool hasMidRight = false;
for (; i != end; ++i) {
if ((*i).zoom() != m_intZoomLevel)
continue;
int x = (*i).x();
if (x == 0)
hasFarLeft = true;
else if (x == (m_sideLength - 1))
hasFarRight = true;
else if (x == ((m_sideLength / 2) - 1)) {
hasMidLeft = true;
} else if (x == (m_sideLength / 2)) {
hasMidRight = true;
}
}
// if dateline crossing is detected we wrap all x pos of tiles
// that are in the left half of the map.
m_tileXWrapsBelow = 0;
if (hasFarLeft && hasFarRight) {
if (!hasMidRight) {
m_tileXWrapsBelow = m_sideLength / 2;
} else if (!hasMidLeft) {
m_tileXWrapsBelow = (m_sideLength / 2) - 1;
}
}
// finally, determine the min and max bounds
i = tiles.constBegin();
QGeoTileSpec tile = *i;
int x = tile.x();
if (tile.x() < m_tileXWrapsBelow)
x += m_sideLength;
m_minTileX = x;
m_maxTileX = x;
m_minTileY = tile.y();
m_maxTileY = tile.y();
++i;
for (; i != end; ++i) {
tile = *i;
if (tile.zoom() != m_intZoomLevel)
continue;
int x = tile.x();
if (tile.x() < m_tileXWrapsBelow)
x += m_sideLength;
m_minTileX = qMin(m_minTileX, x);
m_maxTileX = qMax(m_maxTileX, x);
m_minTileY = qMin(m_minTileY, tile.y());
m_maxTileY = qMax(m_maxTileY, tile.y());
}
}
void QGeoTiledMapScenePrivate::setupCamera()
{
// NOTE: The following instruction is correct only because WebMercator is a square projection!
double f = m_screenSize.height();
// Using fraction of zoom level, z varies between [ m_tileSize , 2 * m_tileSize [
double z = std::pow(2.0, m_cameraData.zoomLevel() - m_intZoomLevel) * m_tileSize;
// calculate altitude that allows the visible map tiles
// to fit in the screen correctly (note that a larger f will cause
// the camera be higher, resulting in gray areas displayed around
// the tiles)
double altitude = f / (2.0 * z);
// calculate center
double edge = m_scaleFactor * m_tileSize;
// first calculate the camera center in map space in the range of 0 <-> sideLength (2^z)
QDoubleVector2D camCenterMercator = QWebMercator::coordToMercator(m_cameraData.center());
QDoubleVector3D center = (m_sideLength * camCenterMercator);
// wrap the center if necessary (due to dateline crossing)
if (center.x() < m_tileXWrapsBelow)
center.setX(center.x() + 1.0 * m_sideLength);
// work out where the camera center is w.r.t minimum tile bounds
center.setX(center.x() - 1.0 * m_minTileX);
center.setY(1.0 * m_minTileY - center.y());
// apply necessary scaling to the camera center
center *= edge;
// calculate eye
double apertureSize = 1.0;
if (m_cameraData.fieldOfView() != 90.0) //aperture(90 / 2) = 1
apertureSize = tan(QLocationUtils::radians(m_cameraData.fieldOfView()) * 0.5);
QDoubleVector3D eye = center;
eye.setZ(altitude * edge / apertureSize);
// calculate up
QDoubleVector3D view = eye - center;
QDoubleVector3D side = QDoubleVector3D::normal(view, QDoubleVector3D(0.0, 1.0, 0.0));
QDoubleVector3D up = QDoubleVector3D::normal(side, view);
// old bearing, tilt and roll code.
// Now using double matrices until distilling the transformation to QMatrix4x4
QDoubleMatrix4x4 mBearing;
// -1.0 * bearing removed, now map north goes in the bearing direction
mBearing.rotate(-1.0 * m_cameraData.bearing(), view);
up = mBearing * up;
QDoubleVector3D side2 = QDoubleVector3D::normal(up, view);
if (m_cameraData.tilt() > 0.01) {
QDoubleMatrix4x4 mTilt;
mTilt.rotate(m_cameraData.tilt(), side2);
eye = mTilt * view + center;
}
view = eye - center;
view.normalize();
side = QDoubleVector3D::normal(view, QDoubleVector3D(0.0, 1.0, 0.0));
up = QDoubleVector3D::normal(view, side2);
// QMatrix4x4 mRoll;
// mRoll.rotate(camera.roll(), view);
// up = mRoll * up;
// near plane and far plane
double nearPlane = 1.0;
// Clip plane. Used to be (altitude + 1.0) * edge. This does not affect the perspective. minimum value would be > 0.0
// Since, for some reasons possibly related to how QSG works, this clipping plane is unable to clip part of tiles,
// Instead of farPlane = (altitude + m_cameraData.clipDistance()) * edge , we use a fixed large clipDistance, and
// leave the clipping only in QGeoCameraTiles::createFrustum
double farPlane = (altitude + 10000.0) * edge;
m_cameraUp = up;
m_cameraCenter = center;
m_cameraEye = eye;
double aspectRatio = 1.0 * m_screenSize.width() / m_screenSize.height();
float halfWidth = 1 * apertureSize;
float halfHeight = 1 * apertureSize;
halfWidth *= aspectRatio;
// m_projectionMatrix.setToIdentity();
// m_projectionMatrix.frustum(-halfWidth, halfWidth, -halfHeight, halfHeight, nearPlane, farPlane);
QRectF va = m_visibleArea;
if (va.isNull())
va = QRectF(0, 0, m_screenSize.width(), m_screenSize.height());
QRectF screen = QRectF(QPointF(0,0),m_screenSize);
QPointF vaCenter = va.center();
QPointF screenCenter = screen.center();
QPointF diff = screenCenter - vaCenter;
float xdiffpct = diff.x() / m_screenSize.width();
float ydiffpct = -(diff.y() / m_screenSize.height());
m_projectionMatrix.setToIdentity();
float l = -halfWidth + (2 * halfWidth) * xdiffpct;
float r = halfWidth + (2 * halfWidth) * xdiffpct;
float t = halfHeight + (2 * halfHeight) * ydiffpct;
float b = -halfHeight + (2 * halfHeight) * ydiffpct;
m_projectionMatrix.frustum(l,
r,
b,
t,
nearPlane, farPlane);
}
static bool qgeotiledmapscene_isTileInViewport_Straight(const QRectF &tileRect, const QMatrix4x4 &matrix)
{
const QRectF boundingRect = QRectF(matrix * tileRect.topLeft(), matrix * tileRect.bottomRight());
return QRectF(-1, -1, 2, 2).intersects(boundingRect);
}
static bool qgeotiledmapscene_isTileInViewport_rotationTilt(const QRectF &tileRect, const QMatrix4x4 &matrix)
{
// Transformed corners
const QPointF tlt = matrix * tileRect.topLeft();
const QPointF trt = matrix * tileRect.topRight();
const QPointF blt = matrix * tileRect.bottomLeft();
const QPointF brt = matrix * tileRect.bottomRight();
const QRectF boundingRect = QRectF(QPointF(qMin(qMin(qMin(tlt.x(), trt.x()), blt.x()), brt.x())
,qMax(qMax(qMax(tlt.y(), trt.y()), blt.y()), brt.y()))
,QPointF(qMax(qMax(qMax(tlt.x(), trt.x()), blt.x()), brt.x())
,qMin(qMin(qMin(tlt.y(), trt.y()), blt.y()), brt.y()))
);
return QRectF(-1, -1, 2, 2).intersects(boundingRect);
}
static bool qgeotiledmapscene_isTileInViewport(const QRectF &tileRect, const QMatrix4x4 &matrix, const bool straight)
{
if (straight)
return qgeotiledmapscene_isTileInViewport_Straight(tileRect, matrix);
return qgeotiledmapscene_isTileInViewport_rotationTilt(tileRect, matrix);
}
void QGeoTiledMapRootNode::updateTiles(QGeoTiledMapTileContainerNode *root,
QGeoTiledMapScenePrivate *d,
double camAdjust,
QQuickWindow *window,
bool ogl)
{
// Set up the matrix...
QDoubleVector3D eye = d->m_cameraEye;
eye.setX(eye.x() + camAdjust);
QDoubleVector3D center = d->m_cameraCenter;
center.setX(center.x() + camAdjust);
QMatrix4x4 cameraMatrix;
cameraMatrix.lookAt(toVector3D(eye), toVector3D(center), toVector3D(d->m_cameraUp));
root->setMatrix(d->m_projectionMatrix * cameraMatrix);
QSet<QGeoTileSpec> tilesInSG;
for (auto it = root->tiles.cbegin(), end = root->tiles.cend(); it != end; ++it)
tilesInSG.insert(it.key());
const QSet<QGeoTileSpec> toRemove = tilesInSG - d->m_visibleTiles;
const QSet<QGeoTileSpec> toAdd = d->m_visibleTiles - tilesInSG;
for (const QGeoTileSpec &s : toRemove)
delete root->tiles.take(s);
bool straight = !d->isTiltedOrRotated();
bool overzooming;
qreal pixelRatio = window->effectiveDevicePixelRatio();
#ifdef QT_LOCATION_DEBUG
QList<QGeoTileSpec> droppedTiles;
#endif
for (QHash<QGeoTileSpec, QSGImageNode *>::iterator it = root->tiles.begin();
it != root->tiles.end(); ) {
QSGImageNode *node = it.value();
bool ok = d->buildGeometry(it.key(), node, overzooming)
&& qgeotiledmapscene_isTileInViewport(node->rect(), root->matrix(), straight);
QSGNode::DirtyState dirtyBits = {};
if (!ok) {
#ifdef QT_LOCATION_DEBUG
droppedTiles.append(it.key());
#endif
it = root->tiles.erase(it);
delete node;
} else {
if (isTextureLinear != d->m_linearScaling) {
if (node->texture()->textureSize().width() > d->m_tileSize * pixelRatio) {
node->setFiltering(QSGTexture::Linear); // With mipmapping QSGTexture::Nearest generates artifacts
node->setMipmapFiltering(QSGTexture::Linear);
} else {
node->setFiltering((d->m_linearScaling || overzooming) ? QSGTexture::Linear : QSGTexture::Nearest);
}
#if QT_CONFIG(opengl)
if (ogl)
static_cast<QSGDefaultImageNode *>(node)->setAnisotropyLevel(QSGTexture::Anisotropy16x);
#else
Q_UNUSED(ogl);
#endif
dirtyBits |= QSGNode::DirtyMaterial;
}
if (dirtyBits != 0)
node->markDirty(dirtyBits);
it++;
}
}
for (const QGeoTileSpec &s : toAdd) {
QGeoTileTexture *tileTexture = d->m_textures.value(s).data();
if (!tileTexture || tileTexture->image.isNull()) {
#ifdef QT_LOCATION_DEBUG
droppedTiles.append(s);
#endif
continue;
}
QSGImageNode *tileNode = window->createImageNode();
// note: setTexture will update coordinates so do it here, before we buildGeometry
tileNode->setTexture(textures.value(s));
if (d->buildGeometry(s, tileNode, overzooming)
&& qgeotiledmapscene_isTileInViewport(tileNode->rect(), root->matrix(), straight)) {
if (tileNode->texture()->textureSize().width() > d->m_tileSize * pixelRatio) {
tileNode->setFiltering(QSGTexture::Linear); // with mipmapping QSGTexture::Nearest generates artifacts
tileNode->setMipmapFiltering(QSGTexture::Linear);
} else {
tileNode->setFiltering((d->m_linearScaling || overzooming) ? QSGTexture::Linear : QSGTexture::Nearest);
}
#if QT_CONFIG(opengl)
if (ogl)
static_cast<QSGDefaultImageNode *>(tileNode)->setAnisotropyLevel(QSGTexture::Anisotropy16x);
#endif
root->addChild(s, tileNode);
} else {
#ifdef QT_LOCATION_DEBUG
droppedTiles.append(s);
#endif
delete tileNode;
}
}
#ifdef QT_LOCATION_DEBUG
m_droppedTiles[camAdjust] = droppedTiles;
#endif
}
QSGNode *QGeoTiledMapScene::updateSceneGraph(QSGNode *oldNode, QQuickWindow *window)
{
Q_D(QGeoTiledMapScene);
float w = d->m_screenSize.width();
float h = d->m_screenSize.height();
if (w <= 0 || h <= 0) {
delete oldNode;
return 0;
}
bool isOpenGL = (window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL);
QGeoTiledMapRootNode *mapRoot = static_cast<QGeoTiledMapRootNode *>(oldNode);
if (!mapRoot)
mapRoot = new QGeoTiledMapRootNode();
#ifdef QT_LOCATION_DEBUG
mapRoot->m_droppedTiles.clear();
d->m_mapRoot = mapRoot;
#endif
// Setting clip rect to fullscreen, as now the map can never be smaller than the viewport.
mapRoot->setClipRect(QRect(0, 0, w, h));
QMatrix4x4 itemSpaceMatrix;
itemSpaceMatrix.scale(w / 2, h / 2);
itemSpaceMatrix.translate(1, 1);
itemSpaceMatrix.scale(1, -1);
mapRoot->root->setMatrix(itemSpaceMatrix);
if (d->m_dropTextures) {
for (const QGeoTileSpec &s : mapRoot->tiles->tiles.keys())
delete mapRoot->tiles->tiles.take(s);
for (const QGeoTileSpec &s : mapRoot->wrapLeft->tiles.keys())
delete mapRoot->wrapLeft->tiles.take(s);
for (const QGeoTileSpec &s : mapRoot->wrapRight->tiles.keys())
delete mapRoot->wrapRight->tiles.take(s);
for (const QGeoTileSpec &spec : mapRoot->textures.keys())
mapRoot->textures.take(spec)->deleteLater();
d->m_dropTextures = false;
}
// Evicting loZL tiles temporarily used in place of hiZL ones
if (d->m_updatedTextures.size()) {
const QVector<QGeoTileSpec> &toRemove = d->m_updatedTextures;
for (const QGeoTileSpec &s : toRemove) {
if (mapRoot->tiles->tiles.contains(s))
delete mapRoot->tiles->tiles.take(s);
if (mapRoot->wrapLeft->tiles.contains(s))
delete mapRoot->wrapLeft->tiles.take(s);
if (mapRoot->wrapRight->tiles.contains(s))
delete mapRoot->wrapRight->tiles.take(s);
if (mapRoot->textures.contains(s))
mapRoot->textures.take(s)->deleteLater();
}
d->m_updatedTextures.clear();
}
QSet<QGeoTileSpec> textures;
for (auto it = mapRoot->textures.cbegin(), end = mapRoot->textures.cend(); it != end; ++it)
textures.insert(it.key());
const QSet<QGeoTileSpec> toRemove = textures - d->m_visibleTiles;
const QSet<QGeoTileSpec> toAdd = d->m_visibleTiles - textures;
for (const QGeoTileSpec &spec : toRemove)
mapRoot->textures.take(spec)->deleteLater();
for (const QGeoTileSpec &spec : toAdd) {
QGeoTileTexture *tileTexture = d->m_textures.value(spec).data();
if (!tileTexture || tileTexture->image.isNull())
continue;
mapRoot->textures.insert(spec, window->createTextureFromImage(tileTexture->image));
}
double sideLength = d->m_scaleFactor * d->m_tileSize * d->m_sideLength;
#ifdef QT_LOCATION_DEBUG
d->m_sideLengthPixel = sideLength;
#endif
mapRoot->updateTiles(mapRoot->tiles, d, 0, window, isOpenGL);
mapRoot->updateTiles(mapRoot->wrapLeft, d, +sideLength, window, isOpenGL);
mapRoot->updateTiles(mapRoot->wrapRight, d, -sideLength, window, isOpenGL);
mapRoot->isTextureLinear = d->m_linearScaling;
return mapRoot;
}
QT_END_NAMESPACE