d6832c7aa55d10aa8334893b93ff2a822174fd9f
[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 <QtCore>
24 #include <QtGlobal>
25 #include <QDebug>
26 #include <QString>
27 #include <QStringList>
28 #include <QUrl>
29 #include <QHash>
30 #include <QHashIterator>
31 #include <QRect>
32
33 #include "mapengine.h"
34 #include "maptile.h"
35
36 MapEngine::MapEngine(QObject *parent)
37     : QObject(parent)
38     , m_zoomLevel(DEFAULT_ZOOM_LEVEL)
39     , m_viewSize(QSize(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT))
40     , m_centerTile(QPoint(UNDEFINED, UNDEFINED))
41 {
42     m_mapScene = new MapScene(this);
43
44     m_mapFetcher = new MapFetcher(new QNetworkAccessManager(this), this);
45     connect(this, SIGNAL(fetchImage(QUrl)), m_mapFetcher, SLOT(fetchMapImage(QUrl)));
46     connect(m_mapFetcher, SIGNAL(mapImageReceived(QUrl,QPixmap)), this,
47             SLOT(mapImageReceived(QUrl, QPixmap)));
48 }
49
50 void MapEngine::init()
51 {
52     emit zoomLevelChanged(m_zoomLevel);
53     setViewLocation(QPointF(DEFAULT_LONGITUDE, DEFAULT_LATITUDE));
54 }
55
56 void MapEngine::setViewLocation(QPointF latLonCoordinate)
57 {
58     qDebug() << __PRETTY_FUNCTION__;
59     setLocation(convertLatLonToSceneCoordinate(latLonCoordinate));
60 }
61
62 QUrl MapEngine::buildURL(int zoomLevel, QPoint tileNumbers)
63 {
64     QString url = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png")
65                   .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
66
67     return QUrl(url);
68 }
69
70 void MapEngine::parseURL(const QUrl &url, int &zoom, int &x, int &y)
71 {
72     QString path = url.path();
73     QStringList pathParts = path.split("/", QString::SkipEmptyParts);
74
75     int size = pathParts.size();
76
77     if (size < 3)
78         return;
79
80     zoom = (pathParts.at(size-3)).toInt();
81     x = (pathParts.at(size-2)).toInt();
82     QString yString = pathParts.at(size-1);
83     yString.chop(4);
84     y = yString.toInt();
85 }
86
87 void MapEngine::mapImageReceived(const QUrl &url, const QPixmap &pixmap)
88 {
89     //qDebug() << __PRETTY_FUNCTION__;
90     int zoom = UNDEFINED;
91     int x = UNDEFINED;
92     int y = UNDEFINED;
93
94     parseURL(url, zoom, x, y);
95
96     if (!m_mapTilesInScene.contains(tilePath(zoom, x, y))) {
97
98         MapTile *mapTile = new MapTile();
99         mapTile->setZoomLevel(zoom);
100         mapTile->setTileNumber(QPoint(x, y));
101         mapTile->setPixmap(pixmap);
102
103         m_mapTilesInScene.insert(tilePath(zoom, x, y), mapTile);
104         m_mapScene->addMapTile(mapTile);
105
106         removeStackedTiles(mapTile);
107    }
108 }
109
110 QGraphicsScene* MapEngine::scene()
111 {
112     return dynamic_cast<QGraphicsScene *>(m_mapScene);
113 }
114
115 int MapEngine::tileMaxValue(int zoomLevel)
116 {
117     return (1 << zoomLevel) - 1;
118 }
119
120 QRect MapEngine::calculateGrid(QPoint sceneCoordinate)
121 {
122     QPoint tileCoordinate = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
123     int gridWidth = (m_viewSize.width()/TILE_SIZE_X + 1) + (GRID_PADDING*2);
124     int gridHeight = (m_viewSize.height()/TILE_SIZE_Y + 1) + (GRID_PADDING*2);
125     int topLeftX = tileCoordinate.x() - (gridWidth/2);
126     int topLeftY = tileCoordinate.y() - (gridHeight/2);
127
128     return QRect(topLeftX, topLeftY, gridWidth, gridHeight);
129 }
130
131 void MapEngine::setLocation(QPoint sceneCoordinate)
132 {
133     //qDebug() << __PRETTY_FUNCTION__;
134
135     m_sceneCoordinate = sceneCoordinate;
136     emit locationChanged(m_sceneCoordinate);
137
138     if (centerTileChanged(sceneCoordinate)) {
139         calculateNewTiles(sceneCoordinate);
140         removeOldTiles();
141     }
142 }
143
144 bool MapEngine::centerTileChanged(QPoint sceneCoordinate)
145 {
146     QPoint centerTile = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
147     QPoint temp = m_centerTile;
148     m_centerTile = centerTile;
149
150     return (centerTile != temp);
151 }
152
153 void MapEngine::calculateNewTiles(QPoint sceneCoordinate)
154 {
155     //qDebug() << __PRETTY_FUNCTION__;
156
157     m_viewGrid = calculateGrid(sceneCoordinate);
158
159     int topLeftX = m_viewGrid.topLeft().x();
160     int topLeftY = m_viewGrid.topLeft().y();
161     int bottomRightX = m_viewGrid.bottomRight().x();
162     int bottomRightY = m_viewGrid.bottomRight().y();
163
164     int tileMaxVal = tileMaxValue(m_zoomLevel);
165
166     for (int x = topLeftX; x <= bottomRightX; ++x) {
167         for (int y = topLeftY; y <= bottomRightY; ++y) {
168
169             int tileX = x;
170             int tileY = y;
171
172             if (tileX < 0)
173                 tileX += tileMaxVal;
174             else if (tileX > tileMaxVal)
175                 tileX -= tileMaxVal;
176
177             if (tileY < 0)
178                 tileY += tileMaxVal;
179             else if (tileY > tileMaxVal)
180                 tileY -= tileMaxVal;
181
182             QUrl url = buildURL(m_zoomLevel, QPoint(tileX, tileY));
183
184             if (!m_mapTilesInScene.contains(tilePath(m_zoomLevel, tileX, tileY)))
185                 emit fetchImage(url);
186         }
187     }
188 }
189
190 void MapEngine::removeTile(MapTile *tile)
191 {
192     //qDebug() << __PRETTY_FUNCTION__;
193
194     if (tile) {
195        m_mapTilesInScene.remove(tilePath(tile->zoomLevel(), tile->tileNumber().x(),
196                                          tile->tileNumber().y()));
197        m_mapScene->removeItem(tile);
198        delete tile;
199     }
200 }
201
202 void MapEngine::removeOldTiles()
203 {
204     //qDebug() << __PRETTY_FUNCTION__;
205
206     QPointF topLeft = convertTileNumberToSceneCoordinate(m_zoomLevel, m_viewGrid.topLeft());
207     QPointF bottomRight = convertTileNumberToSceneCoordinate(m_zoomLevel, m_viewGrid.bottomRight()
208                                                              + QPoint(1, 1));
209     qreal width = bottomRight.x() - topLeft.x();
210     qreal height = bottomRight.y() - topLeft.y();
211
212     QList<QGraphicsItem *> viewTiles = m_mapScene->items(topLeft.x(), topLeft.y(), width, height,
213                                                          Qt::ContainsItemBoundingRect);
214     QList<QGraphicsItem *> allTiles = m_mapScene->items();
215
216     foreach (QGraphicsItem *tile, viewTiles)
217         allTiles.removeOne(tile);
218
219     QHashIterator<QString, MapTile *> i(m_mapTilesInScene);
220
221      while (i.hasNext()) {
222          i.next();
223          if (allTiles.contains(i.value()) && m_mapTilesInScene.contains(i.key())) {
224              MapTile *tile = i.value();
225              removeTile(tile);
226          }
227      }
228
229 //     qDebug() << m_mapScene->items().count();
230 }
231
232 void MapEngine::removeStackedTiles(MapTile *newTile)
233 {
234     //qDebug() << __PRETTY_FUNCTION__;
235
236     QRectF newTileSceneRect = newTile->mapRectToScene(newTile->boundingRect());
237     QList<QGraphicsItem *> collidingItems = newTile->collidingItems(Qt::IntersectsItemBoundingRect);
238
239     //Loop all items under new tile
240     foreach (QGraphicsItem *collidingItem, collidingItems) {
241
242         QRectF collidingItemSceneRect = collidingItem->sceneBoundingRect();
243
244         //If new tile covers the tile under, remove the tile  (zoom out)
245         if (newTileSceneRect.contains(collidingItemSceneRect)) {
246             MapTile *tile = dynamic_cast<MapTile *>(collidingItem);
247             removeTile(tile);
248         }
249
250         else {
251             //Get tiles below removal candidate
252             QList<QGraphicsItem *> stackedItems = m_mapScene->items(collidingItemSceneRect,
253                                                                     Qt::ContainsItemBoundingRect);
254             QRectF combined;
255             int count = 0;
256
257             //Loop all tiles below removal candidate and combine tiles
258             foreach (QGraphicsItem *stackedItem, stackedItems) {
259                 if (stackedItem != collidingItem) {
260                     count++;
261                     QRectF stackedItemSceneRect = stackedItem->sceneBoundingRect();
262                     combined = combined.united(stackedItemSceneRect);
263                 }
264             }
265
266             //If combined tiles below removal candidate covers removal candidate, remove it (zoom in)
267             if ((combined.contains(collidingItemSceneRect)) && (count >= 4)) {
268                 MapTile *tile = dynamic_cast<MapTile *>(collidingItem);
269                 removeTile(tile);
270             }
271         }
272     }
273
274 //    qDebug() << m_mapScene->items().count();
275 }
276
277 void MapEngine::viewResized(const QSize &size)
278 {
279     m_viewSize = size;
280     calculateNewTiles(m_sceneCoordinate);
281     removeOldTiles();
282 }
283
284 void MapEngine::zoomIn()
285 {
286     qDebug() << __PRETTY_FUNCTION__;
287
288     if (m_zoomLevel >= MAX_MAP_ZOOM_LEVEL)
289         return;
290
291     m_zoomLevel++;
292     emit zoomLevelChanged(m_zoomLevel);
293
294     setTilesDrawingLevels();
295
296     calculateNewTiles(m_sceneCoordinate);
297
298     /**
299     * @todo Remove old tiles after zoom
300     */
301     QTimer::singleShot(500, this, SLOT(removeOldTiles()));
302 }
303
304 void MapEngine::zoomOut()
305 {
306     qDebug() << __PRETTY_FUNCTION__;
307
308     if (m_zoomLevel <= MIN_MAP_ZOOM_LEVEL)
309         return;
310
311     m_zoomLevel--;
312     emit zoomLevelChanged(m_zoomLevel);
313
314     setTilesDrawingLevels();
315
316     calculateNewTiles(m_sceneCoordinate);
317 }
318
319 void MapEngine::setTilesDrawingLevels()
320 {
321     //qDebug() << __PRETTY_FUNCTION__ << "m_zoomLevel:" << m_zoomLevel;
322
323     QList<QGraphicsItem *> items = m_mapScene->items();
324
325     for (int i = 0; i < items.size(); ++i) {
326         MapTile *item = dynamic_cast<MapTile *>(items.at(i));
327         if (item)
328             item->setSceneLevel(m_zoomLevel);
329     }
330
331 }
332
333 QString MapEngine::tilePath(int zoomLevel, int x, int y)
334 {
335     QString tilePathString(QString::number(zoomLevel) + "/");
336     tilePathString.append(QString::number(x) + "/");
337     tilePathString.append(QString::number(y));
338
339     return tilePathString;
340 }
341
342 void MapEngine::scalingFactorChanged(qreal scaleFactor)
343 {
344     qDebug() << __PRETTY_FUNCTION__;
345 }
346
347 QPoint MapEngine::convertSceneCoordinateToTileNumber(int zoomLevel, QPoint sceneCoordinate)
348 {
349     int pow = 1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel);
350     int x = static_cast<int>(sceneCoordinate.x() / (TILE_SIZE_X*pow));
351     int y = static_cast<int>(sceneCoordinate.y() / (TILE_SIZE_Y*pow));
352
353     return QPoint(x, y);
354 }
355
356 QPoint MapEngine::convertTileNumberToSceneCoordinate(int zoomLevel, QPoint tileNumber)
357 {
358     int pow = 1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel);
359     int x = tileNumber.x() * TILE_SIZE_X * pow;
360     int y = tileNumber.y() * TILE_SIZE_Y * pow;
361
362     return QPoint(x, y);
363 }
364
365 QPoint MapEngine::convertLatLonToSceneCoordinate(QPointF latLonCoordinate)
366 {
367     qDebug() << __PRETTY_FUNCTION__;
368
369     qreal longitude = latLonCoordinate.x();
370     qreal latitude = latLonCoordinate.y();
371
372     if ((longitude > MAX_LONGITUDE) || (longitude < MIN_LONGITUDE))
373         return QPoint(UNDEFINED, UNDEFINED);
374     if ((latitude > MAX_LATITUDE) || (latitude < MIN_LATITUDE))
375         return QPoint(UNDEFINED, UNDEFINED);
376
377     qreal z = static_cast<qreal>(1 << MAX_MAP_ZOOM_LEVEL);
378
379     qreal x = static_cast<qreal>((longitude + 180.0) / 360.0);
380     qreal y = static_cast<qreal>((1.0 - log(tan(latitude * M_PI / 180.0) + 1.0
381                                 / cos(latitude * M_PI / 180.0)) / M_PI) / 2.0);
382
383     return QPointF(x*z*TILE_SIZE_X, y*z*TILE_SIZE_Y).toPoint();
384 }