Created osm.h, re-factoring, unit test script bug fix
[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 MapScene::MapScene(QObject *parent)
31     : QGraphicsScene(parent)
32     , m_isRemoveStackedTilesRunning(false)
33     , m_tilesSceneRect(QRect(0, 0, 0, 0))
34 {
35     qDebug() << __PRETTY_FUNCTION__;
36
37     setBackgroundBrush(Qt::lightGray);
38     setSceneRect(QRect(QPoint(MAP_SCENE_MIN_PIXEL_X, OSM_MAP_MIN_PIXEL_Y),
39                        QPoint(MAP_SCENE_MAX_PIXEL_X, OSM_MAP_MAX_PIXEL_Y)));
40 }
41
42 void MapScene::addTile(int tileZoomLevel, QPoint tileNumber, const QPixmap &image, int viewZoomLevel)
43 {
44     qDebug() << __PRETTY_FUNCTION__;
45
46     // tile might already be in the scene if expired tile was returned from the cache to be
47     // temporarily shown while downloading the fresh one.
48     QString hashKey = MapEngine::tilePath(tileZoomLevel, tileNumber.x(), tileNumber.y());
49     MapTile *oldTile = tileInScene(hashKey);
50     if (oldTile)
51         removeTile(oldTile);
52
53     MapTile *tile = new MapTile();
54     tile->setZoomLevel(tileZoomLevel, viewZoomLevel);
55     tile->setTileNumber(tileNumber);
56     tile->setPixmap(image);
57
58     m_mapTilesInScene.insert(hashKey, tile);
59     addItem(tile);
60
61     qDebug() << __PRETTY_FUNCTION__ << "tiles count:" << m_mapTilesInScene.count();
62
63     enqueueRemoveStackedTiles(tile);
64     removeOtherLevelTiles();
65 }
66
67 void MapScene::enqueueRemoveStackedTiles(MapTile *newTile)
68 {
69     qDebug() << __PRETTY_FUNCTION__;
70
71     m_removeStackedTilesList << newTile;
72     if (!m_isRemoveStackedTilesRunning) {
73         m_isRemoveStackedTilesRunning = true;
74         QTimer::singleShot(0, this, SLOT(runNextStackedTilesRemoval()));
75     }
76 }
77
78 void MapScene::moveIntersectingItemsHorizontally(QRect from, int distance)
79 {
80     qDebug() << __PRETTY_FUNCTION__;
81
82     QList<QGraphicsItem *> spanItems = items(from, Qt::IntersectsItemBoundingRect);
83     foreach (QGraphicsItem *item, spanItems) {
84         if (!dynamic_cast<MapTile *>(item))
85             item->moveBy(distance, 0);
86     }
87 }
88
89 MapTile* MapScene::tileInScene(QString hashKey)
90 {
91     qDebug() << __PRETTY_FUNCTION__;
92
93     return m_mapTilesInScene.value(hashKey, 0);
94 }
95
96 void MapScene::runNextStackedTilesRemoval()
97 {
98     qDebug() << __PRETTY_FUNCTION__;
99
100     if (!m_removeStackedTilesList.isEmpty()) {
101         MapTile *tile = m_removeStackedTilesList.takeFirst();
102         removeStackedTiles(tile);
103     }
104
105     // schedule removal of the next tile if the list is not empty
106     if (!m_removeStackedTilesList.isEmpty())
107         QTimer::singleShot(0, this, SLOT(runNextStackedTilesRemoval()));
108     else
109         m_isRemoveStackedTilesRunning = false;
110 }
111
112 void MapScene::removeOtherLevelTiles()
113 {
114     qDebug() << __PRETTY_FUNCTION__;
115
116     for (int x = m_viewTilesGrid.left(); x <= m_viewTilesGrid.right(); x++) {
117         for (int y = m_viewTilesGrid.top(); y <= m_viewTilesGrid.bottom(); y++) {
118             if (!m_mapTilesInScene.contains(MapEngine::tilePath(m_zoomLevel, x, y)))
119                 return;
120         }
121     }
122
123     foreach(MapTile *tile, m_mapTilesInScene) {
124         if (tile->zoomLevel() != m_zoomLevel) {
125             removeTile(tile);
126             qDebug() << __PRETTY_FUNCTION__ << "removed other level tile";
127         }
128     }
129 }
130
131 void MapScene::removeOutOfViewTiles(QRect tilesGrid, int zoomLevel)
132 {
133     qDebug() << __PRETTY_FUNCTION__;
134
135     QList<QGraphicsItem *> viewItems = items(m_tilesSceneRect, Qt::IntersectsItemBoundingRect);
136     QList<QGraphicsItem *> allItems = items();
137
138     //Remove tiles which are in view from allTiles
139     foreach (QGraphicsItem *item, viewItems)
140         allItems.removeOne(item);
141
142     // note: add 1 so odd values are rounded up
143     int tilesGridWidthHalf = (tilesGrid.width() + 1) / 2;
144
145     // if view is near east limit of the map, then there is duplicate tiles also on the opposite
146     // side of the world which are removed from allTiles
147     if (tilesGrid.right() > (MapEngine::tileMaxIndex(zoomLevel) - tilesGridWidthHalf + MAP_GRID_PADDING)) {
148         QRect oppositeRect = m_tilesSceneRect;
149         oppositeRect.translate(-OMS_MAP_PIXELS_X, 0);
150         QList<QGraphicsItem *> oppositeItems = items(oppositeRect, Qt::IntersectsItemBoundingRect);
151         foreach (QGraphicsItem *item, oppositeItems)
152             allItems.removeOne(item);
153     }
154
155     // if view is near west limit of the map, then there is duplicate tiles also on the opposite
156     // side of the world which are removed from allTiles
157     if (tilesGrid.left() < (tilesGridWidthHalf - MAP_GRID_PADDING)) {
158         QRect oppositeRect = m_tilesSceneRect;
159         oppositeRect.translate(OMS_MAP_PIXELS_X, 0);
160         QList<QGraphicsItem *> oppositeItems = items(oppositeRect, Qt::IntersectsItemBoundingRect);
161         foreach (QGraphicsItem *item, oppositeItems)
162             allItems.removeOne(item);
163     }
164
165     //Remove tiles out of view
166     foreach (QGraphicsItem *item, allItems) {
167         MapTile *tile = dynamic_cast<MapTile *>(item);
168         if (tile)
169             removeTile(tile);
170     }
171 }
172
173 void MapScene::removeStackedTiles(MapTile *newTile)
174 {
175     qDebug() << __PRETTY_FUNCTION__;
176
177     QRectF newTileSceneRect = newTile->sceneBoundingRect();
178
179     //Loop all items under new tile
180     QList<QGraphicsItem *> collidingItems = newTile->collidingItems(Qt::IntersectsItemBoundingRect);
181     foreach (QGraphicsItem *collidingItem, collidingItems) {
182         MapTile *collidingTile = dynamic_cast<MapTile *>(collidingItem);
183         if (collidingTile) {
184             if (newTile->zValue() > collidingTile->zValue()) {
185                 // remove tile if it is fully obscured by new tile
186                 QRectF collidingTileSceneRect = collidingTile->sceneBoundingRect();
187                 if (newTileSceneRect.contains(collidingTileSceneRect))
188                     removeTile(collidingTile);
189             }
190         }
191     }
192 }
193
194 void MapScene::removeTile(MapTile *tile)
195 {
196     qDebug() << __PRETTY_FUNCTION__;
197
198     m_mapTilesInScene.remove(MapEngine::tilePath(tile->zoomLevel(),
199                                                  tile->tileNumber().x(),
200                                                  tile->tileNumber().y()));
201     removeItem(tile);
202     m_removeStackedTilesList.removeAll(tile);
203     delete tile;
204
205     qDebug() << __PRETTY_FUNCTION__ << "tiles count:" << m_mapTilesInScene.count();
206 }
207
208 void MapScene::setSceneVerticalOverlap(int viewHeight, int zoomLevel)
209 {
210     qDebug() << __PRETTY_FUNCTION__;
211
212     int overlap = viewHeight / 2 * (1 << (OSM_MAX_ZOOM_LEVEL - zoomLevel));
213
214     QRect rect = sceneRect().toRect();
215     rect.setTop(OSM_MAP_MIN_PIXEL_Y - overlap);
216     rect.setBottom(OSM_MAP_MAX_PIXEL_Y + overlap);
217     setSceneRect(rect);
218 }
219
220 void MapScene::setTilesDrawingLevels(int zoomLevel)
221 {
222     qDebug() << __PRETTY_FUNCTION__ << "zoomLevel:" << zoomLevel;
223
224     QList<QGraphicsItem *> allItems = items();
225
226     for (int i = 0; i < allItems.size(); ++i) {
227         MapTile *item = dynamic_cast<MapTile *>(allItems.at(i));
228         if (item)
229             item->setSceneLevel(zoomLevel);
230     }
231 }
232
233 void MapScene::setTilesGrid(QRect grid)
234 {
235     qDebug() << __PRETTY_FUNCTION__ ;
236
237     m_viewTilesGrid = grid;
238 }
239
240 void MapScene::setZoomLevel(int zoomLevel)
241 {
242     qDebug() << __PRETTY_FUNCTION__ ;
243
244     m_zoomLevel = zoomLevel;
245 }
246
247 void MapScene::spanItems(int zoomLevel, QPoint sceneCoordinate, QSize viewSize)
248 {
249     qDebug() << __PRETTY_FUNCTION__;
250
251     // create rects for left and right side
252     QRect leftRect = sceneRect().toRect(); // this way we get the horizontal limits of the scene
253     leftRect.setTop(OSM_MAP_MIN_PIXEL_Y);
254     leftRect.setBottom(OSM_MAP_MAX_PIXEL_Y);
255     QRect rightRect = leftRect;
256
257     // calculate current horizontal area shown on the view
258     int viewSceneWidth = (1 << (OSM_MAX_ZOOM_LEVEL - zoomLevel)) * viewSize.width();
259     int viewSceneLeft = sceneCoordinate.x() - viewSceneWidth / 2;
260     int viewSceneRight = sceneCoordinate.x() + viewSceneWidth / 2;
261
262     // limit rects to include only area which really must be moved
263     leftRect.setRight(-1 - (OMS_MAP_PIXELS_X - 1 - viewSceneRight));
264     rightRect.setLeft(OMS_MAP_PIXELS_X + viewSceneLeft);
265
266     Q_ASSERT_X(leftRect.right() < viewSceneLeft, "spanning rect right value", "move rect is in the view area");
267     Q_ASSERT_X(rightRect.left() > viewSceneRight, "spanning rect left value", "move rect is in the view area");
268
269     // move all items which intersects the rects
270     if (leftRect.left() < leftRect.right())
271         moveIntersectingItemsHorizontally(leftRect, OMS_MAP_PIXELS_X);
272     if (rightRect.left() < rightRect.right())
273         moveIntersectingItemsHorizontally(rightRect, -OMS_MAP_PIXELS_X);
274 }
275
276 void MapScene::tilesSceneRectUpdated(QRect tilesSceneRect)
277 {
278     qDebug() << __PRETTY_FUNCTION__;
279
280     m_tilesSceneRect = tilesSceneRect;
281 }