Modified mapviewscreen to remove margins.
[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     emit locationChanged(m_sceneCoordinate);
55 }
56
57 void MapEngine::setViewLocation(QPointF latLonCoordinate)
58 {
59     qDebug() << __PRETTY_FUNCTION__;
60     setLocation(convertLatLonToSceneCoordinate(latLonCoordinate));
61     emit locationChanged(m_sceneCoordinate);
62 }
63
64 QUrl MapEngine::buildURL(int zoomLevel, QPoint tileNumbers)
65 {
66     QString url = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png")
67                   .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
68
69     return QUrl(url);
70 }
71
72 void MapEngine::parseURL(const QUrl &url, int &zoom, int &x, int &y)
73 {
74     QString path = url.path();
75     QStringList pathParts = path.split("/", QString::SkipEmptyParts);
76
77     int size = pathParts.size();
78
79     if (size < 3)
80         return;
81
82     zoom = (pathParts.at(size-3)).toInt();
83     x = (pathParts.at(size-2)).toInt();
84     QString yString = pathParts.at(size-1);
85     yString.chop(4);
86     y = yString.toInt();
87 }
88
89 void MapEngine::mapImageReceived(const QUrl &url, const QPixmap &pixmap)
90 {
91     //qDebug() << __PRETTY_FUNCTION__;
92
93     if (!mapTilesInScene.contains(url.toString())) {
94         int zoom = UNDEFINED;
95         int x = UNDEFINED;
96         int y = UNDEFINED;
97
98         parseURL(url, zoom, x, y);
99
100         MapTile *mapTile = new MapTile();
101         mapTile->setZoomLevel(zoom);
102         mapTile->setTileNumber(QPoint(x, y));
103         mapTile->setPixmap(pixmap);
104
105         mapTilesInScene.insert(tilePath(zoom, x, y), mapTile);
106         m_mapScene->addMapTile(mapTile);
107
108         removeStackedTiles(mapTile);
109    }
110 }
111
112 QGraphicsScene* MapEngine::scene()
113 {
114     return dynamic_cast<QGraphicsScene *>(m_mapScene);
115 }
116
117 int MapEngine::tileMaxValue(int zoomLevel)
118 {
119     return (1 << zoomLevel) - 1;
120 }
121
122 QRect MapEngine::calculateGrid(QPoint sceneCoordinate)
123 {
124     QPoint tileCoordinate = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
125     int gridWidth = (m_viewSize.width()/TILE_SIZE_X + 1) + (GRID_PADDING*2);
126     int gridHeight = (m_viewSize.height()/TILE_SIZE_Y + 1) + (GRID_PADDING*2);
127     int topLeftX = tileCoordinate.x() - (gridWidth/2);
128     int topLeftY = tileCoordinate.y() - (gridHeight/2);
129
130     return QRect(topLeftX, topLeftY, gridWidth, gridHeight);
131 }
132
133 void MapEngine::setLocation(QPoint sceneCoordinate)
134 {
135     //qDebug() << __PRETTY_FUNCTION__;
136
137     m_sceneCoordinate = sceneCoordinate;
138
139     if (centerTileChanged(sceneCoordinate)) {
140         calculateNewTiles(sceneCoordinate);
141         removeOldTiles();
142     }
143 }
144
145 bool MapEngine::centerTileChanged(QPoint sceneCoordinate)
146 {
147     QPoint centerTile = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
148     QPoint temp = m_centerTile;
149     m_centerTile = centerTile;
150
151     return (centerTile != temp);
152 }
153
154 void MapEngine::calculateNewTiles(QPoint sceneCoordinate)
155 {
156     //qDebug() << __PRETTY_FUNCTION__;
157
158     viewGrid = calculateGrid(sceneCoordinate);
159
160     int topLeftX = viewGrid.topLeft().x();
161     int topLeftY = viewGrid.topLeft().y();
162     int bottomRightX = viewGrid.bottomRight().x();
163     int bottomRightY = viewGrid.bottomRight().y();
164
165     int tileMaxVal = tileMaxValue(m_zoomLevel);
166
167     for (int x = topLeftX; x <= bottomRightX; ++x) {
168         for (int y = topLeftY; y <= bottomRightY; ++y) {
169
170             int tileX = x;
171             int tileY = y;
172
173             if (tileX < 0)
174                 tileX += tileMaxVal;
175             else if (tileX > tileMaxVal)
176                 tileX -= tileMaxVal;
177
178             if (tileY < 0)
179                 tileY += tileMaxVal;
180             else if (tileY > tileMaxVal)
181                 tileY -= tileMaxVal;
182
183             QUrl url = buildURL(m_zoomLevel, QPoint(tileX, tileY));
184
185             if (!mapTilesInScene.contains(tilePath(m_zoomLevel, tileX, tileY)))
186                 emit fetchImage(url);
187         }
188     }
189 }
190
191 void MapEngine::removeOldTiles()
192 {
193     //qDebug() << __PRETTY_FUNCTION__;
194
195     QPointF topLeft = convertTileNumberToSceneCoordinate(m_zoomLevel, viewGrid.topLeft());
196     QPointF bottomRight = convertTileNumberToSceneCoordinate(m_zoomLevel, viewGrid.bottomRight() + QPoint(1, 1));
197     qreal width = bottomRight.x() - topLeft.x();
198     qreal height = bottomRight.y() - topLeft.y();
199
200     QList<QGraphicsItem *> viewTiles = m_mapScene->items(topLeft.x(), topLeft.y(), width, height, Qt::IntersectsItemShape);
201     QList<QGraphicsItem *> allTiles = m_mapScene->items();
202
203     foreach (QGraphicsItem *tile, viewTiles)
204         allTiles.removeOne(tile);
205
206     QHashIterator<QString, MapTile *> i(mapTilesInScene);
207
208      while (i.hasNext()) {
209          i.next();
210          if (allTiles.contains(i.value()) && mapTilesInScene.contains(i.key())) {
211              MapTile *tile = i.value();
212              if (tile) {
213                  mapTilesInScene.remove(i.key());
214                  m_mapScene->removeItem(tile);
215                  delete tile;
216              }
217          }
218      }
219 }
220
221 void MapEngine::removeStackedTiles(MapTile *tile)
222 {
223     QList<QGraphicsItem *> collidingItems = tile->collidingItems(Qt::IntersectsItemBoundingRect);
224
225     foreach (QGraphicsItem *item, collidingItems) {
226         if (item != tile) {
227             QRectF itemSceneRect = item->mapRectToScene(item->boundingRect());
228             QList<QGraphicsItem *> stackedItems = m_mapScene->items(itemSceneRect, Qt::IntersectsItemBoundingRect);
229
230             foreach(QGraphicsItem *stackedItem, stackedItems) {
231                 if (item != stackedItem) {
232                     MapTile *tmp = dynamic_cast<MapTile *>(item);
233                     if (tmp) {
234                         m_mapScene->removeItem(tmp);
235                         mapTilesInScene.remove(tilePath(tmp->zoomLevel(), tmp->x(), tmp->y()));
236                     }
237                 }
238             }
239         }
240     }
241 }
242
243 void MapEngine::viewResized(const QSize &size)
244 {
245     m_viewSize = size;
246     calculateNewTiles(m_sceneCoordinate);
247     removeOldTiles();
248 }
249
250 void MapEngine::zoomIn()
251 {
252     qDebug() << __PRETTY_FUNCTION__;
253
254     if (m_zoomLevel >= MAX_MAP_ZOOM_LEVEL)
255         return;
256
257     m_zoomLevel++;
258     emit zoomLevelChanged(m_zoomLevel);
259
260     setZValues();
261 }
262
263 void MapEngine::zoomOut()
264 {
265     qDebug() << __PRETTY_FUNCTION__;
266
267     if (m_zoomLevel <= MIN_MAP_ZOOM_LEVEL)
268         return;
269
270     m_zoomLevel--;
271     emit zoomLevelChanged(m_zoomLevel);
272
273     setZValues();
274 }
275
276 void MapEngine::setZValues()
277 {
278     //qDebug() << __PRETTY_FUNCTION__ << "m_zoomLevel:" << m_zoomLevel;
279
280     QList<QGraphicsItem *> items = m_mapScene->items();
281
282     for (int i = 0; i < items.size(); ++i) {
283         MapTile *item = dynamic_cast<MapTile *>(items.at(i));
284         if (item)
285             item->setSceneLevel(m_zoomLevel);
286     }
287
288 }
289
290 QString MapEngine::tilePath(int zoomLevel, int x, int y)
291 {
292     QString tilePathString(QString::number(zoomLevel) + "/");
293     tilePathString.append(QString::number(x) + "/");
294     tilePathString.append(QString::number(y));
295
296     return tilePathString;
297 }