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