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