2
* Copyright (C) 2019 Emweb bv, Herent, Belgium.
4
* See the LICENSE file for terms of use.
7
#include "Wt/WLeafletMap.h"
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"
16
#include "Wt/WLogger.h"
18
#include "Wt/WStringStream.h"
20
#include "Wt/Json/Array.h"
21
#include "Wt/Json/Parser.h"
22
#include "Wt/Json/Serializer.h"
23
#include "Wt/Json/Value.h"
25
#include "web/DomElement.h"
26
#include "web/EscapeOStream.h"
27
#include "web/WebUtils.h"
30
#include "js/WLeafletMap.min.js"
35
LOGGER("WLeafletMap");
37
const std::string WLeafletMap::WIDGETMARKER_CONTAINER_RULENAME = "WLeafletMap::WidgetMarker::container";
38
const std::string WLeafletMap::WIDGETMARKER_CONTAINER_CHILDREN_RULENAME = "WLeafletMap::WidgetMarker::container-children";
40
#define WIDGETMARKER_CONTAINER_CLASS "Wt-leaflet-widgetmarker-container"
42
class WLeafletMap::Impl : public WWebWidget {
46
virtual DomElementType domElementType() const override;
49
WLeafletMap::Impl::Impl()
54
DomElementType WLeafletMap::Impl::domElementType() const
56
return DomElementType::DIV;
59
struct WLeafletMap::Overlay {
61
virtual void addJS(WStringStream &ss,
62
WLeafletMap *map) const = 0;
64
Overlay(const Overlay &) = delete;
65
Overlay& operator=(const Overlay &) = delete;
66
Overlay(Overlay &&) = delete;
67
Overlay& operator=(Overlay &&) = delete;
73
struct WLeafletMap::Polyline : WLeafletMap::Overlay {
74
std::vector<Coordinate> points;
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;
86
struct WLeafletMap::Circle : WLeafletMap::Overlay {
92
Circle(const Coordinate ¢er, double radius, const WPen &stroke, const WBrush &fill);
93
virtual ~Circle() override;
95
Circle(const Circle &) = delete;
96
Circle& operator=(const Circle &) = delete;
97
Circle(Circle &&) = delete;
98
Circle& operator=(Circle &&) = delete;
100
virtual void addJS(WStringStream &ss, WLeafletMap *map) const override;
103
WLeafletMap::Overlay::Overlay()
106
WLeafletMap::Overlay::~Overlay()
109
WLeafletMap::Polyline::Polyline(const std::vector<WLeafletMap::Coordinate> &points,
115
WLeafletMap::Polyline::~Polyline()
118
void WLeafletMap::Polyline::addJS(WStringStream &ss,
119
WLeafletMap *map) const
121
if (pen.style() == PenStyle::None)
124
Json::Object options;
125
addPathOptions(options, pen, BrushStyle::None);
126
std::string optionsStr = Json::serialize(options);
128
EscapeOStream es(ss);
129
es << "var o=" << map->jsRef() << ";if(o && o.wtObj){"
130
"o.wtObj.addPolyline(";
132
for (std::size_t i = 0; i < points.size(); ++i) {
137
es << Utils::round_js_str(points[i].latitude(), 16, buf) << ",";
138
es << Utils::round_js_str(points[i].longitude(), 16, buf);
142
es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
148
WLeafletMap::Circle::Circle(const Coordinate ¢er,
158
WLeafletMap::Circle::~Circle()
161
void WLeafletMap::Circle::addJS(WStringStream &ss,
162
WLeafletMap *map) const
164
Json::Object options;
165
options["radius"] = Json::Value(radius);
166
addPathOptions(options, stroke, fill);
167
std::string optionsStr = Json::serialize(options);
169
EscapeOStream es(ss);
170
es << "var o=" << map->jsRef() << ";if(o && o.wtObj){"
171
<< "" "o.wtObj.addCircle([";
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);
181
WLeafletMap::Coordinate::Coordinate()
186
WLeafletMap::Coordinate::Coordinate(double latitude, double longitude)
191
void WLeafletMap::Coordinate::setLatitude(double latitude)
196
void WLeafletMap::Coordinate::setLongitude(double longitude)
201
WLeafletMap::Marker::Marker(const Coordinate &pos)
207
WLeafletMap::Marker::~Marker()
210
void WLeafletMap::Marker::move(const Coordinate &pos)
215
map_->scheduleRender();
219
void WLeafletMap::Marker::setMap(WLeafletMap *map)
224
void WLeafletMap::Marker::unrender()
227
bool WLeafletMap::Marker::needsUpdate() const
232
void WLeafletMap::Marker::update(WStringStream &js)
235
WLeafletMap::WidgetMarker::WidgetMarker(const Coordinate &pos,
236
std::unique_ptr<WWidget> widget)
241
anchorPointChanged_(false)
244
container_->addWidget(std::move(widget));
247
WLeafletMap::WidgetMarker::~WidgetMarker()
252
WWidget *WLeafletMap::WidgetMarker::widget()
254
if (container_ && container_->count() > 0) {
255
return container_->widget(0);
260
const WWidget *WLeafletMap::WidgetMarker::widget() const
262
if (container_ && container_->count() > 0) {
263
return container_->widget(0);
268
void WLeafletMap::WidgetMarker::setMap(WLeafletMap *map)
273
container_->setParentWidget(map);
277
void WLeafletMap::WidgetMarker::createMarkerJS(WStringStream &ss, WStringStream &postJS) const
279
std::unique_ptr<DomElement> element(container_->createSDomElement(WApplication::instance()));
281
DomElement::TimeoutList timeouts;
285
if (anchorX_ >= 0 || anchorY_ >= 0) {
286
updateAnchorJS(postJS);
289
EscapeOStream js(postJS);
291
EscapeOStream es(ss);
292
es << "(function(){";
293
es << "var wIcon=L.divIcon({"
298
es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
299
element->asHTML(es, js, timeouts);
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) << "],";
312
void WLeafletMap::WidgetMarker::setAnchorPoint(double x, double y)
317
if (map() && map()->isRendered()) {
318
anchorPointChanged_ = true;
319
map()->scheduleRender();
323
void WLeafletMap::WidgetMarker::unrender()
325
WWidget *w = widget();
326
std::unique_ptr<WWidget> uW;
328
uW = container_->removeWidget(w);
333
container_->addWidget(std::move(uW));
337
void WLeafletMap::WidgetMarker::createContainer()
339
container_.reset(new Wt::WContainerWidget());
340
container_->addStyleClass(WIDGETMARKER_CONTAINER_CLASS);
341
container_->setJavaScriptMember("wtReparentBarrier", "true");
342
WLeafletMap *m = map();
344
container_->setParentWidget(m);
347
bool WLeafletMap::WidgetMarker::needsUpdate() const
349
return anchorPointChanged_;
352
void WLeafletMap::WidgetMarker::update(WStringStream &js)
354
if (anchorPointChanged_) {
356
anchorPointChanged_ = false;
360
void WLeafletMap::WidgetMarker::updateAnchorJS(WStringStream &js) const
363
js << "var o=" << container_->jsRef() << ";if(o){"
364
"" "o.style.transform='translate(";
366
js << Utils::round_js_str(-anchorX_, 16, buf) << "px";
372
js << Utils::round_js_str(-anchorY_, 16, buf) << "px";
380
WLeafletMap::LeafletMarker::LeafletMarker(const Coordinate &pos)
384
WLeafletMap::LeafletMarker::~LeafletMarker()
387
void WLeafletMap::LeafletMarker::createMarkerJS(WStringStream &ss, WStringStream &) const
391
ss << Utils::round_js_str(position().latitude(), 16, buf) << ",";
392
ss << Utils::round_js_str(position().longitude(), 16, buf) << "])";
395
WLeafletMap::WLeafletMap()
398
zoomLevelChanged_(this, "zoomLevelChanged"),
399
panChanged_(this, "panChanged"),
402
renderedTileLayersSize_(0),
403
renderedOverlaysSize_(0)
408
WLeafletMap::WLeafletMap(const Json::Object &options)
410
zoomLevelChanged_(this, "zoomLevelChanged"),
411
panChanged_(this, "panChanged"),
414
renderedTileLayersSize_(0),
415
renderedOverlaysSize_(0)
420
// called from constructors to reduce code duplication (not currently designed to be run again)
421
void WLeafletMap::setup()
423
setImplementation(std::unique_ptr<Impl>(impl_ = new Impl()));
425
zoomLevelChanged().connect(this, &WLeafletMap::handleZoomLevelChanged);
426
panChanged().connect(this, &WLeafletMap::handlePanChanged);
428
WApplication *app = WApplication::instance();
430
if (!app->styleSheet().isDefined(WIDGETMARKER_CONTAINER_RULENAME)) {
431
app->styleSheet().addRule("." WIDGETMARKER_CONTAINER_CLASS, "transform: translate(-50%, -50%);", WIDGETMARKER_CONTAINER_RULENAME);
433
if (!app->styleSheet().isDefined(WIDGETMARKER_CONTAINER_CHILDREN_RULENAME)) {
434
app->styleSheet().addRule("." WIDGETMARKER_CONTAINER_CLASS " > *", "pointer-events: auto;", WIDGETMARKER_CONTAINER_CHILDREN_RULENAME);
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));
446
throw Wt::WException("Trying to create a WLeafletMap, but the leafletJSURL and/or leafletCSSURL properties are not configured");
449
throw Wt::WException("Trying to create a WLeafletMap without an active WApplication");
453
void WLeafletMap::setOptions(const Json::Object &options)
456
flags_.set(BIT_OPTIONS_CHANGED);
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();
470
WLeafletMap::~WLeafletMap()
473
void WLeafletMap::addTileLayer(const std::string &urlTemplate,
474
const Json::Object &options)
477
layer.urlTemplate = urlTemplate;
478
layer.options = options;
479
tileLayers_.push_back(layer);
484
void WLeafletMap::addTileLayerJS(WStringStream &ss, const TileLayer &layer) const
486
std::string optionsStr = Json::serialize(layer.options);
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;
495
es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
501
void WLeafletMap::addMarker(std::unique_ptr<Marker> marker)
503
marker->setMap(this);
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);
515
entry.uMarker = std::move(marker);
516
entry.marker = entry.uMarker.get();
517
entry.flags.set(MarkerEntry::BIT_ADDED);
518
entry.id = nextMarkerId_;
521
markers_.push_back(std::move(entry));
526
void WLeafletMap::addMarkerJS(WStringStream &ss, long long id, const Marker *marker) const
529
ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
530
"" "o.wtObj.addMarker(" << id << ',';
531
marker->createMarkerJS(ss, js);
537
std::unique_ptr<WLeafletMap::Marker> WLeafletMap::removeMarker(Marker *marker)
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);
548
markers_[i].flags.set(MarkerEntry::BIT_REMOVED);
556
void WLeafletMap::removeMarkerJS(WStringStream &ss, long long id) const
558
ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
559
<< "" "o.wtObj.removeMarker(" << id << ");"
563
void WLeafletMap::moveMarkerJS(WStringStream &ss,
565
const Coordinate &position) const
567
ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
568
<< "" "o.wtObj.moveMarker(" << id << ",[";
570
ss << Utils::round_js_str(position.latitude(), 16, buf) << ",";
571
ss << Utils::round_js_str(position.longitude(), 16, buf) << "]);";
575
void WLeafletMap::addPolyline(const std::vector<Coordinate> &points,
578
overlays_.push_back(cpp14::make_unique<Polyline>(points, pen));
582
void WLeafletMap::addCircle(const Coordinate ¢er,
587
overlays_.push_back(cpp14::make_unique<Circle>(center, radius, stroke, fill));
591
void WLeafletMap::setZoomLevel(int level)
594
flags_.set(BIT_ZOOM_CHANGED);
599
void WLeafletMap::zoomJS(WStringStream &ss,
602
ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
603
"" "o.wtObj.zoom(" << level << ");"
607
void WLeafletMap::panTo(const Coordinate ¢er)
610
flags_.set(BIT_PAN_CHANGED);
615
void WLeafletMap::panToJS(WStringStream &ss,
616
const Coordinate &position) const
618
ss << "var o=" << jsRef() << ";if(o && o.wtObj){"
621
ss << Utils::round_js_str(position.latitude(), 16, buf) << ",";
622
ss << Utils::round_js_str(position.longitude(), 16, buf) << ");"
626
void WLeafletMap::addPathOptions(Json::Object &options,
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);
639
std::string capStyle;
640
switch (stroke.capStyle()) {
641
case PenCapStyle::Flat:
644
case PenCapStyle::Square:
647
case PenCapStyle::Round:
651
options["lineCap"] = Json::Value(WT_USTRING::fromUTF8(capStyle));
653
std::string joinStyle;
654
switch (stroke.joinStyle()) {
655
case PenJoinStyle::Bevel:
658
case PenJoinStyle::Miter:
661
case PenJoinStyle::Round:
665
options["lineJoin"] = Json::Value(WT_USTRING::fromUTF8(joinStyle));
667
// TODO(Roel): dashArray?
669
options["stroke"] = Json::Value(false);
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);
677
options["fill"] = Json::Value(false);
681
void WLeafletMap::handlePanChanged(double latitude, double longitude)
683
position_ = Coordinate(latitude, longitude);
686
void WLeafletMap::handleZoomLevelChanged(int zoomLevel)
688
zoomLevel_ = zoomLevel;
691
void WLeafletMap::defineJavaScript()
693
WApplication *app = WApplication::instance();
695
LOAD_JAVASCRIPT(app, "js/WLeafletMap.js", "WLeafletMap", wtjs1);
697
std::string optionsStr = Json::serialize(options_);
700
EscapeOStream es(ss);
701
es << "new " WT_CLASS ".WLeafletMap("
702
<< app->javaScriptClass() << "," << jsRef() << ",'";
703
es.pushEscape(EscapeOStream::JsStringLiteralSQuote);
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) << ");";
712
setJavaScriptMember(" WLeafletMap", ss.str());
713
setJavaScriptMember(WT_RESIZE_JS,
714
jsRef() + ".wtObj.wtResize");
717
void WLeafletMap::render(WFlags<RenderFlag> flags)
719
if (flags.test(RenderFlag::Full) || flags_.test(BIT_OPTIONS_CHANGED)) {
722
// Just created, no tile layers or overlays have been rendered yet
723
renderedTileLayersSize_ = 0;
724
renderedOverlaysSize_ = 0;
726
// pan and zoom were already set when creating new WLeafletMap
727
flags_.reset(BIT_PAN_CHANGED);
728
flags_.reset(BIT_ZOOM_CHANGED);
733
if (flags_.test(BIT_PAN_CHANGED)) {
734
panToJS(ss, position_);
735
flags_.reset(BIT_PAN_CHANGED);
738
if (flags_.test(BIT_ZOOM_CHANGED)) {
739
zoomJS(ss, zoomLevel_);
740
flags_.reset(BIT_ZOOM_CHANGED);
743
for (std::size_t i = renderedTileLayersSize_; i < tileLayers_.size(); ++i) {
744
addTileLayerJS(ss, tileLayers_[i]);
746
renderedTileLayersSize_ = tileLayers_.size();
748
for (std::size_t i = renderedOverlaysSize_; i < overlays_.size(); ++i) {
749
overlays_[i]->addJS(ss, this);
751
renderedOverlaysSize_ = overlays_.size();
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);
758
markers_.erase(markers_.begin() + i);
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);
771
if (markers_[i].marker->moved_) {
772
moveMarkerJS(ss, markers_[i].id, markers_[i].marker->position());
774
if (markers_[i].marker->needsUpdate()) {
775
markers_[i].marker->update(ss);
778
markers_[i].marker->moved_ = false;
782
doJavaScript(ss.str());
785
flags_.reset(BIT_OPTIONS_CHANGED);
787
WCompositeWidget::render(flags);
790
std::string WLeafletMap::mapJsRef() const
792
return "((function(){var o=" + jsRef() + ";if(o&&o.wtObj){return o.wtObj.map;}return null;})())";
795
WLeafletMap::MarkerEntry::MarkerEntry()