Got spanning of items (friends, locations) working
[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 <cmath>
23
24 #include <QDebug>
25
26 #include "mapcommon.h"
27 #include "mapengine.h"
28 #include "maptile.h"
29
30 #include "mapscene.h"
31
32 const int WORLD_PIXELS_Y = MAX_TILES_PER_SIDE * TILE_SIZE_Y;
33
34 MapScene::MapScene(QObject *parent)
35     : QGraphicsScene(parent)
36     , m_isRemoveStackedTilesRunning(false)
37     , m_tilesSceneRect(QRect(0, 0, 0, 0))
38 {
39     qDebug() << __PRETTY_FUNCTION__;
40
41     setBackgroundBrush(Qt::lightGray);
42     setSceneRect(-WORLD_PIXELS_X * 2, 0, WORLD_PIXELS_X * 5 - 1, WORLD_PIXELS_Y - 1);
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 displayed while downloading the fresh one. Tile can also be in the scene if the
51     // zoom level is low and world is spanning around, in which case the old tile removal is
52     // unnecessary, but this situation can't be recognised with information currently available
53     QString hashKey = MapEngine::tilePath(tileZoomLevel, tileNumber.x(), tileNumber.y());
54     MapTile *oldTile = tileInScene(hashKey);
55     if (oldTile)
56         removeTile(oldTile);
57
58     MapTile *tile = new MapTile();
59     tile->setZoomLevel(tileZoomLevel, viewZoomLevel);
60     tile->setTileNumber(tileNumber);
61     tile->setPixmap(image);
62
63     m_mapTilesInScene.insertMulti(hashKey, tile);
64     addItem(tile);
65
66     enqueueRemoveStackedTiles(tile);
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 void MapScene::moveIntersectingItemsHorizontally(QRect from, int distance)
81 {
82     qDebug() << __PRETTY_FUNCTION__;
83
84     qWarning() << __PRETTY_FUNCTION__ << "left:" << from.left() << "right:" << from.right() << "distance:" << distance;
85
86     QList<QGraphicsItem *> spanItems = items(from, Qt::IntersectsItemBoundingRect);
87     foreach (QGraphicsItem *item, spanItems) {
88         if (dynamic_cast<MapTile *>(item))
89             continue;
90         qWarning() << __PRETTY_FUNCTION__ << "moving item...";
91         item->moveBy(distance, 0);
92     }
93 }
94
95 MapTile* MapScene::tileInScene(QString hashKey)
96 {
97     qDebug() << __PRETTY_FUNCTION__;
98
99     return m_mapTilesInScene.value(hashKey, 0);
100 }
101
102 void MapScene::runNextStackedTilesRemoval()
103 {
104     qDebug() << __PRETTY_FUNCTION__;
105
106     if (!m_removeStackedTilesList.isEmpty()) {
107         MapTile *tile = m_removeStackedTilesList.takeFirst();
108         removeStackedTiles(tile);
109     }
110
111     // schedule removal of the next tile if the list is not empty
112     if (!m_removeStackedTilesList.isEmpty())
113         QTimer::singleShot(0, this, SLOT(runNextStackedTilesRemoval()));
114     else
115         m_isRemoveStackedTilesRunning = false;
116 }
117
118 void MapScene::removeOutOfViewTiles(QRect tilesGrid, int zoomLevel)
119 {
120     qDebug() << __PRETTY_FUNCTION__;
121
122 //    qWarning() << __PRETTY_FUNCTION__ << "m_tilesSceneRect:" << m_tilesSceneRect.left() << m_tilesSceneRect.top() << "/" << m_tilesSceneRect.right() << m_tilesSceneRect.bottom();
123
124     QList<QGraphicsItem *> viewTiles = items(m_tilesSceneRect, Qt::IntersectsItemBoundingRect);
125     QList<QGraphicsItem *> allTiles = items();
126
127     //Remove tiles which are in view from allTiles
128     foreach (QGraphicsItem *tile, viewTiles)
129         allTiles.removeOne(tile);
130
131     int tilesGridWidthHalf = (tilesGrid.width() + 1) / 2;
132 //    qWarning() << __PRETTY_FUNCTION__ << "half:" << tilesGridWidthHalf;
133
134     if (tilesGrid.right() > (MapEngine::tileMaxValue(zoomLevel) - tilesGridWidthHalf + GRID_PADDING)) { /// @todo must be mirrored also when near the world limit
135         QRect oppositeRect = m_tilesSceneRect;
136         oppositeRect.translate(-WORLD_PIXELS_X, 0);
137 //        qWarning() << __PRETTY_FUNCTION__ << "oppositeRect:" << oppositeRect.left() << oppositeRect.top() << "/" << oppositeRect.right() << oppositeRect.bottom();
138         QList<QGraphicsItem *> oppositeTiles = items(oppositeRect, Qt::IntersectsItemBoundingRect);
139         foreach (QGraphicsItem *tile, oppositeTiles)
140             allTiles.removeOne(tile);
141     }
142
143     if (tilesGrid.left() < (tilesGridWidthHalf - GRID_PADDING)) { /// @todo must be mirrored also when near the world limit
144         QRect oppositeRect = m_tilesSceneRect;
145         oppositeRect.translate(WORLD_PIXELS_X, 0);
146         QList<QGraphicsItem *> oppositeTiles = items(oppositeRect, Qt::IntersectsItemBoundingRect);
147         foreach (QGraphicsItem *tile, oppositeTiles)
148             allTiles.removeOne(tile);
149     }
150
151     //Remove tiles out of view
152     foreach (QGraphicsItem *tile, allTiles) {
153         MapTile *tileToRemove = dynamic_cast<MapTile *>(tile);
154         if (tileToRemove) {
155 //            qWarning() << __PRETTY_FUNCTION__ << "removing tile, x:" << tileToRemove->tileNumber().x() << "y:" << tileToRemove->tileNumber().y() << "pos:" << tileToRemove->pos().x() << tileToRemove->pos().y();
156             removeTile(tileToRemove);
157         }
158     }
159 }
160
161 void MapScene::removeStackedTiles(MapTile *newTile)
162 {
163     qDebug() << __PRETTY_FUNCTION__;
164
165     QRectF newTileSceneRect = newTile->sceneBoundingRect();
166
167     //Loop all items under new tile
168     QList<QGraphicsItem *> collidingItems = newTile->collidingItems(Qt::IntersectsItemBoundingRect);
169     foreach (QGraphicsItem *collidingItem, collidingItems) {
170         MapTile *collidingTile = dynamic_cast<MapTile *>(collidingItem);
171         if (collidingTile) {
172             if (newTile->zValue() > collidingTile->zValue()) {
173                 // remove tile if it is fully obscured by new tile
174                 QRectF collidingTileSceneRect = collidingTile->sceneBoundingRect();
175                 if (newTileSceneRect.contains(collidingTileSceneRect))
176                     removeTile(collidingTile);
177             }
178         }
179     }
180 }
181
182 void MapScene::removeTile(MapTile *tile)
183 {
184     qDebug() << __PRETTY_FUNCTION__;
185
186     m_mapTilesInScene.remove(MapEngine::tilePath(tile->zoomLevel(),
187                                                  tile->tileNumber().x(),
188                                                  tile->tileNumber().y()));
189     removeItem(tile);
190     m_removeStackedTilesList.removeAll(tile);
191     delete tile;
192 }
193
194 void MapScene::setSceneVerticalOverlap(int viewHeight, int zoomLevel)
195 {
196     qDebug() << __PRETTY_FUNCTION__;
197
198     int overlap = viewHeight / 2 * (1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel));
199
200     QRect rect = sceneRect().toRect();
201     rect.setTop(-overlap);
202     rect.setBottom(WORLD_PIXELS_Y + overlap - 1);
203     setSceneRect(rect);
204
205 //    qWarning() << __PRETTY_FUNCTION__ << "scene rect:" << rect.left() << rect.top() << rect.right() << rect.bottom();
206 }
207
208 void MapScene::setTilesDrawingLevels(int zoomLevel)
209 {
210     qDebug() << __PRETTY_FUNCTION__ << "zoomLevel:" << zoomLevel;
211
212     QList<QGraphicsItem *> allItems = items();
213
214     for (int i = 0; i < allItems.size(); ++i) {
215         MapTile *item = dynamic_cast<MapTile *>(allItems.at(i));
216         if (item)
217             item->setSceneLevel(zoomLevel);
218     }
219 }
220
221 void MapScene::spanItems(ScrollDirection direction, int zoomLevel, QPoint sceneCoordinate, QSize viewSize)
222 {
223     qWarning() << __PRETTY_FUNCTION__;
224
225     // create rects for left and right side
226     QRect leftRect = sceneRect().toRect(); // this way we get the horizontal limits of the scene
227     leftRect.setTop(0);
228     leftRect.setBottom(WORLD_PIXELS_Y);
229     QRect rightRect = leftRect;
230
231     // calculate current horizontal area shown on the view
232     int viewSceneWidth = (1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel)) * viewSize.width();
233     int viewSceneLeft = sceneCoordinate.x() - viewSceneWidth / 2;
234     int viewSceneRight = sceneCoordinate.x() + viewSceneWidth / 2;
235
236     // limit rect widths to be out of the current view
237     leftRect.setRight(viewSceneLeft);
238     rightRect.setLeft(viewSceneRight);
239
240     // move all items which intersects the rects
241     if (leftRect.width() > 0)
242         moveIntersectingItemsHorizontally(leftRect, WORLD_PIXELS_X);
243     if (rightRect.width() > 0)
244         moveIntersectingItemsHorizontally(rightRect, -WORLD_PIXELS_X);
245
246     /// @todo get small rects from boths sides of the view
247
248     /// @todo get all intersecting items, check which end of the view is nearer and move there
249
250
251 //    QRect spanRect; // = m_viewRect;
252 //    spanRect.setTop(0);
253 //    spanRect.setBottom(WORLD_PIXELS_Y);
254 //    int delta;
255
256 //    int viewSceneWidth = (1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel)) * viewSize.width();
257 //    int gap = WORLD_PIXELS_X - viewSceneWidth;
258 //    if (gap < 0)
259 //        qCritical() << __PRETTY_FUNCTION__ << "viewSceneWidth > WORLD_PIXELS_X";
260
261 //    int limiterUnNormalized = sceneCoordinate.x() + ((viewSceneWidth + gap) / 2);
262 //    int limiter = MapEngine::normalize(limiterUnNormalized, 0, WORLD_PIXELS_X -1);
263
264 //    if (sceneCoordinate.x() > limiter) {
265 //        spanRect.setRight(limiter);
266 //        spanRect.setLeft(sceneRect().left());
267 //        delta = WORLD_PIXELS_X;
268 //        qWarning() << __PRETTY_FUNCTION__ << "-->";
269 //    }
270 //    else {
271 //        spanRect.setLeft(limiter);
272 //        spanRect.setRight(sceneRect().right());
273 //        delta = -WORLD_PIXELS_X;
274 //        qWarning() << __PRETTY_FUNCTION__ << "<--";
275 //    }
276
277 //    QTransform transform;
278 //    qreal scale = pow(2, zoomLevel - MAX_MAP_ZOOM_LEVEL);
279 //    transform.scale(scale, scale);
280 //    QList<QGraphicsItem *> spanItems = items(spanRect, Qt::IntersectsItemBoundingRect,
281 //                                             Qt::DescendingOrder, transform);
282
283 //    foreach (QGraphicsItem *item, spanItems) {
284 //        if (dynamic_cast<MapTile *>(item))
285 //            continue;
286 //        item->moveBy(delta, 0);
287 //    }
288
289 //    return spanRect;
290 }
291
292 void MapScene::tilesSceneRectUpdated(QRect tilesSceneRect)
293 {
294     qDebug() << __PRETTY_FUNCTION__;
295
296     m_tilesSceneRect = tilesSceneRect;
297 }