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