|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the demonstration applications of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include <QtCore> |
|
43 #include <QtGui> |
|
44 #include <QtNetwork> |
|
45 |
|
46 #if defined (Q_OS_SYMBIAN) |
|
47 #include "sym_iap_util.h" |
|
48 #endif |
|
49 |
|
50 #include <math.h> |
|
51 |
|
52 #ifndef M_PI |
|
53 #define M_PI 3.14159265358979323846 |
|
54 #endif |
|
55 |
|
56 // how long (milliseconds) the user need to hold (after a tap on the screen) |
|
57 // before triggering the magnifying glass feature |
|
58 // 701, a prime number, is the sum of 229, 233, 239 |
|
59 // (all three are also prime numbers, consecutive!) |
|
60 #define HOLD_TIME 701 |
|
61 |
|
62 // maximum size of the magnifier |
|
63 // Hint: see above to find why I picked this one :) |
|
64 #define MAX_MAGNIFIER 229 |
|
65 |
|
66 uint qHash(const QPoint& p) |
|
67 { |
|
68 return p.x() * 17 ^ p.y(); |
|
69 } |
|
70 |
|
71 // tile size in pixels |
|
72 const int tdim = 256; |
|
73 |
|
74 QPointF tileForCoordinate(qreal lat, qreal lng, int zoom) |
|
75 { |
|
76 qreal zn = static_cast<qreal>(1 << zoom); |
|
77 qreal tx = (lng + 180.0) / 360.0; |
|
78 qreal ty = (1.0 - log(tan(lat * M_PI / 180.0) + |
|
79 1.0 / cos(lat * M_PI / 180.0)) / M_PI) / 2.0; |
|
80 return QPointF(tx * zn, ty * zn); |
|
81 } |
|
82 |
|
83 qreal longitudeFromTile(qreal tx, int zoom) |
|
84 { |
|
85 qreal zn = static_cast<qreal>(1 << zoom); |
|
86 qreal lat = tx / zn * 360.0 - 180.0; |
|
87 return lat; |
|
88 } |
|
89 |
|
90 qreal latitudeFromTile(qreal ty, int zoom) |
|
91 { |
|
92 qreal zn = static_cast<qreal>(1 << zoom); |
|
93 qreal n = M_PI - 2 * M_PI * ty / zn; |
|
94 qreal lng = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n))); |
|
95 return lng; |
|
96 } |
|
97 |
|
98 class SlippyMap: public QObject |
|
99 { |
|
100 Q_OBJECT |
|
101 |
|
102 public: |
|
103 int width; |
|
104 int height; |
|
105 int zoom; |
|
106 qreal latitude; |
|
107 qreal longitude; |
|
108 |
|
109 SlippyMap(QObject *parent = 0) |
|
110 : QObject(parent) |
|
111 , width(400) |
|
112 , height(300) |
|
113 , zoom(15) |
|
114 , latitude(59.9138204) |
|
115 , longitude(10.7387413) { |
|
116 m_emptyTile = QPixmap(tdim, tdim); |
|
117 m_emptyTile.fill(Qt::lightGray); |
|
118 |
|
119 QNetworkDiskCache *cache = new QNetworkDiskCache; |
|
120 cache->setCacheDirectory(QDesktopServices::storageLocation |
|
121 (QDesktopServices::CacheLocation)); |
|
122 m_manager.setCache(cache); |
|
123 connect(&m_manager, SIGNAL(finished(QNetworkReply*)), |
|
124 this, SLOT(handleNetworkData(QNetworkReply*))); |
|
125 } |
|
126 |
|
127 void invalidate() { |
|
128 if (width <= 0 || height <= 0) |
|
129 return; |
|
130 |
|
131 QPointF ct = tileForCoordinate(latitude, longitude, zoom); |
|
132 qreal tx = ct.x(); |
|
133 qreal ty = ct.y(); |
|
134 |
|
135 // top-left corner of the center tile |
|
136 int xp = width / 2 - (tx - floor(tx)) * tdim; |
|
137 int yp = height / 2 - (ty - floor(ty)) * tdim; |
|
138 |
|
139 // first tile vertical and horizontal |
|
140 int xa = (xp + tdim - 1) / tdim; |
|
141 int ya = (yp + tdim - 1) / tdim; |
|
142 int xs = static_cast<int>(tx) - xa; |
|
143 int ys = static_cast<int>(ty) - ya; |
|
144 |
|
145 // offset for top-left tile |
|
146 m_offset = QPoint(xp - xa * tdim, yp - ya * tdim); |
|
147 |
|
148 // last tile vertical and horizontal |
|
149 int xe = static_cast<int>(tx) + (width - xp - 1) / tdim; |
|
150 int ye = static_cast<int>(ty) + (height - yp - 1) / tdim; |
|
151 |
|
152 // build a rect |
|
153 m_tilesRect = QRect(xs, ys, xe - xs + 1, ye - ys + 1); |
|
154 |
|
155 if (m_url.isEmpty()) |
|
156 download(); |
|
157 |
|
158 emit updated(QRect(0, 0, width, height)); |
|
159 } |
|
160 |
|
161 void render(QPainter *p, const QRect &rect) { |
|
162 for (int x = 0; x <= m_tilesRect.width(); ++x) |
|
163 for (int y = 0; y <= m_tilesRect.height(); ++y) { |
|
164 QPoint tp(x + m_tilesRect.left(), y + m_tilesRect.top()); |
|
165 QRect box = tileRect(tp); |
|
166 if (rect.intersects(box)) { |
|
167 if (m_tilePixmaps.contains(tp)) |
|
168 p->drawPixmap(box, m_tilePixmaps.value(tp)); |
|
169 else |
|
170 p->drawPixmap(box, m_emptyTile); |
|
171 } |
|
172 } |
|
173 } |
|
174 |
|
175 void pan(const QPoint &delta) { |
|
176 QPointF dx = QPointF(delta) / qreal(tdim); |
|
177 QPointF center = tileForCoordinate(latitude, longitude, zoom) - dx; |
|
178 latitude = latitudeFromTile(center.y(), zoom); |
|
179 longitude = longitudeFromTile(center.x(), zoom); |
|
180 invalidate(); |
|
181 } |
|
182 |
|
183 private slots: |
|
184 |
|
185 void handleNetworkData(QNetworkReply *reply) { |
|
186 QImage img; |
|
187 QPoint tp = reply->request().attribute(QNetworkRequest::User).toPoint(); |
|
188 QUrl url = reply->url(); |
|
189 if (!reply->error()) |
|
190 if (!img.load(reply, 0)) |
|
191 img = QImage(); |
|
192 reply->deleteLater(); |
|
193 m_tilePixmaps[tp] = QPixmap::fromImage(img); |
|
194 if (img.isNull()) |
|
195 m_tilePixmaps[tp] = m_emptyTile; |
|
196 emit updated(tileRect(tp)); |
|
197 |
|
198 // purge unused spaces |
|
199 QRect bound = m_tilesRect.adjusted(-2, -2, 2, 2); |
|
200 foreach(QPoint tp, m_tilePixmaps.keys()) |
|
201 if (!bound.contains(tp)) |
|
202 m_tilePixmaps.remove(tp); |
|
203 |
|
204 download(); |
|
205 } |
|
206 |
|
207 void download() { |
|
208 QPoint grab(0, 0); |
|
209 for (int x = 0; x <= m_tilesRect.width(); ++x) |
|
210 for (int y = 0; y <= m_tilesRect.height(); ++y) { |
|
211 QPoint tp = m_tilesRect.topLeft() + QPoint(x, y); |
|
212 if (!m_tilePixmaps.contains(tp)) { |
|
213 grab = tp; |
|
214 break; |
|
215 } |
|
216 } |
|
217 if (grab == QPoint(0, 0)) { |
|
218 m_url = QUrl(); |
|
219 return; |
|
220 } |
|
221 |
|
222 QString path = "http://tile.openstreetmap.org/%1/%2/%3.png"; |
|
223 m_url = QUrl(path.arg(zoom).arg(grab.x()).arg(grab.y())); |
|
224 QNetworkRequest request; |
|
225 request.setUrl(m_url); |
|
226 request.setRawHeader("User-Agent", "Nokia (Qt) Graphics Dojo 1.0"); |
|
227 request.setAttribute(QNetworkRequest::User, QVariant(grab)); |
|
228 m_manager.get(request); |
|
229 } |
|
230 |
|
231 signals: |
|
232 void updated(const QRect &rect); |
|
233 |
|
234 protected: |
|
235 QRect tileRect(const QPoint &tp) { |
|
236 QPoint t = tp - m_tilesRect.topLeft(); |
|
237 int x = t.x() * tdim + m_offset.x(); |
|
238 int y = t.y() * tdim + m_offset.y(); |
|
239 return QRect(x, y, tdim, tdim); |
|
240 } |
|
241 |
|
242 private: |
|
243 QPoint m_offset; |
|
244 QRect m_tilesRect; |
|
245 QPixmap m_emptyTile; |
|
246 QHash<QPoint, QPixmap> m_tilePixmaps; |
|
247 QNetworkAccessManager m_manager; |
|
248 QUrl m_url; |
|
249 }; |
|
250 |
|
251 class LightMaps: public QWidget |
|
252 { |
|
253 Q_OBJECT |
|
254 |
|
255 public: |
|
256 LightMaps(QWidget *parent = 0) |
|
257 : QWidget(parent) |
|
258 , pressed(false) |
|
259 , snapped(false) |
|
260 , zoomed(false) |
|
261 , invert(false) { |
|
262 m_normalMap = new SlippyMap(this); |
|
263 m_largeMap = new SlippyMap(this); |
|
264 connect(m_normalMap, SIGNAL(updated(QRect)), SLOT(updateMap(QRect))); |
|
265 connect(m_largeMap, SIGNAL(updated(QRect)), SLOT(update())); |
|
266 } |
|
267 |
|
268 void setCenter(qreal lat, qreal lng) { |
|
269 m_normalMap->latitude = lat; |
|
270 m_normalMap->longitude = lng; |
|
271 m_normalMap->invalidate(); |
|
272 m_largeMap->invalidate(); |
|
273 } |
|
274 |
|
275 public slots: |
|
276 void toggleNightMode() { |
|
277 invert = !invert; |
|
278 update(); |
|
279 } |
|
280 |
|
281 private slots: |
|
282 void updateMap(const QRect &r) { |
|
283 update(r); |
|
284 } |
|
285 |
|
286 protected: |
|
287 |
|
288 void activateZoom() { |
|
289 zoomed = true; |
|
290 tapTimer.stop(); |
|
291 m_largeMap->zoom = m_normalMap->zoom + 1; |
|
292 m_largeMap->width = m_normalMap->width * 2; |
|
293 m_largeMap->height = m_normalMap->height * 2; |
|
294 m_largeMap->latitude = m_normalMap->latitude; |
|
295 m_largeMap->longitude = m_normalMap->longitude; |
|
296 m_largeMap->invalidate(); |
|
297 update(); |
|
298 } |
|
299 |
|
300 void resizeEvent(QResizeEvent *) { |
|
301 m_normalMap->width = width(); |
|
302 m_normalMap->height = height(); |
|
303 m_normalMap->invalidate(); |
|
304 m_largeMap->width = m_normalMap->width * 2; |
|
305 m_largeMap->height = m_normalMap->height * 2; |
|
306 m_largeMap->invalidate(); |
|
307 } |
|
308 |
|
309 void paintEvent(QPaintEvent *event) { |
|
310 QPainter p; |
|
311 p.begin(this); |
|
312 m_normalMap->render(&p, event->rect()); |
|
313 p.setPen(Qt::black); |
|
314 #if defined(Q_OS_SYMBIAN) |
|
315 QFont font = p.font(); |
|
316 font.setPixelSize(13); |
|
317 p.setFont(font); |
|
318 #endif |
|
319 p.drawText(rect(), Qt::AlignBottom | Qt::TextWordWrap, |
|
320 "Map data CCBYSA 2009 OpenStreetMap.org contributors"); |
|
321 p.end(); |
|
322 |
|
323 if (zoomed) { |
|
324 int dim = qMin(width(), height()); |
|
325 int magnifierSize = qMin(MAX_MAGNIFIER, dim * 2 / 3); |
|
326 int radius = magnifierSize / 2; |
|
327 int ring = radius - 15; |
|
328 QSize box = QSize(magnifierSize, magnifierSize); |
|
329 |
|
330 // reupdate our mask |
|
331 if (maskPixmap.size() != box) { |
|
332 maskPixmap = QPixmap(box); |
|
333 maskPixmap.fill(Qt::transparent); |
|
334 |
|
335 QRadialGradient g; |
|
336 g.setCenter(radius, radius); |
|
337 g.setFocalPoint(radius, radius); |
|
338 g.setRadius(radius); |
|
339 g.setColorAt(1.0, QColor(255, 255, 255, 0)); |
|
340 g.setColorAt(0.5, QColor(128, 128, 128, 255)); |
|
341 |
|
342 QPainter mask(&maskPixmap); |
|
343 mask.setRenderHint(QPainter::Antialiasing); |
|
344 mask.setCompositionMode(QPainter::CompositionMode_Source); |
|
345 mask.setBrush(g); |
|
346 mask.setPen(Qt::NoPen); |
|
347 mask.drawRect(maskPixmap.rect()); |
|
348 mask.setBrush(QColor(Qt::transparent)); |
|
349 mask.drawEllipse(g.center(), ring, ring); |
|
350 mask.end(); |
|
351 } |
|
352 |
|
353 QPoint center = dragPos - QPoint(0, radius); |
|
354 center = center + QPoint(0, radius / 2); |
|
355 QPoint corner = center - QPoint(radius, radius); |
|
356 |
|
357 QPoint xy = center * 2 - QPoint(radius, radius); |
|
358 |
|
359 // only set the dimension to the magnified portion |
|
360 if (zoomPixmap.size() != box) { |
|
361 zoomPixmap = QPixmap(box); |
|
362 zoomPixmap.fill(Qt::lightGray); |
|
363 } |
|
364 if (true) { |
|
365 QPainter p(&zoomPixmap); |
|
366 p.translate(-xy); |
|
367 m_largeMap->render(&p, QRect(xy, box)); |
|
368 p.end(); |
|
369 } |
|
370 |
|
371 QPainterPath clipPath; |
|
372 clipPath.addEllipse(center, ring, ring); |
|
373 |
|
374 QPainter p(this); |
|
375 p.setRenderHint(QPainter::Antialiasing); |
|
376 p.setClipPath(clipPath); |
|
377 p.drawPixmap(corner, zoomPixmap); |
|
378 p.setClipping(false); |
|
379 p.drawPixmap(corner, maskPixmap); |
|
380 p.setPen(Qt::gray); |
|
381 p.drawPath(clipPath); |
|
382 } |
|
383 if (invert) { |
|
384 QPainter p(this); |
|
385 p.setCompositionMode(QPainter::CompositionMode_Difference); |
|
386 p.fillRect(event->rect(), Qt::white); |
|
387 p.end(); |
|
388 } |
|
389 } |
|
390 |
|
391 void timerEvent(QTimerEvent *) { |
|
392 if (!zoomed) |
|
393 activateZoom(); |
|
394 update(); |
|
395 } |
|
396 |
|
397 void mousePressEvent(QMouseEvent *event) { |
|
398 if (event->buttons() != Qt::LeftButton) |
|
399 return; |
|
400 pressed = snapped = true; |
|
401 pressPos = dragPos = event->pos(); |
|
402 tapTimer.stop(); |
|
403 tapTimer.start(HOLD_TIME, this); |
|
404 } |
|
405 |
|
406 void mouseMoveEvent(QMouseEvent *event) { |
|
407 if (!event->buttons()) |
|
408 return; |
|
409 if (!zoomed) { |
|
410 if (!pressed || !snapped) { |
|
411 QPoint delta = event->pos() - pressPos; |
|
412 pressPos = event->pos(); |
|
413 m_normalMap->pan(delta); |
|
414 return; |
|
415 } else { |
|
416 const int threshold = 10; |
|
417 QPoint delta = event->pos() - pressPos; |
|
418 if (snapped) { |
|
419 snapped &= delta.x() < threshold; |
|
420 snapped &= delta.y() < threshold; |
|
421 snapped &= delta.x() > -threshold; |
|
422 snapped &= delta.y() > -threshold; |
|
423 } |
|
424 if (!snapped) |
|
425 tapTimer.stop(); |
|
426 } |
|
427 } else { |
|
428 dragPos = event->pos(); |
|
429 update(); |
|
430 } |
|
431 } |
|
432 |
|
433 void mouseReleaseEvent(QMouseEvent *) { |
|
434 zoomed = false; |
|
435 update(); |
|
436 } |
|
437 |
|
438 void keyPressEvent(QKeyEvent *event) { |
|
439 if (!zoomed) { |
|
440 if (event->key() == Qt::Key_Left) |
|
441 m_normalMap->pan(QPoint(20, 0)); |
|
442 if (event->key() == Qt::Key_Right) |
|
443 m_normalMap->pan(QPoint(-20, 0)); |
|
444 if (event->key() == Qt::Key_Up) |
|
445 m_normalMap->pan(QPoint(0, 20)); |
|
446 if (event->key() == Qt::Key_Down) |
|
447 m_normalMap->pan(QPoint(0, -20)); |
|
448 if (event->key() == Qt::Key_Z || event->key() == Qt::Key_Select) { |
|
449 dragPos = QPoint(width() / 2, height() / 2); |
|
450 activateZoom(); |
|
451 } |
|
452 } else { |
|
453 if (event->key() == Qt::Key_Z || event->key() == Qt::Key_Select) { |
|
454 zoomed = false; |
|
455 update(); |
|
456 } |
|
457 QPoint delta(0, 0); |
|
458 if (event->key() == Qt::Key_Left) |
|
459 delta = QPoint(-15, 0); |
|
460 if (event->key() == Qt::Key_Right) |
|
461 delta = QPoint(15, 0); |
|
462 if (event->key() == Qt::Key_Up) |
|
463 delta = QPoint(0, -15); |
|
464 if (event->key() == Qt::Key_Down) |
|
465 delta = QPoint(0, 15); |
|
466 if (delta != QPoint(0, 0)) { |
|
467 dragPos += delta; |
|
468 update(); |
|
469 } |
|
470 } |
|
471 } |
|
472 |
|
473 private: |
|
474 SlippyMap *m_normalMap; |
|
475 SlippyMap *m_largeMap; |
|
476 bool pressed; |
|
477 bool snapped; |
|
478 QPoint pressPos; |
|
479 QPoint dragPos; |
|
480 QBasicTimer tapTimer; |
|
481 bool zoomed; |
|
482 QPixmap zoomPixmap; |
|
483 QPixmap maskPixmap; |
|
484 bool invert; |
|
485 }; |
|
486 |
|
487 class MapZoom : public QMainWindow |
|
488 { |
|
489 Q_OBJECT |
|
490 |
|
491 private: |
|
492 LightMaps *map; |
|
493 |
|
494 public: |
|
495 MapZoom(): QMainWindow(0) { |
|
496 map = new LightMaps(this); |
|
497 setCentralWidget(map); |
|
498 map->setFocus(); |
|
499 |
|
500 QAction *osloAction = new QAction("&Oslo", this); |
|
501 QAction *berlinAction = new QAction("&Berlin", this); |
|
502 QAction *jakartaAction = new QAction("&Jakarta", this); |
|
503 QAction *nightModeAction = new QAction("Night Mode", this); |
|
504 nightModeAction->setCheckable(true); |
|
505 nightModeAction->setChecked(false); |
|
506 QAction *osmAction = new QAction("About OpenStreetMap", this); |
|
507 connect(osloAction, SIGNAL(triggered()), SLOT(chooseOslo())); |
|
508 connect(berlinAction, SIGNAL(triggered()), SLOT(chooseBerlin())); |
|
509 connect(jakartaAction, SIGNAL(triggered()), SLOT(chooseJakarta())); |
|
510 connect(nightModeAction, SIGNAL(triggered()), map, SLOT(toggleNightMode())); |
|
511 connect(osmAction, SIGNAL(triggered()), SLOT(aboutOsm())); |
|
512 |
|
513 #if defined(Q_OS_SYMBIAN) || defined(Q_OS_WINCE_WM) |
|
514 menuBar()->addAction(osloAction); |
|
515 menuBar()->addAction(berlinAction); |
|
516 menuBar()->addAction(jakartaAction); |
|
517 menuBar()->addAction(nightModeAction); |
|
518 menuBar()->addAction(osmAction); |
|
519 #else |
|
520 QMenu *menu = menuBar()->addMenu("&Options"); |
|
521 menu->addAction(osloAction); |
|
522 menu->addAction(berlinAction); |
|
523 menu->addAction(jakartaAction); |
|
524 menu->addSeparator(); |
|
525 menu->addAction(nightModeAction); |
|
526 menu->addAction(osmAction); |
|
527 #endif |
|
528 |
|
529 QTimer::singleShot(0, this, SLOT(delayedInit())); |
|
530 } |
|
531 |
|
532 private slots: |
|
533 |
|
534 void delayedInit() { |
|
535 #if defined(Q_OS_SYMBIAN) |
|
536 qt_SetDefaultIap(); |
|
537 #endif |
|
538 } |
|
539 |
|
540 void chooseOslo() { |
|
541 map->setCenter(59.9138204, 10.7387413); |
|
542 } |
|
543 |
|
544 void chooseBerlin() { |
|
545 map->setCenter(52.52958999943302, 13.383053541183472); |
|
546 } |
|
547 |
|
548 void chooseJakarta() { |
|
549 map->setCenter(-6.211544, 106.845172); |
|
550 } |
|
551 |
|
552 void aboutOsm() { |
|
553 QDesktopServices::openUrl(QUrl("http://www.openstreetmap.org")); |
|
554 } |
|
555 }; |
|
556 |
|
557 |
|
558 #include "lightmaps.moc" |
|
559 |
|
560 int main(int argc, char **argv) |
|
561 { |
|
562 #if defined(Q_WS_X11) |
|
563 QApplication::setGraphicsSystem("raster"); |
|
564 #endif |
|
565 |
|
566 QApplication app(argc, argv); |
|
567 app.setApplicationName("LightMaps"); |
|
568 |
|
569 MapZoom w; |
|
570 w.setWindowTitle("OpenStreetMap"); |
|
571 #if defined(Q_OS_SYMBIAN) || defined(Q_OS_WINCE_WM) |
|
572 w.showMaximized(); |
|
573 #else |
|
574 w.resize(600, 450); |
|
575 w.show(); |
|
576 #endif |
|
577 |
|
578 return app.exec(); |
|
579 } |