Some bugs fixed.
[gpssportsniffer] / tilesMap.cpp
1 /****************************************************************************
2 **
3 **  Copyright (C) 2011  Tito Eritja Real <jtitoo@gmail.com>
4 **
5 **  This program is free software: you can redistribute it and/or modify
6 **  it under the terms of the GNU General Public License as published by
7 **  the Free Software Foundation, either version 3 of the License, or
8 **  (at your option) any later version.
9 **
10 **  This program is distributed in the hope that it will be useful,
11 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 **  GNU General Public License for more details.
14 **
15 **  You should have received a copy of the GNU General Public License
16 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 **
18 ****************************************************************************/
19
20 #include "tilesMap.h"
21 #include "constants.h"
22 #include "log.h"
23
24 #include <QDebug>
25 #include <QNetworkDiskCache>
26 #include <QDesktopServices>
27 #include <QBuffer>
28 #include <QByteArray>
29
30
31 /*
32 ThreadSaver::ThreadSaver(QImage qi, QString fs){
33     img = qi;
34     fileString = fs;
35 }
36
37 void ThreadSaver::run(){
38
39     QFile file(fileString);
40
41     if (!file.open(QIODevice::WriteOnly)) {
42         qDebug() << QString("Cannot open file for writing: %1").arg(file.errorString());
43     }
44     img.save(fileString);
45 }
46
47 */
48
49
50 TilesMap::TilesMap(QNetworkSession *session, QObject *parent = 0, Log* log=0, int zoom=0, MapType mapType=MapTypeOpenStreetMaps) :
51     m_session(session),
52     QObject(parent),
53     m_log(log),
54     zoom(zoom),
55     mapType(mapType),
56     network(false),
57     latitude(0),
58     longitude(0),
59     width(WIDTH_DEFAULT),
60     height(HEIGHT_DEFAULT),
61     tilesD(0),
62     cache(false){
63
64     m_mapsDir = QDir(QString(APPLICATION_PATH).append(MAPS_DIR));
65     m_manager = new QNetworkAccessManager(this);
66
67     // Creating and fullfill emptytile
68     m_emptyTile = QPixmap(tdim, tdim);
69     m_emptyTile.fill(Qt::lightGray);
70
71     //log->debug(QString("Is Network accessible?:%1").arg(m_manager->networkAccessible()));
72     QNetworkDiskCache *nCache = new QNetworkDiskCache;
73     nCache->setCacheDirectory(QDesktopServices::storageLocation(QDesktopServices::CacheLocation));
74     m_manager->setCache(nCache);
75     connect(m_manager, SIGNAL(finished(QNetworkReply*)),
76             this, SLOT(handleReplies(QNetworkReply*)));
77
78     //log->debug("exiting from slippyMap constructor!!!!");
79
80 }
81
82 TilesMap::~TilesMap() {
83     for (int i = 0; i < m_pendingReplies.size(); ++i) {
84         delete m_pendingReplies.at(i);
85     }
86
87 }
88
89
90 QPointF TilesMap::coordinate2tile(qreal lat, qreal lng, int zoom)
91 {
92     qreal zn = static_cast<qreal>(1 << zoom);
93     qreal tx = (lng + 180.0) / 360.0 * zn;
94     qreal ty = (1.0 - log(tan(lat * PI / 180.0) + 1.0 / cos(lat * PI / 180.0)) / PI) / 2.0 * zn;
95     return QPointF(tx, ty);
96
97 }
98
99 qreal TilesMap::tilex2long(qreal x, int zoom)
100 {
101     return (x / pow(2.0, zoom) * 360.0 - 180);
102 }
103
104 qreal TilesMap::tiley2lat(qreal y, int zoom)
105 {
106     qreal n = M_PI - 2.0 * M_PI * y / pow(2.0, zoom);
107     return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
108 }
109
110 void TilesMap::updateTiles(){
111    updateTiles(latitude,longitude);
112 }
113
114 void TilesMap::updateTiles(qreal lat, qreal lng){
115
116     if (width <= 0 || height <= 0 || (lat==0 && lng==0))
117         return;
118     latitude=lat;
119     longitude=lng;
120
121     QPointF center = coordinate2tile(latitude,longitude,zoom);
122
123     // getting top-left corner of the centered tile
124     QPoint topLeft = QPoint(width / 2 - (center.x() - floor(center.x())) * tdim,height / 2 - (center.y() - floor(center.y())) * tdim);
125
126     // getting first tile vertical and horizontal
127     QPoint offsetTile=QPoint((topLeft.x() + tdim - 1) / tdim,(topLeft.y() + tdim - 1) / tdim);
128
129     QPoint firstTile = QPoint(static_cast<int>(center.x()) - offsetTile.x(),static_cast<int>(center.y()) - offsetTile.y());
130
131     // offset for top-left tile
132     m_offset = QPoint(topLeft.x() - offsetTile.x() * tdim, topLeft.y() - offsetTile.y() * tdim);
133
134     QPoint lastTile = QPoint(static_cast<int>(center.x()) + (width - topLeft.x() - 1) / tdim, static_cast<int>(center.y()) + (height - topLeft.y() - 1) / tdim);
135
136     // build a rect
137     m_tilesRect = QRect(firstTile.x(), firstTile.y(), lastTile.x() - firstTile.x() + 1, lastTile.y() - firstTile.y() + 1);
138
139     getTiles();
140
141     emit updated(QRect(0, 0, width, height));
142 }
143
144 void TilesMap::getTiles() {
145
146     QString path = mapUrlProvider();
147     QPoint grab(0, 0);
148     for (int x = 0; x <= m_tilesRect.width(); ++x)
149         for (int y = 0; y <= m_tilesRect.height(); ++y) {
150         QPoint tp = m_tilesRect.topLeft() + QPoint(x, y);
151
152         if(!m_tileMaps.contains(tp)){
153             grab = tp;
154
155             if(mapIsOnCache(zoom,grab.x(),grab.y())){
156                 // getting maps from tile cache
157                 m_log->debug("Map is on cache");
158                 QString imgString = QString(fileUrlProvider()).arg(zoom).arg(grab.x()).arg(grab.y());
159                 m_log->info(QString("map name:%1").arg(imgString));
160                 QImage img;
161                 img.load(imgString);
162
163                 m_log->debug(QString("going to update tile with tile: x=%1,y=%2").arg(grab.x()).arg(grab.y()));
164                 m_tileMaps[grab] = QPixmap::fromImage(img);
165                 if (img.isNull()){
166                     m_tileMaps[grab]= m_emptyTile;
167                     // TODO: remove empty image from disk
168                 }
169
170                 emit updated(tileRect(grab));
171             }else if(network && (!m_tileRequests.contains(grab) || timeout(m_tileRequests[grab].dateTime))){
172                 // request Tiles from Server
173                 m_url = QUrl(path.arg(zoom).arg(grab.x()).arg(grab.y()));
174                 QNetworkRequest request;
175                 m_log->info(QString("making a url request:").append(m_url.toString()));
176                 request.setUrl(m_url);
177                 request.setRawHeader("User-Agent", "GPSSniffer 1.0");
178                 request.setAttribute(QNetworkRequest::User, QVariant(grab));
179                 m_pendingReplies << m_manager->get(request);
180
181                 m_tileRequests[grab]=TileRequest(zoom,QDateTime::currentDateTime(),cache,false);
182
183             }
184         }
185     }
186 }
187
188 void TilesMap::cancelDownloading(){
189
190 }
191
192 int TilesMap::downloadMaps(Track* track_p){
193     int numTiles = 0;
194     tilesD=0;
195
196     // Downloading Window Map
197
198     m_log->info("starting to downloading maps");
199
200     downloadWindow(&numTiles,zoom);
201
202     m_log->info("simple maps aded");
203
204     // ading tiles needed by track
205     QList<GpsPoint*> gpsPoints = track_p->getGpsPoints();
206     int updateProgress=0;
207     for(int zoom_temp=zoom; zoom_temp <= MAX_ZOOM_DOWNL; zoom_temp++){
208         for (int i = 0; i < gpsPoints.size(); ++i) {
209             GpsPoint* point = gpsPoints.at(i);
210             downloadTiles(point->getLatitude(), point->getLongitude(),zoom_temp,&numTiles);
211             emit pointsRequested(updateProgress);
212             updateProgress++;
213         }
214     }
215     m_log->info(QString("Num tiles to ad:%1").arg(numTiles));
216     return numTiles;
217 }
218
219 void TilesMap::downloadTiles(qreal lat, qreal lng, int zoom, int* numTiles){
220
221
222     if (width <= 0 || height <= 0 || (lat==0 && lng==0))
223         return;
224     latitude=lat;
225     longitude=lng;
226
227     QPointF center = coordinate2tile(latitude,longitude,zoom);
228
229     // getting top-left corner of the centered tile
230     QPoint topLeft = QPoint(width / 2 - (center.x() - floor(center.x())) * tdim,height / 2 - (center.y() - floor(center.y())) * tdim);
231
232     // getting first tile vertical and horizontal
233     QPoint offsetTile=QPoint((topLeft.x() + tdim - 1) / tdim,(topLeft.y() + tdim - 1) / tdim);
234
235     QPoint firstTile = QPoint(static_cast<int>(center.x()) - offsetTile.x(),static_cast<int>(center.y()) - offsetTile.y());
236
237     // offset for top-left tile
238     m_offset = QPoint(topLeft.x() - offsetTile.x() * tdim, topLeft.y() - offsetTile.y() * tdim);
239
240     QPoint lastTile = QPoint(static_cast<int>(center.x()) + (width - topLeft.x() - 1) / tdim, static_cast<int>(center.y()) + (height - topLeft.y() - 1) / tdim);
241
242     // build a rect
243     m_tilesRect = QRect(firstTile.x(), firstTile.y(), lastTile.x() - firstTile.x() + 1, lastTile.y() - firstTile.y() + 1);
244
245     downloadWindow(numTiles,zoom);
246
247 }
248
249
250 void TilesMap::downloadWindow(int* numTiles,int zoom){
251
252     QString path = mapUrlProvider();
253     QPoint grab(0, 0);
254     for (int x = 0; x <= m_tilesRect.width(); ++x){
255         for (int y = 0; y <= m_tilesRect.height(); ++y) {
256             QPoint tp = m_tilesRect.topLeft() + QPoint(x, y);
257             grab=tp;
258             if(!mapIsOnCache(zoom,grab.x(),grab.y()) && network && (!m_tileRequests.contains(grab) || timeout(m_tileRequests[grab].dateTime))){
259
260                 // request Tiles from Server
261                 m_url = QUrl(path.arg(zoom).arg(grab.x()).arg(grab.y()));
262                 QNetworkRequest request;
263                 m_log->info(QString("making %1 url request:").arg(*numTiles).append(m_url.toString()));
264                 request.setUrl(m_url);
265                 request.setRawHeader("User-Agent", "GPSSniffer 1.0");
266                     request.setAttribute(QNetworkRequest::User, QVariant(grab));
267                 m_pendingReplies << m_manager->get(request);
268
269                 m_tileRequests[grab]= TileRequest(zoom,QDateTime::currentDateTime(),true,true);
270                 (*numTiles)++;
271             }
272         }
273     }
274 }
275
276
277 void TilesMap::render(QPainter *p, const QRect &rect) {
278     //log->debug("rendering maps...");
279     for (int x = 0; x <= m_tilesRect.width(); ++x)
280         for (int y = 0; y <= m_tilesRect.height(); ++y) {
281             QPoint tp(x + m_tilesRect.left(), y + m_tilesRect.top());
282             QRect box = tileRect(tp);
283             if (rect.intersects(box)) {
284                 if (m_tileMaps.contains(tp))
285                     p->drawPixmap(box, m_tileMaps.value(tp));
286                 else
287                     p->drawPixmap(box, m_emptyTile);
288             }
289         }
290     //log->debug("done");
291 }
292
293 void TilesMap::pan(const QPoint &delta) {
294     QPointF dx = QPointF(delta) / qreal(tdim);
295     QPointF center = coordinate2tile(latitude, longitude, zoom) - dx;
296     latitude = tiley2lat(center.y(), zoom);
297     longitude = tilex2long(center.x(), zoom);
298     updateTiles();
299 }
300
301 void TilesMap::updatePosition(GpsPoint point) {
302     latitude = point.getLatitude();
303     longitude = point.getLongitude();
304     updateTiles();
305 }
306
307 void TilesMap::clearAllMaps(){
308
309     m_pendingReplies.clear();
310     m_tileRequests.clear();
311     m_tileMaps.clear();
312     updateTiles();
313 }
314
315 void TilesMap::handleReplies(QNetworkReply *reply){
316
317     QImage img;
318     QPoint tp = reply->request().attribute(QNetworkRequest::User).toPoint();
319     TileRequest tr = m_tileRequests.value(tp);
320     int zoomReply=zoom;
321
322     if(!QDir(mapsDir()).exists()){
323         QDir().mkdir(mapsDir());
324     }
325     if (!reply->error()){
326
327         if (!img.load(reply, 0)){
328             img = QImage();
329         }else{
330
331
332             if(tr.downloading){
333                 zoomReply=tr.zoom;
334             }
335
336             if(tr.save){
337                 QString fileString = QString(fileUrlProvider()).arg(zoomReply).arg(tp.x()).arg(tp.y());
338
339                 if(!QDir(urlProvider()).exists())
340                     QDir().mkdir(urlProvider());
341
342                 if(!QDir(urlProvider().append("%1/").arg(zoomReply)).exists())
343                     QDir().mkdir(urlProvider().append("%1").arg(zoomReply));
344
345                 if(!QDir(urlProvider().append("%1/").arg(zoomReply).append("%1/").arg(tp.x())).exists())
346                     QDir().mkdir(urlProvider().append("%1/").arg(zoomReply).append("%1/").arg(tp.x()));
347
348
349                 QFile file(fileString);
350
351                 if (!file.open(QIODevice::WriteOnly)) {
352                     qDebug() << QString("Cannot open file for writing: %1").arg(file.errorString());
353                 }
354                 img.save(fileString);
355
356                 if(tr.downloading)
357                     tilesD++;
358             }
359         }
360     }
361     m_pendingReplies.removeAll(reply);
362     reply->deleteLater();
363
364     m_tileRequests.remove(tp);
365
366     if(!tr.downloading){
367
368         m_tileMaps[tp] = QPixmap::fromImage(img);
369         if (img.isNull()){
370             m_tileMaps[tp] = m_emptyTile;
371         }
372         emit updated(tileRect(tp));
373
374
375         // purge unused spaces
376         QRect bound = m_tilesRect.adjusted(-2, -2, 2, 2);
377         foreach(QPoint tp, m_tileMaps.keys())
378             if (!bound.contains(tp))
379                 m_tileMaps.remove(tp);
380
381     }else{
382         emit tilesDownloaded(tilesD);
383     }
384
385 }
386
387
388
389 QRect TilesMap::tileRect(const QPoint &tp) {
390     QPoint t = tp - m_tilesRect.topLeft();
391     int x = t.x() * tdim + m_offset.x();
392     int y = t.y() * tdim + m_offset.y();
393
394     return QRect(x, y, tdim, tdim);
395 }
396
397 bool TilesMap::mapIsOnCache(int zoom,int x,int y){
398
399     QString fileString = QString(fileUrlProvider()).arg(zoom).arg(x).arg(y);
400
401     QFile file(fileString);
402     if(file.exists()){
403         return true;
404     }else
405         return false;
406 }
407
408 bool TilesMap::timeout(QDateTime qdt){
409
410     QDateTime now = QDateTime::currentDateTime();
411     int duration = qdt.secsTo(now);
412     bool retvalue = (duration>=HTTP_TIMEOUT);
413     return retvalue;
414 }
415
416
417 QString TilesMap::fileUrlProvider(){
418
419     QString dir = QString(APPLICATION_PATH).append(MAPS_DIR);
420     switch(mapType){
421     case MapTypeGoogleMaps:
422         dir.append("/google/%1/%2/%3.png");
423         break;
424     case MapTypeICC:
425         dir.append("/icc/%1/%2/%3.png");
426         break;
427     case MapTypeCloudMade:
428         dir.append("/cloudmade/%1/%2/%3.png");
429         break;
430     case MapTypeOpenCycleMaps:
431         dir.append("/ocm/%1/%2/%3.png");
432         break;
433     default:
434     case MapTypeOpenStreetMaps:
435         dir.append("/osm/%1/%2/%3.png");
436         break;
437     }
438     return dir;
439 }
440
441 QString TilesMap::mapsDir(){
442     QString dir = QString(APPLICATION_PATH).append(MAPS_DIR);
443     return dir;
444 }
445
446 QString TilesMap::urlProvider(){
447
448     QString dir = mapsDir();
449     switch(mapType){
450     case MapTypeGoogleMaps:
451         dir.append("/google/");
452         break;
453     case MapTypeICC:
454         dir.append("/icc/");
455         break;
456     case MapTypeCloudMade:
457         dir.append("/cloudmade/");
458         break;
459     case MapTypeOpenCycleMaps:
460         dir.append("/ocm/");
461         break;
462     default:
463     case MapTypeOpenStreetMaps:
464         dir.append("/osm/");
465         break;
466     }
467     return dir;
468 }
469
470 QString TilesMap::mapUrlProvider(){
471
472     //m_log->debug("--- mapUrlProvider ---");
473     QString url("http://");
474     //m_log->debug(QString("mapType:%1").arg(mapType));
475
476     switch(mapType){
477     case MapTypeCloudMade:
478         url.append("tile.cloudmade.com/e63d1732f3be48299e0c19d6bcefe25c/3/256/%1/%2/%3.png");
479         break;
480     case MapTypeGoogleMaps:
481         url.append("mt1.google.com/vt/lyrs=m@121,bike&hl=es&x=%2&y=%3&z=%1");
482         //url.append("mt1.google.com/vt/&x=%2&y=%3&z=%1");
483         break;
484     case MapTypeICC:
485         url.append("norma.icc.cat/tilecache/tilecache.py/1.0.0/topo3857/%1/%2/%3.png?type=google");
486         break;
487     case MapTypeOpenCycleMaps:
488         url.append("tile.opencyclemap.org/cycle/%1/%2/%3.png");
489         break;
490     default:
491     case MapTypeOpenStreetMaps:
492         url.append("tile.openstreetmap.org/%1/%2/%3.png");
493         break;
494     }
495     return url;
496 }
497
498