Coverage Report

Created: 2025-10-10 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WGoogleMap.C
Line
Count
Source
1
// This may look like C code, but it's really -*- C++ -*-
2
/*
3
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
4
 *
5
 * See the LICENSE file for terms of use.
6
 *
7
 * Contributed by: Richard Ulrich.
8
 */
9
10
#include <Wt/WGoogleMap.h>
11
#include <Wt/WApplication.h>
12
#include <Wt/WContainerWidget.h>
13
14
#include "web/WebUtils.h"
15
16
#include <string>
17
#include <utility>
18
#include <iostream>
19
#include <cmath>
20
21
#ifndef M_PI
22
#define M_PI 3.14159265358979323846
23
#endif
24
25
namespace {
26
  // if there is no google api key configured, use the one for
27
  // http://localhost:8080/
28
  static const std::string localhost_key
29
    = "ABQIAAAAWqrN5o4-ISwj0Up_depYvhTwM0brOpm-"
30
    "All5BF6PoaKBxRWWERS-S9gPtCri-B6BZeXV8KpT4F80DQ";
31
32
  void write(std::stringstream& os, const Wt::WGoogleMap::Coordinate &c)
33
0
  {
34
0
    char b1[35];
35
0
    char b2[35];
36
0
    os << "new google.maps.LatLng("
37
0
       << Wt::Utils::round_js_str(c.latitude(), 15, b1)
38
0
       << "," << Wt::Utils::round_js_str(c.longitude(), 15, b2) << ")";
39
0
  }
40
}
41
42
namespace Wt {
43
44
WGoogleMap::Coordinate::Coordinate()
45
0
  : lat_(0), lon_(0)
46
0
{ }
47
48
WGoogleMap::Coordinate::Coordinate(double lat, double lon)
49
0
{
50
0
  setLatitude(lat);
51
0
  setLongitude(lon);
52
0
}
53
54
#ifndef WT_TARGET_JAVA
55
WGoogleMap::Coordinate::Coordinate(const std::pair<double, double>& lat_long)
56
0
{
57
0
  setLatitude(lat_long.first);
58
0
  setLongitude(lat_long.second);
59
0
}
60
#endif
61
62
void WGoogleMap::Coordinate::setLatitude(double latitude)
63
0
{
64
0
  if (latitude < -90.0 || latitude > 90.0)
65
0
    throw std::out_of_range("invalid latitude: "
66
0
                            + std::to_string(latitude));
67
68
0
  lat_ = latitude;
69
0
}
70
71
void WGoogleMap::Coordinate::setLongitude(double longitude)
72
0
{
73
0
  if (longitude < -180.0 || longitude > 180.0)
74
0
    throw std::out_of_range("invalid longitude: "
75
0
                            + std::to_string(longitude));
76
77
0
  lon_ = longitude;
78
0
}
79
80
double WGoogleMap::Coordinate::distanceTo(const Coordinate &rhs) const
81
0
{
82
0
  const double lat1 = lat_ * M_PI / 180.0;
83
0
  const double lat2 = rhs.latitude() * M_PI / 180.0;
84
0
  const double deltaLong = (rhs.longitude() - lon_) * M_PI / 180.0;
85
0
  const double angle = std::sin(lat1) * std::sin(lat2)
86
0
    + std::cos(lat1) * std::cos(lat2) * std::cos(deltaLong);
87
0
  const double earthRadius = 6371.0; // km
88
0
  const double dist = earthRadius * std::acos(angle);
89
90
0
  return dist;
91
0
}
92
93
#ifndef WT_TARGET_JAVA
94
std::pair<double, double> WGoogleMap::Coordinate::operator ()() const
95
0
{
96
0
  return std::make_pair(lat_, lon_);
97
0
}
98
99
std::istream& operator>> (std::istream& i, WGoogleMap::Coordinate& c)
100
0
{
101
0
  double lat, lon;
102
0
  i >> lat >> std::ws >> lon;
103
0
  c.setLatitude(lat);
104
0
  c.setLongitude(lon);
105
106
0
  return i;
107
0
}
108
#endif
109
110
// example javascript code from :
111
// http://code.google.com/apis/maps/documentation/
112
113
WGoogleMap::WGoogleMap(GoogleMapsVersion version)
114
0
 : clicked_(this, "click"),
115
0
   doubleClicked_(this, "dblclick"),
116
0
   mouseMoved_(nullptr),
117
0
   apiVersion_(version)
118
0
{
119
0
  setImplementation(std::unique_ptr<WWidget>(new WContainerWidget()));
120
121
0
  WApplication *app = WApplication::instance();
122
0
  googlekey_ = localhost_key;
123
0
  WApplication::readConfigurationProperty("google_api_key", googlekey_);
124
125
  // init the google javascript api
126
0
  const std::string gmuri = "//www.google.com/jsapi?key=" + googlekey_;
127
0
  app->require(gmuri, "google");
128
0
}
129
130
WGoogleMap::~WGoogleMap()
131
0
{
132
0
  delete mouseMoved_;
133
0
}
134
135
void WGoogleMap::streamJSListener(const JSignal<Coordinate> &signal,
136
                                  std::string signalName,
137
                                  Wt::WStringStream &strm)
138
0
{
139
0
  strm <<
140
0
    """google.maps.event.addListener(map, \"" << signalName << "\", "
141
0
    ""                              "function(event) {"
142
0
    ""  "if (event && event.latLng) {"
143
0
#ifndef WT_TARGET_JAVA
144
0
         << signal.createCall({"event.latLng.lat() +' '+ event.latLng.lng()"})
145
#else
146
         << signal.createCall("event.latLng.lat() +' '+ event.latLng.lng()")
147
#endif
148
0
 << ";"
149
0
    ""  "}"
150
0
    """});";
151
0
}
152
153
JSignal<WGoogleMap::Coordinate>& WGoogleMap::mouseMoved()
154
0
{
155
0
  if (!mouseMoved_)
156
0
    mouseMoved_ = new JSignal<Coordinate>(this, "mousemove");
157
158
0
  return *mouseMoved_;
159
0
}
160
161
void WGoogleMap::render(WFlags<RenderFlag> flags)
162
0
{
163
0
  if (flags.test(RenderFlag::Full)) {
164
0
    WApplication *app = WApplication::instance();
165
166
0
    std::string initFunction =
167
0
      app->javaScriptClass() + ".init_google_maps_" + id();
168
169
    // initialize the map
170
0
    WStringStream strm;
171
0
    strm <<
172
0
      "{ " << initFunction << " = function() {"
173
0
      """var self = " << jsRef() << ";"
174
0
      """if (!self) { "
175
0
      ""   "setTimeout(" << initFunction << ", 0);"
176
0
      ""   "return;"
177
0
      "}";
178
179
0
    strm <<
180
0
        "var latlng = new google.maps.LatLng(47.01887777, 8.651888);"
181
0
        "var myOptions = {"
182
0
        "" "zoom: 13,"
183
0
        "" "center: latlng,"
184
0
        "" "mapTypeId: google.maps.MapTypeId.ROADMAP"
185
0
        "};"
186
0
        "var map = new google.maps.Map(self, myOptions);"
187
0
        "map.overlays = [];"
188
0
        "map.infowindows = [];";
189
0
      setJavaScriptMember(WT_RESIZE_JS,
190
0
                          "function(self, w, h) {"
191
0
                          """if (w >= 0) "
192
0
                          ""  "self.style.width=w + 'px';"
193
0
                          """if (h >= 0) "
194
0
                          ""  "self.style.height=h + 'px';"
195
0
                          """if (self.map)"
196
0
                          """ google.maps.event.trigger(self.map, 'resize');"
197
0
                          "}");
198
0
    strm << "self.map = map;";
199
200
    // eventhandling
201
0
    streamJSListener(clicked_, "click", strm);
202
0
    streamJSListener(doubleClicked_, "dblclick", strm);
203
0
    if (mouseMoved_)
204
0
      streamJSListener(*mouseMoved_, "mousemove", strm);
205
206
    // additional things
207
0
    for (unsigned int i = 0; i < additions_.size(); i++)
208
0
      strm << additions_[i];
209
210
0
    strm << "setTimeout(function(){ delete " << initFunction << ";}, 0)};"
211
0
         << app->javaScriptClass() << "._p_.loadGoogleMaps('"
212
0
         << '3'
213
0
         << "'," << Wt::WWebWidget::jsStringLiteral(googlekey_)
214
0
         << "," << initFunction << ");"
215
0
         << "}"; // private scope
216
217
0
    additions_.clear();
218
219
0
    app->doJavaScript(strm.str(), true);
220
0
  }
221
222
0
  WCompositeWidget::render(flags);
223
0
}
224
225
void WGoogleMap::clearOverlays()
226
0
{
227
0
  std::stringstream strm;
228
0
  strm
229
0
    << "var mapLocal = " << jsRef() + ".map, i;\n"
230
0
    << "if (mapLocal.overlays) {\n"
231
0
    << """for (i in mapLocal.overlays) {\n"
232
0
    << """mapLocal.overlays[i].setMap(null);\n"
233
0
    << "}\n"
234
0
    << "mapLocal.overlays.length = 0;\n"
235
0
    << "}\n"
236
0
    << "if (mapLocal.infowindows) {\n"
237
0
    << """for (i in mapLocal.infowindows) {\n"
238
0
    << ""  "mapLocal.infowindows[i].close();\n"
239
0
    << ""  "}\n"
240
0
    << """mapLocal.infowindows.length = 0;\n"
241
0
    << "}\n";
242
243
0
  doGmJavaScript(strm.str());
244
0
}
245
246
void WGoogleMap::doGmJavaScript(const std::string& jscode)
247
0
{
248
0
  if (isRendered())
249
0
    doJavaScript(jscode);
250
0
  else
251
0
    additions_.push_back(jscode);
252
0
}
253
254
void WGoogleMap::addMarker(const Coordinate& pos)
255
0
{
256
0
  std::stringstream strm;
257
0
  strm << "var position = ";
258
0
  write(strm, pos);
259
0
  strm << ";"
260
0
       << "var marker = new google.maps.Marker({"
261
0
       << "position: position,"
262
0
       << "map: " << jsRef() << ".map"
263
0
       << "});"
264
0
       << jsRef() << ".map.overlays.push(marker);";
265
266
0
  doGmJavaScript(strm.str());
267
0
}
268
269
void WGoogleMap::addIconMarker(const Coordinate &pos,
270
                               const std::string& iconURL)
271
0
{
272
0
  std::stringstream strm;
273
274
0
  strm << "var position = ";
275
0
  write(strm, pos);
276
0
  strm << ";"
277
0
       << "var marker = new google.maps.Marker({"
278
0
       << "position: position,"
279
0
       << "icon: \"" <<  iconURL << "\","
280
0
       << "map: " << jsRef() << ".map"
281
0
       << "});"
282
283
0
       << jsRef() << ".map.overlays.push(marker);";
284
285
0
  doGmJavaScript(strm.str());
286
0
}
287
288
void WGoogleMap::addCircle(const Coordinate& center, double radius,
289
                           const WColor& strokeColor, int strokeWidth,
290
                           const WColor& fillColor)
291
0
{
292
0
  std::stringstream strm;
293
294
0
  double strokeOpacity = strokeColor.alpha() / 255.0;
295
0
  double fillOpacity = fillColor.alpha() / 255.0;
296
297
0
  strm << "var mapLocal = " << jsRef() + ".map;"
298
0
       << "var latLng = ";
299
0
  write(strm, center);
300
0
  strm << ";"
301
0
       << "var circle = new google.maps.Circle( "
302
0
          "{ "
303
0
          "  map: mapLocal, "
304
0
          "  radius: " << radius << ", "
305
0
          "  center:  latLng  ,"
306
0
          "  fillOpacity: \"" << fillOpacity << "\","
307
0
          "  fillColor: \"" << fillColor.cssText(false) << "\","
308
0
          "  strokeWeight: " << strokeWidth << ","
309
0
          "  strokeColor:\"" << strokeColor.cssText(false) << "\","
310
0
          "  strokeOpacity: " << strokeOpacity <<
311
0
          "} "
312
0
          ");"
313
0
          << jsRef() << ".map.overlays.push(circle);";
314
315
0
  doGmJavaScript(strm.str());
316
0
}
317
318
void WGoogleMap::addPolyline(const std::vector<Coordinate>& points,
319
                             const WColor& color, int width, double opacity)
320
0
{
321
0
  if (opacity == 1.0)
322
0
    opacity = color.alpha() / 255.0;
323
324
  // opacity has to be between 0.0 and 1.0
325
0
  opacity = std::max(std::min(opacity, 1.0), 0.0);
326
327
0
  std::stringstream strm;
328
0
  strm << "var waypoints = [];";
329
0
  for (size_t i = 0; i < points.size(); ++i) {
330
0
    strm << "waypoints[" << i << "] = ";
331
0
    write(strm, points[i]);
332
0
    strm << ";";
333
0
  }
334
335
0
  strm <<
336
0
    "var poly = new google.maps.Polyline({"
337
0
    "path: waypoints,"
338
0
    "strokeColor: \"" << color.cssText(false) << "\"," <<
339
0
    "strokeOpacity: " << opacity << "," <<
340
0
    "strokeWeight: " << width <<
341
0
    "});" <<
342
0
    "poly.setMap(" << jsRef() << ".map);" <<
343
0
    jsRef() << ".map.overlays.push(poly);";
344
345
0
  doGmJavaScript(strm.str());
346
0
}
347
348
void WGoogleMap::openInfoWindow(const Coordinate& pos,
349
                                const WString& myHtml)
350
0
{
351
0
  std::stringstream strm;
352
0
  strm << "var pos = ";
353
0
  write(strm, pos);
354
0
  strm << ";";
355
356
0
  strm << "var infowindow = new google.maps.InfoWindow({"
357
0
    "content: " << WWebWidget::jsStringLiteral(myHtml) << "," <<
358
0
    "position: pos"
359
0
    "});"
360
0
    "infowindow.open(" << jsRef() << ".map);" <<
361
0
    jsRef() << ".map.infowindows.push(infowindow);";
362
363
0
  doGmJavaScript(strm.str());
364
0
}
365
366
void WGoogleMap::setCenter(const Coordinate& center)
367
0
{
368
0
  std::stringstream strm;
369
0
  strm << jsRef() << ".map.setCenter(";
370
0
  write(strm, center);
371
0
  strm << ");";
372
0
  doGmJavaScript(strm.str());
373
0
}
374
375
void WGoogleMap::setCenter(const Coordinate& center, int zoom)
376
0
{
377
0
  std::stringstream strm;
378
0
  strm << jsRef() << ".map.setCenter(";
379
0
  write(strm, center);
380
0
  strm << "); "
381
0
       << jsRef() << ".map.setZoom(" << zoom << ");";
382
383
0
  doGmJavaScript(strm.str());
384
0
}
385
386
void WGoogleMap::panTo(const Coordinate& center)
387
0
{
388
0
  std::stringstream strm;
389
0
  strm << jsRef() << ".map.panTo(";
390
0
  write (strm, center);
391
0
  strm << ");";
392
393
0
  doGmJavaScript(strm.str());
394
0
}
395
396
void WGoogleMap::setZoom(int level)
397
0
{
398
0
  doGmJavaScript(jsRef() + ".map.setZoom(" + std::to_string(level) + ");");
399
0
}
400
401
void WGoogleMap::zoomIn()
402
0
{
403
0
  std::stringstream strm;
404
0
  strm
405
0
    << "var zoom = " << jsRef() << ".map.getZoom();"
406
0
    << jsRef() << ".map.setZoom(zoom + 1);";
407
0
  doGmJavaScript(strm.str());
408
0
}
409
410
void WGoogleMap::zoomOut()
411
0
{
412
0
  std::stringstream strm;
413
0
  strm
414
0
    << "var zoom = " << jsRef() << ".map.getZoom();"
415
0
    << jsRef() << ".map.setZoom(zoom - 1);";
416
0
  doGmJavaScript(strm.str());
417
0
}
418
419
void WGoogleMap::savePosition()
420
0
{
421
0
  std::stringstream strm;
422
0
  strm
423
0
    << jsRef() << ".map.savedZoom = " << jsRef() << ".map.getZoom();"
424
0
    << jsRef() << ".map.savedPosition = " << jsRef() << ".map.getCenter();";
425
0
  doGmJavaScript(strm.str());
426
0
}
427
428
void WGoogleMap::returnToSavedPosition()
429
0
{
430
0
  std::stringstream strm;
431
0
  strm
432
0
    << jsRef() << ".map.setZoom(" << jsRef() << ".map.savedZoom);"
433
0
    << jsRef() << ".map.setCenter(" << jsRef() << ".map.savedPosition);";
434
0
  doGmJavaScript(strm.str());
435
0
}
436
437
void WGoogleMap::setMapOption(const std::string &option,
438
                              const std::string &value)
439
0
{
440
0
  std::stringstream strm;
441
0
  strm
442
0
    << "var option = {"
443
0
    << option << " :" << value
444
0
    << "};"
445
0
    << jsRef() << ".map.setOptions(option);";
446
447
0
  doGmJavaScript(strm.str());
448
0
}
449
450
void WGoogleMap::enableDragging()
451
0
{
452
0
  setMapOption("draggable", "true");
453
0
}
454
455
void WGoogleMap::disableDragging()
456
0
{
457
0
  setMapOption("draggable", "false");
458
0
}
459
460
void WGoogleMap::enableDoubleClickZoom()
461
0
{
462
0
  setMapOption("disableDoubleClickZoom", "false");
463
0
}
464
465
void WGoogleMap::disableDoubleClickZoom()
466
0
{
467
0
  setMapOption("disableDoubleClickZoom", "true");
468
0
}
469
470
void WGoogleMap::enableGoogleBar()
471
0
{
472
0
  throw std::logic_error("WGoogleMap::enableGoogleBar is not supported "
473
0
                           "in the Google Maps API v3.");
474
0
}
475
476
void WGoogleMap::disableGoogleBar()
477
0
{
478
0
  throw std::logic_error("WGoogleMap::disableGoogleBar is not supported "
479
0
                           "in the Google Maps API v3.");
480
0
}
481
482
void WGoogleMap::enableScrollWheelZoom()
483
0
{
484
0
  setMapOption("scrollwheel", "true");
485
0
}
486
487
void WGoogleMap::disableScrollWheelZoom()
488
0
{
489
0
  setMapOption("scrollwheel", "false");
490
0
}
491
492
#ifndef WT_TARGET_JAVA
493
void WGoogleMap::zoomWindow(const std::pair<Coordinate, Coordinate>& bbox)
494
0
{
495
0
  zoomWindow(bbox.first, bbox.second);
496
0
}
497
#endif
498
499
void WGoogleMap::zoomWindow(const Coordinate& topLeft,
500
                            const Coordinate& rightBottom)
501
0
{
502
0
  const Coordinate center
503
0
    ((topLeft.latitude() + rightBottom.latitude()) / 2.0,
504
0
     (topLeft.longitude() + rightBottom.longitude()) / 2.0);
505
506
0
  Coordinate topLeftC =
507
0
    Coordinate(std::min(topLeft.latitude(), rightBottom.latitude()),
508
0
               std::min(topLeft.longitude(), rightBottom.longitude()));
509
0
  Coordinate rightBottomC =
510
0
    Coordinate(std::max(topLeft.latitude(), rightBottom.latitude()),
511
0
               std::max(topLeft.longitude(), rightBottom.longitude()));
512
0
  std::stringstream strm;
513
0
  strm << "var bbox = new google.maps.LatLngBounds(";
514
0
  write(strm, topLeftC);
515
0
  strm << ", ";
516
0
  write(strm, rightBottomC);
517
0
  strm << ");";
518
519
0
  strm
520
0
    << jsRef() << ".map.fitBounds(bbox);";
521
522
0
  doGmJavaScript(strm.str());
523
0
}
524
525
void WGoogleMap::setMapTypeControl(MapTypeControl type)
526
0
{
527
0
  std::stringstream strm;
528
529
0
  std::string control;
530
0
  switch (type) {
531
0
  case MapTypeControl::Default:
532
0
    control = "DEFAULT";
533
0
    break;
534
0
  case MapTypeControl::Menu:
535
0
    control = "DROPDOWN_MENU";
536
0
    break;
537
0
  case MapTypeControl::HorizontalBar:
538
0
    control = "HORIZONTAL_BAR";
539
0
    break;
540
0
  case MapTypeControl::Hierarchical:
541
0
    throw std::logic_error("WGoogleMap::setMapTypeControl: "
542
0
         "HierarchicalControl is not supported when using "
543
0
         "Google Maps API v3.");
544
0
  default:
545
0
    control = "";
546
0
  }
547
548
0
  strm
549
0
    << "var options = {"
550
0
    << """disableDefaultUI: " << (control == "" ? "true" : "false") << ","
551
0
    << ""  "mapTypeControlOptions: {";
552
553
0
  if (control != "")
554
0
    strm << "style: google.maps.MapTypeControlStyle." << control;
555
556
0
  strm
557
0
    << """}"
558
0
    << "};"
559
0
    << jsRef() << ".map.setOptions(options);";
560
561
0
  doGmJavaScript(strm.str());
562
0
}
563
564
}