From 5e792e03bc390abbc361540bb16df368c703e346 Mon Sep 17 00:00:00 2001 From: Arkady <144838822+arkadymarch@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:39:42 -0500 Subject: [PATCH 1/5] added crosstalk function with clusters --- inst/htmlwidgets/assets/leaflet.js | 5402 ++++++++++++++-------------- javascript/src/methods.js | 124 + 2 files changed, 2888 insertions(+), 2638 deletions(-) diff --git a/inst/htmlwidgets/assets/leaflet.js b/inst/htmlwidgets/assets/leaflet.js index 79dbe714a..ec3ffe069 100644 --- a/inst/htmlwidgets/assets/leaflet.js +++ b/inst/htmlwidgets/assets/leaflet.js @@ -1,2787 +1,2913 @@ (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i this.effectiveLength) throw new Error("Row argument was out of bounds: " + row + " > " + this.effectiveLength); - var colIndex = -1; - - if (typeof col === "undefined") { - var rowData = {}; - this.colnames.forEach(function (name, i) { - rowData[name] = _this3.columns[i][row % _this3.columns[i].length]; + + _createClass(DataFrame, [{ + key: "_updateCachedProperties", + value: function _updateCachedProperties() { + var _this = this; + + this.effectiveLength = 0; + this.colindices = {}; + this.columns.forEach(function (column, i) { + _this.effectiveLength = Math.max(_this.effectiveLength, column.length); + _this.colindices[_this.colnames[i]] = i; }); - return rowData; - } else if (typeof col === "string") { - colIndex = this._colIndex(col); - } else if (typeof col === "number") { - colIndex = col; - } - - if (colIndex < 0 || colIndex > this.columns.length) { - if (missingOK) return void 0;else throw new Error("Unknown column index: " + col); - } - - return this.columns[colIndex][row % this.columns[colIndex].length]; - } - }, { - key: "nrow", - value: function nrow() { - return this.effectiveLength; - } - }]); - - return DataFrame; -}(); - -exports["default"] = DataFrame; - - -},{"./util":17}],5:[function(require,module,exports){ -"use strict"; - -var _leaflet = require("./global/leaflet"); - -var _leaflet2 = _interopRequireDefault(_leaflet); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -// In RMarkdown's self-contained mode, we don't have a way to carry around the -// images that Leaflet needs but doesn't load into the page. Instead, we'll use -// the unpkg CDN. -if (typeof _leaflet2["default"].Icon.Default.imagePath === "undefined") { - _leaflet2["default"].Icon.Default.imagePath = "https://unpkg.com/leaflet@1.3.1/dist/images/"; -} - - -},{"./global/leaflet":10}],6:[function(require,module,exports){ -"use strict"; - -var _leaflet = require("./global/leaflet"); - -var _leaflet2 = _interopRequireDefault(_leaflet); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -// add texxtsize, textOnly, and style -_leaflet2["default"].Tooltip.prototype.options.textsize = "10px"; -_leaflet2["default"].Tooltip.prototype.options.textOnly = false; -_leaflet2["default"].Tooltip.prototype.options.style = null; // copy original layout to not completely stomp it. - -var initLayoutOriginal = _leaflet2["default"].Tooltip.prototype._initLayout; - -_leaflet2["default"].Tooltip.prototype._initLayout = function () { - initLayoutOriginal.call(this); - this._container.style.fontSize = this.options.textsize; - - if (this.options.textOnly) { - _leaflet2["default"].DomUtil.addClass(this._container, "leaflet-tooltip-text-only"); + } + }, { + key: "_colIndex", + value: function _colIndex(colname) { + var index = this.colindices[colname]; + if (typeof index === "undefined") return -1; + return index; + } + }, { + key: "col", + value: function col(name, values, strict) { + if (typeof name !== "string") throw new Error("Invalid column name \"" + name + "\""); + + var index = this._colIndex(name); + + if (arguments.length === 1) { + if (index < 0) return null;else return (0, _util.recycle)(this.columns[index], this.effectiveLength); + } + + if (index < 0) { + index = this.colnames.length; + this.colnames.push(name); + } + + this.columns[index] = (0, _util.asArray)(values); + this.colstrict[index] = !!strict; // TODO: Validate strictness (ensure lengths match up with other stricts) + + this._updateCachedProperties(); + + return this; + } + }, { + key: "cbind", + value: function cbind(obj, strict) { + var _this2 = this; + + Object.keys(obj).forEach(function (name) { + var coldata = obj[name]; + + _this2.col(name, coldata); + }); + return this; + } + }, { + key: "get", + value: function get(row, col, missingOK) { + var _this3 = this; + + if (row > this.effectiveLength) throw new Error("Row argument was out of bounds: " + row + " > " + this.effectiveLength); + var colIndex = -1; + + if (typeof col === "undefined") { + var rowData = {}; + this.colnames.forEach(function (name, i) { + rowData[name] = _this3.columns[i][row % _this3.columns[i].length]; + }); + return rowData; + } else if (typeof col === "string") { + colIndex = this._colIndex(col); + } else if (typeof col === "number") { + colIndex = col; + } + + if (colIndex < 0 || colIndex > this.columns.length) { + if (missingOK) return void 0;else throw new Error("Unknown column index: " + col); + } + + return this.columns[colIndex][row % this.columns[colIndex].length]; + } + }, { + key: "nrow", + value: function nrow() { + return this.effectiveLength; + } + }]); + + return DataFrame; + }(); + + exports["default"] = DataFrame; + + + },{"./util":17}],5:[function(require,module,exports){ + "use strict"; + + var _leaflet = require("./global/leaflet"); + + var _leaflet2 = _interopRequireDefault(_leaflet); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + // In RMarkdown's self-contained mode, we don't have a way to carry around the + // images that Leaflet needs but doesn't load into the page. Instead, we'll use + // the unpkg CDN. + if (typeof _leaflet2["default"].Icon.Default.imagePath === "undefined") { + _leaflet2["default"].Icon.Default.imagePath = "https://unpkg.com/leaflet@1.3.1/dist/images/"; } - - if (this.options.style) { - for (var property in this.options.style) { - this._container.style[property] = this.options.style[property]; + + + },{"./global/leaflet":10}],6:[function(require,module,exports){ + "use strict"; + + var _leaflet = require("./global/leaflet"); + + var _leaflet2 = _interopRequireDefault(_leaflet); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + // add texxtsize, textOnly, and style + _leaflet2["default"].Tooltip.prototype.options.textsize = "10px"; + _leaflet2["default"].Tooltip.prototype.options.textOnly = false; + _leaflet2["default"].Tooltip.prototype.options.style = null; // copy original layout to not completely stomp it. + + var initLayoutOriginal = _leaflet2["default"].Tooltip.prototype._initLayout; + + _leaflet2["default"].Tooltip.prototype._initLayout = function () { + initLayoutOriginal.call(this); + this._container.style.fontSize = this.options.textsize; + + if (this.options.textOnly) { + _leaflet2["default"].DomUtil.addClass(this._container, "leaflet-tooltip-text-only"); } - } -}; - - -},{"./global/leaflet":10}],7:[function(require,module,exports){ -"use strict"; - -var _leaflet = require("./global/leaflet"); - -var _leaflet2 = _interopRequireDefault(_leaflet); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -var protocolRegex = /^\/\//; - -var upgrade_protocol = function upgrade_protocol(urlTemplate) { - if (protocolRegex.test(urlTemplate)) { - if (window.location.protocol === "file:") { - // if in a local file, support http - // http should auto upgrade if necessary - urlTemplate = "http:" + urlTemplate; + + if (this.options.style) { + for (var property in this.options.style) { + this._container.style[property] = this.options.style[property]; + } } - } - - return urlTemplate; -}; - -var originalLTileLayerInitialize = _leaflet2["default"].TileLayer.prototype.initialize; - -_leaflet2["default"].TileLayer.prototype.initialize = function (urlTemplate, options) { - urlTemplate = upgrade_protocol(urlTemplate); - originalLTileLayerInitialize.call(this, urlTemplate, options); -}; - -var originalLTileLayerWMSInitialize = _leaflet2["default"].TileLayer.WMS.prototype.initialize; - -_leaflet2["default"].TileLayer.WMS.prototype.initialize = function (urlTemplate, options) { - urlTemplate = upgrade_protocol(urlTemplate); - originalLTileLayerWMSInitialize.call(this, urlTemplate, options); -}; - - -},{"./global/leaflet":10}],8:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = global.HTMLWidgets; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],9:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = global.jQuery; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],10:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = global.L; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],11:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = global.L.Proj; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],12:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = global.Shiny; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],13:[function(require,module,exports){ -"use strict"; - -var _jquery = require("./global/jquery"); - -var _jquery2 = _interopRequireDefault(_jquery); - -var _leaflet = require("./global/leaflet"); - -var _leaflet2 = _interopRequireDefault(_leaflet); - -var _shiny = require("./global/shiny"); - -var _shiny2 = _interopRequireDefault(_shiny); - -var _htmlwidgets = require("./global/htmlwidgets"); - -var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); - -var _util = require("./util"); - -var _crs_utils = require("./crs_utils"); - -var _controlStore = require("./control-store"); - -var _controlStore2 = _interopRequireDefault(_controlStore); - -var _layerManager = require("./layer-manager"); - -var _layerManager2 = _interopRequireDefault(_layerManager); - -var _methods = require("./methods"); - -var _methods2 = _interopRequireDefault(_methods); - -require("./fixup-default-icon"); - -require("./fixup-default-tooltip"); - -require("./fixup-url-protocol"); - -var _dataframe = require("./dataframe"); - -var _dataframe2 = _interopRequireDefault(_dataframe); - -var _clusterLayerStore = require("./cluster-layer-store"); - -var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -window.LeafletWidget = {}; -window.LeafletWidget.utils = {}; - -var methods = window.LeafletWidget.methods = _jquery2["default"].extend({}, _methods2["default"]); - -window.LeafletWidget.DataFrame = _dataframe2["default"]; -window.LeafletWidget.ClusterLayerStore = _clusterLayerStore2["default"]; -window.LeafletWidget.utils.getCRS = _crs_utils.getCRS; // Send updated bounds back to app. Takes a leaflet event object as input. - -function updateBounds(map) { - var id = map.getContainer().id; - var bounds = map.getBounds(); - - _shiny2["default"].onInputChange(id + "_bounds", { - north: bounds.getNorthEast().lat, - east: bounds.getNorthEast().lng, - south: bounds.getSouthWest().lat, - west: bounds.getSouthWest().lng + }; + + + },{"./global/leaflet":10}],7:[function(require,module,exports){ + "use strict"; + + var _leaflet = require("./global/leaflet"); + + var _leaflet2 = _interopRequireDefault(_leaflet); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + var protocolRegex = /^\/\//; + + var upgrade_protocol = function upgrade_protocol(urlTemplate) { + if (protocolRegex.test(urlTemplate)) { + if (window.location.protocol === "file:") { + // if in a local file, support http + // http should auto upgrade if necessary + urlTemplate = "http:" + urlTemplate; + } + } + + return urlTemplate; + }; + + var originalLTileLayerInitialize = _leaflet2["default"].TileLayer.prototype.initialize; + + _leaflet2["default"].TileLayer.prototype.initialize = function (urlTemplate, options) { + urlTemplate = upgrade_protocol(urlTemplate); + originalLTileLayerInitialize.call(this, urlTemplate, options); + }; + + var originalLTileLayerWMSInitialize = _leaflet2["default"].TileLayer.WMS.prototype.initialize; + + _leaflet2["default"].TileLayer.WMS.prototype.initialize = function (urlTemplate, options) { + urlTemplate = upgrade_protocol(urlTemplate); + originalLTileLayerWMSInitialize.call(this, urlTemplate, options); + }; + + + },{"./global/leaflet":10}],8:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true }); - - _shiny2["default"].onInputChange(id + "_center", { - lng: map.getCenter().lng, - lat: map.getCenter().lat + exports["default"] = global.HTMLWidgets; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{}],9:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true }); - - _shiny2["default"].onInputChange(id + "_zoom", map.getZoom()); -} - -function preventUnintendedZoomOnScroll(map) { - // Prevent unwanted scroll capturing. Similar in purpose to - // https://github.com/CliffCloud/Leaflet.Sleep but with a - // different set of heuristics. - // The basic idea is that when a mousewheel/DOMMouseScroll - // event is seen, we disable scroll wheel zooming until the - // user moves their mouse cursor or clicks on the map. This - // is slightly trickier than just listening for mousemove, - // because mousemove is fired when the page is scrolled, - // even if the user did not physically move the mouse. We - // handle this by examining the mousemove event's screenX - // and screenY properties; if they change, we know it's a - // "true" move. - // lastScreen can never be null, but its x and y can. - var lastScreen = { - x: null, - y: null - }; - (0, _jquery2["default"])(document).on("mousewheel DOMMouseScroll", "*", function (e) { - // Disable zooming (until the mouse moves or click) - map.scrollWheelZoom.disable(); // Any mousemove events at this screen position will be ignored. - - lastScreen = { - x: e.originalEvent.screenX, - y: e.originalEvent.screenY - }; + exports["default"] = global.jQuery; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{}],10:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports["default"] = global.L; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{}],11:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports["default"] = global.L.Proj; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{}],12:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true }); - (0, _jquery2["default"])(document).on("mousemove", "*", function (e) { - // Did the mouse really move? - if (map.options.scrollWheelZoom) { - if (lastScreen.x !== null && e.screenX !== lastScreen.x || e.screenY !== lastScreen.y) { - // It really moved. Enable zooming. + exports["default"] = global.Shiny; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{}],13:[function(require,module,exports){ + "use strict"; + + var _jquery = require("./global/jquery"); + + var _jquery2 = _interopRequireDefault(_jquery); + + var _leaflet = require("./global/leaflet"); + + var _leaflet2 = _interopRequireDefault(_leaflet); + + var _shiny = require("./global/shiny"); + + var _shiny2 = _interopRequireDefault(_shiny); + + var _htmlwidgets = require("./global/htmlwidgets"); + + var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); + + var _util = require("./util"); + + var _crs_utils = require("./crs_utils"); + + var _controlStore = require("./control-store"); + + var _controlStore2 = _interopRequireDefault(_controlStore); + + var _layerManager = require("./layer-manager"); + + var _layerManager2 = _interopRequireDefault(_layerManager); + + var _methods = require("./methods"); + + var _methods2 = _interopRequireDefault(_methods); + + require("./fixup-default-icon"); + + require("./fixup-default-tooltip"); + + require("./fixup-url-protocol"); + + var _dataframe = require("./dataframe"); + + var _dataframe2 = _interopRequireDefault(_dataframe); + + var _clusterLayerStore = require("./cluster-layer-store"); + + var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + window.LeafletWidget = {}; + window.LeafletWidget.utils = {}; + + var methods = window.LeafletWidget.methods = _jquery2["default"].extend({}, _methods2["default"]); + + window.LeafletWidget.DataFrame = _dataframe2["default"]; + window.LeafletWidget.ClusterLayerStore = _clusterLayerStore2["default"]; + window.LeafletWidget.utils.getCRS = _crs_utils.getCRS; // Send updated bounds back to app. Takes a leaflet event object as input. + + function updateBounds(map) { + var id = map.getContainer().id; + var bounds = map.getBounds(); + + _shiny2["default"].onInputChange(id + "_bounds", { + north: bounds.getNorthEast().lat, + east: bounds.getNorthEast().lng, + south: bounds.getSouthWest().lat, + west: bounds.getSouthWest().lng + }); + + _shiny2["default"].onInputChange(id + "_center", { + lng: map.getCenter().lng, + lat: map.getCenter().lat + }); + + _shiny2["default"].onInputChange(id + "_zoom", map.getZoom()); + } + + function preventUnintendedZoomOnScroll(map) { + // Prevent unwanted scroll capturing. Similar in purpose to + // https://github.com/CliffCloud/Leaflet.Sleep but with a + // different set of heuristics. + // The basic idea is that when a mousewheel/DOMMouseScroll + // event is seen, we disable scroll wheel zooming until the + // user moves their mouse cursor or clicks on the map. This + // is slightly trickier than just listening for mousemove, + // because mousemove is fired when the page is scrolled, + // even if the user did not physically move the mouse. We + // handle this by examining the mousemove event's screenX + // and screenY properties; if they change, we know it's a + // "true" move. + // lastScreen can never be null, but its x and y can. + var lastScreen = { + x: null, + y: null + }; + (0, _jquery2["default"])(document).on("mousewheel DOMMouseScroll", "*", function (e) { + // Disable zooming (until the mouse moves or click) + map.scrollWheelZoom.disable(); // Any mousemove events at this screen position will be ignored. + + lastScreen = { + x: e.originalEvent.screenX, + y: e.originalEvent.screenY + }; + }); + (0, _jquery2["default"])(document).on("mousemove", "*", function (e) { + // Did the mouse really move? + if (map.options.scrollWheelZoom) { + if (lastScreen.x !== null && e.screenX !== lastScreen.x || e.screenY !== lastScreen.y) { + // It really moved. Enable zooming. + map.scrollWheelZoom.enable(); + lastScreen = { + x: null, + y: null + }; + } + } + }); + (0, _jquery2["default"])(document).on("mousedown", ".leaflet", function (e) { + // Clicking always enables zooming. + if (map.options.scrollWheelZoom) { map.scrollWheelZoom.enable(); lastScreen = { x: null, y: null }; } - } - }); - (0, _jquery2["default"])(document).on("mousedown", ".leaflet", function (e) { - // Clicking always enables zooming. - if (map.options.scrollWheelZoom) { - map.scrollWheelZoom.enable(); - lastScreen = { - x: null, - y: null + }); + } + + _htmlwidgets2["default"].widget({ + name: "leaflet", + type: "output", + factory: function factory(el, width, height) { + var map = null; + return { + // we need to store our map in our returned object. + getMap: function getMap() { + return map; + }, + renderValue: function renderValue(data) { + // Create an appropriate CRS Object if specified + if (data && data.options && data.options.crs) { + data.options.crs = (0, _crs_utils.getCRS)(data.options.crs); + } // As per https://github.com/rstudio/leaflet/pull/294#discussion_r79584810 + + + if (map) { + map.remove(); + + map = function () { + return; + }(); // undefine map + + } + + if (data.options.mapFactory && typeof data.options.mapFactory === "function") { + map = data.options.mapFactory(el, data.options); + } else { + map = _leaflet2["default"].map(el, data.options); + } + + preventUnintendedZoomOnScroll(map); // Store some state in the map object + + map.leafletr = { + // Has the map ever rendered successfully? + hasRendered: false, + // Data to be rendered when resize is called with area != 0 + pendingRenderData: null + }; // Check if the map is rendered statically (no output binding) + + if (_htmlwidgets2["default"].shinyMode && /\bshiny-bound-output\b/.test(el.className)) { + map.id = el.id; // Store the map on the element so we can find it later by ID + + (0, _jquery2["default"])(el).data("leaflet-map", map); // When the map is clicked, send the coordinates back to the app + + map.on("click", function (e) { + _shiny2["default"].onInputChange(map.id + "_click", { + lat: e.latlng.lat, + lng: e.latlng.lng, + ".nonce": Math.random() // Force reactivity if lat/lng hasn't changed + + }); + }); + var groupTimerId = null; + map.on("moveend", function (e) { + updateBounds(e.target); + }).on("layeradd layerremove", function (e) { + // If the layer that's coming or going is a group we created, tell + // the server. + if (map.layerManager.getGroupNameFromLayerGroup(e.layer)) { + // But to avoid chattiness, coalesce events + if (groupTimerId) { + clearTimeout(groupTimerId); + groupTimerId = null; + } + + groupTimerId = setTimeout(function () { + groupTimerId = null; + + _shiny2["default"].onInputChange(map.id + "_groups", map.layerManager.getVisibleGroups()); + }, 100); + } + }); + } + + this.doRenderValue(data, map); + }, + doRenderValue: function doRenderValue(data, map) { + // Leaflet does not behave well when you set up a bunch of layers when + // the map is not visible (width/height == 0). Popups get misaligned + // relative to their owning markers, and the fitBounds calculations + // are off. Therefore we wait until the map is actually showing to + // render the value (we rely on the resize() callback being invoked + // at the appropriate time). + if (el.offsetWidth === 0 || el.offsetHeight === 0) { + map.leafletr.pendingRenderData = data; + return; + } + + map.leafletr.pendingRenderData = null; // Merge data options into defaults + + var options = _jquery2["default"].extend({ + zoomToLimits: "always" + }, data.options); + + if (!map.layerManager) { + map.controls = new _controlStore2["default"](map); + map.layerManager = new _layerManager2["default"](map); + } else { + map.controls.clear(); + map.layerManager.clear(); + } + + var explicitView = false; + + if (data.setView) { + explicitView = true; + map.setView.apply(map, data.setView); + } + + if (data.fitBounds) { + explicitView = true; + methods.fitBounds.apply(map, data.fitBounds); + } + + if (data.flyTo) { + if (!explicitView && !map.leafletr.hasRendered) { + // must be done to give a initial starting point + map.fitWorld(); + } + + explicitView = true; + map.flyTo.apply(map, data.flyTo); + } + + if (data.flyToBounds) { + if (!explicitView && !map.leafletr.hasRendered) { + // must be done to give a initial starting point + map.fitWorld(); + } + + explicitView = true; + methods.flyToBounds.apply(map, data.flyToBounds); + } + + if (data.options.center) { + explicitView = true; + } // Returns true if the zoomToLimits option says that the map should be + // zoomed to map elements. + + + function needsZoom() { + return options.zoomToLimits === "always" || options.zoomToLimits === "first" && !map.leafletr.hasRendered; + } + + if (!explicitView && needsZoom() && !map.getZoom()) { + if (data.limits && !_jquery2["default"].isEmptyObject(data.limits)) { + // Use the natural limits of what's being drawn on the map + // If the size of the bounding box is 0, leaflet gets all weird + var pad = 0.006; + + if (data.limits.lat[0] === data.limits.lat[1]) { + data.limits.lat[0] = data.limits.lat[0] - pad; + data.limits.lat[1] = data.limits.lat[1] + pad; + } + + if (data.limits.lng[0] === data.limits.lng[1]) { + data.limits.lng[0] = data.limits.lng[0] - pad; + data.limits.lng[1] = data.limits.lng[1] + pad; + } + + map.fitBounds([[data.limits.lat[0], data.limits.lng[0]], [data.limits.lat[1], data.limits.lng[1]]]); + } else { + map.fitWorld(); + } + } + + for (var i = 0; data.calls && i < data.calls.length; i++) { + var call = data.calls[i]; + if (methods[call.method]) methods[call.method].apply(map, call.args);else (0, _util.log)("Unknown method " + call.method); + } + + map.leafletr.hasRendered = true; + + if (_htmlwidgets2["default"].shinyMode) { + setTimeout(function () { + updateBounds(map); + }, 1); + } + }, + resize: function resize(width, height) { + if (map) { + map.invalidateSize(); + + if (map.leafletr.pendingRenderData) { + this.doRenderValue(map.leafletr.pendingRenderData, map); + } + } + } }; } }); -} - -_htmlwidgets2["default"].widget({ - name: "leaflet", - type: "output", - factory: function factory(el, width, height) { - var map = null; - return { - // we need to store our map in our returned object. - getMap: function getMap() { - return map; - }, - renderValue: function renderValue(data) { - // Create an appropriate CRS Object if specified - if (data && data.options && data.options.crs) { - data.options.crs = (0, _crs_utils.getCRS)(data.options.crs); - } // As per https://github.com/rstudio/leaflet/pull/294#discussion_r79584810 - - - if (map) { - map.remove(); - - map = function () { - return; - }(); // undefine map - + + if (_htmlwidgets2["default"].shinyMode) { + _shiny2["default"].addCustomMessageHandler("leaflet-calls", function (data) { + var id = data.id; + var el = document.getElementById(id); + var map = el ? (0, _jquery2["default"])(el).data("leaflet-map") : null; + + if (!map) { + (0, _util.log)("Couldn't find map with id " + id); + return; + } // If the map has not rendered, stash the proposed `leafletProxy()` calls + // in `pendingRenderData.calls` to be run on display via `doRenderValue()`. + // This is necessary if the map has not been rendered. + // If new pendingRenderData is set via a new `leaflet()`, the previous calls will be discarded. + + + if (!map.leafletr.hasRendered) { + map.leafletr.pendingRenderData.calls = map.leafletr.pendingRenderData.calls.concat(data.calls); + return; + } + + for (var i = 0; i < data.calls.length; i++) { + var call = data.calls[i]; + var args = call.args; + + for (var _i = 0; _i < call.evals.length; _i++) { + window.HTMLWidgets.evaluateStringMember(args, call.evals[_i]); } - - if (data.options.mapFactory && typeof data.options.mapFactory === "function") { - map = data.options.mapFactory(el, data.options); - } else { - map = _leaflet2["default"].map(el, data.options); + + if (call.dependencies) { + _shiny2["default"].renderDependencies(call.dependencies); } - - preventUnintendedZoomOnScroll(map); // Store some state in the map object - - map.leafletr = { - // Has the map ever rendered successfully? - hasRendered: false, - // Data to be rendered when resize is called with area != 0 - pendingRenderData: null - }; // Check if the map is rendered statically (no output binding) - - if (_htmlwidgets2["default"].shinyMode && /\bshiny-bound-output\b/.test(el.className)) { - map.id = el.id; // Store the map on the element so we can find it later by ID - - (0, _jquery2["default"])(el).data("leaflet-map", map); // When the map is clicked, send the coordinates back to the app - - map.on("click", function (e) { - _shiny2["default"].onInputChange(map.id + "_click", { - lat: e.latlng.lat, - lng: e.latlng.lng, - ".nonce": Math.random() // Force reactivity if lat/lng hasn't changed - - }); - }); - var groupTimerId = null; - map.on("moveend", function (e) { - updateBounds(e.target); - }).on("layeradd layerremove", function (e) { - // If the layer that's coming or going is a group we created, tell - // the server. - if (map.layerManager.getGroupNameFromLayerGroup(e.layer)) { - // But to avoid chattiness, coalesce events - if (groupTimerId) { - clearTimeout(groupTimerId); - groupTimerId = null; + + if (methods[call.method]) methods[call.method].apply(map, args);else (0, _util.log)("Unknown method " + call.method); + } + }); + } + + + },{"./cluster-layer-store":1,"./control-store":2,"./crs_utils":3,"./dataframe":4,"./fixup-default-icon":5,"./fixup-default-tooltip":6,"./fixup-url-protocol":7,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./layer-manager":14,"./methods":15,"./util":17}],14:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports["default"] = undefined; + + var _jquery = require("./global/jquery"); + + var _jquery2 = _interopRequireDefault(_jquery); + + var _leaflet = require("./global/leaflet"); + + var _leaflet2 = _interopRequireDefault(_leaflet); + + var _util = require("./util"); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + + function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + + var LayerManager = /*#__PURE__*/function () { + function LayerManager(map) { + _classCallCheck(this, LayerManager); + + this._map = map; // BEGIN layer indices + // {: {: layer}} + + this._byGroup = {}; // {: {: layer}} + + this._byCategory = {}; // {: layer} + + this._byLayerId = {}; // {: { + // "group": , + // "layerId": , + // "category": , + // "container": + // } + // } + + this._byStamp = {}; // {: {: [, , ...], ...}} + + this._byCrosstalkGroup = {}; // END layer indices + // {: L.layerGroup} + + this._categoryContainers = {}; // {: L.layerGroup} + + this._groupContainers = {}; + } + + _createClass(LayerManager, [{ + key: "addLayer", + value: function addLayer(layer, category, layerId, group, ctGroup, ctKey) { + var _this = this; + + // Was a group provided? + var hasId = typeof layerId === "string"; + var grouped = typeof group === "string"; + var stamp = _leaflet2["default"].Util.stamp(layer) + ""; // This will be the default layer group to add the layer to. + // We may overwrite this let before using it (i.e. if a group is assigned). + // This one liner creates the _categoryContainers[category] entry if it + // doesn't already exist. + + var container = this._categoryContainers[category] = this._categoryContainers[category] || _leaflet2["default"].layerGroup().addTo(this._map); + + var oldLayer = null; + + if (hasId) { + // First, remove any layer with the same category and layerId + var prefixedLayerId = this._layerIdKey(category, layerId); + + oldLayer = this._byLayerId[prefixedLayerId]; + + if (oldLayer) { + this._removeLayer(oldLayer); + } // Update layerId index + + + this._byLayerId[prefixedLayerId] = layer; + } // Update group index + + + if (grouped) { + this._byGroup[group] = this._byGroup[group] || {}; + this._byGroup[group][stamp] = layer; // Since a group is assigned, don't add the layer to the category's layer + // group; instead, use the group's layer group. + // This one liner creates the _groupContainers[group] entry if it doesn't + // already exist. + + container = this.getLayerGroup(group, true); + } // Update category index + + + this._byCategory[category] = this._byCategory[category] || {}; + this._byCategory[category][stamp] = layer; // Update stamp index + + var layerInfo = this._byStamp[stamp] = { + layer: layer, + group: group, + ctGroup: ctGroup, + ctKey: ctKey, + layerId: layerId, + category: category, + container: container, + hidden: false + }; // Update crosstalk group index + + if (ctGroup) { + if (layer.setStyle) { + // Need to save this info so we know what to set opacity to later + layer.options.origOpacity = typeof layer.options.opacity !== "undefined" ? layer.options.opacity : 0.5; + layer.options.origFillOpacity = typeof layer.options.fillOpacity !== "undefined" ? layer.options.fillOpacity : 0.2; + } + + var ctg = this._byCrosstalkGroup[ctGroup]; + + if (!ctg) { + ctg = this._byCrosstalkGroup[ctGroup] = {}; + var crosstalk = global.crosstalk; + + var handleFilter = function handleFilter(e) { + if (!e.value) { + var groupKeys = Object.keys(ctg); + + for (var i = 0; i < groupKeys.length; i++) { + var key = groupKeys[i]; + var _layerInfo = _this._byStamp[ctg[key]]; + + _this._setVisibility(_layerInfo, true); + } + } else { + var selectedKeys = {}; + + for (var _i = 0; _i < e.value.length; _i++) { + selectedKeys[e.value[_i]] = true; + } + + var _groupKeys = Object.keys(ctg); + + for (var _i2 = 0; _i2 < _groupKeys.length; _i2++) { + var _key = _groupKeys[_i2]; + var _layerInfo2 = _this._byStamp[ctg[_key]]; + + _this._setVisibility(_layerInfo2, selectedKeys[_groupKeys[_i2]]); + } } - - groupTimerId = setTimeout(function () { - groupTimerId = null; - - _shiny2["default"].onInputChange(map.id + "_groups", map.layerManager.getVisibleGroups()); - }, 100); - } + }; + + var filterHandle = new crosstalk.FilterHandle(ctGroup); + filterHandle.on("change", handleFilter); + + var handleSelection = function handleSelection(e) { + if (!e.value || !e.value.length) { + var groupKeys = Object.keys(ctg); + + for (var i = 0; i < groupKeys.length; i++) { + var key = groupKeys[i]; + var _layerInfo3 = _this._byStamp[ctg[key]]; + + _this._setOpacity(_layerInfo3, 1.0); + } + } else { + var selectedKeys = {}; + + for (var _i3 = 0; _i3 < e.value.length; _i3++) { + selectedKeys[e.value[_i3]] = true; + } + + var _groupKeys2 = Object.keys(ctg); + + for (var _i4 = 0; _i4 < _groupKeys2.length; _i4++) { + var _key2 = _groupKeys2[_i4]; + var _layerInfo4 = _this._byStamp[ctg[_key2]]; + + _this._setOpacity(_layerInfo4, selectedKeys[_groupKeys2[_i4]] ? 1.0 : 0.2); + } + } + }; + + var selHandle = new crosstalk.SelectionHandle(ctGroup); + selHandle.on("change", handleSelection); + setTimeout(function () { + handleFilter({ + value: filterHandle.filteredKeys + }); + handleSelection({ + value: selHandle.value + }); + }, 100); + } + + if (!ctg[ctKey]) ctg[ctKey] = []; + ctg[ctKey].push(stamp); + } // Add to container + + + if (!layerInfo.hidden) container.addLayer(layer); + return oldLayer; + } + }, { + key: "brush", + value: function brush(bounds, extraInfo) { + var _this2 = this; + + /* eslint-disable no-console */ + // For each Crosstalk group... + Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { + var ctg = _this2._byCrosstalkGroup[ctGroupName]; + var selection = []; // ...iterate over each Crosstalk key (each of which may have multiple + // layers)... + + Object.keys(ctg).forEach(function (ctKey) { + // ...and for each layer... + ctg[ctKey].forEach(function (stamp) { + var layerInfo = _this2._byStamp[stamp]; // ...if it's something with a point... + + if (layerInfo.layer.getLatLng) { + // ... and it's inside the selection bounds... + // TODO: Use pixel containment, not lat/lng containment + if (bounds.contains(layerInfo.layer.getLatLng())) { + // ...add the key to the selection. + selection.push(ctKey); + } + } + }); }); - } - - this.doRenderValue(data, map); - }, - doRenderValue: function doRenderValue(data, map) { - // Leaflet does not behave well when you set up a bunch of layers when - // the map is not visible (width/height == 0). Popups get misaligned - // relative to their owning markers, and the fitBounds calculations - // are off. Therefore we wait until the map is actually showing to - // render the value (we rely on the resize() callback being invoked - // at the appropriate time). - if (el.offsetWidth === 0 || el.offsetHeight === 0) { - map.leafletr.pendingRenderData = data; + new global.crosstalk.SelectionHandle(ctGroupName).set(selection, extraInfo); + }); + } + }, { + key: "unbrush", + value: function unbrush(extraInfo) { + Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { + new global.crosstalk.SelectionHandle(ctGroupName).clear(extraInfo); + }); + } + }, { + key: "_setVisibility", + value: function _setVisibility(layerInfo, visible) { + if (layerInfo.hidden ^ visible) { return; - } - - map.leafletr.pendingRenderData = null; // Merge data options into defaults - - var options = _jquery2["default"].extend({ - zoomToLimits: "always" - }, data.options); - - if (!map.layerManager) { - map.controls = new _controlStore2["default"](map); - map.layerManager = new _layerManager2["default"](map); + } else if (visible) { + layerInfo.container.addLayer(layerInfo.layer); + layerInfo.hidden = false; } else { - map.controls.clear(); - map.layerManager.clear(); - } - - var explicitView = false; - - if (data.setView) { - explicitView = true; - map.setView.apply(map, data.setView); + layerInfo.container.removeLayer(layerInfo.layer); + layerInfo.hidden = true; } - - if (data.fitBounds) { - explicitView = true; - methods.fitBounds.apply(map, data.fitBounds); + } + }, { + key: "_setOpacity", + value: function _setOpacity(layerInfo, opacity) { + if (layerInfo.layer.setOpacity) { + layerInfo.layer.setOpacity(opacity); + } else if (layerInfo.layer.setStyle) { + layerInfo.layer.setStyle({ + opacity: opacity * layerInfo.layer.options.origOpacity, + fillOpacity: opacity * layerInfo.layer.options.origFillOpacity + }); } - - if (data.flyTo) { - if (!explicitView && !map.leafletr.hasRendered) { - // must be done to give a initial starting point - map.fitWorld(); + } + }, { + key: "getLayer", + value: function getLayer(category, layerId) { + return this._byLayerId[this._layerIdKey(category, layerId)]; + } + }, { + key: "removeLayer", + value: function removeLayer(category, layerIds) { + var _this3 = this; + + // Find layer info + _jquery2["default"].each((0, _util.asArray)(layerIds), function (i, layerId) { + var layer = _this3._byLayerId[_this3._layerIdKey(category, layerId)]; + + if (layer) { + _this3._removeLayer(layer); } - - explicitView = true; - map.flyTo.apply(map, data.flyTo); + }); + } + }, { + key: "clearLayers", + value: function clearLayers(category) { + var _this4 = this; + + // Find all layers in _byCategory[category] + var catTable = this._byCategory[category]; + + if (!catTable) { + return false; + } // Remove all layers. Make copy of keys to avoid mutating the collection + // behind the iterator you're accessing. + + + var stamps = []; + + _jquery2["default"].each(catTable, function (k, v) { + stamps.push(k); + }); + + _jquery2["default"].each(stamps, function (i, stamp) { + _this4._removeLayer(stamp); + }); + } + }, { + key: "getLayerGroup", + value: function getLayerGroup(group, ensureExists) { + var g = this._groupContainers[group]; + + if (ensureExists && !g) { + this._byGroup[group] = this._byGroup[group] || {}; + g = this._groupContainers[group] = _leaflet2["default"].featureGroup(); + g.groupname = group; + g.addTo(this._map); } - - if (data.flyToBounds) { - if (!explicitView && !map.leafletr.hasRendered) { - // must be done to give a initial starting point - map.fitWorld(); + + return g; + } + }, { + key: "getGroupNameFromLayerGroup", + value: function getGroupNameFromLayerGroup(layerGroup) { + return layerGroup.groupname; + } + }, { + key: "getVisibleGroups", + value: function getVisibleGroups() { + var _this5 = this; + + var result = []; + + _jquery2["default"].each(this._groupContainers, function (k, v) { + if (_this5._map.hasLayer(v)) { + result.push(k); } - - explicitView = true; - methods.flyToBounds.apply(map, data.flyToBounds); - } - - if (data.options.center) { - explicitView = true; - } // Returns true if the zoomToLimits option says that the map should be - // zoomed to map elements. - - - function needsZoom() { - return options.zoomToLimits === "always" || options.zoomToLimits === "first" && !map.leafletr.hasRendered; + }); + + return result; + } + }, { + key: "getAllGroupNames", + value: function getAllGroupNames() { + var result = []; + + _jquery2["default"].each(this._groupContainers, function (k, v) { + result.push(k); + }); + + return result; + } + }, { + key: "clearGroup", + value: function clearGroup(group) { + var _this6 = this; + + // Find all layers in _byGroup[group] + var groupTable = this._byGroup[group]; + + if (!groupTable) { + return false; + } // Remove all layers. Make copy of keys to avoid mutating the collection + // behind the iterator you're accessing. + + + var stamps = []; + + _jquery2["default"].each(groupTable, function (k, v) { + stamps.push(k); + }); + + _jquery2["default"].each(stamps, function (i, stamp) { + _this6._removeLayer(stamp); + }); + } + }, { + key: "clear", + value: function clear() { + function clearLayerGroup(key, layerGroup) { + layerGroup.clearLayers(); + } // Clear all indices and layerGroups + + + this._byGroup = {}; + this._byCategory = {}; + this._byLayerId = {}; + this._byStamp = {}; + this._byCrosstalkGroup = {}; + + _jquery2["default"].each(this._categoryContainers, clearLayerGroup); + + this._categoryContainers = {}; + + _jquery2["default"].each(this._groupContainers, clearLayerGroup); + + this._groupContainers = {}; + } + }, { + key: "_removeLayer", + value: function _removeLayer(layer) { + var stamp; + + if (typeof layer === "string") { + stamp = layer; + } else { + stamp = _leaflet2["default"].Util.stamp(layer); } - - if (!explicitView && needsZoom() && !map.getZoom()) { - if (data.limits && !_jquery2["default"].isEmptyObject(data.limits)) { - // Use the natural limits of what's being drawn on the map - // If the size of the bounding box is 0, leaflet gets all weird - var pad = 0.006; - - if (data.limits.lat[0] === data.limits.lat[1]) { - data.limits.lat[0] = data.limits.lat[0] - pad; - data.limits.lat[1] = data.limits.lat[1] + pad; - } - - if (data.limits.lng[0] === data.limits.lng[1]) { - data.limits.lng[0] = data.limits.lng[0] - pad; - data.limits.lng[1] = data.limits.lng[1] + pad; - } - - map.fitBounds([[data.limits.lat[0], data.limits.lng[0]], [data.limits.lat[1], data.limits.lng[1]]]); - } else { - map.fitWorld(); - } + + var layerInfo = this._byStamp[stamp]; + + if (!layerInfo) { + return false; } - - for (var i = 0; data.calls && i < data.calls.length; i++) { - var call = data.calls[i]; - if (methods[call.method]) methods[call.method].apply(map, call.args);else (0, _util.log)("Unknown method " + call.method); + + layerInfo.container.removeLayer(stamp); + + if (typeof layerInfo.group === "string") { + delete this._byGroup[layerInfo.group][stamp]; } - - map.leafletr.hasRendered = true; - - if (_htmlwidgets2["default"].shinyMode) { - setTimeout(function () { - updateBounds(map); - }, 1); + + if (typeof layerInfo.layerId === "string") { + delete this._byLayerId[this._layerIdKey(layerInfo.category, layerInfo.layerId)]; } - }, - resize: function resize(width, height) { - if (map) { - map.invalidateSize(); - - if (map.leafletr.pendingRenderData) { - this.doRenderValue(map.leafletr.pendingRenderData, map); + + delete this._byCategory[layerInfo.category][stamp]; + delete this._byStamp[stamp]; + + if (layerInfo.ctGroup) { + var ctGroup = this._byCrosstalkGroup[layerInfo.ctGroup]; + var layersForKey = ctGroup[layerInfo.ctKey]; + var idx = layersForKey ? layersForKey.indexOf(stamp) : -1; + + if (idx >= 0) { + if (layersForKey.length === 1) { + delete ctGroup[layerInfo.ctKey]; + } else { + layersForKey.splice(idx, 1); + } } } } + }, { + key: "_layerIdKey", + value: function _layerIdKey(category, layerId) { + return category + "\n" + layerId; + } + }]); + + return LayerManager; + }(); + + exports["default"] = LayerManager; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{"./global/jquery":9,"./global/leaflet":10,"./util":17}],15:[function(require,module,exports){ + (function (global){(function (){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _jquery = require("./global/jquery"); + + var _jquery2 = _interopRequireDefault(_jquery); + + var _leaflet = require("./global/leaflet"); + + var _leaflet2 = _interopRequireDefault(_leaflet); + + var _shiny = require("./global/shiny"); + + var _shiny2 = _interopRequireDefault(_shiny); + + var _htmlwidgets = require("./global/htmlwidgets"); + + var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); + + var _util = require("./util"); + + var _crs_utils = require("./crs_utils"); + + var _dataframe = require("./dataframe"); + + var _dataframe2 = _interopRequireDefault(_dataframe); + + var _clusterLayerStore = require("./cluster-layer-store"); + + var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); + + var _mipmapper = require("./mipmapper"); + + var _mipmapper2 = _interopRequireDefault(_mipmapper); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + + var methods = {}; + exports["default"] = methods; + + function mouseHandler(mapId, layerId, group, eventName, extraInfo) { + return function (e) { + if (!_htmlwidgets2["default"].shinyMode) return; + var latLng = e.target.getLatLng ? e.target.getLatLng() : e.latlng; + + if (latLng) { + // retrieve only lat, lon values to remove prototype + // and extra parameters added by 3rd party modules + // these objects are for json serialization, not javascript + var latLngVal = _leaflet2["default"].latLng(latLng); // make sure it has consistent shape + + + latLng = { + lat: latLngVal.lat, + lng: latLngVal.lng + }; + } + + var eventInfo = _jquery2["default"].extend({ + id: layerId, + ".nonce": Math.random() // force reactivity + + }, group !== null ? { + group: group + } : null, latLng, extraInfo); + + _shiny2["default"].onInputChange(mapId + "_" + eventName, eventInfo); }; } -}); - -if (_htmlwidgets2["default"].shinyMode) { - _shiny2["default"].addCustomMessageHandler("leaflet-calls", function (data) { - var id = data.id; - var el = document.getElementById(id); - var map = el ? (0, _jquery2["default"])(el).data("leaflet-map") : null; - - if (!map) { - (0, _util.log)("Couldn't find map with id " + id); - return; - } // If the map has not rendered, stash the proposed `leafletProxy()` calls - // in `pendingRenderData.calls` to be run on display via `doRenderValue()`. - // This is necessary if the map has not been rendered. - // If new pendingRenderData is set via a new `leaflet()`, the previous calls will be discarded. - - - if (!map.leafletr.hasRendered) { - map.leafletr.pendingRenderData.calls = map.leafletr.pendingRenderData.calls.concat(data.calls); - return; + + methods.mouseHandler = mouseHandler; + + methods.clearGroup = function (group) { + var _this = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, v) { + _this.layerManager.clearGroup(v); + }); + }; + + methods.setView = function (center, zoom, options) { + this.setView(center, zoom, options); + }; + + methods.fitBounds = function (lat1, lng1, lat2, lng2, options) { + this.fitBounds([[lat1, lng1], [lat2, lng2]], options); + }; + + methods.flyTo = function (center, zoom, options) { + this.flyTo(center, zoom, options); + }; + + methods.flyToBounds = function (lat1, lng1, lat2, lng2, options) { + this.flyToBounds([[lat1, lng1], [lat2, lng2]], options); + }; + + methods.setMaxBounds = function (lat1, lng1, lat2, lng2) { + this.setMaxBounds([[lat1, lng1], [lat2, lng2]]); + }; + + methods.addPopups = function (lat, lng, popup, layerId, group, options) { + var _this2 = this; + + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("popup", popup).col("layerId", layerId).col("group", group).cbind(options); + + var _loop = function _loop(i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { + (function () { + var popup = _leaflet2["default"].popup(df.get(i)).setLatLng([df.get(i, "lat"), df.get(i, "lng")]).setContent(df.get(i, "popup")); + + var thisId = df.get(i, "layerId"); + var thisGroup = df.get(i, "group"); + this.layerManager.addLayer(popup, "popup", thisId, thisGroup); + }).call(_this2); + } + }; + + for (var i = 0; i < df.nrow(); i++) { + _loop(i); } - - for (var i = 0; i < data.calls.length; i++) { - var call = data.calls[i]; - var args = call.args; - - for (var _i = 0; _i < call.evals.length; _i++) { - window.HTMLWidgets.evaluateStringMember(args, call.evals[_i]); - } - - if (call.dependencies) { - _shiny2["default"].renderDependencies(call.dependencies); - } - - if (methods[call.method]) methods[call.method].apply(map, args);else (0, _util.log)("Unknown method " + call.method); + }; + + methods.removePopup = function (layerId) { + this.layerManager.removeLayer("popup", layerId); + }; + + methods.clearPopups = function () { + this.layerManager.clearLayers("popup"); + }; + + methods.addTiles = function (urlTemplate, layerId, group, options) { + this.layerManager.addLayer(_leaflet2["default"].tileLayer(urlTemplate, options), "tile", layerId, group); + }; + + methods.removeTiles = function (layerId) { + this.layerManager.removeLayer("tile", layerId); + }; + + methods.clearTiles = function () { + this.layerManager.clearLayers("tile"); + }; + + methods.addWMSTiles = function (baseUrl, layerId, group, options) { + if (options && options.crs) { + options.crs = (0, _crs_utils.getCRS)(options.crs); } - }); -} - - -},{"./cluster-layer-store":1,"./control-store":2,"./crs_utils":3,"./dataframe":4,"./fixup-default-icon":5,"./fixup-default-tooltip":6,"./fixup-url-protocol":7,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./layer-manager":14,"./methods":15,"./util":17}],14:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports["default"] = undefined; - -var _jquery = require("./global/jquery"); - -var _jquery2 = _interopRequireDefault(_jquery); - -var _leaflet = require("./global/leaflet"); - -var _leaflet2 = _interopRequireDefault(_leaflet); - -var _util = require("./util"); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - -function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - -var LayerManager = /*#__PURE__*/function () { - function LayerManager(map) { - _classCallCheck(this, LayerManager); - - this._map = map; // BEGIN layer indices - // {: {: layer}} - - this._byGroup = {}; // {: {: layer}} - - this._byCategory = {}; // {: layer} - - this._byLayerId = {}; // {: { - // "group": , - // "layerId": , - // "category": , - // "container": - // } - // } - - this._byStamp = {}; // {: {: [, , ...], ...}} - - this._byCrosstalkGroup = {}; // END layer indices - // {: L.layerGroup} - - this._categoryContainers = {}; // {: L.layerGroup} - - this._groupContainers = {}; + + this.layerManager.addLayer(_leaflet2["default"].tileLayer.wms(baseUrl, options), "tile", layerId, group); + }; // Given: + // {data: ["a", "b", "c"], index: [0, 1, 0, 2]} + // returns: + // ["a", "b", "a", "c"] + + + function unpackStrings(iconset) { + if (!iconset) { + return iconset; + } + + if (typeof iconset.index === "undefined") { + return iconset; + } + + iconset.data = (0, _util.asArray)(iconset.data); + iconset.index = (0, _util.asArray)(iconset.index); + return _jquery2["default"].map(iconset.index, function (e, i) { + return iconset.data[e]; + }); } - - _createClass(LayerManager, [{ - key: "addLayer", - value: function addLayer(layer, category, layerId, group, ctGroup, ctKey) { - var _this = this; - - // Was a group provided? - var hasId = typeof layerId === "string"; - var grouped = typeof group === "string"; - var stamp = _leaflet2["default"].Util.stamp(layer) + ""; // This will be the default layer group to add the layer to. - // We may overwrite this let before using it (i.e. if a group is assigned). - // This one liner creates the _categoryContainers[category] entry if it - // doesn't already exist. - - var container = this._categoryContainers[category] = this._categoryContainers[category] || _leaflet2["default"].layerGroup().addTo(this._map); - - var oldLayer = null; - - if (hasId) { - // First, remove any layer with the same category and layerId - var prefixedLayerId = this._layerIdKey(category, layerId); - - oldLayer = this._byLayerId[prefixedLayerId]; - - if (oldLayer) { - this._removeLayer(oldLayer); - } // Update layerId index - - - this._byLayerId[prefixedLayerId] = layer; - } // Update group index - - - if (grouped) { - this._byGroup[group] = this._byGroup[group] || {}; - this._byGroup[group][stamp] = layer; // Since a group is assigned, don't add the layer to the category's layer - // group; instead, use the group's layer group. - // This one liner creates the _groupContainers[group] entry if it doesn't - // already exist. - - container = this.getLayerGroup(group, true); - } // Update category index - - - this._byCategory[category] = this._byCategory[category] || {}; - this._byCategory[category][stamp] = layer; // Update stamp index - - var layerInfo = this._byStamp[stamp] = { - layer: layer, - group: group, - ctGroup: ctGroup, - ctKey: ctKey, - layerId: layerId, - category: category, - container: container, - hidden: false - }; // Update crosstalk group index - - if (ctGroup) { - if (layer.setStyle) { - // Need to save this info so we know what to set opacity to later - layer.options.origOpacity = typeof layer.options.opacity !== "undefined" ? layer.options.opacity : 0.5; - layer.options.origFillOpacity = typeof layer.options.fillOpacity !== "undefined" ? layer.options.fillOpacity : 0.2; + + function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { + function updateClusterMarkers(filtered, ctGroup, clusterGroup, ctKeyColumn) { + // For safety, check required parameters + if (!filtered) { + return; + } + + if (!clusterGroup) { + return; + } // Handle both null/undefined and empty array cases for filter values + + + let filterValues = filtered.value && Array.isArray(filtered.value) ? filtered.value.map(function (v) { + return String(v); + }) : []; + + let showAllMarkers = false; + if (filterValues.length === 0) { + showAllMarkers = true; + } + + + let options = Object.assign({}, clusterOptions); // Create a fresh cluster + + let newClusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(options); + + if (options.freezeAtZoom) { + let freezeAtZoom = options.freezeAtZoom; + newClusterGroup.freezeAtZoom(freezeAtZoom); + } + + newClusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](newClusterGroup); // If showing all markers, use all rows; otherwise filter + + let rowsToShow = []; + + for (let i = 0; i < df.nrow(); i++) { + // When showing all markers OR marker key is in filter values + let ctKey = String(df.get(i, ctKeyColumn) || ""); + let shouldShow = showAllMarkers || filterValues.includes(ctKey); + + if (shouldShow) { + rowsToShow.push(i); } - - var ctg = this._byCrosstalkGroup[ctGroup]; - - if (!ctg) { - ctg = this._byCrosstalkGroup[ctGroup] = {}; - var crosstalk = global.crosstalk; - - var handleFilter = function handleFilter(e) { - if (!e.value) { - var groupKeys = Object.keys(ctg); - - for (var i = 0; i < groupKeys.length; i++) { - var key = groupKeys[i]; - var _layerInfo = _this._byStamp[ctg[key]]; - - _this._setVisibility(_layerInfo, true); - } + } + + + for (let _i = 0, _rowsToShow = rowsToShow; _i < _rowsToShow.length; _i++) { + let _i2 = _rowsToShow[_i]; + + if (_jquery2["default"].isNumeric(df.get(_i2, "lat")) && _jquery2["default"].isNumeric(df.get(_i2, "lng"))) { + // Create a fresh marker - safer than trying to reuse existing ones + let marker = markerFunc(df, _i2); + let thisId = df.get(_i2, "layerId"); // Add popup content if in the dataframe + + let popup = df.get(_i2, "popup"); + let popupOptions = df.get(_i2, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); } else { - var selectedKeys = {}; - - for (var _i = 0; _i < e.value.length; _i++) { - selectedKeys[e.value[_i]] = true; - } - - var _groupKeys = Object.keys(ctg); - - for (var _i2 = 0; _i2 < _groupKeys.length; _i2++) { - var _key = _groupKeys[_i2]; - var _layerInfo2 = _this._byStamp[ctg[_key]]; - - _this._setVisibility(_layerInfo2, selectedKeys[_groupKeys[_i2]]); - } + marker.bindPopup(popup); } - }; - - var filterHandle = new crosstalk.FilterHandle(ctGroup); - filterHandle.on("change", handleFilter); - - var handleSelection = function handleSelection(e) { - if (!e.value || !e.value.length) { - var groupKeys = Object.keys(ctg); - - for (var i = 0; i < groupKeys.length; i++) { - var key = groupKeys[i]; - var _layerInfo3 = _this._byStamp[ctg[key]]; - - _this._setOpacity(_layerInfo3, 1.0); + } // Add label/tooltip if in the dataframe + + + let label = df.get(_i2, "label"); + let labelOptions = df.get(_i2, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); } } else { - var selectedKeys = {}; - - for (var _i3 = 0; _i3 < e.value.length; _i3++) { - selectedKeys[e.value[_i3]] = true; - } - - var _groupKeys2 = Object.keys(ctg); - - for (var _i4 = 0; _i4 < _groupKeys2.length; _i4++) { - var _key2 = _groupKeys2[_i4]; - var _layerInfo4 = _this._byStamp[ctg[_key2]]; - - _this._setOpacity(_layerInfo4, selectedKeys[_groupKeys2[_i4]] ? 1.0 : 0.2); - } + marker.bindTooltip(label); } - }; - - var selHandle = new crosstalk.SelectionHandle(ctGroup); - selHandle.on("change", handleSelection); - setTimeout(function () { - handleFilter({ - value: filterHandle.filteredKeys - }); - handleSelection({ - value: selHandle.value - }); - }, 100); + } // Add to cluster with appropriate ID + + + newClusterGroup.clusterLayerStore.add(marker, thisId); } - - if (!ctg[ctKey]) ctg[ctKey] = []; - ctg[ctKey].push(stamp); - } // Add to container - - - if (!layerInfo.hidden) container.addLayer(layer); - return oldLayer; + } // Replace the old cluster group with the new one + + + map.removeLayer(clusterGroup); + map.addLayer(newClusterGroup); // Update the layer manager reference to the new cluster group + + map.layerManager.removeLayer("cluster", clusterId); + map.layerManager.addLayer(newClusterGroup, "cluster", clusterId, group); + return newClusterGroup; } - }, { - key: "brush", - value: function brush(bounds, extraInfo) { - var _this2 = this; - - /* eslint-disable no-console */ - // For each Crosstalk group... - Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { - var ctg = _this2._byCrosstalkGroup[ctGroupName]; - var selection = []; // ...iterate over each Crosstalk key (each of which may have multiple - // layers)... - - Object.keys(ctg).forEach(function (ctKey) { - // ...and for each layer... - ctg[ctKey].forEach(function (stamp) { - var layerInfo = _this2._byStamp[stamp]; // ...if it's something with a point... - - if (layerInfo.layer.getLatLng) { - // ... and it's inside the selection bounds... - // TODO: Use pixel containment, not lat/lng containment - if (bounds.contains(layerInfo.layer.getLatLng())) { - // ...add the key to the selection. - selection.push(ctKey); + + (function () { + var _this3 = this; + + var clusterGroup = this.layerManager.getLayer("cluster", clusterId), + cluster = clusterOptions !== null; + + if (cluster && !clusterGroup) { + clusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(clusterOptions); + + if (clusterOptions.freezeAtZoom) { + var freezeAtZoom = clusterOptions.freezeAtZoom; + delete clusterOptions.freezeAtZoom; + clusterGroup.freezeAtZoom(freezeAtZoom); + } + + clusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](clusterGroup); + } + + var extraInfo = cluster ? { + clusterId: clusterId + } : {}; + + var _loop2 = function _loop2(i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { + (function () { + var marker = markerFunc(df, i); + var thisId = df.get(i, "layerId"); + var thisGroup = cluster ? null : df.get(i, "group"); + + if (cluster) { + clusterGroup.clusterLayerStore.add(marker, thisId); + } else { + this.layerManager.addLayer(marker, "marker", thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); + } + + var popup = df.get(i, "popup"); + var popupOptions = df.get(i, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); } } - }); - }); - new global.crosstalk.SelectionHandle(ctGroupName).set(selection, extraInfo); - }); - } - }, { - key: "unbrush", - value: function unbrush(extraInfo) { - Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { - new global.crosstalk.SelectionHandle(ctGroupName).clear(extraInfo); - }); - } - }, { - key: "_setVisibility", - value: function _setVisibility(layerInfo, visible) { - if (layerInfo.hidden ^ visible) { - return; - } else if (visible) { - layerInfo.container.addLayer(layerInfo.layer); - layerInfo.hidden = false; - } else { - layerInfo.container.removeLayer(layerInfo.layer); - layerInfo.hidden = true; + + var label = df.get(i, "label"); + var labelOptions = df.get(i, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); + } + } else { + marker.bindTooltip(label); + } + } + + marker.on("click", mouseHandler(this.id, thisId, thisGroup, "marker_click", extraInfo), this); + marker.on("mouseover", mouseHandler(this.id, thisId, thisGroup, "marker_mouseover", extraInfo), this); + marker.on("mouseout", mouseHandler(this.id, thisId, thisGroup, "marker_mouseout", extraInfo), this); + marker.on("dragend", mouseHandler(this.id, thisId, thisGroup, "marker_dragend", extraInfo), this); + }).call(_this3); + } + }; + + for (var i = 0; i < df.nrow(); i++) { + _loop2(i); } - } - }, { - key: "_setOpacity", - value: function _setOpacity(layerInfo, opacity) { - if (layerInfo.layer.setOpacity) { - layerInfo.layer.setOpacity(opacity); - } else if (layerInfo.layer.setStyle) { - layerInfo.layer.setStyle({ - opacity: opacity * layerInfo.layer.options.origOpacity, - fillOpacity: opacity * layerInfo.layer.options.origFillOpacity - }); + + if (cluster && df.get(0, "ctGroup")) { + let ctGroup = df.get(0, "ctGroup"); + let ctKeyColumn = "ctKey"; + + // Check if global crosstalk is available + if (window.crosstalk || global.crosstalk) { + let crosstalkObj = window.crosstalk || global.crosstalk; // Create a proper filter handle instead of using group().on() + + let filterHandle = new crosstalkObj.FilterHandle(ctGroup); // Subscribe to filter changes using the handle + + filterHandle.on("change", function (e) { + let newClusterGroup = updateClusterMarkers(e, ctGroup, clusterGroup, ctKeyColumn); + + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + }); // Initial setup + + + if (filterHandle.filteredKeys) { + let newClusterGroup = updateClusterMarkers({ + value: filterHandle.filteredKeys + }, ctGroup, clusterGroup, ctKeyColumn); + + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + } + } } - } - }, { - key: "getLayer", - value: function getLayer(category, layerId) { - return this._byLayerId[this._layerIdKey(category, layerId)]; - } - }, { - key: "removeLayer", - value: function removeLayer(category, layerIds) { - var _this3 = this; - - // Find layer info - _jquery2["default"].each((0, _util.asArray)(layerIds), function (i, layerId) { - var layer = _this3._byLayerId[_this3._layerIdKey(category, layerId)]; - - if (layer) { - _this3._removeLayer(layer); + + if (cluster) { + this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); + } + }).call(map); + } + + methods.addGenericMarkers = addMarkers; + + methods.addMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { + var icondf; + var getIcon; + + if (icon) { + // Unpack icons + icon.iconUrl = unpackStrings(icon.iconUrl); + icon.iconRetinaUrl = unpackStrings(icon.iconRetinaUrl); + icon.shadowUrl = unpackStrings(icon.shadowUrl); + icon.shadowRetinaUrl = unpackStrings(icon.shadowRetinaUrl); // This cbinds the icon URLs and any other icon options; they're all + // present on the icon object. + + icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. + + getIcon = function getIcon(i) { + var opts = icondf.get(i); + + if (!opts.iconUrl) { + return new _leaflet2["default"].Icon.Default(); + } // Composite options (like points or sizes) are passed from R with each + // individual component as its own option. We need to combine them now + // into their composite form. + + + if (opts.iconWidth) { + opts.iconSize = [opts.iconWidth, opts.iconHeight]; } - }); - } - }, { - key: "clearLayers", - value: function clearLayers(category) { - var _this4 = this; - - // Find all layers in _byCategory[category] - var catTable = this._byCategory[category]; - - if (!catTable) { - return false; - } // Remove all layers. Make copy of keys to avoid mutating the collection - // behind the iterator you're accessing. - - - var stamps = []; - - _jquery2["default"].each(catTable, function (k, v) { - stamps.push(k); - }); - - _jquery2["default"].each(stamps, function (i, stamp) { - _this4._removeLayer(stamp); - }); - } - }, { - key: "getLayerGroup", - value: function getLayerGroup(group, ensureExists) { - var g = this._groupContainers[group]; - - if (ensureExists && !g) { - this._byGroup[group] = this._byGroup[group] || {}; - g = this._groupContainers[group] = _leaflet2["default"].featureGroup(); - g.groupname = group; - g.addTo(this._map); - } - - return g; - } - }, { - key: "getGroupNameFromLayerGroup", - value: function getGroupNameFromLayerGroup(layerGroup) { - return layerGroup.groupname; - } - }, { - key: "getVisibleGroups", - value: function getVisibleGroups() { - var _this5 = this; - - var result = []; - - _jquery2["default"].each(this._groupContainers, function (k, v) { - if (_this5._map.hasLayer(v)) { - result.push(k); + + if (opts.shadowWidth) { + opts.shadowSize = [opts.shadowWidth, opts.shadowHeight]; } - }); - - return result; - } - }, { - key: "getAllGroupNames", - value: function getAllGroupNames() { - var result = []; - - _jquery2["default"].each(this._groupContainers, function (k, v) { - result.push(k); - }); - - return result; + + if (opts.iconAnchorX) { + opts.iconAnchor = [opts.iconAnchorX, opts.iconAnchorY]; + } + + if (opts.shadowAnchorX) { + opts.shadowAnchor = [opts.shadowAnchorX, opts.shadowAnchorY]; + } + + if (opts.popupAnchorX) { + opts.popupAnchor = [opts.popupAnchorX, opts.popupAnchorY]; + } + + return new _leaflet2["default"].Icon(opts); + }; } - }, { - key: "clearGroup", - value: function clearGroup(group) { - var _this6 = this; - - // Find all layers in _byGroup[group] - var groupTable = this._byGroup[group]; - - if (!groupTable) { - return false; - } // Remove all layers. Make copy of keys to avoid mutating the collection - // behind the iterator you're accessing. - - - var stamps = []; - - _jquery2["default"].each(groupTable, function (k, v) { - stamps.push(k); - }); - - _jquery2["default"].each(stamps, function (i, stamp) { - _this6._removeLayer(stamp); + + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); + if (icon) icondf.effectiveLength = df.nrow(); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + var options = df.get(i); + if (icon) options.icon = getIcon(i); + return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); }); } - }, { - key: "clear", - value: function clear() { - function clearLayerGroup(key, layerGroup) { - layerGroup.clearLayers(); - } // Clear all indices and layerGroups - - - this._byGroup = {}; - this._byCategory = {}; - this._byLayerId = {}; - this._byStamp = {}; - this._byCrosstalkGroup = {}; - - _jquery2["default"].each(this._categoryContainers, clearLayerGroup); - - this._categoryContainers = {}; - - _jquery2["default"].each(this._groupContainers, clearLayerGroup); - - this._groupContainers = {}; - } - }, { - key: "_removeLayer", - value: function _removeLayer(layer) { - var stamp; - - if (typeof layer === "string") { - stamp = layer; - } else { - stamp = _leaflet2["default"].Util.stamp(layer); - } - - var layerInfo = this._byStamp[stamp]; - - if (!layerInfo) { - return false; - } - - layerInfo.container.removeLayer(stamp); - - if (typeof layerInfo.group === "string") { - delete this._byGroup[layerInfo.group][stamp]; - } - - if (typeof layerInfo.layerId === "string") { - delete this._byLayerId[this._layerIdKey(layerInfo.category, layerInfo.layerId)]; - } - - delete this._byCategory[layerInfo.category][stamp]; - delete this._byStamp[stamp]; - - if (layerInfo.ctGroup) { - var ctGroup = this._byCrosstalkGroup[layerInfo.ctGroup]; - var layersForKey = ctGroup[layerInfo.ctKey]; - var idx = layersForKey ? layersForKey.indexOf(stamp) : -1; - - if (idx >= 0) { - if (layersForKey.length === 1) { - delete ctGroup[layerInfo.ctKey]; - } else { - layersForKey.splice(idx, 1); - } + }; + + methods.addAwesomeMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { + var icondf; + var getIcon; + + if (icon) { + // This cbinds the icon URLs and any other icon options; they're all + // present on the icon object. + icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. + + getIcon = function getIcon(i) { + var opts = icondf.get(i); + + if (!opts) { + return new _leaflet2["default"].AwesomeMarkers.icon(); } - } - } - }, { - key: "_layerIdKey", - value: function _layerIdKey(category, layerId) { - return category + "\n" + layerId; - } - }]); - - return LayerManager; -}(); - -exports["default"] = LayerManager; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./global/jquery":9,"./global/leaflet":10,"./util":17}],15:[function(require,module,exports){ -(function (global){(function (){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _jquery = require("./global/jquery"); - -var _jquery2 = _interopRequireDefault(_jquery); - -var _leaflet = require("./global/leaflet"); - -var _leaflet2 = _interopRequireDefault(_leaflet); - -var _shiny = require("./global/shiny"); - -var _shiny2 = _interopRequireDefault(_shiny); - -var _htmlwidgets = require("./global/htmlwidgets"); - -var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); - -var _util = require("./util"); - -var _crs_utils = require("./crs_utils"); - -var _dataframe = require("./dataframe"); - -var _dataframe2 = _interopRequireDefault(_dataframe); - -var _clusterLayerStore = require("./cluster-layer-store"); - -var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); - -var _mipmapper = require("./mipmapper"); - -var _mipmapper2 = _interopRequireDefault(_mipmapper); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - -var methods = {}; -exports["default"] = methods; - -function mouseHandler(mapId, layerId, group, eventName, extraInfo) { - return function (e) { - if (!_htmlwidgets2["default"].shinyMode) return; - var latLng = e.target.getLatLng ? e.target.getLatLng() : e.latlng; - - if (latLng) { - // retrieve only lat, lon values to remove prototype - // and extra parameters added by 3rd party modules - // these objects are for json serialization, not javascript - var latLngVal = _leaflet2["default"].latLng(latLng); // make sure it has consistent shape - - - latLng = { - lat: latLngVal.lat, - lng: latLngVal.lng + + if (opts.squareMarker) { + opts.className = "awesome-marker awesome-marker-square"; + } + + return new _leaflet2["default"].AwesomeMarkers.icon(opts); }; } - - var eventInfo = _jquery2["default"].extend({ - id: layerId, - ".nonce": Math.random() // force reactivity - - }, group !== null ? { - group: group - } : null, latLng, extraInfo); - - _shiny2["default"].onInputChange(mapId + "_" + eventName, eventInfo); - }; -} - -methods.mouseHandler = mouseHandler; - -methods.clearGroup = function (group) { - var _this = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, v) { - _this.layerManager.clearGroup(v); - }); -}; - -methods.setView = function (center, zoom, options) { - this.setView(center, zoom, options); -}; - -methods.fitBounds = function (lat1, lng1, lat2, lng2, options) { - this.fitBounds([[lat1, lng1], [lat2, lng2]], options); -}; - -methods.flyTo = function (center, zoom, options) { - this.flyTo(center, zoom, options); -}; - -methods.flyToBounds = function (lat1, lng1, lat2, lng2, options) { - this.flyToBounds([[lat1, lng1], [lat2, lng2]], options); -}; - -methods.setMaxBounds = function (lat1, lng1, lat2, lng2) { - this.setMaxBounds([[lat1, lng1], [lat2, lng2]]); -}; - -methods.addPopups = function (lat, lng, popup, layerId, group, options) { - var _this2 = this; - - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("popup", popup).col("layerId", layerId).col("group", group).cbind(options); - - var _loop = function _loop(i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { - (function () { - var popup = _leaflet2["default"].popup(df.get(i)).setLatLng([df.get(i, "lat"), df.get(i, "lng")]).setContent(df.get(i, "popup")); - - var thisId = df.get(i, "layerId"); - var thisGroup = df.get(i, "group"); - this.layerManager.addLayer(popup, "popup", thisId, thisGroup); - }).call(_this2); - } - }; - - for (var i = 0; i < df.nrow(); i++) { - _loop(i); - } -}; - -methods.removePopup = function (layerId) { - this.layerManager.removeLayer("popup", layerId); -}; - -methods.clearPopups = function () { - this.layerManager.clearLayers("popup"); -}; - -methods.addTiles = function (urlTemplate, layerId, group, options) { - this.layerManager.addLayer(_leaflet2["default"].tileLayer(urlTemplate, options), "tile", layerId, group); -}; - -methods.removeTiles = function (layerId) { - this.layerManager.removeLayer("tile", layerId); -}; - -methods.clearTiles = function () { - this.layerManager.clearLayers("tile"); -}; - -methods.addWMSTiles = function (baseUrl, layerId, group, options) { - if (options && options.crs) { - options.crs = (0, _crs_utils.getCRS)(options.crs); - } - - this.layerManager.addLayer(_leaflet2["default"].tileLayer.wms(baseUrl, options), "tile", layerId, group); -}; // Given: -// {data: ["a", "b", "c"], index: [0, 1, 0, 2]} -// returns: -// ["a", "b", "a", "c"] - - -function unpackStrings(iconset) { - if (!iconset) { - return iconset; - } - - if (typeof iconset.index === "undefined") { - return iconset; - } - - iconset.data = (0, _util.asArray)(iconset.data); - iconset.index = (0, _util.asArray)(iconset.index); - return _jquery2["default"].map(iconset.index, function (e, i) { - return iconset.data[e]; - }); -} - -function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { - (function () { - var _this3 = this; - - var clusterGroup = this.layerManager.getLayer("cluster", clusterId), - cluster = clusterOptions !== null; - - if (cluster && !clusterGroup) { - clusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(clusterOptions); - - if (clusterOptions.freezeAtZoom) { - var freezeAtZoom = clusterOptions.freezeAtZoom; - delete clusterOptions.freezeAtZoom; - clusterGroup.freezeAtZoom(freezeAtZoom); - } - - clusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](clusterGroup); + + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); + if (icon) icondf.effectiveLength = df.nrow(); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + var options = df.get(i); + if (icon) options.icon = getIcon(i); + return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }); } - - var extraInfo = cluster ? { - clusterId: clusterId - } : {}; - - var _loop2 = function _loop2(i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { - (function () { - var marker = markerFunc(df, i); + }; + + function addLayers(map, category, df, layerFunc) { + var _loop3 = function _loop3(i) { + (function () { + var layer = layerFunc(df, i); + + if (!_jquery2["default"].isEmptyObject(layer)) { var thisId = df.get(i, "layerId"); - var thisGroup = cluster ? null : df.get(i, "group"); - - if (cluster) { - clusterGroup.clusterLayerStore.add(marker, thisId); - } else { - this.layerManager.addLayer(marker, "marker", thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); - } - - var popup = df.get(i, "popup"); - var popupOptions = df.get(i, "popupOptions"); - - if (popup !== null) { - if (popupOptions !== null) { - marker.bindPopup(popup, popupOptions); - } else { - marker.bindPopup(popup); + var thisGroup = df.get(i, "group"); + this.layerManager.addLayer(layer, category, thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); + + if (layer.bindPopup) { + var popup = df.get(i, "popup"); + var popupOptions = df.get(i, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + layer.bindPopup(popup, popupOptions); + } else { + layer.bindPopup(popup); + } } } - - var label = df.get(i, "label"); - var labelOptions = df.get(i, "labelOptions"); - - if (label !== null) { - if (labelOptions !== null) { - if (labelOptions.permanent) { - marker.bindTooltip(label, labelOptions).openTooltip(); + + if (layer.bindTooltip) { + var label = df.get(i, "label"); + var labelOptions = df.get(i, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + layer.bindTooltip(label, labelOptions); } else { - marker.bindTooltip(label, labelOptions); + layer.bindTooltip(label); } - } else { - marker.bindTooltip(label); } } - - marker.on("click", mouseHandler(this.id, thisId, thisGroup, "marker_click", extraInfo), this); - marker.on("mouseover", mouseHandler(this.id, thisId, thisGroup, "marker_mouseover", extraInfo), this); - marker.on("mouseout", mouseHandler(this.id, thisId, thisGroup, "marker_mouseout", extraInfo), this); - marker.on("dragend", mouseHandler(this.id, thisId, thisGroup, "marker_dragend", extraInfo), this); - }).call(_this3); - } + + layer.on("click", mouseHandler(this.id, thisId, thisGroup, category + "_click"), this); + layer.on("mouseover", mouseHandler(this.id, thisId, thisGroup, category + "_mouseover"), this); + layer.on("mouseout", mouseHandler(this.id, thisId, thisGroup, category + "_mouseout"), this); + var highlightStyle = df.get(i, "highlightOptions"); + + if (!_jquery2["default"].isEmptyObject(highlightStyle)) { + var defaultStyle = {}; + + _jquery2["default"].each(highlightStyle, function (k, v) { + if (k != "bringToFront" && k != "sendToBack") { + if (df.get(i, k)) { + defaultStyle[k] = df.get(i, k); + } + } + }); + + layer.on("mouseover", function (e) { + this.setStyle(highlightStyle); + + if (highlightStyle.bringToFront) { + this.bringToFront(); + } + }); + layer.on("mouseout", function (e) { + this.setStyle(defaultStyle); + + if (highlightStyle.sendToBack) { + this.bringToBack(); + } + }); + } + } + }).call(map); }; - + for (var i = 0; i < df.nrow(); i++) { - _loop2(i); - } - - if (cluster) { - this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); + _loop3(i); } - }).call(map); -} - -methods.addGenericMarkers = addMarkers; - -methods.addMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { - var icondf; - var getIcon; - - if (icon) { - // Unpack icons - icon.iconUrl = unpackStrings(icon.iconUrl); - icon.iconRetinaUrl = unpackStrings(icon.iconRetinaUrl); - icon.shadowUrl = unpackStrings(icon.shadowUrl); - icon.shadowRetinaUrl = unpackStrings(icon.shadowRetinaUrl); // This cbinds the icon URLs and any other icon options; they're all - // present on the icon object. - - icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. - - getIcon = function getIcon(i) { - var opts = icondf.get(i); - - if (!opts.iconUrl) { - return new _leaflet2["default"].Icon.Default(); - } // Composite options (like points or sizes) are passed from R with each - // individual component as its own option. We need to combine them now - // into their composite form. - - - if (opts.iconWidth) { - opts.iconSize = [opts.iconWidth, opts.iconHeight]; - } - - if (opts.shadowWidth) { - opts.shadowSize = [opts.shadowWidth, opts.shadowHeight]; - } - - if (opts.iconAnchorX) { - opts.iconAnchor = [opts.iconAnchorX, opts.iconAnchorY]; - } - - if (opts.shadowAnchorX) { - opts.shadowAnchor = [opts.shadowAnchorX, opts.shadowAnchorY]; - } - - if (opts.popupAnchorX) { - opts.popupAnchor = [opts.popupAnchorX, opts.popupAnchorY]; - } - - return new _leaflet2["default"].Icon(opts); - }; - } - - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); - if (icon) icondf.effectiveLength = df.nrow(); - addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { - var options = df.get(i); - if (icon) options.icon = getIcon(i); - return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); - }); - } -}; - -methods.addAwesomeMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { - var icondf; - var getIcon; - - if (icon) { - // This cbinds the icon URLs and any other icon options; they're all - // present on the icon object. - icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. - - getIcon = function getIcon(i) { - var opts = icondf.get(i); - - if (!opts) { - return new _leaflet2["default"].AwesomeMarkers.icon(); - } - - if (opts.squareMarker) { - opts.className = "awesome-marker awesome-marker-square"; - } - - return new _leaflet2["default"].AwesomeMarkers.icon(opts); - }; - } - - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); - if (icon) icondf.effectiveLength = df.nrow(); - addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { - var options = df.get(i); - if (icon) options.icon = getIcon(i); - return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); - }); } -}; - -function addLayers(map, category, df, layerFunc) { - var _loop3 = function _loop3(i) { - (function () { - var layer = layerFunc(df, i); - - if (!_jquery2["default"].isEmptyObject(layer)) { - var thisId = df.get(i, "layerId"); - var thisGroup = df.get(i, "group"); - this.layerManager.addLayer(layer, category, thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); - - if (layer.bindPopup) { - var popup = df.get(i, "popup"); - var popupOptions = df.get(i, "popupOptions"); - - if (popup !== null) { - if (popupOptions !== null) { - layer.bindPopup(popup, popupOptions); - } else { - layer.bindPopup(popup); - } - } - } - - if (layer.bindTooltip) { - var label = df.get(i, "label"); - var labelOptions = df.get(i, "labelOptions"); - - if (label !== null) { - if (labelOptions !== null) { - layer.bindTooltip(label, labelOptions); - } else { - layer.bindTooltip(label); - } - } + + methods.addGenericLayers = addLayers; + + methods.addCircles = function (lat, lng, radius, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions, crosstalkOptions) { + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options).cbind(crosstalkOptions || {}); + addLayers(this, "shape", df, function (df, i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng")) && _jquery2["default"].isNumeric(df.get(i, "radius"))) { + return _leaflet2["default"].circle([df.get(i, "lat"), df.get(i, "lng")], df.get(i, "radius"), df.get(i)); + } else { + return null; } - - layer.on("click", mouseHandler(this.id, thisId, thisGroup, category + "_click"), this); - layer.on("mouseover", mouseHandler(this.id, thisId, thisGroup, category + "_mouseover"), this); - layer.on("mouseout", mouseHandler(this.id, thisId, thisGroup, category + "_mouseout"), this); - var highlightStyle = df.get(i, "highlightOptions"); - - if (!_jquery2["default"].isEmptyObject(highlightStyle)) { - var defaultStyle = {}; - - _jquery2["default"].each(highlightStyle, function (k, v) { - if (k != "bringToFront" && k != "sendToBack") { - if (df.get(i, k)) { - defaultStyle[k] = df.get(i, k); - } - } - }); - - layer.on("mouseover", function (e) { - this.setStyle(highlightStyle); - - if (highlightStyle.bringToFront) { - this.bringToFront(); - } - }); - layer.on("mouseout", function (e) { - this.setStyle(defaultStyle); - - if (highlightStyle.sendToBack) { - this.bringToBack(); - } - }); + }); + } + }; + + methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, clusterOptions, clusterId, popup, popupOptions, label, labelOptions, crosstalkOptions) { + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(crosstalkOptions || {}).cbind(options); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + return _leaflet2["default"].circleMarker([df.get(i, "lat"), df.get(i, "lng")], df.get(i)); + }); + } + }; + /* + * @param lat Array of arrays of latitude coordinates for polylines + * @param lng Array of arrays of longitude coordinates for polylines + */ + + + methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + if (polygons.length > 0) { + var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + var shapes = df.get(i, "shapes"); + shapes = shapes.map(function (shape) { + return _htmlwidgets2["default"].dataframeToD3(shape[0]); + }); + + if (shapes.length > 1) { + return _leaflet2["default"].polyline(shapes, df.get(i)); + } else { + return _leaflet2["default"].polyline(shapes[0], df.get(i)); } - } - }).call(map); + }); + } }; - - for (var i = 0; i < df.nrow(); i++) { - _loop3(i); - } -} - -methods.addGenericLayers = addLayers; - -methods.addCircles = function (lat, lng, radius, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions, crosstalkOptions) { - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options).cbind(crosstalkOptions || {}); + + methods.removeMarker = function (layerId) { + this.layerManager.removeLayer("marker", layerId); + }; + + methods.clearMarkers = function () { + this.layerManager.clearLayers("marker"); + }; + + methods.removeMarkerCluster = function (layerId) { + this.layerManager.removeLayer("cluster", layerId); + }; + + methods.removeMarkerFromCluster = function (layerId, clusterId) { + var cluster = this.layerManager.getLayer("cluster", clusterId); + if (!cluster) return; + cluster.clusterLayerStore.remove(layerId); + }; + + methods.clearMarkerClusters = function () { + this.layerManager.clearLayers("cluster"); + }; + + methods.removeShape = function (layerId) { + this.layerManager.removeLayer("shape", layerId); + }; + + methods.clearShapes = function () { + this.layerManager.clearLayers("shape"); + }; + + methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + var df = new _dataframe2["default"]().col("lat1", lat1).col("lng1", lng1).col("lat2", lat2).col("lng2", lng2).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); addLayers(this, "shape", df, function (df, i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng")) && _jquery2["default"].isNumeric(df.get(i, "radius"))) { - return _leaflet2["default"].circle([df.get(i, "lat"), df.get(i, "lng")], df.get(i, "radius"), df.get(i)); + if (_jquery2["default"].isNumeric(df.get(i, "lat1")) && _jquery2["default"].isNumeric(df.get(i, "lng1")) && _jquery2["default"].isNumeric(df.get(i, "lat2")) && _jquery2["default"].isNumeric(df.get(i, "lng2"))) { + return _leaflet2["default"].rectangle([[df.get(i, "lat1"), df.get(i, "lng1")], [df.get(i, "lat2"), df.get(i, "lng2")]], df.get(i)); } else { return null; } }); - } -}; - -methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, clusterOptions, clusterId, popup, popupOptions, label, labelOptions, crosstalkOptions) { - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(crosstalkOptions || {}).cbind(options); - addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { - return _leaflet2["default"].circleMarker([df.get(i, "lat"), df.get(i, "lng")], df.get(i)); - }); - } -}; -/* - * @param lat Array of arrays of latitude coordinates for polylines - * @param lng Array of arrays of longitude coordinates for polylines - */ - - -methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { - if (polygons.length > 0) { - var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); - addLayers(this, "shape", df, function (df, i) { - var shapes = df.get(i, "shapes"); - shapes = shapes.map(function (shape) { - return _htmlwidgets2["default"].dataframeToD3(shape[0]); + }; + /* + * @param lat Array of arrays of latitude coordinates for polygons + * @param lng Array of arrays of longitude coordinates for polygons + */ + + + methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + if (polygons.length > 0) { + var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + // This code used to use L.multiPolygon, but that caused + // double-click on a multipolygon to fail to zoom in on the + // map. Surprisingly, putting all the rings in a single + // polygon seems to still work; complicated multipolygons + // are still rendered correctly. + var shapes = df.get(i, "shapes").map(function (polygon) { + return polygon.map(_htmlwidgets2["default"].dataframeToD3); + }).reduce(function (acc, val) { + return acc.concat(val); + }, []); + return _leaflet2["default"].polygon(shapes, df.get(i)); }); - - if (shapes.length > 1) { - return _leaflet2["default"].polyline(shapes, df.get(i)); - } else { - return _leaflet2["default"].polyline(shapes[0], df.get(i)); + } + }; + + methods.addGeoJSON = function (data, layerId, group, style) { + // This time, self is actually needed because the callbacks below need + // to access both the inner and outer senses of "this" + var self = this; + + if (typeof data === "string") { + data = JSON.parse(data); + } + + var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); + + var gjlayer = _leaflet2["default"].geoJson(data, { + style: function style(feature) { + if (feature.style || feature.properties.style) { + return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); + } else { + return globalStyle; + } + }, + onEachFeature: function onEachFeature(feature, layer) { + var extraInfo = { + featureId: feature.id, + properties: feature.properties + }; + var popup = feature.properties ? feature.properties.popup : null; + if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); + layer.on("click", mouseHandler(self.id, layerId, group, "geojson_click", extraInfo), this); + layer.on("mouseover", mouseHandler(self.id, layerId, group, "geojson_mouseover", extraInfo), this); + layer.on("mouseout", mouseHandler(self.id, layerId, group, "geojson_mouseout", extraInfo), this); } }); - } -}; - -methods.removeMarker = function (layerId) { - this.layerManager.removeLayer("marker", layerId); -}; - -methods.clearMarkers = function () { - this.layerManager.clearLayers("marker"); -}; - -methods.removeMarkerCluster = function (layerId) { - this.layerManager.removeLayer("cluster", layerId); -}; - -methods.removeMarkerFromCluster = function (layerId, clusterId) { - var cluster = this.layerManager.getLayer("cluster", clusterId); - if (!cluster) return; - cluster.clusterLayerStore.remove(layerId); -}; - -methods.clearMarkerClusters = function () { - this.layerManager.clearLayers("cluster"); -}; - -methods.removeShape = function (layerId) { - this.layerManager.removeLayer("shape", layerId); -}; - -methods.clearShapes = function () { - this.layerManager.clearLayers("shape"); -}; - -methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { - var df = new _dataframe2["default"]().col("lat1", lat1).col("lng1", lng1).col("lat2", lat2).col("lng2", lng2).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); - addLayers(this, "shape", df, function (df, i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat1")) && _jquery2["default"].isNumeric(df.get(i, "lng1")) && _jquery2["default"].isNumeric(df.get(i, "lat2")) && _jquery2["default"].isNumeric(df.get(i, "lng2"))) { - return _leaflet2["default"].rectangle([[df.get(i, "lat1"), df.get(i, "lng1")], [df.get(i, "lat2"), df.get(i, "lng2")]], df.get(i)); - } else { - return null; + + this.layerManager.addLayer(gjlayer, "geojson", layerId, group); + }; + + methods.removeGeoJSON = function (layerId) { + this.layerManager.removeLayer("geojson", layerId); + }; + + methods.clearGeoJSON = function () { + this.layerManager.clearLayers("geojson"); + }; + + methods.addTopoJSON = function (data, layerId, group, style) { + // This time, self is actually needed because the callbacks below need + // to access both the inner and outer senses of "this" + var self = this; + + if (typeof data === "string") { + data = JSON.parse(data); } - }); -}; -/* - * @param lat Array of arrays of latitude coordinates for polygons - * @param lng Array of arrays of longitude coordinates for polygons - */ - - -methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { - if (polygons.length > 0) { - var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); - addLayers(this, "shape", df, function (df, i) { - // This code used to use L.multiPolygon, but that caused - // double-click on a multipolygon to fail to zoom in on the - // map. Surprisingly, putting all the rings in a single - // polygon seems to still work; complicated multipolygons - // are still rendered correctly. - var shapes = df.get(i, "shapes").map(function (polygon) { - return polygon.map(_htmlwidgets2["default"].dataframeToD3); - }).reduce(function (acc, val) { - return acc.concat(val); - }, []); - return _leaflet2["default"].polygon(shapes, df.get(i)); + + var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); + + var gjlayer = _leaflet2["default"].geoJson(null, { + style: function style(feature) { + if (feature.style || feature.properties.style) { + return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); + } else { + return globalStyle; + } + }, + onEachFeature: function onEachFeature(feature, layer) { + var extraInfo = { + featureId: feature.id, + properties: feature.properties + }; + var popup = feature.properties.popup; + if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); + layer.on("click", mouseHandler(self.id, layerId, group, "topojson_click", extraInfo), this); + layer.on("mouseover", mouseHandler(self.id, layerId, group, "topojson_mouseover", extraInfo), this); + layer.on("mouseout", mouseHandler(self.id, layerId, group, "topojson_mouseout", extraInfo), this); + } }); - } -}; - -methods.addGeoJSON = function (data, layerId, group, style) { - // This time, self is actually needed because the callbacks below need - // to access both the inner and outer senses of "this" - var self = this; - - if (typeof data === "string") { - data = JSON.parse(data); - } - - var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); - - var gjlayer = _leaflet2["default"].geoJson(data, { - style: function style(feature) { - if (feature.style || feature.properties.style) { - return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); - } else { - return globalStyle; + + global.omnivore.topojson.parse(data, null, gjlayer); + this.layerManager.addLayer(gjlayer, "topojson", layerId, group); + }; + + methods.removeTopoJSON = function (layerId) { + this.layerManager.removeLayer("topojson", layerId); + }; + + methods.clearTopoJSON = function () { + this.layerManager.clearLayers("topojson"); + }; + + methods.addControl = function (html, position, layerId, classes) { + function onAdd(map) { + var div = _leaflet2["default"].DomUtil.create("div", classes); + + if (typeof layerId !== "undefined" && layerId !== null) { + div.setAttribute("id", layerId); } - }, - onEachFeature: function onEachFeature(feature, layer) { - var extraInfo = { - featureId: feature.id, - properties: feature.properties - }; - var popup = feature.properties ? feature.properties.popup : null; - if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); - layer.on("click", mouseHandler(self.id, layerId, group, "geojson_click", extraInfo), this); - layer.on("mouseover", mouseHandler(self.id, layerId, group, "geojson_mouseover", extraInfo), this); - layer.on("mouseout", mouseHandler(self.id, layerId, group, "geojson_mouseout", extraInfo), this); - } - }); - - this.layerManager.addLayer(gjlayer, "geojson", layerId, group); -}; - -methods.removeGeoJSON = function (layerId) { - this.layerManager.removeLayer("geojson", layerId); -}; - -methods.clearGeoJSON = function () { - this.layerManager.clearLayers("geojson"); -}; - -methods.addTopoJSON = function (data, layerId, group, style) { - // This time, self is actually needed because the callbacks below need - // to access both the inner and outer senses of "this" - var self = this; - - if (typeof data === "string") { - data = JSON.parse(data); - } - - var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); - - var gjlayer = _leaflet2["default"].geoJson(null, { - style: function style(feature) { - if (feature.style || feature.properties.style) { - return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); + + this._div = div; // It's possible for window.Shiny to be true but Shiny.initializeInputs to + // not be, when a static leaflet widget is included as part of the shiny + // UI directly (not through leafletOutput or uiOutput). In this case we + // don't do the normal Shiny stuff as that will all happen when Shiny + // itself loads and binds the entire doc. + + if (window.Shiny && _shiny2["default"].initializeInputs) { + _shiny2["default"].renderHtml(html, this._div); + + _shiny2["default"].initializeInputs(this._div); + + _shiny2["default"].bindAll(this._div); } else { - return globalStyle; + this._div.innerHTML = html; } - }, - onEachFeature: function onEachFeature(feature, layer) { - var extraInfo = { - featureId: feature.id, - properties: feature.properties - }; - var popup = feature.properties.popup; - if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); - layer.on("click", mouseHandler(self.id, layerId, group, "topojson_click", extraInfo), this); - layer.on("mouseover", mouseHandler(self.id, layerId, group, "topojson_mouseover", extraInfo), this); - layer.on("mouseout", mouseHandler(self.id, layerId, group, "topojson_mouseout", extraInfo), this); - } - }); - - global.omnivore.topojson.parse(data, null, gjlayer); - this.layerManager.addLayer(gjlayer, "topojson", layerId, group); -}; - -methods.removeTopoJSON = function (layerId) { - this.layerManager.removeLayer("topojson", layerId); -}; - -methods.clearTopoJSON = function () { - this.layerManager.clearLayers("topojson"); -}; - -methods.addControl = function (html, position, layerId, classes) { - function onAdd(map) { - var div = _leaflet2["default"].DomUtil.create("div", classes); - - if (typeof layerId !== "undefined" && layerId !== null) { - div.setAttribute("id", layerId); - } - - this._div = div; // It's possible for window.Shiny to be true but Shiny.initializeInputs to - // not be, when a static leaflet widget is included as part of the shiny - // UI directly (not through leafletOutput or uiOutput). In this case we - // don't do the normal Shiny stuff as that will all happen when Shiny - // itself loads and binds the entire doc. - - if (window.Shiny && _shiny2["default"].initializeInputs) { - _shiny2["default"].renderHtml(html, this._div); - - _shiny2["default"].initializeInputs(this._div); - - _shiny2["default"].bindAll(this._div); - } else { - this._div.innerHTML = html; - } - - return this._div; - } - - function onRemove(map) { - if (window.Shiny && _shiny2["default"].unbindAll) { - _shiny2["default"].unbindAll(this._div); + + return this._div; } - } - - var Control = _leaflet2["default"].Control.extend({ - options: { - position: position - }, - onAdd: onAdd, - onRemove: onRemove - }); - - this.controls.add(new Control(), layerId, html); -}; - -methods.addCustomControl = function (control, layerId) { - this.controls.add(control, layerId); -}; - -methods.removeControl = function (layerId) { - this.controls.remove(layerId); -}; - -methods.getControl = function (layerId) { - this.controls.get(layerId); -}; - -methods.clearControls = function () { - this.controls.clear(); -}; - -methods.addLegend = function (options) { - var legend = _leaflet2["default"].control({ - position: options.position - }); - - var gradSpan; - - legend.onAdd = function (map) { - var div = _leaflet2["default"].DomUtil.create("div", options.className), - colors = options.colors, - labels = options.labels, - legendHTML = ""; - - if (options.type === "numeric") { - // # Formatting constants. - var singleBinHeight = 20; // The distance between tick marks, in px - - var vMargin = 8; // If 1st tick mark starts at top of gradient, how - // many extra px are needed for the top half of the - // 1st label? (ditto for last tick mark/label) - - var tickWidth = 4; // How wide should tick marks be, in px? - - var labelPadding = 6; // How much distance to reserve for tick mark? - // (Must be >= tickWidth) - // # Derived formatting parameters. - // What's the height of a single bin, in percentage (of gradient height)? - // It might not just be 1/(n-1), if the gradient extends past the tick - // marks (which can be the case for pretty cut points). - - var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); // Each bin is `singleBinHeight` high. How tall is the gradient? - - var totalHeight = 1 / singleBinPct * singleBinHeight + 1; // How far should the first tick be shifted down, relative to the top - // of the gradient? - - var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; - gradSpan = (0, _jquery2["default"])("").css({ - "background": "linear-gradient(" + colors + ")", - "opacity": options.opacity, - "height": totalHeight + "px", - "width": "18px", - "display": "block", - "margin-top": vMargin + "px" - }); - var leftDiv = (0, _jquery2["default"])("
").css("float", "left"), - rightDiv = (0, _jquery2["default"])("
").css("float", "left"); - leftDiv.append(gradSpan); - (0, _jquery2["default"])(div).append(leftDiv).append(rightDiv).append((0, _jquery2["default"])("
")); // Have to attach the div to the body at this early point, so that the - // svg text getComputedTextLength() actually works, below. - - document.body.appendChild(div); - var ns = "http://www.w3.org/2000/svg"; - var svg = document.createElementNS(ns, "svg"); - rightDiv.append(svg); - var g = document.createElementNS(ns, "g"); - (0, _jquery2["default"])(g).attr("transform", "translate(0, " + vMargin + ")"); - svg.appendChild(g); // max label width needed to set width of svg, and right-justify text - - var maxLblWidth = 0; // Create tick marks and labels - - _jquery2["default"].each(labels, function (i, label) { - var y = tickOffset + i * singleBinHeight + 0.5; - var thisLabel = document.createElementNS(ns, "text"); - (0, _jquery2["default"])(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); - g.appendChild(thisLabel); - maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); - var thisTick = document.createElementNS(ns, "line"); - (0, _jquery2["default"])(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); - g.appendChild(thisTick); - }); // Now that we know the max label width, we can right-justify - - - (0, _jquery2["default"])(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); // Final size for - - (0, _jquery2["default"])(svg).css({ - width: maxLblWidth + labelPadding + "px", - height: totalHeight + vMargin * 2 + "px" - }); - - if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { - (0, _jquery2["default"])(div).append("
" + options.na_label + "
"); + + function onRemove(map) { + if (window.Shiny && _shiny2["default"].unbindAll) { + _shiny2["default"].unbindAll(this._div); } - } else { - if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { - colors.push(options.na_color); - labels.push(options.na_label); + } + + var Control = _leaflet2["default"].Control.extend({ + options: { + position: position + }, + onAdd: onAdd, + onRemove: onRemove + }); + + this.controls.add(new Control(), layerId, html); + }; + + methods.addCustomControl = function (control, layerId) { + this.controls.add(control, layerId); + }; + + methods.removeControl = function (layerId) { + this.controls.remove(layerId); + }; + + methods.getControl = function (layerId) { + this.controls.get(layerId); + }; + + methods.clearControls = function () { + this.controls.clear(); + }; + + methods.addLegend = function (options) { + var legend = _leaflet2["default"].control({ + position: options.position + }); + + var gradSpan; + + legend.onAdd = function (map) { + var div = _leaflet2["default"].DomUtil.create("div", options.className), + colors = options.colors, + labels = options.labels, + legendHTML = ""; + + if (options.type === "numeric") { + // # Formatting constants. + var singleBinHeight = 20; // The distance between tick marks, in px + + var vMargin = 8; // If 1st tick mark starts at top of gradient, how + // many extra px are needed for the top half of the + // 1st label? (ditto for last tick mark/label) + + var tickWidth = 4; // How wide should tick marks be, in px? + + var labelPadding = 6; // How much distance to reserve for tick mark? + // (Must be >= tickWidth) + // # Derived formatting parameters. + // What's the height of a single bin, in percentage (of gradient height)? + // It might not just be 1/(n-1), if the gradient extends past the tick + // marks (which can be the case for pretty cut points). + + var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); // Each bin is `singleBinHeight` high. How tall is the gradient? + + var totalHeight = 1 / singleBinPct * singleBinHeight + 1; // How far should the first tick be shifted down, relative to the top + // of the gradient? + + var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; + gradSpan = (0, _jquery2["default"])("").css({ + "background": "linear-gradient(" + colors + ")", + "opacity": options.opacity, + "height": totalHeight + "px", + "width": "18px", + "display": "block", + "margin-top": vMargin + "px" + }); + var leftDiv = (0, _jquery2["default"])("
").css("float", "left"), + rightDiv = (0, _jquery2["default"])("
").css("float", "left"); + leftDiv.append(gradSpan); + (0, _jquery2["default"])(div).append(leftDiv).append(rightDiv).append((0, _jquery2["default"])("
")); // Have to attach the div to the body at this early point, so that the + // svg text getComputedTextLength() actually works, below. + + document.body.appendChild(div); + var ns = "http://www.w3.org/2000/svg"; + var svg = document.createElementNS(ns, "svg"); + rightDiv.append(svg); + var g = document.createElementNS(ns, "g"); + (0, _jquery2["default"])(g).attr("transform", "translate(0, " + vMargin + ")"); + svg.appendChild(g); // max label width needed to set width of svg, and right-justify text + + var maxLblWidth = 0; // Create tick marks and labels + + _jquery2["default"].each(labels, function (i, label) { + var y = tickOffset + i * singleBinHeight + 0.5; + var thisLabel = document.createElementNS(ns, "text"); + (0, _jquery2["default"])(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); + g.appendChild(thisLabel); + maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); + var thisTick = document.createElementNS(ns, "line"); + (0, _jquery2["default"])(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); + g.appendChild(thisTick); + }); // Now that we know the max label width, we can right-justify + + + (0, _jquery2["default"])(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); // Final size for + + (0, _jquery2["default"])(svg).css({ + width: maxLblWidth + labelPadding + "px", + height: totalHeight + vMargin * 2 + "px" + }); + + if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { + (0, _jquery2["default"])(div).append("
" + options.na_label + "
"); + } + } else { + if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { + colors.push(options.na_color); + labels.push(options.na_label); + } + + for (var i = 0; i < colors.length; i++) { + legendHTML += " " + labels[i] + "
"; + } + + div.innerHTML = legendHTML; } - - for (var i = 0; i < colors.length; i++) { - legendHTML += " " + labels[i] + "
"; + + if (options.title) (0, _jquery2["default"])(div).prepend("
" + options.title + "
"); + return div; + }; + + if (options.group) { + // Auto generate a layerID if not provided + if (!options.layerId) { + options.layerId = _leaflet2["default"].Util.stamp(legend); } - - div.innerHTML = legendHTML; + + var map = this; + map.on("overlayadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); + } + }); + map.on("overlayremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); + } + }); + map.on("groupadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); + } + }); + map.on("groupremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); + } + }); } - - if (options.title) (0, _jquery2["default"])(div).prepend("
" + options.title + "
"); - return div; + + this.controls.add(legend, options.layerId); }; - - if (options.group) { - // Auto generate a layerID if not provided - if (!options.layerId) { - options.layerId = _leaflet2["default"].Util.stamp(legend); - } - - var map = this; - map.on("overlayadd", function (e) { - if (e.name === options.group) { - map.controls.add(legend, options.layerId); + + methods.addLayersControl = function (baseGroups, overlayGroups, options) { + var _this4 = this; + + // Only allow one layers control at a time + methods.removeLayersControl.call(this); + var firstLayer = true; + var base = {}; + + _jquery2["default"].each((0, _util.asArray)(baseGroups), function (i, g) { + var layer = _this4.layerManager.getLayerGroup(g, true); + + if (layer) { + base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one + + if (_this4.hasLayer(layer)) { + if (firstLayer) { + firstLayer = false; + } else { + _this4.removeLayer(layer); + } + } } }); - map.on("overlayremove", function (e) { - if (e.name === options.group) { - map.controls.remove(options.layerId); + + var overlay = {}; + + _jquery2["default"].each((0, _util.asArray)(overlayGroups), function (i, g) { + var layer = _this4.layerManager.getLayerGroup(g, true); + + if (layer) { + overlay[g] = layer; } }); - map.on("groupadd", function (e) { - if (e.name === options.group) { - map.controls.add(legend, options.layerId); + + this.currentLayersControl = _leaflet2["default"].control.layers(base, overlay, options); + this.addControl(this.currentLayersControl); + }; + + methods.removeLayersControl = function () { + if (this.currentLayersControl) { + this.removeControl(this.currentLayersControl); + this.currentLayersControl = null; + } + }; + + methods.addScaleBar = function (options) { + // Only allow one scale bar at a time + methods.removeScaleBar.call(this); + + var scaleBar = _leaflet2["default"].control.scale(options).addTo(this); + + this.currentScaleBar = scaleBar; + }; + + methods.removeScaleBar = function () { + if (this.currentScaleBar) { + this.currentScaleBar.remove(); + this.currentScaleBar = null; + } + }; + + methods.hideGroup = function (group) { + var _this5 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this5.layerManager.getLayerGroup(g, true); + + if (layer) { + _this5.removeLayer(layer); } }); - map.on("groupremove", function (e) { - if (e.name === options.group) { - map.controls.remove(options.layerId); + }; + + methods.showGroup = function (group) { + var _this6 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this6.layerManager.getLayerGroup(g, true); + + if (layer) { + _this6.addLayer(layer); } }); - } - - this.controls.add(legend, options.layerId); -}; - -methods.addLayersControl = function (baseGroups, overlayGroups, options) { - var _this4 = this; - - // Only allow one layers control at a time - methods.removeLayersControl.call(this); - var firstLayer = true; - var base = {}; - - _jquery2["default"].each((0, _util.asArray)(baseGroups), function (i, g) { - var layer = _this4.layerManager.getLayerGroup(g, true); - - if (layer) { - base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one - - if (_this4.hasLayer(layer)) { - if (firstLayer) { - firstLayer = false; + }; + + function setupShowHideGroupsOnZoom(map) { + if (map.leafletr._hasInitializedShowHideGroups) { + return; + } + + map.leafletr._hasInitializedShowHideGroups = true; + + function setVisibility(layer, visible, group) { + if (visible !== map.hasLayer(layer)) { + if (visible) { + map.addLayer(layer); + map.fire("groupadd", { + "name": group, + "layer": layer + }); } else { - _this4.removeLayer(layer); + map.removeLayer(layer); + map.fire("groupremove", { + "name": group, + "layer": layer + }); } } } - }); - - var overlay = {}; - - _jquery2["default"].each((0, _util.asArray)(overlayGroups), function (i, g) { - var layer = _this4.layerManager.getLayerGroup(g, true); - - if (layer) { - overlay[g] = layer; + + function showHideGroupsOnZoom() { + if (!map.layerManager) return; + var zoom = map.getZoom(); + map.layerManager.getAllGroupNames().forEach(function (group) { + var layer = map.layerManager.getLayerGroup(group, false); + + if (layer && typeof layer.zoomLevels !== "undefined") { + setVisibility(layer, layer.zoomLevels === true || layer.zoomLevels.indexOf(zoom) >= 0, group); + } + }); } - }); - - this.currentLayersControl = _leaflet2["default"].control.layers(base, overlay, options); - this.addControl(this.currentLayersControl); -}; - -methods.removeLayersControl = function () { - if (this.currentLayersControl) { - this.removeControl(this.currentLayersControl); - this.currentLayersControl = null; + + map.showHideGroupsOnZoom = showHideGroupsOnZoom; + map.on("zoomend", showHideGroupsOnZoom); } -}; - -methods.addScaleBar = function (options) { - // Only allow one scale bar at a time - methods.removeScaleBar.call(this); - - var scaleBar = _leaflet2["default"].control.scale(options).addTo(this); - - this.currentScaleBar = scaleBar; -}; - -methods.removeScaleBar = function () { - if (this.currentScaleBar) { - this.currentScaleBar.remove(); - this.currentScaleBar = null; - } -}; - -methods.hideGroup = function (group) { - var _this5 = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { - var layer = _this5.layerManager.getLayerGroup(g, true); - - if (layer) { - _this5.removeLayer(layer); - } - }); -}; - -methods.showGroup = function (group) { - var _this6 = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { - var layer = _this6.layerManager.getLayerGroup(g, true); - - if (layer) { - _this6.addLayer(layer); + + methods.setGroupOptions = function (group, options) { + var _this7 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels + + + if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) { + layer.zoomLevels = (0, _util.asArray)(options.zoomLevels); + } + }); + + setupShowHideGroupsOnZoom(this); + this.showHideGroupsOnZoom(); + }; + + methods.addRasterImage = function (uri, bounds, layerId, group, options) { + // uri is a data URI containing an image. We want to paint this image as a + // layer at (top-left) bounds[0] to (bottom-right) bounds[1]. + // We can't simply use ImageOverlay, as it uses bilinear scaling which looks + // awful as you zoom in (and sometimes shifts positions or disappears). + // Instead, we'll use a TileLayer.Canvas to draw pieces of the image. + // First, some helper functions. + // degree2tile converts latitude, longitude, and zoom to x and y tile + // numbers. The tile numbers returned can be non-integral, as there's no + // reason to expect that the lat/lng inputs are exactly on the border of two + // tiles. + // + // We'll use this to convert the bounds we got from the server, into coords + // in tile-space at a given zoom level. Note that once we do the conversion, + // we don't to do any more trigonometry to convert between pixel coordinates + // and tile coordinates; the source image pixel coords, destination canvas + // pixel coords, and tile coords all can be scaled linearly. + function degree2tile(lat, lng, zoom) { + // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames + var latRad = lat * Math.PI / 180; + var n = Math.pow(2, zoom); + var x = (lng + 180) / 360 * n; + var y = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n; + return { + x: x, + y: y + }; + } // Given a range [from,to) and either one or two numbers, returns true if + // there is any overlap between [x,x1) and the range--or if x1 is omitted, + // then returns true if x is within [from,to). + + + function overlap(from, to, x, + /* optional */ + x1) { + if (arguments.length == 3) x1 = x; + return x < to && x1 >= from; } - }); -}; - -function setupShowHideGroupsOnZoom(map) { - if (map.leafletr._hasInitializedShowHideGroups) { - return; - } - - map.leafletr._hasInitializedShowHideGroups = true; - - function setVisibility(layer, visible, group) { - if (visible !== map.hasLayer(layer)) { - if (visible) { - map.addLayer(layer); - map.fire("groupadd", { - "name": group, - "layer": layer - }); + + function getCanvasSmoothingProperty(ctx) { + var candidates = ["imageSmoothingEnabled", "mozImageSmoothingEnabled", "webkitImageSmoothingEnabled", "msImageSmoothingEnabled"]; + + for (var i = 0; i < candidates.length; i++) { + if (typeof ctx[candidates[i]] !== "undefined") { + return candidates[i]; + } + } + + return null; + } // Our general strategy is to: + // 1. Load the data URI in an Image() object, so we can get its pixel + // dimensions and the underlying image data. (We could have done this + // by not encoding as PNG at all but just send an array of RGBA values + // from the server, but that would inflate the JSON too much.) + // 2. Create a hidden canvas that we use just to extract the image data + // from the Image (using Context2D.getImageData()). + // 3. Create a TileLayer.Canvas and add it to the map. + // We want to synchronously create and attach the TileLayer.Canvas (so an + // immediate call to clearRasters() will be respected, for example), but + // Image loads its data asynchronously. Fortunately we can resolve this + // by putting TileLayer.Canvas into async mode, which will let us create + // and attach the layer but have it wait until the image is loaded before + // it actually draws anything. + // These are the variables that we will populate once the image is loaded. + + + var imgData = null; // 1d row-major array, four [0-255] integers per pixel + + var imgDataMipMapper = null; + var w = null; // image width in pixels + + var h = null; // image height in pixels + // We'll use this array to store callbacks that need to be invoked once + // imgData, w, and h have been resolved. + + var imgDataCallbacks = []; // Consumers of imgData, w, and h can call this to be notified when data + // is available. + + function getImageData(callback) { + if (imgData != null) { + // Must not invoke the callback immediately; it's too confusing and + // fragile to have a function invoke the callback *either* immediately + // or in the future. Better to be consistent here. + setTimeout(function () { + callback(imgData, w, h, imgDataMipMapper); + }, 0); } else { - map.removeLayer(layer); - map.fire("groupremove", { - "name": group, - "layer": layer - }); + imgDataCallbacks.push(callback); } } - } - - function showHideGroupsOnZoom() { - if (!map.layerManager) return; - var zoom = map.getZoom(); - map.layerManager.getAllGroupNames().forEach(function (group) { - var layer = map.layerManager.getLayerGroup(group, false); - - if (layer && typeof layer.zoomLevels !== "undefined") { - setVisibility(layer, layer.zoomLevels === true || layer.zoomLevels.indexOf(zoom) >= 0, group); + + var img = new Image(); + + img.onload = function () { + // Save size + w = img.width; + h = img.height; // Create a dummy canvas to extract the image data + + var imgDataCanvas = document.createElement("canvas"); + imgDataCanvas.width = w; + imgDataCanvas.height = h; + imgDataCanvas.style.display = "none"; + document.body.appendChild(imgDataCanvas); + var imgDataCtx = imgDataCanvas.getContext("2d"); + imgDataCtx.drawImage(img, 0, 0); // Save the image data. + + imgData = imgDataCtx.getImageData(0, 0, w, h).data; + imgDataMipMapper = new _mipmapper2["default"](img); // Done with the canvas, remove it from the page so it can be gc'd. + + document.body.removeChild(imgDataCanvas); // Alert any getImageData callers who are waiting. + + for (var i = 0; i < imgDataCallbacks.length; i++) { + imgDataCallbacks[i](imgData, w, h, imgDataMipMapper); } - }); - } - - map.showHideGroupsOnZoom = showHideGroupsOnZoom; - map.on("zoomend", showHideGroupsOnZoom); -} - -methods.setGroupOptions = function (group, options) { - var _this7 = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { - var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels - - - if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) { - layer.zoomLevels = (0, _util.asArray)(options.zoomLevels); - } - }); - - setupShowHideGroupsOnZoom(this); - this.showHideGroupsOnZoom(); -}; - -methods.addRasterImage = function (uri, bounds, layerId, group, options) { - // uri is a data URI containing an image. We want to paint this image as a - // layer at (top-left) bounds[0] to (bottom-right) bounds[1]. - // We can't simply use ImageOverlay, as it uses bilinear scaling which looks - // awful as you zoom in (and sometimes shifts positions or disappears). - // Instead, we'll use a TileLayer.Canvas to draw pieces of the image. - // First, some helper functions. - // degree2tile converts latitude, longitude, and zoom to x and y tile - // numbers. The tile numbers returned can be non-integral, as there's no - // reason to expect that the lat/lng inputs are exactly on the border of two - // tiles. - // - // We'll use this to convert the bounds we got from the server, into coords - // in tile-space at a given zoom level. Note that once we do the conversion, - // we don't to do any more trigonometry to convert between pixel coordinates - // and tile coordinates; the source image pixel coords, destination canvas - // pixel coords, and tile coords all can be scaled linearly. - function degree2tile(lat, lng, zoom) { - // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames - var latRad = lat * Math.PI / 180; - var n = Math.pow(2, zoom); - var x = (lng + 180) / 360 * n; - var y = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n; - return { - x: x, - y: y + + imgDataCallbacks = []; }; - } // Given a range [from,to) and either one or two numbers, returns true if - // there is any overlap between [x,x1) and the range--or if x1 is omitted, - // then returns true if x is within [from,to). - - - function overlap(from, to, x, - /* optional */ - x1) { - if (arguments.length == 3) x1 = x; - return x < to && x1 >= from; - } - - function getCanvasSmoothingProperty(ctx) { - var candidates = ["imageSmoothingEnabled", "mozImageSmoothingEnabled", "webkitImageSmoothingEnabled", "msImageSmoothingEnabled"]; - - for (var i = 0; i < candidates.length; i++) { - if (typeof ctx[candidates[i]] !== "undefined") { - return candidates[i]; - } - } - - return null; - } // Our general strategy is to: - // 1. Load the data URI in an Image() object, so we can get its pixel - // dimensions and the underlying image data. (We could have done this - // by not encoding as PNG at all but just send an array of RGBA values - // from the server, but that would inflate the JSON too much.) - // 2. Create a hidden canvas that we use just to extract the image data - // from the Image (using Context2D.getImageData()). - // 3. Create a TileLayer.Canvas and add it to the map. - // We want to synchronously create and attach the TileLayer.Canvas (so an - // immediate call to clearRasters() will be respected, for example), but - // Image loads its data asynchronously. Fortunately we can resolve this - // by putting TileLayer.Canvas into async mode, which will let us create - // and attach the layer but have it wait until the image is loaded before - // it actually draws anything. - // These are the variables that we will populate once the image is loaded. - - - var imgData = null; // 1d row-major array, four [0-255] integers per pixel - - var imgDataMipMapper = null; - var w = null; // image width in pixels - - var h = null; // image height in pixels - // We'll use this array to store callbacks that need to be invoked once - // imgData, w, and h have been resolved. - - var imgDataCallbacks = []; // Consumers of imgData, w, and h can call this to be notified when data - // is available. - - function getImageData(callback) { - if (imgData != null) { - // Must not invoke the callback immediately; it's too confusing and - // fragile to have a function invoke the callback *either* immediately - // or in the future. Better to be consistent here. - setTimeout(function () { - callback(imgData, w, h, imgDataMipMapper); - }, 0); - } else { - imgDataCallbacks.push(callback); - } - } - - var img = new Image(); - - img.onload = function () { - // Save size - w = img.width; - h = img.height; // Create a dummy canvas to extract the image data - - var imgDataCanvas = document.createElement("canvas"); - imgDataCanvas.width = w; - imgDataCanvas.height = h; - imgDataCanvas.style.display = "none"; - document.body.appendChild(imgDataCanvas); - var imgDataCtx = imgDataCanvas.getContext("2d"); - imgDataCtx.drawImage(img, 0, 0); // Save the image data. - - imgData = imgDataCtx.getImageData(0, 0, w, h).data; - imgDataMipMapper = new _mipmapper2["default"](img); // Done with the canvas, remove it from the page so it can be gc'd. - - document.body.removeChild(imgDataCanvas); // Alert any getImageData callers who are waiting. - - for (var i = 0; i < imgDataCallbacks.length; i++) { - imgDataCallbacks[i](imgData, w, h, imgDataMipMapper); + + img.src = uri; + + var canvasTiles = _leaflet2["default"].gridLayer(Object.assign({}, options, { + detectRetina: true, + async: true + })); // NOTE: The done() function MUST NOT be invoked until after the current + // tick; done() looks in Leaflet's tile cache for the current tile, and + // since it's still being constructed, it won't be found. + + + canvasTiles.createTile = function (tilePoint, done) { + var zoom = tilePoint.z; + + var canvas = _leaflet2["default"].DomUtil.create("canvas"); + + var error; // setup tile width and height according to the options + + var size = this.getTileSize(); + canvas.width = size.x; + canvas.height = size.y; + getImageData(function (imgData, w, h, mipmapper) { + try { + // The Context2D we'll being drawing onto. It's always 256x256. + var ctx = canvas.getContext("2d"); // Convert our image data's top-left and bottom-right locations into + // x/y tile coordinates. This is essentially doing a spherical mercator + // projection, then multiplying by 2^zoom. + + var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); + var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); // The size of the image in x/y tile coordinates. + + var extent = { + x: bottomRight.x - topLeft.x, + y: bottomRight.y - topLeft.y + }; // Short circuit if tile is totally disjoint from image. + + if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return; + if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; // The linear resolution of the tile we're drawing is always 256px per tile unit. + // If the linear resolution (in either direction) of the image is less than 256px + // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in + // scaling. + + var imgRes = { + x: w / extent.x, + y: h / extent.y + }; // We can do the actual drawing in one of three ways: + // - Call drawImage(). This is easy and fast, and results in smooth + // interpolation (bilinear?). This is what we want when we are + // reducing the image from its native size. + // - Call drawImage() with imageSmoothingEnabled=false. This is easy + // and fast and gives us nearest-neighbor interpolation, which is what + // we want when enlarging the image. However, it's unsupported on many + // browsers (including QtWebkit). + // - Do a manual nearest-neighbor interpolation. This is what we'll fall + // back to when enlarging, and imageSmoothingEnabled isn't supported. + // In theory it's slower, but still pretty fast on my machine, and the + // results look the same AFAICT. + // Is imageSmoothingEnabled supported? If so, we can let canvas do + // nearest-neighbor interpolation for us. + + var smoothingProperty = getCanvasSmoothingProperty(ctx); + + if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { + // Use built-in scaling + // Turn off anti-aliasing if necessary + if (smoothingProperty) { + ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; + } // Don't necessarily draw with the full-size image; if we're + // downscaling, use the mipmapper to get a pre-downscaled image + // (see comments on Mipmapper class for why this matters). + + + mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { + // It's possible that the image will go off the edge of the canvas-- + // that's OK, the canvas should clip appropriately. + ctx.drawImage(mip, // Convert abs tile coords to rel tile coords, then *256 to convert + // to rel pixel coords + (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, // Always draw the whole thing and let canvas clip; so we can just + // convert from size in tile coords straight to pixels + extent.x * 256, extent.y * 256); + }); + } else { + // Use manual nearest-neighbor interpolation + // Calculate the source image pixel coordinates that correspond with + // the top-left and bottom-right of this tile. (If the source image + // only partially overlaps the tile, we use max/min to limit the + // sourceStart/End to only reflect the overlapping portion.) + var sourceStart = { + x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), + y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) + }; + var sourceEnd = { + x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), + y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) + }; // The size, in dest pixels, that each source pixel should occupy. + // This might be greater or less than 1 (e.g. if x and y resolution + // are very different). + + var pixelSize = { + x: 256 / imgRes.x, + y: 256 / imgRes.y + }; // For each pixel in the source image that overlaps the tile... + + for (var row = sourceStart.y; row < sourceEnd.y; row++) { + for (var col = sourceStart.x; col < sourceEnd.x; col++) { + // ...extract the pixel data... + var i = (row * w + col) * 4; + var r = imgData[i]; + var g = imgData[i + 1]; + var b = imgData[i + 2]; + var a = imgData[i + 3]; + ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; // ...calculate the corresponding pixel coord in the dest image + // where it should be drawn... + + var pixelPos = { + x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, + y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 + }; // ...and draw a rectangle there. + + ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), // Looks crazy, but this is necessary to prevent rounding from + // causing overlap between this rect and its neighbors. The + // minuend is the location of the next pixel, while the + // subtrahend is the position of the current pixel (to turn an + // absolute coordinate to a width/height). Yes, I had to look + // up minuend and subtrahend. + Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); + } + } + } + } catch (e) { + error = e; + } finally { + done(error, canvas); + } + }); + return canvas; + }; + + this.layerManager.addLayer(canvasTiles, "image", layerId, group); + }; + + methods.removeImage = function (layerId) { + this.layerManager.removeLayer("image", layerId); + }; + + methods.clearImages = function () { + this.layerManager.clearLayers("image"); + }; + + methods.addMeasure = function (options) { + // if a measureControl already exists, then remove it and + // replace with a new one + methods.removeMeasure.call(this); + this.measureControl = _leaflet2["default"].control.measure(options); + this.addControl(this.measureControl); + }; + + methods.removeMeasure = function () { + if (this.measureControl) { + this.removeControl(this.measureControl); + this.measureControl = null; } - - imgDataCallbacks = []; }; - - img.src = uri; - - var canvasTiles = _leaflet2["default"].gridLayer(Object.assign({}, options, { - detectRetina: true, - async: true - })); // NOTE: The done() function MUST NOT be invoked until after the current - // tick; done() looks in Leaflet's tile cache for the current tile, and - // since it's still being constructed, it won't be found. - - - canvasTiles.createTile = function (tilePoint, done) { - var zoom = tilePoint.z; - - var canvas = _leaflet2["default"].DomUtil.create("canvas"); - - var error; // setup tile width and height according to the options - - var size = this.getTileSize(); - canvas.width = size.x; - canvas.height = size.y; - getImageData(function (imgData, w, h, mipmapper) { - try { - // The Context2D we'll being drawing onto. It's always 256x256. - var ctx = canvas.getContext("2d"); // Convert our image data's top-left and bottom-right locations into - // x/y tile coordinates. This is essentially doing a spherical mercator - // projection, then multiplying by 2^zoom. - - var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); - var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); // The size of the image in x/y tile coordinates. - - var extent = { - x: bottomRight.x - topLeft.x, - y: bottomRight.y - topLeft.y - }; // Short circuit if tile is totally disjoint from image. - - if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return; - if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; // The linear resolution of the tile we're drawing is always 256px per tile unit. - // If the linear resolution (in either direction) of the image is less than 256px - // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in - // scaling. - - var imgRes = { - x: w / extent.x, - y: h / extent.y - }; // We can do the actual drawing in one of three ways: - // - Call drawImage(). This is easy and fast, and results in smooth - // interpolation (bilinear?). This is what we want when we are - // reducing the image from its native size. - // - Call drawImage() with imageSmoothingEnabled=false. This is easy - // and fast and gives us nearest-neighbor interpolation, which is what - // we want when enlarging the image. However, it's unsupported on many - // browsers (including QtWebkit). - // - Do a manual nearest-neighbor interpolation. This is what we'll fall - // back to when enlarging, and imageSmoothingEnabled isn't supported. - // In theory it's slower, but still pretty fast on my machine, and the - // results look the same AFAICT. - // Is imageSmoothingEnabled supported? If so, we can let canvas do - // nearest-neighbor interpolation for us. - - var smoothingProperty = getCanvasSmoothingProperty(ctx); - - if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { - // Use built-in scaling - // Turn off anti-aliasing if necessary - if (smoothingProperty) { - ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; - } // Don't necessarily draw with the full-size image; if we're - // downscaling, use the mipmapper to get a pre-downscaled image - // (see comments on Mipmapper class for why this matters). - - - mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { - // It's possible that the image will go off the edge of the canvas-- - // that's OK, the canvas should clip appropriately. - ctx.drawImage(mip, // Convert abs tile coords to rel tile coords, then *256 to convert - // to rel pixel coords - (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, // Always draw the whole thing and let canvas clip; so we can just - // convert from size in tile coords straight to pixels - extent.x * 256, extent.y * 256); - }); - } else { - // Use manual nearest-neighbor interpolation - // Calculate the source image pixel coordinates that correspond with - // the top-left and bottom-right of this tile. (If the source image - // only partially overlaps the tile, we use max/min to limit the - // sourceStart/End to only reflect the overlapping portion.) - var sourceStart = { - x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), - y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) - }; - var sourceEnd = { - x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), - y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) - }; // The size, in dest pixels, that each source pixel should occupy. - // This might be greater or less than 1 (e.g. if x and y resolution - // are very different). - - var pixelSize = { - x: 256 / imgRes.x, - y: 256 / imgRes.y - }; // For each pixel in the source image that overlaps the tile... - - for (var row = sourceStart.y; row < sourceEnd.y; row++) { - for (var col = sourceStart.x; col < sourceEnd.x; col++) { - // ...extract the pixel data... - var i = (row * w + col) * 4; - var r = imgData[i]; - var g = imgData[i + 1]; - var b = imgData[i + 2]; - var a = imgData[i + 3]; - ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; // ...calculate the corresponding pixel coord in the dest image - // where it should be drawn... - - var pixelPos = { - x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, - y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 - }; // ...and draw a rectangle there. - - ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), // Looks crazy, but this is necessary to prevent rounding from - // causing overlap between this rect and its neighbors. The - // minuend is the location of the next pixel, while the - // subtrahend is the position of the current pixel (to turn an - // absolute coordinate to a width/height). Yes, I had to look - // up minuend and subtrahend. - Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); - } + + methods.addSelect = function (ctGroup) { + var _this8 = this; + + methods.removeSelect.call(this); + this._selectButton = _leaflet2["default"].easyButton({ + states: [{ + stateName: "select-inactive", + icon: "ion-qr-scanner", + title: "Make a selection", + onClick: function onClick(btn, map) { + btn.state("select-active"); + _this8._locationFilter = new _leaflet2["default"].LocationFilter2(); + + if (ctGroup) { + var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); + selectionHandle.on("change", function (e) { + if (e.sender !== selectionHandle) { + if (_this8._locationFilter) { + _this8._locationFilter.disable(); + + btn.state("select-inactive"); + } + } + }); + + var handler = function handler(e) { + _this8.layerManager.brush(_this8._locationFilter.getBounds(), { + sender: selectionHandle + }); + }; + + _this8._locationFilter.on("enabled", handler); + + _this8._locationFilter.on("change", handler); + + _this8._locationFilter.on("disabled", function () { + selectionHandle.close(); + _this8._locationFilter = null; + }); } + + _this8._locationFilter.addTo(map); } - } catch (e) { - error = e; - } finally { - done(error, canvas); - } + }, { + stateName: "select-active", + icon: "ion-close-round", + title: "Dismiss selection", + onClick: function onClick(btn, map) { + btn.state("select-inactive"); + + _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections + + + _this8.layerManager.unbrush(); + } + }] }); - return canvas; + + this._selectButton.addTo(this); }; - - this.layerManager.addLayer(canvasTiles, "image", layerId, group); -}; - -methods.removeImage = function (layerId) { - this.layerManager.removeLayer("image", layerId); -}; - -methods.clearImages = function () { - this.layerManager.clearLayers("image"); -}; - -methods.addMeasure = function (options) { - // if a measureControl already exists, then remove it and - // replace with a new one - methods.removeMeasure.call(this); - this.measureControl = _leaflet2["default"].control.measure(options); - this.addControl(this.measureControl); -}; - -methods.removeMeasure = function () { - if (this.measureControl) { - this.removeControl(this.measureControl); - this.measureControl = null; - } -}; - -methods.addSelect = function (ctGroup) { - var _this8 = this; - - methods.removeSelect.call(this); - this._selectButton = _leaflet2["default"].easyButton({ - states: [{ - stateName: "select-inactive", - icon: "ion-qr-scanner", - title: "Make a selection", - onClick: function onClick(btn, map) { - btn.state("select-active"); - _this8._locationFilter = new _leaflet2["default"].LocationFilter2(); - - if (ctGroup) { - var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); - selectionHandle.on("change", function (e) { - if (e.sender !== selectionHandle) { - if (_this8._locationFilter) { - _this8._locationFilter.disable(); - - btn.state("select-inactive"); - } + + methods.removeSelect = function () { + if (this._locationFilter) { + this._locationFilter.disable(); + } + + if (this._selectButton) { + this.removeControl(this._selectButton); + this._selectButton = null; + } + }; + + methods.createMapPane = function (name, zIndex) { + this.createPane(name); + this.getPane(name).style.zIndex = zIndex; + }; + + + }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + },{"./cluster-layer-store":1,"./crs_utils":3,"./dataframe":4,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./mipmapper":16,"./util":17}],16:[function(require,module,exports){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + + function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + + // This class simulates a mipmap, which shrinks images by powers of two. This + // stepwise reduction results in "pixel-perfect downscaling" (where every + // pixel of the original image has some contribution to the downscaled image) + // as opposed to a single-step downscaling which will discard a lot of data + // (and with sparse images at small scales can give very surprising results). + var Mipmapper = /*#__PURE__*/function () { + function Mipmapper(img) { + _classCallCheck(this, Mipmapper); + + this._layers = [img]; + } // The various functions on this class take a callback function BUT MAY OR MAY + // NOT actually behave asynchronously. + + + _createClass(Mipmapper, [{ + key: "getBySize", + value: function getBySize(desiredWidth, desiredHeight, callback) { + var _this = this; + + var i = 0; + var lastImg = this._layers[0]; + + var testNext = function testNext() { + _this.getByIndex(i, function (img) { + // If current image is invalid (i.e. too small to be rendered) or + // it's smaller than what we wanted, return the last known good image. + if (!img || img.width < desiredWidth || img.height < desiredHeight) { + callback(lastImg); + return; + } else { + lastImg = img; + i++; + testNext(); + return; } }); - - var handler = function handler(e) { - _this8.layerManager.brush(_this8._locationFilter.getBounds(), { - sender: selectionHandle - }); - }; - - _this8._locationFilter.on("enabled", handler); - - _this8._locationFilter.on("change", handler); - - _this8._locationFilter.on("disabled", function () { - selectionHandle.close(); - _this8._locationFilter = null; - }); - } - - _this8._locationFilter.addTo(map); + }; + + testNext(); } }, { - stateName: "select-active", - icon: "ion-close-round", - title: "Dismiss selection", - onClick: function onClick(btn, map) { - btn.state("select-inactive"); - - _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections - - - _this8.layerManager.unbrush(); - } - }] - }); - - this._selectButton.addTo(this); -}; - -methods.removeSelect = function () { - if (this._locationFilter) { - this._locationFilter.disable(); - } - - if (this._selectButton) { - this.removeControl(this._selectButton); - this._selectButton = null; - } -}; - -methods.createMapPane = function (name, zIndex) { - this.createPane(name); - this.getPane(name).style.zIndex = zIndex; -}; - - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./cluster-layer-store":1,"./crs_utils":3,"./dataframe":4,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./mipmapper":16,"./util":17}],16:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - -function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - -// This class simulates a mipmap, which shrinks images by powers of two. This -// stepwise reduction results in "pixel-perfect downscaling" (where every -// pixel of the original image has some contribution to the downscaled image) -// as opposed to a single-step downscaling which will discard a lot of data -// (and with sparse images at small scales can give very surprising results). -var Mipmapper = /*#__PURE__*/function () { - function Mipmapper(img) { - _classCallCheck(this, Mipmapper); - - this._layers = [img]; - } // The various functions on this class take a callback function BUT MAY OR MAY - // NOT actually behave asynchronously. - - - _createClass(Mipmapper, [{ - key: "getBySize", - value: function getBySize(desiredWidth, desiredHeight, callback) { - var _this = this; - - var i = 0; - var lastImg = this._layers[0]; - - var testNext = function testNext() { - _this.getByIndex(i, function (img) { - // If current image is invalid (i.e. too small to be rendered) or - // it's smaller than what we wanted, return the last known good image. - if (!img || img.width < desiredWidth || img.height < desiredHeight) { - callback(lastImg); - return; - } else { - lastImg = img; - i++; - testNext(); + key: "getByIndex", + value: function getByIndex(i, callback) { + var _this2 = this; + + if (this._layers[i]) { + callback(this._layers[i]); + return; + } + + this.getByIndex(i - 1, function (prevImg) { + if (!prevImg) { + // prevImg could not be calculated (too small, possibly) + callback(null); return; } + + if (prevImg.width < 2 || prevImg.height < 2) { + // Can't reduce this image any further + callback(null); + return; + } // If reduce ever becomes truly asynchronous, we should stuff a promise or + // something into this._layers[i] before calling this.reduce(), to prevent + // redundant reduce operations from happening. + + + _this2.reduce(prevImg, function (reducedImg) { + _this2._layers[i] = reducedImg; + callback(reducedImg); + return; + }); }); - }; - - testNext(); - } - }, { - key: "getByIndex", - value: function getByIndex(i, callback) { - var _this2 = this; - - if (this._layers[i]) { - callback(this._layers[i]); - return; } - - this.getByIndex(i - 1, function (prevImg) { - if (!prevImg) { - // prevImg could not be calculated (too small, possibly) - callback(null); - return; + }, { + key: "reduce", + value: function reduce(img, callback) { + var imgDataCanvas = document.createElement("canvas"); + imgDataCanvas.width = Math.ceil(img.width / 2); + imgDataCanvas.height = Math.ceil(img.height / 2); + imgDataCanvas.style.display = "none"; + document.body.appendChild(imgDataCanvas); + + try { + var imgDataCtx = imgDataCanvas.getContext("2d"); + imgDataCtx.drawImage(img, 0, 0, img.width / 2, img.height / 2); + callback(imgDataCanvas); + } finally { + document.body.removeChild(imgDataCanvas); } - - if (prevImg.width < 2 || prevImg.height < 2) { - // Can't reduce this image any further - callback(null); - return; - } // If reduce ever becomes truly asynchronous, we should stuff a promise or - // something into this._layers[i] before calling this.reduce(), to prevent - // redundant reduce operations from happening. - - - _this2.reduce(prevImg, function (reducedImg) { - _this2._layers[i] = reducedImg; - callback(reducedImg); - return; - }); - }); - } - }, { - key: "reduce", - value: function reduce(img, callback) { - var imgDataCanvas = document.createElement("canvas"); - imgDataCanvas.width = Math.ceil(img.width / 2); - imgDataCanvas.height = Math.ceil(img.height / 2); - imgDataCanvas.style.display = "none"; - document.body.appendChild(imgDataCanvas); - - try { - var imgDataCtx = imgDataCanvas.getContext("2d"); - imgDataCtx.drawImage(img, 0, 0, img.width / 2, img.height / 2); - callback(imgDataCanvas); - } finally { - document.body.removeChild(imgDataCanvas); } + }]); + + return Mipmapper; + }(); + + exports["default"] = Mipmapper; + + + },{}],17:[function(require,module,exports){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.log = log; + exports.recycle = recycle; + exports.asArray = asArray; + + function log(message) { + /* eslint-disable no-console */ + if (console && console.log) console.log(message); + /* eslint-enable no-console */ + } + + function recycle(values, length, inPlace) { + if (length === 0 && !inPlace) return []; + + if (!(values instanceof Array)) { + if (inPlace) { + throw new Error("Can't do in-place recycling of a non-Array value"); + } + + values = [values]; } - }]); - - return Mipmapper; -}(); - -exports["default"] = Mipmapper; - - -},{}],17:[function(require,module,exports){ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.log = log; -exports.recycle = recycle; -exports.asArray = asArray; - -function log(message) { - /* eslint-disable no-console */ - if (console && console.log) console.log(message); - /* eslint-enable no-console */ -} - -function recycle(values, length, inPlace) { - if (length === 0 && !inPlace) return []; - - if (!(values instanceof Array)) { - if (inPlace) { - throw new Error("Can't do in-place recycling of a non-Array value"); + + if (typeof length === "undefined") length = values.length; + var dest = inPlace ? values : []; + var origLength = values.length; + + while (dest.length < length) { + dest.push(values[dest.length % origLength]); } - - values = [values]; - } - - if (typeof length === "undefined") length = values.length; - var dest = inPlace ? values : []; - var origLength = values.length; - - while (dest.length < length) { - dest.push(values[dest.length % origLength]); + + if (dest.length > length) { + dest.splice(length, dest.length - length); + } + + return dest; } - - if (dest.length > length) { - dest.splice(length, dest.length - length); + + function asArray(value) { + if (value instanceof Array) return value;else return [value]; } - - return dest; -} - -function asArray(value) { - if (value instanceof Array) return value;else return [value]; -} - - -},{}]},{},[13]); + + + },{}]},{},[13]); + \ No newline at end of file diff --git a/javascript/src/methods.js b/javascript/src/methods.js index 91d8ea70b..687ceb0cc 100644 --- a/javascript/src/methods.js +++ b/javascript/src/methods.js @@ -148,6 +148,95 @@ function unpackStrings(iconset) { } function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { + function updateClusterMarkers(filtered, ctGroup, clusterGroup, ctKeyColumn) { + // For safety, check required parameters + if (!filtered) { + return; + } + + if (!clusterGroup) { + return; + } + + // Handle both null/undefined and empty array cases for filter values + const filterValues = filtered.value && Array.isArray(filtered.value) + ? filtered.value.map(v => String(v)) + : []; + + const showAllMarkers = filtered.value === null || filtered.value === undefined; + + // Store the original cluster options to reuse them + const options = Object.assign({}, clusterOptions); + + // Create a fresh cluster + const newClusterGroup = L.markerClusterGroup.layerSupport(options); + if (options.freezeAtZoom) { + let freezeAtZoom = options.freezeAtZoom; + newClusterGroup.freezeAtZoom(freezeAtZoom); + } + newClusterGroup.clusterLayerStore = new ClusterLayerStore(newClusterGroup); + + // If showing all markers, use all rows; otherwise filter + const rowsToShow = []; + for (let i = 0; i < df.nrow(); i++) { + // When showing all markers OR marker key is in filter values + const ctKey = String(df.get(i, ctKeyColumn) || ""); + const shouldShow = showAllMarkers || filterValues.includes(ctKey); + + if (shouldShow) { + rowsToShow.push(i); + } + } + + // Process rows that should be shown + for (let i of rowsToShow) { + if ($.isNumeric(df.get(i, "lat")) && $.isNumeric(df.get(i, "lng"))) { + // Create a fresh marker - safer than trying to reuse existing ones + const marker = markerFunc(df, i); + const thisId = df.get(i, "layerId"); + + // Add popup content if in the dataframe + let popup = df.get(i, "popup"); + let popupOptions = df.get(i, "popupOptions"); + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); + } + } + + // Add label/tooltip if in the dataframe + let label = df.get(i, "label"); + let labelOptions = df.get(i, "labelOptions"); + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); + } + } else { + marker.bindTooltip(label); + } + } + + // Add to cluster with appropriate ID + newClusterGroup.clusterLayerStore.add(marker, thisId); + } + } + + // Replace the old cluster group with the new one + map.removeLayer(clusterGroup); + map.addLayer(newClusterGroup); + + // Update the layer manager reference to the new cluster group + map.layerManager.removeLayer("cluster", clusterId); + map.layerManager.addLayer(newClusterGroup, "cluster", clusterId, group); + + return newClusterGroup; + } + (function() { let clusterGroup = this.layerManager.getLayer("cluster", clusterId), cluster = clusterOptions !== null; @@ -203,6 +292,41 @@ function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { } } + // Set up crosstalk filtering for clusters + if (cluster && df.get(0, "ctGroup")) { + let ctGroup = df.get(0, "ctGroup"); + let ctKeyColumn = "ctKey"; + + + // Check if global crosstalk is available + if (window.crosstalk || global.crosstalk) { + let crosstalkObj = window.crosstalk || global.crosstalk; + + // Create a proper filter handle instead of using group().on() + let filterHandle = new crosstalkObj.FilterHandle(ctGroup); + + // Subscribe to filter changes using the handle + filterHandle.on("change", function(e) { + const newClusterGroup = updateClusterMarkers(e, ctGroup, clusterGroup, ctKeyColumn); + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + }); + + // Initial setup + if (filterHandle.filteredKeys) { + const newClusterGroup = updateClusterMarkers( + {value: filterHandle.filteredKeys}, + ctGroup, + clusterGroup, + ctKeyColumn + ); + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + } + } + } if (cluster) { this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); } From 488dd0a1383e83d023d2fa0bc27d2aa065fce4fc Mon Sep 17 00:00:00 2001 From: Arkady <144838822+arkadymarch@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:41:36 -0500 Subject: [PATCH 2/5] indentation fix --- inst/htmlwidgets/assets/leaflet.js | 5535 ++++++++++++++-------------- 1 file changed, 2767 insertions(+), 2768 deletions(-) diff --git a/inst/htmlwidgets/assets/leaflet.js b/inst/htmlwidgets/assets/leaflet.js index ec3ffe069..c1aa61a15 100644 --- a/inst/htmlwidgets/assets/leaflet.js +++ b/inst/htmlwidgets/assets/leaflet.js @@ -1,2913 +1,2912 @@ (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i this.effectiveLength) throw new Error("Row argument was out of bounds: " + row + " > " + this.effectiveLength); + var colIndex = -1; + + if (typeof col === "undefined") { + var rowData = {}; + this.colnames.forEach(function (name, i) { + rowData[name] = _this3.columns[i][row % _this3.columns[i].length]; }); - return this; - } - }, { - key: "get", - value: function get(row, col, missingOK) { - var _this3 = this; - - if (row > this.effectiveLength) throw new Error("Row argument was out of bounds: " + row + " > " + this.effectiveLength); - var colIndex = -1; - - if (typeof col === "undefined") { - var rowData = {}; - this.colnames.forEach(function (name, i) { - rowData[name] = _this3.columns[i][row % _this3.columns[i].length]; - }); - return rowData; - } else if (typeof col === "string") { - colIndex = this._colIndex(col); - } else if (typeof col === "number") { - colIndex = col; - } - - if (colIndex < 0 || colIndex > this.columns.length) { - if (missingOK) return void 0;else throw new Error("Unknown column index: " + col); - } - - return this.columns[colIndex][row % this.columns[colIndex].length]; - } - }, { - key: "nrow", - value: function nrow() { - return this.effectiveLength; - } - }]); - - return DataFrame; - }(); - - exports["default"] = DataFrame; - - - },{"./util":17}],5:[function(require,module,exports){ - "use strict"; - - var _leaflet = require("./global/leaflet"); - - var _leaflet2 = _interopRequireDefault(_leaflet); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - // In RMarkdown's self-contained mode, we don't have a way to carry around the - // images that Leaflet needs but doesn't load into the page. Instead, we'll use - // the unpkg CDN. - if (typeof _leaflet2["default"].Icon.Default.imagePath === "undefined") { - _leaflet2["default"].Icon.Default.imagePath = "https://unpkg.com/leaflet@1.3.1/dist/images/"; + return rowData; + } else if (typeof col === "string") { + colIndex = this._colIndex(col); + } else if (typeof col === "number") { + colIndex = col; + } + + if (colIndex < 0 || colIndex > this.columns.length) { + if (missingOK) return void 0;else throw new Error("Unknown column index: " + col); + } + + return this.columns[colIndex][row % this.columns[colIndex].length]; + } + }, { + key: "nrow", + value: function nrow() { + return this.effectiveLength; + } + }]); + + return DataFrame; +}(); + +exports["default"] = DataFrame; + + +},{"./util":17}],5:[function(require,module,exports){ +"use strict"; + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +// In RMarkdown's self-contained mode, we don't have a way to carry around the +// images that Leaflet needs but doesn't load into the page. Instead, we'll use +// the unpkg CDN. +if (typeof _leaflet2["default"].Icon.Default.imagePath === "undefined") { + _leaflet2["default"].Icon.Default.imagePath = "https://unpkg.com/leaflet@1.3.1/dist/images/"; +} + + +},{"./global/leaflet":10}],6:[function(require,module,exports){ +"use strict"; + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +// add texxtsize, textOnly, and style +_leaflet2["default"].Tooltip.prototype.options.textsize = "10px"; +_leaflet2["default"].Tooltip.prototype.options.textOnly = false; +_leaflet2["default"].Tooltip.prototype.options.style = null; // copy original layout to not completely stomp it. + +var initLayoutOriginal = _leaflet2["default"].Tooltip.prototype._initLayout; + +_leaflet2["default"].Tooltip.prototype._initLayout = function () { + initLayoutOriginal.call(this); + this._container.style.fontSize = this.options.textsize; + + if (this.options.textOnly) { + _leaflet2["default"].DomUtil.addClass(this._container, "leaflet-tooltip-text-only"); } - - - },{"./global/leaflet":10}],6:[function(require,module,exports){ - "use strict"; - - var _leaflet = require("./global/leaflet"); - - var _leaflet2 = _interopRequireDefault(_leaflet); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - // add texxtsize, textOnly, and style - _leaflet2["default"].Tooltip.prototype.options.textsize = "10px"; - _leaflet2["default"].Tooltip.prototype.options.textOnly = false; - _leaflet2["default"].Tooltip.prototype.options.style = null; // copy original layout to not completely stomp it. - - var initLayoutOriginal = _leaflet2["default"].Tooltip.prototype._initLayout; - - _leaflet2["default"].Tooltip.prototype._initLayout = function () { - initLayoutOriginal.call(this); - this._container.style.fontSize = this.options.textsize; - - if (this.options.textOnly) { - _leaflet2["default"].DomUtil.addClass(this._container, "leaflet-tooltip-text-only"); - } - - if (this.options.style) { - for (var property in this.options.style) { - this._container.style[property] = this.options.style[property]; - } + + if (this.options.style) { + for (var property in this.options.style) { + this._container.style[property] = this.options.style[property]; } - }; - - - },{"./global/leaflet":10}],7:[function(require,module,exports){ - "use strict"; - - var _leaflet = require("./global/leaflet"); - - var _leaflet2 = _interopRequireDefault(_leaflet); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - var protocolRegex = /^\/\//; - - var upgrade_protocol = function upgrade_protocol(urlTemplate) { - if (protocolRegex.test(urlTemplate)) { - if (window.location.protocol === "file:") { - // if in a local file, support http - // http should auto upgrade if necessary - urlTemplate = "http:" + urlTemplate; - } + } +}; + + +},{"./global/leaflet":10}],7:[function(require,module,exports){ +"use strict"; + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var protocolRegex = /^\/\//; + +var upgrade_protocol = function upgrade_protocol(urlTemplate) { + if (protocolRegex.test(urlTemplate)) { + if (window.location.protocol === "file:") { + // if in a local file, support http + // http should auto upgrade if necessary + urlTemplate = "http:" + urlTemplate; } - - return urlTemplate; - }; - - var originalLTileLayerInitialize = _leaflet2["default"].TileLayer.prototype.initialize; - - _leaflet2["default"].TileLayer.prototype.initialize = function (urlTemplate, options) { - urlTemplate = upgrade_protocol(urlTemplate); - originalLTileLayerInitialize.call(this, urlTemplate, options); - }; - - var originalLTileLayerWMSInitialize = _leaflet2["default"].TileLayer.WMS.prototype.initialize; - - _leaflet2["default"].TileLayer.WMS.prototype.initialize = function (urlTemplate, options) { - urlTemplate = upgrade_protocol(urlTemplate); - originalLTileLayerWMSInitialize.call(this, urlTemplate, options); - }; - - - },{"./global/leaflet":10}],8:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports["default"] = global.HTMLWidgets; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{}],9:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports["default"] = global.jQuery; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{}],10:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports["default"] = global.L; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{}],11:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true + } + + return urlTemplate; +}; + +var originalLTileLayerInitialize = _leaflet2["default"].TileLayer.prototype.initialize; + +_leaflet2["default"].TileLayer.prototype.initialize = function (urlTemplate, options) { + urlTemplate = upgrade_protocol(urlTemplate); + originalLTileLayerInitialize.call(this, urlTemplate, options); +}; + +var originalLTileLayerWMSInitialize = _leaflet2["default"].TileLayer.WMS.prototype.initialize; + +_leaflet2["default"].TileLayer.WMS.prototype.initialize = function (urlTemplate, options) { + urlTemplate = upgrade_protocol(urlTemplate); + originalLTileLayerWMSInitialize.call(this, urlTemplate, options); +}; + + +},{"./global/leaflet":10}],8:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.HTMLWidgets; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],9:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.jQuery; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],10:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.L; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],11:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.L.Proj; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],12:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = global.Shiny; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],13:[function(require,module,exports){ +"use strict"; + +var _jquery = require("./global/jquery"); + +var _jquery2 = _interopRequireDefault(_jquery); + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +var _shiny = require("./global/shiny"); + +var _shiny2 = _interopRequireDefault(_shiny); + +var _htmlwidgets = require("./global/htmlwidgets"); + +var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); + +var _util = require("./util"); + +var _crs_utils = require("./crs_utils"); + +var _controlStore = require("./control-store"); + +var _controlStore2 = _interopRequireDefault(_controlStore); + +var _layerManager = require("./layer-manager"); + +var _layerManager2 = _interopRequireDefault(_layerManager); + +var _methods = require("./methods"); + +var _methods2 = _interopRequireDefault(_methods); + +require("./fixup-default-icon"); + +require("./fixup-default-tooltip"); + +require("./fixup-url-protocol"); + +var _dataframe = require("./dataframe"); + +var _dataframe2 = _interopRequireDefault(_dataframe); + +var _clusterLayerStore = require("./cluster-layer-store"); + +var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +window.LeafletWidget = {}; +window.LeafletWidget.utils = {}; + +var methods = window.LeafletWidget.methods = _jquery2["default"].extend({}, _methods2["default"]); + +window.LeafletWidget.DataFrame = _dataframe2["default"]; +window.LeafletWidget.ClusterLayerStore = _clusterLayerStore2["default"]; +window.LeafletWidget.utils.getCRS = _crs_utils.getCRS; // Send updated bounds back to app. Takes a leaflet event object as input. + +function updateBounds(map) { + var id = map.getContainer().id; + var bounds = map.getBounds(); + + _shiny2["default"].onInputChange(id + "_bounds", { + north: bounds.getNorthEast().lat, + east: bounds.getNorthEast().lng, + south: bounds.getSouthWest().lat, + west: bounds.getSouthWest().lng }); - exports["default"] = global.L.Proj; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{}],12:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true + + _shiny2["default"].onInputChange(id + "_center", { + lng: map.getCenter().lng, + lat: map.getCenter().lat }); - exports["default"] = global.Shiny; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{}],13:[function(require,module,exports){ - "use strict"; - - var _jquery = require("./global/jquery"); - - var _jquery2 = _interopRequireDefault(_jquery); - - var _leaflet = require("./global/leaflet"); - - var _leaflet2 = _interopRequireDefault(_leaflet); - - var _shiny = require("./global/shiny"); - - var _shiny2 = _interopRequireDefault(_shiny); - - var _htmlwidgets = require("./global/htmlwidgets"); - - var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); - - var _util = require("./util"); - - var _crs_utils = require("./crs_utils"); - - var _controlStore = require("./control-store"); - - var _controlStore2 = _interopRequireDefault(_controlStore); - - var _layerManager = require("./layer-manager"); - - var _layerManager2 = _interopRequireDefault(_layerManager); - - var _methods = require("./methods"); - - var _methods2 = _interopRequireDefault(_methods); - - require("./fixup-default-icon"); - - require("./fixup-default-tooltip"); - - require("./fixup-url-protocol"); - - var _dataframe = require("./dataframe"); - - var _dataframe2 = _interopRequireDefault(_dataframe); - - var _clusterLayerStore = require("./cluster-layer-store"); - - var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - window.LeafletWidget = {}; - window.LeafletWidget.utils = {}; - - var methods = window.LeafletWidget.methods = _jquery2["default"].extend({}, _methods2["default"]); - - window.LeafletWidget.DataFrame = _dataframe2["default"]; - window.LeafletWidget.ClusterLayerStore = _clusterLayerStore2["default"]; - window.LeafletWidget.utils.getCRS = _crs_utils.getCRS; // Send updated bounds back to app. Takes a leaflet event object as input. - - function updateBounds(map) { - var id = map.getContainer().id; - var bounds = map.getBounds(); - - _shiny2["default"].onInputChange(id + "_bounds", { - north: bounds.getNorthEast().lat, - east: bounds.getNorthEast().lng, - south: bounds.getSouthWest().lat, - west: bounds.getSouthWest().lng - }); - - _shiny2["default"].onInputChange(id + "_center", { - lng: map.getCenter().lng, - lat: map.getCenter().lat - }); - - _shiny2["default"].onInputChange(id + "_zoom", map.getZoom()); - } - - function preventUnintendedZoomOnScroll(map) { - // Prevent unwanted scroll capturing. Similar in purpose to - // https://github.com/CliffCloud/Leaflet.Sleep but with a - // different set of heuristics. - // The basic idea is that when a mousewheel/DOMMouseScroll - // event is seen, we disable scroll wheel zooming until the - // user moves their mouse cursor or clicks on the map. This - // is slightly trickier than just listening for mousemove, - // because mousemove is fired when the page is scrolled, - // even if the user did not physically move the mouse. We - // handle this by examining the mousemove event's screenX - // and screenY properties; if they change, we know it's a - // "true" move. - // lastScreen can never be null, but its x and y can. - var lastScreen = { - x: null, - y: null + + _shiny2["default"].onInputChange(id + "_zoom", map.getZoom()); +} + +function preventUnintendedZoomOnScroll(map) { + // Prevent unwanted scroll capturing. Similar in purpose to + // https://github.com/CliffCloud/Leaflet.Sleep but with a + // different set of heuristics. + // The basic idea is that when a mousewheel/DOMMouseScroll + // event is seen, we disable scroll wheel zooming until the + // user moves their mouse cursor or clicks on the map. This + // is slightly trickier than just listening for mousemove, + // because mousemove is fired when the page is scrolled, + // even if the user did not physically move the mouse. We + // handle this by examining the mousemove event's screenX + // and screenY properties; if they change, we know it's a + // "true" move. + // lastScreen can never be null, but its x and y can. + var lastScreen = { + x: null, + y: null + }; + (0, _jquery2["default"])(document).on("mousewheel DOMMouseScroll", "*", function (e) { + // Disable zooming (until the mouse moves or click) + map.scrollWheelZoom.disable(); // Any mousemove events at this screen position will be ignored. + + lastScreen = { + x: e.originalEvent.screenX, + y: e.originalEvent.screenY }; - (0, _jquery2["default"])(document).on("mousewheel DOMMouseScroll", "*", function (e) { - // Disable zooming (until the mouse moves or click) - map.scrollWheelZoom.disable(); // Any mousemove events at this screen position will be ignored. - - lastScreen = { - x: e.originalEvent.screenX, - y: e.originalEvent.screenY - }; - }); - (0, _jquery2["default"])(document).on("mousemove", "*", function (e) { - // Did the mouse really move? - if (map.options.scrollWheelZoom) { - if (lastScreen.x !== null && e.screenX !== lastScreen.x || e.screenY !== lastScreen.y) { - // It really moved. Enable zooming. - map.scrollWheelZoom.enable(); - lastScreen = { - x: null, - y: null - }; - } - } - }); - (0, _jquery2["default"])(document).on("mousedown", ".leaflet", function (e) { - // Clicking always enables zooming. - if (map.options.scrollWheelZoom) { + }); + (0, _jquery2["default"])(document).on("mousemove", "*", function (e) { + // Did the mouse really move? + if (map.options.scrollWheelZoom) { + if (lastScreen.x !== null && e.screenX !== lastScreen.x || e.screenY !== lastScreen.y) { + // It really moved. Enable zooming. map.scrollWheelZoom.enable(); lastScreen = { x: null, y: null }; } - }); - } - - _htmlwidgets2["default"].widget({ - name: "leaflet", - type: "output", - factory: function factory(el, width, height) { - var map = null; - return { - // we need to store our map in our returned object. - getMap: function getMap() { - return map; - }, - renderValue: function renderValue(data) { - // Create an appropriate CRS Object if specified - if (data && data.options && data.options.crs) { - data.options.crs = (0, _crs_utils.getCRS)(data.options.crs); - } // As per https://github.com/rstudio/leaflet/pull/294#discussion_r79584810 - - - if (map) { - map.remove(); - - map = function () { - return; - }(); // undefine map - - } - - if (data.options.mapFactory && typeof data.options.mapFactory === "function") { - map = data.options.mapFactory(el, data.options); - } else { - map = _leaflet2["default"].map(el, data.options); - } - - preventUnintendedZoomOnScroll(map); // Store some state in the map object - - map.leafletr = { - // Has the map ever rendered successfully? - hasRendered: false, - // Data to be rendered when resize is called with area != 0 - pendingRenderData: null - }; // Check if the map is rendered statically (no output binding) - - if (_htmlwidgets2["default"].shinyMode && /\bshiny-bound-output\b/.test(el.className)) { - map.id = el.id; // Store the map on the element so we can find it later by ID - - (0, _jquery2["default"])(el).data("leaflet-map", map); // When the map is clicked, send the coordinates back to the app - - map.on("click", function (e) { - _shiny2["default"].onInputChange(map.id + "_click", { - lat: e.latlng.lat, - lng: e.latlng.lng, - ".nonce": Math.random() // Force reactivity if lat/lng hasn't changed - - }); - }); - var groupTimerId = null; - map.on("moveend", function (e) { - updateBounds(e.target); - }).on("layeradd layerremove", function (e) { - // If the layer that's coming or going is a group we created, tell - // the server. - if (map.layerManager.getGroupNameFromLayerGroup(e.layer)) { - // But to avoid chattiness, coalesce events - if (groupTimerId) { - clearTimeout(groupTimerId); - groupTimerId = null; - } - - groupTimerId = setTimeout(function () { - groupTimerId = null; - - _shiny2["default"].onInputChange(map.id + "_groups", map.layerManager.getVisibleGroups()); - }, 100); - } - }); - } - - this.doRenderValue(data, map); - }, - doRenderValue: function doRenderValue(data, map) { - // Leaflet does not behave well when you set up a bunch of layers when - // the map is not visible (width/height == 0). Popups get misaligned - // relative to their owning markers, and the fitBounds calculations - // are off. Therefore we wait until the map is actually showing to - // render the value (we rely on the resize() callback being invoked - // at the appropriate time). - if (el.offsetWidth === 0 || el.offsetHeight === 0) { - map.leafletr.pendingRenderData = data; - return; - } - - map.leafletr.pendingRenderData = null; // Merge data options into defaults - - var options = _jquery2["default"].extend({ - zoomToLimits: "always" - }, data.options); - - if (!map.layerManager) { - map.controls = new _controlStore2["default"](map); - map.layerManager = new _layerManager2["default"](map); - } else { - map.controls.clear(); - map.layerManager.clear(); - } - - var explicitView = false; - - if (data.setView) { - explicitView = true; - map.setView.apply(map, data.setView); - } - - if (data.fitBounds) { - explicitView = true; - methods.fitBounds.apply(map, data.fitBounds); - } - - if (data.flyTo) { - if (!explicitView && !map.leafletr.hasRendered) { - // must be done to give a initial starting point - map.fitWorld(); - } - - explicitView = true; - map.flyTo.apply(map, data.flyTo); - } - - if (data.flyToBounds) { - if (!explicitView && !map.leafletr.hasRendered) { - // must be done to give a initial starting point - map.fitWorld(); - } - - explicitView = true; - methods.flyToBounds.apply(map, data.flyToBounds); - } - - if (data.options.center) { - explicitView = true; - } // Returns true if the zoomToLimits option says that the map should be - // zoomed to map elements. - - - function needsZoom() { - return options.zoomToLimits === "always" || options.zoomToLimits === "first" && !map.leafletr.hasRendered; - } - - if (!explicitView && needsZoom() && !map.getZoom()) { - if (data.limits && !_jquery2["default"].isEmptyObject(data.limits)) { - // Use the natural limits of what's being drawn on the map - // If the size of the bounding box is 0, leaflet gets all weird - var pad = 0.006; - - if (data.limits.lat[0] === data.limits.lat[1]) { - data.limits.lat[0] = data.limits.lat[0] - pad; - data.limits.lat[1] = data.limits.lat[1] + pad; - } - - if (data.limits.lng[0] === data.limits.lng[1]) { - data.limits.lng[0] = data.limits.lng[0] - pad; - data.limits.lng[1] = data.limits.lng[1] + pad; - } - - map.fitBounds([[data.limits.lat[0], data.limits.lng[0]], [data.limits.lat[1], data.limits.lng[1]]]); - } else { - map.fitWorld(); - } - } - - for (var i = 0; data.calls && i < data.calls.length; i++) { - var call = data.calls[i]; - if (methods[call.method]) methods[call.method].apply(map, call.args);else (0, _util.log)("Unknown method " + call.method); - } - - map.leafletr.hasRendered = true; - - if (_htmlwidgets2["default"].shinyMode) { - setTimeout(function () { - updateBounds(map); - }, 1); - } - }, - resize: function resize(width, height) { - if (map) { - map.invalidateSize(); - - if (map.leafletr.pendingRenderData) { - this.doRenderValue(map.leafletr.pendingRenderData, map); - } - } - } + } + }); + (0, _jquery2["default"])(document).on("mousedown", ".leaflet", function (e) { + // Clicking always enables zooming. + if (map.options.scrollWheelZoom) { + map.scrollWheelZoom.enable(); + lastScreen = { + x: null, + y: null }; } }); - - if (_htmlwidgets2["default"].shinyMode) { - _shiny2["default"].addCustomMessageHandler("leaflet-calls", function (data) { - var id = data.id; - var el = document.getElementById(id); - var map = el ? (0, _jquery2["default"])(el).data("leaflet-map") : null; - - if (!map) { - (0, _util.log)("Couldn't find map with id " + id); - return; - } // If the map has not rendered, stash the proposed `leafletProxy()` calls - // in `pendingRenderData.calls` to be run on display via `doRenderValue()`. - // This is necessary if the map has not been rendered. - // If new pendingRenderData is set via a new `leaflet()`, the previous calls will be discarded. - - - if (!map.leafletr.hasRendered) { - map.leafletr.pendingRenderData.calls = map.leafletr.pendingRenderData.calls.concat(data.calls); - return; - } - - for (var i = 0; i < data.calls.length; i++) { - var call = data.calls[i]; - var args = call.args; - - for (var _i = 0; _i < call.evals.length; _i++) { - window.HTMLWidgets.evaluateStringMember(args, call.evals[_i]); +} + +_htmlwidgets2["default"].widget({ + name: "leaflet", + type: "output", + factory: function factory(el, width, height) { + var map = null; + return { + // we need to store our map in our returned object. + getMap: function getMap() { + return map; + }, + renderValue: function renderValue(data) { + // Create an appropriate CRS Object if specified + if (data && data.options && data.options.crs) { + data.options.crs = (0, _crs_utils.getCRS)(data.options.crs); + } // As per https://github.com/rstudio/leaflet/pull/294#discussion_r79584810 + + + if (map) { + map.remove(); + + map = function () { + return; + }(); // undefine map + } - - if (call.dependencies) { - _shiny2["default"].renderDependencies(call.dependencies); + + if (data.options.mapFactory && typeof data.options.mapFactory === "function") { + map = data.options.mapFactory(el, data.options); + } else { + map = _leaflet2["default"].map(el, data.options); } - - if (methods[call.method]) methods[call.method].apply(map, args);else (0, _util.log)("Unknown method " + call.method); - } - }); - } - - - },{"./cluster-layer-store":1,"./control-store":2,"./crs_utils":3,"./dataframe":4,"./fixup-default-icon":5,"./fixup-default-tooltip":6,"./fixup-url-protocol":7,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./layer-manager":14,"./methods":15,"./util":17}],14:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports["default"] = undefined; - - var _jquery = require("./global/jquery"); - - var _jquery2 = _interopRequireDefault(_jquery); - - var _leaflet = require("./global/leaflet"); - - var _leaflet2 = _interopRequireDefault(_leaflet); - - var _util = require("./util"); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - - function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - - var LayerManager = /*#__PURE__*/function () { - function LayerManager(map) { - _classCallCheck(this, LayerManager); - - this._map = map; // BEGIN layer indices - // {: {: layer}} - - this._byGroup = {}; // {: {: layer}} - - this._byCategory = {}; // {: layer} - - this._byLayerId = {}; // {: { - // "group": , - // "layerId": , - // "category": , - // "container": - // } - // } - - this._byStamp = {}; // {: {: [, , ...], ...}} - - this._byCrosstalkGroup = {}; // END layer indices - // {: L.layerGroup} - - this._categoryContainers = {}; // {: L.layerGroup} - - this._groupContainers = {}; - } - - _createClass(LayerManager, [{ - key: "addLayer", - value: function addLayer(layer, category, layerId, group, ctGroup, ctKey) { - var _this = this; - - // Was a group provided? - var hasId = typeof layerId === "string"; - var grouped = typeof group === "string"; - var stamp = _leaflet2["default"].Util.stamp(layer) + ""; // This will be the default layer group to add the layer to. - // We may overwrite this let before using it (i.e. if a group is assigned). - // This one liner creates the _categoryContainers[category] entry if it - // doesn't already exist. - - var container = this._categoryContainers[category] = this._categoryContainers[category] || _leaflet2["default"].layerGroup().addTo(this._map); - - var oldLayer = null; - - if (hasId) { - // First, remove any layer with the same category and layerId - var prefixedLayerId = this._layerIdKey(category, layerId); - - oldLayer = this._byLayerId[prefixedLayerId]; - - if (oldLayer) { - this._removeLayer(oldLayer); - } // Update layerId index - - - this._byLayerId[prefixedLayerId] = layer; - } // Update group index - - - if (grouped) { - this._byGroup[group] = this._byGroup[group] || {}; - this._byGroup[group][stamp] = layer; // Since a group is assigned, don't add the layer to the category's layer - // group; instead, use the group's layer group. - // This one liner creates the _groupContainers[group] entry if it doesn't - // already exist. - - container = this.getLayerGroup(group, true); - } // Update category index - - - this._byCategory[category] = this._byCategory[category] || {}; - this._byCategory[category][stamp] = layer; // Update stamp index - - var layerInfo = this._byStamp[stamp] = { - layer: layer, - group: group, - ctGroup: ctGroup, - ctKey: ctKey, - layerId: layerId, - category: category, - container: container, - hidden: false - }; // Update crosstalk group index - - if (ctGroup) { - if (layer.setStyle) { - // Need to save this info so we know what to set opacity to later - layer.options.origOpacity = typeof layer.options.opacity !== "undefined" ? layer.options.opacity : 0.5; - layer.options.origFillOpacity = typeof layer.options.fillOpacity !== "undefined" ? layer.options.fillOpacity : 0.2; - } - - var ctg = this._byCrosstalkGroup[ctGroup]; - - if (!ctg) { - ctg = this._byCrosstalkGroup[ctGroup] = {}; - var crosstalk = global.crosstalk; - - var handleFilter = function handleFilter(e) { - if (!e.value) { - var groupKeys = Object.keys(ctg); - - for (var i = 0; i < groupKeys.length; i++) { - var key = groupKeys[i]; - var _layerInfo = _this._byStamp[ctg[key]]; - - _this._setVisibility(_layerInfo, true); - } - } else { - var selectedKeys = {}; - - for (var _i = 0; _i < e.value.length; _i++) { - selectedKeys[e.value[_i]] = true; - } - - var _groupKeys = Object.keys(ctg); - - for (var _i2 = 0; _i2 < _groupKeys.length; _i2++) { - var _key = _groupKeys[_i2]; - var _layerInfo2 = _this._byStamp[ctg[_key]]; - - _this._setVisibility(_layerInfo2, selectedKeys[_groupKeys[_i2]]); - } - } - }; - - var filterHandle = new crosstalk.FilterHandle(ctGroup); - filterHandle.on("change", handleFilter); - - var handleSelection = function handleSelection(e) { - if (!e.value || !e.value.length) { - var groupKeys = Object.keys(ctg); - - for (var i = 0; i < groupKeys.length; i++) { - var key = groupKeys[i]; - var _layerInfo3 = _this._byStamp[ctg[key]]; - - _this._setOpacity(_layerInfo3, 1.0); - } - } else { - var selectedKeys = {}; - - for (var _i3 = 0; _i3 < e.value.length; _i3++) { - selectedKeys[e.value[_i3]] = true; - } - - var _groupKeys2 = Object.keys(ctg); - - for (var _i4 = 0; _i4 < _groupKeys2.length; _i4++) { - var _key2 = _groupKeys2[_i4]; - var _layerInfo4 = _this._byStamp[ctg[_key2]]; - - _this._setOpacity(_layerInfo4, selectedKeys[_groupKeys2[_i4]] ? 1.0 : 0.2); - } - } - }; - - var selHandle = new crosstalk.SelectionHandle(ctGroup); - selHandle.on("change", handleSelection); - setTimeout(function () { - handleFilter({ - value: filterHandle.filteredKeys - }); - handleSelection({ - value: selHandle.value - }); - }, 100); - } - - if (!ctg[ctKey]) ctg[ctKey] = []; - ctg[ctKey].push(stamp); - } // Add to container - - - if (!layerInfo.hidden) container.addLayer(layer); - return oldLayer; - } - }, { - key: "brush", - value: function brush(bounds, extraInfo) { - var _this2 = this; - - /* eslint-disable no-console */ - // For each Crosstalk group... - Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { - var ctg = _this2._byCrosstalkGroup[ctGroupName]; - var selection = []; // ...iterate over each Crosstalk key (each of which may have multiple - // layers)... - - Object.keys(ctg).forEach(function (ctKey) { - // ...and for each layer... - ctg[ctKey].forEach(function (stamp) { - var layerInfo = _this2._byStamp[stamp]; // ...if it's something with a point... - - if (layerInfo.layer.getLatLng) { - // ... and it's inside the selection bounds... - // TODO: Use pixel containment, not lat/lng containment - if (bounds.contains(layerInfo.layer.getLatLng())) { - // ...add the key to the selection. - selection.push(ctKey); - } - } + + preventUnintendedZoomOnScroll(map); // Store some state in the map object + + map.leafletr = { + // Has the map ever rendered successfully? + hasRendered: false, + // Data to be rendered when resize is called with area != 0 + pendingRenderData: null + }; // Check if the map is rendered statically (no output binding) + + if (_htmlwidgets2["default"].shinyMode && /\bshiny-bound-output\b/.test(el.className)) { + map.id = el.id; // Store the map on the element so we can find it later by ID + + (0, _jquery2["default"])(el).data("leaflet-map", map); // When the map is clicked, send the coordinates back to the app + + map.on("click", function (e) { + _shiny2["default"].onInputChange(map.id + "_click", { + lat: e.latlng.lat, + lng: e.latlng.lng, + ".nonce": Math.random() // Force reactivity if lat/lng hasn't changed + }); }); - new global.crosstalk.SelectionHandle(ctGroupName).set(selection, extraInfo); - }); - } - }, { - key: "unbrush", - value: function unbrush(extraInfo) { - Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { - new global.crosstalk.SelectionHandle(ctGroupName).clear(extraInfo); - }); - } - }, { - key: "_setVisibility", - value: function _setVisibility(layerInfo, visible) { - if (layerInfo.hidden ^ visible) { + var groupTimerId = null; + map.on("moveend", function (e) { + updateBounds(e.target); + }).on("layeradd layerremove", function (e) { + // If the layer that's coming or going is a group we created, tell + // the server. + if (map.layerManager.getGroupNameFromLayerGroup(e.layer)) { + // But to avoid chattiness, coalesce events + if (groupTimerId) { + clearTimeout(groupTimerId); + groupTimerId = null; + } + + groupTimerId = setTimeout(function () { + groupTimerId = null; + + _shiny2["default"].onInputChange(map.id + "_groups", map.layerManager.getVisibleGroups()); + }, 100); + } + }); + } + + this.doRenderValue(data, map); + }, + doRenderValue: function doRenderValue(data, map) { + // Leaflet does not behave well when you set up a bunch of layers when + // the map is not visible (width/height == 0). Popups get misaligned + // relative to their owning markers, and the fitBounds calculations + // are off. Therefore we wait until the map is actually showing to + // render the value (we rely on the resize() callback being invoked + // at the appropriate time). + if (el.offsetWidth === 0 || el.offsetHeight === 0) { + map.leafletr.pendingRenderData = data; return; - } else if (visible) { - layerInfo.container.addLayer(layerInfo.layer); - layerInfo.hidden = false; + } + + map.leafletr.pendingRenderData = null; // Merge data options into defaults + + var options = _jquery2["default"].extend({ + zoomToLimits: "always" + }, data.options); + + if (!map.layerManager) { + map.controls = new _controlStore2["default"](map); + map.layerManager = new _layerManager2["default"](map); } else { - layerInfo.container.removeLayer(layerInfo.layer); - layerInfo.hidden = true; + map.controls.clear(); + map.layerManager.clear(); } - } - }, { - key: "_setOpacity", - value: function _setOpacity(layerInfo, opacity) { - if (layerInfo.layer.setOpacity) { - layerInfo.layer.setOpacity(opacity); - } else if (layerInfo.layer.setStyle) { - layerInfo.layer.setStyle({ - opacity: opacity * layerInfo.layer.options.origOpacity, - fillOpacity: opacity * layerInfo.layer.options.origFillOpacity - }); + + var explicitView = false; + + if (data.setView) { + explicitView = true; + map.setView.apply(map, data.setView); } - } - }, { - key: "getLayer", - value: function getLayer(category, layerId) { - return this._byLayerId[this._layerIdKey(category, layerId)]; - } - }, { - key: "removeLayer", - value: function removeLayer(category, layerIds) { - var _this3 = this; - - // Find layer info - _jquery2["default"].each((0, _util.asArray)(layerIds), function (i, layerId) { - var layer = _this3._byLayerId[_this3._layerIdKey(category, layerId)]; - - if (layer) { - _this3._removeLayer(layer); + + if (data.fitBounds) { + explicitView = true; + methods.fitBounds.apply(map, data.fitBounds); + } + + if (data.flyTo) { + if (!explicitView && !map.leafletr.hasRendered) { + // must be done to give a initial starting point + map.fitWorld(); } - }); - } - }, { - key: "clearLayers", - value: function clearLayers(category) { - var _this4 = this; - - // Find all layers in _byCategory[category] - var catTable = this._byCategory[category]; - - if (!catTable) { - return false; - } // Remove all layers. Make copy of keys to avoid mutating the collection - // behind the iterator you're accessing. - - - var stamps = []; - - _jquery2["default"].each(catTable, function (k, v) { - stamps.push(k); - }); - - _jquery2["default"].each(stamps, function (i, stamp) { - _this4._removeLayer(stamp); - }); - } - }, { - key: "getLayerGroup", - value: function getLayerGroup(group, ensureExists) { - var g = this._groupContainers[group]; - - if (ensureExists && !g) { - this._byGroup[group] = this._byGroup[group] || {}; - g = this._groupContainers[group] = _leaflet2["default"].featureGroup(); - g.groupname = group; - g.addTo(this._map); + + explicitView = true; + map.flyTo.apply(map, data.flyTo); } - - return g; - } - }, { - key: "getGroupNameFromLayerGroup", - value: function getGroupNameFromLayerGroup(layerGroup) { - return layerGroup.groupname; - } - }, { - key: "getVisibleGroups", - value: function getVisibleGroups() { - var _this5 = this; - - var result = []; - - _jquery2["default"].each(this._groupContainers, function (k, v) { - if (_this5._map.hasLayer(v)) { - result.push(k); + + if (data.flyToBounds) { + if (!explicitView && !map.leafletr.hasRendered) { + // must be done to give a initial starting point + map.fitWorld(); } - }); - - return result; - } - }, { - key: "getAllGroupNames", - value: function getAllGroupNames() { - var result = []; - - _jquery2["default"].each(this._groupContainers, function (k, v) { - result.push(k); - }); - - return result; - } - }, { - key: "clearGroup", - value: function clearGroup(group) { - var _this6 = this; - - // Find all layers in _byGroup[group] - var groupTable = this._byGroup[group]; - - if (!groupTable) { - return false; - } // Remove all layers. Make copy of keys to avoid mutating the collection - // behind the iterator you're accessing. - - - var stamps = []; - - _jquery2["default"].each(groupTable, function (k, v) { - stamps.push(k); - }); - - _jquery2["default"].each(stamps, function (i, stamp) { - _this6._removeLayer(stamp); - }); - } - }, { - key: "clear", - value: function clear() { - function clearLayerGroup(key, layerGroup) { - layerGroup.clearLayers(); - } // Clear all indices and layerGroups - - - this._byGroup = {}; - this._byCategory = {}; - this._byLayerId = {}; - this._byStamp = {}; - this._byCrosstalkGroup = {}; - - _jquery2["default"].each(this._categoryContainers, clearLayerGroup); - - this._categoryContainers = {}; - - _jquery2["default"].each(this._groupContainers, clearLayerGroup); - - this._groupContainers = {}; - } - }, { - key: "_removeLayer", - value: function _removeLayer(layer) { - var stamp; - - if (typeof layer === "string") { - stamp = layer; - } else { - stamp = _leaflet2["default"].Util.stamp(layer); + + explicitView = true; + methods.flyToBounds.apply(map, data.flyToBounds); } - - var layerInfo = this._byStamp[stamp]; - - if (!layerInfo) { - return false; + + if (data.options.center) { + explicitView = true; + } // Returns true if the zoomToLimits option says that the map should be + // zoomed to map elements. + + + function needsZoom() { + return options.zoomToLimits === "always" || options.zoomToLimits === "first" && !map.leafletr.hasRendered; } - - layerInfo.container.removeLayer(stamp); - - if (typeof layerInfo.group === "string") { - delete this._byGroup[layerInfo.group][stamp]; + + if (!explicitView && needsZoom() && !map.getZoom()) { + if (data.limits && !_jquery2["default"].isEmptyObject(data.limits)) { + // Use the natural limits of what's being drawn on the map + // If the size of the bounding box is 0, leaflet gets all weird + var pad = 0.006; + + if (data.limits.lat[0] === data.limits.lat[1]) { + data.limits.lat[0] = data.limits.lat[0] - pad; + data.limits.lat[1] = data.limits.lat[1] + pad; + } + + if (data.limits.lng[0] === data.limits.lng[1]) { + data.limits.lng[0] = data.limits.lng[0] - pad; + data.limits.lng[1] = data.limits.lng[1] + pad; + } + + map.fitBounds([[data.limits.lat[0], data.limits.lng[0]], [data.limits.lat[1], data.limits.lng[1]]]); + } else { + map.fitWorld(); + } } - - if (typeof layerInfo.layerId === "string") { - delete this._byLayerId[this._layerIdKey(layerInfo.category, layerInfo.layerId)]; + + for (var i = 0; data.calls && i < data.calls.length; i++) { + var call = data.calls[i]; + if (methods[call.method]) methods[call.method].apply(map, call.args);else (0, _util.log)("Unknown method " + call.method); } - - delete this._byCategory[layerInfo.category][stamp]; - delete this._byStamp[stamp]; - - if (layerInfo.ctGroup) { - var ctGroup = this._byCrosstalkGroup[layerInfo.ctGroup]; - var layersForKey = ctGroup[layerInfo.ctKey]; - var idx = layersForKey ? layersForKey.indexOf(stamp) : -1; - - if (idx >= 0) { - if (layersForKey.length === 1) { - delete ctGroup[layerInfo.ctKey]; - } else { - layersForKey.splice(idx, 1); - } + + map.leafletr.hasRendered = true; + + if (_htmlwidgets2["default"].shinyMode) { + setTimeout(function () { + updateBounds(map); + }, 1); + } + }, + resize: function resize(width, height) { + if (map) { + map.invalidateSize(); + + if (map.leafletr.pendingRenderData) { + this.doRenderValue(map.leafletr.pendingRenderData, map); } } } - }, { - key: "_layerIdKey", - value: function _layerIdKey(category, layerId) { - return category + "\n" + layerId; - } - }]); - - return LayerManager; - }(); - - exports["default"] = LayerManager; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{"./global/jquery":9,"./global/leaflet":10,"./util":17}],15:[function(require,module,exports){ - (function (global){(function (){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _jquery = require("./global/jquery"); - - var _jquery2 = _interopRequireDefault(_jquery); - - var _leaflet = require("./global/leaflet"); - - var _leaflet2 = _interopRequireDefault(_leaflet); - - var _shiny = require("./global/shiny"); - - var _shiny2 = _interopRequireDefault(_shiny); - - var _htmlwidgets = require("./global/htmlwidgets"); - - var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); - - var _util = require("./util"); - - var _crs_utils = require("./crs_utils"); - - var _dataframe = require("./dataframe"); - - var _dataframe2 = _interopRequireDefault(_dataframe); - - var _clusterLayerStore = require("./cluster-layer-store"); - - var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); - - var _mipmapper = require("./mipmapper"); - - var _mipmapper2 = _interopRequireDefault(_mipmapper); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } - - var methods = {}; - exports["default"] = methods; - - function mouseHandler(mapId, layerId, group, eventName, extraInfo) { - return function (e) { - if (!_htmlwidgets2["default"].shinyMode) return; - var latLng = e.target.getLatLng ? e.target.getLatLng() : e.latlng; - - if (latLng) { - // retrieve only lat, lon values to remove prototype - // and extra parameters added by 3rd party modules - // these objects are for json serialization, not javascript - var latLngVal = _leaflet2["default"].latLng(latLng); // make sure it has consistent shape - - - latLng = { - lat: latLngVal.lat, - lng: latLngVal.lng - }; - } - - var eventInfo = _jquery2["default"].extend({ - id: layerId, - ".nonce": Math.random() // force reactivity - - }, group !== null ? { - group: group - } : null, latLng, extraInfo); - - _shiny2["default"].onInputChange(mapId + "_" + eventName, eventInfo); }; } - - methods.mouseHandler = mouseHandler; - - methods.clearGroup = function (group) { - var _this = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, v) { - _this.layerManager.clearGroup(v); - }); - }; - - methods.setView = function (center, zoom, options) { - this.setView(center, zoom, options); - }; - - methods.fitBounds = function (lat1, lng1, lat2, lng2, options) { - this.fitBounds([[lat1, lng1], [lat2, lng2]], options); - }; - - methods.flyTo = function (center, zoom, options) { - this.flyTo(center, zoom, options); - }; - - methods.flyToBounds = function (lat1, lng1, lat2, lng2, options) { - this.flyToBounds([[lat1, lng1], [lat2, lng2]], options); - }; - - methods.setMaxBounds = function (lat1, lng1, lat2, lng2) { - this.setMaxBounds([[lat1, lng1], [lat2, lng2]]); - }; - - methods.addPopups = function (lat, lng, popup, layerId, group, options) { - var _this2 = this; - - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("popup", popup).col("layerId", layerId).col("group", group).cbind(options); - - var _loop = function _loop(i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { - (function () { - var popup = _leaflet2["default"].popup(df.get(i)).setLatLng([df.get(i, "lat"), df.get(i, "lng")]).setContent(df.get(i, "popup")); - - var thisId = df.get(i, "layerId"); - var thisGroup = df.get(i, "group"); - this.layerManager.addLayer(popup, "popup", thisId, thisGroup); - }).call(_this2); - } - }; - - for (var i = 0; i < df.nrow(); i++) { - _loop(i); +}); + +if (_htmlwidgets2["default"].shinyMode) { + _shiny2["default"].addCustomMessageHandler("leaflet-calls", function (data) { + var id = data.id; + var el = document.getElementById(id); + var map = el ? (0, _jquery2["default"])(el).data("leaflet-map") : null; + + if (!map) { + (0, _util.log)("Couldn't find map with id " + id); + return; + } // If the map has not rendered, stash the proposed `leafletProxy()` calls + // in `pendingRenderData.calls` to be run on display via `doRenderValue()`. + // This is necessary if the map has not been rendered. + // If new pendingRenderData is set via a new `leaflet()`, the previous calls will be discarded. + + + if (!map.leafletr.hasRendered) { + map.leafletr.pendingRenderData.calls = map.leafletr.pendingRenderData.calls.concat(data.calls); + return; } - }; - - methods.removePopup = function (layerId) { - this.layerManager.removeLayer("popup", layerId); - }; - - methods.clearPopups = function () { - this.layerManager.clearLayers("popup"); - }; - - methods.addTiles = function (urlTemplate, layerId, group, options) { - this.layerManager.addLayer(_leaflet2["default"].tileLayer(urlTemplate, options), "tile", layerId, group); - }; - - methods.removeTiles = function (layerId) { - this.layerManager.removeLayer("tile", layerId); - }; - - methods.clearTiles = function () { - this.layerManager.clearLayers("tile"); - }; - - methods.addWMSTiles = function (baseUrl, layerId, group, options) { - if (options && options.crs) { - options.crs = (0, _crs_utils.getCRS)(options.crs); - } - - this.layerManager.addLayer(_leaflet2["default"].tileLayer.wms(baseUrl, options), "tile", layerId, group); - }; // Given: - // {data: ["a", "b", "c"], index: [0, 1, 0, 2]} - // returns: - // ["a", "b", "a", "c"] - - - function unpackStrings(iconset) { - if (!iconset) { - return iconset; - } - - if (typeof iconset.index === "undefined") { - return iconset; - } - - iconset.data = (0, _util.asArray)(iconset.data); - iconset.index = (0, _util.asArray)(iconset.index); - return _jquery2["default"].map(iconset.index, function (e, i) { - return iconset.data[e]; - }); + + for (var i = 0; i < data.calls.length; i++) { + var call = data.calls[i]; + var args = call.args; + + for (var _i = 0; _i < call.evals.length; _i++) { + window.HTMLWidgets.evaluateStringMember(args, call.evals[_i]); + } + + if (call.dependencies) { + _shiny2["default"].renderDependencies(call.dependencies); + } + + if (methods[call.method]) methods[call.method].apply(map, args);else (0, _util.log)("Unknown method " + call.method); + } + }); +} + + +},{"./cluster-layer-store":1,"./control-store":2,"./crs_utils":3,"./dataframe":4,"./fixup-default-icon":5,"./fixup-default-tooltip":6,"./fixup-url-protocol":7,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./layer-manager":14,"./methods":15,"./util":17}],14:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports["default"] = undefined; + +var _jquery = require("./global/jquery"); + +var _jquery2 = _interopRequireDefault(_jquery); + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +var _util = require("./util"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var LayerManager = /*#__PURE__*/function () { + function LayerManager(map) { + _classCallCheck(this, LayerManager); + + this._map = map; // BEGIN layer indices + // {: {: layer}} + + this._byGroup = {}; // {: {: layer}} + + this._byCategory = {}; // {: layer} + + this._byLayerId = {}; // {: { + // "group": , + // "layerId": , + // "category": , + // "container": + // } + // } + + this._byStamp = {}; // {: {: [, , ...], ...}} + + this._byCrosstalkGroup = {}; // END layer indices + // {: L.layerGroup} + + this._categoryContainers = {}; // {: L.layerGroup} + + this._groupContainers = {}; } - - function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { - function updateClusterMarkers(filtered, ctGroup, clusterGroup, ctKeyColumn) { - // For safety, check required parameters - if (!filtered) { - return; - } - - if (!clusterGroup) { - return; - } // Handle both null/undefined and empty array cases for filter values - - - let filterValues = filtered.value && Array.isArray(filtered.value) ? filtered.value.map(function (v) { - return String(v); - }) : []; - - let showAllMarkers = false; - if (filterValues.length === 0) { - showAllMarkers = true; - } - - - let options = Object.assign({}, clusterOptions); // Create a fresh cluster - - let newClusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(options); - - if (options.freezeAtZoom) { - let freezeAtZoom = options.freezeAtZoom; - newClusterGroup.freezeAtZoom(freezeAtZoom); - } - - newClusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](newClusterGroup); // If showing all markers, use all rows; otherwise filter - - let rowsToShow = []; - - for (let i = 0; i < df.nrow(); i++) { - // When showing all markers OR marker key is in filter values - let ctKey = String(df.get(i, ctKeyColumn) || ""); - let shouldShow = showAllMarkers || filterValues.includes(ctKey); - - if (shouldShow) { - rowsToShow.push(i); + + _createClass(LayerManager, [{ + key: "addLayer", + value: function addLayer(layer, category, layerId, group, ctGroup, ctKey) { + var _this = this; + + // Was a group provided? + var hasId = typeof layerId === "string"; + var grouped = typeof group === "string"; + var stamp = _leaflet2["default"].Util.stamp(layer) + ""; // This will be the default layer group to add the layer to. + // We may overwrite this let before using it (i.e. if a group is assigned). + // This one liner creates the _categoryContainers[category] entry if it + // doesn't already exist. + + var container = this._categoryContainers[category] = this._categoryContainers[category] || _leaflet2["default"].layerGroup().addTo(this._map); + + var oldLayer = null; + + if (hasId) { + // First, remove any layer with the same category and layerId + var prefixedLayerId = this._layerIdKey(category, layerId); + + oldLayer = this._byLayerId[prefixedLayerId]; + + if (oldLayer) { + this._removeLayer(oldLayer); + } // Update layerId index + + + this._byLayerId[prefixedLayerId] = layer; + } // Update group index + + + if (grouped) { + this._byGroup[group] = this._byGroup[group] || {}; + this._byGroup[group][stamp] = layer; // Since a group is assigned, don't add the layer to the category's layer + // group; instead, use the group's layer group. + // This one liner creates the _groupContainers[group] entry if it doesn't + // already exist. + + container = this.getLayerGroup(group, true); + } // Update category index + + + this._byCategory[category] = this._byCategory[category] || {}; + this._byCategory[category][stamp] = layer; // Update stamp index + + var layerInfo = this._byStamp[stamp] = { + layer: layer, + group: group, + ctGroup: ctGroup, + ctKey: ctKey, + layerId: layerId, + category: category, + container: container, + hidden: false + }; // Update crosstalk group index + + if (ctGroup) { + if (layer.setStyle) { + // Need to save this info so we know what to set opacity to later + layer.options.origOpacity = typeof layer.options.opacity !== "undefined" ? layer.options.opacity : 0.5; + layer.options.origFillOpacity = typeof layer.options.fillOpacity !== "undefined" ? layer.options.fillOpacity : 0.2; } - } - - - for (let _i = 0, _rowsToShow = rowsToShow; _i < _rowsToShow.length; _i++) { - let _i2 = _rowsToShow[_i]; - - if (_jquery2["default"].isNumeric(df.get(_i2, "lat")) && _jquery2["default"].isNumeric(df.get(_i2, "lng"))) { - // Create a fresh marker - safer than trying to reuse existing ones - let marker = markerFunc(df, _i2); - let thisId = df.get(_i2, "layerId"); // Add popup content if in the dataframe - - let popup = df.get(_i2, "popup"); - let popupOptions = df.get(_i2, "popupOptions"); - - if (popup !== null) { - if (popupOptions !== null) { - marker.bindPopup(popup, popupOptions); - } else { - marker.bindPopup(popup); - } - } // Add label/tooltip if in the dataframe - - - let label = df.get(_i2, "label"); - let labelOptions = df.get(_i2, "labelOptions"); - - if (label !== null) { - if (labelOptions !== null) { - if (labelOptions.permanent) { - marker.bindTooltip(label, labelOptions).openTooltip(); - } else { - marker.bindTooltip(label, labelOptions); + + var ctg = this._byCrosstalkGroup[ctGroup]; + + if (!ctg) { + ctg = this._byCrosstalkGroup[ctGroup] = {}; + var crosstalk = global.crosstalk; + + var handleFilter = function handleFilter(e) { + if (!e.value) { + var groupKeys = Object.keys(ctg); + + for (var i = 0; i < groupKeys.length; i++) { + var key = groupKeys[i]; + var _layerInfo = _this._byStamp[ctg[key]]; + + _this._setVisibility(_layerInfo, true); } } else { - marker.bindTooltip(label); + var selectedKeys = {}; + + for (var _i = 0; _i < e.value.length; _i++) { + selectedKeys[e.value[_i]] = true; + } + + var _groupKeys = Object.keys(ctg); + + for (var _i2 = 0; _i2 < _groupKeys.length; _i2++) { + var _key = _groupKeys[_i2]; + var _layerInfo2 = _this._byStamp[ctg[_key]]; + + _this._setVisibility(_layerInfo2, selectedKeys[_groupKeys[_i2]]); + } } - } // Add to cluster with appropriate ID - - - newClusterGroup.clusterLayerStore.add(marker, thisId); - } - } // Replace the old cluster group with the new one - - - map.removeLayer(clusterGroup); - map.addLayer(newClusterGroup); // Update the layer manager reference to the new cluster group - - map.layerManager.removeLayer("cluster", clusterId); - map.layerManager.addLayer(newClusterGroup, "cluster", clusterId, group); - return newClusterGroup; - } - - (function () { - var _this3 = this; - - var clusterGroup = this.layerManager.getLayer("cluster", clusterId), - cluster = clusterOptions !== null; - - if (cluster && !clusterGroup) { - clusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(clusterOptions); - - if (clusterOptions.freezeAtZoom) { - var freezeAtZoom = clusterOptions.freezeAtZoom; - delete clusterOptions.freezeAtZoom; - clusterGroup.freezeAtZoom(freezeAtZoom); - } - - clusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](clusterGroup); - } - - var extraInfo = cluster ? { - clusterId: clusterId - } : {}; - - var _loop2 = function _loop2(i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { - (function () { - var marker = markerFunc(df, i); - var thisId = df.get(i, "layerId"); - var thisGroup = cluster ? null : df.get(i, "group"); - - if (cluster) { - clusterGroup.clusterLayerStore.add(marker, thisId); + }; + + var filterHandle = new crosstalk.FilterHandle(ctGroup); + filterHandle.on("change", handleFilter); + + var handleSelection = function handleSelection(e) { + if (!e.value || !e.value.length) { + var groupKeys = Object.keys(ctg); + + for (var i = 0; i < groupKeys.length; i++) { + var key = groupKeys[i]; + var _layerInfo3 = _this._byStamp[ctg[key]]; + + _this._setOpacity(_layerInfo3, 1.0); + } } else { - this.layerManager.addLayer(marker, "marker", thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); - } - - var popup = df.get(i, "popup"); - var popupOptions = df.get(i, "popupOptions"); - - if (popup !== null) { - if (popupOptions !== null) { - marker.bindPopup(popup, popupOptions); - } else { - marker.bindPopup(popup); + var selectedKeys = {}; + + for (var _i3 = 0; _i3 < e.value.length; _i3++) { + selectedKeys[e.value[_i3]] = true; } - } - - var label = df.get(i, "label"); - var labelOptions = df.get(i, "labelOptions"); - - if (label !== null) { - if (labelOptions !== null) { - if (labelOptions.permanent) { - marker.bindTooltip(label, labelOptions).openTooltip(); - } else { - marker.bindTooltip(label, labelOptions); - } - } else { - marker.bindTooltip(label); + + var _groupKeys2 = Object.keys(ctg); + + for (var _i4 = 0; _i4 < _groupKeys2.length; _i4++) { + var _key2 = _groupKeys2[_i4]; + var _layerInfo4 = _this._byStamp[ctg[_key2]]; + + _this._setOpacity(_layerInfo4, selectedKeys[_groupKeys2[_i4]] ? 1.0 : 0.2); } } - - marker.on("click", mouseHandler(this.id, thisId, thisGroup, "marker_click", extraInfo), this); - marker.on("mouseover", mouseHandler(this.id, thisId, thisGroup, "marker_mouseover", extraInfo), this); - marker.on("mouseout", mouseHandler(this.id, thisId, thisGroup, "marker_mouseout", extraInfo), this); - marker.on("dragend", mouseHandler(this.id, thisId, thisGroup, "marker_dragend", extraInfo), this); - }).call(_this3); - } - }; - - for (var i = 0; i < df.nrow(); i++) { - _loop2(i); - } - - if (cluster && df.get(0, "ctGroup")) { - let ctGroup = df.get(0, "ctGroup"); - let ctKeyColumn = "ctKey"; - - // Check if global crosstalk is available - if (window.crosstalk || global.crosstalk) { - let crosstalkObj = window.crosstalk || global.crosstalk; // Create a proper filter handle instead of using group().on() - - let filterHandle = new crosstalkObj.FilterHandle(ctGroup); // Subscribe to filter changes using the handle - - filterHandle.on("change", function (e) { - let newClusterGroup = updateClusterMarkers(e, ctGroup, clusterGroup, ctKeyColumn); - - if (newClusterGroup) { - clusterGroup = newClusterGroup; - } - }); // Initial setup - - - if (filterHandle.filteredKeys) { - let newClusterGroup = updateClusterMarkers({ + }; + + var selHandle = new crosstalk.SelectionHandle(ctGroup); + selHandle.on("change", handleSelection); + setTimeout(function () { + handleFilter({ value: filterHandle.filteredKeys - }, ctGroup, clusterGroup, ctKeyColumn); - - if (newClusterGroup) { - clusterGroup = newClusterGroup; - } - } + }); + handleSelection({ + value: selHandle.value + }); + }, 100); } + + if (!ctg[ctKey]) ctg[ctKey] = []; + ctg[ctKey].push(stamp); + } // Add to container + + + if (!layerInfo.hidden) container.addLayer(layer); + return oldLayer; + } + }, { + key: "brush", + value: function brush(bounds, extraInfo) { + var _this2 = this; + + /* eslint-disable no-console */ + // For each Crosstalk group... + Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { + var ctg = _this2._byCrosstalkGroup[ctGroupName]; + var selection = []; // ...iterate over each Crosstalk key (each of which may have multiple + // layers)... + + Object.keys(ctg).forEach(function (ctKey) { + // ...and for each layer... + ctg[ctKey].forEach(function (stamp) { + var layerInfo = _this2._byStamp[stamp]; // ...if it's something with a point... + + if (layerInfo.layer.getLatLng) { + // ... and it's inside the selection bounds... + // TODO: Use pixel containment, not lat/lng containment + if (bounds.contains(layerInfo.layer.getLatLng())) { + // ...add the key to the selection. + selection.push(ctKey); + } + } + }); + }); + new global.crosstalk.SelectionHandle(ctGroupName).set(selection, extraInfo); + }); + } + }, { + key: "unbrush", + value: function unbrush(extraInfo) { + Object.keys(this._byCrosstalkGroup).forEach(function (ctGroupName) { + new global.crosstalk.SelectionHandle(ctGroupName).clear(extraInfo); + }); + } + }, { + key: "_setVisibility", + value: function _setVisibility(layerInfo, visible) { + if (layerInfo.hidden ^ visible) { + return; + } else if (visible) { + layerInfo.container.addLayer(layerInfo.layer); + layerInfo.hidden = false; + } else { + layerInfo.container.removeLayer(layerInfo.layer); + layerInfo.hidden = true; } - - if (cluster) { - this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); + } + }, { + key: "_setOpacity", + value: function _setOpacity(layerInfo, opacity) { + if (layerInfo.layer.setOpacity) { + layerInfo.layer.setOpacity(opacity); + } else if (layerInfo.layer.setStyle) { + layerInfo.layer.setStyle({ + opacity: opacity * layerInfo.layer.options.origOpacity, + fillOpacity: opacity * layerInfo.layer.options.origFillOpacity + }); } - }).call(map); - } - - methods.addGenericMarkers = addMarkers; - - methods.addMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { - var icondf; - var getIcon; - - if (icon) { - // Unpack icons - icon.iconUrl = unpackStrings(icon.iconUrl); - icon.iconRetinaUrl = unpackStrings(icon.iconRetinaUrl); - icon.shadowUrl = unpackStrings(icon.shadowUrl); - icon.shadowRetinaUrl = unpackStrings(icon.shadowRetinaUrl); // This cbinds the icon URLs and any other icon options; they're all - // present on the icon object. - - icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. - - getIcon = function getIcon(i) { - var opts = icondf.get(i); - - if (!opts.iconUrl) { - return new _leaflet2["default"].Icon.Default(); - } // Composite options (like points or sizes) are passed from R with each - // individual component as its own option. We need to combine them now - // into their composite form. - - - if (opts.iconWidth) { - opts.iconSize = [opts.iconWidth, opts.iconHeight]; - } - - if (opts.shadowWidth) { - opts.shadowSize = [opts.shadowWidth, opts.shadowHeight]; - } - - if (opts.iconAnchorX) { - opts.iconAnchor = [opts.iconAnchorX, opts.iconAnchorY]; - } - - if (opts.shadowAnchorX) { - opts.shadowAnchor = [opts.shadowAnchorX, opts.shadowAnchorY]; - } - - if (opts.popupAnchorX) { - opts.popupAnchor = [opts.popupAnchorX, opts.popupAnchorY]; + } + }, { + key: "getLayer", + value: function getLayer(category, layerId) { + return this._byLayerId[this._layerIdKey(category, layerId)]; + } + }, { + key: "removeLayer", + value: function removeLayer(category, layerIds) { + var _this3 = this; + + // Find layer info + _jquery2["default"].each((0, _util.asArray)(layerIds), function (i, layerId) { + var layer = _this3._byLayerId[_this3._layerIdKey(category, layerId)]; + + if (layer) { + _this3._removeLayer(layer); } - - return new _leaflet2["default"].Icon(opts); - }; + }); } - - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); - if (icon) icondf.effectiveLength = df.nrow(); - addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { - var options = df.get(i); - if (icon) options.icon = getIcon(i); - return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }, { + key: "clearLayers", + value: function clearLayers(category) { + var _this4 = this; + + // Find all layers in _byCategory[category] + var catTable = this._byCategory[category]; + + if (!catTable) { + return false; + } // Remove all layers. Make copy of keys to avoid mutating the collection + // behind the iterator you're accessing. + + + var stamps = []; + + _jquery2["default"].each(catTable, function (k, v) { + stamps.push(k); + }); + + _jquery2["default"].each(stamps, function (i, stamp) { + _this4._removeLayer(stamp); }); } - }; - - methods.addAwesomeMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { - var icondf; - var getIcon; - - if (icon) { - // This cbinds the icon URLs and any other icon options; they're all - // present on the icon object. - icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. - - getIcon = function getIcon(i) { - var opts = icondf.get(i); - - if (!opts) { - return new _leaflet2["default"].AwesomeMarkers.icon(); - } - - if (opts.squareMarker) { - opts.className = "awesome-marker awesome-marker-square"; + }, { + key: "getLayerGroup", + value: function getLayerGroup(group, ensureExists) { + var g = this._groupContainers[group]; + + if (ensureExists && !g) { + this._byGroup[group] = this._byGroup[group] || {}; + g = this._groupContainers[group] = _leaflet2["default"].featureGroup(); + g.groupname = group; + g.addTo(this._map); + } + + return g; + } + }, { + key: "getGroupNameFromLayerGroup", + value: function getGroupNameFromLayerGroup(layerGroup) { + return layerGroup.groupname; + } + }, { + key: "getVisibleGroups", + value: function getVisibleGroups() { + var _this5 = this; + + var result = []; + + _jquery2["default"].each(this._groupContainers, function (k, v) { + if (_this5._map.hasLayer(v)) { + result.push(k); } - - return new _leaflet2["default"].AwesomeMarkers.icon(opts); - }; + }); + + return result; } - - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); - if (icon) icondf.effectiveLength = df.nrow(); - addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { - var options = df.get(i); - if (icon) options.icon = getIcon(i); - return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }, { + key: "getAllGroupNames", + value: function getAllGroupNames() { + var result = []; + + _jquery2["default"].each(this._groupContainers, function (k, v) { + result.push(k); }); + + return result; } + }, { + key: "clearGroup", + value: function clearGroup(group) { + var _this6 = this; + + // Find all layers in _byGroup[group] + var groupTable = this._byGroup[group]; + + if (!groupTable) { + return false; + } // Remove all layers. Make copy of keys to avoid mutating the collection + // behind the iterator you're accessing. + + + var stamps = []; + + _jquery2["default"].each(groupTable, function (k, v) { + stamps.push(k); + }); + + _jquery2["default"].each(stamps, function (i, stamp) { + _this6._removeLayer(stamp); + }); + } + }, { + key: "clear", + value: function clear() { + function clearLayerGroup(key, layerGroup) { + layerGroup.clearLayers(); + } // Clear all indices and layerGroups + + + this._byGroup = {}; + this._byCategory = {}; + this._byLayerId = {}; + this._byStamp = {}; + this._byCrosstalkGroup = {}; + + _jquery2["default"].each(this._categoryContainers, clearLayerGroup); + + this._categoryContainers = {}; + + _jquery2["default"].each(this._groupContainers, clearLayerGroup); + + this._groupContainers = {}; + } + }, { + key: "_removeLayer", + value: function _removeLayer(layer) { + var stamp; + + if (typeof layer === "string") { + stamp = layer; + } else { + stamp = _leaflet2["default"].Util.stamp(layer); + } + + var layerInfo = this._byStamp[stamp]; + + if (!layerInfo) { + return false; + } + + layerInfo.container.removeLayer(stamp); + + if (typeof layerInfo.group === "string") { + delete this._byGroup[layerInfo.group][stamp]; + } + + if (typeof layerInfo.layerId === "string") { + delete this._byLayerId[this._layerIdKey(layerInfo.category, layerInfo.layerId)]; + } + + delete this._byCategory[layerInfo.category][stamp]; + delete this._byStamp[stamp]; + + if (layerInfo.ctGroup) { + var ctGroup = this._byCrosstalkGroup[layerInfo.ctGroup]; + var layersForKey = ctGroup[layerInfo.ctKey]; + var idx = layersForKey ? layersForKey.indexOf(stamp) : -1; + + if (idx >= 0) { + if (layersForKey.length === 1) { + delete ctGroup[layerInfo.ctKey]; + } else { + layersForKey.splice(idx, 1); + } + } + } + } + }, { + key: "_layerIdKey", + value: function _layerIdKey(category, layerId) { + return category + "\n" + layerId; + } + }]); + + return LayerManager; +}(); + +exports["default"] = LayerManager; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./global/jquery":9,"./global/leaflet":10,"./util":17}],15:[function(require,module,exports){ +(function (global){(function (){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _jquery = require("./global/jquery"); + +var _jquery2 = _interopRequireDefault(_jquery); + +var _leaflet = require("./global/leaflet"); + +var _leaflet2 = _interopRequireDefault(_leaflet); + +var _shiny = require("./global/shiny"); + +var _shiny2 = _interopRequireDefault(_shiny); + +var _htmlwidgets = require("./global/htmlwidgets"); + +var _htmlwidgets2 = _interopRequireDefault(_htmlwidgets); + +var _util = require("./util"); + +var _crs_utils = require("./crs_utils"); + +var _dataframe = require("./dataframe"); + +var _dataframe2 = _interopRequireDefault(_dataframe); + +var _clusterLayerStore = require("./cluster-layer-store"); + +var _clusterLayerStore2 = _interopRequireDefault(_clusterLayerStore); + +var _mipmapper = require("./mipmapper"); + +var _mipmapper2 = _interopRequireDefault(_mipmapper); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var methods = {}; +exports["default"] = methods; + +function mouseHandler(mapId, layerId, group, eventName, extraInfo) { + return function (e) { + if (!_htmlwidgets2["default"].shinyMode) return; + var latLng = e.target.getLatLng ? e.target.getLatLng() : e.latlng; + + if (latLng) { + // retrieve only lat, lon values to remove prototype + // and extra parameters added by 3rd party modules + // these objects are for json serialization, not javascript + var latLngVal = _leaflet2["default"].latLng(latLng); // make sure it has consistent shape + + + latLng = { + lat: latLngVal.lat, + lng: latLngVal.lng + }; + } + + var eventInfo = _jquery2["default"].extend({ + id: layerId, + ".nonce": Math.random() // force reactivity + + }, group !== null ? { + group: group + } : null, latLng, extraInfo); + + _shiny2["default"].onInputChange(mapId + "_" + eventName, eventInfo); }; - - function addLayers(map, category, df, layerFunc) { - var _loop3 = function _loop3(i) { +} + +methods.mouseHandler = mouseHandler; + +methods.clearGroup = function (group) { + var _this = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, v) { + _this.layerManager.clearGroup(v); + }); +}; + +methods.setView = function (center, zoom, options) { + this.setView(center, zoom, options); +}; + +methods.fitBounds = function (lat1, lng1, lat2, lng2, options) { + this.fitBounds([[lat1, lng1], [lat2, lng2]], options); +}; + +methods.flyTo = function (center, zoom, options) { + this.flyTo(center, zoom, options); +}; + +methods.flyToBounds = function (lat1, lng1, lat2, lng2, options) { + this.flyToBounds([[lat1, lng1], [lat2, lng2]], options); +}; + +methods.setMaxBounds = function (lat1, lng1, lat2, lng2) { + this.setMaxBounds([[lat1, lng1], [lat2, lng2]]); +}; + +methods.addPopups = function (lat, lng, popup, layerId, group, options) { + var _this2 = this; + + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("popup", popup).col("layerId", layerId).col("group", group).cbind(options); + + var _loop = function _loop(i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { (function () { - var layer = layerFunc(df, i); - - if (!_jquery2["default"].isEmptyObject(layer)) { - var thisId = df.get(i, "layerId"); - var thisGroup = df.get(i, "group"); - this.layerManager.addLayer(layer, category, thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); - - if (layer.bindPopup) { - var popup = df.get(i, "popup"); - var popupOptions = df.get(i, "popupOptions"); - - if (popup !== null) { - if (popupOptions !== null) { - layer.bindPopup(popup, popupOptions); - } else { - layer.bindPopup(popup); - } + var popup = _leaflet2["default"].popup(df.get(i)).setLatLng([df.get(i, "lat"), df.get(i, "lng")]).setContent(df.get(i, "popup")); + + var thisId = df.get(i, "layerId"); + var thisGroup = df.get(i, "group"); + this.layerManager.addLayer(popup, "popup", thisId, thisGroup); + }).call(_this2); + } + }; + + for (var i = 0; i < df.nrow(); i++) { + _loop(i); + } +}; + +methods.removePopup = function (layerId) { + this.layerManager.removeLayer("popup", layerId); +}; + +methods.clearPopups = function () { + this.layerManager.clearLayers("popup"); +}; + +methods.addTiles = function (urlTemplate, layerId, group, options) { + this.layerManager.addLayer(_leaflet2["default"].tileLayer(urlTemplate, options), "tile", layerId, group); +}; + +methods.removeTiles = function (layerId) { + this.layerManager.removeLayer("tile", layerId); +}; + +methods.clearTiles = function () { + this.layerManager.clearLayers("tile"); +}; + +methods.addWMSTiles = function (baseUrl, layerId, group, options) { + if (options && options.crs) { + options.crs = (0, _crs_utils.getCRS)(options.crs); + } + + this.layerManager.addLayer(_leaflet2["default"].tileLayer.wms(baseUrl, options), "tile", layerId, group); +}; // Given: +// {data: ["a", "b", "c"], index: [0, 1, 0, 2]} +// returns: +// ["a", "b", "a", "c"] + + +function unpackStrings(iconset) { + if (!iconset) { + return iconset; + } + + if (typeof iconset.index === "undefined") { + return iconset; + } + + iconset.data = (0, _util.asArray)(iconset.data); + iconset.index = (0, _util.asArray)(iconset.index); + return _jquery2["default"].map(iconset.index, function (e, i) { + return iconset.data[e]; + }); +} + +function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { + function updateClusterMarkers(filtered, ctGroup, clusterGroup, ctKeyColumn) { + // For safety, check required parameters + if (!filtered) { + return; + } + + if (!clusterGroup) { + return; + } // Handle both null/undefined and empty array cases for filter values + + + let filterValues = filtered.value && Array.isArray(filtered.value) ? filtered.value.map(function (v) { + return String(v); + }) : []; + + let showAllMarkers = false; + if (filterValues.length === 0) { + showAllMarkers = true; + } + + + let options = Object.assign({}, clusterOptions); // Create a fresh cluster + + let newClusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(options); + + if (options.freezeAtZoom) { + let freezeAtZoom = options.freezeAtZoom; + newClusterGroup.freezeAtZoom(freezeAtZoom); + } + + newClusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](newClusterGroup); // If showing all markers, use all rows; otherwise filter + + let rowsToShow = []; + + for (let i = 0; i < df.nrow(); i++) { + // When showing all markers OR marker key is in filter values + let ctKey = String(df.get(i, ctKeyColumn) || ""); + let shouldShow = showAllMarkers || filterValues.includes(ctKey); + + if (shouldShow) { + rowsToShow.push(i); + } + } + + + for (let _i = 0, _rowsToShow = rowsToShow; _i < _rowsToShow.length; _i++) { + let _i2 = _rowsToShow[_i]; + + if (_jquery2["default"].isNumeric(df.get(_i2, "lat")) && _jquery2["default"].isNumeric(df.get(_i2, "lng"))) { + // Create a fresh marker - safer than trying to reuse existing ones + let marker = markerFunc(df, _i2); + let thisId = df.get(_i2, "layerId"); // Add popup content if in the dataframe + + let popup = df.get(_i2, "popup"); + let popupOptions = df.get(_i2, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); + } + } // Add label/tooltip if in the dataframe + + + let label = df.get(_i2, "label"); + let labelOptions = df.get(_i2, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); + } + } else { + marker.bindTooltip(label); + } + } // Add to cluster with appropriate ID + + + newClusterGroup.clusterLayerStore.add(marker, thisId); + } + } // Replace the old cluster group with the new one + + + map.removeLayer(clusterGroup); + map.addLayer(newClusterGroup); // Update the layer manager reference to the new cluster group + + map.layerManager.removeLayer("cluster", clusterId); + map.layerManager.addLayer(newClusterGroup, "cluster", clusterId, group); + return newClusterGroup; + } + + (function () { + var _this3 = this; + + var clusterGroup = this.layerManager.getLayer("cluster", clusterId), + cluster = clusterOptions !== null; + + if (cluster && !clusterGroup) { + clusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(clusterOptions); + + if (clusterOptions.freezeAtZoom) { + var freezeAtZoom = clusterOptions.freezeAtZoom; + delete clusterOptions.freezeAtZoom; + clusterGroup.freezeAtZoom(freezeAtZoom); + } + + clusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](clusterGroup); + } + + var extraInfo = cluster ? { + clusterId: clusterId + } : {}; + + var _loop2 = function _loop2(i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng"))) { + (function () { + var marker = markerFunc(df, i); + var thisId = df.get(i, "layerId"); + var thisGroup = cluster ? null : df.get(i, "group"); + + if (cluster) { + clusterGroup.clusterLayerStore.add(marker, thisId); + } else { + this.layerManager.addLayer(marker, "marker", thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); + } + + var popup = df.get(i, "popup"); + var popupOptions = df.get(i, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); } } - - if (layer.bindTooltip) { - var label = df.get(i, "label"); - var labelOptions = df.get(i, "labelOptions"); - - if (label !== null) { - if (labelOptions !== null) { - layer.bindTooltip(label, labelOptions); + + var label = df.get(i, "label"); + var labelOptions = df.get(i, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); } else { - layer.bindTooltip(label); + marker.bindTooltip(label, labelOptions); } + } else { + marker.bindTooltip(label); } } - - layer.on("click", mouseHandler(this.id, thisId, thisGroup, category + "_click"), this); - layer.on("mouseover", mouseHandler(this.id, thisId, thisGroup, category + "_mouseover"), this); - layer.on("mouseout", mouseHandler(this.id, thisId, thisGroup, category + "_mouseout"), this); - var highlightStyle = df.get(i, "highlightOptions"); - - if (!_jquery2["default"].isEmptyObject(highlightStyle)) { - var defaultStyle = {}; - - _jquery2["default"].each(highlightStyle, function (k, v) { - if (k != "bringToFront" && k != "sendToBack") { - if (df.get(i, k)) { - defaultStyle[k] = df.get(i, k); - } - } - }); - - layer.on("mouseover", function (e) { - this.setStyle(highlightStyle); - - if (highlightStyle.bringToFront) { - this.bringToFront(); - } - }); - layer.on("mouseout", function (e) { - this.setStyle(defaultStyle); - - if (highlightStyle.sendToBack) { - this.bringToBack(); - } - }); - } - } - }).call(map); + + marker.on("click", mouseHandler(this.id, thisId, thisGroup, "marker_click", extraInfo), this); + marker.on("mouseover", mouseHandler(this.id, thisId, thisGroup, "marker_mouseover", extraInfo), this); + marker.on("mouseout", mouseHandler(this.id, thisId, thisGroup, "marker_mouseout", extraInfo), this); + marker.on("dragend", mouseHandler(this.id, thisId, thisGroup, "marker_dragend", extraInfo), this); + }).call(_this3); + } }; - + for (var i = 0; i < df.nrow(); i++) { - _loop3(i); + _loop2(i); } - } - - methods.addGenericLayers = addLayers; - - methods.addCircles = function (lat, lng, radius, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions, crosstalkOptions) { - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options).cbind(crosstalkOptions || {}); - addLayers(this, "shape", df, function (df, i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng")) && _jquery2["default"].isNumeric(df.get(i, "radius"))) { - return _leaflet2["default"].circle([df.get(i, "lat"), df.get(i, "lng")], df.get(i, "radius"), df.get(i)); - } else { - return null; + + if (cluster && df.get(0, "ctGroup")) { + let ctGroup = df.get(0, "ctGroup"); + let ctKeyColumn = "ctKey"; + + // Check if global crosstalk is available + if (window.crosstalk || global.crosstalk) { + let crosstalkObj = window.crosstalk || global.crosstalk; // Create a proper filter handle instead of using group().on() + + let filterHandle = new crosstalkObj.FilterHandle(ctGroup); // Subscribe to filter changes using the handle + + filterHandle.on("change", function (e) { + let newClusterGroup = updateClusterMarkers(e, ctGroup, clusterGroup, ctKeyColumn); + + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + }); // Initial setup + + + if (filterHandle.filteredKeys) { + let newClusterGroup = updateClusterMarkers({ + value: filterHandle.filteredKeys + }, ctGroup, clusterGroup, ctKeyColumn); + + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } } - }); + } } - }; - - methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, clusterOptions, clusterId, popup, popupOptions, label, labelOptions, crosstalkOptions) { - if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { - var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(crosstalkOptions || {}).cbind(options); - addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { - return _leaflet2["default"].circleMarker([df.get(i, "lat"), df.get(i, "lng")], df.get(i)); - }); + + if (cluster) { + this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); } - }; - /* - * @param lat Array of arrays of latitude coordinates for polylines - * @param lng Array of arrays of longitude coordinates for polylines - */ - - - methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { - if (polygons.length > 0) { - var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); - addLayers(this, "shape", df, function (df, i) { - var shapes = df.get(i, "shapes"); - shapes = shapes.map(function (shape) { - return _htmlwidgets2["default"].dataframeToD3(shape[0]); - }); - - if (shapes.length > 1) { - return _leaflet2["default"].polyline(shapes, df.get(i)); - } else { - return _leaflet2["default"].polyline(shapes[0], df.get(i)); + }).call(map); +} + +methods.addGenericMarkers = addMarkers; + +methods.addMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { + var icondf; + var getIcon; + + if (icon) { + // Unpack icons + icon.iconUrl = unpackStrings(icon.iconUrl); + icon.iconRetinaUrl = unpackStrings(icon.iconRetinaUrl); + icon.shadowUrl = unpackStrings(icon.shadowUrl); + icon.shadowRetinaUrl = unpackStrings(icon.shadowRetinaUrl); // This cbinds the icon URLs and any other icon options; they're all + // present on the icon object. + + icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. + + getIcon = function getIcon(i) { + var opts = icondf.get(i); + + if (!opts.iconUrl) { + return new _leaflet2["default"].Icon.Default(); + } // Composite options (like points or sizes) are passed from R with each + // individual component as its own option. We need to combine them now + // into their composite form. + + + if (opts.iconWidth) { + opts.iconSize = [opts.iconWidth, opts.iconHeight]; + } + + if (opts.shadowWidth) { + opts.shadowSize = [opts.shadowWidth, opts.shadowHeight]; + } + + if (opts.iconAnchorX) { + opts.iconAnchor = [opts.iconAnchorX, opts.iconAnchorY]; + } + + if (opts.shadowAnchorX) { + opts.shadowAnchor = [opts.shadowAnchorX, opts.shadowAnchorY]; + } + + if (opts.popupAnchorX) { + opts.popupAnchor = [opts.popupAnchorX, opts.popupAnchorY]; + } + + return new _leaflet2["default"].Icon(opts); + }; + } + + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); + if (icon) icondf.effectiveLength = df.nrow(); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + var options = df.get(i); + if (icon) options.icon = getIcon(i); + return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }); + } +}; + +methods.addAwesomeMarkers = function (lat, lng, icon, layerId, group, options, popup, popupOptions, clusterOptions, clusterId, label, labelOptions, crosstalkOptions) { + var icondf; + var getIcon; + + if (icon) { + // This cbinds the icon URLs and any other icon options; they're all + // present on the icon object. + icondf = new _dataframe2["default"]().cbind(icon); // Constructs an icon from a specified row of the icon dataframe. + + getIcon = function getIcon(i) { + var opts = icondf.get(i); + + if (!opts) { + return new _leaflet2["default"].AwesomeMarkers.icon(); + } + + if (opts.squareMarker) { + opts.className = "awesome-marker awesome-marker-square"; + } + + return new _leaflet2["default"].AwesomeMarkers.icon(opts); + }; + } + + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(options).cbind(crosstalkOptions || {}); + if (icon) icondf.effectiveLength = df.nrow(); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + var options = df.get(i); + if (icon) options.icon = getIcon(i); + return _leaflet2["default"].marker([df.get(i, "lat"), df.get(i, "lng")], options); + }); + } +}; + +function addLayers(map, category, df, layerFunc) { + var _loop3 = function _loop3(i) { + (function () { + var layer = layerFunc(df, i); + + if (!_jquery2["default"].isEmptyObject(layer)) { + var thisId = df.get(i, "layerId"); + var thisGroup = df.get(i, "group"); + this.layerManager.addLayer(layer, category, thisId, thisGroup, df.get(i, "ctGroup", true), df.get(i, "ctKey", true)); + + if (layer.bindPopup) { + var popup = df.get(i, "popup"); + var popupOptions = df.get(i, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + layer.bindPopup(popup, popupOptions); + } else { + layer.bindPopup(popup); + } + } } - }); - } - }; - - methods.removeMarker = function (layerId) { - this.layerManager.removeLayer("marker", layerId); - }; - - methods.clearMarkers = function () { - this.layerManager.clearLayers("marker"); - }; - - methods.removeMarkerCluster = function (layerId) { - this.layerManager.removeLayer("cluster", layerId); - }; - - methods.removeMarkerFromCluster = function (layerId, clusterId) { - var cluster = this.layerManager.getLayer("cluster", clusterId); - if (!cluster) return; - cluster.clusterLayerStore.remove(layerId); - }; - - methods.clearMarkerClusters = function () { - this.layerManager.clearLayers("cluster"); - }; - - methods.removeShape = function (layerId) { - this.layerManager.removeLayer("shape", layerId); - }; - - methods.clearShapes = function () { - this.layerManager.clearLayers("shape"); + + if (layer.bindTooltip) { + var label = df.get(i, "label"); + var labelOptions = df.get(i, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + layer.bindTooltip(label, labelOptions); + } else { + layer.bindTooltip(label); + } + } + } + + layer.on("click", mouseHandler(this.id, thisId, thisGroup, category + "_click"), this); + layer.on("mouseover", mouseHandler(this.id, thisId, thisGroup, category + "_mouseover"), this); + layer.on("mouseout", mouseHandler(this.id, thisId, thisGroup, category + "_mouseout"), this); + var highlightStyle = df.get(i, "highlightOptions"); + + if (!_jquery2["default"].isEmptyObject(highlightStyle)) { + var defaultStyle = {}; + + _jquery2["default"].each(highlightStyle, function (k, v) { + if (k != "bringToFront" && k != "sendToBack") { + if (df.get(i, k)) { + defaultStyle[k] = df.get(i, k); + } + } + }); + + layer.on("mouseover", function (e) { + this.setStyle(highlightStyle); + + if (highlightStyle.bringToFront) { + this.bringToFront(); + } + }); + layer.on("mouseout", function (e) { + this.setStyle(defaultStyle); + + if (highlightStyle.sendToBack) { + this.bringToBack(); + } + }); + } + } + }).call(map); }; - - methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { - var df = new _dataframe2["default"]().col("lat1", lat1).col("lng1", lng1).col("lat2", lat2).col("lng2", lng2).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + + for (var i = 0; i < df.nrow(); i++) { + _loop3(i); + } +} + +methods.addGenericLayers = addLayers; + +methods.addCircles = function (lat, lng, radius, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions, crosstalkOptions) { + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options).cbind(crosstalkOptions || {}); addLayers(this, "shape", df, function (df, i) { - if (_jquery2["default"].isNumeric(df.get(i, "lat1")) && _jquery2["default"].isNumeric(df.get(i, "lng1")) && _jquery2["default"].isNumeric(df.get(i, "lat2")) && _jquery2["default"].isNumeric(df.get(i, "lng2"))) { - return _leaflet2["default"].rectangle([[df.get(i, "lat1"), df.get(i, "lng1")], [df.get(i, "lat2"), df.get(i, "lng2")]], df.get(i)); + if (_jquery2["default"].isNumeric(df.get(i, "lat")) && _jquery2["default"].isNumeric(df.get(i, "lng")) && _jquery2["default"].isNumeric(df.get(i, "radius"))) { + return _leaflet2["default"].circle([df.get(i, "lat"), df.get(i, "lng")], df.get(i, "radius"), df.get(i)); } else { return null; } }); - }; - /* - * @param lat Array of arrays of latitude coordinates for polygons - * @param lng Array of arrays of longitude coordinates for polygons - */ - - - methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { - if (polygons.length > 0) { - var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); - addLayers(this, "shape", df, function (df, i) { - // This code used to use L.multiPolygon, but that caused - // double-click on a multipolygon to fail to zoom in on the - // map. Surprisingly, putting all the rings in a single - // polygon seems to still work; complicated multipolygons - // are still rendered correctly. - var shapes = df.get(i, "shapes").map(function (polygon) { - return polygon.map(_htmlwidgets2["default"].dataframeToD3); - }).reduce(function (acc, val) { - return acc.concat(val); - }, []); - return _leaflet2["default"].polygon(shapes, df.get(i)); + } +}; + +methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, clusterOptions, clusterId, popup, popupOptions, label, labelOptions, crosstalkOptions) { + if (!(_jquery2["default"].isEmptyObject(lat) || _jquery2["default"].isEmptyObject(lng)) || _jquery2["default"].isNumeric(lat) && _jquery2["default"].isNumeric(lng)) { + var df = new _dataframe2["default"]().col("lat", lat).col("lng", lng).col("radius", radius).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).cbind(crosstalkOptions || {}).cbind(options); + addMarkers(this, df, group, clusterOptions, clusterId, function (df, i) { + return _leaflet2["default"].circleMarker([df.get(i, "lat"), df.get(i, "lng")], df.get(i)); + }); + } +}; +/* + * @param lat Array of arrays of latitude coordinates for polylines + * @param lng Array of arrays of longitude coordinates for polylines + */ + + +methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + if (polygons.length > 0) { + var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + var shapes = df.get(i, "shapes"); + shapes = shapes.map(function (shape) { + return _htmlwidgets2["default"].dataframeToD3(shape[0]); }); - } - }; - - methods.addGeoJSON = function (data, layerId, group, style) { - // This time, self is actually needed because the callbacks below need - // to access both the inner and outer senses of "this" - var self = this; - - if (typeof data === "string") { - data = JSON.parse(data); - } - - var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); - - var gjlayer = _leaflet2["default"].geoJson(data, { - style: function style(feature) { - if (feature.style || feature.properties.style) { - return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); - } else { - return globalStyle; - } - }, - onEachFeature: function onEachFeature(feature, layer) { - var extraInfo = { - featureId: feature.id, - properties: feature.properties - }; - var popup = feature.properties ? feature.properties.popup : null; - if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); - layer.on("click", mouseHandler(self.id, layerId, group, "geojson_click", extraInfo), this); - layer.on("mouseover", mouseHandler(self.id, layerId, group, "geojson_mouseover", extraInfo), this); - layer.on("mouseout", mouseHandler(self.id, layerId, group, "geojson_mouseout", extraInfo), this); + + if (shapes.length > 1) { + return _leaflet2["default"].polyline(shapes, df.get(i)); + } else { + return _leaflet2["default"].polyline(shapes[0], df.get(i)); } }); - - this.layerManager.addLayer(gjlayer, "geojson", layerId, group); - }; - - methods.removeGeoJSON = function (layerId) { - this.layerManager.removeLayer("geojson", layerId); - }; - - methods.clearGeoJSON = function () { - this.layerManager.clearLayers("geojson"); - }; - - methods.addTopoJSON = function (data, layerId, group, style) { - // This time, self is actually needed because the callbacks below need - // to access both the inner and outer senses of "this" - var self = this; - - if (typeof data === "string") { - data = JSON.parse(data); - } - - var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); - - var gjlayer = _leaflet2["default"].geoJson(null, { - style: function style(feature) { - if (feature.style || feature.properties.style) { - return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); - } else { - return globalStyle; - } - }, - onEachFeature: function onEachFeature(feature, layer) { - var extraInfo = { - featureId: feature.id, - properties: feature.properties - }; - var popup = feature.properties.popup; - if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); - layer.on("click", mouseHandler(self.id, layerId, group, "topojson_click", extraInfo), this); - layer.on("mouseover", mouseHandler(self.id, layerId, group, "topojson_mouseover", extraInfo), this); - layer.on("mouseout", mouseHandler(self.id, layerId, group, "topojson_mouseout", extraInfo), this); - } + } +}; + +methods.removeMarker = function (layerId) { + this.layerManager.removeLayer("marker", layerId); +}; + +methods.clearMarkers = function () { + this.layerManager.clearLayers("marker"); +}; + +methods.removeMarkerCluster = function (layerId) { + this.layerManager.removeLayer("cluster", layerId); +}; + +methods.removeMarkerFromCluster = function (layerId, clusterId) { + var cluster = this.layerManager.getLayer("cluster", clusterId); + if (!cluster) return; + cluster.clusterLayerStore.remove(layerId); +}; + +methods.clearMarkerClusters = function () { + this.layerManager.clearLayers("cluster"); +}; + +methods.removeShape = function (layerId) { + this.layerManager.removeLayer("shape", layerId); +}; + +methods.clearShapes = function () { + this.layerManager.clearLayers("shape"); +}; + +methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + var df = new _dataframe2["default"]().col("lat1", lat1).col("lng1", lng1).col("lat2", lat2).col("lng2", lng2).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + if (_jquery2["default"].isNumeric(df.get(i, "lat1")) && _jquery2["default"].isNumeric(df.get(i, "lng1")) && _jquery2["default"].isNumeric(df.get(i, "lat2")) && _jquery2["default"].isNumeric(df.get(i, "lng2"))) { + return _leaflet2["default"].rectangle([[df.get(i, "lat1"), df.get(i, "lng1")], [df.get(i, "lat2"), df.get(i, "lng2")]], df.get(i)); + } else { + return null; + } + }); +}; +/* + * @param lat Array of arrays of latitude coordinates for polygons + * @param lng Array of arrays of longitude coordinates for polygons + */ + + +methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { + if (polygons.length > 0) { + var df = new _dataframe2["default"]().col("shapes", polygons).col("layerId", layerId).col("group", group).col("popup", popup).col("popupOptions", popupOptions).col("label", label).col("labelOptions", labelOptions).col("highlightOptions", highlightOptions).cbind(options); + addLayers(this, "shape", df, function (df, i) { + // This code used to use L.multiPolygon, but that caused + // double-click on a multipolygon to fail to zoom in on the + // map. Surprisingly, putting all the rings in a single + // polygon seems to still work; complicated multipolygons + // are still rendered correctly. + var shapes = df.get(i, "shapes").map(function (polygon) { + return polygon.map(_htmlwidgets2["default"].dataframeToD3); + }).reduce(function (acc, val) { + return acc.concat(val); + }, []); + return _leaflet2["default"].polygon(shapes, df.get(i)); }); - - global.omnivore.topojson.parse(data, null, gjlayer); - this.layerManager.addLayer(gjlayer, "topojson", layerId, group); - }; - - methods.removeTopoJSON = function (layerId) { - this.layerManager.removeLayer("topojson", layerId); - }; - - methods.clearTopoJSON = function () { - this.layerManager.clearLayers("topojson"); - }; - - methods.addControl = function (html, position, layerId, classes) { - function onAdd(map) { - var div = _leaflet2["default"].DomUtil.create("div", classes); - - if (typeof layerId !== "undefined" && layerId !== null) { - div.setAttribute("id", layerId); - } - - this._div = div; // It's possible for window.Shiny to be true but Shiny.initializeInputs to - // not be, when a static leaflet widget is included as part of the shiny - // UI directly (not through leafletOutput or uiOutput). In this case we - // don't do the normal Shiny stuff as that will all happen when Shiny - // itself loads and binds the entire doc. - - if (window.Shiny && _shiny2["default"].initializeInputs) { - _shiny2["default"].renderHtml(html, this._div); - - _shiny2["default"].initializeInputs(this._div); - - _shiny2["default"].bindAll(this._div); + } +}; + +methods.addGeoJSON = function (data, layerId, group, style) { + // This time, self is actually needed because the callbacks below need + // to access both the inner and outer senses of "this" + var self = this; + + if (typeof data === "string") { + data = JSON.parse(data); + } + + var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); + + var gjlayer = _leaflet2["default"].geoJson(data, { + style: function style(feature) { + if (feature.style || feature.properties.style) { + return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); } else { - this._div.innerHTML = html; - } - - return this._div; - } - - function onRemove(map) { - if (window.Shiny && _shiny2["default"].unbindAll) { - _shiny2["default"].unbindAll(this._div); + return globalStyle; } + }, + onEachFeature: function onEachFeature(feature, layer) { + var extraInfo = { + featureId: feature.id, + properties: feature.properties + }; + var popup = feature.properties ? feature.properties.popup : null; + if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); + layer.on("click", mouseHandler(self.id, layerId, group, "geojson_click", extraInfo), this); + layer.on("mouseover", mouseHandler(self.id, layerId, group, "geojson_mouseover", extraInfo), this); + layer.on("mouseout", mouseHandler(self.id, layerId, group, "geojson_mouseout", extraInfo), this); } - - var Control = _leaflet2["default"].Control.extend({ - options: { - position: position - }, - onAdd: onAdd, - onRemove: onRemove - }); - - this.controls.add(new Control(), layerId, html); - }; - - methods.addCustomControl = function (control, layerId) { - this.controls.add(control, layerId); - }; - - methods.removeControl = function (layerId) { - this.controls.remove(layerId); - }; - - methods.getControl = function (layerId) { - this.controls.get(layerId); - }; - - methods.clearControls = function () { - this.controls.clear(); - }; - - methods.addLegend = function (options) { - var legend = _leaflet2["default"].control({ - position: options.position - }); - - var gradSpan; - - legend.onAdd = function (map) { - var div = _leaflet2["default"].DomUtil.create("div", options.className), - colors = options.colors, - labels = options.labels, - legendHTML = ""; - - if (options.type === "numeric") { - // # Formatting constants. - var singleBinHeight = 20; // The distance between tick marks, in px - - var vMargin = 8; // If 1st tick mark starts at top of gradient, how - // many extra px are needed for the top half of the - // 1st label? (ditto for last tick mark/label) - - var tickWidth = 4; // How wide should tick marks be, in px? - - var labelPadding = 6; // How much distance to reserve for tick mark? - // (Must be >= tickWidth) - // # Derived formatting parameters. - // What's the height of a single bin, in percentage (of gradient height)? - // It might not just be 1/(n-1), if the gradient extends past the tick - // marks (which can be the case for pretty cut points). - - var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); // Each bin is `singleBinHeight` high. How tall is the gradient? - - var totalHeight = 1 / singleBinPct * singleBinHeight + 1; // How far should the first tick be shifted down, relative to the top - // of the gradient? - - var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; - gradSpan = (0, _jquery2["default"])("").css({ - "background": "linear-gradient(" + colors + ")", - "opacity": options.opacity, - "height": totalHeight + "px", - "width": "18px", - "display": "block", - "margin-top": vMargin + "px" - }); - var leftDiv = (0, _jquery2["default"])("
").css("float", "left"), - rightDiv = (0, _jquery2["default"])("
").css("float", "left"); - leftDiv.append(gradSpan); - (0, _jquery2["default"])(div).append(leftDiv).append(rightDiv).append((0, _jquery2["default"])("
")); // Have to attach the div to the body at this early point, so that the - // svg text getComputedTextLength() actually works, below. - - document.body.appendChild(div); - var ns = "http://www.w3.org/2000/svg"; - var svg = document.createElementNS(ns, "svg"); - rightDiv.append(svg); - var g = document.createElementNS(ns, "g"); - (0, _jquery2["default"])(g).attr("transform", "translate(0, " + vMargin + ")"); - svg.appendChild(g); // max label width needed to set width of svg, and right-justify text - - var maxLblWidth = 0; // Create tick marks and labels - - _jquery2["default"].each(labels, function (i, label) { - var y = tickOffset + i * singleBinHeight + 0.5; - var thisLabel = document.createElementNS(ns, "text"); - (0, _jquery2["default"])(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); - g.appendChild(thisLabel); - maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); - var thisTick = document.createElementNS(ns, "line"); - (0, _jquery2["default"])(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); - g.appendChild(thisTick); - }); // Now that we know the max label width, we can right-justify - - - (0, _jquery2["default"])(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); // Final size for - - (0, _jquery2["default"])(svg).css({ - width: maxLblWidth + labelPadding + "px", - height: totalHeight + vMargin * 2 + "px" - }); - - if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { - (0, _jquery2["default"])(div).append("
" + options.na_label + "
"); - } + }); + + this.layerManager.addLayer(gjlayer, "geojson", layerId, group); +}; + +methods.removeGeoJSON = function (layerId) { + this.layerManager.removeLayer("geojson", layerId); +}; + +methods.clearGeoJSON = function () { + this.layerManager.clearLayers("geojson"); +}; + +methods.addTopoJSON = function (data, layerId, group, style) { + // This time, self is actually needed because the callbacks below need + // to access both the inner and outer senses of "this" + var self = this; + + if (typeof data === "string") { + data = JSON.parse(data); + } + + var globalStyle = _jquery2["default"].extend({}, style, data.style || {}); + + var gjlayer = _leaflet2["default"].geoJson(null, { + style: function style(feature) { + if (feature.style || feature.properties.style) { + return _jquery2["default"].extend({}, globalStyle, feature.style, feature.properties.style); } else { - if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { - colors.push(options.na_color); - labels.push(options.na_label); - } - - for (var i = 0; i < colors.length; i++) { - legendHTML += " " + labels[i] + "
"; - } - - div.innerHTML = legendHTML; + return globalStyle; } - - if (options.title) (0, _jquery2["default"])(div).prepend("
" + options.title + "
"); - return div; - }; - - if (options.group) { - // Auto generate a layerID if not provided - if (!options.layerId) { - options.layerId = _leaflet2["default"].Util.stamp(legend); - } - - var map = this; - map.on("overlayadd", function (e) { - if (e.name === options.group) { - map.controls.add(legend, options.layerId); - } - }); - map.on("overlayremove", function (e) { - if (e.name === options.group) { - map.controls.remove(options.layerId); - } - }); - map.on("groupadd", function (e) { - if (e.name === options.group) { - map.controls.add(legend, options.layerId); - } + }, + onEachFeature: function onEachFeature(feature, layer) { + var extraInfo = { + featureId: feature.id, + properties: feature.properties + }; + var popup = feature.properties.popup; + if (typeof popup !== "undefined" && popup !== null) layer.bindPopup(popup); + layer.on("click", mouseHandler(self.id, layerId, group, "topojson_click", extraInfo), this); + layer.on("mouseover", mouseHandler(self.id, layerId, group, "topojson_mouseover", extraInfo), this); + layer.on("mouseout", mouseHandler(self.id, layerId, group, "topojson_mouseout", extraInfo), this); + } + }); + + global.omnivore.topojson.parse(data, null, gjlayer); + this.layerManager.addLayer(gjlayer, "topojson", layerId, group); +}; + +methods.removeTopoJSON = function (layerId) { + this.layerManager.removeLayer("topojson", layerId); +}; + +methods.clearTopoJSON = function () { + this.layerManager.clearLayers("topojson"); +}; + +methods.addControl = function (html, position, layerId, classes) { + function onAdd(map) { + var div = _leaflet2["default"].DomUtil.create("div", classes); + + if (typeof layerId !== "undefined" && layerId !== null) { + div.setAttribute("id", layerId); + } + + this._div = div; // It's possible for window.Shiny to be true but Shiny.initializeInputs to + // not be, when a static leaflet widget is included as part of the shiny + // UI directly (not through leafletOutput or uiOutput). In this case we + // don't do the normal Shiny stuff as that will all happen when Shiny + // itself loads and binds the entire doc. + + if (window.Shiny && _shiny2["default"].initializeInputs) { + _shiny2["default"].renderHtml(html, this._div); + + _shiny2["default"].initializeInputs(this._div); + + _shiny2["default"].bindAll(this._div); + } else { + this._div.innerHTML = html; + } + + return this._div; + } + + function onRemove(map) { + if (window.Shiny && _shiny2["default"].unbindAll) { + _shiny2["default"].unbindAll(this._div); + } + } + + var Control = _leaflet2["default"].Control.extend({ + options: { + position: position + }, + onAdd: onAdd, + onRemove: onRemove + }); + + this.controls.add(new Control(), layerId, html); +}; + +methods.addCustomControl = function (control, layerId) { + this.controls.add(control, layerId); +}; + +methods.removeControl = function (layerId) { + this.controls.remove(layerId); +}; + +methods.getControl = function (layerId) { + this.controls.get(layerId); +}; + +methods.clearControls = function () { + this.controls.clear(); +}; + +methods.addLegend = function (options) { + var legend = _leaflet2["default"].control({ + position: options.position + }); + + var gradSpan; + + legend.onAdd = function (map) { + var div = _leaflet2["default"].DomUtil.create("div", options.className), + colors = options.colors, + labels = options.labels, + legendHTML = ""; + + if (options.type === "numeric") { + // # Formatting constants. + var singleBinHeight = 20; // The distance between tick marks, in px + + var vMargin = 8; // If 1st tick mark starts at top of gradient, how + // many extra px are needed for the top half of the + // 1st label? (ditto for last tick mark/label) + + var tickWidth = 4; // How wide should tick marks be, in px? + + var labelPadding = 6; // How much distance to reserve for tick mark? + // (Must be >= tickWidth) + // # Derived formatting parameters. + // What's the height of a single bin, in percentage (of gradient height)? + // It might not just be 1/(n-1), if the gradient extends past the tick + // marks (which can be the case for pretty cut points). + + var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); // Each bin is `singleBinHeight` high. How tall is the gradient? + + var totalHeight = 1 / singleBinPct * singleBinHeight + 1; // How far should the first tick be shifted down, relative to the top + // of the gradient? + + var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; + gradSpan = (0, _jquery2["default"])("").css({ + "background": "linear-gradient(" + colors + ")", + "opacity": options.opacity, + "height": totalHeight + "px", + "width": "18px", + "display": "block", + "margin-top": vMargin + "px" }); - map.on("groupremove", function (e) { - if (e.name === options.group) { - map.controls.remove(options.layerId); - } + var leftDiv = (0, _jquery2["default"])("
").css("float", "left"), + rightDiv = (0, _jquery2["default"])("
").css("float", "left"); + leftDiv.append(gradSpan); + (0, _jquery2["default"])(div).append(leftDiv).append(rightDiv).append((0, _jquery2["default"])("
")); // Have to attach the div to the body at this early point, so that the + // svg text getComputedTextLength() actually works, below. + + document.body.appendChild(div); + var ns = "http://www.w3.org/2000/svg"; + var svg = document.createElementNS(ns, "svg"); + rightDiv.append(svg); + var g = document.createElementNS(ns, "g"); + (0, _jquery2["default"])(g).attr("transform", "translate(0, " + vMargin + ")"); + svg.appendChild(g); // max label width needed to set width of svg, and right-justify text + + var maxLblWidth = 0; // Create tick marks and labels + + _jquery2["default"].each(labels, function (i, label) { + var y = tickOffset + i * singleBinHeight + 0.5; + var thisLabel = document.createElementNS(ns, "text"); + (0, _jquery2["default"])(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); + g.appendChild(thisLabel); + maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); + var thisTick = document.createElementNS(ns, "line"); + (0, _jquery2["default"])(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); + g.appendChild(thisTick); + }); // Now that we know the max label width, we can right-justify + + + (0, _jquery2["default"])(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); // Final size for + + (0, _jquery2["default"])(svg).css({ + width: maxLblWidth + labelPadding + "px", + height: totalHeight + vMargin * 2 + "px" }); + + if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { + (0, _jquery2["default"])(div).append("
" + options.na_label + "
"); + } + } else { + if (options.na_color && _jquery2["default"].inArray(options.na_label, labels) < 0) { + colors.push(options.na_color); + labels.push(options.na_label); + } + + for (var i = 0; i < colors.length; i++) { + legendHTML += " " + labels[i] + "
"; + } + + div.innerHTML = legendHTML; } - - this.controls.add(legend, options.layerId); + + if (options.title) (0, _jquery2["default"])(div).prepend("
" + options.title + "
"); + return div; }; - - methods.addLayersControl = function (baseGroups, overlayGroups, options) { - var _this4 = this; - - // Only allow one layers control at a time - methods.removeLayersControl.call(this); - var firstLayer = true; - var base = {}; - - _jquery2["default"].each((0, _util.asArray)(baseGroups), function (i, g) { - var layer = _this4.layerManager.getLayerGroup(g, true); - - if (layer) { - base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one - - if (_this4.hasLayer(layer)) { - if (firstLayer) { - firstLayer = false; - } else { - _this4.removeLayer(layer); - } - } + + if (options.group) { + // Auto generate a layerID if not provided + if (!options.layerId) { + options.layerId = _leaflet2["default"].Util.stamp(legend); + } + + var map = this; + map.on("overlayadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); } }); - - var overlay = {}; - - _jquery2["default"].each((0, _util.asArray)(overlayGroups), function (i, g) { - var layer = _this4.layerManager.getLayerGroup(g, true); - - if (layer) { - overlay[g] = layer; + map.on("overlayremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); } }); - - this.currentLayersControl = _leaflet2["default"].control.layers(base, overlay, options); - this.addControl(this.currentLayersControl); - }; - - methods.removeLayersControl = function () { - if (this.currentLayersControl) { - this.removeControl(this.currentLayersControl); - this.currentLayersControl = null; - } - }; - - methods.addScaleBar = function (options) { - // Only allow one scale bar at a time - methods.removeScaleBar.call(this); - - var scaleBar = _leaflet2["default"].control.scale(options).addTo(this); - - this.currentScaleBar = scaleBar; - }; - - methods.removeScaleBar = function () { - if (this.currentScaleBar) { - this.currentScaleBar.remove(); - this.currentScaleBar = null; - } - }; - - methods.hideGroup = function (group) { - var _this5 = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { - var layer = _this5.layerManager.getLayerGroup(g, true); - - if (layer) { - _this5.removeLayer(layer); + map.on("groupadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); } }); - }; - - methods.showGroup = function (group) { - var _this6 = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { - var layer = _this6.layerManager.getLayerGroup(g, true); - - if (layer) { - _this6.addLayer(layer); + map.on("groupremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); } }); - }; - - function setupShowHideGroupsOnZoom(map) { - if (map.leafletr._hasInitializedShowHideGroups) { - return; - } - - map.leafletr._hasInitializedShowHideGroups = true; - - function setVisibility(layer, visible, group) { - if (visible !== map.hasLayer(layer)) { - if (visible) { - map.addLayer(layer); - map.fire("groupadd", { - "name": group, - "layer": layer - }); + } + + this.controls.add(legend, options.layerId); +}; + +methods.addLayersControl = function (baseGroups, overlayGroups, options) { + var _this4 = this; + + // Only allow one layers control at a time + methods.removeLayersControl.call(this); + var firstLayer = true; + var base = {}; + + _jquery2["default"].each((0, _util.asArray)(baseGroups), function (i, g) { + var layer = _this4.layerManager.getLayerGroup(g, true); + + if (layer) { + base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one + + if (_this4.hasLayer(layer)) { + if (firstLayer) { + firstLayer = false; } else { - map.removeLayer(layer); - map.fire("groupremove", { - "name": group, - "layer": layer - }); + _this4.removeLayer(layer); } } } - - function showHideGroupsOnZoom() { - if (!map.layerManager) return; - var zoom = map.getZoom(); - map.layerManager.getAllGroupNames().forEach(function (group) { - var layer = map.layerManager.getLayerGroup(group, false); - - if (layer && typeof layer.zoomLevels !== "undefined") { - setVisibility(layer, layer.zoomLevels === true || layer.zoomLevels.indexOf(zoom) >= 0, group); - } - }); + }); + + var overlay = {}; + + _jquery2["default"].each((0, _util.asArray)(overlayGroups), function (i, g) { + var layer = _this4.layerManager.getLayerGroup(g, true); + + if (layer) { + overlay[g] = layer; } - - map.showHideGroupsOnZoom = showHideGroupsOnZoom; - map.on("zoomend", showHideGroupsOnZoom); + }); + + this.currentLayersControl = _leaflet2["default"].control.layers(base, overlay, options); + this.addControl(this.currentLayersControl); +}; + +methods.removeLayersControl = function () { + if (this.currentLayersControl) { + this.removeControl(this.currentLayersControl); + this.currentLayersControl = null; } - - methods.setGroupOptions = function (group, options) { - var _this7 = this; - - _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { - var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels - - - if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) { - layer.zoomLevels = (0, _util.asArray)(options.zoomLevels); - } - }); - - setupShowHideGroupsOnZoom(this); - this.showHideGroupsOnZoom(); - }; - - methods.addRasterImage = function (uri, bounds, layerId, group, options) { - // uri is a data URI containing an image. We want to paint this image as a - // layer at (top-left) bounds[0] to (bottom-right) bounds[1]. - // We can't simply use ImageOverlay, as it uses bilinear scaling which looks - // awful as you zoom in (and sometimes shifts positions or disappears). - // Instead, we'll use a TileLayer.Canvas to draw pieces of the image. - // First, some helper functions. - // degree2tile converts latitude, longitude, and zoom to x and y tile - // numbers. The tile numbers returned can be non-integral, as there's no - // reason to expect that the lat/lng inputs are exactly on the border of two - // tiles. - // - // We'll use this to convert the bounds we got from the server, into coords - // in tile-space at a given zoom level. Note that once we do the conversion, - // we don't to do any more trigonometry to convert between pixel coordinates - // and tile coordinates; the source image pixel coords, destination canvas - // pixel coords, and tile coords all can be scaled linearly. - function degree2tile(lat, lng, zoom) { - // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames - var latRad = lat * Math.PI / 180; - var n = Math.pow(2, zoom); - var x = (lng + 180) / 360 * n; - var y = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n; - return { - x: x, - y: y - }; - } // Given a range [from,to) and either one or two numbers, returns true if - // there is any overlap between [x,x1) and the range--or if x1 is omitted, - // then returns true if x is within [from,to). - - - function overlap(from, to, x, - /* optional */ - x1) { - if (arguments.length == 3) x1 = x; - return x < to && x1 >= from; - } - - function getCanvasSmoothingProperty(ctx) { - var candidates = ["imageSmoothingEnabled", "mozImageSmoothingEnabled", "webkitImageSmoothingEnabled", "msImageSmoothingEnabled"]; - - for (var i = 0; i < candidates.length; i++) { - if (typeof ctx[candidates[i]] !== "undefined") { - return candidates[i]; - } - } - - return null; - } // Our general strategy is to: - // 1. Load the data URI in an Image() object, so we can get its pixel - // dimensions and the underlying image data. (We could have done this - // by not encoding as PNG at all but just send an array of RGBA values - // from the server, but that would inflate the JSON too much.) - // 2. Create a hidden canvas that we use just to extract the image data - // from the Image (using Context2D.getImageData()). - // 3. Create a TileLayer.Canvas and add it to the map. - // We want to synchronously create and attach the TileLayer.Canvas (so an - // immediate call to clearRasters() will be respected, for example), but - // Image loads its data asynchronously. Fortunately we can resolve this - // by putting TileLayer.Canvas into async mode, which will let us create - // and attach the layer but have it wait until the image is loaded before - // it actually draws anything. - // These are the variables that we will populate once the image is loaded. - - - var imgData = null; // 1d row-major array, four [0-255] integers per pixel - - var imgDataMipMapper = null; - var w = null; // image width in pixels - - var h = null; // image height in pixels - // We'll use this array to store callbacks that need to be invoked once - // imgData, w, and h have been resolved. - - var imgDataCallbacks = []; // Consumers of imgData, w, and h can call this to be notified when data - // is available. - - function getImageData(callback) { - if (imgData != null) { - // Must not invoke the callback immediately; it's too confusing and - // fragile to have a function invoke the callback *either* immediately - // or in the future. Better to be consistent here. - setTimeout(function () { - callback(imgData, w, h, imgDataMipMapper); - }, 0); +}; + +methods.addScaleBar = function (options) { + // Only allow one scale bar at a time + methods.removeScaleBar.call(this); + + var scaleBar = _leaflet2["default"].control.scale(options).addTo(this); + + this.currentScaleBar = scaleBar; +}; + +methods.removeScaleBar = function () { + if (this.currentScaleBar) { + this.currentScaleBar.remove(); + this.currentScaleBar = null; + } +}; + +methods.hideGroup = function (group) { + var _this5 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this5.layerManager.getLayerGroup(g, true); + + if (layer) { + _this5.removeLayer(layer); + } + }); +}; + +methods.showGroup = function (group) { + var _this6 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this6.layerManager.getLayerGroup(g, true); + + if (layer) { + _this6.addLayer(layer); + } + }); +}; + +function setupShowHideGroupsOnZoom(map) { + if (map.leafletr._hasInitializedShowHideGroups) { + return; + } + + map.leafletr._hasInitializedShowHideGroups = true; + + function setVisibility(layer, visible, group) { + if (visible !== map.hasLayer(layer)) { + if (visible) { + map.addLayer(layer); + map.fire("groupadd", { + "name": group, + "layer": layer + }); } else { - imgDataCallbacks.push(callback); + map.removeLayer(layer); + map.fire("groupremove", { + "name": group, + "layer": layer + }); } } - - var img = new Image(); - - img.onload = function () { - // Save size - w = img.width; - h = img.height; // Create a dummy canvas to extract the image data - - var imgDataCanvas = document.createElement("canvas"); - imgDataCanvas.width = w; - imgDataCanvas.height = h; - imgDataCanvas.style.display = "none"; - document.body.appendChild(imgDataCanvas); - var imgDataCtx = imgDataCanvas.getContext("2d"); - imgDataCtx.drawImage(img, 0, 0); // Save the image data. - - imgData = imgDataCtx.getImageData(0, 0, w, h).data; - imgDataMipMapper = new _mipmapper2["default"](img); // Done with the canvas, remove it from the page so it can be gc'd. - - document.body.removeChild(imgDataCanvas); // Alert any getImageData callers who are waiting. - - for (var i = 0; i < imgDataCallbacks.length; i++) { - imgDataCallbacks[i](imgData, w, h, imgDataMipMapper); + } + + function showHideGroupsOnZoom() { + if (!map.layerManager) return; + var zoom = map.getZoom(); + map.layerManager.getAllGroupNames().forEach(function (group) { + var layer = map.layerManager.getLayerGroup(group, false); + + if (layer && typeof layer.zoomLevels !== "undefined") { + setVisibility(layer, layer.zoomLevels === true || layer.zoomLevels.indexOf(zoom) >= 0, group); } - - imgDataCallbacks = []; - }; - - img.src = uri; - - var canvasTiles = _leaflet2["default"].gridLayer(Object.assign({}, options, { - detectRetina: true, - async: true - })); // NOTE: The done() function MUST NOT be invoked until after the current - // tick; done() looks in Leaflet's tile cache for the current tile, and - // since it's still being constructed, it won't be found. - - - canvasTiles.createTile = function (tilePoint, done) { - var zoom = tilePoint.z; - - var canvas = _leaflet2["default"].DomUtil.create("canvas"); - - var error; // setup tile width and height according to the options - - var size = this.getTileSize(); - canvas.width = size.x; - canvas.height = size.y; - getImageData(function (imgData, w, h, mipmapper) { - try { - // The Context2D we'll being drawing onto. It's always 256x256. - var ctx = canvas.getContext("2d"); // Convert our image data's top-left and bottom-right locations into - // x/y tile coordinates. This is essentially doing a spherical mercator - // projection, then multiplying by 2^zoom. - - var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); - var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); // The size of the image in x/y tile coordinates. - - var extent = { - x: bottomRight.x - topLeft.x, - y: bottomRight.y - topLeft.y - }; // Short circuit if tile is totally disjoint from image. - - if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return; - if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; // The linear resolution of the tile we're drawing is always 256px per tile unit. - // If the linear resolution (in either direction) of the image is less than 256px - // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in - // scaling. - - var imgRes = { - x: w / extent.x, - y: h / extent.y - }; // We can do the actual drawing in one of three ways: - // - Call drawImage(). This is easy and fast, and results in smooth - // interpolation (bilinear?). This is what we want when we are - // reducing the image from its native size. - // - Call drawImage() with imageSmoothingEnabled=false. This is easy - // and fast and gives us nearest-neighbor interpolation, which is what - // we want when enlarging the image. However, it's unsupported on many - // browsers (including QtWebkit). - // - Do a manual nearest-neighbor interpolation. This is what we'll fall - // back to when enlarging, and imageSmoothingEnabled isn't supported. - // In theory it's slower, but still pretty fast on my machine, and the - // results look the same AFAICT. - // Is imageSmoothingEnabled supported? If so, we can let canvas do - // nearest-neighbor interpolation for us. - - var smoothingProperty = getCanvasSmoothingProperty(ctx); - - if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { - // Use built-in scaling - // Turn off anti-aliasing if necessary - if (smoothingProperty) { - ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; - } // Don't necessarily draw with the full-size image; if we're - // downscaling, use the mipmapper to get a pre-downscaled image - // (see comments on Mipmapper class for why this matters). - - - mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { - // It's possible that the image will go off the edge of the canvas-- - // that's OK, the canvas should clip appropriately. - ctx.drawImage(mip, // Convert abs tile coords to rel tile coords, then *256 to convert - // to rel pixel coords - (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, // Always draw the whole thing and let canvas clip; so we can just - // convert from size in tile coords straight to pixels - extent.x * 256, extent.y * 256); - }); - } else { - // Use manual nearest-neighbor interpolation - // Calculate the source image pixel coordinates that correspond with - // the top-left and bottom-right of this tile. (If the source image - // only partially overlaps the tile, we use max/min to limit the - // sourceStart/End to only reflect the overlapping portion.) - var sourceStart = { - x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), - y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) - }; - var sourceEnd = { - x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), - y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) - }; // The size, in dest pixels, that each source pixel should occupy. - // This might be greater or less than 1 (e.g. if x and y resolution - // are very different). - - var pixelSize = { - x: 256 / imgRes.x, - y: 256 / imgRes.y - }; // For each pixel in the source image that overlaps the tile... - - for (var row = sourceStart.y; row < sourceEnd.y; row++) { - for (var col = sourceStart.x; col < sourceEnd.x; col++) { - // ...extract the pixel data... - var i = (row * w + col) * 4; - var r = imgData[i]; - var g = imgData[i + 1]; - var b = imgData[i + 2]; - var a = imgData[i + 3]; - ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; // ...calculate the corresponding pixel coord in the dest image - // where it should be drawn... - - var pixelPos = { - x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, - y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 - }; // ...and draw a rectangle there. - - ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), // Looks crazy, but this is necessary to prevent rounding from - // causing overlap between this rect and its neighbors. The - // minuend is the location of the next pixel, while the - // subtrahend is the position of the current pixel (to turn an - // absolute coordinate to a width/height). Yes, I had to look - // up minuend and subtrahend. - Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); - } - } - } - } catch (e) { - error = e; - } finally { - done(error, canvas); - } - }); - return canvas; + }); + } + + map.showHideGroupsOnZoom = showHideGroupsOnZoom; + map.on("zoomend", showHideGroupsOnZoom); +} + +methods.setGroupOptions = function (group, options) { + var _this7 = this; + + _jquery2["default"].each((0, _util.asArray)(group), function (i, g) { + var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels + + + if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) { + layer.zoomLevels = (0, _util.asArray)(options.zoomLevels); + } + }); + + setupShowHideGroupsOnZoom(this); + this.showHideGroupsOnZoom(); +}; + +methods.addRasterImage = function (uri, bounds, layerId, group, options) { + // uri is a data URI containing an image. We want to paint this image as a + // layer at (top-left) bounds[0] to (bottom-right) bounds[1]. + // We can't simply use ImageOverlay, as it uses bilinear scaling which looks + // awful as you zoom in (and sometimes shifts positions or disappears). + // Instead, we'll use a TileLayer.Canvas to draw pieces of the image. + // First, some helper functions. + // degree2tile converts latitude, longitude, and zoom to x and y tile + // numbers. The tile numbers returned can be non-integral, as there's no + // reason to expect that the lat/lng inputs are exactly on the border of two + // tiles. + // + // We'll use this to convert the bounds we got from the server, into coords + // in tile-space at a given zoom level. Note that once we do the conversion, + // we don't to do any more trigonometry to convert between pixel coordinates + // and tile coordinates; the source image pixel coords, destination canvas + // pixel coords, and tile coords all can be scaled linearly. + function degree2tile(lat, lng, zoom) { + // See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames + var latRad = lat * Math.PI / 180; + var n = Math.pow(2, zoom); + var x = (lng + 180) / 360 * n; + var y = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n; + return { + x: x, + y: y }; - - this.layerManager.addLayer(canvasTiles, "image", layerId, group); - }; - - methods.removeImage = function (layerId) { - this.layerManager.removeLayer("image", layerId); - }; - - methods.clearImages = function () { - this.layerManager.clearLayers("image"); - }; - - methods.addMeasure = function (options) { - // if a measureControl already exists, then remove it and - // replace with a new one - methods.removeMeasure.call(this); - this.measureControl = _leaflet2["default"].control.measure(options); - this.addControl(this.measureControl); - }; - - methods.removeMeasure = function () { - if (this.measureControl) { - this.removeControl(this.measureControl); - this.measureControl = null; + } // Given a range [from,to) and either one or two numbers, returns true if + // there is any overlap between [x,x1) and the range--or if x1 is omitted, + // then returns true if x is within [from,to). + + + function overlap(from, to, x, + /* optional */ + x1) { + if (arguments.length == 3) x1 = x; + return x < to && x1 >= from; + } + + function getCanvasSmoothingProperty(ctx) { + var candidates = ["imageSmoothingEnabled", "mozImageSmoothingEnabled", "webkitImageSmoothingEnabled", "msImageSmoothingEnabled"]; + + for (var i = 0; i < candidates.length; i++) { + if (typeof ctx[candidates[i]] !== "undefined") { + return candidates[i]; + } + } + + return null; + } // Our general strategy is to: + // 1. Load the data URI in an Image() object, so we can get its pixel + // dimensions and the underlying image data. (We could have done this + // by not encoding as PNG at all but just send an array of RGBA values + // from the server, but that would inflate the JSON too much.) + // 2. Create a hidden canvas that we use just to extract the image data + // from the Image (using Context2D.getImageData()). + // 3. Create a TileLayer.Canvas and add it to the map. + // We want to synchronously create and attach the TileLayer.Canvas (so an + // immediate call to clearRasters() will be respected, for example), but + // Image loads its data asynchronously. Fortunately we can resolve this + // by putting TileLayer.Canvas into async mode, which will let us create + // and attach the layer but have it wait until the image is loaded before + // it actually draws anything. + // These are the variables that we will populate once the image is loaded. + + + var imgData = null; // 1d row-major array, four [0-255] integers per pixel + + var imgDataMipMapper = null; + var w = null; // image width in pixels + + var h = null; // image height in pixels + // We'll use this array to store callbacks that need to be invoked once + // imgData, w, and h have been resolved. + + var imgDataCallbacks = []; // Consumers of imgData, w, and h can call this to be notified when data + // is available. + + function getImageData(callback) { + if (imgData != null) { + // Must not invoke the callback immediately; it's too confusing and + // fragile to have a function invoke the callback *either* immediately + // or in the future. Better to be consistent here. + setTimeout(function () { + callback(imgData, w, h, imgDataMipMapper); + }, 0); + } else { + imgDataCallbacks.push(callback); } + } + + var img = new Image(); + + img.onload = function () { + // Save size + w = img.width; + h = img.height; // Create a dummy canvas to extract the image data + + var imgDataCanvas = document.createElement("canvas"); + imgDataCanvas.width = w; + imgDataCanvas.height = h; + imgDataCanvas.style.display = "none"; + document.body.appendChild(imgDataCanvas); + var imgDataCtx = imgDataCanvas.getContext("2d"); + imgDataCtx.drawImage(img, 0, 0); // Save the image data. + + imgData = imgDataCtx.getImageData(0, 0, w, h).data; + imgDataMipMapper = new _mipmapper2["default"](img); // Done with the canvas, remove it from the page so it can be gc'd. + + document.body.removeChild(imgDataCanvas); // Alert any getImageData callers who are waiting. + + for (var i = 0; i < imgDataCallbacks.length; i++) { + imgDataCallbacks[i](imgData, w, h, imgDataMipMapper); + } + + imgDataCallbacks = []; }; - - methods.addSelect = function (ctGroup) { - var _this8 = this; - - methods.removeSelect.call(this); - this._selectButton = _leaflet2["default"].easyButton({ - states: [{ - stateName: "select-inactive", - icon: "ion-qr-scanner", - title: "Make a selection", - onClick: function onClick(btn, map) { - btn.state("select-active"); - _this8._locationFilter = new _leaflet2["default"].LocationFilter2(); - - if (ctGroup) { - var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); - selectionHandle.on("change", function (e) { - if (e.sender !== selectionHandle) { - if (_this8._locationFilter) { - _this8._locationFilter.disable(); - - btn.state("select-inactive"); - } - } - }); - - var handler = function handler(e) { - _this8.layerManager.brush(_this8._locationFilter.getBounds(), { - sender: selectionHandle - }); - }; - - _this8._locationFilter.on("enabled", handler); - - _this8._locationFilter.on("change", handler); - - _this8._locationFilter.on("disabled", function () { - selectionHandle.close(); - _this8._locationFilter = null; - }); + + img.src = uri; + + var canvasTiles = _leaflet2["default"].gridLayer(Object.assign({}, options, { + detectRetina: true, + async: true + })); // NOTE: The done() function MUST NOT be invoked until after the current + // tick; done() looks in Leaflet's tile cache for the current tile, and + // since it's still being constructed, it won't be found. + + + canvasTiles.createTile = function (tilePoint, done) { + var zoom = tilePoint.z; + + var canvas = _leaflet2["default"].DomUtil.create("canvas"); + + var error; // setup tile width and height according to the options + + var size = this.getTileSize(); + canvas.width = size.x; + canvas.height = size.y; + getImageData(function (imgData, w, h, mipmapper) { + try { + // The Context2D we'll being drawing onto. It's always 256x256. + var ctx = canvas.getContext("2d"); // Convert our image data's top-left and bottom-right locations into + // x/y tile coordinates. This is essentially doing a spherical mercator + // projection, then multiplying by 2^zoom. + + var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); + var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); // The size of the image in x/y tile coordinates. + + var extent = { + x: bottomRight.x - topLeft.x, + y: bottomRight.y - topLeft.y + }; // Short circuit if tile is totally disjoint from image. + + if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return; + if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; // The linear resolution of the tile we're drawing is always 256px per tile unit. + // If the linear resolution (in either direction) of the image is less than 256px + // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in + // scaling. + + var imgRes = { + x: w / extent.x, + y: h / extent.y + }; // We can do the actual drawing in one of three ways: + // - Call drawImage(). This is easy and fast, and results in smooth + // interpolation (bilinear?). This is what we want when we are + // reducing the image from its native size. + // - Call drawImage() with imageSmoothingEnabled=false. This is easy + // and fast and gives us nearest-neighbor interpolation, which is what + // we want when enlarging the image. However, it's unsupported on many + // browsers (including QtWebkit). + // - Do a manual nearest-neighbor interpolation. This is what we'll fall + // back to when enlarging, and imageSmoothingEnabled isn't supported. + // In theory it's slower, but still pretty fast on my machine, and the + // results look the same AFAICT. + // Is imageSmoothingEnabled supported? If so, we can let canvas do + // nearest-neighbor interpolation for us. + + var smoothingProperty = getCanvasSmoothingProperty(ctx); + + if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { + // Use built-in scaling + // Turn off anti-aliasing if necessary + if (smoothingProperty) { + ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; + } // Don't necessarily draw with the full-size image; if we're + // downscaling, use the mipmapper to get a pre-downscaled image + // (see comments on Mipmapper class for why this matters). + + + mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { + // It's possible that the image will go off the edge of the canvas-- + // that's OK, the canvas should clip appropriately. + ctx.drawImage(mip, // Convert abs tile coords to rel tile coords, then *256 to convert + // to rel pixel coords + (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, // Always draw the whole thing and let canvas clip; so we can just + // convert from size in tile coords straight to pixels + extent.x * 256, extent.y * 256); + }); + } else { + // Use manual nearest-neighbor interpolation + // Calculate the source image pixel coordinates that correspond with + // the top-left and bottom-right of this tile. (If the source image + // only partially overlaps the tile, we use max/min to limit the + // sourceStart/End to only reflect the overlapping portion.) + var sourceStart = { + x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), + y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) + }; + var sourceEnd = { + x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), + y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) + }; // The size, in dest pixels, that each source pixel should occupy. + // This might be greater or less than 1 (e.g. if x and y resolution + // are very different). + + var pixelSize = { + x: 256 / imgRes.x, + y: 256 / imgRes.y + }; // For each pixel in the source image that overlaps the tile... + + for (var row = sourceStart.y; row < sourceEnd.y; row++) { + for (var col = sourceStart.x; col < sourceEnd.x; col++) { + // ...extract the pixel data... + var i = (row * w + col) * 4; + var r = imgData[i]; + var g = imgData[i + 1]; + var b = imgData[i + 2]; + var a = imgData[i + 3]; + ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; // ...calculate the corresponding pixel coord in the dest image + // where it should be drawn... + + var pixelPos = { + x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, + y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 + }; // ...and draw a rectangle there. + + ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), // Looks crazy, but this is necessary to prevent rounding from + // causing overlap between this rect and its neighbors. The + // minuend is the location of the next pixel, while the + // subtrahend is the position of the current pixel (to turn an + // absolute coordinate to a width/height). Yes, I had to look + // up minuend and subtrahend. + Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); + } } - - _this8._locationFilter.addTo(map); - } - }, { - stateName: "select-active", - icon: "ion-close-round", - title: "Dismiss selection", - onClick: function onClick(btn, map) { - btn.state("select-inactive"); - - _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections - - - _this8.layerManager.unbrush(); } - }] + } catch (e) { + error = e; + } finally { + done(error, canvas); + } }); - - this._selectButton.addTo(this); - }; - - methods.removeSelect = function () { - if (this._locationFilter) { - this._locationFilter.disable(); - } - - if (this._selectButton) { - this.removeControl(this._selectButton); - this._selectButton = null; - } + return canvas; }; - - methods.createMapPane = function (name, zIndex) { - this.createPane(name); - this.getPane(name).style.zIndex = zIndex; - }; - - - }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - },{"./cluster-layer-store":1,"./crs_utils":3,"./dataframe":4,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./mipmapper":16,"./util":17}],16:[function(require,module,exports){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } - - function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } - - // This class simulates a mipmap, which shrinks images by powers of two. This - // stepwise reduction results in "pixel-perfect downscaling" (where every - // pixel of the original image has some contribution to the downscaled image) - // as opposed to a single-step downscaling which will discard a lot of data - // (and with sparse images at small scales can give very surprising results). - var Mipmapper = /*#__PURE__*/function () { - function Mipmapper(img) { - _classCallCheck(this, Mipmapper); - - this._layers = [img]; - } // The various functions on this class take a callback function BUT MAY OR MAY - // NOT actually behave asynchronously. - - - _createClass(Mipmapper, [{ - key: "getBySize", - value: function getBySize(desiredWidth, desiredHeight, callback) { - var _this = this; - - var i = 0; - var lastImg = this._layers[0]; - - var testNext = function testNext() { - _this.getByIndex(i, function (img) { - // If current image is invalid (i.e. too small to be rendered) or - // it's smaller than what we wanted, return the last known good image. - if (!img || img.width < desiredWidth || img.height < desiredHeight) { - callback(lastImg); - return; - } else { - lastImg = img; - i++; - testNext(); - return; + + this.layerManager.addLayer(canvasTiles, "image", layerId, group); +}; + +methods.removeImage = function (layerId) { + this.layerManager.removeLayer("image", layerId); +}; + +methods.clearImages = function () { + this.layerManager.clearLayers("image"); +}; + +methods.addMeasure = function (options) { + // if a measureControl already exists, then remove it and + // replace with a new one + methods.removeMeasure.call(this); + this.measureControl = _leaflet2["default"].control.measure(options); + this.addControl(this.measureControl); +}; + +methods.removeMeasure = function () { + if (this.measureControl) { + this.removeControl(this.measureControl); + this.measureControl = null; + } +}; + +methods.addSelect = function (ctGroup) { + var _this8 = this; + + methods.removeSelect.call(this); + this._selectButton = _leaflet2["default"].easyButton({ + states: [{ + stateName: "select-inactive", + icon: "ion-qr-scanner", + title: "Make a selection", + onClick: function onClick(btn, map) { + btn.state("select-active"); + _this8._locationFilter = new _leaflet2["default"].LocationFilter2(); + + if (ctGroup) { + var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); + selectionHandle.on("change", function (e) { + if (e.sender !== selectionHandle) { + if (_this8._locationFilter) { + _this8._locationFilter.disable(); + + btn.state("select-inactive"); + } } }); - }; - - testNext(); + + var handler = function handler(e) { + _this8.layerManager.brush(_this8._locationFilter.getBounds(), { + sender: selectionHandle + }); + }; + + _this8._locationFilter.on("enabled", handler); + + _this8._locationFilter.on("change", handler); + + _this8._locationFilter.on("disabled", function () { + selectionHandle.close(); + _this8._locationFilter = null; + }); + } + + _this8._locationFilter.addTo(map); } }, { - key: "getByIndex", - value: function getByIndex(i, callback) { - var _this2 = this; - - if (this._layers[i]) { - callback(this._layers[i]); - return; - } - - this.getByIndex(i - 1, function (prevImg) { - if (!prevImg) { - // prevImg could not be calculated (too small, possibly) - callback(null); - return; - } - - if (prevImg.width < 2 || prevImg.height < 2) { - // Can't reduce this image any further - callback(null); + stateName: "select-active", + icon: "ion-close-round", + title: "Dismiss selection", + onClick: function onClick(btn, map) { + btn.state("select-inactive"); + + _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections + + + _this8.layerManager.unbrush(); + } + }] + }); + + this._selectButton.addTo(this); +}; + +methods.removeSelect = function () { + if (this._locationFilter) { + this._locationFilter.disable(); + } + + if (this._selectButton) { + this.removeControl(this._selectButton); + this._selectButton = null; + } +}; + +methods.createMapPane = function (name, zIndex) { + this.createPane(name); + this.getPane(name).style.zIndex = zIndex; +}; + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./cluster-layer-store":1,"./crs_utils":3,"./dataframe":4,"./global/htmlwidgets":8,"./global/jquery":9,"./global/leaflet":10,"./global/shiny":12,"./mipmapper":16,"./util":17}],16:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +// This class simulates a mipmap, which shrinks images by powers of two. This +// stepwise reduction results in "pixel-perfect downscaling" (where every +// pixel of the original image has some contribution to the downscaled image) +// as opposed to a single-step downscaling which will discard a lot of data +// (and with sparse images at small scales can give very surprising results). +var Mipmapper = /*#__PURE__*/function () { + function Mipmapper(img) { + _classCallCheck(this, Mipmapper); + + this._layers = [img]; + } // The various functions on this class take a callback function BUT MAY OR MAY + // NOT actually behave asynchronously. + + + _createClass(Mipmapper, [{ + key: "getBySize", + value: function getBySize(desiredWidth, desiredHeight, callback) { + var _this = this; + + var i = 0; + var lastImg = this._layers[0]; + + var testNext = function testNext() { + _this.getByIndex(i, function (img) { + // If current image is invalid (i.e. too small to be rendered) or + // it's smaller than what we wanted, return the last known good image. + if (!img || img.width < desiredWidth || img.height < desiredHeight) { + callback(lastImg); return; - } // If reduce ever becomes truly asynchronous, we should stuff a promise or - // something into this._layers[i] before calling this.reduce(), to prevent - // redundant reduce operations from happening. - - - _this2.reduce(prevImg, function (reducedImg) { - _this2._layers[i] = reducedImg; - callback(reducedImg); + } else { + lastImg = img; + i++; + testNext(); return; - }); + } }); + }; + + testNext(); + } + }, { + key: "getByIndex", + value: function getByIndex(i, callback) { + var _this2 = this; + + if (this._layers[i]) { + callback(this._layers[i]); + return; } - }, { - key: "reduce", - value: function reduce(img, callback) { - var imgDataCanvas = document.createElement("canvas"); - imgDataCanvas.width = Math.ceil(img.width / 2); - imgDataCanvas.height = Math.ceil(img.height / 2); - imgDataCanvas.style.display = "none"; - document.body.appendChild(imgDataCanvas); - - try { - var imgDataCtx = imgDataCanvas.getContext("2d"); - imgDataCtx.drawImage(img, 0, 0, img.width / 2, img.height / 2); - callback(imgDataCanvas); - } finally { - document.body.removeChild(imgDataCanvas); + + this.getByIndex(i - 1, function (prevImg) { + if (!prevImg) { + // prevImg could not be calculated (too small, possibly) + callback(null); + return; } + + if (prevImg.width < 2 || prevImg.height < 2) { + // Can't reduce this image any further + callback(null); + return; + } // If reduce ever becomes truly asynchronous, we should stuff a promise or + // something into this._layers[i] before calling this.reduce(), to prevent + // redundant reduce operations from happening. + + + _this2.reduce(prevImg, function (reducedImg) { + _this2._layers[i] = reducedImg; + callback(reducedImg); + return; + }); + }); + } + }, { + key: "reduce", + value: function reduce(img, callback) { + var imgDataCanvas = document.createElement("canvas"); + imgDataCanvas.width = Math.ceil(img.width / 2); + imgDataCanvas.height = Math.ceil(img.height / 2); + imgDataCanvas.style.display = "none"; + document.body.appendChild(imgDataCanvas); + + try { + var imgDataCtx = imgDataCanvas.getContext("2d"); + imgDataCtx.drawImage(img, 0, 0, img.width / 2, img.height / 2); + callback(imgDataCanvas); + } finally { + document.body.removeChild(imgDataCanvas); } - }]); - - return Mipmapper; - }(); - - exports["default"] = Mipmapper; - - - },{}],17:[function(require,module,exports){ - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports.log = log; - exports.recycle = recycle; - exports.asArray = asArray; - - function log(message) { - /* eslint-disable no-console */ - if (console && console.log) console.log(message); - /* eslint-enable no-console */ + } + }]); + + return Mipmapper; +}(); + +exports["default"] = Mipmapper; + + +},{}],17:[function(require,module,exports){ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.log = log; +exports.recycle = recycle; +exports.asArray = asArray; + +function log(message) { + /* eslint-disable no-console */ + if (console && console.log) console.log(message); + /* eslint-enable no-console */ +} + +function recycle(values, length, inPlace) { + if (length === 0 && !inPlace) return []; + + if (!(values instanceof Array)) { + if (inPlace) { + throw new Error("Can't do in-place recycling of a non-Array value"); + } + + values = [values]; } - - function recycle(values, length, inPlace) { - if (length === 0 && !inPlace) return []; - - if (!(values instanceof Array)) { - if (inPlace) { - throw new Error("Can't do in-place recycling of a non-Array value"); - } - - values = [values]; - } - - if (typeof length === "undefined") length = values.length; - var dest = inPlace ? values : []; - var origLength = values.length; - - while (dest.length < length) { - dest.push(values[dest.length % origLength]); - } - - if (dest.length > length) { - dest.splice(length, dest.length - length); - } - - return dest; + + if (typeof length === "undefined") length = values.length; + var dest = inPlace ? values : []; + var origLength = values.length; + + while (dest.length < length) { + dest.push(values[dest.length % origLength]); } - - function asArray(value) { - if (value instanceof Array) return value;else return [value]; + + if (dest.length > length) { + dest.splice(length, dest.length - length); } - - - },{}]},{},[13]); - \ No newline at end of file + + return dest; +} + +function asArray(value) { + if (value instanceof Array) return value;else return [value]; +} + + +},{}]},{},[13]); From 73651d58ebe4d9b6a2c157ab0614c0ffe3ebdff2 Mon Sep 17 00:00:00 2001 From: Arkady <144838822+arkadymarch@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:43:00 -0500 Subject: [PATCH 3/5] style fix --- inst/htmlwidgets/assets/leaflet.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inst/htmlwidgets/assets/leaflet.js b/inst/htmlwidgets/assets/leaflet.js index c1aa61a15..3a888a21e 100644 --- a/inst/htmlwidgets/assets/leaflet.js +++ b/inst/htmlwidgets/assets/leaflet.js @@ -1883,9 +1883,9 @@ methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, } }; /* - * @param lat Array of arrays of latitude coordinates for polylines - * @param lng Array of arrays of longitude coordinates for polylines - */ +* @param lat Array of arrays of latitude coordinates for polylines +* @param lng Array of arrays of longitude coordinates for polylines +*/ methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { @@ -1947,9 +1947,9 @@ methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, option }); }; /* - * @param lat Array of arrays of latitude coordinates for polygons - * @param lng Array of arrays of longitude coordinates for polygons - */ +* @param lat Array of arrays of latitude coordinates for polygons +* @param lng Array of arrays of longitude coordinates for polygons +*/ methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { From 18a11b9f6aaca2aed63e111e63fb306afd3eb756 Mon Sep 17 00:00:00 2001 From: Arkady <144838822+arkadymarch@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:43:49 -0500 Subject: [PATCH 4/5] style fix --- inst/htmlwidgets/assets/leaflet.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inst/htmlwidgets/assets/leaflet.js b/inst/htmlwidgets/assets/leaflet.js index 3a888a21e..de80e05b3 100644 --- a/inst/htmlwidgets/assets/leaflet.js +++ b/inst/htmlwidgets/assets/leaflet.js @@ -1883,9 +1883,9 @@ methods.addCircleMarkers = function (lat, lng, radius, layerId, group, options, } }; /* -* @param lat Array of arrays of latitude coordinates for polylines -* @param lng Array of arrays of longitude coordinates for polylines -*/ + * @param lat Array of arrays of latitude coordinates for polylines + * @param lng Array of arrays of longitude coordinates for polylines + */ methods.addPolylines = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { @@ -1947,9 +1947,9 @@ methods.addRectangles = function (lat1, lng1, lat2, lng2, layerId, group, option }); }; /* -* @param lat Array of arrays of latitude coordinates for polygons -* @param lng Array of arrays of longitude coordinates for polygons -*/ + * @param lat Array of arrays of latitude coordinates for polygons + * @param lng Array of arrays of longitude coordinates for polygons + */ methods.addPolygons = function (polygons, layerId, group, options, popup, popupOptions, label, labelOptions, highlightOptions) { From 9fe96c3883ba3fd80a9458e431fbffc0a0a4259a Mon Sep 17 00:00:00 2001 From: Arkady <144838822+arkadymarch@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:55:47 -0500 Subject: [PATCH 5/5] update news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index d19b8f52f..842d3515b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # leaflet (development version) +* `{leaflet}` cluster markers now filter according to `{crosstalk}` filters. + * `{leaflet}` no longer install sp by default and attempts to convert object to sf internally before creating a map and warns when it fails conversion (@olivroy, #942). * The `breweries91`, `atlStorms2005`, and `gadmCHE` datasets are now `{sf}` objects (@olivroy, #944).