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