Implemented CloudMade authentication using user token
[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        Pekka Nissinen - pekka.nissinen@ixonos.com
8        Ville Tiensuu - ville.tiensuu@ixonos.com
9        Henri Lampela - henri.lampela@ixonos.com
10
11    Situare is free software; you can redistribute it and/or
12    modify it under the terms of the GNU General Public License
13    version 2 as published by the Free Software Foundation.
14
15    Situare is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with Situare; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
23    USA.
24 */
25
26 #include <QtAlgorithms>
27 #include <QDebug>
28 #include <QString>
29 #include <QStringList>
30 #include <QUrl>
31 #include <QHash>
32 #include <QHashIterator>
33 #include <QRect>
34
35 #include "common.h"
36 #include "frienditemshandler.h"
37 #include "gpslocationitem.h"
38 #include "mapcommon.h"
39 #include "mapfetcher.h"
40 #include "maprouteitem.h"
41 #include "mapscene.h"
42 #include "mapscroller.h"
43 #include "maptile.h"
44 #include "network/networkaccessmanager.h"
45 #include "ownlocationitem.h"
46 #include "user/user.h"
47
48 #include "mapengine.h"
49
50 const int SMOOTH_CENTERING_TIME_MS = 1000;
51
52 MapEngine::MapEngine(QObject *parent)
53     : QObject(parent),
54       m_autoCenteringEnabled(false),
55       m_scrollStartedByGps(false),
56       m_smoothScrollRunning(false),
57       m_zoomedIn(false),
58       m_zoomLevel(DEFAULT_ZOOM_LEVEL),
59       m_centerTile(QPoint(UNDEFINED, UNDEFINED)),
60       m_lastAutomaticPosition(QPoint(0, 0)),
61       m_tilesGridSize(QSize(0, 0)),
62       m_viewSize(QSize(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT)),
63       m_mapRouteItem(0)
64 {
65     qDebug() << __PRETTY_FUNCTION__;
66
67     m_mapScene = new MapScene(this);
68
69     m_mapFetcher = new MapFetcher(NetworkAccessManager::instance(), this);
70     connect(this, SIGNAL(fetchImage(int, int, int)),
71             m_mapFetcher, SLOT(enqueueFetchMapImage(int, int, int)));
72     connect(m_mapFetcher, SIGNAL(mapImageReceived(int, int, int, QPixmap)),
73             this, SLOT(mapImageReceived(int, int, int, QPixmap)));
74     connect(m_mapFetcher, SIGNAL(error(int, int)),
75             this, SIGNAL(error(int, int)));
76
77     m_ownLocation = new OwnLocationItem();
78     m_ownLocation->hide(); // hide until first location info is received
79     m_mapScene->addItem(m_ownLocation);
80
81     m_gpsLocationItem = new GPSLocationItem();
82     m_mapScene->addItem(m_gpsLocationItem);
83
84     m_friendItemsHandler = new FriendItemsHandler(m_mapScene, this);
85     connect(this, SIGNAL(zoomLevelChanged(int)),
86             m_friendItemsHandler, SLOT(refactorFriendItems(int)));
87
88     connect(this, SIGNAL(friendsLocationsReady(QList<User*>&)),
89             m_friendItemsHandler, SLOT(friendListUpdated(QList<User*>&)));
90
91     connect(this, SIGNAL(friendImageReady(User*)),
92             m_friendItemsHandler, SLOT(friendImageReady(User*)));
93
94     connect(this, SIGNAL(friendsLocationsReady(QList<User*>&)),
95             this, SLOT(friendsPositionsUpdated()));
96
97     connect(m_friendItemsHandler, SIGNAL(locationItemClicked(QList<QString>)),
98             this, SIGNAL(locationItemClicked(QList<QString>)));
99
100     m_scroller = &MapScroller::getInstance();
101
102     connect(m_scroller, SIGNAL(coordinateUpdated(QPoint)),
103             this, SLOT(setCenterPosition(QPoint)));
104
105     connect(m_scroller, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)),
106             this, SLOT(scrollerStateChanged(QAbstractAnimation::State)));
107 }
108
109 MapEngine::~MapEngine()
110 {
111     qDebug() << __PRETTY_FUNCTION__;
112
113     QSettings settings(DIRECTORY_NAME, FILE_NAME);
114     settings.setValue(MAP_LAST_POSITION,
115                       convertSceneCoordinateToLatLon(m_zoomLevel, m_sceneCoordinate));
116     settings.setValue(MAP_LAST_ZOOMLEVEL, m_zoomLevel);
117 }
118
119 QRect MapEngine::calculateTileGrid(QPoint sceneCoordinate)
120 {
121     qDebug() << __PRETTY_FUNCTION__;
122
123     QPoint tileCoordinate = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
124
125     QPoint topLeft;
126     topLeft.setX(tileCoordinate.x() - (m_tilesGridSize.width() / 2));
127     topLeft.setY(tileCoordinate.y() - (m_tilesGridSize.height() / 2));
128
129     return QRect(topLeft, m_tilesGridSize);
130 }
131
132 void MapEngine::centerAndZoomTo(QRect rect)
133 {
134     const int MARGIN_HORIZONTAL = 50;
135     const int MARGIN_VERTICAL = 5;
136
137     // calculate the usable size of the view
138     int viewUsableHeight = m_viewSize.height() - 2 * MARGIN_VERTICAL;
139     int viewUsableWidth = m_viewSize.width() - 2 * MARGIN_HORIZONTAL;
140
141     // calculate how many levels must be zoomed out from the closest zoom level to get the rect
142     // fit inside the usable view area
143     int shift = 0;
144     while ((rect.height() > (viewUsableHeight * (1 << shift)))
145            || (rect.width() > (viewUsableWidth * (1 << shift))))
146         shift++;
147
148     scrollToPosition(rect.center());
149
150     int zoomLevel = qBound(MIN_VIEW_ZOOM_LEVEL, MAX_MAP_ZOOM_LEVEL - shift, MAX_MAP_ZOOM_LEVEL);
151     setZoomLevel(zoomLevel);
152 }
153
154 QPointF MapEngine::centerGeoCoordinate()
155 {
156     qDebug() << __PRETTY_FUNCTION__;
157
158     return convertSceneCoordinateToLatLon(m_zoomLevel, m_sceneCoordinate);
159 }
160
161 void MapEngine::centerToCoordinates(QPointF latLonCoordinate)
162 {
163     qDebug() << __PRETTY_FUNCTION__;
164
165     scrollToPosition(convertLatLonToSceneCoordinate(latLonCoordinate));
166 }
167
168 QPoint MapEngine::convertLatLonToSceneCoordinate(QPointF latLonCoordinate)
169 {
170     qDebug() << __PRETTY_FUNCTION__;
171
172     qreal longitude = latLonCoordinate.x();
173     qreal latitude = latLonCoordinate.y();
174
175     if ((longitude > MAX_LONGITUDE) || (longitude < MIN_LONGITUDE))
176         return QPoint(UNDEFINED, UNDEFINED);
177     if ((latitude > MAX_LATITUDE) || (latitude < MIN_LATITUDE))
178         return QPoint(UNDEFINED, UNDEFINED);
179
180     qreal z = static_cast<qreal>(MapEngine::tilesPerSide(MAX_MAP_ZOOM_LEVEL));
181
182     qreal x = static_cast<qreal>((longitude + 180.0) / 360.0);
183     qreal y = static_cast<qreal>((1.0 - log(tan(latitude * M_PI / 180.0) + 1.0
184                                 / cos(latitude * M_PI / 180.0)) / M_PI) / 2.0);
185
186     return QPointF(x * z * TILE_SIZE_X, y * z * TILE_SIZE_Y).toPoint();
187 }
188
189 QPointF MapEngine::convertSceneCoordinateToLatLon(int zoomLevel, QPoint sceneCoordinate)
190 {
191     qDebug() << __PRETTY_FUNCTION__;
192
193     double tileFactor = 1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel);
194     double xFactor = (sceneCoordinate.x() / (TILE_SIZE_X*tileFactor));
195     double yFactor = (sceneCoordinate.y() / (TILE_SIZE_Y*tileFactor));
196
197     tileFactor = 1 << zoomLevel;
198     double longitude = xFactor / tileFactor * 360.0 - 180;
199
200     double n = M_PI - 2.0 * M_PI * yFactor / tileFactor;
201     double latitude = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
202
203     return QPointF(longitude, latitude);
204 }
205
206 QPoint MapEngine::convertSceneCoordinateToTileNumber(int zoomLevel, QPoint sceneCoordinate)
207 {
208     qDebug() << __PRETTY_FUNCTION__;
209
210     int pow = 1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel);
211     int x = static_cast<int>(sceneCoordinate.x() / (TILE_SIZE_X * pow));
212     int y = static_cast<int>(sceneCoordinate.y() / (TILE_SIZE_Y * pow));
213
214     return QPoint(x, y);
215 }
216
217 QPoint MapEngine::convertTileNumberToSceneCoordinate(int zoomLevel, QPoint tileNumber)
218 {
219     qDebug() << __PRETTY_FUNCTION__;
220
221     int pow = 1 << (MAX_MAP_ZOOM_LEVEL - zoomLevel);
222     int x = tileNumber.x() * TILE_SIZE_X * pow;
223     int y = tileNumber.y() * TILE_SIZE_Y * pow;
224
225     return QPoint(x, y);
226 }
227
228 void MapEngine::disableAutoCenteringIfRequired(QPoint sceneCoordinate)
229 {
230     if (isAutoCenteringEnabled()) {
231         int zoomFactor = (1 << (MAX_MAP_ZOOM_LEVEL - m_zoomLevel));
232
233         QPoint oldPixelValue = QPoint(m_lastAutomaticPosition.x() / zoomFactor,
234                                       m_lastAutomaticPosition.y() / zoomFactor);
235
236         QPoint newPixelValue = QPoint(sceneCoordinate.x() / zoomFactor,
237                                       sceneCoordinate.y() / zoomFactor);
238
239         if ((abs(oldPixelValue.x() - newPixelValue.x()) > AUTO_CENTERING_DISABLE_DISTANCE)
240             || (abs(oldPixelValue.y() - newPixelValue.y()) > AUTO_CENTERING_DISABLE_DISTANCE)) {
241
242             emit mapScrolledManually();
243         }
244     }
245 }
246
247 void MapEngine::friendsPositionsUpdated()
248 {
249     qDebug() << __PRETTY_FUNCTION__;
250
251     m_mapScene->spanItems(m_zoomLevel, m_sceneCoordinate, m_viewSize);
252 }
253
254 void MapEngine::getTiles(QPoint sceneCoordinate)
255 {
256     qDebug() << __PRETTY_FUNCTION__;
257
258     m_viewTilesGrid = calculateTileGrid(sceneCoordinate);
259     updateViewTilesSceneRect();
260     m_mapScene->setTilesGrid(m_viewTilesGrid);
261
262     int topLeftX = m_viewTilesGrid.topLeft().x();
263     int topLeftY = m_viewTilesGrid.topLeft().y();
264     int bottomRightX = m_viewTilesGrid.bottomRight().x();
265     int bottomRightY = m_viewTilesGrid.bottomRight().y();
266
267     int tileMaxVal = tileMaxIndex(m_zoomLevel);
268
269     for (int x = topLeftX; x <= bottomRightX; ++x) {
270         for (int y = topLeftY; y <= bottomRightY; ++y) {
271
272             // map doesn't span in vertical direction, so y index must be inside the limits
273             if (y >= MAP_TILE_MIN_INDEX && y <= tileMaxVal) {
274                 if (!m_mapScene->tileInScene(tilePath(m_zoomLevel, x, y)))
275                     emit fetchImage(m_zoomLevel, normalize(x, MAP_TILE_MIN_INDEX, tileMaxVal), y);
276             }
277         }
278     }
279 }
280
281 void MapEngine::gpsPositionUpdate(QPointF position, qreal accuracy)
282 {
283     qDebug() << __PRETTY_FUNCTION__;
284
285     m_gpsLocationItem->updatePosition(convertLatLonToSceneCoordinate(position), accuracy);
286     m_mapScene->spanItems(m_zoomLevel, m_sceneCoordinate, m_viewSize);
287
288     if (m_autoCenteringEnabled) {
289         m_lastAutomaticPosition = convertLatLonToSceneCoordinate(position);
290         m_scrollStartedByGps = true;
291         scrollToPosition(m_lastAutomaticPosition);
292     }
293 }
294
295 qreal MapEngine::greatCircleDistance(QPointF firstLocation, QPointF secondLocation)
296 {
297     qDebug() << __PRETTY_FUNCTION__;
298
299     const qreal TORAD = (M_PI/180);
300
301     qreal dLat = (secondLocation.y() - firstLocation.y())*TORAD;
302     qreal dLon = (secondLocation.x() - firstLocation.x())*TORAD;
303     qreal a = pow(sin(dLat/2),2) + cos(firstLocation.y()*TORAD) * cos(secondLocation.y()*TORAD)
304               * pow(sin(dLon/2),2);
305     qreal c = 2 * atan2(sqrt(a), sqrt(1-a));
306
307     return (EARTH_RADIUS * c);
308 }
309
310 void MapEngine::init()
311 {
312     qDebug() << __PRETTY_FUNCTION__;
313
314     QPointF startLocation;
315     QSettings settings(DIRECTORY_NAME, FILE_NAME);
316
317     if (settings.value(MAP_LAST_POSITION, ERROR_VALUE_NOT_FOUND_ON_SETTINGS).toString()
318         == ERROR_VALUE_NOT_FOUND_ON_SETTINGS || settings.value(MAP_LAST_ZOOMLEVEL,
319         ERROR_VALUE_NOT_FOUND_ON_SETTINGS).toString() == ERROR_VALUE_NOT_FOUND_ON_SETTINGS) {
320
321         startLocation = QPointF(DEFAULT_LONGITUDE, DEFAULT_LATITUDE);
322         m_zoomLevel = qBound(MIN_VIEW_ZOOM_LEVEL, DEFAULT_START_ZOOM_LEVEL, MAX_MAP_ZOOM_LEVEL);
323     } else {
324         m_zoomLevel = settings.value(MAP_LAST_ZOOMLEVEL, ERROR_VALUE_NOT_FOUND_ON_SETTINGS).toInt();
325         startLocation = settings.value(MAP_LAST_POSITION,
326                                        ERROR_VALUE_NOT_FOUND_ON_SETTINGS).toPointF();
327     }
328
329     emit zoomLevelChanged(m_zoomLevel);
330     centerToCoordinates(QPointF(startLocation.x(), startLocation.y()));
331 }
332
333 bool MapEngine::isAutoCenteringEnabled()
334 {
335     return m_autoCenteringEnabled;
336 }
337
338 bool MapEngine::isCenterTileChanged(QPoint sceneCoordinate)
339 {
340     qDebug() << __PRETTY_FUNCTION__;
341
342     QPoint centerTile = convertSceneCoordinateToTileNumber(m_zoomLevel, sceneCoordinate);
343     QPoint temp = m_centerTile;
344     m_centerTile = centerTile;
345
346     return (centerTile != temp);
347 }
348
349 qreal MapEngine::sceneResolution()
350 {
351     qDebug() << __PRETTY_FUNCTION__;
352
353     const int SHIFT = 200;
354     const int KM_TO_M = 1000;
355     qreal scale = (1 << (MAX_MAP_ZOOM_LEVEL - m_zoomLevel));
356     QPointF centerCoordinate = centerGeoCoordinate();
357     QPoint shiftedSceneCoordinate = QPoint(m_sceneCoordinate.x() + SHIFT*scale
358                                            , m_sceneCoordinate.y());
359     QPointF shiftedCoordinate = convertSceneCoordinateToLatLon(m_zoomLevel, shiftedSceneCoordinate);
360     qreal dist = greatCircleDistance(centerCoordinate, shiftedCoordinate) * KM_TO_M;
361     return (dist / SHIFT);
362 }
363
364 void MapEngine::mapImageReceived(int zoomLevel, int x, int y, const QPixmap &image)
365 {
366     qDebug() << __PRETTY_FUNCTION__;
367
368     // add normal tile inside the world
369     QPoint tileNumber(x, y);
370     m_mapScene->addTile(zoomLevel, tileNumber, image, m_zoomLevel);
371
372     // note: add 1 so odd width is rounded up and even is rounded down
373     int tilesGridWidthHalf = (m_viewTilesGrid.width() + 1) / 2;
374
375     // duplicate to east side? (don't need to duplicate over padding)
376     if (tileNumber.x() < (tilesGridWidthHalf - GRID_PADDING)) {
377         QPoint adjustedTileNumber(tileNumber.x() + tileMaxIndex(zoomLevel) + 1, tileNumber.y());
378         m_mapScene->addTile(zoomLevel, adjustedTileNumber, image, m_zoomLevel);
379     }
380
381     // duplicate to west side? (don't need to duplicate over padding)
382     if (tileNumber.x() > (tileMaxIndex(zoomLevel) - tilesGridWidthHalf + GRID_PADDING)) {
383         QPoint adjustedTileNumber(tileNumber.x() - tileMaxIndex(zoomLevel) - 1, tileNumber.y());
384         m_mapScene->addTile(zoomLevel, adjustedTileNumber, image, m_zoomLevel);
385     }
386 }
387
388 int MapEngine::normalize(int value, int min, int max)
389 {
390     qDebug() << __PRETTY_FUNCTION__;
391     Q_ASSERT_X(max >= min, "parameters", "max can't be smaller than min");
392
393     while (value < min)
394         value += max - min + 1;
395
396     while (value > max)
397         value -= max - min + 1;
398
399     return value;
400 }
401
402 void MapEngine::receiveOwnLocation(User *user)
403 {
404     qDebug() << __PRETTY_FUNCTION__;
405
406     if(user) {
407         QPoint newPosition = convertLatLonToSceneCoordinate(user->coordinates());
408         if (m_ownLocation->pos().toPoint() != newPosition) {
409             m_ownLocation->setPos(newPosition);
410         }
411
412         if (!m_ownLocation->isVisible())
413             m_ownLocation->show();
414     } else {
415         m_ownLocation->hide();
416     }
417
418     m_mapScene->spanItems(m_zoomLevel, m_sceneCoordinate, m_viewSize);
419 }
420
421 QGraphicsScene* MapEngine::scene()
422 {
423     qDebug() << __PRETTY_FUNCTION__;
424
425     return m_mapScene;
426 }
427
428 void MapEngine::scrollerStateChanged(QAbstractAnimation::State newState)
429 {
430     qDebug() << __PRETTY_FUNCTION__;
431
432     if (m_smoothScrollRunning
433         && newState != QAbstractAnimation::Running) {
434             m_smoothScrollRunning = false;
435
436             // don't disable auto centering if current animation was stopped by new update from GPS
437             if (!m_scrollStartedByGps)
438                 disableAutoCenteringIfRequired(m_sceneCoordinate);
439     }
440
441     m_scrollStartedByGps = false;
442 }
443
444 void MapEngine::scrollToPosition(QPoint scenePosition)
445 {
446     qDebug() << __PRETTY_FUNCTION__;
447
448     m_scroller->stop();
449     m_scroller->setEasingCurve(QEasingCurve::InOutQuart);
450     m_scroller->setDuration(SMOOTH_CENTERING_TIME_MS);
451     m_scroller->setStartValue(m_sceneCoordinate);
452     m_scroller->setEndValue(scenePosition);
453     m_smoothScrollRunning = true;
454     m_scroller->start();
455 }
456
457 void MapEngine::setAutoCentering(bool enabled)
458 {
459     qDebug() << __PRETTY_FUNCTION__;
460
461     m_autoCenteringEnabled = enabled;
462 }
463
464 void MapEngine::setCenterPosition(QPoint scenePosition)
465 {
466     qDebug() << __PRETTY_FUNCTION__;
467
468     // jump to opposite side of the world if world horizontal limit is exceeded
469     scenePosition.setX(normalize(scenePosition.x(), MAP_MIN_PIXEL_X, MAP_MAX_PIXEL_X));
470
471     // don't allow vertical scene coordinates go out of the map
472     scenePosition.setY(qBound(MAP_MIN_PIXEL_Y, scenePosition.y(), MAP_MAX_PIXEL_Y));
473
474     if (!m_smoothScrollRunning)
475         disableAutoCenteringIfRequired(scenePosition);
476
477     m_sceneCoordinate = scenePosition;
478     emit locationChanged(m_sceneCoordinate);
479
480     if (isCenterTileChanged(scenePosition)) {
481         getTiles(scenePosition);
482         m_mapScene->removeOutOfViewTiles(m_viewTilesGrid, m_zoomLevel);
483     }
484
485     m_mapScene->spanItems(m_zoomLevel, m_sceneCoordinate, m_viewSize);
486     emit newMapResolution(sceneResolution());
487 }
488
489 void MapEngine::setGPSEnabled(bool enabled)
490 {
491     qDebug() << __PRETTY_FUNCTION__;
492
493     m_gpsLocationItem->setEnabled(enabled);
494 }
495
496 void MapEngine::setRoute(Route &route)
497 {
498     qDebug() << __PRETTY_FUNCTION__;
499
500     m_route = route;
501
502     qDebug() << __PRETTY_FUNCTION__ << "from:" << m_route.startPointName();
503     qDebug() << __PRETTY_FUNCTION__ << "to:" << m_route.endPointName();
504     qDebug() << __PRETTY_FUNCTION__ << "distance:" << m_route.totalDistance();
505     qDebug() << __PRETTY_FUNCTION__ << "estimated time:" << m_route.totalTime();
506
507     foreach (QPointF point, m_route.geometryPoints())
508         qDebug() << __PRETTY_FUNCTION__ << "geometry point:" << point.x() << point.y();
509
510     foreach (RouteSegment segment, m_route.segments()) {
511         qDebug() << __PRETTY_FUNCTION__ << "segment:" << segment.instruction();
512     }
513
514     // delete old route track (if exists)
515     if (m_mapRouteItem) {
516         m_mapScene->removeItem(m_mapRouteItem);
517         delete m_mapRouteItem;
518     }
519
520     // create new route track
521     m_mapRouteItem = new MapRouteItem(&m_route);
522     m_mapScene->addItem(m_mapRouteItem);
523
524     centerAndZoomTo(m_mapRouteItem->boundingRect().toRect());
525 }
526
527 void MapEngine::setZoomLevel(int newZoomLevel)
528 {
529     qDebug() << __PRETTY_FUNCTION__;
530
531     m_zoomLevel = newZoomLevel;
532     zoomed();
533 }
534
535 void MapEngine::setTilesGridSize(const QSize &viewSize)
536 {
537     qDebug() << __PRETTY_FUNCTION__;
538
539     // there must be scrolling reserve of at least half tile added to tile amount
540     // calculated from view size
541     const qreal SCROLLING_RESERVE = 0.5;
542
543     // converting scene tile to tile number does cause grid centering inaccuracy of one tile
544     const int CENTER_TILE_INACCURACY = 1;
545
546     int gridWidth = ceil(qreal(viewSize.width()) / TILE_SIZE_X + SCROLLING_RESERVE)
547                     + CENTER_TILE_INACCURACY + (GRID_PADDING * 2);
548     int gridHeight = ceil(qreal(viewSize.height()) / TILE_SIZE_Y + SCROLLING_RESERVE)
549                      + CENTER_TILE_INACCURACY + (GRID_PADDING * 2);
550
551     m_mapFetcher->setDownloadQueueSize(gridWidth * gridHeight);
552
553     m_tilesGridSize.setHeight(gridHeight);
554     m_tilesGridSize.setWidth(gridWidth);
555 }
556
557 int MapEngine::tileMaxIndex(int zoomLevel)
558 {
559     qDebug() << __PRETTY_FUNCTION__;
560
561     // subtract one because first tile index is zero
562     return tilesPerSide(zoomLevel) - 1;
563 }
564
565 QString MapEngine::tilePath(int zoomLevel, int x, int y)
566 {
567     qDebug() << __PRETTY_FUNCTION__;
568
569     QString tilePathString(QString::number(zoomLevel) + "/");
570     tilePathString.append(QString::number(x) + "/");
571     tilePathString.append(QString::number(y));
572
573     return tilePathString;
574 }
575
576 int MapEngine::tilesPerSide(int zoomLevel)
577 {
578     return (1 << zoomLevel);
579 }
580
581 void MapEngine::updateViewTilesSceneRect()
582 {
583     qDebug() << __PRETTY_FUNCTION__;
584
585     const QPoint ONE_TILE = QPoint(1, 1);
586     const QPoint ONE_PIXEL = QPoint(1, 1);
587
588     QPoint topLeft = convertTileNumberToSceneCoordinate(m_zoomLevel, m_viewTilesGrid.topLeft());
589     // one tile - one pixel is added because returned coordinates are pointing to upper left corner
590     // of the last tile.
591     QPoint bottomRight = convertTileNumberToSceneCoordinate(m_zoomLevel,
592                                                             m_viewTilesGrid.bottomRight()
593                                                              + ONE_TILE) - ONE_PIXEL;
594
595     m_mapScene->tilesSceneRectUpdated(QRect(topLeft, bottomRight));
596 }
597
598 void MapEngine::viewResized(const QSize &size)
599 {
600     qDebug() << __PRETTY_FUNCTION__;
601
602     m_viewSize = size;
603     setTilesGridSize(m_viewSize);
604
605     emit locationChanged(m_sceneCoordinate);
606     getTiles(m_sceneCoordinate);
607     m_mapScene->removeOutOfViewTiles(m_viewTilesGrid, m_zoomLevel);
608     m_mapScene->setSceneVerticalOverlap(m_viewSize.height(), m_zoomLevel);
609 }
610
611 void MapEngine::viewZoomFinished()
612 {
613     qDebug() << __PRETTY_FUNCTION__;
614
615     if (m_zoomedIn) {
616         m_zoomedIn = false;
617         m_mapScene->removeOutOfViewTiles(m_viewTilesGrid, m_zoomLevel);
618     }
619
620     if (m_zoomLevel == MAX_MAP_ZOOM_LEVEL)
621         emit maxZoomLevelReached();
622     else if (m_zoomLevel == MIN_VIEW_ZOOM_LEVEL)
623         emit minZoomLevelReached();
624 }
625
626 void MapEngine::zoomed()
627 {
628     emit zoomLevelChanged(m_zoomLevel);
629     m_mapScene->setTilesDrawingLevels(m_zoomLevel);
630     m_mapScene->setZoomLevel(m_zoomLevel);
631     getTiles(m_sceneCoordinate);
632     m_mapScene->setSceneVerticalOverlap(m_viewSize.height(), m_zoomLevel);
633     m_mapScene->spanItems(m_zoomLevel, m_sceneCoordinate, m_viewSize);
634     emit newMapResolution(sceneResolution());
635 }
636
637 void MapEngine::zoomIn()
638 {
639     qDebug() << __PRETTY_FUNCTION__;
640
641     if (m_zoomLevel < MAX_MAP_ZOOM_LEVEL) {
642         m_zoomLevel++;
643         m_zoomedIn = true;
644         zoomed();
645     }
646 }
647
648 void MapEngine::zoomOut()
649 {
650     qDebug() << __PRETTY_FUNCTION__;
651
652     if (m_zoomLevel > MIN_VIEW_ZOOM_LEVEL) {
653         m_zoomLevel--;
654         zoomed();
655     }
656 }