1 // -*- C++ -*-
2 // $Id: map.cpp,v 1.2 2009-08-28 17:08:55 robertl Exp $
3 //------------------------------------------------------------------------
4 //
5 // Copyright (C) 2009 S. Khai Mong <khai@mangrai.com>.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program 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 GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111
20 // USA
21 //
22 //------------------------------------------------------------------------
23 #include <QNetworkRequest>
24 #include <QMessageBox>
25 #include <QNetworkAccessManager>
26 #include <QWebFrame>
27 #include <QWebPage>
28 #include <QApplication>
29 #include <QCursor>
30 #include <QFile>
31
32 #include <math.h>
33 #include "map.h"
34 #include "appname.h"
35 #include "dpencode.h"
36
37 //------------------------------------------------------------------------
stripDoubleQuotes(const QString s)38 static QString stripDoubleQuotes(const QString s) {
39 QString out;
40 foreach (QChar c, s) {
41 if (c != QChar('"'))
42 out += c;
43 }
44 return out;
45 }
46
47 //------------------------------------------------------------------------
Map(QWidget * parent,const Gpx & gpx,QPlainTextEdit * te)48 Map::Map(QWidget *parent,
49 const Gpx &gpx, QPlainTextEdit *te):
50 QWebView(parent),
51 gpx(gpx),
52 mapPresent(false),
53 busyCursor(false),
54 te(te)
55 {
56 busyCursor = true;
57 stopWatch.start();
58 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
59 manager = new QNetworkAccessManager(this);
60 connect(this,SIGNAL(loadFinished(bool)),
61 this,SLOT(loadFinishedX(bool)));
62 this->logTimeX("Start map constuctor");
63 QString baseFile = QApplication::applicationDirPath() + "/gmapbase.html";
64 if (!QFile(baseFile).exists()) {
65 QMessageBox::critical(0, appName,
66 tr("Missing \"gmapbase.html\" file. Check installation"));
67 }
68 else {
69 QString urlStr = "file:///" + baseFile;
70 load(QUrl(urlStr));
71 }
72 }
73
74 //------------------------------------------------------------------------
~Map()75 Map::~Map()
76 {
77 if (busyCursor)
78 QApplication::restoreOverrideCursor();
79 }
80 //------------------------------------------------------------------------
loadFinishedX(bool f)81 void Map::loadFinishedX(bool f)
82 {
83 this->logTimeX("Done initial page load");
84 if (!f)
85 QMessageBox::critical(0, appName,
86 tr("Failed to load Google maps base page"));
87 else {
88 QApplication::processEvents();
89 showGpxData();
90 }
91 QApplication::restoreOverrideCursor();
92 busyCursor = false;
93 }
94
95 //------------------------------------------------------------------------
96
makeLiteralVar(const QString & name,const string & s)97 static QStringList makeLiteralVar(const QString &name, const string &s)
98 {
99 QStringList out;
100 out << QString("var %1 = ").arg(name);
101
102 QString ws = "\"";
103 for (unsigned int i=0; i<s.length(); i++) {
104 if (s[i] =='\\') {
105 ws += s[i];
106 }
107 ws += s[i];
108 if (ws.length() > 5120) {
109 ws += "\" + ";
110 out << ws;
111 ws = "\"";
112 }
113 }
114 ws += "\";";
115 out << ws;
116 return out;
117 }
118
119 //------------------------------------------------------------------------
fmtLatLng(const LatLng & l)120 static QString fmtLatLng(const LatLng &l) {
121 return QString("%1, %3").arg(l.lat(), 0, 'f', 5) .arg(l.lng(), 0, 'f', 5);
122 }
123
124 //------------------------------------------------------------------------
showGpxData()125 void Map::showGpxData()
126 {
127 MarkerClicker *mclicker = new MarkerClicker(this);
128 this->page()->mainFrame()->addToJavaScriptWindowObject("mclicker", mclicker);
129 connect(mclicker, SIGNAL(markerClicked(int, int )), this, SLOT(markerClicked(int, int)));
130 connect(mclicker, SIGNAL(logTime(const QString &)), this, SLOT(logTimeX(const QString &)));
131
132 // It is appreciably faster to do the encoding on the C++ side.
133 int numLevels = 18;
134 double zoomFactor = 2;
135 PolylineEncoder encoder(numLevels, zoomFactor, 0.00001);
136
137
138 this->logTimeX("Start defining JS string");
139 QStringList scriptStr;
140 scriptStr
141 << "mclicker.logTime(\"Start JS execution\");"
142 << "var map = new GMap2(document.getElementById(\"map\"));"
143 << "var bounds = new GLatLngBounds;"
144 << "var waypts = [];"
145 << "var rtes = [];"
146 << "var trks = [];"
147 << "map.enableScrollWheelZoom();"
148 << "map.enableContinuousZoom();"
149 << "map.addControl(new GLargeMapControl());"
150 << "map.addControl(new GScaleControl());"
151 << "map.addControl(new GMapTypeControl());"
152 << "var pn = map.getPane(G_MAP_MARKER_PANE);"
153 << "pn.style.KhtmlUserSelect='none';"
154 << "pn.style.KhtmlUserDrag='none';"
155 << "mclicker.logTime(\"Done prelim JS definition\");"
156 << QString("var zoomFactor = %1;").arg(zoomFactor)
157 << QString("var numLevels = %1;").arg(numLevels)
158 ;
159
160 mapPresent = true;
161
162 // Waypoints.
163 int num=0;
164 foreach (const GpxWaypoint &pt, gpx.getWaypoints() ) {
165 scriptStr
166 << QString("waypts[%1] = new GMarker(new GLatLng(%2), "
167 "{title:\"%3\",icon:blueIcon});")
168 .arg(num)
169 .arg(fmtLatLng(pt.getLocation()))
170 .arg(stripDoubleQuotes(pt.getName()));
171 num++;
172 }
173
174 scriptStr
175 << "for( var i=0; i<waypts.length; ++i ) {"
176 << " bounds.extend(waypts[i].getPoint());"
177 << " var ftemp = new MarkerHandler(0, i);"
178 << " GEvent.bind(waypts[i], \"click\", ftemp, ftemp.clicked);"
179 << " map.addOverlay(waypts[i]);"
180 << "}"
181 << "mclicker.logTime(\"Done waypoints definition\");"
182 ;
183
184 // Tracks
185 num = 0;
186 foreach (const GpxTrack &trk, gpx.getTracks()) {
187 vector <LatLng> epts;
188 foreach (const GpxTrackSegment seg, trk.getTrackSegments()) {
189 foreach (const GpxTrackPoint pt, seg.getTrackPoints()) {
190 epts.push_back(pt.getLocation());
191 }
192 }
193 string encPts, encLevels;
194 encoder.dpEncode(encPts, encLevels, epts);
195
196 scriptStr
197 << QString("var startPt = new GLatLng(%1);").arg(fmtLatLng(epts[0]))
198 << QString("var endPt = new GLatLng(%1);").arg(fmtLatLng(epts[epts.size()-1]))
199 << QString("var idx = %1;").arg(num)
200 << QString("var nm = \"%1\";").arg(stripDoubleQuotes(trk.getName()))
201 << makeLiteralVar("encpts", encPts)
202 << makeLiteralVar("enclvs", encLevels)
203
204 << "var trk = GPolyline.fromEncoded({color:\"#0000E0\", weight:2, opacity:0.6,"
205 << "points:encpts, zoomFactor:zoomFactor, levels:enclvs, numLevels:numLevels});"
206 << "trks[idx] = new RTPolyline(trk, startPt, endPt, new MarkerHandler(1, idx));"
207 ;
208 num++;
209 }
210
211 scriptStr
212 << "for( var i=0; i<trks.length; ++i ) {"
213 << " var trkbound = trks[i].getBounds();"
214 << " bounds.extend(trkbound.getSouthWest());"
215 << " bounds.extend(trkbound.getNorthEast());"
216 << "}"
217 << "mclicker.logTime(\"Done track definition\");"
218 ;
219
220 // Routes
221 num = 0;
222 foreach (const GpxRoute &rte, gpx.getRoutes()) {
223 vector <LatLng> epts;
224 foreach (const GpxRoutePoint &pt, rte.getRoutePoints()) {
225 epts.push_back(pt.getLocation());
226 }
227 string encPts, encLevels;
228 encoder.dpEncode(encPts, encLevels, epts);
229 scriptStr
230 << QString("var startPt = new GLatLng(%1);").arg(fmtLatLng(epts[0]))
231 << QString("var endPt = new GLatLng(%1);").arg(fmtLatLng(epts[epts.size()-1]))
232 << QString("var idx = %1;").arg(num)
233 << QString("var nm = \"%1\";").arg(stripDoubleQuotes(rte.getName()))
234 << makeLiteralVar("encpts", encPts)
235 << makeLiteralVar("enclvs", encLevels)
236 << "var rte = GPolyline.fromEncoded({color:\"#8000B0\", weight:2, opacity:0.6,"
237 << " points:encpts, zoomFactor:zoomFactor, levels:enclvs, numLevels:numLevels});"
238 << "rtes[idx] = new RTPolyline(rte, startPt, endPt, new MarkerHandler(2, idx));"
239 ;
240 num++;
241 }
242
243 scriptStr
244 << "for( var i=0; i<rtes.length; ++i ) {"
245 << " var rtebound = rtes[i].getBounds();"
246 << " bounds.extend(rtebound.getSouthWest());"
247 << " bounds.extend(rtebound.getNorthEast());"
248 << "}"
249 << "mclicker.logTime(\"Done route definition\");"
250 ;
251
252 scriptStr
253 << "map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));"
254 << "mclicker.logTime(\"done setCenter\");"
255 ;
256
257 this->logTimeX("Done defining JS string");
258 evaluateJS(scriptStr);
259 this->logTimeX("Done JS evaluation");
260 }
261
262 //------------------------------------------------------------------------
markerClicked(int t,int i)263 void Map::markerClicked(int t, int i){
264 if (t == 0)
265 emit waypointClicked(i);
266 else if (t == 1)
267 emit trackClicked(i);
268 else if (t == 2)
269 emit routeClicked(i);
270
271 }
272
273 //------------------------------------------------------------------------
logTimeX(const QString & s)274 void Map::logTimeX(const QString &s)
275 {
276 // fprintf(stderr, "Log: %s: %d ms\n", s.toStdString().c_str(), stopWatch.elapsed());
277 if (te) {
278 te->appendPlainText(QString("%1: %2 ms").arg(s).arg(stopWatch.elapsed()));
279 }
280 stopWatch.start();
281 }
282 //------------------------------------------------------------------------
showTracks(const QList<GpxTrack> & tracks)283 void Map::showTracks(const QList<GpxTrack> &tracks)
284 {
285 QStringList scriptStr;
286 int i=0;
287 foreach(const GpxTrack &trk, tracks) {
288 scriptStr << QString("trks[%1].%2();").arg(i).arg(trk.getVisible()?"show":"hide");
289 i++;
290 }
291 evaluateJS(scriptStr);
292 }
293
294 //------------------------------------------------------------------------
hideAllTracks()295 void Map::hideAllTracks()
296 {
297 QStringList scriptStr;
298 scriptStr
299 << "for( var i=0; i<trks.length; ++i ) {"
300 << " trks[i].hide();"
301 << "}"
302 ;
303 evaluateJS(scriptStr);
304 }
305
306 //------------------------------------------------------------------------
showWaypoints(const QList<GpxWaypoint> & waypoints)307 void Map::showWaypoints(const QList<GpxWaypoint> &waypoints)
308 {
309 QStringList scriptStr;
310 int i=0;
311 foreach(const GpxWaypoint &pt, waypoints) {
312 scriptStr << QString("waypts[%1].%2();").arg(i++).arg(pt.getVisible()?"show":"hide");
313 }
314 evaluateJS(scriptStr);
315 }
316 //------------------------------------------------------------------------
hideAllWaypoints()317 void Map::hideAllWaypoints()
318 {
319 QStringList scriptStr;
320 scriptStr
321 << "for( var i=0; i<waypts.length; ++i ) {"
322 << " waypts[i].hide();"
323 << "}"
324 ;
325 evaluateJS(scriptStr);
326 }
327
328 //------------------------------------------------------------------------
showRoutes(const QList<GpxRoute> & routes)329 void Map::showRoutes(const QList<GpxRoute> &routes)
330 {
331 QStringList scriptStr;
332 int i=0;
333 foreach(const GpxRoute &rt, routes) {
334 scriptStr << QString("rtes[%1].%2();").arg(i).arg(rt.getVisible()?"show":"hide");
335 i++;
336 }
337 evaluateJS(scriptStr);
338 }
339 //------------------------------------------------------------------------
hideAllRoutes()340 void Map::hideAllRoutes()
341 {
342 QStringList scriptStr;
343 scriptStr
344 << "for( var i=0; i<rtes.length; ++i ) {"
345 << " rtes[i].hide();"
346 << "}"
347 ;
348 evaluateJS(scriptStr);
349 }
350 //------------------------------------------------------------------------
setWaypointVisibility(int i,bool show)351 void Map::setWaypointVisibility(int i, bool show)
352 {
353 evaluateJS(QString("waypts[%1].%2();\n")
354 .arg(i).arg(show?"show": "hide"));
355 }
356
357 //------------------------------------------------------------------------
setTrackVisibility(int i,bool show)358 void Map::setTrackVisibility(int i, bool show)
359 {
360 QString x = show?"show": "hide";
361 QStringList scriptStr;
362 scriptStr
363 << QString("trks[%1].%2();").arg(i).arg(x)
364 ;
365 evaluateJS(scriptStr);
366 }
367
368 //------------------------------------------------------------------------
setRouteVisibility(int i,bool show)369 void Map::setRouteVisibility(int i, bool show)
370 {
371 QString x = show?"show": "hide";
372 QStringList scriptStr;
373 scriptStr
374 << QString("rtes[%1].%2();").arg(i).arg(x)
375 ;
376 evaluateJS(scriptStr);
377 }
378
379 //------------------------------------------------------------------------
panTo(const LatLng & loc)380 void Map::panTo(const LatLng &loc)
381 {
382 evaluateJS(QString("map.panTo(new GLatLng(%1));").arg(fmtLatLng(loc)));
383 }
384
385 //------------------------------------------------------------------------
resizeEvent(QResizeEvent * ev)386 void Map::resizeEvent ( QResizeEvent * ev)
387 {
388 QWebView::resizeEvent(ev);
389 if (mapPresent)
390 evaluateJS(QString("map.checkResize();"));
391 }
392
393 //------------------------------------------------------------------------
setWaypointColorRed(int i)394 void Map::setWaypointColorRed(int i)
395 {
396 evaluateJS(QString("waypts[%1].setImage(redIcon.image)").arg(i));
397 }
398
399 //------------------------------------------------------------------------
setWaypointColorBlue(int i)400 void Map::setWaypointColorBlue(int i)
401 {
402 evaluateJS(QString("waypts[%1].setImage(blueIcon.image)").arg(i));
403 }
404
405 //------------------------------------------------------------------------
frameTrack(int i)406 void Map::frameTrack(int i)
407 {
408 QStringList scriptStr;
409 scriptStr
410 << QString("var trkbound = trks[%1].getBounds();").arg(i)
411 << "map.setCenter(trkbound.getCenter(), map.getBoundsZoomLevel(trkbound));"
412 ;
413 evaluateJS(scriptStr);
414 }
415
416
417 //------------------------------------------------------------------------
frameRoute(int i)418 void Map::frameRoute(int i)
419 {
420 QStringList scriptStr;
421 scriptStr
422 << QString("var rtebound = rtes[%1].getBounds();").arg(i)
423 << "map.setCenter(rtebound.getCenter(), map.getBoundsZoomLevel(rtebound));"
424 ;
425 evaluateJS(scriptStr);
426 }
427
428
429 //------------------------------------------------------------------------
evaluateJS(const QString & s,bool upd)430 void Map::evaluateJS(const QString &s, bool upd)
431 {
432 this->page()->mainFrame()->evaluateJavaScript(s);
433 if (upd) {
434 this->update();
435 }
436 }
437
438 //------------------------------------------------------------------------
evaluateJS(const QStringList & s,bool upd)439 void Map::evaluateJS(const QStringList &s, bool upd)
440 {
441 evaluateJS(s.join("\n"), upd);
442 }
443