2 Situare - A location system for Facebook
3 Copyright (C) 2010 Ixonos Plc. Authors:
5 Jussi Laitinen - jussi.laitinen@ixonos.com
6 Sami Rämö - sami.ramo@ixonos.com
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.
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.
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,
23 #include <QNetworkAccessManager>
24 #include <QNetworkRequest>
25 #include <QNetworkReply>
29 #include <QNetworkDiskCache>
30 #include <QDesktopServices>
32 #include "mapfetcher.h"
33 #include "mapcommon.h"
35 #include "network/networkaccessmanager.h"
37 const int MAX_PARALLEL_DOWNLOADS = 2; ///< Max simultaneous parallel downloads
38 const int NOT_FOUND = -1; ///< Return value if matching request is not found from the list
40 MapFetcher::MapFetcher(NetworkAccessManager *manager, QObject *parent)
42 , m_pendingRequestsSize(0)
43 , m_fetchMapImagesTimerRunning(false)
46 qDebug() << __PRETTY_FUNCTION__;
48 QNetworkDiskCache *diskCache = new QNetworkDiskCache(this);
49 diskCache->setCacheDirectory(QDesktopServices::storageLocation(
50 QDesktopServices::CacheLocation));
51 m_manager->setCache(diskCache);
53 connect(m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(
54 downloadFinished(QNetworkReply*)));
57 QUrl MapFetcher::buildURL(int zoomLevel, QPoint tileNumbers)
59 qDebug() << __PRETTY_FUNCTION__;
62 * @brief Map server string for building actual URL
68 * NOTE: If the URL is changed, then the parseURL method must be changed to match
69 * the new URL structure
73 const QString MAP_SERVER_URL = QString("http://tile.openstreetmap.org/mapnik/%1/%2/%3.png");
75 return QString(MAP_SERVER_URL)
76 .arg(zoomLevel).arg(tileNumbers.x()).arg(tileNumbers.y());
79 void MapFetcher::checkNextRequestFromCache()
81 qDebug() << __PRETTY_FUNCTION__;
83 int i = newestRequestIndex(false);
86 QUrl url = m_pendingRequests[i].url;
87 if (!url.isEmpty() && url.isValid()) {
88 if (loadImageFromCache(url)) {
89 // was found, remove from the list
90 m_pendingRequests.removeAt(i);
93 // didn't found from cache so mark cache checked and leave to queue
94 m_pendingRequests[i].cacheChecked = true;
96 if (m_currentDownloads.size() < MAX_PARALLEL_DOWNLOADS)
102 // schedule checking of the next request if the list is not empty
103 if (newestRequestIndex(false) != NOT_FOUND)
104 QTimer::singleShot(0, this, SLOT(checkNextRequestFromCache()));
106 m_fetchMapImagesTimerRunning = false;
109 void MapFetcher::downloadFinished(QNetworkReply *reply)
111 qDebug() << __PRETTY_FUNCTION__;
113 if (m_currentDownloads.contains(reply)) {
115 if (reply->error() == QNetworkReply::NoError) {
117 QUrl url = reply->url();
119 if (!image.load(reply, 0))
125 parseURL(url, &zoomLevel, &x, &y);
127 emit mapImageReceived(zoomLevel, x, y, QPixmap::fromImage(image));
130 emit error(ErrorContext::SITUARE, SituareError::MAP_IMAGE_DOWNLOAD_FAILED);
133 m_currentDownloads.removeAll(reply);
134 reply->deleteLater();
139 void MapFetcher::enqueueFetchMapImage(int zoomLevel, int x, int y)
141 qDebug() << __PRETTY_FUNCTION__;
143 QUrl url = buildURL(zoomLevel, QPoint(x, y));
145 // ignore request if it is already downloading
146 foreach (QNetworkReply *reply, m_currentDownloads) {
147 if (reply->url() == url)
151 // check if new request is already in the list and move it to the begin of the list...
153 for (int i = 0; i < m_pendingRequests.size(); i++) {
154 if (m_pendingRequests[i].url == url) {
155 m_pendingRequests.move(i, 0);
160 // ...or add new request to the begining of the list
162 MapTileRequest request(url);
163 m_pendingRequests.prepend(request);
166 limitPendingRequestsListSize();
168 if (!m_fetchMapImagesTimerRunning) {
169 m_fetchMapImagesTimerRunning = true;
170 QTimer::singleShot(0, this, SLOT(checkNextRequestFromCache()));
174 void MapFetcher::limitPendingRequestsListSize()
176 qDebug() << __PRETTY_FUNCTION__;
178 while (m_pendingRequests.size() > m_pendingRequestsSize) {
179 m_pendingRequests.removeLast();
183 bool MapFetcher::loadImageFromCache(const QUrl &url)
185 qDebug() << __PRETTY_FUNCTION__;
187 bool imageFound = false;
189 QAbstractNetworkCache *cache = m_manager->cache();
196 parseURL(url, &zoomLevel, &x, &y);
197 int originalZoomLevel = zoomLevel;
199 // try to fetch requested and upper level images until found or MAX_UPPER_LEVELS
201 const int MAX_UPPER_LEVELS = 4;
203 QIODevice *cacheImage = cache->data(buildURL(zoomLevel, QPoint(x, y)));
206 if (pixmap.loadFromData(cacheImage->readAll())) {
208 emit mapImageReceived(zoomLevel, x, y, pixmap);
214 && ((originalZoomLevel - zoomLevel) < MAX_UPPER_LEVELS)
215 && translateIndexesToUpperLevel(zoomLevel, x, y));
217 // check expiration if image was found from requested level
218 if (imageFound && (originalZoomLevel == zoomLevel)) {
219 // check if image is expired
220 QNetworkCacheMetaData metaData = cache->metaData(url);
221 if ((metaData.expirationDate().isValid()) && (url.isValid())) {
223 if (metaData.expirationDate() < QDateTime::currentDateTime()) {
230 // if image was found, but from upper level, return false
231 if (imageFound && (originalZoomLevel != zoomLevel))
238 bool MapFetcher::translateIndexesToUpperLevel(int &zoomLevel, int &x, int &y)
240 qDebug() << __PRETTY_FUNCTION__;
242 if (zoomLevel > OSM_MIN_ZOOM_LEVEL) {
253 int MapFetcher::newestRequestIndex(bool cacheChecked)
255 qDebug() << __PRETTY_FUNCTION__;
257 for (int i = 0; i < m_pendingRequests.size(); i++) {
258 if (m_pendingRequests[i].cacheChecked == cacheChecked) {
266 void MapFetcher::parseURL(const QUrl &url, int *zoom, int *x, int *y)
268 qDebug() << __PRETTY_FUNCTION__;
270 QString path = url.path();
271 QStringList pathParts = path.split("/", QString::SkipEmptyParts);
273 int size = pathParts.size();
275 // Example URL: "http://tile.openstreetmap.org/mapnik/14/9354/4263.png"
276 const int MIN_PATH_SPLITTED_PARTS = 4;
277 const int ZOOM_INDEX = size - 3;
278 const int X_INDEX = size - 2;
279 const int Y_INDEX = size - 1;
280 const int FILE_EXTENSION_LENGTH = 4;
282 if (size >= MIN_PATH_SPLITTED_PARTS) {
283 *zoom = (pathParts.at(ZOOM_INDEX)).toInt();
284 *x = (pathParts.at(X_INDEX)).toInt();
285 QString yString = pathParts.at(Y_INDEX);
286 yString.chop(FILE_EXTENSION_LENGTH);
287 *y = yString.toInt();
291 void MapFetcher::setDownloadQueueSize(int size)
293 qDebug() << __PRETTY_FUNCTION__ << "size:" << size;
295 m_pendingRequestsSize = size;
296 limitPendingRequestsListSize();
299 void MapFetcher::startNextDownload()
301 qDebug() << __PRETTY_FUNCTION__;
303 int i = newestRequestIndex(true);
305 if (i != NOT_FOUND) {
306 QUrl url = m_pendingRequests.takeAt(i).url;
308 QNetworkRequest request(url);
309 request.setRawHeader("User-Agent", "Situare");
310 QNetworkReply *reply = m_manager->get(request);
312 m_currentDownloads.append(reply);