~mhier/libwt-latest/libwt-package

« back to all changes in this revision

Viewing changes to src/Wt/WLeafletMap.C

  • Committer: Martin Hierholzer
  • Date: 2020-06-18 12:21:07 UTC
  • Revision ID: martin.hierholzer@desy.de-20200618122107-jxk2kefgptu2hh3v
update to upstream version 4.3.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2019 Emweb bv, Herent, Belgium.
 
3
 *
 
4
 * See the LICENSE file for terms of use.
 
5
 */
 
6
 
 
7
#include "Wt/WLeafletMap.h"
 
8
 
 
9
#include "Wt/WApplication.h"
 
10
#include "Wt/WBrush.h"
 
11
#include "Wt/WColor.h"
 
12
#include "Wt/WContainerWidget.h"
 
13
#include "Wt/WCssStyleSheet.h"
 
14
#include "Wt/WJavaScriptPreamble.h"
 
15
#include "Wt/WLink.h"
 
16
#include "Wt/WLogger.h"
 
17
#include "Wt/WPen.h"
 
18
#include "Wt/WStringStream.h"
 
19
 
 
20
#include "Wt/Json/Array.h"
 
21
#include "Wt/Json/Parser.h"
 
22
#include "Wt/Json/Serializer.h"
 
23
#include "Wt/Json/Value.h"
 
24
 
 
25
#include "web/DomElement.h"
 
26
#include "web/EscapeOStream.h"
 
27
#include "web/WebUtils.h"
 
28
 
 
29
#ifndef WT_DEBUG_JS
 
30
#include "js/WLeafletMap.min.js"
 
31
#endif
 
32
 
 
33
namespace Wt {
 
34
 
 
35
LOGGER("WLeafletMap");
 
36
 
 
37
const std::string WLeafletMap::WIDGETMARKER_CONTAINER_RULENAME = "WLeafletMap::WidgetMarker::container";
 
38
const std::string WLeafletMap::WIDGETMARKER_CONTAINER_CHILDREN_RULENAME = "WLeafletMap::WidgetMarker::container-children";
 
39
 
 
40
#define WIDGETMARKER_CONTAINER_CLASS "Wt-leaflet-widgetmarker-container"
 
41
 
 
42
class WLeafletMap::Impl : public WWebWidget {
 
43
public:
 
44
  Impl();
 
45
 
 
46
  virtual DomElementType domElementType() const override;
 
47
};
 
48
 
 
49
WLeafletMap::Impl::Impl()
 
50
{
 
51
  setInline(false);
 
52
}
 
53
 
 
54
DomElementType WLeafletMap::Impl::domElementType() const
 
55
{
 
56
  return DomElementType::DIV;
 
57
}
 
58
 
 
59
struct WLeafletMap::Overlay {
 
60
  virtual ~Overlay();
 
61
  virtual void addJS(WStringStream &ss,
 
62
                     WLeafletMap *map) const = 0;
 
63
 
 
64
  Overlay(const Overlay &) = delete;
 
65
  Overlay& operator=(const Overlay &) = delete;
 
66
  Overlay(Overlay &&) = delete;
 
67
  Overlay& operator=(Overlay &&) = delete;
 
68
 
 
69
protected:
 
70
  Overlay();
 
71
};
 
72
 
 
73
struct WLeafletMap::Polyline : WLeafletMap::Overlay {
 
74
  std::vector<Coordinate> points;
 
75
  WPen pen;
 
76
 
 
77
  Polyline(const std::vector<Coordinate> &points, const WPen &pen);
 
78
  virtual ~Polyline() override;
 
79
  Polyline(const Polyline &) = delete;
 
80
  Polyline& operator=(const Polyline &) = delete;
 
81
  Polyline(Polyline &&) = delete;
 
82
  Polyline& operator=(Polyline &&) = delete;
 
83
  virtual void addJS(WStringStream &ss, WLeafletMap *map) const override;
 
84
};
 
85
 
 
86
struct WLeafletMap::Circle : WLeafletMap::Overlay {
 
87
  Coordinate center;
 
88
  double radius;
 
89
  WPen stroke;
 
90
  WBrush fill;
 
91
 
 
92
  Circle(const Coordinate &center, double radius, const WPen &stroke, const WBrush &fill);
 
93
  virtual ~Circle() override;
 
94
 
 
95
  Circle(const Circle &) = delete;
 
96
  Circle& operator=(const Circle &) = delete;
 
97
  Circle(Circle &&) = delete;
 
98
  Circle& operator=(Circle &&) = delete;
 
99
 
 
100
  virtual void addJS(WStringStream &ss, WLeafletMap *map) const override;
 
101
};
 
102
 
 
103
WLeafletMap::Overlay::Overlay()
 
104
{ }
 
105
 
 
106
WLeafletMap::Overlay::~Overlay()
 
107
{ }
 
108
 
 
109
WLeafletMap::Polyline::Polyline(const std::vector<WLeafletMap::Coordinate> &points,
 
110
                                const WPen &pen)
 
111
  : points(points),
 
112
    pen(pen)
 
113
{ }
 
114
 
 
115
WLeafletMap::Polyline::~Polyline()
 
116
{ }
 
117
 
 
118
void WLeafletMap::Polyline::addJS(WStringStream &ss,
 
119
                                  WLeafletMap *map) const
 
120
{
 
121
  if (pen.style() == PenStyle::None)
 
122
    return;
 
123
 
 
124
  Json::Object options;
 
125
  addPathOptions(options, pen, BrushStyle::None);
 
126
  std::string optionsStr = Json::serialize(options);
 
127
 
 
128
  EscapeOStream es(ss);
 
129
  es << "var o=" << map->jsRef() << ";if(o && o.wtObj){"
 
130
        "o.wtObj.addPolyline(";
 
131
  es << "[";
 
132
  for (std::size_t i = 0; i < points.size(); ++i) {
 
133
    if (i != 0)
 
134
      es << ',';
 
135
    es << "[";
 
136
    char buf[30];
 
137
    es << Utils::round_js_str(points[i].latitude(), 16, buf) << ",";
 
138
    es << Utils::round_js_str(points[i].longitude(), 16, buf);
 
139
    es << "]";
 
140
  }
 
141
  es << "],'";
 
142
  es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
 
143
  es << optionsStr;
 
144
  es.popEscape();
 
145
  es << "');}";
 
146
}
 
147
 
 
148
WLeafletMap::Circle::Circle(const Coordinate &center,
 
149
                            double radius,
 
150
                            const WPen &stroke,
 
151
                            const WBrush &fill)
 
152
  : center(center),
 
153
    radius(radius),
 
154
    stroke(stroke),
 
155
    fill(fill)
 
156
{ }
 
157
 
 
158
WLeafletMap::Circle::~Circle()
 
159
{ }
 
160
 
 
161
void WLeafletMap::Circle::addJS(WStringStream &ss,
 
162
                                WLeafletMap *map) const
 
163
{
 
164
  Json::Object options;
 
165
  options["radius"] = Json::Value(radius);
 
166
  addPathOptions(options, stroke, fill);
 
167
  std::string optionsStr = Json::serialize(options);
 
168
 
 
169
  EscapeOStream es(ss);
 
170
  es << "var o=" << map->jsRef() << ";if(o && o.wtObj){"
 
171
     << "" "o.wtObj.addCircle([";
 
172
  char buf[30];
 
173
  es << Utils::round_js_str(center.latitude(), 16, buf) << ",";
 
174
  es << Utils::round_js_str(center.longitude(), 16, buf) << "],'";
 
175
  es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
 
176
  es << optionsStr;
 
177
  es.popEscape();
 
178
  es << "');}";
 
179
}
 
180
 
 
181
WLeafletMap::Coordinate::Coordinate()
 
182
  : lat_(0.0),
 
183
    lng_(0.0)
 
184
{ }
 
185
 
 
186
WLeafletMap::Coordinate::Coordinate(double latitude, double longitude)
 
187
  : lat_(latitude),
 
188
    lng_(longitude)
 
189
{ }
 
190
 
 
191
void WLeafletMap::Coordinate::setLatitude(double latitude)
 
192
{
 
193
  lat_ = latitude;
 
194
}
 
195
 
 
196
void WLeafletMap::Coordinate::setLongitude(double longitude)
 
197
{
 
198
  lng_ = longitude;
 
199
}
 
200
 
 
201
WLeafletMap::Marker::Marker(const Coordinate &pos)
 
202
  : pos_(pos),
 
203
    map_(nullptr),
 
204
    moved_(false)
 
205
{ }
 
206
 
 
207
WLeafletMap::Marker::~Marker()
 
208
{ }
 
209
 
 
210
void WLeafletMap::Marker::move(const Coordinate &pos)
 
211
{
 
212
  pos_ = pos;
 
213
  if (map_) {
 
214
    moved_ = true;
 
215
    map_->scheduleRender();
 
216
  }
 
217
}
 
218
 
 
219
void WLeafletMap::Marker::setMap(WLeafletMap *map)
 
220
{
 
221
  map_ = map;
 
222
}
 
223
 
 
224
void WLeafletMap::Marker::unrender()
 
225
{ }
 
226
 
 
227
bool WLeafletMap::Marker::needsUpdate() const
 
228
{
 
229
  return false;
 
230
}
 
231
 
 
232
void WLeafletMap::Marker::update(WStringStream &js)
 
233
{ }
 
234
 
 
235
WLeafletMap::WidgetMarker::WidgetMarker(const Coordinate &pos,
 
236
                                        std::unique_ptr<WWidget> widget)
 
237
  : Marker(pos),
 
238
    container_(nullptr),
 
239
    anchorX_(-1),
 
240
    anchorY_(-1),
 
241
    anchorPointChanged_(false)
 
242
{
 
243
  createContainer();
 
244
  container_->addWidget(std::move(widget));
 
245
}
 
246
 
 
247
WLeafletMap::WidgetMarker::~WidgetMarker()
 
248
{
 
249
  container_.reset();
 
250
}
 
251
 
 
252
WWidget *WLeafletMap::WidgetMarker::widget()
 
253
{
 
254
  if (container_ && container_->count() > 0) {
 
255
    return container_->widget(0);
 
256
  }
 
257
  return 0;
 
258
}
 
259
 
 
260
const WWidget *WLeafletMap::WidgetMarker::widget() const
 
261
{
 
262
  if (container_ && container_->count() > 0) {
 
263
    return container_->widget(0);
 
264
  }
 
265
  return 0;
 
266
}
 
267
 
 
268
void WLeafletMap::WidgetMarker::setMap(WLeafletMap *map)
 
269
{
 
270
  Marker::setMap(map);
 
271
 
 
272
  if (container_) {
 
273
    container_->setParentWidget(map);
 
274
  }
 
275
}
 
276
 
 
277
void WLeafletMap::WidgetMarker::createMarkerJS(WStringStream &ss, WStringStream &postJS) const
 
278
{
 
279
  std::unique_ptr<DomElement> element(container_->createSDomElement(WApplication::instance()));
 
280
 
 
281
  DomElement::TimeoutList timeouts;
 
282
 
 
283
  char buf[30];
 
284
 
 
285
  if (anchorX_ >= 0 || anchorY_ >= 0) {
 
286
    updateAnchorJS(postJS);
 
287
  }
 
288
 
 
289
  EscapeOStream js(postJS);
 
290
 
 
291
  EscapeOStream es(ss);
 
292
  es << "(function(){";
 
293
  es << "var wIcon=L.divIcon({"
 
294
        "className:'',"
 
295
        "iconSize:null,"
 
296
        "iconAnchor:null,";
 
297
  es << "html:'";
 
298
  es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
 
299
  element->asHTML(es, js, timeouts);
 
300
  es.popEscape();
 
301
  es << "'});";
 
302
  es << "return L.marker([";
 
303
  es << Utils::round_js_str(position().latitude(), 16, buf) << ",";
 
304
  es << Utils::round_js_str(position().longitude(), 16, buf) << "],";
 
305
  es << "{"
 
306
          "interactive:false,"
 
307
          "icon:wIcon,"
 
308
          "keyboard:false"
 
309
        "});})()";
 
310
}
 
311
 
 
312
void WLeafletMap::WidgetMarker::setAnchorPoint(double x, double y)
 
313
{
 
314
  anchorX_ = x;
 
315
  anchorY_ = y;
 
316
 
 
317
  if (map() && map()->isRendered()) {
 
318
    anchorPointChanged_ = true;
 
319
    map()->scheduleRender();
 
320
  }
 
321
}
 
322
 
 
323
void WLeafletMap::WidgetMarker::unrender()
 
324
{
 
325
  WWidget *w = widget();
 
326
  std::unique_ptr<WWidget> uW;
 
327
  if (w) {
 
328
    uW = container_->removeWidget(w);
 
329
  }
 
330
  container_.reset();
 
331
  createContainer();
 
332
  if (uW) {
 
333
    container_->addWidget(std::move(uW));
 
334
  }
 
335
}
 
336
 
 
337
void WLeafletMap::WidgetMarker::createContainer()
 
338
{
 
339
  container_.reset(new Wt::WContainerWidget());
 
340
  container_->addStyleClass(WIDGETMARKER_CONTAINER_CLASS);
 
341
  container_->setJavaScriptMember("wtReparentBarrier", "true");
 
342
  WLeafletMap *m = map();
 
343
  if (m)
 
344
    container_->setParentWidget(m);
 
345
}
 
346
 
 
347
bool WLeafletMap::WidgetMarker::needsUpdate() const
 
348
{
 
349
  return anchorPointChanged_;
 
350
}
 
351
 
 
352
void WLeafletMap::WidgetMarker::update(WStringStream &js)
 
353
{
 
354
  if (anchorPointChanged_) {
 
355
    updateAnchorJS(js);
 
356
    anchorPointChanged_ = false;
 
357
  }
 
358
}
 
359
 
 
360
void WLeafletMap::WidgetMarker::updateAnchorJS(WStringStream &js) const
 
361
{
 
362
  char buf[30];
 
363
  js << "var o=" << container_->jsRef() << ";if(o){"
 
364
        "" "o.style.transform='translate(";
 
365
  if (anchorX_ >= 0) {
 
366
    js << Utils::round_js_str(-anchorX_, 16, buf) << "px";
 
367
  } else {
 
368
    js << "-50%";
 
369
  }
 
370
  js << ',';
 
371
  if (anchorY_ >= 0) {
 
372
    js << Utils::round_js_str(-anchorY_, 16, buf) << "px";
 
373
  } else {
 
374
    js << "-50%";
 
375
  }
 
376
  js << ")';"
 
377
        "}";
 
378
}
 
379
 
 
380
WLeafletMap::LeafletMarker::LeafletMarker(const Coordinate &pos)
 
381
  : Marker(pos)
 
382
{ }
 
383
 
 
384
WLeafletMap::LeafletMarker::~LeafletMarker()
 
385
{ }
 
386
 
 
387
void WLeafletMap::LeafletMarker::createMarkerJS(WStringStream &ss, WStringStream &) const
 
388
{
 
389
  ss << "L.marker([";
 
390
  char buf[30];
 
391
  ss << Utils::round_js_str(position().latitude(), 16, buf) << ",";
 
392
  ss << Utils::round_js_str(position().longitude(), 16, buf) << "])";
 
393
}
 
394
 
 
395
WLeafletMap::WLeafletMap()
 
396
  : impl_(nullptr),
 
397
    options_(),
 
398
    zoomLevelChanged_(this, "zoomLevelChanged"),
 
399
    panChanged_(this, "panChanged"),
 
400
    zoomLevel_(13),
 
401
    nextMarkerId_(0),
 
402
    renderedTileLayersSize_(0),
 
403
    renderedOverlaysSize_(0)
 
404
{
 
405
  setup();
 
406
}
 
407
 
 
408
WLeafletMap::WLeafletMap(const Json::Object &options)
 
409
  : options_(options),
 
410
    zoomLevelChanged_(this, "zoomLevelChanged"),
 
411
    panChanged_(this, "panChanged"),
 
412
    zoomLevel_(13),
 
413
    nextMarkerId_(0),
 
414
    renderedTileLayersSize_(0),
 
415
    renderedOverlaysSize_(0)
 
416
{
 
417
  setup();
 
418
}
 
419
 
 
420
// called from constructors to reduce code duplication (not currently designed to be run again)
 
421
void WLeafletMap::setup()
 
422
{
 
423
  setImplementation(std::unique_ptr<Impl>(impl_ = new Impl()));
 
424
 
 
425
  zoomLevelChanged().connect(this, &WLeafletMap::handleZoomLevelChanged);
 
426
  panChanged().connect(this, &WLeafletMap::handlePanChanged);
 
427
 
 
428
  WApplication *app = WApplication::instance();
 
429
  if (app) {
 
430
    if (!app->styleSheet().isDefined(WIDGETMARKER_CONTAINER_RULENAME)) {
 
431
      app->styleSheet().addRule("." WIDGETMARKER_CONTAINER_CLASS, "transform: translate(-50%, -50%);", WIDGETMARKER_CONTAINER_RULENAME);
 
432
    }
 
433
    if (!app->styleSheet().isDefined(WIDGETMARKER_CONTAINER_CHILDREN_RULENAME)) {
 
434
      app->styleSheet().addRule("." WIDGETMARKER_CONTAINER_CLASS " > *", "pointer-events: auto;", WIDGETMARKER_CONTAINER_CHILDREN_RULENAME);
 
435
    }
 
436
 
 
437
    std::string leafletJSURL;
 
438
    std::string leafletCSSURL;
 
439
    Wt::WApplication::readConfigurationProperty("leafletJSURL", leafletJSURL);
 
440
    Wt::WApplication::readConfigurationProperty("leafletCSSURL", leafletCSSURL);
 
441
    if (!leafletJSURL.empty() &&
 
442
        !leafletCSSURL.empty()) {
 
443
      app->require(leafletJSURL);
 
444
      app->useStyleSheet(WLink(leafletCSSURL));
 
445
    } else {
 
446
      throw Wt::WException("Trying to create a WLeafletMap, but the leafletJSURL and/or leafletCSSURL properties are not configured");
 
447
    }
 
448
  } else {
 
449
    throw Wt::WException("Trying to create a WLeafletMap without an active WApplication");
 
450
  }
 
451
}
 
452
 
 
453
void WLeafletMap::setOptions(const Json::Object &options)
 
454
{
 
455
  options_ = options;
 
456
  flags_.set(BIT_OPTIONS_CHANGED);
 
457
 
 
458
  if (isRendered()) {
 
459
    for (std::size_t i = 0; i < markers_.size(); ++i) {
 
460
      if (!markers_[i].flags.test(MarkerEntry::BIT_ADDED) &&
 
461
          !markers_[i].flags.test(MarkerEntry::BIT_REMOVED)) {
 
462
        markers_[i].marker->unrender();
 
463
      }
 
464
    }
 
465
  }
 
466
 
 
467
  scheduleRender();
 
468
}
 
469
 
 
470
WLeafletMap::~WLeafletMap()
 
471
{ }
 
472
 
 
473
void WLeafletMap::addTileLayer(const std::string &urlTemplate,
 
474
                               const Json::Object &options)
 
475
{
 
476
  TileLayer layer;
 
477
  layer.urlTemplate = urlTemplate;
 
478
  layer.options = options;
 
479
  tileLayers_.push_back(layer);
 
480
 
 
481
  scheduleRender();
 
482
}
 
483
 
 
484
void WLeafletMap::addTileLayerJS(WStringStream &ss, const TileLayer &layer) const
 
485
{
 
486
  std::string optionsStr = Json::serialize(layer.options);
 
487
 
 
488
  EscapeOStream es(ss);
 
489
  es << "var o=" << jsRef() << ";if(o && o.wtObj){"
 
490
        "" "o.wtObj.addTileLayer('";
 
491
  es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
 
492
  es << layer.urlTemplate;
 
493
  es.popEscape();
 
494
  es << "','";
 
495
  es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
 
496
  es << optionsStr;
 
497
  es.popEscape();
 
498
  es << "');}";
 
499
}
 
500
 
 
501
void WLeafletMap::addMarker(std::unique_ptr<Marker> marker)
 
502
{
 
503
  marker->setMap(this);
 
504
 
 
505
  for (std::size_t i = 0; i < markers_.size(); ++i) {
 
506
    if (markers_[i].marker == marker.get() &&
 
507
        markers_[i].flags.test(MarkerEntry::BIT_REMOVED)) {
 
508
      markers_[i].uMarker = std::move(marker);
 
509
      markers_[i].flags.reset(MarkerEntry::BIT_REMOVED);
 
510
      return;
 
511
    }
 
512
  }
 
513
 
 
514
  MarkerEntry entry;
 
515
  entry.uMarker = std::move(marker);
 
516
  entry.marker = entry.uMarker.get();
 
517
  entry.flags.set(MarkerEntry::BIT_ADDED);
 
518
  entry.id = nextMarkerId_;
 
519
  ++nextMarkerId_;
 
520
 
 
521
  markers_.push_back(std::move(entry));
 
522
 
 
523
  scheduleRender();
 
524
}
 
525
 
 
526
void WLeafletMap::addMarkerJS(WStringStream &ss, long long id, const Marker *marker) const
 
527
{
 
528
  WStringStream js;
 
529
  ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
 
530
        "" "o.wtObj.addMarker(" << id << ',';
 
531
  marker->createMarkerJS(ss, js);
 
532
  ss << ");";
 
533
  ss << js.str();
 
534
  ss << "}";
 
535
}
 
536
 
 
537
std::unique_ptr<WLeafletMap::Marker> WLeafletMap::removeMarker(Marker *marker)
 
538
{
 
539
  for (std::size_t i = 0; i < markers_.size(); ++i) {
 
540
    if (markers_[i].uMarker.get() == marker &&
 
541
        markers_[i].marker == marker) {
 
542
      marker->setMap(nullptr);
 
543
      std::unique_ptr<Marker> result = std::move(markers_[i].uMarker);
 
544
      if (markers_[i].flags.test(MarkerEntry::BIT_ADDED)) {
 
545
        markers_.erase(markers_.begin() + i);
 
546
        return result;
 
547
      }
 
548
      markers_[i].flags.set(MarkerEntry::BIT_REMOVED);
 
549
      scheduleRender();
 
550
      return result;
 
551
    }
 
552
  }
 
553
  return nullptr;
 
554
}
 
555
 
 
556
void WLeafletMap::removeMarkerJS(WStringStream &ss, long long id) const
 
557
{
 
558
  ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
 
559
     << "" "o.wtObj.removeMarker(" << id << ");"
 
560
        "}";
 
561
}
 
562
 
 
563
void WLeafletMap::moveMarkerJS(WStringStream &ss,
 
564
                               long long id,
 
565
                               const Coordinate &position) const
 
566
{
 
567
  ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
 
568
     << "" "o.wtObj.moveMarker(" << id << ",[";
 
569
  char buf[30];
 
570
  ss << Utils::round_js_str(position.latitude(), 16, buf) << ",";
 
571
  ss << Utils::round_js_str(position.longitude(), 16, buf) << "]);";
 
572
  ss << "}";
 
573
}
 
574
 
 
575
void WLeafletMap::addPolyline(const std::vector<Coordinate> &points,
 
576
                              const WPen &pen)
 
577
{
 
578
  overlays_.push_back(cpp14::make_unique<Polyline>(points, pen));
 
579
  scheduleRender();
 
580
}
 
581
 
 
582
void WLeafletMap::addCircle(const Coordinate &center,
 
583
                            double radius,
 
584
                            const WPen &stroke,
 
585
                            const WBrush &fill)
 
586
{
 
587
  overlays_.push_back(cpp14::make_unique<Circle>(center, radius, stroke, fill));
 
588
  scheduleRender();
 
589
}
 
590
 
 
591
void WLeafletMap::setZoomLevel(int level)
 
592
{
 
593
  zoomLevel_ = level;
 
594
  flags_.set(BIT_ZOOM_CHANGED);
 
595
 
 
596
  scheduleRender();
 
597
}
 
598
 
 
599
void WLeafletMap::zoomJS(WStringStream &ss,
 
600
                         int level) const
 
601
{
 
602
  ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
 
603
        "" "o.wtObj.zoom(" << level << ");"
 
604
        "}";
 
605
}
 
606
 
 
607
void WLeafletMap::panTo(const Coordinate &center)
 
608
{
 
609
  position_ = center;
 
610
  flags_.set(BIT_PAN_CHANGED);
 
611
 
 
612
  scheduleRender();
 
613
}
 
614
 
 
615
void WLeafletMap::panToJS(WStringStream &ss,
 
616
                          const Coordinate &position) const
 
617
{
 
618
  ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
 
619
        "" "o.wtObj.panTo(";
 
620
  char buf[30];
 
621
  ss << Utils::round_js_str(position.latitude(), 16, buf) << ",";
 
622
  ss << Utils::round_js_str(position.longitude(), 16, buf) << ");"
 
623
        "}";
 
624
}
 
625
 
 
626
void WLeafletMap::addPathOptions(Json::Object &options,
 
627
                                 const WPen &stroke,
 
628
                                 const WBrush &fill)
 
629
{
 
630
  using namespace Wt;
 
631
  if (stroke.style() != PenStyle::None) {
 
632
    options["stroke"] = Json::Value(true);
 
633
    options["color"] = Json::Value(WT_USTRING::fromUTF8(stroke.color().cssText(false)));
 
634
    options["opacity"] = Json::Value(stroke.color().alpha() / 255.0);
 
635
    double weight = stroke.width().toPixels();
 
636
    weight = weight == 0 ? 1.0 : weight;
 
637
    options["weight"] = Json::Value(weight);
 
638
 
 
639
    std::string capStyle;
 
640
    switch (stroke.capStyle()) {
 
641
    case PenCapStyle::Flat:
 
642
      capStyle = "butt";
 
643
      break;
 
644
    case PenCapStyle::Square:
 
645
      capStyle = "square";
 
646
      break;
 
647
    case PenCapStyle::Round:
 
648
      capStyle = "round";
 
649
    }
 
650
 
 
651
    options["lineCap"] = Json::Value(WT_USTRING::fromUTF8(capStyle));
 
652
 
 
653
    std::string joinStyle;
 
654
    switch (stroke.joinStyle()) {
 
655
    case PenJoinStyle::Bevel:
 
656
      joinStyle = "bevel";
 
657
      break;
 
658
    case PenJoinStyle::Miter:
 
659
      joinStyle = "miter";
 
660
      break;
 
661
    case PenJoinStyle::Round:
 
662
      joinStyle = "round";
 
663
    }
 
664
 
 
665
    options["lineJoin"] = Json::Value(WT_USTRING::fromUTF8(joinStyle));
 
666
 
 
667
    // TODO(Roel): dashArray?
 
668
  } else {
 
669
    options["stroke"] = Json::Value(false);
 
670
  }
 
671
 
 
672
  if (fill.style() != BrushStyle::None) {
 
673
    options["fill"] = Json::Value(true);
 
674
    options["fillColor"] = Json::Value(WT_USTRING::fromUTF8(fill.color().cssText(false)));
 
675
    options["fillOpacity"] = Json::Value(fill.color().alpha() / 255.0);
 
676
  } else {
 
677
    options["fill"] = Json::Value(false);
 
678
  }
 
679
}
 
680
 
 
681
void WLeafletMap::handlePanChanged(double latitude, double longitude)
 
682
{
 
683
  position_ = Coordinate(latitude, longitude);
 
684
}
 
685
 
 
686
void WLeafletMap::handleZoomLevelChanged(int zoomLevel)
 
687
{
 
688
  zoomLevel_ = zoomLevel;
 
689
}
 
690
 
 
691
void WLeafletMap::defineJavaScript()
 
692
{
 
693
  WApplication *app = WApplication::instance();
 
694
 
 
695
  LOAD_JAVASCRIPT(app, "js/WLeafletMap.js", "WLeafletMap", wtjs1);
 
696
 
 
697
  std::string optionsStr = Json::serialize(options_);
 
698
 
 
699
  WStringStream ss;
 
700
  EscapeOStream es(ss);
 
701
  es << "new " WT_CLASS ".WLeafletMap("
 
702
     << app->javaScriptClass() << "," << jsRef() << ",'";
 
703
  es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
 
704
  es << optionsStr;
 
705
  es.popEscape();
 
706
  es << "',";
 
707
  char buf[30];
 
708
  es << Utils::round_js_str(position_.latitude(), 16, buf) << ",";
 
709
  es << Utils::round_js_str(position_.longitude(), 16, buf) << ",";
 
710
  es << Utils::round_js_str(zoomLevel_, 16, buf) << ");";
 
711
 
 
712
  setJavaScriptMember(" WLeafletMap", ss.str());
 
713
  setJavaScriptMember(WT_RESIZE_JS,
 
714
                      jsRef() + ".wtObj.wtResize");
 
715
}
 
716
 
 
717
void WLeafletMap::render(WFlags<RenderFlag> flags)
 
718
{
 
719
  if (flags.test(RenderFlag::Full) || flags_.test(BIT_OPTIONS_CHANGED)) {
 
720
    defineJavaScript();
 
721
 
 
722
    // Just created, no tile layers or overlays have been rendered yet
 
723
    renderedTileLayersSize_ = 0;
 
724
    renderedOverlaysSize_ = 0;
 
725
 
 
726
    // pan and zoom were already set when creating new WLeafletMap
 
727
    flags_.reset(BIT_PAN_CHANGED);
 
728
    flags_.reset(BIT_ZOOM_CHANGED);
 
729
  }
 
730
 
 
731
  WStringStream ss;
 
732
 
 
733
  if (flags_.test(BIT_PAN_CHANGED)) {
 
734
    panToJS(ss, position_);
 
735
    flags_.reset(BIT_PAN_CHANGED);
 
736
  }
 
737
 
 
738
  if (flags_.test(BIT_ZOOM_CHANGED)) {
 
739
    zoomJS(ss, zoomLevel_);
 
740
    flags_.reset(BIT_ZOOM_CHANGED);
 
741
  }
 
742
 
 
743
  for (std::size_t i = renderedTileLayersSize_; i < tileLayers_.size(); ++i) {
 
744
    addTileLayerJS(ss, tileLayers_[i]);
 
745
  }
 
746
  renderedTileLayersSize_ = tileLayers_.size();
 
747
 
 
748
  for (std::size_t i = renderedOverlaysSize_; i < overlays_.size(); ++i) {
 
749
    overlays_[i]->addJS(ss, this);
 
750
  }
 
751
  renderedOverlaysSize_ = overlays_.size();
 
752
 
 
753
  for (std::size_t i = 0; i < markers_.size();) {
 
754
    if (markers_[i].flags.test(MarkerEntry::BIT_REMOVED)) {
 
755
      if (!flags_.test(BIT_OPTIONS_CHANGED)) {
 
756
        removeMarkerJS(ss, markers_[i].id);
 
757
      }
 
758
      markers_.erase(markers_.begin() + i);
 
759
    } else {
 
760
      ++i;
 
761
    }
 
762
  }
 
763
 
 
764
  for (std::size_t i = 0; i < markers_.size(); ++i) {
 
765
    if (flags.test(RenderFlag::Full) ||
 
766
        flags_.test(BIT_OPTIONS_CHANGED) ||
 
767
        markers_[i].flags.test(MarkerEntry::BIT_ADDED)) {
 
768
      addMarkerJS(ss, markers_[i].id, markers_[i].marker);
 
769
      markers_[i].flags.reset(MarkerEntry::BIT_ADDED);
 
770
    } else {
 
771
      if (markers_[i].marker->moved_) {
 
772
        moveMarkerJS(ss, markers_[i].id, markers_[i].marker->position());
 
773
      }
 
774
      if (markers_[i].marker->needsUpdate()) {
 
775
        markers_[i].marker->update(ss);
 
776
      }
 
777
    }
 
778
    markers_[i].marker->moved_ = false;
 
779
  }
 
780
 
 
781
  if (!ss.empty()) {
 
782
    doJavaScript(ss.str());
 
783
  }
 
784
 
 
785
  flags_.reset(BIT_OPTIONS_CHANGED);
 
786
 
 
787
  WCompositeWidget::render(flags);
 
788
}
 
789
 
 
790
std::string WLeafletMap::mapJsRef() const
 
791
{
 
792
  return "((function(){var o=" + jsRef() + ";if(o&&o.wtObj){return o.wtObj.map;}return null;})())";
 
793
}
 
794
 
 
795
WLeafletMap::MarkerEntry::MarkerEntry()
 
796
  : uMarker(nullptr),
 
797
    marker(nullptr),
 
798
    id(-1),
 
799
    flags()
 
800
{ }
 
801
 
 
802
}