Modified MapEngine::removeStackedTiles and added MapEngine::removeTile.
[situare] / src / map / mapengine.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5        Sami Rämö - sami.ramo@ixonos.com
6        Jussi Laitinen - jussi.laitinen@ixonos.com
7
8    Situare is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License
10    version 2 as published by the Free Software Foundation.
11
12    Situare is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Situare; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
20    USA.
21 */
22
23 #include <QtCore>
24 #include <QtGlobal>
25 #include <QDebug>
26 #include <QString>
27 #include <QStringList>
28 #include <QUrl>
29 #include <QHash>
30 #include <QHashIterator>
31 #include <QRect>
32
33 #include "mapengine.h"
34 #include "maptile.h"
35
36 MapEngine::MapEngine(QObject *parent)
37     : QObject(parent)
38     , m_zoomLevel(DEFAULT_ZOOM_LEVEL)
39     , m_viewSize(QSize(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT))
40     , m_centerTile(QPoint(UNDEFINED, UNDEFINED))
41 {
42     m_mapScene = new MapScene(this);
43
44     m_mapFetcher = new MapFetcher(new QNetworkAccessManager(this), this);
45     connect(this, SIGNAL(fetchImage(QUrl)), m_mapFetcher, SLOT(fetchMapImage(QUrl)));
46     connect(m_mapFetcher, SIGNAL(mapImageReceived(QUrl,QPixmap)), this,
47             SLOT(mapImageReceived(QUrl, QPixmap)));
48 }
49
50 void MapEngine::init()
51 {
52     emit zoomLevelChanged(m_zoomLevel);
53     setViewLocation(QPointF(DEFAULT_LONGITUDE, DEFAULT_LATITUDE));
54 }
55
56 void MapEngine::setViewLocation(QPointF latLonCoordinate)
57 {
58     qDebug() << __PRETTY_FUNCTION__;
59     setLocation(convertLatLonToSceneCoordinate(latLonCoordinate));
60 }
61
62 QUrl MapEngine::buildURL(int zoomLevel, QPoint tileNumbers)
63 {
64     QString url = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png")
65                   .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
66
67     return QUrl(url);
68 }
69
70 void MapEngine::parseURL(const QUrl &url, int &zoom, int &x, int &y)
71 {
72     QString path = url.path();
73     QStringList pathParts = path.split("/", QString::SkipEmptyParts);
74
75     int size = pathParts.size();
76
77     if (size < 3)
78         return;
79
80     zoom = (pathParts.at(size-3)).toInt();
81     x = (pathParts.at(size-2)).toInt();
82     QString yString = pathParts.at(size-1);
83     yString.chop(4);
84     y = yString.toInt();
85 }
86
87 void MapEngine::mapImageReceived(const QUrl &url, const QPixmap &pixmap)
88 {
89     //qDebug() << __PRETTY_FUNCTION__;
90     int zoom = UNDEFINED;
91     int x = UNDEFINED;
92     int y = UNDEFINED;
93
94     parseURL(url, zoom, x, y);
95
96     if (!mapTilesInScene.contains(tilePath(zoom, x, y))) {
97
98         MapTile *mapTile = new MapTile();
99         mapTile->setZoomLevel(zoom);
100         mapTile->setTileNumber(QPoint(x, y));
101         mapTile->setPixmap(pixmap);
102
103         mapTilesInScene.insert(tilePath(zoom, x, y), mapTile);
104         m_mapScene->addMapTile(mapTile);
105
106         removeStackedTiles(mapTile);
107    }
108 }
109
110 QGraphicsScene* MapEngine::scene()
111 {
112     return dynamic_cast<QGraphicsScene *>(m_mapScene);
113 }
114
115 int MapEngine::tileMaxValue(int zoomLevel)
116 {
117     return (1 << zoomLevel) - 1;
118 }
119
120 QRect MapEngine::calculateGrid(QPoint sceneCoordinate)
121 {
122     QPoint tileCoordinate = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
123     int gridWidth = (m_viewSize.width()/TILE_SIZE_X + 1) + (GRID_PADDING*2);
124     int gridHeight = (m_viewSize.height()/TILE_SIZE_Y + 1) + (GRID_PADDING*2);
125     int topLeftX = tileCoordinate.x() - (gridWidth/2);
126     int topLeftY = tileCoordinate.y() - (gridHeight/2);
127
128     return QRect(topLeftX, topLeftY, gridWidth, gridHeight);
129 }
130
131 void MapEngine::setLocation(QPoint sceneCoordinate)
132 {
133     //qDebug() << __PRETTY_FUNCTION__;
134
135     m_sceneCoordinate = sceneCoordinate;
136     emit locationChanged(m_sceneCoordinate);
137
138     if (centerTileChanged(sceneCoordinate)) {
139         calculateNewTiles(sceneCoordinate);
140         removeOldTiles();
141     }
142 }
143
144 bool MapEngine::centerTileChanged(QPoint sceneCoordinate)
145 {
146     QPoint centerTile = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
147     QPoint temp = m_centerTile;
148     m_centerTile = centerTile;
149
150     return (centerTile != temp);
151 }
152
153 void MapEngine::calculateNewTiles(QPoint sceneCoordinate)
154 {
155     //qDebug() << __PRETTY_FUNCTION__;
156
157     viewGrid = calculateGrid(sceneCoordinate);
158
159     int topLeftX = viewGrid.topLeft().x();
160     int topLeftY = viewGrid.topLeft().y();
161     int bottomRightX = viewGrid.bottomRight().x();
162     int bottomRightY = viewGrid.bottomRight().y();
163
164     int tileMaxVal = tileMaxValue(m_zoomLevel);
165
166     for (int x = topLeftX; x <= bottomRightX; ++x) {
167         for (int y = topLeftY; y <= bottomRightY; ++y) {
168
169             int tileX = x;
170             int tileY = y;
171
172             if (tileX < 0)
173                 tileX += tileMaxVal;
174             else if (tileX > tileMaxVal)
175                 tileX -= tileMaxVal;
176
177             if (tileY < 0)
178                 tileY += tileMaxVal;
179             else if (tileY > tileMaxVal)
180                 tileY -= tileMaxVal;
181
182             QUrl url = buildURL(m_zoomLevel, QPoint(tileX, tileY));
183
184             if (!mapTilesInScene.contains(tilePath(m_zoomLevel, tileX, tileY)))
185                 emit fetchImage(url);
186         }
187     }
188 }
189
190 void MapEngine::removeTile(MapTile *tile)
191 {
192     //qDebug() << __PRETTY_FUNCTION__;
193
194     if (tile) {
195        mapTilesInScene.remove(tilePath(tile->zoomLevel(), tile->tileNumber().x(), tile->tileNumber().y()));
196        m_mapScene->removeItem(tile);
197        delete tile;
198     }
199 }
200
201 void MapEngine::removeOldTiles()
202 {
203     //qDebug() << __PRETTY_FUNCTION__;
204
205     QPointF topLeft = convertTileNumberToSceneCoordinate(m_zoomLevel, viewGrid.topLeft());
206     QPointF bottomRight = convertTileNumberToSceneCoordinate(m_zoomLevel, viewGrid.bottomRight() + QPoint(1, 1));
207     qreal width = bottomRight.x() - topLeft.x();
208     qreal height = bottomRight.y() - topLeft.y();
209
210     QList<QGraphicsItem *> viewTiles = m_mapScene->items(topLeft.x(), topLeft.y(), width, height, Qt::ContainsItemBoundingRect);
211     QList<QGraphicsItem *> allTiles = m_mapScene->items();
212
213     foreach (QGraphicsItem *tile, viewTiles)
214         allTiles.removeOne(tile);
215
216     QHashIterator<QString, MapTile *> i(mapTilesInScene);
217
218      while (i.hasNext()) {
219          i.next();
220          if (allTiles.contains(i.value()) && mapTilesInScene.contains(i.key())) {
221              MapTile *tile = i.value();
222              removeTile(tile);
223          }
224      }
225 }
226
227 void MapEngine::removeStackedTiles(MapTile *newTile)
228 {
229     //qDebug() << __PRETTY_FUNCTION__;
230
231     QRectF newTileSceneRect = newTile->mapRectToScene(newTile->boundingRect());
232     QList<QGraphicsItem *> collidingItems = newTile->collidingItems(Qt::IntersectsItemBoundingRect);
233
234     //Loop all items under new tile
235     foreach (QGraphicsItem *collidingItem, collidingItems) {
236
237         QRectF collidingItemSceneRect = collidingItem->sceneBoundingRect();
238
239         //If new tile covers the tile under, remove the tile  (zoom out)
240         if (newTileSceneRect.contains(collidingItemSceneRect)) {
241             MapTile *tile = dynamic_cast<MapTile *>(collidingItem);
242             removeTile(tile);
243         }
244
245         else {
246             //Get tiles below removal candidate
247             QList<QGraphicsItem *> stackedItems = m_mapScene->items(collidingItemSceneRect, Qt::ContainsItemBoundingRect);
248             QRectF combined;
249             int count = 0;
250
251             //Loop all tiles below removal candidate and combine tiles
252             foreach (QGraphicsItem *stackedItem, stackedItems) {
253                 if (stackedItem != collidingItem) {
254                     count++;
255                     QRectF stackedItemSceneRect = stackedItem->sceneBoundingRect();
256                     combined = combined.united(stackedItemSceneRect);
257                 }
258             }
259
260             //If combined tiles below removal candidate covers removal candidate, remove it (zoom in)
261             if ((combined.contains(collidingItemSceneRect)) && (count >= 4)) {
262                 MapTile *tile = dynamic_cast<MapTile *>(collidingItem);
263                 removeTile(tile);
264             }
265         }
266     }
267 }
268
269 void MapEngine::viewResized(const QSize &size)
270 {
271     m_viewSize = size;
272     calculateNewTiles(m_sceneCoordinate);
273     removeOldTiles();
274 }
275
276 void MapEngine::zoomIn()
277 {
278     qDebug() << __PRETTY_FUNCTION__;
279
280     if (m_zoomLevel >= MAX_MAP_ZOOM_LEVEL)
281         return;
282
283     m_zoomLevel++;
284     emit zoomLevelChanged(m_zoomLevel);
285
286     setTilesDrawingLevels();
287
288     calculateNewTiles(m_sceneCoordinate);
289
290     /**
291     * @todo Remove old tiles after zoom
292     */
293     removeOldTiles();
294 }
295
296 void MapEngine::zoomOut()
297 {
298     qDebug() << __PRETTY_FUNCTION__;
299
300     if (m_zoomLevel <= MIN_MAP_ZOOM_LEVEL)
301         return;
302
303     m_zoomLevel--;
304     emit zoomLevelChanged(m_zoomLevel);
305
306     setTilesDrawingLevels();
307
308     calculateNewTiles(m_sceneCoordinate);
309 }
310
311 void MapEngine::setTilesDrawingLevels()
312 {
313     //qDebug() << __PRETTY_FUNCTION__ << "m_zoomLevel:" << m_zoomLevel;
314
315     QList<QGraphicsItem *> items = m_mapScene->items();
316
317     for (int i = 0; i < items.size(); ++i) {
318         MapTile *item = dynamic_cast<MapTile *>(items.at(i));
319         if (item)
320             item->setSceneLevel(m_zoomLevel);
321     }
322
323 }
324
325 QString MapEngine::tilePath(int zoomLevel, int x, int y)
326 {
327     QString tilePathString(QString::number(zoomLevel) + "/");
328     tilePathString.append(QString::number(x) + "/");
329     tilePathString.append(QString::number(y));
330
331     return tilePathString;
332 }
333
334 void MapEngine::scalingFactorChanged(qreal scaleFactor)
335 {
336     qDebug() << __PRETTY_FUNCTION__;
337 }