Cleanup
[situare] / src / map / mapscene.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
7    Situare is free software; you can redistribute it and/or
8    modify it under the terms of the GNU General Public License
9    version 2 as published by the Free Software Foundation.
10
11    Situare is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with Situare; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
19    USA.
20 */
21
22 #include <QDebug>
23
24 #include "mapcommon.h"
25 #include "mapengine.h"
26 #include "maptile.h"
27
28 #include "mapscene.h"
29
30 const int MAP_MIN_PIXEL_Y = 0;
31 const int MAP_MAX_PIXEL_Y = MAX_TILES_PER_SIDE * TILE_SIZE_Y - 1;
32
33 MapScene::MapScene(QObject *parent)
34     : QGraphicsScene(parent)
35     , m_isRemoveStackedTilesRunning(false)
36     , m_tilesSceneRect(QRect(0, 0, 0, 0))
37 {
38     qDebug() << __PRETTY_FUNCTION__;
39
40     setBackgroundBrush(Qt::lightGray);
41     setSceneRect(MAP_SCENE_MIN_PIXEL_X, MAP_MIN_PIXEL_Y, MAP_SCENE_MAX_PIXEL_X, MAP_MAX_PIXEL_Y);
42 }
43
44 void MapScene::addTile(int tileZoomLevel, QPoint tileNumber, const QPixmap &image, int viewZoomLevel)
45 {
46     qDebug() << __PRETTY_FUNCTION__;
47
48     // tile might already be in the scene if:
49     //    - expired tile was returned from the cache to be temporarily displayed while downloading
50     //      the fresh one.
51     //    - upper level tile is not cleaned after zooming in and back to out (not scrolled enough)
52     //    - upper level tile was earlier returned from cache while downloading the requested tile
53     //      and user has zoomed up to that level
54     QString hashKey = MapEngine::tilePath(tileZoomLevel, tileNumber.x(), tileNumber.y());
55     MapTile *oldTile = tileInScene(hashKey);
56     if (oldTile)
57         removeTile(oldTile);
58
59     MapTile *tile = new MapTile();
60     tile->setZoomLevel(tileZoomLevel, viewZoomLevel);
61     tile->setTileNumber(tileNumber);
62     tile->setPixmap(image);
63
64     m_mapTilesInScene.insert(hashKey, tile);
65     addItem(tile);
66
67     enqueueRemoveStackedTiles(tile);
68 }
69
70 void MapScene::enqueueRemoveStackedTiles(MapTile *newTile)
71 {
72     qDebug() << __PRETTY_FUNCTION__;
73
74     m_removeStackedTilesList << newTile;
75     if (!m_isRemoveStackedTilesRunning) {
76         m_isRemoveStackedTilesRunning = true;
77         QTimer::singleShot(0, this, SLOT(runNextStackedTilesRemoval()));
78     }
79 }
80
81 void MapScene::moveIntersectingItemsHorizontally(QRect from, int distance)
82 {
83     qDebug() << __PRETTY_FUNCTION__;
84
85     QList<QGraphicsItem *> spanItems = items(from, Qt::IntersectsItemBoundingRect);
86     foreach (QGraphicsItem *item, spanItems) {
87         if (dynamic_cast<MapTile *>(item))
88             continue;
89         item->moveBy(distance, 0);
90     }
91 }
92
93 MapTile* MapScene::tileInScene(QString hashKey)
94 {
95     qDebug() << __PRETTY_FUNCTION__;
96
97     return m_mapTilesInScene.value(hashKey, 0);
98 }
99
100 void MapScene::runNextStackedTilesRemoval()
101 {
102     qDebug() << __PRETTY_FUNCTION__;
103
104     if (!m_removeStackedTilesList.isEmpty()) {
105         MapTile *tile = m_removeStackedTilesList.takeFirst();
106         removeStackedTiles(tile);
107     }
108
109     // schedule removal of the next tile if the list is not empty
110     if (!m_removeStackedTilesList.isEmpty())
111         QTimer::singleShot(0, this, SLOT(runNextStackedTilesRemoval()));
112     else
113         m_isRemoveStackedTilesRunning = false;
114 }
115
116 void MapScene::removeOutOfViewTiles(QRect tilesGrid, int zoomLevel)
117 {
118     qDebug() << __PRETTY_FUNCTION__;
119
120     QList<QGraphicsItem *> viewTiles = items(m_tilesSceneRect, Qt::IntersectsItemBoundingRect);
121     QList<QGraphicsItem *> allTiles = items();
122
123     //Remove tiles which are in view from allTiles
124     foreach (QGraphicsItem *tile, viewTiles)
125         allTiles.removeOne(tile);
126
127     // note: add 1 so odd values are rounded up
128     int tilesGridWidthHalf = (tilesGrid.width() + 1) / 2;
129
130     if (tilesGrid.right() > (MapEngine::tileMaxValue(zoomLevel) - tilesGridWidthHalf + GRID_PADDING)) {
131         QRect oppositeRect = m_tilesSceneRect;
132         oppositeRect.translate(-MAP_PIXELS_X, 0);
133         QList<QGraphicsItem *> oppositeTiles = items(oppositeRect, Qt::IntersectsItemBoundingRect);
134         foreach (QGraphicsItem *tile, oppositeTiles)
135             allTiles.removeOne(tile);
136     }
137
138     if (tilesGrid.left() < (tilesGridWidthHalf - GRID_PADDING)) {
139         QRect oppositeRect = m_tilesSceneRect;
140         oppositeRect.translate(MAP_PIXELS_X, 0);
141         QList<QGraphicsItem *> oppositeTiles = items(oppositeRect, Qt::IntersectsItemBoundingRect);
142         foreach (QGraphicsItem *tile, oppositeTiles)
143             allTiles.removeOne(tile);
144     }
145
146     //Remove tiles out of view
147     foreach (QGraphicsItem *tile, allTiles) {
148         MapTile *tileToRemove = dynamic_cast<MapTile *>(tile);
149         if (tileToRemove)
150             removeTile(tileToRemove);
151     }
152
153     qWarning() << __PRETTY_FUNCTION__ << "items in scene:" << items().count();
154 }
155
156 void MapScene::removeStackedTiles(MapTile *newTile)
157 {
158     qDebug() << __PRETTY_FUNCTION__;
159
160     QRectF newTileSceneRect = newTile->sceneBoundingRect();
161
162     //Loop all items under new tile
163     QList<QGraphicsItem *> collidingItems = newTile->collidingItems(Qt::IntersectsItemBoundingRect);
164     foreach (QGraphicsItem *collidingItem, collidingItems) {
165         MapTile *collidingTile = dynamic_cast<MapTile *>(collidingItem);
166         if (collidingTile) {
167             if (newTile->zValue() > collidingTile->zValue()) {
168                 // remove tile if it is fully obscured by new tile
169                 QRectF collidingTileSceneRect = collidingTile->sceneBoundingRect();
170                 if (newTileSceneRect.contains(collidingTileSceneRect))
171                     removeTile(collidingTile);
172             }
173         }
174     }
175     qWarning() << __PRETTY_FUNCTION__ << "items in scene:" << items().count();
176 }
177
178 void MapScene::removeTile(MapTile *tile)
179 {
180     qDebug() << __PRETTY_FUNCTION__;
181
182     m_mapTilesInScene.remove(MapEngine::tilePath(tile->zoomLevel(),
183                                                  tile->tileNumber().x(),
184                                                  tile->tileNumber().y()));
185     removeItem(tile);
186     m_removeStackedTilesList.removeAll(tile);
187     delete tile;
188 }
189
190 void MapScene::setSceneVerticalOverlap(int viewHeight, int zoomLevel)
191 {
192     qDebug() << __PRETTY_FUNCTION__;
193
194     int overlap = viewHeight / 2 * (1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel));
195
196     QRect rect = sceneRect().toRect();
197     rect.setTop(MAP_MIN_PIXEL_Y - overlap);
198     rect.setBottom(MAP_MAX_PIXEL_Y + overlap);
199     setSceneRect(rect);
200 }
201
202 void MapScene::setTilesDrawingLevels(int zoomLevel)
203 {
204     qDebug() << __PRETTY_FUNCTION__ << "zoomLevel:" << zoomLevel;
205
206     QList<QGraphicsItem *> allItems = items();
207
208     for (int i = 0; i < allItems.size(); ++i) {
209         MapTile *item = dynamic_cast<MapTile *>(allItems.at(i));
210         if (item)
211             item->setSceneLevel(zoomLevel);
212     }
213 }
214
215 void MapScene::spanItems(int zoomLevel, QPoint sceneCoordinate, QSize viewSize)
216 {
217     qDebug() << __PRETTY_FUNCTION__;
218
219     // create rects for left and right side
220     QRect leftRect = sceneRect().toRect(); // this way we get the horizontal limits of the scene
221     leftRect.setTop(MAP_MIN_PIXEL_Y);
222     leftRect.setBottom(MAP_MAX_PIXEL_Y);
223     QRect rightRect = leftRect;
224
225     // calculate current horizontal area shown on the view
226     int viewSceneWidth = (1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel)) * viewSize.width();
227     int viewSceneLeft = sceneCoordinate.x() - viewSceneWidth / 2;
228     int viewSceneRight = sceneCoordinate.x() + viewSceneWidth / 2;
229
230     // limit rects to include only area which really must be moved
231     leftRect.setRight(-1 - (MAP_PIXELS_X - 1 - viewSceneRight));
232     rightRect.setLeft(MAP_PIXELS_X + viewSceneLeft);
233
234     Q_ASSERT_X(leftRect.right() < viewSceneLeft, "spanning rect right value", "move rect is in the view area");
235     Q_ASSERT_X(rightRect.left() > viewSceneRight, "spanning rect left value", "move rect is in the view area");
236
237     // move all items which intersects the rects
238     if (leftRect.left() < leftRect.right())
239         moveIntersectingItemsHorizontally(leftRect, MAP_PIXELS_X);
240     if (rightRect.left() < rightRect.right())
241         moveIntersectingItemsHorizontally(rightRect, -MAP_PIXELS_X);
242 }
243
244 void MapScene::tilesSceneRectUpdated(QRect tilesSceneRect)
245 {
246     qDebug() << __PRETTY_FUNCTION__;
247
248     m_tilesSceneRect = tilesSceneRect;
249 }