Merge branch 'map' of https://vcs.maemo.org/git/situare into map
[situare] / src / map / mapengine.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        Jussi Laitinen - jussi.laitinen@ixonos.com
7
8    Situare is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License
10    version 2 as published by the Free Software Foundation.
11
12    Situare is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Situare; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
20    USA.
21 */
22
23 #include <QDebug>
24 #include <QString>
25 #include <QStringList>
26 #include <QUrl>
27 #include <QHash>
28 #include <QHashIterator>
29 #include <QQueue>
30 #include <QRect>
31
32 #include "mapengine.h"
33 #include "maptile.h"
34
35 #include <QtCore>
36 #include <QtGlobal>
37
38 MapEngine::MapEngine(QObject *parent)
39     : QObject(parent)
40     , m_zoomLevel(14)
41     , m_viewSize(QSize(973, 614))
42     , m_centerTile(QPoint(-1, -1))
43 {
44     m_mapScene = new MapScene(this);
45
46     m_mapFetcher = new MapFetcher(new QNetworkAccessManager(this), this);
47     connect(this, SIGNAL(fetchImage(QUrl)), m_mapFetcher, SLOT(fetchMapImage(QUrl)));
48     connect(m_mapFetcher, SIGNAL(mapImageReceived(QUrl,QPixmap)), this,
49             SLOT(mapImageReceived(QUrl, QPixmap)));
50 }
51
52 void MapEngine::init()
53 {
54     emit zoomLevelChanged(m_zoomLevel);
55     setViewLocation(QPointF(25.5000, 65.0000));
56
57 //    // Fetch some map tiles for demo purposes
58 //    for (int x=9351; x<=9354; x++) {
59 //        for (int y=4261; y<=4264; y++) {
60 //            QUrl url = buildURL(m_zoomLevel, QPoint(x, y));
61 //            m_mapFetcher->fetchMapImage(url);
62 //        }
63 //    }
64 }
65
66 void MapEngine::setViewLocation(QPointF latLonCoordinate)
67 {
68     qDebug() << __PRETTY_FUNCTION__;
69     setLocation(convertLatLonToSceneCoordinate(latLonCoordinate));
70 }
71
72 QUrl MapEngine::buildURL(int zoomLevel, QPoint tileNumbers)
73 {
74     QString url = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png")
75                   .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
76
77     return QUrl(url);
78 }
79
80 void MapEngine::parseURL(const QUrl &url, int &zoom, int &x, int &y)
81 {
82     QString path = url.path();
83     QStringList pathParts = path.split("/", QString::SkipEmptyParts);
84
85     int size = pathParts.size();
86
87     if (size < 3)
88         return;
89
90     zoom = (pathParts.at(size-3)).toInt();
91     x = (pathParts.at(size-2)).toInt();
92     QString yString = pathParts.at(size-1);
93     yString.chop(4);
94     y = yString.toInt();
95 }
96
97 void MapEngine::mapImageReceived(const QUrl &url, const QPixmap &pixmap)
98 {
99     //qDebug() << __PRETTY_FUNCTION__;
100
101     if (mapTilesInScene.contains(url.toString())) {
102         int zoom = -1;
103         int x = -1;
104         int y = -1;
105
106         parseURL(url, zoom, x, y);
107
108         MapTile *mapTile = new MapTile();
109         mapTile->setZoomLevel(zoom);
110         mapTile->setTileNumber(QPoint(x, y));
111         mapTile->setPixmap(pixmap);
112
113         mapTilesInScene.insert(url.toString(), mapTile);
114         m_mapScene->addMapTile(mapTile);
115
116         removeStackedTiles(mapTile);
117
118         qDebug() << "Tile count: " << m_mapScene->items().count();
119    }
120 }
121
122 QGraphicsScene* MapEngine::scene()
123 {
124     return dynamic_cast<QGraphicsScene *>(m_mapScene);
125 }
126
127 int MapEngine::tileMaxValue(int zoomLevel)
128 {
129     return (1 << zoomLevel) - 1;
130 }
131
132 QRect MapEngine::calculateGrid(QPointF sceneCoordinate)
133 {
134     QPoint tileCoordinate = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
135     int gridWidth = (m_viewSize.width()/TILE_SIZE_X + 1) + (GRID_PADDING*2);
136     int gridHeight = (m_viewSize.height()/TILE_SIZE_Y + 1) + (GRID_PADDING*2);
137     int topLeftX = tileCoordinate.x() - (gridWidth/2);
138     int topLeftY = tileCoordinate.y() - (gridHeight/2);
139
140     return QRect(topLeftX, topLeftY, gridWidth, gridHeight);
141 }
142
143 void MapEngine::setLocation(QPointF sceneCoordinate)
144 {
145     m_sceneCoordinate = sceneCoordinate;
146     emit locationChanged(m_sceneCoordinate);
147
148     if (centerTileChanged(sceneCoordinate)) {
149         calculateNewTiles(sceneCoordinate);
150         removeOldTiles();
151     }
152 }
153
154 bool MapEngine::centerTileChanged(QPointF sceneCoordinate)
155 {
156     QPoint centerTile = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
157     QPoint temp = m_centerTile;
158     m_centerTile = centerTile;
159
160     return (centerTile != temp);
161 }
162
163 void MapEngine::calculateNewTiles(QPointF sceneCoordinate)
164 {
165     //qDebug() << __PRETTY_FUNCTION__;
166
167     viewGrid = calculateGrid(sceneCoordinate);
168
169     int topLeftX = viewGrid.topLeft().x();
170     int topLeftY = viewGrid.topLeft().y();
171     int bottomRightX = viewGrid.bottomRight().x();
172     int bottomRightY = viewGrid.bottomRight().y();
173
174     int tileMaxVal = tileMaxValue(m_zoomLevel);
175
176     for (int x = topLeftX; x <= bottomRightX; ++x) {
177         for (int y = topLeftY; y <= bottomRightY; ++y) {
178
179             int tileX = x;
180             int tileY = y;
181
182             if (tileX < 0)
183                 tileX += tileMaxVal;
184             else if (tileX > tileMaxVal)
185                 tileX -= tileMaxVal;
186
187             if (tileY < 0)
188                 tileY += tileMaxVal;
189             else if (tileY > tileMaxVal)
190                 tileY -= tileMaxVal;
191
192             QUrl url = buildURL(m_zoomLevel, QPoint(tileX, tileY));
193
194             if (!mapTilesInScene.contains(url.toString())) {
195                 mapTilesInScene.insert(url.toString(), NULL);
196                 emit fetchImage(url);
197             }
198         }
199     }
200 }
201
202 void MapEngine::removeOldTiles()
203 {
204     //qDebug() << __PRETTY_FUNCTION__;
205
206     QPointF topLeft = convertTileNumberToSceneCoordinate(m_zoomLevel, viewGrid.topLeft());
207     QPointF bottomRight = convertTileNumberToSceneCoordinate(m_zoomLevel, viewGrid.bottomRight() + QPoint(1, 1));
208     qreal width = bottomRight.x() - topLeft.x();
209     qreal height = bottomRight.y() - topLeft.y();
210
211     QList<QGraphicsItem *> viewTiles = m_mapScene->items(topLeft.x(), topLeft.y(), width, height, Qt::IntersectsItemShape);
212     QList<QGraphicsItem *> allTiles = m_mapScene->items();
213
214     foreach (QGraphicsItem *tile, viewTiles)
215         allTiles.removeOne(tile);
216
217     QHashIterator<QString, MapTile *> i(mapTilesInScene);
218
219      while (i.hasNext()) {
220          i.next();
221          if (allTiles.contains(i.value())) {
222              MapTile *tile = i.value();
223              if (tile) {
224                  mapTilesInScene.remove(i.key());
225                  m_mapScene->removeItem(tile);
226                  delete tile;
227              }
228          }
229      }
230 }
231
232 void MapEngine::removeStackedTiles(MapTile *tile)
233 {
234     QList<QGraphicsItem *> collidingItems = tile->collidingItems(Qt::IntersectsItemBoundingRect);
235
236     foreach (QGraphicsItem *item, collidingItems) {
237
238         QRectF itemSceneRect = item->mapRectToScene(item->boundingRect());
239         QList<QGraphicsItem *> stackedItems = m_mapScene->items(itemSceneRect, Qt::IntersectsItemBoundingRect);
240
241         foreach(QGraphicsItem *stackedItem, stackedItems) {
242             if (item != stackedItem) {
243                 MapTile *tmp = dynamic_cast<MapTile *>(item);
244                 m_mapScene->removeItem(tmp);
245                 mapTilesInScene.remove(buildURL(tmp->zoomLevel(), tmp->tileNumber()).toString());
246             }
247         }
248     }
249 }
250
251 void MapEngine::viewResized(const QSize &size)
252 {
253     m_viewSize = size;
254     //setLocation(m_sceneCoordinate);
255     calculateNewTiles(m_sceneCoordinate);
256 }
257
258 void MapEngine::zoomIn()
259 {
260     qDebug() << __PRETTY_FUNCTION__;
261
262     if (m_zoomLevel >= MAX_MAP_ZOOM_LEVEL)
263         return;
264
265     m_zoomLevel++;
266     emit zoomLevelChanged(m_zoomLevel);
267
268     setZValues();
269     calculateNewTiles(m_sceneCoordinate);
270     removeOldTiles();
271 }
272
273 void MapEngine::zoomOut()
274 {
275     qDebug() << __PRETTY_FUNCTION__;
276
277     if (m_zoomLevel <= MIN_MAP_ZOOM_LEVEL)
278         return;
279
280     m_zoomLevel--;
281     emit zoomLevelChanged(m_zoomLevel);
282
283     setZValues();
284     calculateNewTiles(m_sceneCoordinate);
285     removeOldTiles();
286 }
287
288 void MapEngine::setZValues()
289 {
290     //qDebug() << __PRETTY_FUNCTION__ << "m_zoomLevel:" << m_zoomLevel;
291
292     QList<QGraphicsItem *> items = m_mapScene->items();
293
294     for (int i = 0; i < items.size(); ++i) {
295         MapTile *item = dynamic_cast<MapTile *>(items.at(i));
296         if (item)
297             item->setSceneLevel(m_zoomLevel);
298     }
299
300 }