1/******/ (() => { // webpackBootstrap 2/******/ var __webpack_modules__ = ({ 3
4/***/ "./resources/js/app.js": 5/*!*****************************!*\ 6 !*** ./resources/js/app.js ***! 7 \*****************************/ 8/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 9 10"use strict"; 11__webpack_require__.r(__webpack_exports__); 12/* harmony import */ var _sliders__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sliders */ "./resources/js/sliders.js"); 13/* harmony import */ var _maps__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./maps */ "./resources/js/maps.js"); 14/* harmony import */ var superfish__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! superfish */ "./node_modules/superfish/dist/js/superfish.js"); 15/* harmony import */ var superfish__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(superfish__WEBPACK_IMPORTED_MODULE_2__); 16 17 18 19 20/** Nav Menu Toggle*/ 21if (window.innerWidth < 768) { 22 document.querySelector("#primary-menu").setAttribute("data-menu-hidden", "true"); 23 // document.querySelector("#primary-menu").classList.add("hidden"); 24 25 var menuToggle = document.querySelector("#primary-menu-toggle"); 26 if (menuToggle) menuToggle.addEventListener("click", function () { 27 var _document$querySelect; 28 var current = (_document$querySelect = document.querySelector("#primary-menu").getAttribute("data-menu-hidden")) !== null && _document$querySelect !== void 0 ? _document$querySelect : "false"; 29 if (current === "true") { 30 document.querySelector("#primary-menu").setAttribute("data-menu-hidden", "false"); 31 document.querySelector("#primary-menu").classList.remove("hidden"); 32 return; 33 } 34 if (current === "false") { 35 document.querySelector("#primary-menu").setAttribute("data-menu-hidden", "true"); 36 document.querySelector("#primary-menu").classList.add("hidden"); 37 return; 38 } 39 }); 40} 41jQuery("ul#menu-primary").superfish({}); 42 43/***/ }), 44
45/***/ "./resources/js/maps.js": 46/*!******************************!*\ 47 !*** ./resources/js/maps.js ***! 48 \******************************/ 49/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 50 51"use strict"; 52__webpack_require__.r(__webpack_exports__); 53/* harmony import */ var leaflet__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! leaflet */ "./node_modules/leaflet/dist/leaflet-src.js"); 54/* harmony import */ var leaflet__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(leaflet__WEBPACK_IMPORTED_MODULE_0__); 55/* harmony import */ var leaflet_dist_leaflet_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! leaflet/dist/leaflet.css */ "./node_modules/leaflet/dist/leaflet.css"); 56 57 58 59// Initialize Leaflet map when DOM is ready 60document.addEventListener('DOMContentLoaded', function () { 61 var element = document.getElementById('map'); 62 if (!element) return; 63 var lat = parseFloat(element.dataset.mapLatitude); 64 var lng = parseFloat(element.dataset.mapLongitude); 65 var position = [lat, lng]; 66 var map = leaflet__WEBPACK_IMPORTED_MODULE_0___default().map(element, { 67 center: position, 68 zoom: 16 69 }); 70 71 // OpenStreetMap tiles (replace with a different provider if needed) 72 leaflet__WEBPACK_IMPORTED_MODULE_0___default().tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 73 attribution: '© OpenStreetMap contributors', 74 maxZoom: 19 75 }).addTo(map); 76 77 // Custom SVG icon marker 78 var svgIcon = encodeURIComponent("\n <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='48' height='48'>\n <path d='M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z' fill='#E11D48'/>\n <circle cx='12' cy='9' r='2.5' fill='#fff'/>\n </svg>\n "); 79 var icon = leaflet__WEBPACK_IMPORTED_MODULE_0___default().icon({ 80 iconUrl: "data:image/svg+xml;utf8,".concat(svgIcon), 81 iconSize: [48, 48], 82 iconAnchor: [24, 48], 83 popupAnchor: [0, -44], 84 className: 'leaflet-custom-icon' 85 }); 86 var marker = leaflet__WEBPACK_IMPORTED_MODULE_0___default().marker(position, { 87 icon: icon 88 }).addTo(map); 89 var template = "\n <div class=\"p-2 space-y-2 \">\n <h2 class=\"text-lg font-bold tracking-normal\">\n ".concat(element.dataset.locationName, "\n </h2>\n <p>").concat(element.dataset.locationAddress, "</p>\n <p>\n <a target=\"_blank\" class=\"border-b border-theme-1-base\" href=\"https://www.google.com/maps/search/").concat(encodeURI(element.dataset.locationName), "/@").concat(element.dataset.mapLatitude, ",").concat(element.dataset.mapLongitude, ",17z/\">\n View on Google Maps\n </a>\n </p>\n </div>"); 90 marker.bindPopup(template); 91 marker.on('click', function () { 92 marker.openPopup(); 93 }); 94}); 95 96/***/ }), 97
98/***/ "./resources/js/sliders.js": 99/*!*********************************!*\ 100 !*** ./resources/js/sliders.js ***! 101 \*********************************/ 102/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 103 104"use strict"; 105__webpack_require__.r(__webpack_exports__); 106/* harmony import */ var _glidejs_glide__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @glidejs/glide */ "./node_modules/@glidejs/glide/dist/glide.esm.js"); 107/** Sliders */ 108 109var sliders = document.querySelectorAll(".glide.terms"); 110sliders.forEach(function (slider) { 111 var cols = slider.dataset.columns || 1; 112 new _glidejs_glide__WEBPACK_IMPORTED_MODULE_0__["default"](slider, { 113 perView: 4, 114 type: "carousel", 115 breakpoints: { 116 800: { 117 perView: 1 118 } 119 } 120 }).mount(); 121}); 122var galleries = document.querySelectorAll(".glide.gallery"); 123galleries.forEach(function (gallery) { 124 new _glidejs_glide__WEBPACK_IMPORTED_MODULE_0__["default"](gallery, { 125 type: "carousel", 126 perView: 1, 127 peek: 100, 128 focusAt: "center", 129 breakpoints: { 130 800: { 131 peek: 20 132 } 133 } 134 }).mount(); 135}); 136 137/***/ }), 138 139/***/ "./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[2]!./node_modules/leaflet/dist/leaflet.css": 140/*!*********************************************************************************************************************************************************************************************************!*\ 141 !*** ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[2]!./node_modules/leaflet/dist/leaflet.css ***! 142 \*********************************************************************************************************************************************************************************************************/ 143/***/ ((module, __webpack_exports__, __webpack_require__) => { 144 145"use strict"; 146__webpack_require__.r(__webpack_exports__); 147/* harmony export */ __webpack_require__.d(__webpack_exports__, { 148/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) 149/* harmony export */ }); 150/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js"); 151/* harmony import */ var _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__); 152// Imports 153 154var ___CSS_LOADER_EXPORT___ = _css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()(function(i){return i[1]}); 155// Module 156___CSS_LOADER_EXPORT___.push([module.id, "/* required styles */\r\n\r\n.leaflet-pane,\r\n.leaflet-tile,\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow,\r\n.leaflet-tile-container,\r\n.leaflet-pane > svg,\r\n.leaflet-pane > canvas,\r\n.leaflet-zoom-box,\r\n.leaflet-image-layer,\r\n.leaflet-layer {\r\n\tposition: absolute;\r\n\tleft: 0;\r\n\ttop: 0;\r\n\t}\r\n\r\n.leaflet-container {\r\n\toverflow: hidden;\r\n\t}\r\n\r\n.leaflet-tile,\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow {\r\n\t-webkit-user-select: none;\r\n\t -moz-user-select: none;\r\n\t user-select: none;\r\n\t -webkit-user-drag: none;\r\n\t}\r\n\r\n/* Prevents IE11 from highlighting tiles in blue */\r\n\r\n.leaflet-tile::-moz-selection {\r\n\tbackground: transparent;\r\n}\r\n\r\n.leaflet-tile::selection {\r\n\tbackground: transparent;\r\n}\r\n\r\n/* Safari renders non-retina tile on retina better with this, but Chrome is worse */\r\n\r\n.leaflet-safari .leaflet-tile {\r\n\timage-rendering: -webkit-optimize-contrast;\r\n\t}\r\n\r\n/* hack that prevents hw layers \"stretching\" when loading new tiles */\r\n\r\n.leaflet-safari .leaflet-tile-container {\r\n\twidth: 1600px;\r\n\theight: 1600px;\r\n\t-webkit-transform-origin: 0 0;\r\n\t}\r\n\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow {\r\n\tdisplay: block;\r\n\t}\r\n\r\n/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */\r\n\r\n/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */\r\n\r\n.leaflet-container .leaflet-overlay-pane svg {\r\n\tmax-width: none !important;\r\n\tmax-height: none !important;\r\n\t}\r\n\r\n.leaflet-container .leaflet-marker-pane img,\r\n.leaflet-container .leaflet-shadow-
156pane img,\r\n.leaflet-container .leaflet-tile-pane img,\r\n.leaflet-container img.leaflet-image-layer,\r\n.leaflet-container .leaflet-tile {\r\n\tmax-width: none !important;\r\n\tmax-height: none !important;\r\n\twidth: auto;\r\n\tpadding: 0;\r\n\t}\r\n\r\n.leaflet-container img.leaflet-tile {\r\n\t/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */\r\n\tmix-blend-mode: plus-lighter;\r\n}\r\n\r\n.leaflet-container.leaflet-touch-zoom {\r\n\ttouch-action: pan-x pan-y;\r\n\t}\r\n\r\n.leaflet-container.leaflet-touch-drag {\r\n\t/* Fallback for FF which doesn't support pinch-zoom */\r\n\ttouch-action: none;\r\n\ttouch-action: pinch-zoom;\r\n}\r\n\r\n.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {\r\n\ttouch-action: none;\r\n}\r\n\r\n.leaflet-container {\r\n\t-webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.leaflet-container a {\r\n\t-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);\r\n}\r\n\r\n.leaflet-tile {\r\n\tfilter: inherit;\r\n\tvisibility: hidden;\r\n\t}\r\n\r\n.leaflet-tile-loaded {\r\n\tvisibility: inherit;\r\n\t}\r\n\r\n.leaflet-zoom-box {\r\n\twidth: 0;\r\n\theight: 0;\r\n\tbox-sizing: border-box;\r\n\tz-index: 800;\r\n\t}\r\n\r\n/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\r\n\r\n.leaflet-overlay-pane svg {\r\n\t-moz-user-select: none;\r\n\t}\r\n\r\n.leaflet-pane { z-index: 400; }\r\n\r\n.leaflet-tile-pane { z-index: 200; }\r\n\r\n.leaflet-overlay-pane { z-index: 400; }\r\n\r\n.leaflet-shadow-pane { z-index: 500; }\r\n\r\n.leaflet-marker-pane { z-index: 600; }\r\n\r\n.leaflet-tooltip-pane { z-index: 650; }\r\n\r\n.leaflet-popup-pane { z-index: 700; }\r\n\r\n.leaflet-map-pane canvas { z-index: 100; }\r\n\r\n.leaflet-map-pane svg { z-index: 200; }\r\n\r\n.leaflet-vml-shape {\r\n\twidth: 1px;\r\n\theight: 1px;\r\n\t}\r\n\r\n.lvml {\r\n\tbehavior: url(#default#VML);\r\n\tdisplay: inline-block;\r\n\tposition: absolute;\r\n\t}\r\n\r\n/* control positioning */\r\n\r\n.leaflet-control {\r\n\tposition: relative;\r\n\tz-index: 800;\r\n\tpointer-events: visiblePainted; /* IE 9-10 doesn't have auto */\r\n\tpointer-events: auto;\r\n\t}\r\n\r\n.leaflet-top,\r\n.leaflet-bottom {\r\n\tposition: absolute;\r\n\tz-index: 1000;\r\n\tpointer-events: none;\r\n\t}\r\n\r\n.leaflet-top {\r\n\ttop: 0;\r\n\t}\r\n\r\n.leaflet-right {\r\n\tright: 0;\r\n\t}\r\n\r\n.leaflet-bottom {\r\n\tbottom: 0;\r\n\t}\r\n\r\n.leaflet-left {\r\n\tleft: 0;\r\n\t}\r\n\r\n.leaflet-control {\r\n\tfloat: left;\r\n\tclear: both;\r\n\t}\r\n\r\n.leaflet-right .leaflet-control {\r\n\tfloat: right;\r\n\t}\r\n\r\n.leaflet-top .leaflet-control {\r\n\tmargin-top: 10px;\r\n\t}\r\n\r\n.leaflet-bottom .leaflet-control {\r\n\tmargin-bottom: 10px;\r\n\t}\r\n\r\n.leaflet-left .leaflet-control {\r\n\tmargin-left: 10px;\r\n\t}\r\n\r\n.leaflet-right .leaflet-control {\r\n\tmargin-right: 10px;\r\n\t}\r\n\r\n/* zoom and fade animations */\r\n\r\n.leaflet-fade-anim .leaflet-popup {\r\n\topacity: 0;\r\n\ttransition: opacity 0.2s linear;\r\n\t}\r\n\r\n.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\r\n\topacity: 1;\r\n\t}\r\n\r\n.leaflet-zoom-animated {\r\n\ttransform-origin: 0 0;\r\n\t}\r\n\r\nsvg.leaflet-zoom-animated {\r\n\twill-change: transform;\r\n}\r\n\r\n.leaflet-zoom-anim .leaflet-zoom-animated {\r\n\ttransition: transform 0.25s cubic-bezier(0,0,0.25,1);\r\n\t}\r\n\r\n.leaflet-zoom-anim .leaflet-tile,\r\n.leaflet-pan-anim .leaflet-tile {\r\n\ttransition: none;\r\n\t}\r\n\r\n.leaflet-zoom-anim .leaflet-zoom-hide {\r\n\tvisibility: hidden;\r\n\t}\r\n\r\n/* cursors */\r\n\r\n.leaflet-interactive {\r\n\tcursor: pointer;\r\n\t}\r\n\r\n.leaflet-grab {\r\n\tcursor: grab;\r\n\t}\r\n\r\n.leaflet-crosshair,\r\n.leaflet-crosshair .leaflet-interactive {\r\n\tcursor: crosshair;\r\n\t}\r\n\r\n.leaflet-popup-pane,\r\n.leaflet-control {\r\n\tcursor: auto;\r\n\t}\r\n\r\n.leaflet-dragging .leaflet-grab,\r\n.leaflet-dragging .leaflet-grab .leaflet-interactive,\r\n.leaflet-dragging .leaflet-marker-draggable {\r\n\tcursor: move;\r\n\tcursor: grabbing;\r\n\t}\r\n\r\n/* marker & overlays interactivity */\r\n\r\n.leaflet-marker-icon,\r\n.leaflet-marker-shadow,\r\n.leaflet-image-layer,\r\n.leaflet-pane > svg path,\r\n.leaflet-tile-container {\r\n\tpointer-events: none;\r\n\t}\r\n\r\n.leaflet-marker-icon.leaflet-interactive,\r\n.leaflet-image-layer.leaflet-interactive,\r\n.leaflet-pane > svg path.leaflet-interactive,\r\nsvg.leaflet-image-layer.leaflet-interactive path {\r\n\tpointer-events: visiblePainted; /* IE 9-10 doesn't have auto */\r\n\tpointer-events: auto;\r\n\t}\r\n\r\n/* visual tweaks */\r\n\r\n.leaflet-container {\r\n\tbackground: #ddd;\r\n\toutline-offset: 1px;\r\n\t}\r\n\r\n.leaflet-container a {\r\n\tcolor: #0078A8;\r\n\t}\r\n\r\n.leaflet-zoom-box {\r\n\tborder: 2px dotted #38f;\r\n\tbackground: rgba(255,255,255,0.5);\r\n\t}\r\n\r\n/* general typography */\r\n\r\n.leaflet-container {\r\n\tfont-family: \"Helvetica Neue\", Arial, Helvetica, sans-serif;\r\n\tfont-size: 12px;\r\n\tfont-size: 0.75rem;\r\n\tline-height: 1.5;\r\n\t}\r\n\r\n/* general toolbar styles */\r\n\r\n.leaflet-bar {\r\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.65);\r\n\tborder-radius: 4px;\r\n\t}\r\n\r\n.leaflet-bar a {\r\n\tbackground-color: #fff;\r\n\tborder-bottom: 1px solid #ccc;\r\n\twidth: 26px;
156\r\n\theight: 26px;\r\n\tline-height: 26px;\r\n\tdisplay: block;\r\n\ttext-align: center;\r\n\ttext-decoration: none;\r\n\tcolor: black;\r\n\t}\r\n\r\n.leaflet-bar a,\r\n.leaflet-control-layers-toggle {\r\n\tbackground-position: 50% 50%;\r\n\tbackground-repeat: no-repeat;\r\n\tdisplay: block;\r\n\t}\r\n\r\n.leaflet-bar a:hover,\r\n.leaflet-bar a:focus {\r\n\tbackground-color: #f4f4f4;\r\n\t}\r\n\r\n.leaflet-bar a:first-child {\r\n\tborder-top-left-radius: 4px;\r\n\tborder-top-right-radius: 4px;\r\n\t}\r\n\r\n.leaflet-bar a:last-child {\r\n\tborder-bottom-left-radius: 4px;\r\n\tborder-bottom-right-radius: 4px;\r\n\tborder-bottom: none;\r\n\t}\r\n\r\n.leaflet-bar a.leaflet-disabled {\r\n\tcursor: default;\r\n\tbackground-color: #f4f4f4;\r\n\tcolor: #bbb;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-bar a {\r\n\twidth: 30px;\r\n\theight: 30px;\r\n\tline-height: 30px;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-bar a:first-child {\r\n\tborder-top-left-radius: 2px;\r\n\tborder-top-right-radius: 2px;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-bar a:last-child {\r\n\tborder-bottom-left-radius: 2px;\r\n\tborder-bottom-right-radius: 2px;\r\n\t}\r\n\r\n/* zoom control */\r\n\r\n.leaflet-control-zoom-in,\r\n.leaflet-control-zoom-out {\r\n\tfont: bold 18px 'Lucida Console', Monaco, monospace;\r\n\ttext-indent: 1px;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {\r\n\tfont-size: 22px;\r\n\t}\r\n\r\n/* layers control */\r\n\r\n.leaflet-control-layers {\r\n\tbox-shadow: 0 1px 5px rgba(0,0,0,0.4);\r\n\tbackground: #fff;\r\n\tborder-radius: 5px;\r\n\t}\r\n\r\n.leaflet-control-layers-toggle {\r\n\tbackground-image: url(images/layers.png);\r\n\twidth: 36px;\r\n\theight: 36px;\r\n\t}\r\n\r\n.leaflet-retina .leaflet-control-layers-toggle {\r\n\tbackground-image: url(images/layers-2x.png);\r\n\tbackground-size: 26px 26px;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-layers-toggle {\r\n\twidth: 44px;\r\n\theight: 44px;\r\n\t}\r\n\r\n.leaflet-control-layers .leaflet-control-layers-list,\r\n.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\r\n\tdisplay: none;\r\n\t}\r\n\r\n.leaflet-control-layers-expanded .leaflet-control-layers-list {\r\n\tdisplay: block;\r\n\tposition: relative;\r\n\t}\r\n\r\n.leaflet-control-layers-expanded {\r\n\tpadding: 6px 10px 6px 6px;\r\n\tcolor: #333;\r\n\tbackground: #fff;\r\n\t}\r\n\r\n.leaflet-control-layers-scrollbar {\r\n\toverflow-y: scroll;\r\n\toverflow-x: hidden;\r\n\tpadding-right: 5px;\r\n\t}\r\n\r\n.leaflet-control-layers-selector {\r\n\tmargin-top: 2px;\r\n\tposition: relative;\r\n\ttop: 1px;\r\n\t}\r\n\r\n.leaflet-control-layers label {\r\n\tdisplay: block;\r\n\tfont-size: 13px;\r\n\tfont-size: 1.08333em;\r\n\t}\r\n\r\n.leaflet-control-layers-separator {\r\n\theight: 0;\r\n\tborder-top: 1px solid #ddd;\r\n\tmargin: 5px -10px 5px -6px;\r\n\t}\r\n\r\n/* Default icon URLs */\r\n\r\n.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */\r\n\tbackground-image: url(images/marker-icon.png);\r\n\t}\r\n\r\n/* attribution and scale controls */\r\n\r\n.leaflet-container .leaflet-control-attribution {\r\n\tbackground: #fff;\r\n\tbackground: rgba(255, 255, 255, 0.8);\r\n\tmargin: 0;\r\n\t}\r\n\r\n.leaflet-control-attribution,\r\n.leaflet-control-scale-line {\r\n\tpadding: 0 5px;\r\n\tcolor: #333;\r\n\tline-height: 1.4;\r\n\t}\r\n\r\n.leaflet-control-attribution a {\r\n\ttext-decoration: none;\r\n\t}\r\n\r\n.leaflet-control-attribution a:hover,\r\n.leaflet-control-attribution a:focus {\r\n\ttext-decoration: underline;\r\n\t}\r\n\r\n.leaflet-attribution-flag {\r\n\tdisplay: inline !important;\r\n\tvertical-align: baseline !important;\r\n\twidth: 1em;\r\n\theight: 0.6669em;\r\n\t}\r\n\r\n.leaflet-left .leaflet-control-scale {\r\n\tmargin-left: 5px;\r\n\t}\r\n\r\n.leaflet-bottom .leaflet-control-scale {\r\n\tmargin-bottom: 5px;\r\n\t}\r\n\r\n.leaflet-control-scale-line {\r\n\tborder: 2px solid #777;\r\n\tborder-top: none;\r\n\tline-height: 1.1;\r\n\tpadding: 2px 5px 1px;\r\n\twhite-space: nowrap;\r\n\tbox-sizing: border-box;\r\n\tbackground: rgba(255, 255, 255, 0.8);\r\n\ttext-shadow: 1px 1px #fff;\r\n\t}\r\n\r\n.leaflet-control-scale-line:not(:first-child) {\r\n\tborder-top: 2px solid #777;\r\n\tborder-bottom: none;\r\n\tmargin-top: -2px;\r\n\t}\r\n\r\n.leaflet-control-scale-line:not(:first-child):not(:last-child) {\r\n\tborder-bottom: 2px solid #777;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-attribution,\r\n.leaflet-touch .leaflet-control-layers,\r\n.leaflet-touch .leaflet-bar {\r\n\tbox-shadow: none;\r\n\t}\r\n\r\n.leaflet-touch .leaflet-control-layers,\r\n.leaflet-touch .leaflet-bar {\r\n\tborder: 2px solid rgba(0,0,0,0.2);\r\n\tbackground-clip: padding-box;\r\n\t}\r\n\r\n/* popup */\r\n\r\n.leaflet-popup {\r\n\tposition: absolute;\r\n\ttext-align: center;\r\n\tmargin-bottom: 20px;\r\n\t}\r\n\r\n.leaflet-popup-content-wrapper {\r\n\tpadding: 1px;\r\n\ttext-align: left;\r\n\tborder-radius: 12px;\r\n\t}\r\n\r\n.leaflet-popup-content {\r\n\tmargin: 13px 24px 13px 20px;\r\n\tline-height: 1.3;\r\n\tfont-size: 13px;\r\n\tfont-size: 1.08333em;\r\n\tmin-height: 1px;\r\n\t}\r\n\r\n.leaflet-popup-content p {\r\n\tmargin: 17px 0;\r\n\tmargin: 1.3em 0;\r\n\t}\r\n\r\n.leaflet-popup-tip-container {\r\n\twidth: 40px;\r\n\theight: 20px;\r\n\tposition: absolute;\r\n\tleft: 50%;
156\r\n\tmargin-top: -1px;\r\n\tmargin-left: -20px;\r\n\toverflow: hidden;\r\n\tpointer-events: none;\r\n\t}\r\n\r\n.leaflet-popup-tip {\r\n\twidth: 17px;\r\n\theight: 17px;\r\n\tpadding: 1px;\r\n\r\n\tmargin: -10px auto 0;\r\n\tpointer-events: auto;\r\n\ttransform: rotate(45deg);\r\n\t}\r\n\r\n.leaflet-popup-content-wrapper,\r\n.leaflet-popup-tip {\r\n\tbackground: white;\r\n\tcolor: #333;\r\n\tbox-shadow: 0 3px 14px rgba(0,0,0,0.4);\r\n\t}\r\n\r\n.leaflet-container a.leaflet-popup-close-button {\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tright: 0;\r\n\tborder: none;\r\n\ttext-align: center;\r\n\twidth: 24px;\r\n\theight: 24px;\r\n\tfont: 16px/24px Tahoma, Verdana, sans-serif;\r\n\tcolor: #757575;\r\n\ttext-decoration: none;\r\n\tbackground: transparent;\r\n\t}\r\n\r\n.leaflet-container a.leaflet-popup-close-button:hover,\r\n.leaflet-container a.leaflet-popup-close-button:focus {\r\n\tcolor: #585858;\r\n\t}\r\n\r\n.leaflet-popup-scrolled {\r\n\toverflow: auto;\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-popup-content-wrapper {\r\n\t-ms-zoom: 1;\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-popup-tip {\r\n\twidth: 24px;\r\n\tmargin: 0 auto;\r\n\r\n\t-ms-filter: \"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)\";\r\n\tfilter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\r\n\t}\r\n\r\n.leaflet-oldie .leaflet-control-zoom,\r\n.leaflet-oldie .leaflet-control-layers,\r\n.leaflet-oldie .leaflet-popup-content-wrapper,\r\n.leaflet-oldie .leaflet-popup-tip {\r\n\tborder: 1px solid #999;\r\n\t}\r\n\r\n/* div icon */\r\n\r\n.leaflet-div-icon {\r\n\tbackground: #fff;\r\n\tborder: 1px solid #666;\r\n\t}\r\n\r\n/* Tooltip */\r\n\r\n/* Base styles for the element that has a tooltip */\r\n\r\n.leaflet-tooltip {\r\n\tposition: absolute;\r\n\tpadding: 6px;\r\n\tbackground-color: #fff;\r\n\tborder: 1px solid #fff;\r\n\tborder-radius: 3px;\r\n\tcolor: #222;\r\n\twhite-space: nowrap;\r\n\t-webkit-user-select: none;\r\n\t-moz-user-select: none;\r\n\tuser-select: none;\r\n\tpointer-events: none;\r\n\tbox-shadow: 0 1px 3px rgba(0,0,0,0.4);\r\n\t}\r\n\r\n.leaflet-tooltip.leaflet-interactive {\r\n\tcursor: pointer;\r\n\tpointer-events: auto;\r\n\t}\r\n\r\n.leaflet-tooltip-top:before,\r\n.leaflet-tooltip-bottom:before,\r\n.leaflet-tooltip-left:before,\r\n.leaflet-tooltip-right:before {\r\n\tposition: absolute;\r\n\tpointer-events: none;\r\n\tborder: 6px solid transparent;\r\n\tbackground: transparent;\r\n\tcontent: \"\";\r\n\t}\r\n\r\n/* Directions */\r\n\r\n.leaflet-tooltip-bottom {\r\n\tmargin-top: 6px;\r\n}\r\n\r\n.leaflet-tooltip-top {\r\n\tmargin-top: -6px;\r\n}\r\n\r\n.leaflet-tooltip-bottom:before,\r\n.leaflet-tooltip-top:before {\r\n\tleft: 50%;\r\n\tmargin-left: -6px;\r\n\t}\r\n\r\n.leaflet-tooltip-top:before {\r\n\tbottom: 0;\r\n\tmargin-bottom: -12px;\r\n\tborder-top-color: #fff;\r\n\t}\r\n\r\n.leaflet-tooltip-bottom:before {\r\n\ttop: 0;\r\n\tmargin-top: -12px;\r\n\tmargin-left: -6px;\r\n\tborder-bottom-color: #fff;\r\n\t}\r\n\r\n.leaflet-tooltip-left {\r\n\tmargin-left: -6px;\r\n}\r\n\r\n.leaflet-tooltip-right {\r\n\tmargin-left: 6px;\r\n}\r\n\r\n.leaflet-tooltip-left:before,\r\n.leaflet-tooltip-right:before {\r\n\ttop: 50%;\r\n\tmargin-top: -6px;\r\n\t}\r\n\r\n.leaflet-tooltip-left:before {\r\n\tright: 0;\r\n\tmargin-right: -12px;\r\n\tborder-left-color: #fff;\r\n\t}\r\n\r\n.leaflet-tooltip-right:before {\r\n\tleft: 0;\r\n\tmargin-left: -12px;\r\n\tborder-right-color: #fff;\r\n\t}\r\n\r\n/* Printing */\r\n\r\n@media print {\r\n\t/* Prevent printers from removing background-images of controls. */\r\n\t.leaflet-control {\r\n\t\t-webkit-print-color-adjust: exact;\r\n\t\tprint-color-adjust: exact;\r\n\t\t}\r\n\t}\r\n", ""]); 157// Exports 158/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); 159 160 161/***/ }), 162
vendor: 1,948 bytes, lines 163-238
163/***/ "./node_modules/css-loader/dist/runtime/api.js": 164/*!*****************************************************!*\ 165 !*** ./node_modules/css-loader/dist/runtime/api.js ***! 166 \*****************************************************/ 167/***/ ((module) => { 168 169"use strict"; 170 171 172/* 173 MIT License http://www.opensource.org/licenses/mit-license.php 174 Author Tobias Koppers @sokra 175*/ 176// css base code, injected by the css-loader 177// eslint-disable-next-line func-names 178module.exports = function (cssWithMappingToString) { 179 var list = []; // return the list of modules as css string 180 181 list.toString = function toString() { 182 return this.map(function (item) { 183 var content = cssWithMappingToString(item); 184 185 if (item[2]) { 186 return "@media ".concat(item[2], " {").concat(content, "}"); 187 } 188 189 return content; 190 }).join(""); 191 }; // import a list of modules into the list 192 // eslint-disable-next-line func-names 193 194 195 list.i = function (modules, mediaQuery, dedupe) { 196 if (typeof modules === "string") { 197 // eslint-disable-next-line no-param-reassign 198 modules = [[null, modules, ""]]; 199 } 200 201 var alreadyImportedModules = {}; 202 203 if (dedupe) { 204 for (var i = 0; i < this.length; i++) { 205 // eslint-disable-next-line prefer-destructuring 206 var id = this[i][0]; 207 208 if (id != null) { 209 alreadyImportedModules[id] = true; 210 } 211 } 212 } 213 214 for (var _i = 0; _i < modules.length; _i++) { 215 var item = [].concat(modules[_i]); 216 217 if (dedupe && alreadyImportedModules[item[0]]) { 218 // eslint-disable-next-line no-continue 219 continue; 220 } 221 222 if (mediaQuery) { 223 if (!item[2]) { 224 item[2] = mediaQuery; 225 } else { 226 item[2] = "".concat(mediaQuery, " and ").concat(item[2]); 227 } 228 } 229 230 list.push(item); 231 } 232 }; 233 234 return list; 235}; 236 237/***/ }), 238
vendor: 455,849 bytes, lines 239-14759
239/***/ "./node_modules/leaflet/dist/leaflet-src.js": 240/*!**************************************************!*\ 241 !*** ./node_modules/leaflet/dist/leaflet-src.js ***! 242 \**************************************************/ 243/***/ (function(__unused_webpack_module, exports) { 244 245/* @preserve 246 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com 247 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade 248 */ 249 250(function (global, factory) { 251 true ? factory(exports) : 252 0; 253})(this, (function (exports) { 'use strict'; 254 255 var version = "1.9.4"; 256 257 /* 258 * @namespace Util 259 * 260 * Various utility functions, used by Leaflet internally. 261 */ 262 263 // @function extend(dest: Object, src?: Object): Object 264 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 265 function extend(dest) { 266 var i, j, len, src; 267 268 for (j = 1, len = arguments.length; j < len; j++) { 269 src = arguments[j]; 270 for (i in src) { 271 dest[i] = src[i]; 272 } 273 } 274 return dest; 275 } 276 277 // @function create(proto: Object, properties?: Object): Object 278 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 279 var create$2 = Object.create || (function () { 280 function F() {} 281 return function (proto) { 282 F.prototype = proto; 283 return new F(); 284 }; 285 })(); 286 287 // @function bind(fn: Function, â¦): Function 288 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). 289 // Has a `L.bind()` shortcut. 290 function bind(fn, obj) { 291 var slice = Array.prototype.slice; 292 293 if (fn.bind) { 294 return fn.bind.apply(fn, slice.call(arguments, 1)); 295 } 296 297 var args = slice.call(arguments, 2); 298 299 return function () { 300 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 301 }; 302 } 303 304 // @property lastId: Number 305 // Last unique ID used by [`stamp()`](#util-stamp) 306 var lastId = 0; 307 308 // @function stamp(obj: Object): Number 309 // Returns the unique ID of an object, assigning it one if it doesn't have it. 310 function stamp(obj) { 311 if (!('_leaflet_id' in obj)) { 312 obj['_leaflet_id'] = ++lastId; 313 } 314 return obj._leaflet_id; 315 } 316 317 // @function throttle(fn: Function, time: Number, context: Object): Function 318 // Returns a function which executes function `fn` with the given scope `context` 319 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function 320 // `fn` will be called no more than one time per given amount of `time`. The arguments 321 // received by the bound function will be any arguments passed when binding the 322 // function, followed by any arguments passed when invoking the bound function. 323 // Has an `L.throttle` shortcut. 324 function throttle(fn, time, context) { 325 var lock, args, wrapperFn, later; 326 327 later = function () { 328 // reset lock and call if queued 329 lock = false; 330 if (args) { 331 wrapperFn.apply(context, args); 332 args = false; 333 } 334 }; 335 336 wrapperFn = function () { 337 if (lock) { 338 // called too soon, queue to call later 339 args = arguments; 340 341 } else { 342 // call and lock until later 343 fn.apply(context, arguments); 344 setTimeout(later, time); 345 lock = true; 346 } 347 }; 348 349 return wrapperFn; 350 } 351 352 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 353 // Returns the number `num` modulo `range` in such a way so it lies within 354 // `range[0]` and `range[1]`. The returned value will be always smaller than 355 // `range[1]` unless `includeMax` is set to `true`. 356 function wrapNum(x, range, includeMax) { 357 var max = range[1], 358 min = range[0], 359 d = max - min; 360 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 361 } 362 363 // @function falseFn(): Function 364 // Returns a function which always returns `false`. 365 function falseFn() { return false; } 366 367 // @function formatNum(num: Number, precision?: Number|false): Number 368 // Returns the number `num` rounded with specified `precision`. 369 // The default `precision` value is 6 decimal places. 370 // `false` can be passed to skip any processing (can be useful to avoid round-off errors). 371 function formatNum(num, precision) { 372 if (precision === false) { return num; } 373 var pow = Math.pow(10, precision === undefined ? 6 : precision); 374 return Math.round(num * pow) / pow; 375 } 376 377 // @function trim(str: String): String 378 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 379 function trim(str) { 380 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 381 } 382 383 // @function splitWords(str: String): String[] 384 // Trims and splits the string on whitespace and returns the array of parts. 385 function splitWords(str) { 386 return trim(str).split(/\s+/); 387 } 388 389 // @function setOptions(obj: Object, options: Object): Object 390 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. 391 function setOptions(obj, options) { 392 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) { 393 obj.options = obj.options ? create$2(obj.options) : {}; 394 } 395 for (var i in options) { 396 obj.options[i] = options[i]; 397 } 398 return obj.options; 399 } 400 401 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String 402 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` 403 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will 404 // be appended at the end. If `uppercase` is `true`, the parameter names will 405 // be uppercased (e.g. `'?A=foo&B=bar'`) 406 function getParamString(obj, existingUrl, uppercase) { 407 var params = []; 408 for (var i in obj) { 409 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); 410 } 411 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); 412 } 413 414 var templateRe = /\{ *([\w_ -]+) *\}/g; 415 416 // @function template(str: String, data: Object): String 417 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` 418 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string 419 // `('Hello foo, bar')`. You can also specify functions instead of strings for 420 // data values â they will be evaluated passing `data` as an argument. 421 function template(str, data) { 422 return str.replace(templateRe, function (str, key) { 423 var value = data[key]; 424 425 if (value === undefined) { 426 throw new Error('No value provided for variable ' + str); 427 428 } else if (typeof value === 'function') { 429 value = value(data); 430 } 431 return value; 432 }); 433 } 434 435 // @function isArray(obj): Boolean 436 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) 437 var isArray = Array.isArray || function (obj) { 438 return (Object.prototype.toString.call(obj) === '[object Array]'); 439 }; 440 441 // @function indexOf(array: Array, el: Object): Number 442 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) 443 function indexOf(array, el) { 444 for (var i = 0; i < array.length; i++) { 445 if (array[i] === el) { return i; } 446 } 447 return -1; 448 } 449 450 // @property emptyImageUrl: String 451 // Data URI string containing a base64-encoded empty GIF image. 452 // Used as a hack to free memory from unused images on WebKit-powered 453 // mobile devices (by setting image `src` to this string). 454 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; 455 456 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/ 457 458 function getPrefixed(name) { 459 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 460 } 461 462 var lastTime = 0; 463 464 // fallback for IE 7-8 465 function timeoutDefer(fn) { 466 var time = +new Date(), 467 timeToCall = Math.max(0, 16 - (time - lastTime)); 468 469 lastTime = time + timeToCall; 470 return window.setTimeout(fn, timeToCall); 471 } 472 473 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; 474 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || 475 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; 476 477 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number 478 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to 479 // `context` if given. When `immediate` is set, `fn` is called immediately if 480 // the browser doesn't have native support for 481 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), 482 // otherwise it's delayed. Returns a request ID that can be used to cancel the request. 483 function requestAnimFrame(fn, context, immediate) { 484 if (immediate && requestFn === timeoutDefer) { 485 fn.call(context); 486 } else { 487 return requestFn.call(window, bind(fn, context)); 488 } 489 } 490 491 // @function cancelAnimFrame(id: Number): undefined 492 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). 493 function cancelAnimFrame(id) { 494 if (id) { 495 cancelFn.call(window, id); 496 } 497 } 498 499 var Util = { 500 __proto__: null, 501 extend: extend, 502 create: create$2, 503 bind: bind, 504 get lastId () { return lastId; }, 505 stamp: stamp, 506 throttle: throttle, 507 wrapNum: wrapNum, 508 falseFn: falseFn, 509 formatNum: formatNum, 510 trim: trim, 511 splitWords: splitWords, 512 setOptions: setOptions, 513 getParamString: getParamString, 514 template: template, 515 isArray: isArray, 516 indexOf: indexOf, 517 emptyImageUrl: emptyImageUrl, 518 requestFn: requestFn, 519 cancelFn: cancelFn, 520 requestAnimFrame: requestAnimFrame, 521 cancelAnimFrame: cancelAnimFrame 522 }; 523 524 // @class Class 525 // @aka L.Class 526 527 // @section 528 // @uninheritable 529 530 // Thanks to John Resig and Dean Edwards for inspiration! 531 532 function Class() {} 533 534 Class.extend = function (props) { 535 536 // @function extend(props: Object): Function 537 // [Extends the current class](#class-inheritance) given the properties to be included. 538 // Returns a Javascript function that is a class constructor (to be called with `new`). 539 var NewClass = function () { 540 541 setOptions(this); 542 543 // call the constructor 544 if (this.initialize) { 545 this.initialize.apply(this, arguments); 546 } 547 548 // call all constructor hooks 549 this.callInitHooks(); 550 }; 551 552 var parentProto = NewClass.__super__ = this.prototype; 553 554 var proto = create$2(parentProto); 555 proto.constructor = NewClass; 556 557 NewClass.prototype = proto; 558 559 // inherit parent's statics 560 for (var i in this) { 561 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') { 562 NewClass[i] = this[i]; 563 } 564 } 565 566 // mix static properties into the class 567 if (props.statics) { 568 extend(NewClass, props.statics); 569 } 570 571 // mix includes into the prototype 572 if (props.includes) { 573 checkDeprecatedMixinEvents(props.includes); 574 extend.apply(null, [proto].concat(props.includes)); 575 } 576 577 // mix given properties into the prototype 578 extend(proto, props); 579 delete proto.statics; 580 delete proto.includes; 581 582 // merge options 583 if (proto.options) { 584 proto.options = parentProto.options ? create$2(parentProto.options) : {}; 585 extend(proto.options, props.options); 586 } 587 588 proto._initHooks = []; 589 590 // add method for calling all hooks 591 proto.callInitHooks = function () { 592 593 if (this._initHooksCalled) { return; } 594 595 if (parentProto.callInitHooks) { 596 parentProto.callInitHooks.call(this); 597 } 598 599 this._initHooksCalled = true; 600 601 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 602 proto._initHooks[i].call(this); 603 } 604 }; 605 606 return NewClass; 607 }; 608 609 610 // @function include(properties: Object): this 611 // [Includes a mixin](#class-includes) into the current class. 612 Class.include = function (props) { 613 var parentOptions = this.prototype.options; 614 extend(this.prototype, props); 615 if (props.options) { 616 this.prototype.options = parentOptions; 617 this.mergeOptions(props.options); 618 } 619 return this; 620 }; 621 622 // @function mergeOptions(options: Object): this 623 // [Merges `options`](#class-options) into the defaults of the class. 624 Class.mergeOptions = function (options) { 625 extend(this.prototype.options, options); 626 return this; 627 }; 628 629 // @function addInitHook(fn: Function): this 630 // Adds a [constructor hook](#class-constructor-hooks) to the class. 631 Class.addInitHook = function (fn) { // (Function) || (String, args...) 632 var args = Array.prototype.slice.call(arguments, 1); 633 634 var init = typeof fn === 'function' ? fn : function () { 635 this[fn].apply(this, args); 636 }; 637 638 this.prototype._initHooks = this.prototype._initHooks || []; 639 this.prototype._initHooks.push(init); 640 return this; 641 }; 642 643 function checkDeprecatedMixinEvents(includes) { 644 /* global L: true */ 645 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 646 647 includes = isArray(includes) ? includes : [includes]; 648 649 for (var i = 0; i < includes.length; i++) { 650 if (includes[i] === L.Mixin.Events) { 651 console.warn('Deprecated include of L.Mixin.Events: ' + 652 'this property will be removed in future releases, ' + 653 'please inherit from L.Evented instead.', new Error().stack); 654 } 655 } 656 } 657 658 /* 659 * @class Evented 660 * @aka L.Evented 661 * @inherits Class 662 * 663 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event). 664 * 665 * @example 666 * 667 * ```js 668 * map.on('click', function(e) { 669 * alert(e.latlng); 670 * } ); 671 * ``` 672 * 673 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 674 * 675 * ```js 676 * function onClick(e) { ... } 677 * 678 * map.on('click', onClick); 679 * map.off('click', onClick); 680 * ``` 681 */ 682 683 var Events = { 684 /* @method on(type: String, fn: Function, context?: Object): this 685 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). 686 * 687 * @alternative 688 * @method on(eventMap: Object): this 689 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 690 */ 691 on: function (types, fn, context) { 692 693 // types can be a map of types/handlers 694 if (typeof types === 'object') { 695 for (var type in types) { 696 // we don't process space-separated events here for performance; 697 // it's a hot path since Layer uses the on(obj) syntax 698 this._on(type, types[type], fn); 699 } 700 701 } else { 702 // types can be a string of space-separated words 703 types = splitWords(types); 704 705 for (var i = 0, len = types.length; i < len; i++) { 706 this._on(types[i], fn, context); 707 } 708 } 709 710 return this; 711 }, 712 713 /* @method off(type: String, fn?: Function, context?: Object): this 714 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. 715 * 716 * @alternative 717 * @method off(eventMap: Object): this 718 * Removes a set of type/listener pairs. 719 * 720 * @alternative 721 * @method off: this 722 * Removes all listeners to all events on the object. This includes implicitly attached events. 723 */ 724 off: function (types, fn, context) { 725 726 if (!arguments.length) { 727 // clear all listeners if called without arguments 728 delete this._events; 729 730 } else if (typeof types === 'object') { 731 for (var type in types) { 732 this._off(type, types[type], fn); 733 } 734 735 } else { 736 types = splitWords(types); 737 738 var removeAll = arguments.length === 1; 739 for (var i = 0, len = types.length; i < len; i++) { 740 if (removeAll) { 741 this._off(types[i]); 742 } else { 743 this._off(types[i], fn, context); 744 } 745 } 746 } 747 748 return this; 749 }, 750 751 // attach listener (without syntactic sugar now) 752 _on: function (type, fn, context, _once) { 753 if (typeof fn !== 'function') { 754 console.warn('wrong listener type: ' + typeof fn); 755 return; 756 } 757 758 // check if fn already there 759 if (this._listens(type, fn, context) !== false) { 760 return; 761 } 762 763 if (context === this) { 764 // Less memory footprint. 765 context = undefined; 766 } 767 768 var newListener = {fn: fn, ctx: context}; 769 if (_once) { 770 newListener.once = true; 771 } 772 773 this._events = this._events || {}; 774 this._events[type] = this._events[type] || []; 775 this._events[type].push(newListener); 776 }, 777 778 _off: function (type, fn, context) { 779 var listeners, 780 i, 781 len; 782 783 if (!this._events) { 784 return; 785 } 786 787 listeners = this._events[type]; 788 if (!listeners) { 789 return; 790 } 791 792 if (arguments.length === 1) { // remove all 793 if (this._firingCount) { 794 // Set all removed listeners to noop 795 // so they are not called if remove happens in fire 796 for (i = 0, len = listeners.length; i < len; i++) { 797 listeners[i].fn = falseFn; 798 } 799 } 800 // clear all listeners for a type if function isn't specified 801 delete this._events[type]; 802 return; 803 } 804 805 if (typeof fn !== 'function') { 806 console.warn('wrong listener type: ' + typeof fn); 807 return; 808 } 809 810 // find fn and remove it 811 var index = this._listens(type, fn, context); 812 if (index !== false) { 813 var listener = listeners[index]; 814 if (this._firingCount) { 815 // set the removed listener to noop so that's not called if remove happens in fire 816 listener.fn = falseFn; 817 818 /* copy array in case events are being fired */ 819 this._events[type] = listeners = listeners.slice(); 820 } 821 listeners.splice(index, 1); 822 } 823 }, 824 825 // @method fire(type: String, data?: Object, propagate?: Boolean): this 826 // Fires an event of the specified type. You can optionally provide a data 827 // object â the first argument of the listener function will contain its 828 // properties. The event can optionally be propagated to event parents. 829 fire: function (type, data, propagate) { 830 if (!this.listens(type, propagate)) { return this; } 831 832 var event = extend({}, data, { 833 type: type, 834 target: this, 835 sourceTarget: data && data.sourceTarget || this 836 }); 837 838 if (this._events) { 839 var listeners = this._events[type]; 840 if (listeners) { 841 this._firingCount = (this._firingCount + 1) || 1; 842 for (var i = 0, len = listeners.length; i < len; i++) { 843 var l = listeners[i]; 844 // off overwrites l.fn, so we need to copy fn to a var 845 var fn = l.fn; 846 if (l.once) { 847 this.off(type, fn, l.ctx); 848 } 849 fn.call(l.ctx || this, event); 850 } 851 852 this._firingCount--; 853 } 854 } 855 856 if (propagate) { 857 // propagate the event to parents (set with addEventParent) 858 this._propagateEvent(event); 859 } 860 861 return this; 862 }, 863 864 // @method listens(type: String, propagate?: Boolean): Boolean 865 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean 866 // Returns `true` if a particular event type has any listeners attached to it. 867 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. 868 listens: function (type, fn, context, propagate) { 869 if (typeof type !== 'string') { 870 console.warn('"string" type argument expected'); 871 } 872 873 // we don't overwrite the input `fn` value, because we need to use it for propagation 874 var _fn = fn; 875 if (typeof fn !== 'function') { 876 propagate = !!fn; 877 _fn = undefined; 878 context = undefined; 879 } 880 881 var listeners = this._events && this._events[type]; 882 if (listeners && listeners.length) { 883 if (this._listens(type, _fn, context) !== false) { 884 return true; 885 } 886 } 887 888 if (propagate) { 889 // also check parents for listeners if event propagates 890 for (var id in this._eventParents) { 891 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; } 892 } 893 } 894 return false; 895 }, 896 897 // returns the index (number) or false 898 _listens: function (type, fn, context) { 899 if (!this._events) { 900 return false; 901 } 902 903 var listeners = this._events[type] || []; 904 if (!fn) { 905 return !!listeners.length; 906 } 907 908 if (context === this) { 909 // Less memory footprint. 910 context = undefined; 911 } 912 913 for (var i = 0, len = listeners.length; i < len; i++) { 914 if (listeners[i].fn === fn && listeners[i].ctx === context) { 915 return i; 916 } 917 } 918 return false; 919 920 }, 921 922 // @method once(â¦): this 923 // Behaves as [`on(â¦)`](#evented-on), except the listener will only get fired once and then removed. 924 once: function (types, fn, context) { 925 926 // types can be a map of types/handlers 927 if (typeof types === 'object') { 928 for (var type in types) { 929 // we don't process space-separated events here for performance; 930 // it's a hot path since Layer uses the on(obj) syntax 931 this._on(type, types[type], fn, true); 932 } 933 934 } else { 935 // types can be a string of space-separated words 936 types = splitWords(types); 937 938 for (var i = 0, len = types.length; i < len; i++) { 939 this._on(types[i], fn, context, true); 940 } 941 } 942 943 return this; 944 }, 945 946 // @method addEventParent(obj: Evented): this 947 // Adds an event parent - an `Evented` that will receive propagated events 948 addEventParent: function (obj) { 949 this._eventParents = this._eventParents || {}; 950 this._eventParents[stamp(obj)] = obj; 951 return this; 952 }, 953 954 // @method removeEventParent(obj: Evented): this 955 // Removes an event parent, so it will stop receiving propagated events 956 removeEventParent: function (obj) { 957 if (this._eventParents) { 958 delete this._eventParents[stamp(obj)]; 959 } 960 return this; 961 }, 962 963 _propagateEvent: function (e) { 964 for (var id in this._eventParents) { 965 this._eventParents[id].fire(e.type, extend({ 966 layer: e.target, 967 propagatedFrom: e.target 968 }, e), true); 969 } 970 } 971 }; 972 973 // aliases; we should ditch those eventually 974 975 // @method addEventListener(â¦): this 976 // Alias to [`on(â¦)`](#evented-on) 977 Events.addEventListener = Events.on; 978 979 // @method removeEventListener(â¦): this 980 // Alias to [`off(â¦)`](#evented-off) 981 982 // @method clearAllEventListeners(â¦): this 983 // Alias to [`off()`](#evented-off) 984 Events.removeEventListener = Events.clearAllEventListeners = Events.off; 985 986 // @method addOneTimeEventListener(â¦): this 987 // Alias to [`once(â¦)`](#evented-once) 988 Events.addOneTimeEventListener = Events.once; 989 990 // @method fireEvent(â¦): this 991 // Alias to [`fire(â¦)`](#evented-fire) 992 Events.fireEvent = Events.fire; 993 994 // @method hasEventListeners(â¦): Boolean 995 // Alias to [`listens(â¦)`](#evented-listens) 996 Events.hasEventListeners = Events.listens; 997 998 var Evented = Class.extend(Events); 999 1000 /* 1001 * @class Point 1002 * @aka L.Point 1003 * 1004 * Represents a point with `x` and `y` coordinates in pixels. 1005 * 1006 * @example 1007 * 1008 * ```js 1009 * var point = L.point(200, 300); 1010 * ``` 1011 * 1012 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent: 1013 * 1014 * ```js 1015 * map.panBy([200, 300]); 1016 * map.panBy(L.point(200, 300)); 1017 * ``` 1018 * 1019 * Note that `Point` does not inherit from Leaflet's `Class` object, 1020 * which means new classes can't inherit from it, and new methods 1021 * can't be added to it with the `include` function. 1022 */ 1023 1024 function Point(x, y, round) { 1025 // @property x: Number; The `x` coordinate of the point 1026 this.x = (round ? Math.round(x) : x); 1027 // @property y: Number; The `y` coordinate of the point 1028 this.y = (round ? Math.round(y) : y); 1029 } 1030 1031 var trunc = Math.trunc || function (v) { 1032 return v > 0 ? Math.floor(v) : Math.ceil(v); 1033 }; 1034 1035 Point.prototype = { 1036 1037 // @method clone(): Point 1038 // Returns a copy of the current point. 1039 clone: function () { 1040 return new Point(this.x, this.y); 1041 }, 1042 1043 // @method add(otherPoint: Point): Point 1044 // Returns the result of addition of the current and the given points. 1045 add: function (point) { 1046 // non-destructive, returns a new point 1047 return this.clone()._add(toPoint(point)); 1048 }, 1049 1050 _add: function (point) { 1051 // destructive, used directly for performance in situations where it's safe to modify existing point 1052 this.x += point.x; 1053 this.y += point.y; 1054 return this; 1055 }, 1056 1057 // @method subtract(otherPoint: Point): Point 1058 // Returns the result of subtraction of the given point from the current. 1059 subtract: function (point) { 1060 return this.clone()._subtract(toPoint(point)); 1061 }, 1062 1063 _subtract: function (point) { 1064 this.x -= point.x; 1065 this.y -= point.y; 1066 return this; 1067 }, 1068 1069 // @method divideBy(num: Number): Point 1070 // Returns the result of division of the current point by the given number. 1071 divideBy: function (num) { 1072 return this.clone()._divideBy(num); 1073 }, 1074 1075 _divideBy: function (num) { 1076 this.x /= num; 1077 this.y /= num; 1078 return this; 1079 }, 1080 1081 // @method multiplyBy(num: Number): Point 1082 // Returns the result of multiplication of the current point by the given number. 1083 multiplyBy: function (num) { 1084 return this.clone()._multiplyBy(num); 1085 }, 1086 1087 _multiplyBy: function (num) { 1088 this.x *= num; 1089 this.y *= num; 1090 return this; 1091 }, 1092 1093 // @method scaleBy(scale: Point): Point 1094 // Multiply each coordinate of the current point by each coordinate of 1095 // `scale`. In linear algebra terms, multiply the point by the 1096 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 1097 // defined by `scale`. 1098 scaleBy: function (point) { 1099 return new Point(this.x * point.x, this.y * point.y); 1100 }, 1101 1102 // @method unscaleBy(scale: Point): Point 1103 // Inverse of `scaleBy`. Divide each coordinate of the current point by 1104 // each coordinate of `scale`. 1105 unscaleBy: function (point) { 1106 return new Point(this.x / point.x, this.y / point.y); 1107 }, 1108 1109 // @method round(): Point 1110 // Returns a copy of the current point with rounded coordinates. 1111 round: function () { 1112 return this.clone()._round(); 1113 }, 1114 1115 _round: function () { 1116 this.x = Math.round(this.x); 1117 this.y = Math.round(this.y); 1118 return this; 1119 }, 1120 1121 // @method floor(): Point 1122 // Returns a copy of the current point with floored coordinates (rounded down). 1123 floor: function () { 1124 return this.clone()._floor(); 1125 }, 1126 1127 _floor: function () { 1128 this.x = Math.floor(this.x); 1129 this.y = Math.floor(this.y); 1130 return this; 1131 }, 1132 1133 // @method ceil(): Point 1134 // Returns a copy of the current point with ceiled coordinates (rounded up). 1135 ceil: function () { 1136 return this.clone()._ceil(); 1137 }, 1138 1139 _ceil: function () { 1140 this.x = Math.ceil(this.x); 1141 this.y = Math.ceil(this.y); 1142 return this; 1143 }, 1144 1145 // @method trunc(): Point 1146 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 1147 trunc: function () { 1148 return this.clone()._trunc(); 1149 }, 1150 1151 _trunc: function () { 1152 this.x = trunc(this.x); 1153 this.y = trunc(this.y); 1154 return this; 1155 }, 1156 1157 // @method distanceTo(otherPoint: Point): Number 1158 // Returns the cartesian distance between the current and the given points. 1159 distanceTo: function (point) { 1160 point = toPoint(point); 1161 1162 var x = point.x - this.x, 1163 y = point.y - this.y; 1164 1165 return Math.sqrt(x * x + y * y); 1166 }, 1167 1168 // @method equals(otherPoint: Point): Boolean 1169 // Returns `true` if the given point has the same coordinates. 1170 equals: function (point) { 1171 point = toPoint(point); 1172 1173 return point.x === this.x && 1174 point.y === this.y; 1175 }, 1176 1177 // @method contains(otherPoint: Point): Boolean 1178 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 1179 contains: function (point) { 1180 point = toPoint(point); 1181 1182 return Math.abs(point.x) <= Math.abs(this.x) && 1183 Math.abs(point.y) <= Math.abs(this.y); 1184 }, 1185 1186 // @method toString(): String 1187 // Returns a string representation of the point for debugging purposes. 1188 toString: function () { 1189 return 'Point(' + 1190 formatNum(this.x) + ', ' + 1191 formatNum(this.y) + ')'; 1192 } 1193 }; 1194 1195 // @factory L.point(x: Number, y: Number, round?: Boolean) 1196 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 1197 1198 // @alternative 1199 // @factory L.point(coords: Number[]) 1200 // Expects an array of the form `[x, y]` instead. 1201 1202 // @alternative 1203 // @factory L.point(coords: Object) 1204 // Expects a plain object of the form `{x: Number, y: Number}` instead. 1205 function toPoint(x, y, round) { 1206 if (x instanceof Point) { 1207 return x; 1208 } 1209 if (isArray(x)) { 1210 return new Point(x[0], x[1]); 1211 } 1212 if (x === undefined || x === null) { 1213 return x; 1214 } 1215 if (typeof x === 'object' && 'x' in x && 'y' in x) { 1216 return new Point(x.x, x.y); 1217 } 1218 return new Point(x, y, round); 1219 } 1220 1221 /* 1222 * @class Bounds 1223 * @aka L.Bounds 1224 * 1225 * Represents a rectangular area in pixel coordinates. 1226 * 1227 * @example 1228 * 1229 * ```js 1230 * var p1 = L.point(10, 10), 1231 * p2 = L.point(40, 60), 1232 * bounds = L.bounds(p1, p2); 1233 * ``` 1234 * 1235 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: 1236 * 1237 * ```js 1238 * otherBounds.intersects([[10, 10], [40, 60]]); 1239 * ``` 1240 * 1241 * Note that `Bounds` does not inherit from Leaflet's `Class` object, 1242 * which means new classes can't inherit from it, and new methods 1243 * can't be added to it with the `include` function. 1244 */ 1245 1246 function Bounds(a, b) { 1247 if (!a) { return; } 1248 1249 var points = b ? [a, b] : a; 1250 1251 for (var i = 0, len = points.length; i < len; i++) { 1252 this.extend(points[i]); 1253 } 1254 } 1255 1256 Bounds.prototype = { 1257 // @method extend(point: Point): this 1258 // Extends the bounds to contain the given point. 1259 1260 // @alternative 1261 // @method extend(otherBounds: Bounds): this 1262 // Extend the bounds to contain the given bounds 1263 extend: function (obj) { 1264 var min2, max2; 1265 if (!obj) { return this; } 1266 1267 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) { 1268 min2 = max2 = toPoint(obj); 1269 } else { 1270 obj = toBounds(obj); 1271 min2 = obj.min; 1272 max2 = obj.max; 1273 1274 if (!min2 || !max2) { return this; } 1275 } 1276 1277 // @property min: Point 1278 // The top left corner of the rectangle. 1279 // @property max: Point 1280 // The bottom right corner of the rectangle. 1281 if (!this.min && !this.max) { 1282 this.min = min2.clone(); 1283 this.max = max2.clone(); 1284 } else { 1285 this.min.x = Math.min(min2.x, this.min.x); 1286 this.max.x = Math.max(max2.x, this.max.x); 1287 this.min.y = Math.min(min2.y, this.min.y); 1288 this.max.y = Math.max(max2.y, this.max.y); 1289 } 1290 return this; 1291 }, 1292 1293 // @method getCenter(round?: Boolean): Point 1294 // Returns the center point of the bounds. 1295 getCenter: function (round) { 1296 return toPoint( 1297 (this.min.x + this.max.x) / 2, 1298 (this.min.y + this.max.y) / 2, round); 1299 }, 1300 1301 // @method getBottomLeft(): Point 1302 // Returns the bottom-left point of the bounds. 1303 getBottomLeft: function () { 1304 return toPoint(this.min.x, this.max.y); 1305 }, 1306 1307 // @method getTopRight(): Point 1308 // Returns the top-right point of the bounds. 1309 getTopRight: function () { // -> Point 1310 return toPoint(this.max.x, this.min.y); 1311 }, 1312 1313 // @method getTopLeft(): Point 1314 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 1315 getTopLeft: function () { 1316 return this.min; // left, top 1317 }, 1318 1319 // @method getBottomRight(): Point 1320 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1321 getBottomRight: function () { 1322 return this.max; // right, bottom 1323 }, 1324 1325 // @method getSize(): Point 1326 // Returns the size of the given bounds 1327 getSize: function () { 1328 return this.max.subtract(this.min); 1329 }, 1330 1331 // @method contains(otherBounds: Bounds): Boolean 1332 // Returns `true` if the rectangle contains the given one. 1333 // @alternative 1334 // @method contains(point: Point): Boolean 1335 // Returns `true` if the rectangle contains the given point. 1336 contains: function (obj) { 1337 var min, max; 1338 1339 if (typeof obj[0] === 'number' || obj instanceof Point) { 1340 obj = toPoint(obj); 1341 } else { 1342 obj = toBounds(obj); 1343 } 1344 1345 if (obj instanceof Bounds) { 1346 min = obj.min; 1347 max = obj.max; 1348 } else { 1349 min = max = obj; 1350 } 1351 1352 return (min.x >= this.min.x) && 1353 (max.x <= this.max.x) && 1354 (min.y >= this.min.y) && 1355 (max.y <= this.max.y); 1356 }, 1357 1358 // @method intersects(otherBounds: Bounds): Boolean 1359 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1360 // intersect if they have at least one point in common. 1361 intersects: function (bounds) { // (Bounds) -> Boolean 1362 bounds = toBounds(bounds); 1363 1364 var min = this.min, 1365 max = this.max, 1366 min2 = bounds.min, 1367 max2 = bounds.max, 1368 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1369 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1370 1371 return xIntersects && yIntersects; 1372 }, 1373 1374 // @method overlaps(otherBounds: Bounds): Boolean 1375 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1376 // overlap if their intersection is an area. 1377 overlaps: function (bounds) { // (Bounds) -> Boolean 1378 bounds = toBounds(bounds); 1379 1380 var min = this.min, 1381 max = this.max, 1382 min2 = bounds.min, 1383 max2 = bounds.max, 1384 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1385 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1386 1387 return xOverlaps && yOverlaps; 1388 }, 1389 1390 // @method isValid(): Boolean 1391 // Returns `true` if the bounds are properly initialized. 1392 isValid: function () { 1393 return !!(this.min && this.max); 1394 }, 1395 1396 1397 // @method pad(bufferRatio: Number): Bounds 1398 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1399 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1400 // Negative values will retract the bounds. 1401 pad: function (bufferRatio) { 1402 var min = this.min, 1403 max = this.max, 1404 heightBuffer = Math.abs(min.x - max.x) * bufferRatio, 1405 widthBuffer = Math.abs(min.y - max.y) * bufferRatio; 1406 1407 1408 return toBounds( 1409 toPoint(min.x - heightBuffer, min.y - widthBuffer), 1410 toPoint(max.x + heightBuffer, max.y + widthBuffer)); 1411 }, 1412 1413 1414 // @method equals(otherBounds: Bounds): Boolean 1415 // Returns `true` if the rectangle is equivalent to the given bounds. 1416 equals: function (bounds) { 1417 if (!bounds) { return false; } 1418 1419 bounds = toBounds(bounds); 1420 1421 return this.min.equals(bounds.getTopLeft()) && 1422 this.max.equals(bounds.getBottomRight()); 1423 }, 1424 }; 1425 1426 1427 // @factory L.bounds(corner1: Point, corner2: Point) 1428 // Creates a Bounds object from two corners coordinate pairs. 1429 // @alternative 1430 // @factory L.bounds(points: Point[]) 1431 // Creates a Bounds object from the given array of points. 1432 function toBounds(a, b) { 1433 if (!a || a instanceof Bounds) { 1434 return a; 1435 } 1436 return new Bounds(a, b); 1437 } 1438 1439 /* 1440 * @class LatLngBounds 1441 * @aka L.LatLngBounds 1442 * 1443 * Represents a rectangular geographical area on a map. 1444 * 1445 * @example 1446 * 1447 * ```js 1448 * var corner1 = L.latLng(40.712, -74.227), 1449 * corner2 = L.latLng(40.774, -74.125), 1450 * bounds = L.latLngBounds(corner1, corner2); 1451 * ``` 1452 * 1453 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: 1454 * 1455 * ```js 1456 * map.fitBounds([ 1457 * [40.712, -74.227], 1458 * [40.774, -74.125] 1459 * ]); 1460 * ``` 1461 * 1462 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. 1463 * 1464 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object, 1465 * which means new classes can't inherit from it, and new methods 1466 * can't be added to it with the `include` function. 1467 */ 1468 1469 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1470 if (!corner1) { return; } 1471 1472 var latlngs = corner2 ? [corner1, corner2] : corner1; 1473 1474 for (var i = 0, len = latlngs.length; i < len; i++) { 1475 this.extend(latlngs[i]); 1476 } 1477 } 1478 1479 LatLngBounds.prototype = { 1480 1481 // @method extend(latlng: LatLng): this 1482 // Extend the bounds to contain the given point 1483 1484 // @alternative 1485 // @method extend(otherBounds: LatLngBounds): this 1486 // Extend the bounds to contain the given bounds 1487 extend: function (obj) { 1488 var sw = this._southWest, 1489 ne = this._northEast, 1490 sw2, ne2; 1491 1492 if (obj instanceof LatLng) { 1493 sw2 = obj; 1494 ne2 = obj; 1495 1496 } else if (obj instanceof LatLngBounds) { 1497 sw2 = obj._southWest; 1498 ne2 = obj._northEast; 1499 1500 if (!sw2 || !ne2) { return this; } 1501 1502 } else { 1503 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1504 } 1505 1506 if (!sw && !ne) { 1507 this._southWest = new LatLng(sw2.lat, sw2.lng); 1508 this._northEast = new LatLng(ne2.lat, ne2.lng); 1509 } else { 1510 sw.lat = Math.min(sw2.lat, sw.lat); 1511 sw.lng = Math.min(sw2.lng, sw.lng); 1512 ne.lat = Math.max(ne2.lat, ne.lat); 1513 ne.lng = Math.max(ne2.lng, ne.lng); 1514 } 1515 1516 return this; 1517 }, 1518 1519 // @method pad(bufferRatio: Number): LatLngBounds 1520 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1521 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1522 // Negative values will retract the bounds. 1523 pad: function (bufferRatio) { 1524 var sw = this._southWest, 1525 ne = this._northEast, 1526 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1527 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1528 1529 return new LatLngBounds( 1530 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1531 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1532 }, 1533 1534 // @method getCenter(): LatLng 1535 // Returns the center point of the bounds. 1536 getCenter: function () { 1537 return new LatLng( 1538 (this._southWest.lat + this._northEast.lat) / 2, 1539 (this._southWest.lng + this._northEast.lng) / 2); 1540 }, 1541 1542 // @method getSouthWest(): LatLng 1543 // Returns the south-west point of the bounds. 1544 getSouthWest: function () { 1545 return this._southWest; 1546 }, 1547 1548 // @method getNorthEast(): LatLng 1549 // Returns the north-east point of the bounds. 1550 getNorthEast: function () { 1551 return this._northEast; 1552 }, 1553 1554 // @method getNorthWest(): LatLng 1555 // Returns the north-west point of the bounds. 1556 getNorthWest: function () { 1557 return new LatLng(this.getNorth(), this.getWest()); 1558 }, 1559 1560 // @method getSouthEast(): LatLng 1561 // Returns the south-east point of the bounds. 1562 getSouthEast: function () { 1563 return new LatLng(this.getSouth(), this.getEast()); 1564 }, 1565 1566 // @method getWest(): Number 1567 // Returns the west longitude of the bounds 1568 getWest: function () { 1569 return this._southWest.lng; 1570 }, 1571 1572 // @method getSouth(): Number 1573 // Returns the south latitude of the bounds 1574 getSouth: function () { 1575 return this._southWest.lat; 1576 }, 1577 1578 // @method getEast(): Number 1579 // Returns the east longitude of the bounds 1580 getEast: function () { 1581 return this._northEast.lng; 1582 }, 1583 1584 // @method getNorth(): Number 1585 // Returns the north latitude of the bounds 1586 getNorth: function () { 1587 return this._northEast.lat; 1588 }, 1589 1590 // @method contains(otherBounds: LatLngBounds): Boolean 1591 // Returns `true` if the rectangle contains the given one. 1592 1593 // @alternative 1594 // @method contains (latlng: LatLng): Boolean 1595 // Returns `true` if the rectangle contains the given point. 1596 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1597 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1598 obj = toLatLng(obj); 1599 } else { 1600 obj = toLatLngBounds(obj); 1601 } 1602 1603 var sw = this._southWest, 1604 ne = this._northEast, 1605 sw2, ne2; 1606 1607 if (obj instanceof LatLngBounds) { 1608 sw2 = obj.getSouthWest(); 1609 ne2 = obj.getNorthEast(); 1610 } else { 1611 sw2 = ne2 = obj; 1612 } 1613 1614 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1615 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1616 }, 1617 1618 // @method intersects(otherBounds: LatLngBounds): Boolean 1619 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1620 intersects: function (bounds) { 1621 bounds = toLatLngBounds(bounds); 1622 1623 var sw = this._southWest, 1624 ne = this._northEast, 1625 sw2 = bounds.getSouthWest(), 1626 ne2 = bounds.getNorthEast(), 1627 1628 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1629 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1630 1631 return latIntersects && lngIntersects; 1632 }, 1633 1634 // @method overlaps(otherBounds: LatLngBounds): Boolean 1635 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1636 overlaps: function (bounds) { 1637 bounds = toLatLngBounds(bounds); 1638 1639 var sw = this._southWest, 1640 ne = this._northEast, 1641 sw2 = bounds.getSouthWest(), 1642 ne2 = bounds.getNorthEast(), 1643 1644 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1645 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1646 1647 return latOverlaps && lngOverlaps; 1648 }, 1649 1650 // @method toBBoxString(): String 1651 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. 1652 toBBoxString: function () { 1653 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1654 }, 1655 1656 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1657 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number. 1658 equals: function (bounds, maxMargin) { 1659 if (!bounds) { return false; } 1660 1661 bounds = toLatLngBounds(bounds); 1662 1663 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1664 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1665 }, 1666 1667 // @method isValid(): Boolean 1668 // Returns `true` if the bounds are properly initialized. 1669 isValid: function () { 1670 return !!(this._southWest && this._northEast); 1671 } 1672 }; 1673 1674 // TODO International date line? 1675 1676 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1677 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1678 1679 // @alternative 1680 // @factory L.latLngBounds(latlngs: LatLng[]) 1681 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). 1682 function toLatLngBounds(a, b) { 1683 if (a instanceof LatLngBounds) { 1684 return a; 1685 } 1686 return new LatLngBounds(a, b); 1687 } 1688 1689 /* @class LatLng 1690 * @aka L.LatLng 1691 * 1692 * Represents a geographical point with a certain latitude and longitude. 1693 * 1694 * @example 1695 * 1696 * ``` 1697 * var latlng = L.latLng(50.5, 30.5); 1698 * ``` 1699 * 1700 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent: 1701 * 1702 * ``` 1703 * map.panTo([50, 30]); 1704 * map.panTo({lon: 30, lat: 50}); 1705 * map.panTo({lat: 50, lng: 30}); 1706 * map.panTo(L.latLng(50, 30)); 1707 * ``` 1708 * 1709 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1710 * which means new classes can't inherit from it, and new methods 1711 * can't be added to it with the `include` function. 1712 */ 1713 1714 function LatLng(lat, lng, alt) { 1715 if (isNaN(lat) || isNaN(lng)) { 1716 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1717 } 1718 1719 // @property lat: Number 1720 // Latitude in degrees 1721 this.lat = +lat; 1722 1723 // @property lng: Number 1724 // Longitude in degrees 1725 this.lng = +lng; 1726 1727 // @property alt: Number 1728 // Altitude in meters (optional) 1729 if (alt !== undefined) { 1730 this.alt = +alt; 1731 } 1732 } 1733 1734 LatLng.prototype = { 1735 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1736 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number. 1737 equals: function (obj, maxMargin) { 1738 if (!obj) { return false; } 1739 1740 obj = toLatLng(obj); 1741 1742 var margin = Math.max( 1743 Math.abs(this.lat - obj.lat), 1744 Math.abs(this.lng - obj.lng)); 1745 1746 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1747 }, 1748 1749 // @method toString(): String 1750 // Returns a string representation of the point (for debugging purposes). 1751 toString: function (precision) { 1752 return 'LatLng(' + 1753 formatNum(this.lat, precision) + ', ' + 1754 formatNum(this.lng, precision) + ')'; 1755 }, 1756 1757 // @method distanceTo(otherLatLng: LatLng): Number 1758 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines). 1759 distanceTo: function (other) { 1760 return Earth.distance(this, toLatLng(other)); 1761 }, 1762 1763 // @method wrap(): LatLng 1764 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1765 wrap: function () { 1766 return Earth.wrapLatLng(this); 1767 }, 1768 1769 // @method toBounds(sizeInMeters: Number): LatLngBounds 1770 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1771 toBounds: function (sizeInMeters) { 1772 var latAccuracy = 180 * sizeInMeters / 40075017, 1773 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1774 1775 return toLatLngBounds( 1776 [this.lat - latAccuracy, this.lng - lngAccuracy], 1777 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1778 }, 1779 1780 clone: function () { 1781 return new LatLng(this.lat, this.lng, this.alt); 1782 } 1783 }; 1784 1785 1786 1787 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1788 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1789 1790 // @alternative 1791 // @factory L.latLng(coords: Array): LatLng 1792 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1793 1794 // @alternative 1795 // @factory L.latLng(coords: Object): LatLng 1796 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1797 1798 function toLatLng(a, b, c) { 1799 if (a instanceof LatLng) { 1800 return a; 1801 } 1802 if (isArray(a) && typeof a[0] !== 'object') { 1803 if (a.length === 3) { 1804 return new LatLng(a[0], a[1], a[2]); 1805 } 1806 if (a.length === 2) { 1807 return new LatLng(a[0], a[1]); 1808 } 1809 return null; 1810 } 1811 if (a === undefined || a === null) { 1812 return a; 1813 } 1814 if (typeof a === 'object' && 'lat' in a) { 1815 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1816 } 1817 if (b === undefined) { 1818 return null; 1819 } 1820 return new LatLng(a, b, c); 1821 } 1822 1823 /* 1824 * @namespace CRS 1825 * @crs L.CRS.Base 1826 * Object that defines coordinate reference systems for projecting 1827 * geographical points into pixel (screen) coordinates and back (and to 1828 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1829 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system). 1830 * 1831 * Leaflet defines the most usual CRSs by default. If you want to use a 1832 * CRS not defined by default, take a look at the 1833 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1834 * 1835 * Note that the CRS instances do not inherit from Leaflet's `Class` object, 1836 * and can't be instantiated. Also, new classes can't inherit from them, 1837 * and methods can't be added to them with the `include` function. 1838 */ 1839 1840 var CRS = { 1841 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1842 // Projects geographical coordinates into pixel coordinates for a given zoom. 1843 latLngToPoint: function (latlng, zoom) { 1844 var projectedPoint = this.projection.project(latlng), 1845 scale = this.scale(zoom); 1846 1847 return this.transformation._transform(projectedPoint, scale); 1848 }, 1849 1850 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1851 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1852 // zoom into geographical coordinates. 1853 pointToLatLng: function (point, zoom) { 1854 var scale = this.scale(zoom), 1855 untransformedPoint = this.transformation.untransform(point, scale); 1856 1857 return this.projection.unproject(untransformedPoint); 1858 }, 1859 1860 // @method project(latlng: LatLng): Point 1861 // Projects geographical coordinates into coordinates in units accepted for 1862 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1863 project: function (latlng) { 1864 return this.projection.project(latlng); 1865 }, 1866 1867 // @method unproject(point: Point): LatLng 1868 // Given a projected coordinate returns the corresponding LatLng. 1869 // The inverse of `project`. 1870 unproject: function (point) { 1871 return this.projection.unproject(point); 1872 }, 1873 1874 // @method scale(zoom: Number): Number 1875 // Returns the scale used when transforming projected coordinates into 1876 // pixel coordinates for a particular zoom. For example, it returns 1877 // `256 * 2^zoom` for Mercator-based CRS. 1878 scale: function (zoom) { 1879 return 256 * Math.pow(2, zoom); 1880 }, 1881 1882 // @method zoom(scale: Number): Number 1883 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1884 // factor of `scale`. 1885 zoom: function (scale) { 1886 return Math.log(scale / 256) / Math.LN2; 1887 }, 1888 1889 // @method getProjectedBounds(zoom: Number): Bounds 1890 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1891 getProjectedBounds: function (zoom) { 1892 if (this.infinite) { return null; } 1893 1894 var b = this.projection.bounds, 1895 s = this.scale(zoom), 1896 min = this.transformation.transform(b.min, s), 1897 max = this.transformation.transform(b.max, s); 1898 1899 return new Bounds(min, max); 1900 }, 1901 1902 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1903 // Returns the distance between two geographical coordinates. 1904 1905 // @property code: String 1906 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1907 // 1908 // @property wrapLng: Number[] 1909 // An array of two numbers defining whether the longitude (horizontal) coordinate 1910 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1911 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1912 // 1913 // @property wrapLat: Number[] 1914 // Like `wrapLng`, but for the latitude (vertical) axis. 1915 1916 // wrapLng: [min, max], 1917 // wrapLat: [min, max], 1918 1919 // @property infinite: Boolean 1920 // If true, the coordinate space will be unbounded (infinite in both axes) 1921 infinite: false, 1922 1923 // @method wrapLatLng(latlng: LatLng): LatLng 1924 // Returns a `LatLng` where lat and lng has been wrapped according to the 1925 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1926 wrapLatLng: function (latlng) { 1927 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1928 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1929 alt = latlng.alt; 1930 1931 return new LatLng(lat, lng, alt); 1932 }, 1933 1934 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1935 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1936 // that its center is within the CRS's bounds. 1937 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1938 wrapLatLngBounds: function (bounds) { 1939 var center = bounds.getCenter(), 1940 newCenter = this.wrapLatLng(center), 1941 latShift = center.lat - newCenter.lat, 1942 lngShift = center.lng - newCenter.lng; 1943 1944 if (latShift === 0 && lngShift === 0) { 1945 return bounds; 1946 } 1947 1948 var sw = bounds.getSouthWest(), 1949 ne = bounds.getNorthEast(), 1950 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1951 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1952 1953 return new LatLngBounds(newSw, newNe); 1954 } 1955 }; 1956 1957 /* 1958 * @namespace CRS 1959 * @crs L.CRS.Earth 1960 * 1961 * Serves as the base for CRS that are global such that they cover the earth. 1962 * Can only be used as the base for other CRS and cannot be used directly, 1963 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1964 * meters. 1965 */ 1966 1967 var Earth = extend({}, CRS, { 1968 wrapLng: [-180, 180], 1969 1970 // Mean Earth Radius, as recommended for use by 1971 // the International Union of Geodesy and Geophysics, 1972 // see https://rosettacode.org/wiki/Haversine_formula 1973 R: 6371000, 1974 1975 // distance between two geographical points using spherical law of cosines approximation 1976 distance: function (latlng1, latlng2) { 1977 var rad = Math.PI / 180, 1978 lat1 = latlng1.lat * rad, 1979 lat2 = latlng2.lat * rad, 1980 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1981 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1982 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1983 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1984 return this.R * c; 1985 } 1986 }); 1987 1988 /* 1989 * @namespace Projection 1990 * @projection L.Projection.SphericalMercator 1991 * 1992 * Spherical Mercator projection â the most common projection for online maps, 1993 * used by almost all free and commercial tile providers. Assumes that Earth is 1994 * a sphere. Used by the `EPSG:3857` CRS. 1995 */ 1996 1997 var earthRadius = 6378137; 1998 1999 var SphericalMercator = { 2000 2001 R: earthRadius, 2002 MAX_LATITUDE: 85.0511287798, 2003 2004 project: function (latlng) { 2005 var d = Math.PI / 180, 2006 max = this.MAX_LATITUDE, 2007 lat = Math.max(Math.min(max, latlng.lat), -max), 2008 sin = Math.sin(lat * d); 2009 2010 return new Point( 2011 this.R * latlng.lng * d, 2012 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 2013 }, 2014 2015 unproject: function (point) { 2016 var d = 180 / Math.PI; 2017 2018 return new LatLng( 2019 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 2020 point.x * d / this.R); 2021 }, 2022 2023 bounds: (function () { 2024 var d = earthRadius * Math.PI; 2025 return new Bounds([-d, -d], [d, d]); 2026 })() 2027 }; 2028 2029 /* 2030 * @class Transformation 2031 * @aka L.Transformation 2032 * 2033 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 2034 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 2035 * the reverse. Used by Leaflet in its projections code. 2036 * 2037 * @example 2038 * 2039 * ```js 2040 * var transformation = L.transformation(2, 5, -1, 10), 2041 * p = L.point(1, 2), 2042 * p2 = transformation.transform(p), // L.point(7, 8) 2043 * p3 = transformation.untransform(p2); // L.point(1, 2) 2044 * ``` 2045 */ 2046 2047 2048 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 2049 // Creates a `Transformation` object with the given coefficients. 2050 function Transformation(a, b, c, d) { 2051 if (isArray(a)) { 2052 // use array properties 2053 this._a = a[0]; 2054 this._b = a[1]; 2055 this._c = a[2]; 2056 this._d = a[3]; 2057 return; 2058 } 2059 this._a = a; 2060 this._b = b; 2061 this._c = c; 2062 this._d = d; 2063 } 2064 2065 Transformation.prototype = { 2066 // @method transform(point: Point, scale?: Number): Point 2067 // Returns a transformed point, optionally multiplied by the given scale. 2068 // Only accepts actual `L.Point` instances, not arrays. 2069 transform: function (point, scale) { // (Point, Number) -> Point 2070 return this._transform(point.clone(), scale); 2071 }, 2072 2073 // destructive transform (faster) 2074 _transform: function (point, scale) { 2075 scale = scale || 1; 2076 point.x = scale * (this._a * point.x + this._b); 2077 point.y = scale * (this._c * point.y + this._d); 2078 return point; 2079 }, 2080 2081 // @method untransform(point: Point, scale?: Number): Point 2082 // Returns the reverse transformation of the given point, optionally divided 2083 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 2084 untransform: function (point, scale) { 2085 scale = scale || 1; 2086 return new Point( 2087 (point.x / scale - this._b) / this._a, 2088 (point.y / scale - this._d) / this._c); 2089 } 2090 }; 2091 2092 // factory L.transformation(a: Number, b: Number, c: Number, d: Number) 2093 2094 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 2095 // Instantiates a Transformation object with the given coefficients. 2096 2097 // @alternative 2098 // @factory L.transformation(coefficients: Array): Transformation 2099 // Expects an coefficients array of the form 2100 // `[a: Number, b: Number, c: Number, d: Number]`. 2101 2102 function toTransformation(a, b, c, d) { 2103 return new Transformation(a, b, c, d); 2104 } 2105 2106 /* 2107 * @namespace CRS 2108 * @crs L.CRS.EPSG3857 2109 * 2110 * The most common CRS for online maps, used by almost all free and commercial 2111 * tile providers. Uses Spherical Mercator projection. Set in by default in 2112 * Map's `crs` option. 2113 */ 2114 2115 var EPSG3857 = extend({}, Earth, { 2116 code: 'EPSG:3857', 2117 projection: SphericalMercator, 2118 2119 transformation: (function () { 2120 var scale = 0.5 / (Math.PI * SphericalMercator.R); 2121 return toTransformation(scale, 0.5, -scale, 0.5); 2122 }()) 2123 }); 2124 2125 var EPSG900913 = extend({}, EPSG3857, { 2126 code: 'EPSG:900913' 2127 }); 2128 2129 // @namespace SVG; @section 2130 // There are several static functions which can be called without instantiating L.SVG: 2131 2132 // @function create(name: String): SVGElement 2133 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 2134 // corresponding to the class name passed. For example, using 'line' will return 2135 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 2136 function svgCreate(name) { 2137 return document.createElementNS('http://www.w3.org/2000/svg', name); 2138 } 2139 2140 // @function pointsToPath(rings: Point[], closed: Boolean): String 2141 // Generates a SVG path string for multiple rings, with each ring turning 2142 // into "M..L..L.." instructions 2143 function pointsToPath(rings, closed) { 2144 var str = '', 2145 i, j, len, len2, points, p; 2146 2147 for (i = 0, len = rings.length; i < len; i++) { 2148 points = rings[i]; 2149 2150 for (j = 0, len2 = points.length; j < len2; j++) { 2151 p = points[j]; 2152 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 2153 } 2154 2155 // closes the ring for polygons; "x" is VML syntax 2156 str += closed ? (Browser.svg ? 'z' : 'x') : ''; 2157 } 2158 2159 // SVG complains about empty path strings 2160 return str || 'M0 0'; 2161 } 2162 2163 /* 2164 * @namespace Browser 2165 * @aka L.Browser 2166 * 2167 * A namespace with static properties for browser/feature detection used by Leaflet internally. 2168 * 2169 * @example 2170 * 2171 * ```js 2172 * if (L.Browser.ielt9) { 2173 * alert('Upgrade your browser, dude!'); 2174 * } 2175 * ``` 2176 */ 2177 2178 var style = document.documentElement.style; 2179 2180 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 2181 var ie = 'ActiveXObject' in window; 2182 2183 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 2184 var ielt9 = ie && !document.addEventListener; 2185 2186 // @property edge: Boolean; `true` for the Edge web browser. 2187 var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 2188 2189 // @property webkit: Boolean; 2190 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 2191 var webkit = userAgentContains('webkit'); 2192 2193 // @property android: Boolean 2194 // **Deprecated.** `true` for any browser running on an Android platform. 2195 var android = userAgentContains('android'); 2196 2197 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3. 2198 var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 2199 2200 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 2201 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 2202 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome) 2203 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 2204 2205 // @property opera: Boolean; `true` for the Opera browser 2206 var opera = !!window.opera; 2207 2208 // @property chrome: Boolean; `true` for the Chrome browser. 2209 var chrome = !edge && userAgentContains('chrome'); 2210 2211 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 2212 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 2213 2214 // @property safari: Boolean; `true` for the Safari browser. 2215 var safari = !chrome && userAgentContains('safari'); 2216 2217 var phantom = userAgentContains('phantom'); 2218 2219 // @property opera12: Boolean 2220 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 2221 var opera12 = 'OTransition' in style; 2222 2223 // @property win: Boolean; `true` when the browser is running in a Windows platform 2224 var win = navigator.platform.indexOf('Win') === 0; 2225 2226 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. 2227 var ie3d = ie && ('transition' in style); 2228 2229 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 2230 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 2231 2232 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 2233 var gecko3d = 'MozPerspective' in style; 2234 2235 // @property any3d: Boolean 2236 // `true` for all browsers supporting CSS transforms. 2237 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 2238 2239 // @property mobile: Boolean; `true` for all browsers running in a mobile device. 2240 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 2241 2242 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 2243 var mobileWebkit = mobile && webkit; 2244 2245 // @property mobileWebkit3d: Boolean 2246 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 2247 var mobileWebkit3d = mobile && webkit3d; 2248 2249 // @property msPointer: Boolean 2250 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 2251 var msPointer = !window.PointerEvent && window.MSPointerEvent; 2252 2253 // @property pointer: Boolean 2254 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 2255 var pointer = !!(window.PointerEvent || msPointer); 2256 2257 // @property touchNative: Boolean 2258 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 2259 // **This does not necessarily mean** that the browser is running in a computer with 2260 // a touchscreen, it only means that the browser is capable of understanding 2261 // touch events. 2262 var touchNative = 'ontouchstart' in window || !!window.TouchEvent; 2263 2264 // @property touch: Boolean 2265 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events. 2266 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners. 2267 var touch = !window.L_NO_TOUCH && (touchNative || pointer); 2268 2269 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 2270 var mobileOpera = mobile && opera; 2271 2272 // @property mobileGecko: Boolean 2273 // `true` for gecko-based browsers running in a mobile device. 2274 var mobileGecko = mobile && gecko; 2275 2276 // @property retina: Boolean 2277 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 2278 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 2279 2280 // @property passiveEvents: Boolean 2281 // `true` for browsers that support passive events. 2282 var passiveEvents = (function () { 2283 var supportsPassiveOption = false; 2284 try { 2285 var opts = Object.defineProperty({}, 'passive', { 2286 get: function () { // eslint-disable-line getter-return 2287 supportsPassiveOption = true; 2288 } 2289 }); 2290 window.addEventListener('testPassiveEventSupport', falseFn, opts); 2291 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 2292 } catch (e) { 2293 // Errors can safely be ignored since this is only a browser support test. 2294 } 2295 return supportsPassiveOption; 2296 }()); 2297 2298 // @property canvas: Boolean 2299 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 2300 var canvas$1 = (function () { 2301 return !!document.createElement('canvas').getContext; 2302 }()); 2303 2304 // @property svg: Boolean 2305 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 2306 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); 2307 2308 var inlineSvg = !!svg$1 && (function () { 2309 var div = document.createElement('div'); 2310 div.innerHTML = '<svg/>'; 2311 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg'; 2312 })(); 2313 2314 // @property vml: Boolean 2315 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 2316 var vml = !svg$1 && (function () { 2317 try { 2318 var div = document.createElement('div'); 2319 div.innerHTML = '<v:shape adj="1"/>'; 2320 2321 var shape = div.firstChild; 2322 shape.style.behavior = 'url(#default#VML)'; 2323 2324 return shape && (typeof shape.adj === 'object'); 2325 2326 } catch (e) { 2327 return false; 2328 } 2329 }()); 2330 2331 2332 // @property mac: Boolean; `true` when the browser is running in a Mac platform 2333 var mac = navigator.platform.indexOf('Mac') === 0; 2334 2335 // @property mac: Boolean; `true` when the browser is running in a Linux platform 2336 var linux = navigator.platform.indexOf('Linux') === 0; 2337 2338 function userAgentContains(str) { 2339 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 2340 } 2341 2342 2343 var Browser = { 2344 ie: ie, 2345 ielt9: ielt9, 2346 edge: edge, 2347 webkit: webkit, 2348 android: android, 2349 android23: android23, 2350 androidStock: androidStock, 2351 opera: opera, 2352 chrome: chrome, 2353 gecko: gecko, 2354 safari: safari, 2355 phantom: phantom, 2356 opera12: opera12, 2357 win: win, 2358 ie3d: ie3d, 2359 webkit3d: webkit3d, 2360 gecko3d: gecko3d, 2361 any3d: any3d, 2362 mobile: mobile, 2363 mobileWebkit: mobileWebkit, 2364 mobileWebkit3d: mobileWebkit3d, 2365 msPointer: msPointer, 2366 pointer: pointer, 2367 touch: touch, 2368 touchNative: touchNative, 2369 mobileOpera: mobileOpera, 2370 mobileGecko: mobileGecko, 2371 retina: retina, 2372 passiveEvents: passiveEvents, 2373 canvas: canvas$1, 2374 svg: svg$1, 2375 vml: vml, 2376 inlineSvg: inlineSvg, 2377 mac: mac, 2378 linux: linux 2379 }; 2380 2381 /* 2382 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2383 */ 2384 2385 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown'; 2386 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove'; 2387 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup'; 2388 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel'; 2389 var pEvent = { 2390 touchstart : POINTER_DOWN, 2391 touchmove : POINTER_MOVE, 2392 touchend : POINTER_UP, 2393 touchcancel : POINTER_CANCEL 2394 }; 2395 var handle = { 2396 touchstart : _onPointerStart, 2397 touchmove : _handlePointer, 2398 touchend : _handlePointer, 2399 touchcancel : _handlePointer 2400 }; 2401 var _pointers = {}; 2402 var _pointerDocListener = false; 2403 2404 // Provides a touch events wrapper for (ms)pointer events. 2405 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2406 2407 function addPointerListener(obj, type, handler) { 2408 if (type === 'touchstart') { 2409 _addPointerDocListener(); 2410 } 2411 if (!handle[type]) { 2412 console.warn('wrong event specified:', type); 2413 return falseFn; 2414 } 2415 handler = handle[type].bind(this, handler); 2416 obj.addEventListener(pEvent[type], handler, false); 2417 return handler; 2418 } 2419 2420 function removePointerListener(obj, type, handler) { 2421 if (!pEvent[type]) { 2422 console.warn('wrong event specified:', type); 2423 return; 2424 } 2425 obj.removeEventListener(pEvent[type], handler, false); 2426 } 2427 2428 function _globalPointerDown(e) { 2429 _pointers[e.pointerId] = e; 2430 } 2431 2432 function _globalPointerMove(e) { 2433 if (_pointers[e.pointerId]) { 2434 _pointers[e.pointerId] = e; 2435 } 2436 } 2437 2438 function _globalPointerUp(e) { 2439 delete _pointers[e.pointerId]; 2440 } 2441 2442 function _addPointerDocListener() { 2443 // need to keep track of what pointers and how many are active to provide e.touches emulation 2444 if (!_pointerDocListener) { 2445 // we listen document as any drags that end by moving the touch off the screen get fired there 2446 document.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2447 document.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2448 document.addEventListener(POINTER_UP, _globalPointerUp, true); 2449 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2450 2451 _pointerDocListener = true; 2452 } 2453 } 2454 2455 function _handlePointer(handler, e) { 2456 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; } 2457 2458 e.touches = []; 2459 for (var i in _pointers) { 2460 e.touches.push(_pointers[i]); 2461 } 2462 e.changedTouches = [e]; 2463 2464 handler(e); 2465 } 2466 2467 function _onPointerStart(handler, e) { 2468 // IE10 specific: MsTouch needs preventDefault. See #2000 2469 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { 2470 preventDefault(e); 2471 } 2472 _handlePointer(handler, e); 2473 } 2474 2475 /* 2476 * Extends the event handling code with double tap support for mobile browsers. 2477 * 2478 * Note: currently most browsers fire native dblclick, with only a few exceptions 2479 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386) 2480 */ 2481 2482 function makeDblclick(event) { 2483 // in modern browsers `type` cannot be just overridden: 2484 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only 2485 var newEvent = {}, 2486 prop, i; 2487 for (i in event) { 2488 prop = event[i]; 2489 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; 2490 } 2491 event = newEvent; 2492 newEvent.type = 'dblclick'; 2493 newEvent.detail = 2; 2494 newEvent.isTrusted = false; 2495 newEvent._simulated = true; // for debug purposes 2496 return newEvent; 2497 } 2498 2499 var delay = 200; 2500 function addDoubleTapListener(obj, handler) { 2501 // Most browsers handle double tap natively 2502 obj.addEventListener('dblclick', handler); 2503 2504 // On some platforms the browser doesn't fire native dblclicks for touch events. 2505 // It seems that in all such cases `detail` property of `click` event is always `1`. 2506 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed. 2507 var last = 0, 2508 detail; 2509 function simDblclick(e) { 2510 if (e.detail !== 1) { 2511 detail = e.detail; // keep in sync to avoid false dblclick in some cases 2512 return; 2513 } 2514 2515 if (e.pointerType === 'mouse' || 2516 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) { 2517 2518 return; 2519 } 2520 2521 // When clicking on an <input>, the browser generates a click on its 2522 // <label> (and vice versa) triggering two clicks in quick succession. 2523 // This ignores clicks on elements which are a label with a 'for' 2524 // attribute (or children of such a label), but not children of 2525 // a <input>. 2526 var path = getPropagationPath(e); 2527 if (path.some(function (el) { 2528 return el instanceof HTMLLabelElement && el.attributes.for; 2529 }) && 2530 !path.some(function (el) { 2531 return ( 2532 el instanceof HTMLInputElement || 2533 el instanceof HTMLSelectElement 2534 ); 2535 }) 2536 ) { 2537 return; 2538 } 2539 2540 var now = Date.now(); 2541 if (now - last <= delay) { 2542 detail++; 2543 if (detail === 2) { 2544 handler(makeDblclick(e)); 2545 } 2546 } else { 2547 detail = 1; 2548 } 2549 last = now; 2550 } 2551 2552 obj.addEventListener('click', simDblclick); 2553 2554 return { 2555 dblclick: handler, 2556 simDblclick: simDblclick 2557 }; 2558 } 2559 2560 function removeDoubleTapListener(obj, handlers) { 2561 obj.removeEventListener('dblclick', handlers.dblclick); 2562 obj.removeEventListener('click', handlers.simDblclick); 2563 } 2564 2565 /* 2566 * @namespace DomUtil 2567 * 2568 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2569 * tree, used by Leaflet internally. 2570 * 2571 * Most functions expecting or returning a `HTMLElement` also work for 2572 * SVG elements. The only difference is that classes refer to CSS classes 2573 * in HTML and SVG classes in SVG. 2574 */ 2575 2576 2577 // @property TRANSFORM: String 2578 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2579 var TRANSFORM = testProp( 2580 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2581 2582 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 2583 // the same for the transitionend event, in particular the Android 4.1 stock browser 2584 2585 // @property TRANSITION: String 2586 // Vendor-prefixed transition style name. 2587 var TRANSITION = testProp( 2588 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2589 2590 // @property TRANSITION_END: String 2591 // Vendor-prefixed transitionend event name. 2592 var TRANSITION_END = 2593 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2594 2595 2596 // @function get(id: String|HTMLElement): HTMLElement 2597 // Returns an element given its DOM id, or returns the element itself 2598 // if it was passed directly. 2599 function get(id) { 2600 return typeof id === 'string' ? document.getElementById(id) : id; 2601 } 2602 2603 // @function getStyle(el: HTMLElement, styleAttrib: String): String 2604 // Returns the value for a certain style attribute on an element, 2605 // including computed values or values set through CSS. 2606 function getStyle(el, style) { 2607 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2608 2609 if ((!value || value === 'auto') && document.defaultView) { 2610 var css = document.defaultView.getComputedStyle(el, null); 2611 value = css ? css[style] : null; 2612 } 2613 return value === 'auto' ? null : value; 2614 } 2615 2616 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2617 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2618 function create$1(tagName, className, container) { 2619 var el = document.createElement(tagName); 2620 el.className = className || ''; 2621 2622 if (container) { 2623 container.appendChild(el); 2624 } 2625 return el; 2626 } 2627 2628 // @function remove(el: HTMLElement) 2629 // Removes `el` from its parent element 2630 function remove(el) { 2631 var parent = el.parentNode; 2632 if (parent) { 2633 parent.removeChild(el); 2634 } 2635 } 2636 2637 // @function empty(el: HTMLElement) 2638 // Removes all of `el`'s children elements from `el` 2639 function empty(el) { 2640 while (el.firstChild) { 2641 el.removeChild(el.firstChild); 2642 } 2643 } 2644 2645 // @function toFront(el: HTMLElement) 2646 // Makes `el` the last child of its parent, so it renders in front of the other children. 2647 function toFront(el) { 2648 var parent = el.parentNode; 2649 if (parent && parent.lastChild !== el) { 2650 parent.appendChild(el); 2651 } 2652 } 2653 2654 // @function toBack(el: HTMLElement) 2655 // Makes `el` the first child of its parent, so it renders behind the other children. 2656 function toBack(el) { 2657 var parent = el.parentNode; 2658 if (parent && parent.firstChild !== el) { 2659 parent.insertBefore(el, parent.firstChild); 2660 } 2661 } 2662 2663 // @function hasClass(el: HTMLElement, name: String): Boolean 2664 // Returns `true` if the element's class attribute contains `name`. 2665 function hasClass(el, name) { 2666 if (el.classList !== undefined) { 2667 return el.classList.contains(name); 2668 } 2669 var className = getClass(el); 2670 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2671 } 2672 2673 // @function addClass(el: HTMLElement, name: String) 2674 // Adds `name` to the element's class attribute. 2675 function addClass(el, name) { 2676 if (el.classList !== undefined) { 2677 var classes = splitWords(name); 2678 for (var i = 0, len = classes.length; i < len; i++) { 2679 el.classList.add(classes[i]); 2680 } 2681 } else if (!hasClass(el, name)) { 2682 var className = getClass(el); 2683 setClass(el, (className ? className + ' ' : '') + name); 2684 } 2685 } 2686 2687 // @function removeClass(el: HTMLElement, name: String) 2688 // Removes `name` from the element's class attribute. 2689 function removeClass(el, name) { 2690 if (el.classList !== undefined) { 2691 el.classList.remove(name); 2692 } else { 2693 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2694 } 2695 } 2696 2697 // @function setClass(el: HTMLElement, name: String) 2698 // Sets the element's class. 2699 function setClass(el, name) { 2700 if (el.className.baseVal === undefined) { 2701 el.className = name; 2702 } else { 2703 // in case of SVG element 2704 el.className.baseVal = name; 2705 } 2706 } 2707 2708 // @function getClass(el: HTMLElement): String 2709 // Returns the element's class. 2710 function getClass(el) { 2711 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2712 // (Required for linked SVG elements in IE11.) 2713 if (el.correspondingElement) { 2714 el = el.correspondingElement; 2715 } 2716 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2717 } 2718 2719 // @function setOpacity(el: HTMLElement, opacity: Number) 2720 // Set the opacity of an element (including old IE support). 2721 // `opacity` must be a number from `0` to `1`. 2722 function setOpacity(el, value) { 2723 if ('opacity' in el.style) { 2724 el.style.opacity = value; 2725 } else if ('filter' in el.style) { 2726 _setOpacityIE(el, value); 2727 } 2728 } 2729 2730 function _setOpacityIE(el, value) { 2731 var filter = false, 2732 filterName = 'DXImageTransform.Microsoft.Alpha'; 2733 2734 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2735 try { 2736 filter = el.filters.item(filterName); 2737 } catch (e) { 2738 // don't set opacity to 1 if we haven't already set an opacity, 2739 // it isn't needed and breaks transparent pngs. 2740 if (value === 1) { return; } 2741 } 2742 2743 value = Math.round(value * 100); 2744 2745 if (filter) { 2746 filter.Enabled = (value !== 100); 2747 filter.Opacity = value; 2748 } else { 2749 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2750 } 2751 } 2752 2753 // @function testProp(props: String[]): String|false 2754 // Goes through the array of style names and returns the first name 2755 // that is a valid style name for an element. If no such name is found, 2756 // it returns false. Useful for vendor-prefixed styles like `transform`. 2757 function testProp(props) { 2758 var style = document.documentElement.style; 2759 2760 for (var i = 0; i < props.length; i++) { 2761 if (props[i] in style) { 2762 return props[i]; 2763 } 2764 } 2765 return false; 2766 } 2767 2768 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2769 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2770 // and optionally scaled by `scale`. Does not have an effect if the 2771 // browser doesn't support 3D CSS transforms. 2772 function setTransform(el, offset, scale) { 2773 var pos = offset || new Point(0, 0); 2774 2775 el.style[TRANSFORM] = 2776 (Browser.ie3d ? 2777 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2778 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2779 (scale ? ' scale(' + scale + ')' : ''); 2780 } 2781 2782 // @function setPosition(el: HTMLElement, position: Point) 2783 // Sets the position of `el` to coordinates specified by `position`, 2784 // using CSS translate or top/left positioning depending on the browser 2785 // (used by Leaflet internally to position its layers). 2786 function setPosition(el, point) { 2787 2788 /*eslint-disable */ 2789 el._leaflet_pos = point; 2790 /* eslint-enable */ 2791 2792 if (Browser.any3d) { 2793 setTransform(el, point); 2794 } else { 2795 el.style.left = point.x + 'px'; 2796 el.style.top = point.y + 'px'; 2797 } 2798 } 2799 2800 // @function getPosition(el: HTMLElement): Point 2801 // Returns the coordinates of an element previously positioned with setPosition. 2802 function getPosition(el) { 2803 // this method is only used for elements previously positioned using setPosition, 2804 // so it's safe to cache the position for performance 2805 2806 return el._leaflet_pos || new Point(0, 0); 2807 } 2808 2809 // @function disableTextSelection() 2810 // Prevents the user from generating `selectstart` DOM events, usually generated 2811 // when the user drags the mouse through a page with text. Used internally 2812 // by Leaflet to override the behaviour of any click-and-drag interaction on 2813 // the map. Affects drag interactions on the whole document. 2814 2815 // @function enableTextSelection() 2816 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2817 var disableTextSelection; 2818 var enableTextSelection; 2819 var _userSelect; 2820 if ('onselectstart' in document) { 2821 disableTextSelection = function () { 2822 on(window, 'selectstart', preventDefault); 2823 }; 2824 enableTextSelection = function () { 2825 off(window, 'selectstart', preventDefault); 2826 }; 2827 } else { 2828 var userSelectProperty = testProp( 2829 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2830 2831 disableTextSelection = function () { 2832 if (userSelectProperty) { 2833 var style = document.documentElement.style; 2834 _userSelect = style[userSelectProperty]; 2835 style[userSelectProperty] = 'none'; 2836 } 2837 }; 2838 enableTextSelection = function () { 2839 if (userSelectProperty) { 2840 document.documentElement.style[userSelectProperty] = _userSelect; 2841 _userSelect = undefined; 2842 } 2843 }; 2844 } 2845 2846 // @function disableImageDrag() 2847 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2848 // for `dragstart` DOM events, usually generated when the user drags an image. 2849 function disableImageDrag() { 2850 on(window, 'dragstart', preventDefault); 2851 } 2852 2853 // @function enableImageDrag() 2854 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2855 function enableImageDrag() { 2856 off(window, 'dragstart', preventDefault); 2857 } 2858 2859 var _outlineElement, _outlineStyle; 2860 // @function preventOutline(el: HTMLElement) 2861 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2862 // of the element `el` invisible. Used internally by Leaflet to prevent 2863 // focusable elements from displaying an outline when the user performs a 2864 // drag interaction on them. 2865 function preventOutline(element) { 2866 while (element.tabIndex === -1) { 2867 element = element.parentNode; 2868 } 2869 if (!element.style) { return; } 2870 restoreOutline(); 2871 _outlineElement = element; 2872 _outlineStyle = element.style.outlineStyle; 2873 element.style.outlineStyle = 'none'; 2874 on(window, 'keydown', restoreOutline); 2875 } 2876 2877 // @function restoreOutline() 2878 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2879 function restoreOutline() { 2880 if (!_outlineElement) { return; } 2881 _outlineElement.style.outlineStyle = _outlineStyle; 2882 _outlineElement = undefined; 2883 _outlineStyle = undefined; 2884 off(window, 'keydown', restoreOutline); 2885 } 2886 2887 // @function getSizedParentNode(el: HTMLElement): HTMLElement 2888 // Finds the closest parent node which size (width and height) is not null. 2889 function getSizedParentNode(element) { 2890 do { 2891 element = element.parentNode; 2892 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2893 return element; 2894 } 2895 2896 // @function getScale(el: HTMLElement): Object 2897 // Computes the CSS scale currently applied on the element. 2898 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2899 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2900 function getScale(element) { 2901 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2902 2903 return { 2904 x: rect.width / element.offsetWidth || 1, 2905 y: rect.height / element.offsetHeight || 1, 2906 boundingClientRect: rect 2907 }; 2908 } 2909 2910 var DomUtil = { 2911 __proto__: null, 2912 TRANSFORM: TRANSFORM, 2913 TRANSITION: TRANSITION, 2914 TRANSITION_END: TRANSITION_END, 2915 get: get, 2916 getStyle: getStyle, 2917 create: create$1, 2918 remove: remove, 2919 empty: empty, 2920 toFront: toFront, 2921 toBack: toBack, 2922 hasClass: hasClass, 2923 addClass: addClass, 2924 removeClass: removeClass, 2925 setClass: setClass, 2926 getClass: getClass, 2927 setOpacity: setOpacity, 2928 testProp: testProp, 2929 setTransform: setTransform, 2930 setPosition: setPosition, 2931 getPosition: getPosition, 2932 get disableTextSelection () { return disableTextSelection; }, 2933 get enableTextSelection () { return enableTextSelection; }, 2934 disableImageDrag: disableImageDrag, 2935 enableImageDrag: enableImageDrag, 2936 preventOutline: preventOutline, 2937 restoreOutline: restoreOutline, 2938 getSizedParentNode: getSizedParentNode, 2939 getScale: getScale 2940 }; 2941 2942 /* 2943 * @namespace DomEvent 2944 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2945 */ 2946 2947 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2948 2949 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2950 // Adds a listener function (`fn`) to a particular DOM event type of the 2951 // element `el`. You can optionally specify the context of the listener 2952 // (object the `this` keyword will point to). You can also pass several 2953 // space-separated types (e.g. `'click dblclick'`). 2954 2955 // @alternative 2956 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2957 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2958 function on(obj, types, fn, context) { 2959 2960 if (types && typeof types === 'object') { 2961 for (var type in types) { 2962 addOne(obj, type, types[type], fn); 2963 } 2964 } else { 2965 types = splitWords(types); 2966 2967 for (var i = 0, len = types.length; i < len; i++) { 2968 addOne(obj, types[i], fn, context); 2969 } 2970 } 2971 2972 return this; 2973 } 2974 2975 var eventsKey = '_leaflet_events'; 2976 2977 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2978 // Removes a previously added listener function. 2979 // Note that if you passed a custom context to on, you must pass the same 2980 // context to `off` in order to remove the listener. 2981 2982 // @alternative 2983 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2984 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2985 2986 // @alternative 2987 // @function off(el: HTMLElement, types: String): this 2988 // Removes all previously added listeners of given types. 2989 2990 // @alternative 2991 // @function off(el: HTMLElement): this 2992 // Removes all previously added listeners from given HTMLElement 2993 function off(obj, types, fn, context) { 2994 2995 if (arguments.length === 1) { 2996 batchRemove(obj); 2997 delete obj[eventsKey]; 2998 2999 } else if (types && typeof types === 'object') { 3000 for (var type in types) { 3001 removeOne(obj, type, types[type], fn); 3002 } 3003 3004 } else { 3005 types = splitWords(types); 3006 3007 if (arguments.length === 2) { 3008 batchRemove(obj, function (type) { 3009 return indexOf(types, type) !== -1; 3010 }); 3011 } else { 3012 for (var i = 0, len = types.length; i < len; i++) { 3013 removeOne(obj, types[i], fn, context); 3014 } 3015 } 3016 } 3017 3018 return this; 3019 } 3020 3021 function batchRemove(obj, filterFn) { 3022 for (var id in obj[eventsKey]) { 3023 var type = id.split(/\d/)[0]; 3024 if (!filterFn || filterFn(type)) { 3025 removeOne(obj, type, null, null, id); 3026 } 3027 } 3028 } 3029 3030 var mouseSubst = { 3031 mouseenter: 'mouseover', 3032 mouseleave: 'mouseout', 3033 wheel: !('onwheel' in window) && 'mousewheel' 3034 }; 3035 3036 function addOne(obj, type, fn, context) { 3037 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 3038 3039 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 3040 3041 var handler = function (e) { 3042 return fn.call(context || obj, e || window.event); 3043 }; 3044 3045 var originalHandler = handler; 3046 3047 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 3048 // Needs DomEvent.Pointer.js 3049 handler = addPointerListener(obj, type, handler); 3050 3051 } else if (Browser.touch && (type === 'dblclick')) { 3052 handler = addDoubleTapListener(obj, handler); 3053 3054 } else if ('addEventListener' in obj) { 3055 3056 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') { 3057 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false); 3058 3059 } else if (type === 'mouseenter' || type === 'mouseleave') { 3060 handler = function (e) { 3061 e = e || window.event; 3062 if (isExternalTarget(obj, e)) { 3063 originalHandler(e); 3064 } 3065 }; 3066 obj.addEventListener(mouseSubst[type], handler, false); 3067 3068 } else { 3069 obj.addEventListener(type, originalHandler, false); 3070 } 3071 3072 } else { 3073 obj.attachEvent('on' + type, handler); 3074 } 3075 3076 obj[eventsKey] = obj[eventsKey] || {}; 3077 obj[eventsKey][id] = handler; 3078 } 3079 3080 function removeOne(obj, type, fn, context, id) { 3081 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : ''); 3082 var handler = obj[eventsKey] && obj[eventsKey][id]; 3083 3084 if (!handler) { return this; } 3085 3086 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 3087 removePointerListener(obj, type, handler); 3088 3089 } else if (Browser.touch && (type === 'dblclick')) { 3090 removeDoubleTapListener(obj, handler); 3091 3092 } else if ('removeEventListener' in obj) { 3093 3094 obj.removeEventListener(mouseSubst[type] || type, handler, false); 3095 3096 } else { 3097 obj.detachEvent('on' + type, handler); 3098 } 3099 3100 obj[eventsKey][id] = null; 3101 } 3102 3103 // @function stopPropagation(ev: DOMEvent): this 3104 // Stop the given event from propagation to parent elements. Used inside the listener functions: 3105 // ```js 3106 // L.DomEvent.on(div, 'click', function (ev) { 3107 // L.DomEvent.stopPropagation(ev); 3108 // }); 3109 // ``` 3110 function stopPropagation(e) { 3111 3112 if (e.stopPropagation) { 3113 e.stopPropagation(); 3114 } else if (e.originalEvent) { // In case of Leaflet event. 3115 e.originalEvent._stopped = true; 3116 } else { 3117 e.cancelBubble = true; 3118 } 3119 3120 return this; 3121 } 3122 3123 // @function disableScrollPropagation(el: HTMLElement): this 3124 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants). 3125 function disableScrollPropagation(el) { 3126 addOne(el, 'wheel', stopPropagation); 3127 return this; 3128 } 3129 3130 // @function disableClickPropagation(el: HTMLElement): this 3131 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`, 3132 // `'mousedown'` and `'touchstart'` events (plus browser variants). 3133 function disableClickPropagation(el) { 3134 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation); 3135 el['_leaflet_disable_click'] = true; 3136 return this; 3137 } 3138 3139 // @function preventDefault(ev: DOMEvent): this 3140 // Prevents the default action of the DOM Event `ev` from happening (such as 3141 // following a link in the href of the a element, or doing a POST request 3142 // with page reload when a `<form>` is submitted). 3143 // Use it inside listener functions. 3144 function preventDefault(e) { 3145 if (e.preventDefault) { 3146 e.preventDefault(); 3147 } else { 3148 e.returnValue = false; 3149 } 3150 return this; 3151 } 3152 3153 // @function stop(ev: DOMEvent): this 3154 // Does `stopPropagation` and `preventDefault` at the same time. 3155 function stop(e) { 3156 preventDefault(e); 3157 stopPropagation(e); 3158 return this; 3159 } 3160 3161 // @function getPropagationPath(ev: DOMEvent): Array 3162 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath). 3163 // Returns an array containing the `HTMLElement`s that the given DOM event 3164 // should propagate to (if not stopped). 3165 function getPropagationPath(ev) { 3166 if (ev.composedPath) { 3167 return ev.composedPath(); 3168 } 3169 3170 var path = []; 3171 var el = ev.target; 3172 3173 while (el) { 3174 path.push(el); 3175 el = el.parentNode; 3176 } 3177 return path; 3178 } 3179 3180 3181 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 3182 // Gets normalized mouse position from a DOM event relative to the 3183 // `container` (border excluded) or to the whole page if not specified. 3184 function getMousePosition(e, container) { 3185 if (!container) { 3186 return new Point(e.clientX, e.clientY); 3187 } 3188 3189 var scale = getScale(container), 3190 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 3191 3192 return new Point( 3193 // offset.left/top values are in page scale (like clientX/Y), 3194 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 3195 (e.clientX - offset.left) / scale.x - container.clientLeft, 3196 (e.clientY - offset.top) / scale.y - container.clientTop 3197 ); 3198 } 3199 3200 3201 // except , Safari and 3202 // We need double the scroll pixels (see #7403 and #4538) for all Browsers 3203 // except OSX (Mac) -> 3x, Chrome running on Linux 1x 3204 3205 var wheelPxFactor = 3206 (Browser.linux && Browser.chrome) ? window.devicePixelRatio : 3207 Browser.mac ? window.devicePixelRatio * 3 : 3208 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1; 3209 // @function getWheelDelta(ev: DOMEvent): Number 3210 // Gets normalized wheel delta from a wheel DOM event, in vertical 3211 // pixels scrolled (negative if scrolling down). 3212 // Events from pointing devices without precise scrolling are mapped to 3213 // a best guess of 60 pixels. 3214 function getWheelDelta(e) { 3215 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 3216 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 3217 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 3218 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 3219 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 3220 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 3221 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 3222 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 3223 0; 3224 } 3225 3226 // check if element really left/entered the event target (for mouseenter/mouseleave) 3227 function isExternalTarget(el, e) { 3228 3229 var related = e.relatedTarget; 3230 3231 if (!related) { return true; } 3232 3233 try { 3234 while (related && (related !== el)) { 3235 related = related.parentNode; 3236 } 3237 } catch (err) { 3238 return false; 3239 } 3240 return (related !== el); 3241 } 3242 3243 var DomEvent = { 3244 __proto__: null, 3245 on: on, 3246 off: off, 3247 stopPropagation: stopPropagation, 3248 disableScrollPropagation: disableScrollPropagation, 3249 disableClickPropagation: disableClickPropagation, 3250 preventDefault: preventDefault, 3251 stop: stop, 3252 getPropagationPath: getPropagationPath, 3253 getMousePosition: getMousePosition, 3254 getWheelDelta: getWheelDelta, 3255 isExternalTarget: isExternalTarget, 3256 addListener: on, 3257 removeListener: off 3258 }; 3259 3260 /* 3261 * @class PosAnimation 3262 * @aka L.PosAnimation 3263 * @inherits Evented 3264 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 3265 * 3266 * @example 3267 * ```js 3268 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map); 3269 * 3270 * myPositionMarker.on("click", function() { 3271 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng()); 3272 * pos.y -= 25; 3273 * var fx = new L.PosAnimation(); 3274 * 3275 * fx.once('end',function() { 3276 * pos.y += 25; 3277 * fx.run(myPositionMarker._icon, pos, 0.8); 3278 * }); 3279 * 3280 * fx.run(myPositionMarker._icon, pos, 0.3); 3281 * }); 3282 * 3283 * ``` 3284 * 3285 * @constructor L.PosAnimation() 3286 * Creates a `PosAnimation` object. 3287 * 3288 */ 3289 3290 var PosAnimation = Evented.extend({ 3291 3292 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 3293 // Run an animation of a given element to a new position, optionally setting 3294 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 3295 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1), 3296 // `0.5` by default). 3297 run: function (el, newPos, duration, easeLinearity) { 3298 this.stop(); 3299 3300 this._el = el; 3301 this._inProgress = true; 3302 this._duration = duration || 0.25; 3303 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 3304 3305 this._startPos = getPosition(el); 3306 this._offset = newPos.subtract(this._startPos); 3307 this._startTime = +new Date(); 3308 3309 // @event start: Event 3310 // Fired when the animation starts 3311 this.fire('start'); 3312 3313 this._animate(); 3314 }, 3315 3316 // @method stop() 3317 // Stops the animation (if currently running). 3318 stop: function () { 3319 if (!this._inProgress) { return; } 3320 3321 this._step(true); 3322 this._complete(); 3323 }, 3324 3325 _animate: function () { 3326 // animation loop 3327 this._animId = requestAnimFrame(this._animate, this); 3328 this._step(); 3329 }, 3330 3331 _step: function (round) { 3332 var elapsed = (+new Date()) - this._startTime, 3333 duration = this._duration * 1000; 3334 3335 if (elapsed < duration) { 3336 this._runFrame(this._easeOut(elapsed / duration), round); 3337 } else { 3338 this._runFrame(1); 3339 this._complete(); 3340 } 3341 }, 3342 3343 _runFrame: function (progress, round) { 3344 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 3345 if (round) { 3346 pos._round(); 3347 } 3348 setPosition(this._el, pos); 3349 3350 // @event step: Event 3351 // Fired continuously during the animation. 3352 this.fire('step'); 3353 }, 3354 3355 _complete: function () { 3356 cancelAnimFrame(this._animId); 3357 3358 this._inProgress = false; 3359 // @event end: Event 3360 // Fired when the animation ends. 3361 this.fire('end'); 3362 }, 3363 3364 _easeOut: function (t) { 3365 return 1 - Math.pow(1 - t, this._easeOutPower); 3366 } 3367 }); 3368 3369 /* 3370 * @class Map 3371 * @aka L.Map 3372 * @inherits Evented 3373 * 3374 * The central class of the API â it is used to create a map on a page and manipulate it. 3375 * 3376 * @example 3377 * 3378 * ```js 3379 * // initialize the map on the "map" div with a given center and zoom 3380 * var map = L.map('map', { 3381 * center: [51.505, -0.09], 3382 * zoom: 13 3383 * }); 3384 * ``` 3385 * 3386 */ 3387 3388 var Map = Evented.extend({ 3389 3390 options: { 3391 // @section Map State Options 3392 // @option crs: CRS = L.CRS.EPSG3857 3393 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3394 // sure what it means. 3395 crs: EPSG3857, 3396 3397 // @option center: LatLng = undefined 3398 // Initial geographic center of the map 3399 center: undefined, 3400 3401 // @option zoom: Number = undefined 3402 // Initial map zoom level 3403 zoom: undefined, 3404 3405 // @option minZoom: Number = * 3406 // Minimum zoom level of the map. 3407 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3408 // the lowest of their `minZoom` options will be used instead. 3409 minZoom: undefined, 3410 3411 // @option maxZoom: Number = * 3412 // Maximum zoom level of the map. 3413 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3414 // the highest of their `maxZoom` options will be used instead. 3415 maxZoom: undefined, 3416 3417 // @option layers: Layer[] = [] 3418 // Array of layers that will be added to the map initially 3419 layers: [], 3420 3421 // @option maxBounds: LatLngBounds = null 3422 // When this option is set, the map restricts the view to the given 3423 // geographical bounds, bouncing the user back if the user tries to pan 3424 // outside the view. To set the restriction dynamically, use 3425 // [`setMaxBounds`](#map-setmaxbounds) method. 3426 maxBounds: undefined, 3427 3428 // @option renderer: Renderer = * 3429 // The default method for drawing vector layers on the map. `L.SVG` 3430 // or `L.Canvas` by default depending on browser support. 3431 renderer: undefined, 3432 3433 3434 // @section Animation Options 3435 // @option zoomAnimation: Boolean = true 3436 // Whether the map zoom animation is enabled. By default it's enabled 3437 // in all browsers that support CSS3 Transitions except Android. 3438 zoomAnimation: true, 3439 3440 // @option zoomAnimationThreshold: Number = 4 3441 // Won't animate zoom if the zoom difference exceeds this value. 3442 zoomAnimationThreshold: 4, 3443 3444 // @option fadeAnimation: Boolean = true 3445 // Whether the tile fade animation is enabled. By default it's enabled 3446 // in all browsers that support CSS3 Transitions except Android. 3447 fadeAnimation: true, 3448 3449 // @option markerZoomAnimation: Boolean = true 3450 // Whether markers animate their zoom with the zoom animation, if disabled 3451 // they will disappear for the length of the animation. By default it's 3452 // enabled in all browsers that support CSS3 Transitions except Android. 3453 markerZoomAnimation: true, 3454 3455 // @option transform3DLimit: Number = 2^23 3456 // Defines the maximum size of a CSS translation transform. The default 3457 // value should not be changed unless a web browser positions layers in 3458 // the wrong place after doing a large `panBy`. 3459 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3460 3461 // @section Interaction Options 3462 // @option zoomSnap: Number = 1 3463 // Forces the map's zoom level to always be a multiple of this, particularly 3464 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3465 // By default, the zoom level snaps to the nearest integer; lower values 3466 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3467 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3468 zoomSnap: 1, 3469 3470 // @option zoomDelta: Number = 1 3471 // Controls how much the map's zoom level will change after a 3472 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3473 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3474 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3475 zoomDelta: 1, 3476 3477 // @option trackResize: Boolean = true 3478 // Whether the map automatically handles browser window resize to update itself. 3479 trackResize: true 3480 }, 3481 3482 initialize: function (id, options) { // (HTMLElement or String, Object) 3483 options = setOptions(this, options); 3484 3485 // Make sure to assign internal flags at the beginning, 3486 // to avoid inconsistent state in some edge cases. 3487 this._handlers = []; 3488 this._layers = {}; 3489 this._zoomBoundLayers = {}; 3490 this._sizeChanged = true; 3491 3492 this._initContainer(id); 3493 this._initLayout(); 3494 3495 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3496 this._onResize = bind(this._onResize, this); 3497 3498 this._initEvents(); 3499 3500 if (options.maxBounds) { 3501 this.setMaxBounds(options.maxBounds); 3502 } 3503 3504 if (options.zoom !== undefined) { 3505 this._zoom = this._limitZoom(options.zoom); 3506 } 3507 3508 if (options.center && options.zoom !== undefined) { 3509 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3510 } 3511 3512 this.callInitHooks(); 3513 3514 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3515 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera && 3516 this.options.zoomAnimation; 3517 3518 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3519 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3520 if (this._zoomAnimated) { 3521 this._createAnimProxy(); 3522 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3523 } 3524 3525 this._addLayers(this.options.layers); 3526 }, 3527 3528 3529 // @section Methods for modifying map state 3530 3531 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3532 // Sets the view of the map (geographical center and zoom) with the given 3533 // animation options. 3534 setView: function (center, zoom, options) { 3535 3536 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3537 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3538 options = options || {}; 3539 3540 this._stop(); 3541 3542 if (this._loaded && !options.reset && options !== true) { 3543 3544 if (options.animate !== undefined) { 3545 options.zoom = extend({animate: options.animate}, options.zoom); 3546 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3547 } 3548 3549 // try animating pan or zoom 3550 var moved = (this._zoom !== zoom) ? 3551 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3552 this._tryAnimatedPan(center, options.pan); 3553 3554 if (moved) { 3555 // prevent resize handler call, the view will refresh after animation anyway 3556 clearTimeout(this._sizeTimer); 3557 return this; 3558 } 3559 } 3560 3561 // animation didn't start, just reset the map view 3562 this._resetView(center, zoom, options.pan && options.pan.noMoveStart); 3563 3564 return this; 3565 }, 3566 3567 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3568 // Sets the zoom of the map. 3569 setZoom: function (zoom, options) { 3570 if (!this._loaded) { 3571 this._zoom = zoom; 3572 return this; 3573 } 3574 return this.setView(this.getCenter(), zoom, {zoom: options}); 3575 }, 3576 3577 // @method zoomIn(delta?: Number, options?: Zoom options): this 3578 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3579 zoomIn: function (delta, options) { 3580 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3581 return this.setZoom(this._zoom + delta, options); 3582 }, 3583 3584 // @method zoomOut(delta?: Number, options?: Zoom options): this 3585 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3586 zoomOut: function (delta, options) { 3587 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3588 return this.setZoom(this._zoom - delta, options); 3589 }, 3590 3591 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3592 // Zooms the map while keeping a specified geographical point on the map 3593 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3594 // @alternative 3595 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3596 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3597 setZoomAround: function (latlng, zoom, options) { 3598 var scale = this.getZoomScale(zoom), 3599 viewHalf = this.getSize().divideBy(2), 3600 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3601 3602 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3603 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3604 3605 return this.setView(newCenter, zoom, {zoom: options}); 3606 }, 3607 3608 _getBoundsCenterZoom: function (bounds, options) { 3609 3610 options = options || {}; 3611 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3612 3613 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3614 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3615 3616 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3617 3618 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3619 3620 if (zoom === Infinity) { 3621 return { 3622 center: bounds.getCenter(), 3623 zoom: zoom 3624 }; 3625 } 3626 3627 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3628 3629 swPoint = this.project(bounds.getSouthWest(), zoom), 3630 nePoint = this.project(bounds.getNorthEast(), zoom), 3631 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3632 3633 return { 3634 center: center, 3635 zoom: zoom 3636 }; 3637 }, 3638 3639 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3640 // Sets a map view that contains the given geographical bounds with the 3641 // maximum zoom level possible. 3642 fitBounds: function (bounds, options) { 3643 3644 bounds = toLatLngBounds(bounds); 3645 3646 if (!bounds.isValid()) { 3647 throw new Error('Bounds are not valid.'); 3648 } 3649 3650 var target = this._getBoundsCenterZoom(bounds, options); 3651 return this.setView(target.center, target.zoom, options); 3652 }, 3653 3654 // @method fitWorld(options?: fitBounds options): this 3655 // Sets a map view that mostly contains the whole world with the maximum 3656 // zoom level possible. 3657 fitWorld: function (options) { 3658 return this.fitBounds([[-90, -180], [90, 180]], options); 3659 }, 3660 3661 // @method panTo(latlng: LatLng, options?: Pan options): this 3662 // Pans the map to a given center. 3663 panTo: function (center, options) { // (LatLng) 3664 return this.setView(center, this._zoom, {pan: options}); 3665 }, 3666 3667 // @method panBy(offset: Point, options?: Pan options): this 3668 // Pans the map by a given number of pixels (animated). 3669 panBy: function (offset, options) { 3670 offset = toPoint(offset).round(); 3671 options = options || {}; 3672 3673 if (!offset.x && !offset.y) { 3674 return this.fire('moveend'); 3675 } 3676 // If we pan too far, Chrome gets issues with tiles 3677 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3678 if (options.animate !== true && !this.getSize().contains(offset)) { 3679 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3680 return this; 3681 } 3682 3683 if (!this._panAnim) { 3684 this._panAnim = new PosAnimation(); 3685 3686 this._panAnim.on({ 3687 'step': this._onPanTransitionStep, 3688 'end': this._onPanTransitionEnd 3689 }, this); 3690 } 3691 3692 // don't fire movestart if animating inertia 3693 if (!options.noMoveStart) { 3694 this.fire('movestart'); 3695 } 3696 3697 // animate pan unless animate: false specified 3698 if (options.animate !== false) { 3699 addClass(this._mapPane, 'leaflet-pan-anim'); 3700 3701 var newPos = this._getMapPanePos().subtract(offset).round(); 3702 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3703 } else { 3704 this._rawPanBy(offset); 3705 this.fire('move').fire('moveend'); 3706 } 3707 3708 return this; 3709 }, 3710 3711 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3712 // Sets the view of the map (geographical center and zoom) performing a smooth 3713 // pan-zoom animation. 3714 flyTo: function (targetCenter, targetZoom, options) { 3715 3716 options = options || {}; 3717 if (options.animate === false || !Browser.any3d) { 3718 return this.setView(targetCenter, targetZoom, options); 3719 } 3720 3721 this._stop(); 3722 3723 var from = this.project(this.getCenter()), 3724 to = this.project(targetCenter), 3725 size = this.getSize(), 3726 startZoom = this._zoom; 3727 3728 targetCenter = toLatLng(targetCenter); 3729 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3730 3731 var w0 = Math.max(size.x, size.y), 3732 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3733 u1 = (to.distanceTo(from)) || 1, 3734 rho = 1.42, 3735 rho2 = rho * rho; 3736 3737 function r(i) { 3738 var s1 = i ? -1 : 1, 3739 s2 = i ? w1 : w0, 3740 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3741 b1 = 2 * s2 * rho2 * u1, 3742 b = t1 / b1, 3743 sq = Math.sqrt(b * b + 1) - b; 3744 3745 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3746 // thus triggering an infinite loop in flyTo 3747 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3748 3749 return log; 3750 } 3751 3752 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3753 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3754 function tanh(n) { return sinh(n) / cosh(n); } 3755 3756 var r0 = r(0); 3757 3758 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3759 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3760 3761 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3762 3763 var start = Date.now(), 3764 S = (r(1) - r0) / rho, 3765 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3766 3767 function frame() { 3768 var t = (Date.now() - start) / duration, 3769 s = easeOut(t) * S; 3770 3771 if (t <= 1) { 3772 this._flyToFrame = requestAnimFrame(frame, this); 3773 3774 this._move( 3775 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3776 this.getScaleZoom(w0 / w(s), startZoom), 3777 {flyTo: true}); 3778 3779 } else { 3780 this 3781 ._move(targetCenter, targetZoom) 3782 ._moveEnd(true); 3783 } 3784 } 3785 3786 this._moveStart(true, options.noMoveStart); 3787 3788 frame.call(this); 3789 return this; 3790 }, 3791 3792 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3793 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3794 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3795 flyToBounds: function (bounds, options) { 3796 var target = this._getBoundsCenterZoom(bounds, options); 3797 return this.flyTo(target.center, target.zoom, options); 3798 }, 3799 3800 // @method setMaxBounds(bounds: LatLngBounds): this 3801 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3802 setMaxBounds: function (bounds) { 3803 bounds = toLatLngBounds(bounds); 3804 3805 if (this.listens('moveend', this._panInsideMaxBounds)) { 3806 this.off('moveend', this._panInsideMaxBounds); 3807 } 3808 3809 if (!bounds.isValid()) { 3810 this.options.maxBounds = null; 3811 return this; 3812 } 3813 3814 this.options.maxBounds = bounds; 3815 3816 if (this._loaded) { 3817 this._panInsideMaxBounds(); 3818 } 3819 3820 return this.on('moveend', this._panInsideMaxBounds); 3821 }, 3822 3823 // @method setMinZoom(zoom: Number): this 3824 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3825 setMinZoom: function (zoom) { 3826 var oldZoom = this.options.minZoom; 3827 this.options.minZoom = zoom; 3828 3829 if (this._loaded && oldZoom !== zoom) { 3830 this.fire('zoomlevelschange'); 3831 3832 if (this.getZoom() < this.options.minZoom) { 3833 return this.setZoom(zoom); 3834 } 3835 } 3836 3837 return this; 3838 }, 3839 3840 // @method setMaxZoom(zoom: Number): this 3841 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3842 setMaxZoom: function (zoom) { 3843 var oldZoom = this.options.maxZoom; 3844 this.options.maxZoom = zoom; 3845 3846 if (this._loaded && oldZoom !== zoom) { 3847 this.fire('zoomlevelschange'); 3848 3849 if (this.getZoom() > this.options.maxZoom) { 3850 return this.setZoom(zoom); 3851 } 3852 } 3853 3854 return this; 3855 }, 3856 3857 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3858 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any. 3859 panInsideBounds: function (bounds, options) { 3860 this._enforcingBounds = true; 3861 var center = this.getCenter(), 3862 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3863 3864 if (!center.equals(newCenter)) { 3865 this.panTo(newCenter, options); 3866 } 3867 3868 this._enforcingBounds = false; 3869 return this; 3870 }, 3871 3872 // @method panInside(latlng: LatLng, options?: padding options): this 3873 // Pans the map the minimum amount to make the `latlng` visible. Use 3874 // padding options to fit the display to more restricted bounds. 3875 // If `latlng` is already within the (optionally padded) display bounds, 3876 // the map will not be panned. 3877 panInside: function (latlng, options) { 3878 options = options || {}; 3879 3880 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3881 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3882 pixelCenter = this.project(this.getCenter()), 3883 pixelPoint = this.project(latlng), 3884 pixelBounds = this.getPixelBounds(), 3885 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), 3886 paddedSize = paddedBounds.getSize(); 3887 3888 if (!paddedBounds.contains(pixelPoint)) { 3889 this._enforcingBounds = true; 3890 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter()); 3891 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize); 3892 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x; 3893 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y; 3894 this.panTo(this.unproject(pixelCenter), options); 3895 this._enforcingBounds = false; 3896 } 3897 return this; 3898 }, 3899 3900 // @method invalidateSize(options: Zoom/pan options): this 3901 // Checks if the map container size changed and updates the map if so â 3902 // call it after you've changed the map size dynamically, also animating 3903 // pan by default. If `options.pan` is `false`, panning will not occur. 3904 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3905 // that it doesn't happen often even if the method is called many 3906 // times in a row. 3907 3908 // @alternative 3909 // @method invalidateSize(animate: Boolean): this 3910 // Checks if the map container size changed and updates the map if so â 3911 // call it after you've changed the map size dynamically, also animating 3912 // pan by default. 3913 invalidateSize: function (options) { 3914 if (!this._loaded) { return this; } 3915 3916 options = extend({ 3917 animate: false, 3918 pan: true 3919 }, options === true ? {animate: true} : options); 3920 3921 var oldSize = this.getSize(); 3922 this._sizeChanged = true; 3923 this._lastCenter = null; 3924 3925 var newSize = this.getSize(), 3926 oldCenter = oldSize.divideBy(2).round(), 3927 newCenter = newSize.divideBy(2).round(), 3928 offset = oldCenter.subtract(newCenter); 3929 3930 if (!offset.x && !offset.y) { return this; } 3931 3932 if (options.animate && options.pan) { 3933 this.panBy(offset); 3934 3935 } else { 3936 if (options.pan) { 3937 this._rawPanBy(offset); 3938 } 3939 3940 this.fire('move'); 3941 3942 if (options.debounceMoveend) { 3943 clearTimeout(this._sizeTimer); 3944 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3945 } else { 3946 this.fire('moveend'); 3947 } 3948 } 3949 3950 // @section Map state change events 3951 // @event resize: ResizeEvent 3952 // Fired when the map is resized. 3953 return this.fire('resize', { 3954 oldSize: oldSize, 3955 newSize: newSize 3956 }); 3957 }, 3958 3959 // @section Methods for modifying map state 3960 // @method stop(): this 3961 // Stops the currently running `panTo` or `flyTo` animation, if any. 3962 stop: function () { 3963 this.setZoom(this._limitZoom(this._zoom)); 3964 if (!this.options.zoomSnap) { 3965 this.fire('viewreset'); 3966 } 3967 return this._stop(); 3968 }, 3969 3970 // @section Geolocation methods 3971 // @method locate(options?: Locate options): this 3972 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3973 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3974 // and optionally sets the map view to the user's location with respect to 3975 // detection accuracy (or to the world view if geolocation failed). 3976 // Note that, if your page doesn't use HTTPS, this method will fail in 3977 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3978 // See `Locate options` for more details. 3979 locate: function (options) { 3980 3981 options = this._locateOptions = extend({ 3982 timeout: 10000, 3983 watch: false 3984 // setView: false 3985 // maxZoom: <Number> 3986 // maximumAge: 0 3987 // enableHighAccuracy: false 3988 }, options); 3989 3990 if (!('geolocation' in navigator)) { 3991 this._handleGeolocationError({ 3992 code: 0, 3993 message: 'Geolocation not supported.' 3994 }); 3995 return this; 3996 } 3997 3998 var onResponse = bind(this._handleGeolocationResponse, this), 3999 onError = bind(this._handleGeolocationError, this); 4000 4001 if (options.watch) { 4002 this._locationWatchId = 4003 navigator.geolocation.watchPosition(onResponse, onError, options); 4004 } else { 4005 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 4006 } 4007 return this; 4008 }, 4009 4010 // @method stopLocate(): this 4011 // Stops watching location previously initiated by `map.locate({watch: true})` 4012 // and aborts resetting the map view if map.locate was called with 4013 // `{setView: true}`. 4014 stopLocate: function () { 4015 if (navigator.geolocation && navigator.geolocation.clearWatch) { 4016 navigator.geolocation.clearWatch(this._locationWatchId); 4017 } 4018 if (this._locateOptions) { 4019 this._locateOptions.setView = false; 4020 } 4021 return this; 4022 }, 4023 4024 _handleGeolocationError: function (error) { 4025 if (!this._container._leaflet_id) { return; } 4026 4027 var c = error.code, 4028 message = error.message || 4029 (c === 1 ? 'permission denied' : 4030 (c === 2 ? 'position unavailable' : 'timeout')); 4031 4032 if (this._locateOptions.setView && !this._loaded) { 4033 this.fitWorld(); 4034 } 4035 4036 // @section Location events 4037 // @event locationerror: ErrorEvent 4038 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 4039 this.fire('locationerror', { 4040 code: c, 4041 message: 'Geolocation error: ' + message + '.' 4042 }); 4043 }, 4044 4045 _handleGeolocationResponse: function (pos) { 4046 if (!this._container._leaflet_id) { return; } 4047 4048 var lat = pos.coords.latitude, 4049 lng = pos.coords.longitude, 4050 latlng = new LatLng(lat, lng), 4051 bounds = latlng.toBounds(pos.coords.accuracy * 2), 4052 options = this._locateOptions; 4053 4054 if (options.setView) { 4055 var zoom = this.getBoundsZoom(bounds); 4056 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 4057 } 4058 4059 var data = { 4060 latlng: latlng, 4061 bounds: bounds, 4062 timestamp: pos.timestamp 4063 }; 4064 4065 for (var i in pos.coords) { 4066 if (typeof pos.coords[i] === 'number') { 4067 data[i] = pos.coords[i]; 4068 } 4069 } 4070 4071 // @event locationfound: LocationEvent 4072 // Fired when geolocation (using the [`locate`](#map-locate) method) 4073 // went successfully. 4074 this.fire('locationfound', data); 4075 }, 4076 4077 // TODO Appropriate docs section? 4078 // @section Other Methods 4079 // @method addHandler(name: String, HandlerClass: Function): this 4080 // Adds a new `Handler` to the map, given its name and constructor function. 4081 addHandler: function (name, HandlerClass) { 4082 if (!HandlerClass) { return this; } 4083 4084 var handler = this[name] = new HandlerClass(this); 4085 4086 this._handlers.push(handler); 4087 4088 if (this.options[name]) { 4089 handler.enable(); 4090 } 4091 4092 return this; 4093 }, 4094 4095 // @method remove(): this 4096 // Destroys the map and clears all related event listeners. 4097 remove: function () { 4098 4099 this._initEvents(true); 4100 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); } 4101 4102 if (this._containerId !== this._container._leaflet_id) { 4103 throw new Error('Map container is being reused by another instance'); 4104 } 4105 4106 try { 4107 // throws error in IE6-8 4108 delete this._container._leaflet_id; 4109 delete this._containerId; 4110 } catch (e) { 4111 /*eslint-disable */ 4112 this._container._leaflet_id = undefined; 4113 /* eslint-enable */ 4114 this._containerId = undefined; 4115 } 4116 4117 if (this._locationWatchId !== undefined) { 4118 this.stopLocate(); 4119 } 4120 4121 this._stop(); 4122 4123 remove(this._mapPane); 4124 4125 if (this._clearControlPos) { 4126 this._clearControlPos(); 4127 } 4128 if (this._resizeRequest) { 4129 cancelAnimFrame(this._resizeRequest); 4130 this._resizeRequest = null; 4131 } 4132 4133 this._clearHandlers(); 4134 4135 if (this._loaded) { 4136 // @section Map state change events 4137 // @event unload: Event 4138 // Fired when the map is destroyed with [remove](#map-remove) method. 4139 this.fire('unload'); 4140 } 4141 4142 var i; 4143 for (i in this._layers) { 4144 this._layers[i].remove(); 4145 } 4146 for (i in this._panes) { 4147 remove(this._panes[i]); 4148 } 4149 4150 this._layers = []; 4151 this._panes = []; 4152 delete this._mapPane; 4153 delete this._renderer; 4154 4155 return this; 4156 }, 4157 4158 // @section Other Methods 4159 // @method createPane(name: String, container?: HTMLElement): HTMLElement 4160 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 4161 // then returns it. The pane is created as a child of `container`, or 4162 // as a child of the main map pane if not set. 4163 createPane: function (name, container) { 4164 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 4165 pane = create$1('div', className, container || this._mapPane); 4166 4167 if (name) { 4168 this._panes[name] = pane; 4169 } 4170 return pane; 4171 }, 4172 4173 // @section Methods for Getting Map State 4174 4175 // @method getCenter(): LatLng 4176 // Returns the geographical center of the map view 4177 getCenter: function () { 4178 this._checkIfLoaded(); 4179 4180 if (this._lastCenter && !this._moved()) { 4181 return this._lastCenter.clone(); 4182 } 4183 return this.layerPointToLatLng(this._getCenterLayerPoint()); 4184 }, 4185 4186 // @method getZoom(): Number 4187 // Returns the current zoom level of the map view 4188 getZoom: function () { 4189 return this._zoom; 4190 }, 4191 4192 // @method getBounds(): LatLngBounds 4193 // Returns the geographical bounds visible in the current map view 4194 getBounds: function () { 4195 var bounds = this.getPixelBounds(), 4196 sw = this.unproject(bounds.getBottomLeft()), 4197 ne = this.unproject(bounds.getTopRight()); 4198 4199 return new LatLngBounds(sw, ne); 4200 }, 4201 4202 // @method getMinZoom(): Number 4203 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default. 4204 getMinZoom: function () { 4205 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 4206 }, 4207 4208 // @method getMaxZoom(): Number 4209 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 4210 getMaxZoom: function () { 4211 return this.options.maxZoom === undefined ? 4212 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 4213 this.options.maxZoom; 4214 }, 4215 4216 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 4217 // Returns the maximum zoom level on which the given bounds fit to the map 4218 // view in its entirety. If `inside` (optional) is set to `true`, the method 4219 // instead returns the minimum zoom level on which the map view fits into 4220 // the given bounds in its entirety. 4221 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 4222 bounds = toLatLngBounds(bounds); 4223 padding = toPoint(padding || [0, 0]); 4224 4225 var zoom = this.getZoom() || 0, 4226 min = this.getMinZoom(), 4227 max = this.getMaxZoom(), 4228 nw = bounds.getNorthWest(), 4229 se = bounds.getSouthEast(), 4230 size = this.getSize().subtract(padding), 4231 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 4232 snap = Browser.any3d ? this.options.zoomSnap : 1, 4233 scalex = size.x / boundsSize.x, 4234 scaley = size.y / boundsSize.y, 4235 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 4236 4237 zoom = this.getScaleZoom(scale, zoom); 4238 4239 if (snap) { 4240 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 4241 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 4242 } 4243 4244 return Math.max(min, Math.min(max, zoom)); 4245 }, 4246 4247 // @method getSize(): Point 4248 // Returns the current size of the map container (in pixels). 4249 getSize: function () { 4250 if (!this._size || this._sizeChanged) { 4251 this._size = new Point( 4252 this._container.clientWidth || 0, 4253 this._container.clientHeight || 0); 4254 4255 this._sizeChanged = false; 4256 } 4257 return this._size.clone(); 4258 }, 4259 4260 // @method getPixelBounds(): Bounds 4261 // Returns the bounds of the current map view in projected pixel 4262 // coordinates (sometimes useful in layer and overlay implementations). 4263 getPixelBounds: function (center, zoom) { 4264 var topLeftPoint = this._getTopLeftPoint(center, zoom); 4265 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 4266 }, 4267 4268 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 4269 // the map pane? "left point of the map layer" can be confusing, specially 4270 // since there can be negative offsets. 4271 // @method getPixelOrigin(): Point 4272 // Returns the projected pixel coordinates of the top left point of 4273 // the map layer (useful in custom layer and overlay implementations). 4274 getPixelOrigin: function () { 4275 this._checkIfLoaded(); 4276 return this._pixelOrigin; 4277 }, 4278 4279 // @method getPixelWorldBounds(zoom?: Number): Bounds 4280 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 4281 // If `zoom` is omitted, the map's current zoom level is used. 4282 getPixelWorldBounds: function (zoom) { 4283 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 4284 }, 4285 4286 // @section Other Methods 4287 4288 // @method getPane(pane: String|HTMLElement): HTMLElement 4289 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 4290 getPane: function (pane) { 4291 return typeof pane === 'string' ? this._panes[pane] : pane; 4292 }, 4293 4294 // @method getPanes(): Object 4295 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 4296 // the panes as values. 4297 getPanes: function () { 4298 return this._panes; 4299 }, 4300 4301 // @method getContainer: HTMLElement 4302 // Returns the HTML element that contains the map. 4303 getContainer: function () { 4304 return this._container; 4305 }, 4306 4307 4308 // @section Conversion Methods 4309 4310 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 4311 // Returns the scale factor to be applied to a map transition from zoom level 4312 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 4313 getZoomScale: function (toZoom, fromZoom) { 4314 // TODO replace with universal implementation after refactoring projections 4315 var crs = this.options.crs; 4316 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 4317 return crs.scale(toZoom) / crs.scale(fromZoom); 4318 }, 4319 4320 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 4321 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 4322 // level and everything is scaled by a factor of `scale`. Inverse of 4323 // [`getZoomScale`](#map-getZoomScale). 4324 getScaleZoom: function (scale, fromZoom) { 4325 var crs = this.options.crs; 4326 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 4327 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 4328 return isNaN(zoom) ? Infinity : zoom; 4329 }, 4330 4331 // @method project(latlng: LatLng, zoom: Number): Point 4332 // Projects a geographical coordinate `LatLng` according to the projection 4333 // of the map's CRS, then scales it according to `zoom` and the CRS's 4334 // `Transformation`. The result is pixel coordinate relative to 4335 // the CRS origin. 4336 project: function (latlng, zoom) { 4337 zoom = zoom === undefined ? this._zoom : zoom; 4338 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 4339 }, 4340 4341 // @method unproject(point: Point, zoom: Number): LatLng 4342 // Inverse of [`project`](#map-project). 4343 unproject: function (point, zoom) { 4344 zoom = zoom === undefined ? this._zoom : zoom; 4345 return this.options.crs.pointToLatLng(toPoint(point), zoom); 4346 }, 4347 4348 // @method layerPointToLatLng(point: Point): LatLng 4349 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4350 // returns the corresponding geographical coordinate (for the current zoom level). 4351 layerPointToLatLng: function (point) { 4352 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 4353 return this.unproject(projectedPoint); 4354 }, 4355 4356 // @method latLngToLayerPoint(latlng: LatLng): Point 4357 // Given a geographical coordinate, returns the corresponding pixel coordinate 4358 // relative to the [origin pixel](#map-getpixelorigin). 4359 latLngToLayerPoint: function (latlng) { 4360 var projectedPoint = this.project(toLatLng(latlng))._round(); 4361 return projectedPoint._subtract(this.getPixelOrigin()); 4362 }, 4363 4364 // @method wrapLatLng(latlng: LatLng): LatLng 4365 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 4366 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 4367 // CRS's bounds. 4368 // By default this means longitude is wrapped around the dateline so its 4369 // value is between -180 and +180 degrees. 4370 wrapLatLng: function (latlng) { 4371 return this.options.crs.wrapLatLng(toLatLng(latlng)); 4372 }, 4373 4374 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 4375 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 4376 // its center is within the CRS's bounds. 4377 // By default this means the center longitude is wrapped around the dateline so its 4378 // value is between -180 and +180 degrees, and the majority of the bounds 4379 // overlaps the CRS's bounds. 4380 wrapLatLngBounds: function (latlng) { 4381 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 4382 }, 4383 4384 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 4385 // Returns the distance between two geographical coordinates according to 4386 // the map's CRS. By default this measures distance in meters. 4387 distance: function (latlng1, latlng2) { 4388 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 4389 }, 4390 4391 // @method containerPointToLayerPoint(point: Point): Point 4392 // Given a pixel coordinate relative to the map container, returns the corresponding 4393 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 4394 containerPointToLayerPoint: function (point) { // (Point) 4395 return toPoint(point).subtract(this._getMapPanePos()); 4396 }, 4397 4398 // @method layerPointToContainerPoint(point: Point): Point 4399 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4400 // returns the corresponding pixel coordinate relative to the map container. 4401 layerPointToContainerPoint: function (point) { // (Point) 4402 return toPoint(point).add(this._getMapPanePos()); 4403 }, 4404 4405 // @method containerPointToLatLng(point: Point): LatLng 4406 // Given a pixel coordinate relative to the map container, returns 4407 // the corresponding geographical coordinate (for the current zoom level). 4408 containerPointToLatLng: function (point) { 4409 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4410 return this.layerPointToLatLng(layerPoint); 4411 }, 4412 4413 // @method latLngToContainerPoint(latlng: LatLng): Point 4414 // Given a geographical coordinate, returns the corresponding pixel coordinate 4415 // relative to the map container. 4416 latLngToContainerPoint: function (latlng) { 4417 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4418 }, 4419 4420 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4421 // Given a MouseEvent object, returns the pixel coordinate relative to the 4422 // map container where the event took place. 4423 mouseEventToContainerPoint: function (e) { 4424 return getMousePosition(e, this._container); 4425 }, 4426 4427 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4428 // Given a MouseEvent object, returns the pixel coordinate relative to 4429 // the [origin pixel](#map-getpixelorigin) where the event took place. 4430 mouseEventToLayerPoint: function (e) { 4431 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4432 }, 4433 4434 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4435 // Given a MouseEvent object, returns geographical coordinate where the 4436 // event took place. 4437 mouseEventToLatLng: function (e) { // (MouseEvent) 4438 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4439 }, 4440 4441 4442 // map initialization methods 4443 4444 _initContainer: function (id) { 4445 var container = this._container = get(id); 4446 4447 if (!container) { 4448 throw new Error('Map container not found.'); 4449 } else if (container._leaflet_id) { 4450 throw new Error('Map container is already initialized.'); 4451 } 4452 4453 on(container, 'scroll', this._onScroll, this); 4454 this._containerId = stamp(container); 4455 }, 4456 4457 _initLayout: function () { 4458 var container = this._container; 4459 4460 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d; 4461 4462 addClass(container, 'leaflet-container' + 4463 (Browser.touch ? ' leaflet-touch' : '') + 4464 (Browser.retina ? ' leaflet-retina' : '') + 4465 (Browser.ielt9 ? ' leaflet-oldie' : '') + 4466 (Browser.safari ? ' leaflet-safari' : '') + 4467 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4468 4469 var position = getStyle(container, 'position'); 4470 4471 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') { 4472 container.style.position = 'relative'; 4473 } 4474 4475 this._initPanes(); 4476 4477 if (this._initControlPos) { 4478 this._initControlPos(); 4479 } 4480 }, 4481 4482 _initPanes: function () { 4483 var panes = this._panes = {}; 4484 this._paneRenderers = {}; 4485 4486 // @section 4487 // 4488 // Panes are DOM elements used to control the ordering of layers on the map. You 4489 // can access panes with [`map.getPane`](#map-getpane) or 4490 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4491 // [`map.createPane`](#map-createpane) method. 4492 // 4493 // Every map has the following default panes that differ only in zIndex. 4494 // 4495 // @pane mapPane: HTMLElement = 'auto' 4496 // Pane that contains all other map panes 4497 4498 this._mapPane = this.createPane('mapPane', this._container); 4499 setPosition(this._mapPane, new Point(0, 0)); 4500 4501 // @pane tilePane: HTMLElement = 200 4502 // Pane for `GridLayer`s and `TileLayer`s 4503 this.createPane('tilePane'); 4504 // @pane overlayPane: HTMLElement = 400 4505 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4506 this.createPane('overlayPane'); 4507 // @pane shadowPane: HTMLElement = 500 4508 // Pane for overlay shadows (e.g. `Marker` shadows) 4509 this.createPane('shadowPane'); 4510 // @pane markerPane: HTMLElement = 600 4511 // Pane for `Icon`s of `Marker`s 4512 this.createPane('markerPane'); 4513 // @pane tooltipPane: HTMLElement = 650 4514 // Pane for `Tooltip`s. 4515 this.createPane('tooltipPane'); 4516 // @pane popupPane: HTMLElement = 700 4517 // Pane for `Popup`s. 4518 this.createPane('popupPane'); 4519 4520 if (!this.options.markerZoomAnimation) { 4521 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4522 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4523 } 4524 }, 4525 4526 4527 // private methods that modify map state 4528 4529 // @section Map state change events 4530 _resetView: function (center, zoom, noMoveStart) { 4531 setPosition(this._mapPane, new Point(0, 0)); 4532 4533 var loading = !this._loaded; 4534 this._loaded = true; 4535 zoom = this._limitZoom(zoom); 4536 4537 this.fire('viewprereset'); 4538 4539 var zoomChanged = this._zoom !== zoom; 4540 this 4541 ._moveStart(zoomChanged, noMoveStart) 4542 ._move(center, zoom) 4543 ._moveEnd(zoomChanged); 4544 4545 // @event viewreset: Event 4546 // Fired when the map needs to redraw its content (this usually happens 4547 // on map zoom or load). Very useful for creating custom overlays. 4548 this.fire('viewreset'); 4549 4550 // @event load: Event 4551 // Fired when the map is initialized (when its center and zoom are set 4552 // for the first time). 4553 if (loading) { 4554 this.fire('load'); 4555 } 4556 }, 4557 4558 _moveStart: function (zoomChanged, noMoveStart) { 4559 // @event zoomstart: Event 4560 // Fired when the map zoom is about to change (e.g. before zoom animation). 4561 // @event movestart: Event 4562 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4563 if (zoomChanged) { 4564 this.fire('zoomstart'); 4565 } 4566 if (!noMoveStart) { 4567 this.fire('movestart'); 4568 } 4569 return this; 4570 }, 4571 4572 _move: function (center, zoom, data, supressEvent) { 4573 if (zoom === undefined) { 4574 zoom = this._zoom; 4575 } 4576 var zoomChanged = this._zoom !== zoom; 4577 4578 this._zoom = zoom; 4579 this._lastCenter = center; 4580 this._pixelOrigin = this._getNewPixelOrigin(center); 4581 4582 if (!supressEvent) { 4583 // @event zoom: Event 4584 // Fired repeatedly during any change in zoom level, 4585 // including zoom and fly animations. 4586 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4587 this.fire('zoom', data); 4588 } 4589 4590 // @event move: Event 4591 // Fired repeatedly during any movement of the map, 4592 // including pan and fly animations. 4593 this.fire('move', data); 4594 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530 4595 this.fire('zoom', data); 4596 } 4597 return this; 4598 }, 4599 4600 _moveEnd: function (zoomChanged) { 4601 // @event zoomend: Event 4602 // Fired when the map zoom changed, after any animations. 4603 if (zoomChanged) { 4604 this.fire('zoomend'); 4605 } 4606 4607 // @event moveend: Event 4608 // Fired when the center of the map stops changing 4609 // (e.g. user stopped dragging the map or after non-centered zoom). 4610 return this.fire('moveend'); 4611 }, 4612 4613 _stop: function () { 4614 cancelAnimFrame(this._flyToFrame); 4615 if (this._panAnim) { 4616 this._panAnim.stop(); 4617 } 4618 return this; 4619 }, 4620 4621 _rawPanBy: function (offset) { 4622 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4623 }, 4624 4625 _getZoomSpan: function () { 4626 return this.getMaxZoom() - this.getMinZoom(); 4627 }, 4628 4629 _panInsideMaxBounds: function () { 4630 if (!this._enforcingBounds) { 4631 this.panInsideBounds(this.options.maxBounds); 4632 } 4633 }, 4634 4635 _checkIfLoaded: function () { 4636 if (!this._loaded) { 4637 throw new Error('Set map center and zoom first.'); 4638 } 4639 }, 4640 4641 // DOM event handling 4642 4643 // @section Interaction events 4644 _initEvents: function (remove) { 4645 this._targets = {}; 4646 this._targets[stamp(this._container)] = this; 4647 4648 var onOff = remove ? off : on; 4649 4650 // @event click: MouseEvent 4651 // Fired when the user clicks (or taps) the map. 4652 // @event dblclick: MouseEvent 4653 // Fired when the user double-clicks (or double-taps) the map. 4654 // @event mousedown: MouseEvent 4655 // Fired when the user pushes the mouse button on the map. 4656 // @event mouseup: MouseEvent 4657 // Fired when the user releases the mouse button on the map. 4658 // @event mouseover: MouseEvent 4659 // Fired when the mouse enters the map. 4660 // @event mouseout: MouseEvent 4661 // Fired when the mouse leaves the map. 4662 // @event mousemove: MouseEvent 4663 // Fired while the mouse moves over the map. 4664 // @event contextmenu: MouseEvent 4665 // Fired when the user pushes the right mouse button on the map, prevents 4666 // default browser context menu from showing if there are listeners on 4667 // this event. Also fired on mobile when the user holds a single touch 4668 // for a second (also called long press). 4669 // @event keypress: KeyboardEvent 4670 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4671 // @event keydown: KeyboardEvent 4672 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4673 // the `keydown` event is fired for keys that produce a character value and for keys 4674 // that do not produce a character value. 4675 // @event keyup: KeyboardEvent 4676 // Fired when the user releases a key from the keyboard while the map is focused. 4677 onOff(this._container, 'click dblclick mousedown mouseup ' + 4678 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4679 4680 if (this.options.trackResize) { 4681 onOff(window, 'resize', this._onResize, this); 4682 } 4683 4684 if (Browser.any3d && this.options.transform3DLimit) { 4685 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4686 } 4687 }, 4688 4689 _onResize: function () { 4690 cancelAnimFrame(this._resizeRequest); 4691 this._resizeRequest = requestAnimFrame( 4692 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4693 }, 4694 4695 _onScroll: function () { 4696 this._container.scrollTop = 0; 4697 this._container.scrollLeft = 0; 4698 }, 4699 4700 _onMoveEnd: function () { 4701 var pos = this._getMapPanePos(); 4702 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4703 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4704 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/ 4705 this._resetView(this.getCenter(), this.getZoom()); 4706 } 4707 }, 4708 4709 _findEventTargets: function (e, type) { 4710 var targets = [], 4711 target, 4712 isHover = type === 'mouseout' || type === 'mouseover', 4713 src = e.target || e.srcElement, 4714 dragging = false; 4715 4716 while (src) { 4717 target = this._targets[stamp(src)]; 4718 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) { 4719 // Prevent firing click after you just dragged an object. 4720 dragging = true; 4721 break; 4722 } 4723 if (target && target.listens(type, true)) { 4724 if (isHover && !isExternalTarget(src, e)) { break; } 4725 targets.push(target); 4726 if (isHover) { break; } 4727 } 4728 if (src === this._container) { break; } 4729 src = src.parentNode; 4730 } 4731 if (!targets.length && !dragging && !isHover && this.listens(type, true)) { 4732 targets = [this]; 4733 } 4734 return targets; 4735 }, 4736 4737 _isClickDisabled: function (el) { 4738 while (el && el !== this._container) { 4739 if (el['_leaflet_disable_click']) { return true; } 4740 el = el.parentNode; 4741 } 4742 }, 4743 4744 _handleDOMEvent: function (e) { 4745 var el = (e.target || e.srcElement); 4746 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) { 4747 return; 4748 } 4749 4750 var type = e.type; 4751 4752 if (type === 'mousedown') { 4753 // prevents outline when clicking on keyboard-focusable element 4754 preventOutline(el); 4755 } 4756 4757 this._fireDOMEvent(e, type); 4758 }, 4759 4760 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4761 4762 _fireDOMEvent: function (e, type, canvasTargets) { 4763 4764 if (e.type === 'click') { 4765 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4766 // @event preclick: MouseEvent 4767 // Fired before mouse click on the map (sometimes useful when you 4768 // want something to happen on click before any existing click 4769 // handlers start running). 4770 var synth = extend({}, e); 4771 synth.type = 'preclick'; 4772 this._fireDOMEvent(synth, synth.type, canvasTargets); 4773 } 4774 4775 // Find the layer the event is propagating from and its parents. 4776 var targets = this._findEventTargets(e, type); 4777 4778 if (canvasTargets) { 4779 var filtered = []; // pick only targets with listeners 4780 for (var i = 0; i < canvasTargets.length; i++) { 4781 if (canvasTargets[i].listens(type, true)) { 4782 filtered.push(canvasTargets[i]); 4783 } 4784 } 4785 targets = filtered.concat(targets); 4786 } 4787 4788 if (!targets.length) { return; } 4789 4790 if (type === 'contextmenu') { 4791 preventDefault(e); 4792 } 4793 4794 var target = targets[0]; 4795 var data = { 4796 originalEvent: e 4797 }; 4798 4799 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4800 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4801 data.containerPoint = isMarker ? 4802 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4803 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4804 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4805 } 4806 4807 for (i = 0; i < targets.length; i++) { 4808 targets[i].fire(type, data, true); 4809 if (data.originalEvent._stopped || 4810 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4811 } 4812 }, 4813 4814 _draggableMoved: function (obj) { 4815 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4816 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4817 }, 4818 4819 _clearHandlers: function () { 4820 for (var i = 0, len = this._handlers.length; i < len; i++) { 4821 this._handlers[i].disable(); 4822 } 4823 }, 4824 4825 // @section Other Methods 4826 4827 // @method whenReady(fn: Function, context?: Object): this 4828 // Runs the given function `fn` when the map gets initialized with 4829 // a view (center and zoom) and at least one layer, or immediately 4830 // if it's already initialized, optionally passing a function context. 4831 whenReady: function (callback, context) { 4832 if (this._loaded) { 4833 callback.call(context || this, {target: this}); 4834 } else { 4835 this.on('load', callback, context); 4836 } 4837 return this; 4838 }, 4839 4840 4841 // private methods for getting map state 4842 4843 _getMapPanePos: function () { 4844 return getPosition(this._mapPane) || new Point(0, 0); 4845 }, 4846 4847 _moved: function () { 4848 var pos = this._getMapPanePos(); 4849 return pos && !pos.equals([0, 0]); 4850 }, 4851 4852 _getTopLeftPoint: function (center, zoom) { 4853 var pixelOrigin = center && zoom !== undefined ? 4854 this._getNewPixelOrigin(center, zoom) : 4855 this.getPixelOrigin(); 4856 return pixelOrigin.subtract(this._getMapPanePos()); 4857 }, 4858 4859 _getNewPixelOrigin: function (center, zoom) { 4860 var viewHalf = this.getSize()._divideBy(2); 4861 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4862 }, 4863 4864 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4865 var topLeft = this._getNewPixelOrigin(center, zoom); 4866 return this.project(latlng, zoom)._subtract(topLeft); 4867 }, 4868 4869 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4870 var topLeft = this._getNewPixelOrigin(center, zoom); 4871 return toBounds([ 4872 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4873 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4874 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4875 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4876 ]); 4877 }, 4878 4879 // layer point of the current center 4880 _getCenterLayerPoint: function () { 4881 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4882 }, 4883 4884 // offset of the specified place to the current center in pixels 4885 _getCenterOffset: function (latlng) { 4886 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4887 }, 4888 4889 // adjust center for view to get inside bounds 4890 _limitCenter: function (center, zoom, bounds) { 4891 4892 if (!bounds) { return center; } 4893 4894 var centerPoint = this.project(center, zoom), 4895 viewHalf = this.getSize().divideBy(2), 4896 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4897 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4898 4899 // If offset is less than a pixel, ignore. 4900 // This prevents unstable projections from getting into 4901 // an infinite loop of tiny offsets. 4902 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) { 4903 return center; 4904 } 4905 4906 return this.unproject(centerPoint.add(offset), zoom); 4907 }, 4908 4909 // adjust offset for view to get inside bounds 4910 _limitOffset: function (offset, bounds) { 4911 if (!bounds) { return offset; } 4912 4913 var viewBounds = this.getPixelBounds(), 4914 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4915 4916 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4917 }, 4918 4919 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4920 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4921 var projectedMaxBounds = toBounds( 4922 this.project(maxBounds.getNorthEast(), zoom), 4923 this.project(maxBounds.getSouthWest(), zoom) 4924 ), 4925 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4926 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4927 4928 dx = this._rebound(minOffset.x, -maxOffset.x), 4929 dy = this._rebound(minOffset.y, -maxOffset.y); 4930 4931 return new Point(dx, dy); 4932 }, 4933 4934 _rebound: function (left, right) { 4935 return left + right > 0 ? 4936 Math.round(left - right) / 2 : 4937 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4938 }, 4939 4940 _limitZoom: function (zoom) { 4941 var min = this.getMinZoom(), 4942 max = this.getMaxZoom(), 4943 snap = Browser.any3d ? this.options.zoomSnap : 1; 4944 if (snap) { 4945 zoom = Math.round(zoom / snap) * snap; 4946 } 4947 return Math.max(min, Math.min(max, zoom)); 4948 }, 4949 4950 _onPanTransitionStep: function () { 4951 this.fire('move'); 4952 }, 4953 4954 _onPanTransitionEnd: function () { 4955 removeClass(this._mapPane, 'leaflet-pan-anim'); 4956 this.fire('moveend'); 4957 }, 4958 4959 _tryAnimatedPan: function (center, options) { 4960 // difference between the new and current centers in pixels 4961 var offset = this._getCenterOffset(center)._trunc(); 4962 4963 // don't animate too far unless animate: true specified in options 4964 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4965 4966 this.panBy(offset, options); 4967 4968 return true; 4969 }, 4970 4971 _createAnimProxy: function () { 4972 4973 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4974 this._panes.mapPane.appendChild(proxy); 4975 4976 this.on('zoomanim', function (e) { 4977 var prop = TRANSFORM, 4978 transform = this._proxy.style[prop]; 4979 4980 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4981 4982 // workaround for case when transform is the same and so transitionend event is not fired 4983 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4984 this._onZoomTransitionEnd(); 4985 } 4986 }, this); 4987 4988 this.on('load moveend', this._animMoveEnd, this); 4989 4990 this._on('unload', this._destroyAnimProxy, this); 4991 }, 4992 4993 _destroyAnimProxy: function () { 4994 remove(this._proxy); 4995 this.off('load moveend', this._animMoveEnd, this); 4996 delete this._proxy; 4997 }, 4998 4999 _animMoveEnd: function () { 5000 var c = this.getCenter(), 5001 z = this.getZoom(); 5002 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 5003 }, 5004 5005 _catchTransitionEnd: function (e) { 5006 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 5007 this._onZoomTransitionEnd(); 5008 } 5009 }, 5010 5011 _nothingToAnimate: function () { 5012 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 5013 }, 5014 5015 _tryAnimatedZoom: function (center, zoom, options) { 5016 5017 if (this._animatingZoom) { return true; } 5018 5019 options = options || {}; 5020 5021 // don't animate if disabled, not supported or zoom difference is too large 5022 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 5023 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 5024 5025 // offset is the pixel coords of the zoom origin relative to the current center 5026 var scale = this.getZoomScale(zoom), 5027 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 5028 5029 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 5030 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 5031 5032 requestAnimFrame(function () { 5033 this 5034 ._moveStart(true, options.noMoveStart || false) 5035 ._animateZoom(center, zoom, true); 5036 }, this); 5037 5038 return true; 5039 }, 5040 5041 _animateZoom: function (center, zoom, startAnim, noUpdate) { 5042 if (!this._mapPane) { return; } 5043 5044 if (startAnim) { 5045 this._animatingZoom = true; 5046 5047 // remember what center/zoom to set after animation 5048 this._animateToCenter = center; 5049 this._animateToZoom = zoom; 5050 5051 addClass(this._mapPane, 'leaflet-zoom-anim'); 5052 } 5053 5054 // @section Other Events 5055 // @event zoomanim: ZoomAnimEvent 5056 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 5057 this.fire('zoomanim', { 5058 center: center, 5059 zoom: zoom, 5060 noUpdate: noUpdate 5061 }); 5062 5063 if (!this._tempFireZoomEvent) { 5064 this._tempFireZoomEvent = this._zoom !== this._animateToZoom; 5065 } 5066 5067 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 5068 5069 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 5070 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 5071 }, 5072 5073 _onZoomTransitionEnd: function () { 5074 if (!this._animatingZoom) { return; } 5075 5076 if (this._mapPane) { 5077 removeClass(this._mapPane, 'leaflet-zoom-anim'); 5078 } 5079 5080 this._animatingZoom = false; 5081 5082 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 5083 5084 if (this._tempFireZoomEvent) { 5085 this.fire('zoom'); 5086 } 5087 delete this._tempFireZoomEvent; 5088 5089 this.fire('move'); 5090 5091 this._moveEnd(true); 5092 } 5093 }); 5094 5095 // @section 5096 5097 // @factory L.map(id: String, options?: Map options) 5098 // Instantiates a map object given the DOM ID of a `<div>` element 5099 // and optionally an object literal with `Map options`. 5100 // 5101 // @alternative 5102 // @factory L.map(el: HTMLElement, options?: Map options) 5103 // Instantiates a map object given an instance of a `<div>` HTML element 5104 // and optionally an object literal with `Map options`. 5105 function createMap(id, options) { 5106 return new Map(id, options); 5107 } 5108 5109 /* 5110 * @class Control 5111 * @aka L.Control 5112 * @inherits Class 5113 * 5114 * L.Control is a base class for implementing map controls. Handles positioning. 5115 * All other controls extend from this class. 5116 */ 5117 5118 var Control = Class.extend({ 5119 // @section 5120 // @aka Control Options 5121 options: { 5122 // @option position: String = 'topright' 5123 // The position of the control (one of the map corners). Possible values are `'topleft'`, 5124 // `'topright'`, `'bottomleft'` or `'bottomright'` 5125 position: 'topright' 5126 }, 5127 5128 initialize: function (options) { 5129 setOptions(this, options); 5130 }, 5131 5132 /* @section 5133 * Classes extending L.Control will inherit the following methods: 5134 * 5135 * @method getPosition: string 5136 * Returns the position of the control. 5137 */ 5138 getPosition: function () { 5139 return this.options.position; 5140 }, 5141 5142 // @method setPosition(position: string): this 5143 // Sets the position of the control. 5144 setPosition: function (position) { 5145 var map = this._map; 5146 5147 if (map) { 5148 map.removeControl(this); 5149 } 5150 5151 this.options.position = position; 5152 5153 if (map) { 5154 map.addControl(this); 5155 } 5156 5157 return this; 5158 }, 5159 5160 // @method getContainer: HTMLElement 5161 // Returns the HTMLElement that contains the control. 5162 getContainer: function () { 5163 return this._container; 5164 }, 5165 5166 // @method addTo(map: Map): this 5167 // Adds the control to the given map. 5168 addTo: function (map) { 5169 this.remove(); 5170 this._map = map; 5171 5172 var container = this._container = this.onAdd(map), 5173 pos = this.getPosition(), 5174 corner = map._controlCorners[pos]; 5175 5176 addClass(container, 'leaflet-control'); 5177 5178 if (pos.indexOf('bottom') !== -1) { 5179 corner.insertBefore(container, corner.firstChild); 5180 } else { 5181 corner.appendChild(container); 5182 } 5183 5184 this._map.on('unload', this.remove, this); 5185 5186 return this; 5187 }, 5188 5189 // @method remove: this 5190 // Removes the control from the map it is currently active on. 5191 remove: function () { 5192 if (!this._map) { 5193 return this; 5194 } 5195 5196 remove(this._container); 5197 5198 if (this.onRemove) { 5199 this.onRemove(this._map); 5200 } 5201 5202 this._map.off('unload', this.remove, this); 5203 this._map = null; 5204 5205 return this; 5206 }, 5207 5208 _refocusOnMap: function (e) { 5209 // if map exists and event is not a keyboard event 5210 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 5211 this._map.getContainer().focus(); 5212 } 5213 } 5214 }); 5215 5216 var control = function (options) { 5217 return new Control(options); 5218 }; 5219 5220 /* @section Extension methods 5221 * @uninheritable 5222 * 5223 * Every control should extend from `L.Control` and (re-)implement the following methods. 5224 * 5225 * @method onAdd(map: Map): HTMLElement 5226 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 5227 * 5228 * @method onRemove(map: Map) 5229 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 5230 */ 5231 5232 /* @namespace Map 5233 * @section Methods for Layers and Controls 5234 */ 5235 Map.include({ 5236 // @method addControl(control: Control): this 5237 // Adds the given control to the map 5238 addControl: function (control) { 5239 control.addTo(this); 5240 return this; 5241 }, 5242 5243 // @method removeControl(control: Control): this 5244 // Removes the given control from the map 5245 removeControl: function (control) { 5246 control.remove(); 5247 return this; 5248 }, 5249 5250 _initControlPos: function () { 5251 var corners = this._controlCorners = {}, 5252 l = 'leaflet-', 5253 container = this._controlContainer = 5254 create$1('div', l + 'control-container', this._container); 5255 5256 function createCorner(vSide, hSide) { 5257 var className = l + vSide + ' ' + l + hSide; 5258 5259 corners[vSide + hSide] = create$1('div', className, container); 5260 } 5261 5262 createCorner('top', 'left'); 5263 createCorner('top', 'right'); 5264 createCorner('bottom', 'left'); 5265 createCorner('bottom', 'right'); 5266 }, 5267 5268 _clearControlPos: function () { 5269 for (var i in this._controlCorners) { 5270 remove(this._controlCorners[i]); 5271 } 5272 remove(this._controlContainer); 5273 delete this._controlCorners; 5274 delete this._controlContainer; 5275 } 5276 }); 5277 5278 /* 5279 * @class Control.Layers 5280 * @aka L.Control.Layers 5281 * @inherits Control 5282 * 5283 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`. 5284 * 5285 * @example 5286 * 5287 * ```js 5288 * var baseLayers = { 5289 * "Mapbox": mapbox, 5290 * "OpenStreetMap": osm 5291 * }; 5292 * 5293 * var overlays = { 5294 * "Marker": marker, 5295 * "Roads": roadsLayer 5296 * }; 5297 * 5298 * L.control.layers(baseLayers, overlays).addTo(map); 5299 * ``` 5300 * 5301 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 5302 * 5303 * ```js 5304 * { 5305 * "<someName1>": layer1, 5306 * "<someName2>": layer2 5307 * } 5308 * ``` 5309 * 5310 * The layer names can contain HTML, which allows you to add additional styling to the items: 5311 * 5312 * ```js 5313 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 5314 * ``` 5315 */ 5316 5317 var Layers = Control.extend({ 5318 // @section 5319 // @aka Control.Layers options 5320 options: { 5321 // @option collapsed: Boolean = true 5322 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation. 5323 collapsed: true, 5324 position: 'topright', 5325 5326 // @option autoZIndex: Boolean = true 5327 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. 5328 autoZIndex: true, 5329 5330 // @option hideSingleBase: Boolean = false 5331 // If `true`, the base layers in the control will be hidden when there is only one. 5332 hideSingleBase: false, 5333 5334 // @option sortLayers: Boolean = false 5335 // Whether to sort the layers. When `false`, layers will keep the order 5336 // in which they were added to the control. 5337 sortLayers: false, 5338 5339 // @option sortFunction: Function = * 5340 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 5341 // that will be used for sorting the layers, when `sortLayers` is `true`. 5342 // The function receives both the `L.Layer` instances and their names, as in 5343 // `sortFunction(layerA, layerB, nameA, nameB)`. 5344 // By default, it sorts layers alphabetically by their name. 5345 sortFunction: function (layerA, layerB, nameA, nameB) { 5346 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 5347 } 5348 }, 5349 5350 initialize: function (baseLayers, overlays, options) { 5351 setOptions(this, options); 5352 5353 this._layerControlInputs = []; 5354 this._layers = []; 5355 this._lastZIndex = 0; 5356 this._handlingClick = false; 5357 this._preventClick = false; 5358 5359 for (var i in baseLayers) { 5360 this._addLayer(baseLayers[i], i); 5361 } 5362 5363 for (i in overlays) { 5364 this._addLayer(overlays[i], i, true); 5365 } 5366 }, 5367 5368 onAdd: function (map) { 5369 this._initLayout(); 5370 this._update(); 5371 5372 this._map = map; 5373 map.on('zoomend', this._checkDisabledLayers, this); 5374 5375 for (var i = 0; i < this._layers.length; i++) { 5376 this._layers[i].layer.on('add remove', this._onLayerChange, this); 5377 } 5378 5379 return this._container; 5380 }, 5381 5382 addTo: function (map) { 5383 Control.prototype.addTo.call(this, map); 5384 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 5385 return this._expandIfNotCollapsed(); 5386 }, 5387 5388 onRemove: function () { 5389 this._map.off('zoomend', this._checkDisabledLayers, this); 5390 5391 for (var i = 0; i < this._layers.length; i++) { 5392 this._layers[i].layer.off('add remove', this._onLayerChange, this); 5393 } 5394 }, 5395 5396 // @method addBaseLayer(layer: Layer, name: String): this 5397 // Adds a base layer (radio button entry) with the given name to the control. 5398 addBaseLayer: function (layer, name) { 5399 this._addLayer(layer, name); 5400 return (this._map) ? this._update() : this; 5401 }, 5402 5403 // @method addOverlay(layer: Layer, name: String): this 5404 // Adds an overlay (checkbox entry) with the given name to the control. 5405 addOverlay: function (layer, name) { 5406 this._addLayer(layer, name, true); 5407 return (this._map) ? this._update() : this; 5408 }, 5409 5410 // @method removeLayer(layer: Layer): this 5411 // Remove the given layer from the control. 5412 removeLayer: function (layer) { 5413 layer.off('add remove', this._onLayerChange, this); 5414 5415 var obj = this._getLayer(stamp(layer)); 5416 if (obj) { 5417 this._layers.splice(this._layers.indexOf(obj), 1); 5418 } 5419 return (this._map) ? this._update() : this; 5420 }, 5421 5422 // @method expand(): this 5423 // Expand the control container if collapsed. 5424 expand: function () { 5425 addClass(this._container, 'leaflet-control-layers-expanded'); 5426 this._section.style.height = null; 5427 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5428 if (acceptableHeight < this._section.clientHeight) { 5429 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5430 this._section.style.height = acceptableHeight + 'px'; 5431 } else { 5432 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5433 } 5434 this._checkDisabledLayers(); 5435 return this; 5436 }, 5437 5438 // @method collapse(): this 5439 // Collapse the control container if expanded. 5440 collapse: function () { 5441 removeClass(this._container, 'leaflet-control-layers-expanded'); 5442 return this; 5443 }, 5444 5445 _initLayout: function () { 5446 var className = 'leaflet-control-layers', 5447 container = this._container = create$1('div', className), 5448 collapsed = this.options.collapsed; 5449 5450 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5451 container.setAttribute('aria-haspopup', true); 5452 5453 disableClickPropagation(container); 5454 disableScrollPropagation(container); 5455 5456 var section = this._section = create$1('section', className + '-list'); 5457 5458 if (collapsed) { 5459 this._map.on('click', this.collapse, this); 5460 5461 on(container, { 5462 mouseenter: this._expandSafely, 5463 mouseleave: this.collapse 5464 }, this); 5465 } 5466 5467 var link = this._layersLink = create$1('a', className + '-toggle', container); 5468 link.href = '#'; 5469 link.title = 'Layers'; 5470 link.setAttribute('role', 'button'); 5471 5472 on(link, { 5473 keydown: function (e) { 5474 if (e.keyCode === 13) { 5475 this._expandSafely(); 5476 } 5477 }, 5478 // Certain screen readers intercept the key event and instead send a click event 5479 click: function (e) { 5480 preventDefault(e); 5481 this._expandSafely(); 5482 } 5483 }, this); 5484 5485 if (!collapsed) { 5486 this.expand(); 5487 } 5488 5489 this._baseLayersList = create$1('div', className + '-base', section); 5490 this._separator = create$1('div', className + '-separator', section); 5491 this._overlaysList = create$1('div', className + '-overlays', section); 5492 5493 container.appendChild(section); 5494 }, 5495 5496 _getLayer: function (id) { 5497 for (var i = 0; i < this._layers.length; i++) { 5498 5499 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5500 return this._layers[i]; 5501 } 5502 } 5503 }, 5504 5505 _addLayer: function (layer, name, overlay) { 5506 if (this._map) { 5507 layer.on('add remove', this._onLayerChange, this); 5508 } 5509 5510 this._layers.push({ 5511 layer: layer, 5512 name: name, 5513 overlay: overlay 5514 }); 5515 5516 if (this.options.sortLayers) { 5517 this._layers.sort(bind(function (a, b) { 5518 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5519 }, this)); 5520 } 5521 5522 if (this.options.autoZIndex && layer.setZIndex) { 5523 this._lastZIndex++; 5524 layer.setZIndex(this._lastZIndex); 5525 } 5526 5527 this._expandIfNotCollapsed(); 5528 }, 5529 5530 _update: function () { 5531 if (!this._container) { return this; } 5532 5533 empty(this._baseLayersList); 5534 empty(this._overlaysList); 5535 5536 this._layerControlInputs = []; 5537 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5538 5539 for (i = 0; i < this._layers.length; i++) { 5540 obj = this._layers[i]; 5541 this._addItem(obj); 5542 overlaysPresent = overlaysPresent || obj.overlay; 5543 baseLayersPresent = baseLayersPresent || !obj.overlay; 5544 baseLayersCount += !obj.overlay ? 1 : 0; 5545 } 5546 5547 // Hide base layers section if there's only one layer. 5548 if (this.options.hideSingleBase) { 5549 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5550 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5551 } 5552 5553 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5554 5555 return this; 5556 }, 5557 5558 _onLayerChange: function (e) { 5559 if (!this._handlingClick) { 5560 this._update(); 5561 } 5562 5563 var obj = this._getLayer(stamp(e.target)); 5564 5565 // @namespace Map 5566 // @section Layer events 5567 // @event baselayerchange: LayersControlEvent 5568 // Fired when the base layer is changed through the [layers control](#control-layers). 5569 // @event overlayadd: LayersControlEvent 5570 // Fired when an overlay is selected through the [layers control](#control-layers). 5571 // @event overlayremove: LayersControlEvent 5572 // Fired when an overlay is deselected through the [layers control](#control-layers). 5573 // @namespace Control.Layers 5574 var type = obj.overlay ? 5575 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5576 (e.type === 'add' ? 'baselayerchange' : null); 5577 5578 if (type) { 5579 this._map.fire(type, obj); 5580 } 5581 }, 5582 5583 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079) 5584 _createRadioElement: function (name, checked) { 5585 5586 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5587 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5588 5589 var radioFragment = document.createElement('div'); 5590 radioFragment.innerHTML = radioHtml; 5591 5592 return radioFragment.firstChild; 5593 }, 5594 5595 _addItem: function (obj) { 5596 var label = document.createElement('label'), 5597 checked = this._map.hasLayer(obj.layer), 5598 input; 5599 5600 if (obj.overlay) { 5601 input = document.createElement('input'); 5602 input.type = 'checkbox'; 5603 input.className = 'leaflet-control-layers-selector'; 5604 input.defaultChecked = checked; 5605 } else { 5606 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5607 } 5608 5609 this._layerControlInputs.push(input); 5610 input.layerId = stamp(obj.layer); 5611 5612 on(input, 'click', this._onInputClick, this); 5613 5614 var name = document.createElement('span'); 5615 name.innerHTML = ' ' + obj.name; 5616 5617 // Helps from preventing layer control flicker when checkboxes are disabled 5618 // https://github.com/Leaflet/Leaflet/issues/2771 5619 var holder = document.createElement('span'); 5620 5621 label.appendChild(holder); 5622 holder.appendChild(input); 5623 holder.appendChild(name); 5624 5625 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5626 container.appendChild(label); 5627 5628 this._checkDisabledLayers(); 5629 return label; 5630 }, 5631 5632 _onInputClick: function () { 5633 // expanding the control on mobile with a click can cause adding a layer - we don't want this 5634 if (this._preventClick) { 5635 return; 5636 } 5637 5638 var inputs = this._layerControlInputs, 5639 input, layer; 5640 var addedLayers = [], 5641 removedLayers = []; 5642 5643 this._handlingClick = true; 5644 5645 for (var i = inputs.length - 1; i >= 0; i--) { 5646 input = inputs[i]; 5647 layer = this._getLayer(input.layerId).layer; 5648 5649 if (input.checked) { 5650 addedLayers.push(layer); 5651 } else if (!input.checked) { 5652 removedLayers.push(layer); 5653 } 5654 } 5655 5656 // Bugfix issue 2318: Should remove all old layers before readding new ones 5657 for (i = 0; i < removedLayers.length; i++) { 5658 if (this._map.hasLayer(removedLayers[i])) { 5659 this._map.removeLayer(removedLayers[i]); 5660 } 5661 } 5662 for (i = 0; i < addedLayers.length; i++) { 5663 if (!this._map.hasLayer(addedLayers[i])) { 5664 this._map.addLayer(addedLayers[i]); 5665 } 5666 } 5667 5668 this._handlingClick = false; 5669 5670 this._refocusOnMap(); 5671 }, 5672 5673 _checkDisabledLayers: function () { 5674 var inputs = this._layerControlInputs, 5675 input, 5676 layer, 5677 zoom = this._map.getZoom(); 5678 5679 for (var i = inputs.length - 1; i >= 0; i--) { 5680 input = inputs[i]; 5681 layer = this._getLayer(input.layerId).layer; 5682 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5683 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5684 5685 } 5686 }, 5687 5688 _expandIfNotCollapsed: function () { 5689 if (this._map && !this.options.collapsed) { 5690 this.expand(); 5691 } 5692 return this; 5693 }, 5694 5695 _expandSafely: function () { 5696 var section = this._section; 5697 this._preventClick = true; 5698 on(section, 'click', preventDefault); 5699 this.expand(); 5700 var that = this; 5701 setTimeout(function () { 5702 off(section, 'click', preventDefault); 5703 that._preventClick = false; 5704 }); 5705 } 5706 5707 }); 5708 5709 5710 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5711 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. 5712 var layers = function (baseLayers, overlays, options) { 5713 return new Layers(baseLayers, overlays, options); 5714 }; 5715 5716 /* 5717 * @class Control.Zoom 5718 * @aka L.Control.Zoom 5719 * @inherits Control 5720 * 5721 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`. 5722 */ 5723 5724 var Zoom = Control.extend({ 5725 // @section 5726 // @aka Control.Zoom options 5727 options: { 5728 position: 'topleft', 5729 5730 // @option zoomInText: String = '<span aria-hidden="true">+</span>' 5731 // The text set on the 'zoom in' button. 5732 zoomInText: '<span aria-hidden="true">+</span>', 5733 5734 // @option zoomInTitle: String = 'Zoom in' 5735 // The title set on the 'zoom in' button. 5736 zoomInTitle: 'Zoom in', 5737 5738 // @option zoomOutText: String = '<span aria-hidden="true">−</span>' 5739 // The text set on the 'zoom out' button. 5740 zoomOutText: '<span aria-hidden="true">−</span>', 5741 5742 // @option zoomOutTitle: String = 'Zoom out' 5743 // The title set on the 'zoom out' button. 5744 zoomOutTitle: 'Zoom out' 5745 }, 5746 5747 onAdd: function (map) { 5748 var zoomName = 'leaflet-control-zoom', 5749 container = create$1('div', zoomName + ' leaflet-bar'), 5750 options = this.options; 5751 5752 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5753 zoomName + '-in', container, this._zoomIn); 5754 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5755 zoomName + '-out', container, this._zoomOut); 5756 5757 this._updateDisabled(); 5758 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5759 5760 return container; 5761 }, 5762 5763 onRemove: function (map) { 5764 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5765 }, 5766 5767 disable: function () { 5768 this._disabled = true; 5769 this._updateDisabled(); 5770 return this; 5771 }, 5772 5773 enable: function () { 5774 this._disabled = false; 5775 this._updateDisabled(); 5776 return this; 5777 }, 5778 5779 _zoomIn: function (e) { 5780 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5781 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5782 } 5783 }, 5784 5785 _zoomOut: function (e) { 5786 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5787 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5788 } 5789 }, 5790 5791 _createButton: function (html, title, className, container, fn) { 5792 var link = create$1('a', className, container); 5793 link.innerHTML = html; 5794 link.href = '#'; 5795 link.title = title; 5796 5797 /* 5798 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5799 */ 5800 link.setAttribute('role', 'button'); 5801 link.setAttribute('aria-label', title); 5802 5803 disableClickPropagation(link); 5804 on(link, 'click', stop); 5805 on(link, 'click', fn, this); 5806 on(link, 'click', this._refocusOnMap, this); 5807 5808 return link; 5809 }, 5810 5811 _updateDisabled: function () { 5812 var map = this._map, 5813 className = 'leaflet-disabled'; 5814 5815 removeClass(this._zoomInButton, className); 5816 removeClass(this._zoomOutButton, className); 5817 this._zoomInButton.setAttribute('aria-disabled', 'false'); 5818 this._zoomOutButton.setAttribute('aria-disabled', 'false'); 5819 5820 if (this._disabled || map._zoom === map.getMinZoom()) { 5821 addClass(this._zoomOutButton, className); 5822 this._zoomOutButton.setAttribute('aria-disabled', 'true'); 5823 } 5824 if (this._disabled || map._zoom === map.getMaxZoom()) { 5825 addClass(this._zoomInButton, className); 5826 this._zoomInButton.setAttribute('aria-disabled', 'true'); 5827 } 5828 } 5829 }); 5830 5831 // @namespace Map 5832 // @section Control options 5833 // @option zoomControl: Boolean = true 5834 // Whether a [zoom control](#control-zoom) is added to the map by default. 5835 Map.mergeOptions({ 5836 zoomControl: true 5837 }); 5838 5839 Map.addInitHook(function () { 5840 if (this.options.zoomControl) { 5841 // @section Controls 5842 // @property zoomControl: Control.Zoom 5843 // The default zoom control (only available if the 5844 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5845 this.zoomControl = new Zoom(); 5846 this.addControl(this.zoomControl); 5847 } 5848 }); 5849 5850 // @namespace Control.Zoom 5851 // @factory L.control.zoom(options: Control.Zoom options) 5852 // Creates a zoom control 5853 var zoom = function (options) { 5854 return new Zoom(options); 5855 }; 5856 5857 /* 5858 * @class Control.Scale 5859 * @aka L.Control.Scale 5860 * @inherits Control 5861 * 5862 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`. 5863 * 5864 * @example 5865 * 5866 * ```js 5867 * L.control.scale().addTo(map); 5868 * ``` 5869 */ 5870 5871 var Scale = Control.extend({ 5872 // @section 5873 // @aka Control.Scale options 5874 options: { 5875 position: 'bottomleft', 5876 5877 // @option maxWidth: Number = 100 5878 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5879 maxWidth: 100, 5880 5881 // @option metric: Boolean = True 5882 // Whether to show the metric scale line (m/km). 5883 metric: true, 5884 5885 // @option imperial: Boolean = True 5886 // Whether to show the imperial scale line (mi/ft). 5887 imperial: true 5888 5889 // @option updateWhenIdle: Boolean = false 5890 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5891 }, 5892 5893 onAdd: function (map) { 5894 var className = 'leaflet-control-scale', 5895 container = create$1('div', className), 5896 options = this.options; 5897 5898 this._addScales(options, className + '-line', container); 5899 5900 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5901 map.whenReady(this._update, this); 5902 5903 return container; 5904 }, 5905 5906 onRemove: function (map) { 5907 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5908 }, 5909 5910 _addScales: function (options, className, container) { 5911 if (options.metric) { 5912 this._mScale = create$1('div', className, container); 5913 } 5914 if (options.imperial) { 5915 this._iScale = create$1('div', className, container); 5916 } 5917 }, 5918 5919 _update: function () { 5920 var map = this._map, 5921 y = map.getSize().y / 2; 5922 5923 var maxMeters = map.distance( 5924 map.containerPointToLatLng([0, y]), 5925 map.containerPointToLatLng([this.options.maxWidth, y])); 5926 5927 this._updateScales(maxMeters); 5928 }, 5929 5930 _updateScales: function (maxMeters) { 5931 if (this.options.metric && maxMeters) { 5932 this._updateMetric(maxMeters); 5933 } 5934 if (this.options.imperial && maxMeters) { 5935 this._updateImperial(maxMeters); 5936 } 5937 }, 5938 5939 _updateMetric: function (maxMeters) { 5940 var meters = this._getRoundNum(maxMeters), 5941 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5942 5943 this._updateScale(this._mScale, label, meters / maxMeters); 5944 }, 5945 5946 _updateImperial: function (maxMeters) { 5947 var maxFeet = maxMeters * 3.2808399, 5948 maxMiles, miles, feet; 5949 5950 if (maxFeet > 5280) { 5951 maxMiles = maxFeet / 5280; 5952 miles = this._getRoundNum(maxMiles); 5953 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5954 5955 } else { 5956 feet = this._getRoundNum(maxFeet); 5957 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5958 } 5959 }, 5960 5961 _updateScale: function (scale, text, ratio) { 5962 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5963 scale.innerHTML = text; 5964 }, 5965 5966 _getRoundNum: function (num) { 5967 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5968 d = num / pow10; 5969 5970 d = d >= 10 ? 10 : 5971 d >= 5 ? 5 : 5972 d >= 3 ? 3 : 5973 d >= 2 ? 2 : 1; 5974 5975 return pow10 * d; 5976 } 5977 }); 5978 5979 5980 // @factory L.control.scale(options?: Control.Scale options) 5981 // Creates an scale control with the given options. 5982 var scale = function (options) { 5983 return new Scale(options); 5984 }; 5985 5986 var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>'; 5987 5988 5989 /* 5990 * @class Control.Attribution 5991 * @aka L.Control.Attribution 5992 * @inherits Control 5993 * 5994 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control. 5995 */ 5996 5997 var Attribution = Control.extend({ 5998 // @section 5999 // @aka Control.Attribution options 6000 options: { 6001 position: 'bottomright', 6002 6003 // @option prefix: String|false = 'Leaflet' 6004 // The HTML text shown before the attributions. Pass `false` to disable. 6005 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>' 6006 }, 6007 6008 initialize: function (options) { 6009 setOptions(this, options); 6010 6011 this._attributions = {}; 6012 }, 6013 6014 onAdd: function (map) { 6015 map.attributionControl = this; 6016 this._container = create$1('div', 'leaflet-control-attribution'); 6017 disableClickPropagation(this._container); 6018 6019 // TODO ugly, refactor 6020 for (var i in map._layers) { 6021 if (map._layers[i].getAttribution) { 6022 this.addAttribution(map._layers[i].getAttribution()); 6023 } 6024 } 6025 6026 this._update(); 6027 6028 map.on('layeradd', this._addAttribution, this); 6029 6030 return this._container; 6031 }, 6032 6033 onRemove: function (map) { 6034 map.off('layeradd', this._addAttribution, this); 6035 }, 6036 6037 _addAttribution: function (ev) { 6038 if (ev.layer.getAttribution) { 6039 this.addAttribution(ev.layer.getAttribution()); 6040 ev.layer.once('remove', function () { 6041 this.removeAttribution(ev.layer.getAttribution()); 6042 }, this); 6043 } 6044 }, 6045 6046 // @method setPrefix(prefix: String|false): this 6047 // The HTML text shown before the attributions. Pass `false` to disable. 6048 setPrefix: function (prefix) { 6049 this.options.prefix = prefix; 6050 this._update(); 6051 return this; 6052 }, 6053 6054 // @method addAttribution(text: String): this 6055 // Adds an attribution text (e.g. `'© OpenStreetMap contributors'`). 6056 addAttribution: function (text) { 6057 if (!text) { return this; } 6058 6059 if (!this._attributions[text]) { 6060 this._attributions[text] = 0; 6061 } 6062 this._attributions[text]++; 6063 6064 this._update(); 6065 6066 return this; 6067 }, 6068 6069 // @method removeAttribution(text: String): this 6070 // Removes an attribution text. 6071 removeAttribution: function (text) { 6072 if (!text) { return this; } 6073 6074 if (this._attributions[text]) { 6075 this._attributions[text]--; 6076 this._update(); 6077 } 6078 6079 return this; 6080 }, 6081 6082 _update: function () { 6083 if (!this._map) { return; } 6084 6085 var attribs = []; 6086 6087 for (var i in this._attributions) { 6088 if (this._attributions[i]) { 6089 attribs.push(i); 6090 } 6091 } 6092 6093 var prefixAndAttribs = []; 6094 6095 if (this.options.prefix) { 6096 prefixAndAttribs.push(this.options.prefix); 6097 } 6098 if (attribs.length) { 6099 prefixAndAttribs.push(attribs.join(', ')); 6100 } 6101 6102 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> '); 6103 } 6104 }); 6105 6106 // @namespace Map 6107 // @section Control options 6108 // @option attributionControl: Boolean = true 6109 // Whether a [attribution control](#control-attribution) is added to the map by default. 6110 Map.mergeOptions({ 6111 attributionControl: true 6112 }); 6113 6114 Map.addInitHook(function () { 6115 if (this.options.attributionControl) { 6116 new Attribution().addTo(this); 6117 } 6118 }); 6119 6120 // @namespace Control.Attribution 6121 // @factory L.control.attribution(options: Control.Attribution options) 6122 // Creates an attribution control. 6123 var attribution = function (options) { 6124 return new Attribution(options); 6125 }; 6126 6127 Control.Layers = Layers; 6128 Control.Zoom = Zoom; 6129 Control.Scale = Scale; 6130 Control.Attribution = Attribution; 6131 6132 control.layers = layers; 6133 control.zoom = zoom; 6134 control.scale = scale; 6135 control.attribution = attribution; 6136 6137 /* 6138 L.Handler is a base class for handler classes that are used internally to inject 6139 interaction features like dragging to classes like Map and Marker. 6140 */ 6141 6142 // @class Handler 6143 // @aka L.Handler 6144 // Abstract class for map interaction handlers 6145 6146 var Handler = Class.extend({ 6147 initialize: function (map) { 6148 this._map = map; 6149 }, 6150 6151 // @method enable(): this 6152 // Enables the handler 6153 enable: function () { 6154 if (this._enabled) { return this; } 6155 6156 this._enabled = true; 6157 this.addHooks(); 6158 return this; 6159 }, 6160 6161 // @method disable(): this 6162 // Disables the handler 6163 disable: function () { 6164 if (!this._enabled) { return this; } 6165 6166 this._enabled = false; 6167 this.removeHooks(); 6168 return this; 6169 }, 6170 6171 // @method enabled(): Boolean 6172 // Returns `true` if the handler is enabled 6173 enabled: function () { 6174 return !!this._enabled; 6175 } 6176 6177 // @section Extension methods 6178 // Classes inheriting from `Handler` must implement the two following methods: 6179 // @method addHooks() 6180 // Called when the handler is enabled, should add event hooks. 6181 // @method removeHooks() 6182 // Called when the handler is disabled, should remove the event hooks added previously. 6183 }); 6184 6185 // @section There is static function which can be called without instantiating L.Handler: 6186 // @function addTo(map: Map, name: String): this 6187 // Adds a new Handler to the given map with the given name. 6188 Handler.addTo = function (map, name) { 6189 map.addHandler(name, this); 6190 return this; 6191 }; 6192 6193 var Mixin = {Events: Events}; 6194 6195 /* 6196 * @class Draggable 6197 * @aka L.Draggable 6198 * @inherits Evented 6199 * 6200 * A class for making DOM elements draggable (including touch support). 6201 * Used internally for map and marker dragging. Only works for elements 6202 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 6203 * 6204 * @example 6205 * ```js 6206 * var draggable = new L.Draggable(elementToDrag); 6207 * draggable.enable(); 6208 * ``` 6209 */ 6210 6211 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown'; 6212 6213 var Draggable = Evented.extend({ 6214 6215 options: { 6216 // @section 6217 // @aka Draggable options 6218 // @option clickTolerance: Number = 3 6219 // The max number of pixels a user can shift the mouse pointer during a click 6220 // for it to be considered a valid click (as opposed to a mouse drag). 6221 clickTolerance: 3 6222 }, 6223 6224 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 6225 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 6226 initialize: function (element, dragStartTarget, preventOutline, options) { 6227 setOptions(this, options); 6228 6229 this._element = element; 6230 this._dragStartTarget = dragStartTarget || element; 6231 this._preventOutline = preventOutline; 6232 }, 6233 6234 // @method enable() 6235 // Enables the dragging ability 6236 enable: function () { 6237 if (this._enabled) { return; } 6238 6239 on(this._dragStartTarget, START, this._onDown, this); 6240 6241 this._enabled = true; 6242 }, 6243 6244 // @method disable() 6245 // Disables the dragging ability 6246 disable: function () { 6247 if (!this._enabled) { return; } 6248 6249 // If we're currently dragging this draggable, 6250 // disabling it counts as first ending the drag. 6251 if (Draggable._dragging === this) { 6252 this.finishDrag(true); 6253 } 6254 6255 off(this._dragStartTarget, START, this._onDown, this); 6256 6257 this._enabled = false; 6258 this._moved = false; 6259 }, 6260 6261 _onDown: function (e) { 6262 // Ignore the event if disabled; this happens in IE11 6263 // under some circumstances, see #3666. 6264 if (!this._enabled) { return; } 6265 6266 this._moved = false; 6267 6268 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 6269 6270 if (e.touches && e.touches.length !== 1) { 6271 // Finish dragging to avoid conflict with touchZoom 6272 if (Draggable._dragging === this) { 6273 this.finishDrag(); 6274 } 6275 return; 6276 } 6277 6278 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 6279 Draggable._dragging = this; // Prevent dragging multiple objects at once. 6280 6281 if (this._preventOutline) { 6282 preventOutline(this._element); 6283 } 6284 6285 disableImageDrag(); 6286 disableTextSelection(); 6287 6288 if (this._moving) { return; } 6289 6290 // @event down: Event 6291 // Fired when a drag is about to start. 6292 this.fire('down'); 6293 6294 var first = e.touches ? e.touches[0] : e, 6295 sizedParent = getSizedParentNode(this._element); 6296 6297 this._startPoint = new Point(first.clientX, first.clientY); 6298 this._startPos = getPosition(this._element); 6299 6300 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 6301 this._parentScale = getScale(sizedParent); 6302 6303 var mouseevent = e.type === 'mousedown'; 6304 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this); 6305 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this); 6306 }, 6307 6308 _onMove: function (e) { 6309 // Ignore the event if disabled; this happens in IE11 6310 // under some circumstances, see #3666. 6311 if (!this._enabled) { return; } 6312 6313 if (e.touches && e.touches.length > 1) { 6314 this._moved = true; 6315 return; 6316 } 6317 6318 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 6319 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 6320 6321 if (!offset.x && !offset.y) { return; } 6322 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 6323 6324 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 6325 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 6326 // and we can use the cached value for the scale. 6327 offset.x /= this._parentScale.x; 6328 offset.y /= this._parentScale.y; 6329 6330 preventDefault(e); 6331 6332 if (!this._moved) { 6333 // @event dragstart: Event 6334 // Fired when a drag starts 6335 this.fire('dragstart'); 6336 6337 this._moved = true; 6338 6339 addClass(document.body, 'leaflet-dragging'); 6340 6341 this._lastTarget = e.target || e.srcElement; 6342 // IE and Edge do not give the <use> element, so fetch it 6343 // if necessary 6344 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) { 6345 this._lastTarget = this._lastTarget.correspondingUseElement; 6346 } 6347 addClass(this._lastTarget, 'leaflet-drag-target'); 6348 } 6349 6350 this._newPos = this._startPos.add(offset); 6351 this._moving = true; 6352 6353 this._lastEvent = e; 6354 this._updatePosition(); 6355 }, 6356 6357 _updatePosition: function () { 6358 var e = {originalEvent: this._lastEvent}; 6359 6360 // @event predrag: Event 6361 // Fired continuously during dragging *before* each corresponding 6362 // update of the element's position. 6363 this.fire('predrag', e); 6364 setPosition(this._element, this._newPos); 6365 6366 // @event drag: Event 6367 // Fired continuously during dragging. 6368 this.fire('drag', e); 6369 }, 6370 6371 _onUp: function () { 6372 // Ignore the event if disabled; this happens in IE11 6373 // under some circumstances, see #3666. 6374 if (!this._enabled) { return; } 6375 this.finishDrag(); 6376 }, 6377 6378 finishDrag: function (noInertia) { 6379 removeClass(document.body, 'leaflet-dragging'); 6380 6381 if (this._lastTarget) { 6382 removeClass(this._lastTarget, 'leaflet-drag-target'); 6383 this._lastTarget = null; 6384 } 6385 6386 off(document, 'mousemove touchmove', this._onMove, this); 6387 off(document, 'mouseup touchend touchcancel', this._onUp, this); 6388 6389 enableImageDrag(); 6390 enableTextSelection(); 6391 6392 var fireDragend = this._moved && this._moving; 6393 6394 this._moving = false; 6395 Draggable._dragging = false; 6396 6397 if (fireDragend) { 6398 // @event dragend: DragEndEvent 6399 // Fired when the drag ends. 6400 this.fire('dragend', { 6401 noInertia: noInertia, 6402 distance: this._newPos.distanceTo(this._startPos) 6403 }); 6404 } 6405 } 6406 6407 }); 6408 6409 /* 6410 * @namespace PolyUtil 6411 * Various utility functions for polygon geometries. 6412 */ 6413 6414 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6415 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). 6416 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6417 * performance. Note that polygon points needs different algorithm for clipping 6418 * than polyline, so there's a separate method for it. 6419 */ 6420 function clipPolygon(points, bounds, round) { 6421 var clippedPoints, 6422 edges = [1, 4, 2, 8], 6423 i, j, k, 6424 a, b, 6425 len, edge, p; 6426 6427 for (i = 0, len = points.length; i < len; i++) { 6428 points[i]._code = _getBitCode(points[i], bounds); 6429 } 6430 6431 // for each edge (left, bottom, right, top) 6432 for (k = 0; k < 4; k++) { 6433 edge = edges[k]; 6434 clippedPoints = []; 6435 6436 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6437 a = points[i]; 6438 b = points[j]; 6439 6440 // if a is inside the clip window 6441 if (!(a._code & edge)) { 6442 // if b is outside the clip window (a->b goes out of screen) 6443 if (b._code & edge) { 6444 p = _getEdgeIntersection(b, a, edge, bounds, round); 6445 p._code = _getBitCode(p, bounds); 6446 clippedPoints.push(p); 6447 } 6448 clippedPoints.push(a); 6449 6450 // else if b is inside the clip window (a->b enters the screen) 6451 } else if (!(b._code & edge)) { 6452 p = _getEdgeIntersection(b, a, edge, bounds, round); 6453 p._code = _getBitCode(p, bounds); 6454 clippedPoints.push(p); 6455 } 6456 } 6457 points = clippedPoints; 6458 } 6459 6460 return points; 6461 } 6462 6463 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng 6464 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon. 6465 */ 6466 function polygonCenter(latlngs, crs) { 6467 var i, j, p1, p2, f, area, x, y, center; 6468 6469 if (!latlngs || latlngs.length === 0) { 6470 throw new Error('latlngs not passed'); 6471 } 6472 6473 if (!isFlat(latlngs)) { 6474 console.warn('latlngs are not flat! Only the first ring will be used'); 6475 latlngs = latlngs[0]; 6476 } 6477 6478 var centroidLatLng = toLatLng([0, 0]); 6479 6480 var bounds = toLatLngBounds(latlngs); 6481 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 6482 // tests showed that below 1700 rounding errors are happening 6483 if (areaBounds < 1700) { 6484 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors 6485 centroidLatLng = centroid(latlngs); 6486 } 6487 6488 var len = latlngs.length; 6489 var points = []; 6490 for (i = 0; i < len; i++) { 6491 var latlng = toLatLng(latlngs[i]); 6492 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 6493 } 6494 6495 area = x = y = 0; 6496 6497 // polygon centroid algorithm; 6498 for (i = 0, j = len - 1; i < len; j = i++) { 6499 p1 = points[i]; 6500 p2 = points[j]; 6501 6502 f = p1.y * p2.x - p2.y * p1.x; 6503 x += (p1.x + p2.x) * f; 6504 y += (p1.y + p2.y) * f; 6505 area += f * 3; 6506 } 6507 6508 if (area === 0) { 6509 // Polygon is so small that all points are on same pixel. 6510 center = points[0]; 6511 } else { 6512 center = [x / area, y / area]; 6513 } 6514 6515 var latlngCenter = crs.unproject(toPoint(center)); 6516 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 6517 } 6518 6519 /* @function centroid(latlngs: LatLng[]): LatLng 6520 * Returns the 'center of mass' of the passed LatLngs. 6521 */ 6522 function centroid(coords) { 6523 var latSum = 0; 6524 var lngSum = 0; 6525 var len = 0; 6526 for (var i = 0; i < coords.length; i++) { 6527 var latlng = toLatLng(coords[i]); 6528 latSum += latlng.lat; 6529 lngSum += latlng.lng; 6530 len++; 6531 } 6532 return toLatLng([latSum / len, lngSum / len]); 6533 } 6534 6535 var PolyUtil = { 6536 __proto__: null, 6537 clipPolygon: clipPolygon, 6538 polygonCenter: polygonCenter, 6539 centroid: centroid 6540 }; 6541 6542 /* 6543 * @namespace LineUtil 6544 * 6545 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 6546 */ 6547 6548 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 6549 // Improves rendering performance dramatically by lessening the number of points to draw. 6550 6551 // @function simplify(points: Point[], tolerance: Number): Point[] 6552 // Dramatically reduces the number of points in a polyline while retaining 6553 // its shape and returns a new array of simplified points, using the 6554 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm). 6555 // Used for a huge performance boost when processing/displaying Leaflet polylines for 6556 // each zoom level and also reducing visual noise. tolerance affects the amount of 6557 // simplification (lesser value means higher quality but slower and with more points). 6558 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/). 6559 function simplify(points, tolerance) { 6560 if (!tolerance || !points.length) { 6561 return points.slice(); 6562 } 6563 6564 var sqTolerance = tolerance * tolerance; 6565 6566 // stage 1: vertex reduction 6567 points = _reducePoints(points, sqTolerance); 6568 6569 // stage 2: Douglas-Peucker simplification 6570 points = _simplifyDP(points, sqTolerance); 6571 6572 return points; 6573 } 6574 6575 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 6576 // Returns the distance between point `p` and segment `p1` to `p2`. 6577 function pointToSegmentDistance(p, p1, p2) { 6578 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 6579 } 6580 6581 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6582 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 6583 function closestPointOnSegment(p, p1, p2) { 6584 return _sqClosestPointOnSegment(p, p1, p2); 6585 } 6586 6587 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm 6588 function _simplifyDP(points, sqTolerance) { 6589 6590 var len = points.length, 6591 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6592 markers = new ArrayConstructor(len); 6593 6594 markers[0] = markers[len - 1] = 1; 6595 6596 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6597 6598 var i, 6599 newPoints = []; 6600 6601 for (i = 0; i < len; i++) { 6602 if (markers[i]) { 6603 newPoints.push(points[i]); 6604 } 6605 } 6606 6607 return newPoints; 6608 } 6609 6610 function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6611 6612 var maxSqDist = 0, 6613 index, i, sqDist; 6614 6615 for (i = first + 1; i <= last - 1; i++) { 6616 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6617 6618 if (sqDist > maxSqDist) { 6619 index = i; 6620 maxSqDist = sqDist; 6621 } 6622 } 6623 6624 if (maxSqDist > sqTolerance) { 6625 markers[index] = 1; 6626 6627 _simplifyDPStep(points, markers, sqTolerance, first, index); 6628 _simplifyDPStep(points, markers, sqTolerance, index, last); 6629 } 6630 } 6631 6632 // reduce points that are too close to each other to a single point 6633 function _reducePoints(points, sqTolerance) { 6634 var reducedPoints = [points[0]]; 6635 6636 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6637 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6638 reducedPoints.push(points[i]); 6639 prev = i; 6640 } 6641 } 6642 if (prev < len - 1) { 6643 reducedPoints.push(points[len - 1]); 6644 } 6645 return reducedPoints; 6646 } 6647 6648 var _lastCode; 6649 6650 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean 6651 // Clips the segment a to b by rectangular bounds with the 6652 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6653 // (modifying the segment points directly!). Used by Leaflet to only show polyline 6654 // points that are on the screen or near, increasing performance. 6655 function clipSegment(a, b, bounds, useLastCode, round) { 6656 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6657 codeB = _getBitCode(b, bounds), 6658 6659 codeOut, p, newCode; 6660 6661 // save 2nd code to avoid calculating it on the next segment 6662 _lastCode = codeB; 6663 6664 while (true) { 6665 // if a,b is inside the clip window (trivial accept) 6666 if (!(codeA | codeB)) { 6667 return [a, b]; 6668 } 6669 6670 // if a,b is outside the clip window (trivial reject) 6671 if (codeA & codeB) { 6672 return false; 6673 } 6674 6675 // other cases 6676 codeOut = codeA || codeB; 6677 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6678 newCode = _getBitCode(p, bounds); 6679 6680 if (codeOut === codeA) { 6681 a = p; 6682 codeA = newCode; 6683 } else { 6684 b = p; 6685 codeB = newCode; 6686 } 6687 } 6688 } 6689 6690 function _getEdgeIntersection(a, b, code, bounds, round) { 6691 var dx = b.x - a.x, 6692 dy = b.y - a.y, 6693 min = bounds.min, 6694 max = bounds.max, 6695 x, y; 6696 6697 if (code & 8) { // top 6698 x = a.x + dx * (max.y - a.y) / dy; 6699 y = max.y; 6700 6701 } else if (code & 4) { // bottom 6702 x = a.x + dx * (min.y - a.y) / dy; 6703 y = min.y; 6704 6705 } else if (code & 2) { // right 6706 x = max.x; 6707 y = a.y + dy * (max.x - a.x) / dx; 6708 6709 } else if (code & 1) { // left 6710 x = min.x; 6711 y = a.y + dy * (min.x - a.x) / dx; 6712 } 6713 6714 return new Point(x, y, round); 6715 } 6716 6717 function _getBitCode(p, bounds) { 6718 var code = 0; 6719 6720 if (p.x < bounds.min.x) { // left 6721 code |= 1; 6722 } else if (p.x > bounds.max.x) { // right 6723 code |= 2; 6724 } 6725 6726 if (p.y < bounds.min.y) { // bottom 6727 code |= 4; 6728 } else if (p.y > bounds.max.y) { // top 6729 code |= 8; 6730 } 6731 6732 return code; 6733 } 6734 6735 // square distance (to avoid unnecessary Math.sqrt calls) 6736 function _sqDist(p1, p2) { 6737 var dx = p2.x - p1.x, 6738 dy = p2.y - p1.y; 6739 return dx * dx + dy * dy; 6740 } 6741 6742 // return closest point on segment or distance to that point 6743 function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6744 var x = p1.x, 6745 y = p1.y, 6746 dx = p2.x - x, 6747 dy = p2.y - y, 6748 dot = dx * dx + dy * dy, 6749 t; 6750 6751 if (dot > 0) { 6752 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6753 6754 if (t > 1) { 6755 x = p2.x; 6756 y = p2.y; 6757 } else if (t > 0) { 6758 x += dx * t; 6759 y += dy * t; 6760 } 6761 } 6762 6763 dx = p.x - x; 6764 dy = p.y - y; 6765 6766 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6767 } 6768 6769 6770 // @function isFlat(latlngs: LatLng[]): Boolean 6771 // Returns true if `latlngs` is a flat array, false is nested. 6772 function isFlat(latlngs) { 6773 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6774 } 6775 6776 function _flat(latlngs) { 6777 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6778 return isFlat(latlngs); 6779 } 6780 6781 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng 6782 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline. 6783 */ 6784 function polylineCenter(latlngs, crs) { 6785 var i, halfDist, segDist, dist, p1, p2, ratio, center; 6786 6787 if (!latlngs || latlngs.length === 0) { 6788 throw new Error('latlngs not passed'); 6789 } 6790 6791 if (!isFlat(latlngs)) { 6792 console.warn('latlngs are not flat! Only the first ring will be used'); 6793 latlngs = latlngs[0]; 6794 } 6795 6796 var centroidLatLng = toLatLng([0, 0]); 6797 6798 var bounds = toLatLngBounds(latlngs); 6799 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 6800 // tests showed that below 1700 rounding errors are happening 6801 if (areaBounds < 1700) { 6802 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors 6803 centroidLatLng = centroid(latlngs); 6804 } 6805 6806 var len = latlngs.length; 6807 var points = []; 6808 for (i = 0; i < len; i++) { 6809 var latlng = toLatLng(latlngs[i]); 6810 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 6811 } 6812 6813 for (i = 0, halfDist = 0; i < len - 1; i++) { 6814 halfDist += points[i].distanceTo(points[i + 1]) / 2; 6815 } 6816 6817 // The line is so small in the current view that all points are on the same pixel. 6818 if (halfDist === 0) { 6819 center = points[0]; 6820 } else { 6821 for (i = 0, dist = 0; i < len - 1; i++) { 6822 p1 = points[i]; 6823 p2 = points[i + 1]; 6824 segDist = p1.distanceTo(p2); 6825 dist += segDist; 6826 6827 if (dist > halfDist) { 6828 ratio = (dist - halfDist) / segDist; 6829 center = [ 6830 p2.x - ratio * (p2.x - p1.x), 6831 p2.y - ratio * (p2.y - p1.y) 6832 ]; 6833 break; 6834 } 6835 } 6836 } 6837 6838 var latlngCenter = crs.unproject(toPoint(center)); 6839 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 6840 } 6841 6842 var LineUtil = { 6843 __proto__: null, 6844 simplify: simplify, 6845 pointToSegmentDistance: pointToSegmentDistance, 6846 closestPointOnSegment: closestPointOnSegment, 6847 clipSegment: clipSegment, 6848 _getEdgeIntersection: _getEdgeIntersection, 6849 _getBitCode: _getBitCode, 6850 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6851 isFlat: isFlat, 6852 _flat: _flat, 6853 polylineCenter: polylineCenter 6854 }; 6855 6856 /* 6857 * @namespace Projection 6858 * @section 6859 * Leaflet comes with a set of already defined Projections out of the box: 6860 * 6861 * @projection L.Projection.LonLat 6862 * 6863 * Equirectangular, or Plate Carree projection â the most simple projection, 6864 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6865 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6866 * `EPSG:4326` and `Simple` CRS. 6867 */ 6868 6869 var LonLat = { 6870 project: function (latlng) { 6871 return new Point(latlng.lng, latlng.lat); 6872 }, 6873 6874 unproject: function (point) { 6875 return new LatLng(point.y, point.x); 6876 }, 6877 6878 bounds: new Bounds([-180, -90], [180, 90]) 6879 }; 6880 6881 /* 6882 * @namespace Projection 6883 * @projection L.Projection.Mercator 6884 * 6885 * Elliptical Mercator projection â more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6886 */ 6887 6888 var Mercator = { 6889 R: 6378137, 6890 R_MINOR: 6356752.314245179, 6891 6892 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6893 6894 project: function (latlng) { 6895 var d = Math.PI / 180, 6896 r = this.R, 6897 y = latlng.lat * d, 6898 tmp = this.R_MINOR / r, 6899 e = Math.sqrt(1 - tmp * tmp), 6900 con = e * Math.sin(y); 6901 6902 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6903 y = -r * Math.log(Math.max(ts, 1E-10)); 6904 6905 return new Point(latlng.lng * d * r, y); 6906 }, 6907 6908 unproject: function (point) { 6909 var d = 180 / Math.PI, 6910 r = this.R, 6911 tmp = this.R_MINOR / r, 6912 e = Math.sqrt(1 - tmp * tmp), 6913 ts = Math.exp(-point.y / r), 6914 phi = Math.PI / 2 - 2 * Math.atan(ts); 6915 6916 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6917 con = e * Math.sin(phi); 6918 con = Math.pow((1 - con) / (1 + con), e / 2); 6919 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6920 phi += dphi; 6921 } 6922 6923 return new LatLng(phi * d, point.x * d / r); 6924 } 6925 }; 6926 6927 /* 6928 * @class Projection 6929 6930 * An object with methods for projecting geographical coordinates of the world onto 6931 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection). 6932 6933 * @property bounds: Bounds 6934 * The bounds (specified in CRS units) where the projection is valid 6935 6936 * @method project(latlng: LatLng): Point 6937 * Projects geographical coordinates into a 2D point. 6938 * Only accepts actual `L.LatLng` instances, not arrays. 6939 6940 * @method unproject(point: Point): LatLng 6941 * The inverse of `project`. Projects a 2D point into a geographical location. 6942 * Only accepts actual `L.Point` instances, not arrays. 6943 6944 * Note that the projection instances do not inherit from Leaflet's `Class` object, 6945 * and can't be instantiated. Also, new classes can't inherit from them, 6946 * and methods can't be added to them with the `include` function. 6947 6948 */ 6949 6950 var index = { 6951 __proto__: null, 6952 LonLat: LonLat, 6953 Mercator: Mercator, 6954 SphericalMercator: SphericalMercator 6955 }; 6956 6957 /* 6958 * @namespace CRS 6959 * @crs L.CRS.EPSG3395 6960 * 6961 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6962 */ 6963 var EPSG3395 = extend({}, Earth, { 6964 code: 'EPSG:3395', 6965 projection: Mercator, 6966 6967 transformation: (function () { 6968 var scale = 0.5 / (Math.PI * Mercator.R); 6969 return toTransformation(scale, 0.5, -scale, 0.5); 6970 }()) 6971 }); 6972 6973 /* 6974 * @namespace CRS 6975 * @crs L.CRS.EPSG4326 6976 * 6977 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6978 * 6979 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6980 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6981 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6982 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6983 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6984 */ 6985 6986 var EPSG4326 = extend({}, Earth, { 6987 code: 'EPSG:4326', 6988 projection: LonLat, 6989 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6990 }); 6991 6992 /* 6993 * @namespace CRS 6994 * @crs L.CRS.Simple 6995 * 6996 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6997 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6998 * axis should still be inverted (going from bottom to top). `distance()` returns 6999 * simple euclidean distance. 7000 */ 7001 7002 var Simple = extend({}, CRS, { 7003 projection: LonLat, 7004 transformation: toTransformation(1, 0, -1, 0), 7005 7006 scale: function (zoom) { 7007 return Math.pow(2, zoom); 7008 }, 7009 7010 zoom: function (scale) { 7011 return Math.log(scale) / Math.LN2; 7012 }, 7013 7014 distance: function (latlng1, latlng2) { 7015 var dx = latlng2.lng - latlng1.lng, 7016 dy = latlng2.lat - latlng1.lat; 7017 7018 return Math.sqrt(dx * dx + dy * dy); 7019 }, 7020 7021 infinite: true 7022 }); 7023 7024 CRS.Earth = Earth; 7025 CRS.EPSG3395 = EPSG3395; 7026 CRS.EPSG3857 = EPSG3857; 7027 CRS.EPSG900913 = EPSG900913; 7028 CRS.EPSG4326 = EPSG4326; 7029 CRS.Simple = Simple; 7030 7031 /* 7032 * @class Layer 7033 * @inherits Evented 7034 * @aka L.Layer 7035 * @aka ILayer 7036 * 7037 * A set of methods from the Layer base class that all Leaflet layers use. 7038 * Inherits all methods, options and events from `L.Evented`. 7039 * 7040 * @example 7041 * 7042 * ```js 7043 * var layer = L.marker(latlng).addTo(map); 7044 * layer.addTo(map); 7045 * layer.remove(); 7046 * ``` 7047 * 7048 * @event add: Event 7049 * Fired after the layer is added to a map 7050 * 7051 * @event remove: Event 7052 * Fired after the layer is removed from a map 7053 */ 7054 7055 7056 var Layer = Evented.extend({ 7057 7058 // Classes extending `L.Layer` will inherit the following options: 7059 options: { 7060 // @option pane: String = 'overlayPane' 7061 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default. 7062 pane: 'overlayPane', 7063 7064 // @option attribution: String = null 7065 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers. 7066 attribution: null, 7067 7068 bubblingMouseEvents: true 7069 }, 7070 7071 /* @section 7072 * Classes extending `L.Layer` will inherit the following methods: 7073 * 7074 * @method addTo(map: Map|LayerGroup): this 7075 * Adds the layer to the given map or layer group. 7076 */ 7077 addTo: function (map) { 7078 map.addLayer(this); 7079 return this; 7080 }, 7081 7082 // @method remove: this 7083 // Removes the layer from the map it is currently active on. 7084 remove: function () { 7085 return this.removeFrom(this._map || this._mapToAdd); 7086 }, 7087 7088 // @method removeFrom(map: Map): this 7089 // Removes the layer from the given map 7090 // 7091 // @alternative 7092 // @method removeFrom(group: LayerGroup): this 7093 // Removes the layer from the given `LayerGroup` 7094 removeFrom: function (obj) { 7095 if (obj) { 7096 obj.removeLayer(this); 7097 } 7098 return this; 7099 }, 7100 7101 // @method getPane(name? : String): HTMLElement 7102 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 7103 getPane: function (name) { 7104 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 7105 }, 7106 7107 addInteractiveTarget: function (targetEl) { 7108 this._map._targets[stamp(targetEl)] = this; 7109 return this; 7110 }, 7111 7112 removeInteractiveTarget: function (targetEl) { 7113 delete this._map._targets[stamp(targetEl)]; 7114 return this; 7115 }, 7116 7117 // @method getAttribution: String 7118 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 7119 getAttribution: function () { 7120 return this.options.attribution; 7121 }, 7122 7123 _layerAdd: function (e) { 7124 var map = e.target; 7125 7126 // check in case layer gets added and then removed before the map is ready 7127 if (!map.hasLayer(this)) { return; } 7128 7129 this._map = map; 7130 this._zoomAnimated = map._zoomAnimated; 7131 7132 if (this.getEvents) { 7133 var events = this.getEvents(); 7134 map.on(events, this); 7135 this.once('remove', function () { 7136 map.off(events, this); 7137 }, this); 7138 } 7139 7140 this.onAdd(map); 7141 7142 this.fire('add'); 7143 map.fire('layeradd', {layer: this}); 7144 } 7145 }); 7146 7147 /* @section Extension methods 7148 * @uninheritable 7149 * 7150 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 7151 * 7152 * @method onAdd(map: Map): this 7153 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer). 7154 * 7155 * @method onRemove(map: Map): this 7156 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer). 7157 * 7158 * @method getEvents(): Object 7159 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer. 7160 * 7161 * @method getAttribution(): String 7162 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 7163 * 7164 * @method beforeAdd(map: Map): this 7165 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only. 7166 */ 7167 7168 7169 /* @namespace Map 7170 * @section Layer events 7171 * 7172 * @event layeradd: LayerEvent 7173 * Fired when a new layer is added to the map. 7174 * 7175 * @event layerremove: LayerEvent 7176 * Fired when some layer is removed from the map 7177 * 7178 * @section Methods for Layers and Controls 7179 */ 7180 Map.include({ 7181 // @method addLayer(layer: Layer): this 7182 // Adds the given layer to the map 7183 addLayer: function (layer) { 7184 if (!layer._layerAdd) { 7185 throw new Error('The provided object is not a Layer.'); 7186 } 7187 7188 var id = stamp(layer); 7189 if (this._layers[id]) { return this; } 7190 this._layers[id] = layer; 7191 7192 layer._mapToAdd = this; 7193 7194 if (layer.beforeAdd) { 7195 layer.beforeAdd(this); 7196 } 7197 7198 this.whenReady(layer._layerAdd, layer); 7199 7200 return this; 7201 }, 7202 7203 // @method removeLayer(layer: Layer): this 7204 // Removes the given layer from the map. 7205 removeLayer: function (layer) { 7206 var id = stamp(layer); 7207 7208 if (!this._layers[id]) { return this; } 7209 7210 if (this._loaded) { 7211 layer.onRemove(this); 7212 } 7213 7214 delete this._layers[id]; 7215 7216 if (this._loaded) { 7217 this.fire('layerremove', {layer: layer}); 7218 layer.fire('remove'); 7219 } 7220 7221 layer._map = layer._mapToAdd = null; 7222 7223 return this; 7224 }, 7225 7226 // @method hasLayer(layer: Layer): Boolean 7227 // Returns `true` if the given layer is currently added to the map 7228 hasLayer: function (layer) { 7229 return stamp(layer) in this._layers; 7230 }, 7231 7232 /* @method eachLayer(fn: Function, context?: Object): this 7233 * Iterates over the layers of the map, optionally specifying context of the iterator function. 7234 * ``` 7235 * map.eachLayer(function(layer){ 7236 * layer.bindPopup('Hello'); 7237 * }); 7238 * ``` 7239 */ 7240 eachLayer: function (method, context) { 7241 for (var i in this._layers) { 7242 method.call(context, this._layers[i]); 7243 } 7244 return this; 7245 }, 7246 7247 _addLayers: function (layers) { 7248 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 7249 7250 for (var i = 0, len = layers.length; i < len; i++) { 7251 this.addLayer(layers[i]); 7252 } 7253 }, 7254 7255 _addZoomLimit: function (layer) { 7256 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 7257 this._zoomBoundLayers[stamp(layer)] = layer; 7258 this._updateZoomLevels(); 7259 } 7260 }, 7261 7262 _removeZoomLimit: function (layer) { 7263 var id = stamp(layer); 7264 7265 if (this._zoomBoundLayers[id]) { 7266 delete this._zoomBoundLayers[id]; 7267 this._updateZoomLevels(); 7268 } 7269 }, 7270 7271 _updateZoomLevels: function () { 7272 var minZoom = Infinity, 7273 maxZoom = -Infinity, 7274 oldZoomSpan = this._getZoomSpan(); 7275 7276 for (var i in this._zoomBoundLayers) { 7277 var options = this._zoomBoundLayers[i].options; 7278 7279 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 7280 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 7281 } 7282 7283 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 7284 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 7285 7286 // @section Map state change events 7287 // @event zoomlevelschange: Event 7288 // Fired when the number of zoomlevels on the map is changed due 7289 // to adding or removing a layer. 7290 if (oldZoomSpan !== this._getZoomSpan()) { 7291 this.fire('zoomlevelschange'); 7292 } 7293 7294 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 7295 this.setZoom(this._layersMaxZoom); 7296 } 7297 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 7298 this.setZoom(this._layersMinZoom); 7299 } 7300 } 7301 }); 7302 7303 /* 7304 * @class LayerGroup 7305 * @aka L.LayerGroup 7306 * @inherits Interactive layer 7307 * 7308 * Used to group several layers and handle them as one. If you add it to the map, 7309 * any layers added or removed from the group will be added/removed on the map as 7310 * well. Extends `Layer`. 7311 * 7312 * @example 7313 * 7314 * ```js 7315 * L.layerGroup([marker1, marker2]) 7316 * .addLayer(polyline) 7317 * .addTo(map); 7318 * ``` 7319 */ 7320 7321 var LayerGroup = Layer.extend({ 7322 7323 initialize: function (layers, options) { 7324 setOptions(this, options); 7325 7326 this._layers = {}; 7327 7328 var i, len; 7329 7330 if (layers) { 7331 for (i = 0, len = layers.length; i < len; i++) { 7332 this.addLayer(layers[i]); 7333 } 7334 } 7335 }, 7336 7337 // @method addLayer(layer: Layer): this 7338 // Adds the given layer to the group. 7339 addLayer: function (layer) { 7340 var id = this.getLayerId(layer); 7341 7342 this._layers[id] = layer; 7343 7344 if (this._map) { 7345 this._map.addLayer(layer); 7346 } 7347 7348 return this; 7349 }, 7350 7351 // @method removeLayer(layer: Layer): this 7352 // Removes the given layer from the group. 7353 // @alternative 7354 // @method removeLayer(id: Number): this 7355 // Removes the layer with the given internal ID from the group. 7356 removeLayer: function (layer) { 7357 var id = layer in this._layers ? layer : this.getLayerId(layer); 7358 7359 if (this._map && this._layers[id]) { 7360 this._map.removeLayer(this._layers[id]); 7361 } 7362 7363 delete this._layers[id]; 7364 7365 return this; 7366 }, 7367 7368 // @method hasLayer(layer: Layer): Boolean 7369 // Returns `true` if the given layer is currently added to the group. 7370 // @alternative 7371 // @method hasLayer(id: Number): Boolean 7372 // Returns `true` if the given internal ID is currently added to the group. 7373 hasLayer: function (layer) { 7374 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer); 7375 return layerId in this._layers; 7376 }, 7377 7378 // @method clearLayers(): this 7379 // Removes all the layers from the group. 7380 clearLayers: function () { 7381 return this.eachLayer(this.removeLayer, this); 7382 }, 7383 7384 // @method invoke(methodName: String, â¦): this 7385 // Calls `methodName` on every layer contained in this group, passing any 7386 // additional parameters. Has no effect if the layers contained do not 7387 // implement `methodName`. 7388 invoke: function (methodName) { 7389 var args = Array.prototype.slice.call(arguments, 1), 7390 i, layer; 7391 7392 for (i in this._layers) { 7393 layer = this._layers[i]; 7394 7395 if (layer[methodName]) { 7396 layer[methodName].apply(layer, args); 7397 } 7398 } 7399 7400 return this; 7401 }, 7402 7403 onAdd: function (map) { 7404 this.eachLayer(map.addLayer, map); 7405 }, 7406 7407 onRemove: function (map) { 7408 this.eachLayer(map.removeLayer, map); 7409 }, 7410 7411 // @method eachLayer(fn: Function, context?: Object): this 7412 // Iterates over the layers of the group, optionally specifying context of the iterator function. 7413 // ```js 7414 // group.eachLayer(function (layer) { 7415 // layer.bindPopup('Hello'); 7416 // }); 7417 // ``` 7418 eachLayer: function (method, context) { 7419 for (var i in this._layers) { 7420 method.call(context, this._layers[i]); 7421 } 7422 return this; 7423 }, 7424 7425 // @method getLayer(id: Number): Layer 7426 // Returns the layer with the given internal ID. 7427 getLayer: function (id) { 7428 return this._layers[id]; 7429 }, 7430 7431 // @method getLayers(): Layer[] 7432 // Returns an array of all the layers added to the group. 7433 getLayers: function () { 7434 var layers = []; 7435 this.eachLayer(layers.push, layers); 7436 return layers; 7437 }, 7438 7439 // @method setZIndex(zIndex: Number): this 7440 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 7441 setZIndex: function (zIndex) { 7442 return this.invoke('setZIndex', zIndex); 7443 }, 7444 7445 // @method getLayerId(layer: Layer): Number 7446 // Returns the internal ID for a layer 7447 getLayerId: function (layer) { 7448 return stamp(layer); 7449 } 7450 }); 7451 7452 7453 // @factory L.layerGroup(layers?: Layer[], options?: Object) 7454 // Create a layer group, optionally given an initial set of layers and an `options` object. 7455 var layerGroup = function (layers, options) { 7456 return new LayerGroup(layers, options); 7457 }; 7458 7459 /* 7460 * @class FeatureGroup 7461 * @aka L.FeatureGroup 7462 * @inherits LayerGroup 7463 * 7464 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 7465 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 7466 * * Events are propagated to the `FeatureGroup`, so if the group has an event 7467 * handler, it will handle events from any of the layers. This includes mouse events 7468 * and custom events. 7469 * * Has `layeradd` and `layerremove` events 7470 * 7471 * @example 7472 * 7473 * ```js 7474 * L.featureGroup([marker1, marker2, polyline]) 7475 * .bindPopup('Hello world!') 7476 * .on('click', function() { alert('Clicked on a member of the group!'); }) 7477 * .addTo(map); 7478 * ``` 7479 */ 7480 7481 var FeatureGroup = LayerGroup.extend({ 7482 7483 addLayer: function (layer) { 7484 if (this.hasLayer(layer)) { 7485 return this; 7486 } 7487 7488 layer.addEventParent(this); 7489 7490 LayerGroup.prototype.addLayer.call(this, layer); 7491 7492 // @event layeradd: LayerEvent 7493 // Fired when a layer is added to this `FeatureGroup` 7494 return this.fire('layeradd', {layer: layer}); 7495 }, 7496 7497 removeLayer: function (layer) { 7498 if (!this.hasLayer(layer)) { 7499 return this; 7500 } 7501 if (layer in this._layers) { 7502 layer = this._layers[layer]; 7503 } 7504 7505 layer.removeEventParent(this); 7506 7507 LayerGroup.prototype.removeLayer.call(this, layer); 7508 7509 // @event layerremove: LayerEvent 7510 // Fired when a layer is removed from this `FeatureGroup` 7511 return this.fire('layerremove', {layer: layer}); 7512 }, 7513 7514 // @method setStyle(style: Path options): this 7515 // Sets the given path options to each layer of the group that has a `setStyle` method. 7516 setStyle: function (style) { 7517 return this.invoke('setStyle', style); 7518 }, 7519 7520 // @method bringToFront(): this 7521 // Brings the layer group to the top of all other layers 7522 bringToFront: function () { 7523 return this.invoke('bringToFront'); 7524 }, 7525 7526 // @method bringToBack(): this 7527 // Brings the layer group to the back of all other layers 7528 bringToBack: function () { 7529 return this.invoke('bringToBack'); 7530 }, 7531 7532 // @method getBounds(): LatLngBounds 7533 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 7534 getBounds: function () { 7535 var bounds = new LatLngBounds(); 7536 7537 for (var id in this._layers) { 7538 var layer = this._layers[id]; 7539 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 7540 } 7541 return bounds; 7542 } 7543 }); 7544 7545 // @factory L.featureGroup(layers?: Layer[], options?: Object) 7546 // Create a feature group, optionally given an initial set of layers and an `options` object. 7547 var featureGroup = function (layers, options) { 7548 return new FeatureGroup(layers, options); 7549 }; 7550 7551 /* 7552 * @class Icon 7553 * @aka L.Icon 7554 * 7555 * Represents an icon to provide when creating a marker. 7556 * 7557 * @example 7558 * 7559 * ```js 7560 * var myIcon = L.icon({ 7561 * iconUrl: 'my-icon.png', 7562 * iconRetinaUrl: '[email protected]', 7563 * iconSize: [38, 95], 7564 * iconAnchor: [22, 94], 7565 * popupAnchor: [-3, -76], 7566 * shadowUrl: 'my-icon-shadow.png', 7567 * shadowRetinaUrl: '[email protected]', 7568 * shadowSize: [68, 95], 7569 * shadowAnchor: [22, 94] 7570 * }); 7571 * 7572 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 7573 * ``` 7574 * 7575 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 7576 * 7577 */ 7578 7579 var Icon = Class.extend({ 7580 7581 /* @section 7582 * @aka Icon options 7583 * 7584 * @option iconUrl: String = null 7585 * **(required)** The URL to the icon image (absolute or relative to your script path). 7586 * 7587 * @option iconRetinaUrl: String = null 7588 * The URL to a retina sized version of the icon image (absolute or relative to your 7589 * script path). Used for Retina screen devices. 7590 * 7591 * @option iconSize: Point = null 7592 * Size of the icon image in pixels. 7593 * 7594 * @option iconAnchor: Point = null 7595 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7596 * will be aligned so that this point is at the marker's geographical location. Centered 7597 * by default if size is specified, also can be set in CSS with negative margins. 7598 * 7599 * @option popupAnchor: Point = [0, 0] 7600 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7601 * 7602 * @option tooltipAnchor: Point = [0, 0] 7603 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7604 * 7605 * @option shadowUrl: String = null 7606 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7607 * 7608 * @option shadowRetinaUrl: String = null 7609 * 7610 * @option shadowSize: Point = null 7611 * Size of the shadow image in pixels. 7612 * 7613 * @option shadowAnchor: Point = null 7614 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7615 * as iconAnchor if not specified). 7616 * 7617 * @option className: String = '' 7618 * A custom class name to assign to both icon and shadow images. Empty by default. 7619 */ 7620 7621 options: { 7622 popupAnchor: [0, 0], 7623 tooltipAnchor: [0, 0], 7624 7625 // @option crossOrigin: Boolean|String = false 7626 // Whether the crossOrigin attribute will be added to the tiles. 7627 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. 7628 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 7629 crossOrigin: false 7630 }, 7631 7632 initialize: function (options) { 7633 setOptions(this, options); 7634 }, 7635 7636 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7637 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7638 // styled according to the options. 7639 createIcon: function (oldIcon) { 7640 return this._createIcon('icon', oldIcon); 7641 }, 7642 7643 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7644 // As `createIcon`, but for the shadow beneath it. 7645 createShadow: function (oldIcon) { 7646 return this._createIcon('shadow', oldIcon); 7647 }, 7648 7649 _createIcon: function (name, oldIcon) { 7650 var src = this._getIconUrl(name); 7651 7652 if (!src) { 7653 if (name === 'icon') { 7654 throw new Error('iconUrl not set in Icon options (see the docs).'); 7655 } 7656 return null; 7657 } 7658 7659 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7660 this._setIconStyles(img, name); 7661 7662 if (this.options.crossOrigin || this.options.crossOrigin === '') { 7663 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 7664 } 7665 7666 return img; 7667 }, 7668 7669 _setIconStyles: function (img, name) { 7670 var options = this.options; 7671 var sizeOption = options[name + 'Size']; 7672 7673 if (typeof sizeOption === 'number') { 7674 sizeOption = [sizeOption, sizeOption]; 7675 } 7676 7677 var size = toPoint(sizeOption), 7678 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7679 size && size.divideBy(2, true)); 7680 7681 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7682 7683 if (anchor) { 7684 img.style.marginLeft = (-anchor.x) + 'px'; 7685 img.style.marginTop = (-anchor.y) + 'px'; 7686 } 7687 7688 if (size) { 7689 img.style.width = size.x + 'px'; 7690 img.style.height = size.y + 'px'; 7691 } 7692 }, 7693 7694 _createImg: function (src, el) { 7695 el = el || document.createElement('img'); 7696 el.src = src; 7697 return el; 7698 }, 7699 7700 _getIconUrl: function (name) { 7701 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7702 } 7703 }); 7704 7705 7706 // @factory L.icon(options: Icon options) 7707 // Creates an icon instance with the given options. 7708 function icon(options) { 7709 return new Icon(options); 7710 } 7711 7712 /* 7713 * @miniclass Icon.Default (Icon) 7714 * @aka L.Icon.Default 7715 * @section 7716 * 7717 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7718 * no icon is specified. Points to the blue marker image distributed with Leaflet 7719 * releases. 7720 * 7721 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7722 * (which is a set of `Icon options`). 7723 * 7724 * If you want to _completely_ replace the default icon, override the 7725 * `L.Marker.prototype.options.icon` with your own icon instead. 7726 */ 7727 7728 var IconDefault = Icon.extend({ 7729 7730 options: { 7731 iconUrl: 'marker-icon.png', 7732 iconRetinaUrl: 'marker-icon-2x.png', 7733 shadowUrl: 'marker-shadow.png', 7734 iconSize: [25, 41], 7735 iconAnchor: [12, 41], 7736 popupAnchor: [1, -34], 7737 tooltipAnchor: [16, -28], 7738 shadowSize: [41, 41] 7739 }, 7740 7741 _getIconUrl: function (name) { 7742 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only 7743 IconDefault.imagePath = this._detectIconPath(); 7744 } 7745 7746 // @option imagePath: String 7747 // `Icon.Default` will try to auto-detect the location of the 7748 // blue icon images. If you are placing these images in a non-standard 7749 // way, set this option to point to the right path. 7750 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7751 }, 7752 7753 _stripUrl: function (path) { // separate function to use in tests 7754 var strip = function (str, re, idx) { 7755 var match = re.exec(str); 7756 return match && match[idx]; 7757 }; 7758 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2); 7759 return path && strip(path, /^(.*)marker-icon\.png$/, 1); 7760 }, 7761 7762 _detectIconPath: function () { 7763 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7764 var path = getStyle(el, 'background-image') || 7765 getStyle(el, 'backgroundImage'); // IE8 7766 7767 document.body.removeChild(el); 7768 path = this._stripUrl(path); 7769 if (path) { return path; } 7770 var link = document.querySelector('link[href$="leaflet.css"]'); 7771 if (!link) { return ''; } 7772 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1); 7773 } 7774 }); 7775 7776 /* 7777 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7778 */ 7779 7780 7781 /* @namespace Marker 7782 * @section Interaction handlers 7783 * 7784 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example: 7785 * 7786 * ```js 7787 * marker.dragging.disable(); 7788 * ``` 7789 * 7790 * @property dragging: Handler 7791 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7792 */ 7793 7794 var MarkerDrag = Handler.extend({ 7795 initialize: function (marker) { 7796 this._marker = marker; 7797 }, 7798 7799 addHooks: function () { 7800 var icon = this._marker._icon; 7801 7802 if (!this._draggable) { 7803 this._draggable = new Draggable(icon, icon, true); 7804 } 7805 7806 this._draggable.on({ 7807 dragstart: this._onDragStart, 7808 predrag: this._onPreDrag, 7809 drag: this._onDrag, 7810 dragend: this._onDragEnd 7811 }, this).enable(); 7812 7813 addClass(icon, 'leaflet-marker-draggable'); 7814 }, 7815 7816 removeHooks: function () { 7817 this._draggable.off({ 7818 dragstart: this._onDragStart, 7819 predrag: this._onPreDrag, 7820 drag: this._onDrag, 7821 dragend: this._onDragEnd 7822 }, this).disable(); 7823 7824 if (this._marker._icon) { 7825 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7826 } 7827 }, 7828 7829 moved: function () { 7830 return this._draggable && this._draggable._moved; 7831 }, 7832 7833 _adjustPan: function (e) { 7834 var marker = this._marker, 7835 map = marker._map, 7836 speed = this._marker.options.autoPanSpeed, 7837 padding = this._marker.options.autoPanPadding, 7838 iconPos = getPosition(marker._icon), 7839 bounds = map.getPixelBounds(), 7840 origin = map.getPixelOrigin(); 7841 7842 var panBounds = toBounds( 7843 bounds.min._subtract(origin).add(padding), 7844 bounds.max._subtract(origin).subtract(padding) 7845 ); 7846 7847 if (!panBounds.contains(iconPos)) { 7848 // Compute incremental movement 7849 var movement = toPoint( 7850 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7851 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7852 7853 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7854 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7855 ).multiplyBy(speed); 7856 7857 map.panBy(movement, {animate: false}); 7858 7859 this._draggable._newPos._add(movement); 7860 this._draggable._startPos._add(movement); 7861 7862 setPosition(marker._icon, this._draggable._newPos); 7863 this._onDrag(e); 7864 7865 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7866 } 7867 }, 7868 7869 _onDragStart: function () { 7870 // @section Dragging events 7871 // @event dragstart: Event 7872 // Fired when the user starts dragging the marker. 7873 7874 // @event movestart: Event 7875 // Fired when the marker starts moving (because of dragging). 7876 7877 this._oldLatLng = this._marker.getLatLng(); 7878 7879 // When using ES6 imports it could not be set when `Popup` was not imported as well 7880 this._marker.closePopup && this._marker.closePopup(); 7881 7882 this._marker 7883 .fire('movestart') 7884 .fire('dragstart'); 7885 }, 7886 7887 _onPreDrag: function (e) { 7888 if (this._marker.options.autoPan) { 7889 cancelAnimFrame(this._panRequest); 7890 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7891 } 7892 }, 7893 7894 _onDrag: function (e) { 7895 var marker = this._marker, 7896 shadow = marker._shadow, 7897 iconPos = getPosition(marker._icon), 7898 latlng = marker._map.layerPointToLatLng(iconPos); 7899 7900 // update shadow position 7901 if (shadow) { 7902 setPosition(shadow, iconPos); 7903 } 7904 7905 marker._latlng = latlng; 7906 e.latlng = latlng; 7907 e.oldLatLng = this._oldLatLng; 7908 7909 // @event drag: Event 7910 // Fired repeatedly while the user drags the marker. 7911 marker 7912 .fire('move', e) 7913 .fire('drag', e); 7914 }, 7915 7916 _onDragEnd: function (e) { 7917 // @event dragend: DragEndEvent 7918 // Fired when the user stops dragging the marker. 7919 7920 cancelAnimFrame(this._panRequest); 7921 7922 // @event moveend: Event 7923 // Fired when the marker stops moving (because of dragging). 7924 delete this._oldLatLng; 7925 this._marker 7926 .fire('moveend') 7927 .fire('dragend', e); 7928 } 7929 }); 7930 7931 /* 7932 * @class Marker 7933 * @inherits Interactive layer 7934 * @aka L.Marker 7935 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7936 * 7937 * @example 7938 * 7939 * ```js 7940 * L.marker([50.5, 30.5]).addTo(map); 7941 * ``` 7942 */ 7943 7944 var Marker = Layer.extend({ 7945 7946 // @section 7947 // @aka Marker options 7948 options: { 7949 // @option icon: Icon = * 7950 // Icon instance to use for rendering the marker. 7951 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7952 // If not specified, a common instance of `L.Icon.Default` is used. 7953 icon: new IconDefault(), 7954 7955 // Option inherited from "Interactive layer" abstract class 7956 interactive: true, 7957 7958 // @option keyboard: Boolean = true 7959 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7960 keyboard: true, 7961 7962 // @option title: String = '' 7963 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7964 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 7965 title: '', 7966 7967 // @option alt: String = 'Marker' 7968 // Text for the `alt` attribute of the icon image. 7969 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 7970 alt: 'Marker', 7971 7972 // @option zIndexOffset: Number = 0 7973 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively). 7974 zIndexOffset: 0, 7975 7976 // @option opacity: Number = 1.0 7977 // The opacity of the marker. 7978 opacity: 1, 7979 7980 // @option riseOnHover: Boolean = false 7981 // If `true`, the marker will get on top of others when you hover the mouse over it. 7982 riseOnHover: false, 7983 7984 // @option riseOffset: Number = 250 7985 // The z-index offset used for the `riseOnHover` feature. 7986 riseOffset: 250, 7987 7988 // @option pane: String = 'markerPane' 7989 // `Map pane` where the markers icon will be added. 7990 pane: 'markerPane', 7991 7992 // @option shadowPane: String = 'shadowPane' 7993 // `Map pane` where the markers shadow will be added. 7994 shadowPane: 'shadowPane', 7995 7996 // @option bubblingMouseEvents: Boolean = false 7997 // When `true`, a mouse event on this marker will trigger the same event on the map 7998 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7999 bubblingMouseEvents: false, 8000 8001 // @option autoPanOnFocus: Boolean = true 8002 // When `true`, the map will pan whenever the marker is focused (via 8003 // e.g. pressing `tab` on the keyboard) to ensure the marker is 8004 // visible within the map's bounds 8005 autoPanOnFocus: true, 8006 8007 // @section Draggable marker options 8008 // @option draggable: Boolean = false 8009 // Whether the marker is draggable with mouse/touch or not. 8010 draggable: false, 8011 8012 // @option autoPan: Boolean = false 8013 // Whether to pan the map when dragging this marker near its edge or not. 8014 autoPan: false, 8015 8016 // @option autoPanPadding: Point = Point(50, 50) 8017 // Distance (in pixels to the left/right and to the top/bottom) of the 8018 // map edge to start panning the map. 8019 autoPanPadding: [50, 50], 8020 8021 // @option autoPanSpeed: Number = 10 8022 // Number of pixels the map should pan by. 8023 autoPanSpeed: 10 8024 }, 8025 8026 /* @section 8027 * 8028 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 8029 */ 8030 8031 initialize: function (latlng, options) { 8032 setOptions(this, options); 8033 this._latlng = toLatLng(latlng); 8034 }, 8035 8036 onAdd: function (map) { 8037 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 8038 8039 if (this._zoomAnimated) { 8040 map.on('zoomanim', this._animateZoom, this); 8041 } 8042 8043 this._initIcon(); 8044 this.update(); 8045 }, 8046 8047 onRemove: function (map) { 8048 if (this.dragging && this.dragging.enabled()) { 8049 this.options.draggable = true; 8050 this.dragging.removeHooks(); 8051 } 8052 delete this.dragging; 8053 8054 if (this._zoomAnimated) { 8055 map.off('zoomanim', this._animateZoom, this); 8056 } 8057 8058 this._removeIcon(); 8059 this._removeShadow(); 8060 }, 8061 8062 getEvents: function () { 8063 return { 8064 zoom: this.update, 8065 viewreset: this.update 8066 }; 8067 }, 8068 8069 // @method getLatLng: LatLng 8070 // Returns the current geographical position of the marker. 8071 getLatLng: function () { 8072 return this._latlng; 8073 }, 8074 8075 // @method setLatLng(latlng: LatLng): this 8076 // Changes the marker position to the given point. 8077 setLatLng: function (latlng) { 8078 var oldLatLng = this._latlng; 8079 this._latlng = toLatLng(latlng); 8080 this.update(); 8081 8082 // @event move: Event 8083 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 8084 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 8085 }, 8086 8087 // @method setZIndexOffset(offset: Number): this 8088 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 8089 setZIndexOffset: function (offset) { 8090 this.options.zIndexOffset = offset; 8091 return this.update(); 8092 }, 8093 8094 // @method getIcon: Icon 8095 // Returns the current icon used by the marker 8096 getIcon: function () { 8097 return this.options.icon; 8098 }, 8099 8100 // @method setIcon(icon: Icon): this 8101 // Changes the marker icon. 8102 setIcon: function (icon) { 8103 8104 this.options.icon = icon; 8105 8106 if (this._map) { 8107 this._initIcon(); 8108 this.update(); 8109 } 8110 8111 if (this._popup) { 8112 this.bindPopup(this._popup, this._popup.options); 8113 } 8114 8115 return this; 8116 }, 8117 8118 getElement: function () { 8119 return this._icon; 8120 }, 8121 8122 update: function () { 8123 8124 if (this._icon && this._map) { 8125 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 8126 this._setPos(pos); 8127 } 8128 8129 return this; 8130 }, 8131 8132 _initIcon: function () { 8133 var options = this.options, 8134 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 8135 8136 var icon = options.icon.createIcon(this._icon), 8137 addIcon = false; 8138 8139 // if we're not reusing the icon, remove the old one and init new one 8140 if (icon !== this._icon) { 8141 if (this._icon) { 8142 this._removeIcon(); 8143 } 8144 addIcon = true; 8145 8146 if (options.title) { 8147 icon.title = options.title; 8148 } 8149 8150 if (icon.tagName === 'IMG') { 8151 icon.alt = options.alt || ''; 8152 } 8153 } 8154 8155 addClass(icon, classToAdd); 8156 8157 if (options.keyboard) { 8158 icon.tabIndex = '0'; 8159 icon.setAttribute('role', 'button'); 8160 } 8161 8162 this._icon = icon; 8163 8164 if (options.riseOnHover) { 8165 this.on({ 8166 mouseover: this._bringToFront, 8167 mouseout: this._resetZIndex 8168 }); 8169 } 8170 8171 if (this.options.autoPanOnFocus) { 8172 on(icon, 'focus', this._panOnFocus, this); 8173 } 8174 8175 var newShadow = options.icon.createShadow(this._shadow), 8176 addShadow = false; 8177 8178 if (newShadow !== this._shadow) { 8179 this._removeShadow(); 8180 addShadow = true; 8181 } 8182 8183 if (newShadow) { 8184 addClass(newShadow, classToAdd); 8185 newShadow.alt = ''; 8186 } 8187 this._shadow = newShadow; 8188 8189 8190 if (options.opacity < 1) { 8191 this._updateOpacity(); 8192 } 8193 8194 8195 if (addIcon) { 8196 this.getPane().appendChild(this._icon); 8197 } 8198 this._initInteraction(); 8199 if (newShadow && addShadow) { 8200 this.getPane(options.shadowPane).appendChild(this._shadow); 8201 } 8202 }, 8203 8204 _removeIcon: function () { 8205 if (this.options.riseOnHover) { 8206 this.off({ 8207 mouseover: this._bringToFront, 8208 mouseout: this._resetZIndex 8209 }); 8210 } 8211 8212 if (this.options.autoPanOnFocus) { 8213 off(this._icon, 'focus', this._panOnFocus, this); 8214 } 8215 8216 remove(this._icon); 8217 this.removeInteractiveTarget(this._icon); 8218 8219 this._icon = null; 8220 }, 8221 8222 _removeShadow: function () { 8223 if (this._shadow) { 8224 remove(this._shadow); 8225 } 8226 this._shadow = null; 8227 }, 8228 8229 _setPos: function (pos) { 8230 8231 if (this._icon) { 8232 setPosition(this._icon, pos); 8233 } 8234 8235 if (this._shadow) { 8236 setPosition(this._shadow, pos); 8237 } 8238 8239 this._zIndex = pos.y + this.options.zIndexOffset; 8240 8241 this._resetZIndex(); 8242 }, 8243 8244 _updateZIndex: function (offset) { 8245 if (this._icon) { 8246 this._icon.style.zIndex = this._zIndex + offset; 8247 } 8248 }, 8249 8250 _animateZoom: function (opt) { 8251 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 8252 8253 this._setPos(pos); 8254 }, 8255 8256 _initInteraction: function () { 8257 8258 if (!this.options.interactive) { return; } 8259 8260 addClass(this._icon, 'leaflet-interactive'); 8261 8262 this.addInteractiveTarget(this._icon); 8263 8264 if (MarkerDrag) { 8265 var draggable = this.options.draggable; 8266 if (this.dragging) { 8267 draggable = this.dragging.enabled(); 8268 this.dragging.disable(); 8269 } 8270 8271 this.dragging = new MarkerDrag(this); 8272 8273 if (draggable) { 8274 this.dragging.enable(); 8275 } 8276 } 8277 }, 8278 8279 // @method setOpacity(opacity: Number): this 8280 // Changes the opacity of the marker. 8281 setOpacity: function (opacity) { 8282 this.options.opacity = opacity; 8283 if (this._map) { 8284 this._updateOpacity(); 8285 } 8286 8287 return this; 8288 }, 8289 8290 _updateOpacity: function () { 8291 var opacity = this.options.opacity; 8292 8293 if (this._icon) { 8294 setOpacity(this._icon, opacity); 8295 } 8296 8297 if (this._shadow) { 8298 setOpacity(this._shadow, opacity); 8299 } 8300 }, 8301 8302 _bringToFront: function () { 8303 this._updateZIndex(this.options.riseOffset); 8304 }, 8305 8306 _resetZIndex: function () { 8307 this._updateZIndex(0); 8308 }, 8309 8310 _panOnFocus: function () { 8311 var map = this._map; 8312 if (!map) { return; } 8313 8314 var iconOpts = this.options.icon.options; 8315 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0); 8316 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0); 8317 8318 map.panInside(this._latlng, { 8319 paddingTopLeft: anchor, 8320 paddingBottomRight: size.subtract(anchor) 8321 }); 8322 }, 8323 8324 _getPopupAnchor: function () { 8325 return this.options.icon.options.popupAnchor; 8326 }, 8327 8328 _getTooltipAnchor: function () { 8329 return this.options.icon.options.tooltipAnchor; 8330 } 8331 }); 8332 8333 8334 // factory L.marker(latlng: LatLng, options? : Marker options) 8335 8336 // @factory L.marker(latlng: LatLng, options? : Marker options) 8337 // Instantiates a Marker object given a geographical point and optionally an options object. 8338 function marker(latlng, options) { 8339 return new Marker(latlng, options); 8340 } 8341 8342 /* 8343 * @class Path 8344 * @aka L.Path 8345 * @inherits Interactive layer 8346 * 8347 * An abstract class that contains options and constants shared between vector 8348 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 8349 */ 8350 8351 var Path = Layer.extend({ 8352 8353 // @section 8354 // @aka Path options 8355 options: { 8356 // @option stroke: Boolean = true 8357 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 8358 stroke: true, 8359 8360 // @option color: String = '#3388ff' 8361 // Stroke color 8362 color: '#3388ff', 8363 8364 // @option weight: Number = 3 8365 // Stroke width in pixels 8366 weight: 3, 8367 8368 // @option opacity: Number = 1.0 8369 // Stroke opacity 8370 opacity: 1, 8371 8372 // @option lineCap: String= 'round' 8373 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 8374 lineCap: 'round', 8375 8376 // @option lineJoin: String = 'round' 8377 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 8378 lineJoin: 'round', 8379 8380 // @option dashArray: String = null 8381 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). 8382 dashArray: null, 8383 8384 // @option dashOffset: String = null 8385 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). 8386 dashOffset: null, 8387 8388 // @option fill: Boolean = depends 8389 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 8390 fill: false, 8391 8392 // @option fillColor: String = * 8393 // Fill color. Defaults to the value of the [`color`](#path-color) option 8394 fillColor: null, 8395 8396 // @option fillOpacity: Number = 0.2 8397 // Fill opacity. 8398 fillOpacity: 0.2, 8399 8400 // @option fillRule: String = 'evenodd' 8401 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 8402 fillRule: 'evenodd', 8403 8404 // className: '', 8405 8406 // Option inherited from "Interactive layer" abstract class 8407 interactive: true, 8408 8409 // @option bubblingMouseEvents: Boolean = true 8410 // When `true`, a mouse event on this path will trigger the same event on the map 8411 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 8412 bubblingMouseEvents: true 8413 }, 8414 8415 beforeAdd: function (map) { 8416 // Renderer is set here because we need to call renderer.getEvents 8417 // before this.getEvents. 8418 this._renderer = map.getRenderer(this); 8419 }, 8420 8421 onAdd: function () { 8422 this._renderer._initPath(this); 8423 this._reset(); 8424 this._renderer._addPath(this); 8425 }, 8426 8427 onRemove: function () { 8428 this._renderer._removePath(this); 8429 }, 8430 8431 // @method redraw(): this 8432 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 8433 redraw: function () { 8434 if (this._map) { 8435 this._renderer._updatePath(this); 8436 } 8437 return this; 8438 }, 8439 8440 // @method setStyle(style: Path options): this 8441 // Changes the appearance of a Path based on the options in the `Path options` object. 8442 setStyle: function (style) { 8443 setOptions(this, style); 8444 if (this._renderer) { 8445 this._renderer._updateStyle(this); 8446 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) { 8447 this._updateBounds(); 8448 } 8449 } 8450 return this; 8451 }, 8452 8453 // @method bringToFront(): this 8454 // Brings the layer to the top of all path layers. 8455 bringToFront: function () { 8456 if (this._renderer) { 8457 this._renderer._bringToFront(this); 8458 } 8459 return this; 8460 }, 8461 8462 // @method bringToBack(): this 8463 // Brings the layer to the bottom of all path layers. 8464 bringToBack: function () { 8465 if (this._renderer) { 8466 this._renderer._bringToBack(this); 8467 } 8468 return this; 8469 }, 8470 8471 getElement: function () { 8472 return this._path; 8473 }, 8474 8475 _reset: function () { 8476 // defined in child classes 8477 this._project(); 8478 this._update(); 8479 }, 8480 8481 _clickTolerance: function () { 8482 // used when doing hit detection for Canvas layers 8483 return (this.options.stroke ? this.options.weight / 2 : 0) + 8484 (this._renderer.options.tolerance || 0); 8485 } 8486 }); 8487 8488 /* 8489 * @class CircleMarker 8490 * @aka L.CircleMarker 8491 * @inherits Path 8492 * 8493 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 8494 */ 8495 8496 var CircleMarker = Path.extend({ 8497 8498 // @section 8499 // @aka CircleMarker options 8500 options: { 8501 fill: true, 8502 8503 // @option radius: Number = 10 8504 // Radius of the circle marker, in pixels 8505 radius: 10 8506 }, 8507 8508 initialize: function (latlng, options) { 8509 setOptions(this, options); 8510 this._latlng = toLatLng(latlng); 8511 this._radius = this.options.radius; 8512 }, 8513 8514 // @method setLatLng(latLng: LatLng): this 8515 // Sets the position of a circle marker to a new location. 8516 setLatLng: function (latlng) { 8517 var oldLatLng = this._latlng; 8518 this._latlng = toLatLng(latlng); 8519 this.redraw(); 8520 8521 // @event move: Event 8522 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 8523 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 8524 }, 8525 8526 // @method getLatLng(): LatLng 8527 // Returns the current geographical position of the circle marker 8528 getLatLng: function () { 8529 return this._latlng; 8530 }, 8531 8532 // @method setRadius(radius: Number): this 8533 // Sets the radius of a circle marker. Units are in pixels. 8534 setRadius: function (radius) { 8535 this.options.radius = this._radius = radius; 8536 return this.redraw(); 8537 }, 8538 8539 // @method getRadius(): Number 8540 // Returns the current radius of the circle 8541 getRadius: function () { 8542 return this._radius; 8543 }, 8544 8545 setStyle : function (options) { 8546 var radius = options && options.radius || this._radius; 8547 Path.prototype.setStyle.call(this, options); 8548 this.setRadius(radius); 8549 return this; 8550 }, 8551 8552 _project: function () { 8553 this._point = this._map.latLngToLayerPoint(this._latlng); 8554 this._updateBounds(); 8555 }, 8556 8557 _updateBounds: function () { 8558 var r = this._radius, 8559 r2 = this._radiusY || r, 8560 w = this._clickTolerance(), 8561 p = [r + w, r2 + w]; 8562 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 8563 }, 8564 8565 _update: function () { 8566 if (this._map) { 8567 this._updatePath(); 8568 } 8569 }, 8570 8571 _updatePath: function () { 8572 this._renderer._updateCircle(this); 8573 }, 8574 8575 _empty: function () { 8576 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8577 }, 8578 8579 // Needed by the `Canvas` renderer for interactivity 8580 _containsPoint: function (p) { 8581 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 8582 } 8583 }); 8584 8585 8586 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8587 // Instantiates a circle marker object given a geographical point, and an optional options object. 8588 function circleMarker(latlng, options) { 8589 return new CircleMarker(latlng, options); 8590 } 8591 8592 /* 8593 * @class Circle 8594 * @aka L.Circle 8595 * @inherits CircleMarker 8596 * 8597 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8598 * 8599 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8600 * 8601 * @example 8602 * 8603 * ```js 8604 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8605 * ``` 8606 */ 8607 8608 var Circle = CircleMarker.extend({ 8609 8610 initialize: function (latlng, options, legacyOptions) { 8611 if (typeof options === 'number') { 8612 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8613 options = extend({}, legacyOptions, {radius: options}); 8614 } 8615 setOptions(this, options); 8616 this._latlng = toLatLng(latlng); 8617 8618 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8619 8620 // @section 8621 // @aka Circle options 8622 // @option radius: Number; Radius of the circle, in meters. 8623 this._mRadius = this.options.radius; 8624 }, 8625 8626 // @method setRadius(radius: Number): this 8627 // Sets the radius of a circle. Units are in meters. 8628 setRadius: function (radius) { 8629 this._mRadius = radius; 8630 return this.redraw(); 8631 }, 8632 8633 // @method getRadius(): Number 8634 // Returns the current radius of a circle. Units are in meters. 8635 getRadius: function () { 8636 return this._mRadius; 8637 }, 8638 8639 // @method getBounds(): LatLngBounds 8640 // Returns the `LatLngBounds` of the path. 8641 getBounds: function () { 8642 var half = [this._radius, this._radiusY || this._radius]; 8643 8644 return new LatLngBounds( 8645 this._map.layerPointToLatLng(this._point.subtract(half)), 8646 this._map.layerPointToLatLng(this._point.add(half))); 8647 }, 8648 8649 setStyle: Path.prototype.setStyle, 8650 8651 _project: function () { 8652 8653 var lng = this._latlng.lng, 8654 lat = this._latlng.lat, 8655 map = this._map, 8656 crs = map.options.crs; 8657 8658 if (crs.distance === Earth.distance) { 8659 var d = Math.PI / 180, 8660 latR = (this._mRadius / Earth.R) / d, 8661 top = map.project([lat + latR, lng]), 8662 bottom = map.project([lat - latR, lng]), 8663 p = top.add(bottom).divideBy(2), 8664 lat2 = map.unproject(p).lat, 8665 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8666 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8667 8668 if (isNaN(lngR) || lngR === 0) { 8669 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8670 } 8671 8672 this._point = p.subtract(map.getPixelOrigin()); 8673 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8674 this._radiusY = p.y - top.y; 8675 8676 } else { 8677 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8678 8679 this._point = map.latLngToLayerPoint(this._latlng); 8680 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8681 } 8682 8683 this._updateBounds(); 8684 } 8685 }); 8686 8687 // @factory L.circle(latlng: LatLng, options?: Circle options) 8688 // Instantiates a circle object given a geographical point, and an options object 8689 // which contains the circle radius. 8690 // @alternative 8691 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8692 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8693 // Do not use in new applications or plugins. 8694 function circle(latlng, options, legacyOptions) { 8695 return new Circle(latlng, options, legacyOptions); 8696 } 8697 8698 /* 8699 * @class Polyline 8700 * @aka L.Polyline 8701 * @inherits Path 8702 * 8703 * A class for drawing polyline overlays on a map. Extends `Path`. 8704 * 8705 * @example 8706 * 8707 * ```js 8708 * // create a red polyline from an array of LatLng points 8709 * var latlngs = [ 8710 * [45.51, -122.68], 8711 * [37.77, -122.43], 8712 * [34.04, -118.2] 8713 * ]; 8714 * 8715 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8716 * 8717 * // zoom the map to the polyline 8718 * map.fitBounds(polyline.getBounds()); 8719 * ``` 8720 * 8721 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8722 * 8723 * ```js 8724 * // create a red polyline from an array of arrays of LatLng points 8725 * var latlngs = [ 8726 * [[45.51, -122.68], 8727 * [37.77, -122.43], 8728 * [34.04, -118.2]], 8729 * [[40.78, -73.91], 8730 * [41.83, -87.62], 8731 * [32.76, -96.72]] 8732 * ]; 8733 * ``` 8734 */ 8735 8736 8737 var Polyline = Path.extend({ 8738 8739 // @section 8740 // @aka Polyline options 8741 options: { 8742 // @option smoothFactor: Number = 1.0 8743 // How much to simplify the polyline on each zoom level. More means 8744 // better performance and smoother look, and less means more accurate representation. 8745 smoothFactor: 1.0, 8746 8747 // @option noClip: Boolean = false 8748 // Disable polyline clipping. 8749 noClip: false 8750 }, 8751 8752 initialize: function (latlngs, options) { 8753 setOptions(this, options); 8754 this._setLatLngs(latlngs); 8755 }, 8756 8757 // @method getLatLngs(): LatLng[] 8758 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8759 getLatLngs: function () { 8760 return this._latlngs; 8761 }, 8762 8763 // @method setLatLngs(latlngs: LatLng[]): this 8764 // Replaces all the points in the polyline with the given array of geographical points. 8765 setLatLngs: function (latlngs) { 8766 this._setLatLngs(latlngs); 8767 return this.redraw(); 8768 }, 8769 8770 // @method isEmpty(): Boolean 8771 // Returns `true` if the Polyline has no LatLngs. 8772 isEmpty: function () { 8773 return !this._latlngs.length; 8774 }, 8775 8776 // @method closestLayerPoint(p: Point): Point 8777 // Returns the point closest to `p` on the Polyline. 8778 closestLayerPoint: function (p) { 8779 var minDistance = Infinity, 8780 minPoint = null, 8781 closest = _sqClosestPointOnSegment, 8782 p1, p2; 8783 8784 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8785 var points = this._parts[j]; 8786 8787 for (var i = 1, len = points.length; i < len; i++) { 8788 p1 = points[i - 1]; 8789 p2 = points[i]; 8790 8791 var sqDist = closest(p, p1, p2, true); 8792 8793 if (sqDist < minDistance) { 8794 minDistance = sqDist; 8795 minPoint = closest(p, p1, p2); 8796 } 8797 } 8798 } 8799 if (minPoint) { 8800 minPoint.distance = Math.sqrt(minDistance); 8801 } 8802 return minPoint; 8803 }, 8804 8805 // @method getCenter(): LatLng 8806 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline. 8807 getCenter: function () { 8808 // throws error when not yet added to map as this center calculation requires projected coordinates 8809 if (!this._map) { 8810 throw new Error('Must add layer to map before using getCenter()'); 8811 } 8812 return polylineCenter(this._defaultShape(), this._map.options.crs); 8813 }, 8814 8815 // @method getBounds(): LatLngBounds 8816 // Returns the `LatLngBounds` of the path. 8817 getBounds: function () { 8818 return this._bounds; 8819 }, 8820 8821 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this 8822 // Adds a given point to the polyline. By default, adds to the first ring of 8823 // the polyline in case of a multi-polyline, but can be overridden by passing 8824 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8825 addLatLng: function (latlng, latlngs) { 8826 latlngs = latlngs || this._defaultShape(); 8827 latlng = toLatLng(latlng); 8828 latlngs.push(latlng); 8829 this._bounds.extend(latlng); 8830 return this.redraw(); 8831 }, 8832 8833 _setLatLngs: function (latlngs) { 8834 this._bounds = new LatLngBounds(); 8835 this._latlngs = this._convertLatLngs(latlngs); 8836 }, 8837 8838 _defaultShape: function () { 8839 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8840 }, 8841 8842 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8843 _convertLatLngs: function (latlngs) { 8844 var result = [], 8845 flat = isFlat(latlngs); 8846 8847 for (var i = 0, len = latlngs.length; i < len; i++) { 8848 if (flat) { 8849 result[i] = toLatLng(latlngs[i]); 8850 this._bounds.extend(result[i]); 8851 } else { 8852 result[i] = this._convertLatLngs(latlngs[i]); 8853 } 8854 } 8855 8856 return result; 8857 }, 8858 8859 _project: function () { 8860 var pxBounds = new Bounds(); 8861 this._rings = []; 8862 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8863 8864 if (this._bounds.isValid() && pxBounds.isValid()) { 8865 this._rawPxBounds = pxBounds; 8866 this._updateBounds(); 8867 } 8868 }, 8869 8870 _updateBounds: function () { 8871 var w = this._clickTolerance(), 8872 p = new Point(w, w); 8873 8874 if (!this._rawPxBounds) { 8875 return; 8876 } 8877 8878 this._pxBounds = new Bounds([ 8879 this._rawPxBounds.min.subtract(p), 8880 this._rawPxBounds.max.add(p) 8881 ]); 8882 }, 8883 8884 // recursively turns latlngs into a set of rings with projected coordinates 8885 _projectLatlngs: function (latlngs, result, projectedBounds) { 8886 var flat = latlngs[0] instanceof LatLng, 8887 len = latlngs.length, 8888 i, ring; 8889 8890 if (flat) { 8891 ring = []; 8892 for (i = 0; i < len; i++) { 8893 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8894 projectedBounds.extend(ring[i]); 8895 } 8896 result.push(ring); 8897 } else { 8898 for (i = 0; i < len; i++) { 8899 this._projectLatlngs(latlngs[i], result, projectedBounds); 8900 } 8901 } 8902 }, 8903 8904 // clip polyline by renderer bounds so that we have less to render for performance 8905 _clipPoints: function () { 8906 var bounds = this._renderer._bounds; 8907 8908 this._parts = []; 8909 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8910 return; 8911 } 8912 8913 if (this.options.noClip) { 8914 this._parts = this._rings; 8915 return; 8916 } 8917 8918 var parts = this._parts, 8919 i, j, k, len, len2, segment, points; 8920 8921 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8922 points = this._rings[i]; 8923 8924 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8925 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8926 8927 if (!segment) { continue; } 8928 8929 parts[k] = parts[k] || []; 8930 parts[k].push(segment[0]); 8931 8932 // if segment goes out of screen, or it's the last one, it's the end of the line part 8933 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8934 parts[k].push(segment[1]); 8935 k++; 8936 } 8937 } 8938 } 8939 }, 8940 8941 // simplify each clipped part of the polyline for performance 8942 _simplifyPoints: function () { 8943 var parts = this._parts, 8944 tolerance = this.options.smoothFactor; 8945 8946 for (var i = 0, len = parts.length; i < len; i++) { 8947 parts[i] = simplify(parts[i], tolerance); 8948 } 8949 }, 8950 8951 _update: function () { 8952 if (!this._map) { return; } 8953 8954 this._clipPoints(); 8955 this._simplifyPoints(); 8956 this._updatePath(); 8957 }, 8958 8959 _updatePath: function () { 8960 this._renderer._updatePoly(this); 8961 }, 8962 8963 // Needed by the `Canvas` renderer for interactivity 8964 _containsPoint: function (p, closed) { 8965 var i, j, k, len, len2, part, 8966 w = this._clickTolerance(); 8967 8968 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8969 8970 // hit detection for polylines 8971 for (i = 0, len = this._parts.length; i < len; i++) { 8972 part = this._parts[i]; 8973 8974 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8975 if (!closed && (j === 0)) { continue; } 8976 8977 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8978 return true; 8979 } 8980 } 8981 } 8982 return false; 8983 } 8984 }); 8985 8986 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8987 // Instantiates a polyline object given an array of geographical points and 8988 // optionally an options object. You can create a `Polyline` object with 8989 // multiple separate lines (`MultiPolyline`) by passing an array of arrays 8990 // of geographic points. 8991 function polyline(latlngs, options) { 8992 return new Polyline(latlngs, options); 8993 } 8994 8995 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8996 Polyline._flat = _flat; 8997 8998 /* 8999 * @class Polygon 9000 * @aka L.Polygon 9001 * @inherits Polyline 9002 * 9003 * A class for drawing polygon overlays on a map. Extends `Polyline`. 9004 * 9005 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one â it's better to filter out such points. 9006 * 9007 * 9008 * @example 9009 * 9010 * ```js 9011 * // create a red polygon from an array of LatLng points 9012 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 9013 * 9014 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 9015 * 9016 * // zoom the map to the polygon 9017 * map.fitBounds(polygon.getBounds()); 9018 * ``` 9019 * 9020 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape: 9021 * 9022 * ```js 9023 * var latlngs = [ 9024 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 9025 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 9026 * ]; 9027 * ``` 9028 * 9029 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 9030 * 9031 * ```js 9032 * var latlngs = [ 9033 * [ // first polygon 9034 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 9035 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 9036 * ], 9037 * [ // second polygon 9038 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 9039 * ] 9040 * ]; 9041 * ``` 9042 */ 9043 9044 var Polygon = Polyline.extend({ 9045 9046 options: { 9047 fill: true 9048 }, 9049 9050 isEmpty: function () { 9051 return !this._latlngs.length || !this._latlngs[0].length; 9052 }, 9053 9054 // @method getCenter(): LatLng 9055 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon. 9056 getCenter: function () { 9057 // throws error when not yet added to map as this center calculation requires projected coordinates 9058 if (!this._map) { 9059 throw new Error('Must add layer to map before using getCenter()'); 9060 } 9061 return polygonCenter(this._defaultShape(), this._map.options.crs); 9062 }, 9063 9064 _convertLatLngs: function (latlngs) { 9065 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 9066 len = result.length; 9067 9068 // remove last point if it equals first one 9069 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 9070 result.pop(); 9071 } 9072 return result; 9073 }, 9074 9075 _setLatLngs: function (latlngs) { 9076 Polyline.prototype._setLatLngs.call(this, latlngs); 9077 if (isFlat(this._latlngs)) { 9078 this._latlngs = [this._latlngs]; 9079 } 9080 }, 9081 9082 _defaultShape: function () { 9083 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 9084 }, 9085 9086 _clipPoints: function () { 9087 // polygons need a different clipping algorithm so we redefine that 9088 9089 var bounds = this._renderer._bounds, 9090 w = this.options.weight, 9091 p = new Point(w, w); 9092 9093 // increase clip padding by stroke width to avoid stroke on clip edges 9094 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 9095 9096 this._parts = []; 9097 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 9098 return; 9099 } 9100 9101 if (this.options.noClip) { 9102 this._parts = this._rings; 9103 return; 9104 } 9105 9106 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 9107 clipped = clipPolygon(this._rings[i], bounds, true); 9108 if (clipped.length) { 9109 this._parts.push(clipped); 9110 } 9111 } 9112 }, 9113 9114 _updatePath: function () { 9115 this._renderer._updatePoly(this, true); 9116 }, 9117 9118 // Needed by the `Canvas` renderer for interactivity 9119 _containsPoint: function (p) { 9120 var inside = false, 9121 part, p1, p2, i, j, k, len, len2; 9122 9123 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 9124 9125 // ray casting algorithm for detecting if point is in polygon 9126 for (i = 0, len = this._parts.length; i < len; i++) { 9127 part = this._parts[i]; 9128 9129 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 9130 p1 = part[j]; 9131 p2 = part[k]; 9132 9133 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { 9134 inside = !inside; 9135 } 9136 } 9137 } 9138 9139 // also check if it's on polygon stroke 9140 return inside || Polyline.prototype._containsPoint.call(this, p, true); 9141 } 9142 9143 }); 9144 9145 9146 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 9147 function polygon(latlngs, options) { 9148 return new Polygon(latlngs, options); 9149 } 9150 9151 /* 9152 * @class GeoJSON 9153 * @aka L.GeoJSON 9154 * @inherits FeatureGroup 9155 * 9156 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 9157 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 9158 * 9159 * @example 9160 * 9161 * ```js 9162 * L.geoJSON(data, { 9163 * style: function (feature) { 9164 * return {color: feature.properties.color}; 9165 * } 9166 * }).bindPopup(function (layer) { 9167 * return layer.feature.properties.description; 9168 * }).addTo(map); 9169 * ``` 9170 */ 9171 9172 var GeoJSON = FeatureGroup.extend({ 9173 9174 /* @section 9175 * @aka GeoJSON options 9176 * 9177 * @option pointToLayer: Function = * 9178 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 9179 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 9180 * The default is to spawn a default `Marker`: 9181 * ```js 9182 * function(geoJsonPoint, latlng) { 9183 * return L.marker(latlng); 9184 * } 9185 * ``` 9186 * 9187 * @option style: Function = * 9188 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, 9189 * called internally when data is added. 9190 * The default value is to not override any defaults: 9191 * ```js 9192 * function (geoJsonFeature) { 9193 * return {} 9194 * } 9195 * ``` 9196 * 9197 * @option onEachFeature: Function = * 9198 * A `Function` that will be called once for each created `Feature`, after it has 9199 * been created and styled. Useful for attaching events and popups to features. 9200 * The default is to do nothing with the newly created layers: 9201 * ```js 9202 * function (feature, layer) {} 9203 * ``` 9204 * 9205 * @option filter: Function = * 9206 * A `Function` that will be used to decide whether to include a feature or not. 9207 * The default is to include all features: 9208 * ```js 9209 * function (geoJsonFeature) { 9210 * return true; 9211 * } 9212 * ``` 9213 * Note: dynamically changing the `filter` option will have effect only on newly 9214 * added data. It will _not_ re-evaluate already included features. 9215 * 9216 * @option coordsToLatLng: Function = * 9217 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 9218 * The default is the `coordsToLatLng` static method. 9219 * 9220 * @option markersInheritOptions: Boolean = false 9221 * Whether default Markers for "Point" type Features inherit from group options. 9222 */ 9223 9224 initialize: function (geojson, options) { 9225 setOptions(this, options); 9226 9227 this._layers = {}; 9228 9229 if (geojson) { 9230 this.addData(geojson); 9231 } 9232 }, 9233 9234 // @method addData( <GeoJSON> data ): this 9235 // Adds a GeoJSON object to the layer. 9236 addData: function (geojson) { 9237 var features = isArray(geojson) ? geojson : geojson.features, 9238 i, len, feature; 9239 9240 if (features) { 9241 for (i = 0, len = features.length; i < len; i++) { 9242 // only add this if geometry or geometries are set and not null 9243 feature = features[i]; 9244 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 9245 this.addData(feature); 9246 } 9247 } 9248 return this; 9249 } 9250 9251 var options = this.options; 9252 9253 if (options.filter && !options.filter(geojson)) { return this; } 9254 9255 var layer = geometryToLayer(geojson, options); 9256 if (!layer) { 9257 return this; 9258 } 9259 layer.feature = asFeature(geojson); 9260 9261 layer.defaultOptions = layer.options; 9262 this.resetStyle(layer); 9263 9264 if (options.onEachFeature) { 9265 options.onEachFeature(geojson, layer); 9266 } 9267 9268 return this.addLayer(layer); 9269 }, 9270 9271 // @method resetStyle( <Path> layer? ): this 9272 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 9273 // If `layer` is omitted, the style of all features in the current layer is reset. 9274 resetStyle: function (layer) { 9275 if (layer === undefined) { 9276 return this.eachLayer(this.resetStyle, this); 9277 } 9278 // reset any custom styles 9279 layer.options = extend({}, layer.defaultOptions); 9280 this._setLayerStyle(layer, this.options.style); 9281 return this; 9282 }, 9283 9284 // @method setStyle( <Function> style ): this 9285 // Changes styles of GeoJSON vector layers with the given style function. 9286 setStyle: function (style) { 9287 return this.eachLayer(function (layer) { 9288 this._setLayerStyle(layer, style); 9289 }, this); 9290 }, 9291 9292 _setLayerStyle: function (layer, style) { 9293 if (layer.setStyle) { 9294 if (typeof style === 'function') { 9295 style = style(layer.feature); 9296 } 9297 layer.setStyle(style); 9298 } 9299 } 9300 }); 9301 9302 // @section 9303 // There are several static functions which can be called without instantiating L.GeoJSON: 9304 9305 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 9306 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 9307 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 9308 // functions if provided as options. 9309 function geometryToLayer(geojson, options) { 9310 9311 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 9312 coords = geometry ? geometry.coordinates : null, 9313 layers = [], 9314 pointToLayer = options && options.pointToLayer, 9315 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 9316 latlng, latlngs, i, len; 9317 9318 if (!coords && !geometry) { 9319 return null; 9320 } 9321 9322 switch (geometry.type) { 9323 case 'Point': 9324 latlng = _coordsToLatLng(coords); 9325 return _pointToLayer(pointToLayer, geojson, latlng, options); 9326 9327 case 'MultiPoint': 9328 for (i = 0, len = coords.length; i < len; i++) { 9329 latlng = _coordsToLatLng(coords[i]); 9330 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 9331 } 9332 return new FeatureGroup(layers); 9333 9334 case 'LineString': 9335 case 'MultiLineString': 9336 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 9337 return new Polyline(latlngs, options); 9338 9339 case 'Polygon': 9340 case 'MultiPolygon': 9341 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 9342 return new Polygon(latlngs, options); 9343 9344 case 'GeometryCollection': 9345 for (i = 0, len = geometry.geometries.length; i < len; i++) { 9346 var geoLayer = geometryToLayer({ 9347 geometry: geometry.geometries[i], 9348 type: 'Feature', 9349 properties: geojson.properties 9350 }, options); 9351 9352 if (geoLayer) { 9353 layers.push(geoLayer); 9354 } 9355 } 9356 return new FeatureGroup(layers); 9357 9358 case 'FeatureCollection': 9359 for (i = 0, len = geometry.features.length; i < len; i++) { 9360 var featureLayer = geometryToLayer(geometry.features[i], options); 9361 9362 if (featureLayer) { 9363 layers.push(featureLayer); 9364 } 9365 } 9366 return new FeatureGroup(layers); 9367 9368 default: 9369 throw new Error('Invalid GeoJSON object.'); 9370 } 9371 } 9372 9373 function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 9374 return pointToLayerFn ? 9375 pointToLayerFn(geojson, latlng) : 9376 new Marker(latlng, options && options.markersInheritOptions && options); 9377 } 9378 9379 // @function coordsToLatLng(coords: Array): LatLng 9380 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 9381 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 9382 function coordsToLatLng(coords) { 9383 return new LatLng(coords[1], coords[0], coords[2]); 9384 } 9385 9386 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 9387 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 9388 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 9389 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 9390 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 9391 var latlngs = []; 9392 9393 for (var i = 0, len = coords.length, latlng; i < len; i++) { 9394 latlng = levelsDeep ? 9395 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 9396 (_coordsToLatLng || coordsToLatLng)(coords[i]); 9397 9398 latlngs.push(latlng); 9399 } 9400 9401 return latlngs; 9402 } 9403 9404 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array 9405 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 9406 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 9407 function latLngToCoords(latlng, precision) { 9408 latlng = toLatLng(latlng); 9409 return latlng.alt !== undefined ? 9410 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 9411 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 9412 } 9413 9414 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array 9415 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 9416 // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default. 9417 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 9418 function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 9419 var coords = []; 9420 9421 for (var i = 0, len = latlngs.length; i < len; i++) { 9422 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion 9423 coords.push(levelsDeep ? 9424 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) : 9425 latLngToCoords(latlngs[i], precision)); 9426 } 9427 9428 if (!levelsDeep && closed && coords.length > 0) { 9429 coords.push(coords[0].slice()); 9430 } 9431 9432 return coords; 9433 } 9434 9435 function getFeature(layer, newGeometry) { 9436 return layer.feature ? 9437 extend({}, layer.feature, {geometry: newGeometry}) : 9438 asFeature(newGeometry); 9439 } 9440 9441 // @function asFeature(geojson: Object): Object 9442 // Normalize GeoJSON geometries/features into GeoJSON features. 9443 function asFeature(geojson) { 9444 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 9445 return geojson; 9446 } 9447 9448 return { 9449 type: 'Feature', 9450 properties: {}, 9451 geometry: geojson 9452 }; 9453 } 9454 9455 var PointToGeoJSON = { 9456 toGeoJSON: function (precision) { 9457 return getFeature(this, { 9458 type: 'Point', 9459 coordinates: latLngToCoords(this.getLatLng(), precision) 9460 }); 9461 } 9462 }; 9463 9464 // @namespace Marker 9465 // @section Other methods 9466 // @method toGeoJSON(precision?: Number|false): Object 9467 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9468 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 9469 Marker.include(PointToGeoJSON); 9470 9471 // @namespace CircleMarker 9472 // @method toGeoJSON(precision?: Number|false): Object 9473 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9474 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 9475 Circle.include(PointToGeoJSON); 9476 CircleMarker.include(PointToGeoJSON); 9477 9478 9479 // @namespace Polyline 9480 // @method toGeoJSON(precision?: Number|false): Object 9481 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9482 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 9483 Polyline.include({ 9484 toGeoJSON: function (precision) { 9485 var multi = !isFlat(this._latlngs); 9486 9487 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 9488 9489 return getFeature(this, { 9490 type: (multi ? 'Multi' : '') + 'LineString', 9491 coordinates: coords 9492 }); 9493 } 9494 }); 9495 9496 // @namespace Polygon 9497 // @method toGeoJSON(precision?: Number|false): Object 9498 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9499 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 9500 Polygon.include({ 9501 toGeoJSON: function (precision) { 9502 var holes = !isFlat(this._latlngs), 9503 multi = holes && !isFlat(this._latlngs[0]); 9504 9505 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 9506 9507 if (!holes) { 9508 coords = [coords]; 9509 } 9510 9511 return getFeature(this, { 9512 type: (multi ? 'Multi' : '') + 'Polygon', 9513 coordinates: coords 9514 }); 9515 } 9516 }); 9517 9518 9519 // @namespace LayerGroup 9520 LayerGroup.include({ 9521 toMultiPoint: function (precision) { 9522 var coords = []; 9523 9524 this.eachLayer(function (layer) { 9525 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 9526 }); 9527 9528 return getFeature(this, { 9529 type: 'MultiPoint', 9530 coordinates: coords 9531 }); 9532 }, 9533 9534 // @method toGeoJSON(precision?: Number|false): Object 9535 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9536 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 9537 toGeoJSON: function (precision) { 9538 9539 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9540 9541 if (type === 'MultiPoint') { 9542 return this.toMultiPoint(precision); 9543 } 9544 9545 var isGeometryCollection = type === 'GeometryCollection', 9546 jsons = []; 9547 9548 this.eachLayer(function (layer) { 9549 if (layer.toGeoJSON) { 9550 var json = layer.toGeoJSON(precision); 9551 if (isGeometryCollection) { 9552 jsons.push(json.geometry); 9553 } else { 9554 var feature = asFeature(json); 9555 // Squash nested feature collections 9556 if (feature.type === 'FeatureCollection') { 9557 jsons.push.apply(jsons, feature.features); 9558 } else { 9559 jsons.push(feature); 9560 } 9561 } 9562 } 9563 }); 9564 9565 if (isGeometryCollection) { 9566 return getFeature(this, { 9567 geometries: jsons, 9568 type: 'GeometryCollection' 9569 }); 9570 } 9571 9572 return { 9573 type: 'FeatureCollection', 9574 features: jsons 9575 }; 9576 } 9577 }); 9578 9579 // @namespace GeoJSON 9580 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9581 // Creates a GeoJSON layer. Optionally accepts an object in 9582 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9583 // (you can alternatively add it later with `addData` method) and an `options` object. 9584 function geoJSON(geojson, options) { 9585 return new GeoJSON(geojson, options); 9586 } 9587 9588 // Backward compatibility. 9589 var geoJson = geoJSON; 9590 9591 /* 9592 * @class ImageOverlay 9593 * @aka L.ImageOverlay 9594 * @inherits Interactive layer 9595 * 9596 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9597 * 9598 * @example 9599 * 9600 * ```js 9601 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9602 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9603 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9604 * ``` 9605 */ 9606 9607 var ImageOverlay = Layer.extend({ 9608 9609 // @section 9610 // @aka ImageOverlay options 9611 options: { 9612 // @option opacity: Number = 1.0 9613 // The opacity of the image overlay. 9614 opacity: 1, 9615 9616 // @option alt: String = '' 9617 // Text for the `alt` attribute of the image (useful for accessibility). 9618 alt: '', 9619 9620 // @option interactive: Boolean = false 9621 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9622 interactive: false, 9623 9624 // @option crossOrigin: Boolean|String = false 9625 // Whether the crossOrigin attribute will be added to the image. 9626 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data. 9627 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9628 crossOrigin: false, 9629 9630 // @option errorOverlayUrl: String = '' 9631 // URL to the overlay image to show in place of the overlay that failed to load. 9632 errorOverlayUrl: '', 9633 9634 // @option zIndex: Number = 1 9635 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9636 zIndex: 1, 9637 9638 // @option className: String = '' 9639 // A custom class name to assign to the image. Empty by default. 9640 className: '' 9641 }, 9642 9643 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9644 this._url = url; 9645 this._bounds = toLatLngBounds(bounds); 9646 9647 setOptions(this, options); 9648 }, 9649 9650 onAdd: function () { 9651 if (!this._image) { 9652 this._initImage(); 9653 9654 if (this.options.opacity < 1) { 9655 this._updateOpacity(); 9656 } 9657 } 9658 9659 if (this.options.interactive) { 9660 addClass(this._image, 'leaflet-interactive'); 9661 this.addInteractiveTarget(this._image); 9662 } 9663 9664 this.getPane().appendChild(this._image); 9665 this._reset(); 9666 }, 9667 9668 onRemove: function () { 9669 remove(this._image); 9670 if (this.options.interactive) { 9671 this.removeInteractiveTarget(this._image); 9672 } 9673 }, 9674 9675 // @method setOpacity(opacity: Number): this 9676 // Sets the opacity of the overlay. 9677 setOpacity: function (opacity) { 9678 this.options.opacity = opacity; 9679 9680 if (this._image) { 9681 this._updateOpacity(); 9682 } 9683 return this; 9684 }, 9685 9686 setStyle: function (styleOpts) { 9687 if (styleOpts.opacity) { 9688 this.setOpacity(styleOpts.opacity); 9689 } 9690 return this; 9691 }, 9692 9693 // @method bringToFront(): this 9694 // Brings the layer to the top of all overlays. 9695 bringToFront: function () { 9696 if (this._map) { 9697 toFront(this._image); 9698 } 9699 return this; 9700 }, 9701 9702 // @method bringToBack(): this 9703 // Brings the layer to the bottom of all overlays. 9704 bringToBack: function () { 9705 if (this._map) { 9706 toBack(this._image); 9707 } 9708 return this; 9709 }, 9710 9711 // @method setUrl(url: String): this 9712 // Changes the URL of the image. 9713 setUrl: function (url) { 9714 this._url = url; 9715 9716 if (this._image) { 9717 this._image.src = url; 9718 } 9719 return this; 9720 }, 9721 9722 // @method setBounds(bounds: LatLngBounds): this 9723 // Update the bounds that this ImageOverlay covers 9724 setBounds: function (bounds) { 9725 this._bounds = toLatLngBounds(bounds); 9726 9727 if (this._map) { 9728 this._reset(); 9729 } 9730 return this; 9731 }, 9732 9733 getEvents: function () { 9734 var events = { 9735 zoom: this._reset, 9736 viewreset: this._reset 9737 }; 9738 9739 if (this._zoomAnimated) { 9740 events.zoomanim = this._animateZoom; 9741 } 9742 9743 return events; 9744 }, 9745 9746 // @method setZIndex(value: Number): this 9747 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9748 setZIndex: function (value) { 9749 this.options.zIndex = value; 9750 this._updateZIndex(); 9751 return this; 9752 }, 9753 9754 // @method getBounds(): LatLngBounds 9755 // Get the bounds that this ImageOverlay covers 9756 getBounds: function () { 9757 return this._bounds; 9758 }, 9759 9760 // @method getElement(): HTMLElement 9761 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9762 // used by this overlay. 9763 getElement: function () { 9764 return this._image; 9765 }, 9766 9767 _initImage: function () { 9768 var wasElementSupplied = this._url.tagName === 'IMG'; 9769 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9770 9771 addClass(img, 'leaflet-image-layer'); 9772 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9773 if (this.options.className) { addClass(img, this.options.className); } 9774 9775 img.onselectstart = falseFn; 9776 img.onmousemove = falseFn; 9777 9778 // @event load: Event 9779 // Fired when the ImageOverlay layer has loaded its image 9780 img.onload = bind(this.fire, this, 'load'); 9781 img.onerror = bind(this._overlayOnError, this, 'error'); 9782 9783 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9784 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9785 } 9786 9787 if (this.options.zIndex) { 9788 this._updateZIndex(); 9789 } 9790 9791 if (wasElementSupplied) { 9792 this._url = img.src; 9793 return; 9794 } 9795 9796 img.src = this._url; 9797 img.alt = this.options.alt; 9798 }, 9799 9800 _animateZoom: function (e) { 9801 var scale = this._map.getZoomScale(e.zoom), 9802 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9803 9804 setTransform(this._image, offset, scale); 9805 }, 9806 9807 _reset: function () { 9808 var image = this._image, 9809 bounds = new Bounds( 9810 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9811 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9812 size = bounds.getSize(); 9813 9814 setPosition(image, bounds.min); 9815 9816 image.style.width = size.x + 'px'; 9817 image.style.height = size.y + 'px'; 9818 }, 9819 9820 _updateOpacity: function () { 9821 setOpacity(this._image, this.options.opacity); 9822 }, 9823 9824 _updateZIndex: function () { 9825 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9826 this._image.style.zIndex = this.options.zIndex; 9827 } 9828 }, 9829 9830 _overlayOnError: function () { 9831 // @event error: Event 9832 // Fired when the ImageOverlay layer fails to load its image 9833 this.fire('error'); 9834 9835 var errorUrl = this.options.errorOverlayUrl; 9836 if (errorUrl && this._url !== errorUrl) { 9837 this._url = errorUrl; 9838 this._image.src = errorUrl; 9839 } 9840 }, 9841 9842 // @method getCenter(): LatLng 9843 // Returns the center of the ImageOverlay. 9844 getCenter: function () { 9845 return this._bounds.getCenter(); 9846 } 9847 }); 9848 9849 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9850 // Instantiates an image overlay object given the URL of the image and the 9851 // geographical bounds it is tied to. 9852 var imageOverlay = function (url, bounds, options) { 9853 return new ImageOverlay(url, bounds, options); 9854 }; 9855 9856 /* 9857 * @class VideoOverlay 9858 * @aka L.VideoOverlay 9859 * @inherits ImageOverlay 9860 * 9861 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9862 * 9863 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9864 * HTML5 element. 9865 * 9866 * @example 9867 * 9868 * ```js 9869 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9870 * videoBounds = [[ 32, -130], [ 13, -100]]; 9871 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9872 * ``` 9873 */ 9874 9875 var VideoOverlay = ImageOverlay.extend({ 9876 9877 // @section 9878 // @aka VideoOverlay options 9879 options: { 9880 // @option autoplay: Boolean = true 9881 // Whether the video starts playing automatically when loaded. 9882 // On some browsers autoplay will only work with `muted: true` 9883 autoplay: true, 9884 9885 // @option loop: Boolean = true 9886 // Whether the video will loop back to the beginning when played. 9887 loop: true, 9888 9889 // @option keepAspectRatio: Boolean = true 9890 // Whether the video will save aspect ratio after the projection. 9891 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 9892 keepAspectRatio: true, 9893 9894 // @option muted: Boolean = false 9895 // Whether the video starts on mute when loaded. 9896 muted: false, 9897 9898 // @option playsInline: Boolean = true 9899 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode. 9900 playsInline: true 9901 }, 9902 9903 _initImage: function () { 9904 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9905 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9906 9907 addClass(vid, 'leaflet-image-layer'); 9908 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9909 if (this.options.className) { addClass(vid, this.options.className); } 9910 9911 vid.onselectstart = falseFn; 9912 vid.onmousemove = falseFn; 9913 9914 // @event load: Event 9915 // Fired when the video has finished loading the first frame 9916 vid.onloadeddata = bind(this.fire, this, 'load'); 9917 9918 if (wasElementSupplied) { 9919 var sourceElements = vid.getElementsByTagName('source'); 9920 var sources = []; 9921 for (var j = 0; j < sourceElements.length; j++) { 9922 sources.push(sourceElements[j].src); 9923 } 9924 9925 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9926 return; 9927 } 9928 9929 if (!isArray(this._url)) { this._url = [this._url]; } 9930 9931 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) { 9932 vid.style['objectFit'] = 'fill'; 9933 } 9934 vid.autoplay = !!this.options.autoplay; 9935 vid.loop = !!this.options.loop; 9936 vid.muted = !!this.options.muted; 9937 vid.playsInline = !!this.options.playsInline; 9938 for (var i = 0; i < this._url.length; i++) { 9939 var source = create$1('source'); 9940 source.src = this._url[i]; 9941 vid.appendChild(source); 9942 } 9943 } 9944 9945 // @method getElement(): HTMLVideoElement 9946 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9947 // used by this overlay. 9948 }); 9949 9950 9951 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9952 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9953 // geographical bounds it is tied to. 9954 9955 function videoOverlay(video, bounds, options) { 9956 return new VideoOverlay(video, bounds, options); 9957 } 9958 9959 /* 9960 * @class SVGOverlay 9961 * @aka L.SVGOverlay 9962 * @inherits ImageOverlay 9963 * 9964 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9965 * 9966 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9967 * 9968 * @example 9969 * 9970 * ```js 9971 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9972 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); 9973 * svgElement.setAttribute('viewBox', "0 0 200 200"); 9974 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>'; 9975 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; 9976 * L.svgOverlay(svgElement, svgElementBounds).addTo(map); 9977 * ``` 9978 */ 9979 9980 var SVGOverlay = ImageOverlay.extend({ 9981 _initImage: function () { 9982 var el = this._image = this._url; 9983 9984 addClass(el, 'leaflet-image-layer'); 9985 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); } 9986 if (this.options.className) { addClass(el, this.options.className); } 9987 9988 el.onselectstart = falseFn; 9989 el.onmousemove = falseFn; 9990 } 9991 9992 // @method getElement(): SVGElement 9993 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 9994 // used by this overlay. 9995 }); 9996 9997 9998 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options) 9999 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to. 10000 // A viewBox attribute is required on the SVG element to zoom in and out properly. 10001 10002 function svgOverlay(el, bounds, options) { 10003 return new SVGOverlay(el, bounds, options); 10004 } 10005 10006 /* 10007 * @class DivOverlay 10008 * @inherits Interactive layer 10009 * @aka L.DivOverlay 10010 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins. 10011 */ 10012 10013 // @namespace DivOverlay 10014 var DivOverlay = Layer.extend({ 10015 10016 // @section 10017 // @aka DivOverlay options 10018 options: { 10019 // @option interactive: Boolean = false 10020 // If true, the popup/tooltip will listen to the mouse events. 10021 interactive: false, 10022 10023 // @option offset: Point = Point(0, 0) 10024 // The offset of the overlay position. 10025 offset: [0, 0], 10026 10027 // @option className: String = '' 10028 // A custom CSS class name to assign to the overlay. 10029 className: '', 10030 10031 // @option pane: String = undefined 10032 // `Map pane` where the overlay will be added. 10033 pane: undefined, 10034 10035 // @option content: String|HTMLElement|Function = '' 10036 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be 10037 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay. 10038 content: '' 10039 }, 10040 10041 initialize: function (options, source) { 10042 if (options && (options instanceof LatLng || isArray(options))) { 10043 this._latlng = toLatLng(options); 10044 setOptions(this, source); 10045 } else { 10046 setOptions(this, options); 10047 this._source = source; 10048 } 10049 if (this.options.content) { 10050 this._content = this.options.content; 10051 } 10052 }, 10053 10054 // @method openOn(map: Map): this 10055 // Adds the overlay to the map. 10056 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`. 10057 openOn: function (map) { 10058 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 10059 if (!map.hasLayer(this)) { 10060 map.addLayer(this); 10061 } 10062 return this; 10063 }, 10064 10065 // @method close(): this 10066 // Closes the overlay. 10067 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)` 10068 // and `layer.closePopup()`/`.closeTooltip()`. 10069 close: function () { 10070 if (this._map) { 10071 this._map.removeLayer(this); 10072 } 10073 return this; 10074 }, 10075 10076 // @method toggle(layer?: Layer): this 10077 // Opens or closes the overlay bound to layer depending on its current state. 10078 // Argument may be omitted only for overlay bound to layer. 10079 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`. 10080 toggle: function (layer) { 10081 if (this._map) { 10082 this.close(); 10083 } else { 10084 if (arguments.length) { 10085 this._source = layer; 10086 } else { 10087 layer = this._source; 10088 } 10089 this._prepareOpen(); 10090 10091 // open the overlay on the map 10092 this.openOn(layer._map); 10093 } 10094 return this; 10095 }, 10096 10097 onAdd: function (map) { 10098 this._zoomAnimated = map._zoomAnimated; 10099 10100 if (!this._container) { 10101 this._initLayout(); 10102 } 10103 10104 if (map._fadeAnimated) { 10105 setOpacity(this._container, 0); 10106 } 10107 10108 clearTimeout(this._removeTimeout); 10109 this.getPane().appendChild(this._container); 10110 this.update(); 10111 10112 if (map._fadeAnimated) { 10113 setOpacity(this._container, 1); 10114 } 10115 10116 this.bringToFront(); 10117 10118 if (this.options.interactive) { 10119 addClass(this._container, 'leaflet-interactive'); 10120 this.addInteractiveTarget(this._container); 10121 } 10122 }, 10123 10124 onRemove: function (map) { 10125 if (map._fadeAnimated) { 10126 setOpacity(this._container, 0); 10127 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 10128 } else { 10129 remove(this._container); 10130 } 10131 10132 if (this.options.interactive) { 10133 removeClass(this._container, 'leaflet-interactive'); 10134 this.removeInteractiveTarget(this._container); 10135 } 10136 }, 10137 10138 // @namespace DivOverlay 10139 // @method getLatLng: LatLng 10140 // Returns the geographical point of the overlay. 10141 getLatLng: function () { 10142 return this._latlng; 10143 }, 10144 10145 // @method setLatLng(latlng: LatLng): this 10146 // Sets the geographical point where the overlay will open. 10147 setLatLng: function (latlng) { 10148 this._latlng = toLatLng(latlng); 10149 if (this._map) { 10150 this._updatePosition(); 10151 this._adjustPan(); 10152 } 10153 return this; 10154 }, 10155 10156 // @method getContent: String|HTMLElement 10157 // Returns the content of the overlay. 10158 getContent: function () { 10159 return this._content; 10160 }, 10161 10162 // @method setContent(htmlContent: String|HTMLElement|Function): this 10163 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function. 10164 // The function should return a `String` or `HTMLElement` to be used in the overlay. 10165 setContent: function (content) { 10166 this._content = content; 10167 this.update(); 10168 return this; 10169 }, 10170 10171 // @method getElement: String|HTMLElement 10172 // Returns the HTML container of the overlay. 10173 getElement: function () { 10174 return this._container; 10175 }, 10176 10177 // @method update: null 10178 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded. 10179 update: function () { 10180 if (!this._map) { return; } 10181 10182 this._container.style.visibility = 'hidden'; 10183 10184 this._updateContent(); 10185 this._updateLayout(); 10186 this._updatePosition(); 10187 10188 this._container.style.visibility = ''; 10189 10190 this._adjustPan(); 10191 }, 10192 10193 getEvents: function () { 10194 var events = { 10195 zoom: this._updatePosition, 10196 viewreset: this._updatePosition 10197 }; 10198 10199 if (this._zoomAnimated) { 10200 events.zoomanim = this._animateZoom; 10201 } 10202 return events; 10203 }, 10204 10205 // @method isOpen: Boolean 10206 // Returns `true` when the overlay is visible on the map. 10207 isOpen: function () { 10208 return !!this._map && this._map.hasLayer(this); 10209 }, 10210 10211 // @method bringToFront: this 10212 // Brings this overlay in front of other overlays (in the same map pane). 10213 bringToFront: function () { 10214 if (this._map) { 10215 toFront(this._container); 10216 } 10217 return this; 10218 }, 10219 10220 // @method bringToBack: this 10221 // Brings this overlay to the back of other overlays (in the same map pane). 10222 bringToBack: function () { 10223 if (this._map) { 10224 toBack(this._container); 10225 } 10226 return this; 10227 }, 10228 10229 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup) 10230 _prepareOpen: function (latlng) { 10231 var source = this._source; 10232 if (!source._map) { return false; } 10233 10234 if (source instanceof FeatureGroup) { 10235 source = null; 10236 var layers = this._source._layers; 10237 for (var id in layers) { 10238 if (layers[id]._map) { 10239 source = layers[id]; 10240 break; 10241 } 10242 } 10243 if (!source) { return false; } // Unable to get source layer. 10244 10245 // set overlay source to this layer 10246 this._source = source; 10247 } 10248 10249 if (!latlng) { 10250 if (source.getCenter) { 10251 latlng = source.getCenter(); 10252 } else if (source.getLatLng) { 10253 latlng = source.getLatLng(); 10254 } else if (source.getBounds) { 10255 latlng = source.getBounds().getCenter(); 10256 } else { 10257 throw new Error('Unable to get source layer LatLng.'); 10258 } 10259 } 10260 this.setLatLng(latlng); 10261 10262 if (this._map) { 10263 // update the overlay (content, layout, etc...) 10264 this.update(); 10265 } 10266 10267 return true; 10268 }, 10269 10270 _updateContent: function () { 10271 if (!this._content) { return; } 10272 10273 var node = this._contentNode; 10274 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 10275 10276 if (typeof content === 'string') { 10277 node.innerHTML = content; 10278 } else { 10279 while (node.hasChildNodes()) { 10280 node.removeChild(node.firstChild); 10281 } 10282 node.appendChild(content); 10283 } 10284 10285 // @namespace DivOverlay 10286 // @section DivOverlay events 10287 // @event contentupdate: Event 10288 // Fired when the content of the overlay is updated 10289 this.fire('contentupdate'); 10290 }, 10291 10292 _updatePosition: function () { 10293 if (!this._map) { return; } 10294 10295 var pos = this._map.latLngToLayerPoint(this._latlng), 10296 offset = toPoint(this.options.offset), 10297 anchor = this._getAnchor(); 10298 10299 if (this._zoomAnimated) { 10300 setPosition(this._container, pos.add(anchor)); 10301 } else { 10302 offset = offset.add(pos).add(anchor); 10303 } 10304 10305 var bottom = this._containerBottom = -offset.y, 10306 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 10307 10308 // bottom position the overlay in case the height of the overlay changes (images loading etc) 10309 this._container.style.bottom = bottom + 'px'; 10310 this._container.style.left = left + 'px'; 10311 }, 10312 10313 _getAnchor: function () { 10314 return [0, 0]; 10315 } 10316 10317 }); 10318 10319 Map.include({ 10320 _initOverlay: function (OverlayClass, content, latlng, options) { 10321 var overlay = content; 10322 if (!(overlay instanceof OverlayClass)) { 10323 overlay = new OverlayClass(options).setContent(content); 10324 } 10325 if (latlng) { 10326 overlay.setLatLng(latlng); 10327 } 10328 return overlay; 10329 } 10330 }); 10331 10332 10333 Layer.include({ 10334 _initOverlay: function (OverlayClass, old, content, options) { 10335 var overlay = content; 10336 if (overlay instanceof OverlayClass) { 10337 setOptions(overlay, options); 10338 overlay._source = this; 10339 } else { 10340 overlay = (old && !options) ? old : new OverlayClass(options, this); 10341 overlay.setContent(content); 10342 } 10343 return overlay; 10344 } 10345 }); 10346 10347 /* 10348 * @class Popup 10349 * @inherits DivOverlay 10350 * @aka L.Popup 10351 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 10352 * open popups while making sure that only one popup is open at one time 10353 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 10354 * 10355 * @example 10356 * 10357 * If you want to just bind a popup to marker click and then open it, it's really easy: 10358 * 10359 * ```js 10360 * marker.bindPopup(popupContent).openPopup(); 10361 * ``` 10362 * Path overlays like polylines also have a `bindPopup` method. 10363 * 10364 * A popup can be also standalone: 10365 * 10366 * ```js 10367 * var popup = L.popup() 10368 * .setLatLng(latlng) 10369 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 10370 * .openOn(map); 10371 * ``` 10372 * or 10373 * ```js 10374 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>') 10375 * .openOn(map); 10376 * ``` 10377 */ 10378 10379 10380 // @namespace Popup 10381 var Popup = DivOverlay.extend({ 10382 10383 // @section 10384 // @aka Popup options 10385 options: { 10386 // @option pane: String = 'popupPane' 10387 // `Map pane` where the popup will be added. 10388 pane: 'popupPane', 10389 10390 // @option offset: Point = Point(0, 7) 10391 // The offset of the popup position. 10392 offset: [0, 7], 10393 10394 // @option maxWidth: Number = 300 10395 // Max width of the popup, in pixels. 10396 maxWidth: 300, 10397 10398 // @option minWidth: Number = 50 10399 // Min width of the popup, in pixels. 10400 minWidth: 50, 10401 10402 // @option maxHeight: Number = null 10403 // If set, creates a scrollable container of the given height 10404 // inside a popup if its content exceeds it. 10405 // The scrollable container can be styled using the 10406 // `leaflet-popup-scrolled` CSS class selector. 10407 maxHeight: null, 10408 10409 // @option autoPan: Boolean = true 10410 // Set it to `false` if you don't want the map to do panning animation 10411 // to fit the opened popup. 10412 autoPan: true, 10413 10414 // @option autoPanPaddingTopLeft: Point = null 10415 // The margin between the popup and the top left corner of the map 10416 // view after autopanning was performed. 10417 autoPanPaddingTopLeft: null, 10418 10419 // @option autoPanPaddingBottomRight: Point = null 10420 // The margin between the popup and the bottom right corner of the map 10421 // view after autopanning was performed. 10422 autoPanPaddingBottomRight: null, 10423 10424 // @option autoPanPadding: Point = Point(5, 5) 10425 // Equivalent of setting both top left and bottom right autopan padding to the same value. 10426 autoPanPadding: [5, 5], 10427 10428 // @option keepInView: Boolean = false 10429 // Set it to `true` if you want to prevent users from panning the popup 10430 // off of the screen while it is open. 10431 keepInView: false, 10432 10433 // @option closeButton: Boolean = true 10434 // Controls the presence of a close button in the popup. 10435 closeButton: true, 10436 10437 // @option autoClose: Boolean = true 10438 // Set it to `false` if you want to override the default behavior of 10439 // the popup closing when another popup is opened. 10440 autoClose: true, 10441 10442 // @option closeOnEscapeKey: Boolean = true 10443 // Set it to `false` if you want to override the default behavior of 10444 // the ESC key for closing of the popup. 10445 closeOnEscapeKey: true, 10446 10447 // @option closeOnClick: Boolean = * 10448 // Set it if you want to override the default behavior of the popup closing when user clicks 10449 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 10450 10451 // @option className: String = '' 10452 // A custom CSS class name to assign to the popup. 10453 className: '' 10454 }, 10455 10456 // @namespace Popup 10457 // @method openOn(map: Map): this 10458 // Alternative to `map.openPopup(popup)`. 10459 // Adds the popup to the map and closes the previous one. 10460 openOn: function (map) { 10461 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 10462 10463 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) { 10464 map.removeLayer(map._popup); 10465 } 10466 map._popup = this; 10467 10468 return DivOverlay.prototype.openOn.call(this, map); 10469 }, 10470 10471 onAdd: function (map) { 10472 DivOverlay.prototype.onAdd.call(this, map); 10473 10474 // @namespace Map 10475 // @section Popup events 10476 // @event popupopen: PopupEvent 10477 // Fired when a popup is opened in the map 10478 map.fire('popupopen', {popup: this}); 10479 10480 if (this._source) { 10481 // @namespace Layer 10482 // @section Popup events 10483 // @event popupopen: PopupEvent 10484 // Fired when a popup bound to this layer is opened 10485 this._source.fire('popupopen', {popup: this}, true); 10486 // For non-path layers, we toggle the popup when clicking 10487 // again the layer, so prevent the map to reopen it. 10488 if (!(this._source instanceof Path)) { 10489 this._source.on('preclick', stopPropagation); 10490 } 10491 } 10492 }, 10493 10494 onRemove: function (map) { 10495 DivOverlay.prototype.onRemove.call(this, map); 10496 10497 // @namespace Map 10498 // @section Popup events 10499 // @event popupclose: PopupEvent 10500 // Fired when a popup in the map is closed 10501 map.fire('popupclose', {popup: this}); 10502 10503 if (this._source) { 10504 // @namespace Layer 10505 // @section Popup events 10506 // @event popupclose: PopupEvent 10507 // Fired when a popup bound to this layer is closed 10508 this._source.fire('popupclose', {popup: this}, true); 10509 if (!(this._source instanceof Path)) { 10510 this._source.off('preclick', stopPropagation); 10511 } 10512 } 10513 }, 10514 10515 getEvents: function () { 10516 var events = DivOverlay.prototype.getEvents.call(this); 10517 10518 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 10519 events.preclick = this.close; 10520 } 10521 10522 if (this.options.keepInView) { 10523 events.moveend = this._adjustPan; 10524 } 10525 10526 return events; 10527 }, 10528 10529 _initLayout: function () { 10530 var prefix = 'leaflet-popup', 10531 container = this._container = create$1('div', 10532 prefix + ' ' + (this.options.className || '') + 10533 ' leaflet-zoom-animated'); 10534 10535 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 10536 this._contentNode = create$1('div', prefix + '-content', wrapper); 10537 10538 disableClickPropagation(container); 10539 disableScrollPropagation(this._contentNode); 10540 on(container, 'contextmenu', stopPropagation); 10541 10542 this._tipContainer = create$1('div', prefix + '-tip-container', container); 10543 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 10544 10545 if (this.options.closeButton) { 10546 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 10547 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399 10548 closeButton.setAttribute('aria-label', 'Close popup'); 10549 closeButton.href = '#close'; 10550 closeButton.innerHTML = '<span aria-hidden="true">×</span>'; 10551 10552 on(closeButton, 'click', function (ev) { 10553 preventDefault(ev); 10554 this.close(); 10555 }, this); 10556 } 10557 }, 10558 10559 _updateLayout: function () { 10560 var container = this._contentNode, 10561 style = container.style; 10562 10563 style.width = ''; 10564 style.whiteSpace = 'nowrap'; 10565 10566 var width = container.offsetWidth; 10567 width = Math.min(width, this.options.maxWidth); 10568 width = Math.max(width, this.options.minWidth); 10569 10570 style.width = (width + 1) + 'px'; 10571 style.whiteSpace = ''; 10572 10573 style.height = ''; 10574 10575 var height = container.offsetHeight, 10576 maxHeight = this.options.maxHeight, 10577 scrolledClass = 'leaflet-popup-scrolled'; 10578 10579 if (maxHeight && height > maxHeight) { 10580 style.height = maxHeight + 'px'; 10581 addClass(container, scrolledClass); 10582 } else { 10583 removeClass(container, scrolledClass); 10584 } 10585 10586 this._containerWidth = this._container.offsetWidth; 10587 }, 10588 10589 _animateZoom: function (e) { 10590 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 10591 anchor = this._getAnchor(); 10592 setPosition(this._container, pos.add(anchor)); 10593 }, 10594 10595 _adjustPan: function () { 10596 if (!this.options.autoPan) { return; } 10597 if (this._map._panAnim) { this._map._panAnim.stop(); } 10598 10599 // We can endlessly recurse if keepInView is set and the view resets. 10600 // Let's guard against that by exiting early if we're responding to our own autopan. 10601 if (this._autopanning) { 10602 this._autopanning = false; 10603 return; 10604 } 10605 10606 var map = this._map, 10607 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 10608 containerHeight = this._container.offsetHeight + marginBottom, 10609 containerWidth = this._containerWidth, 10610 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 10611 10612 layerPos._add(getPosition(this._container)); 10613 10614 var containerPos = map.layerPointToContainerPoint(layerPos), 10615 padding = toPoint(this.options.autoPanPadding), 10616 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 10617 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 10618 size = map.getSize(), 10619 dx = 0, 10620 dy = 0; 10621 10622 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 10623 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 10624 } 10625 if (containerPos.x - dx - paddingTL.x < 0) { // left 10626 dx = containerPos.x - paddingTL.x; 10627 } 10628 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 10629 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 10630 } 10631 if (containerPos.y - dy - paddingTL.y < 0) { // top 10632 dy = containerPos.y - paddingTL.y; 10633 } 10634 10635 // @namespace Map 10636 // @section Popup events 10637 // @event autopanstart: Event 10638 // Fired when the map starts autopanning when opening a popup. 10639 if (dx || dy) { 10640 // Track that we're autopanning, as this function will be re-ran on moveend 10641 if (this.options.keepInView) { 10642 this._autopanning = true; 10643 } 10644 10645 map 10646 .fire('autopanstart') 10647 .panBy([dx, dy]); 10648 } 10649 }, 10650 10651 _getAnchor: function () { 10652 // Where should we anchor the popup on the source layer? 10653 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 10654 } 10655 10656 }); 10657 10658 // @namespace Popup 10659 // @factory L.popup(options?: Popup options, source?: Layer) 10660 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers. 10661 // @alternative 10662 // @factory L.popup(latlng: LatLng, options?: Popup options) 10663 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location. 10664 var popup = function (options, source) { 10665 return new Popup(options, source); 10666 }; 10667 10668 10669 /* @namespace Map 10670 * @section Interaction Options 10671 * @option closePopupOnClick: Boolean = true 10672 * Set it to `false` if you don't want popups to close when user clicks the map. 10673 */ 10674 Map.mergeOptions({ 10675 closePopupOnClick: true 10676 }); 10677 10678 10679 // @namespace Map 10680 // @section Methods for Layers and Controls 10681 Map.include({ 10682 // @method openPopup(popup: Popup): this 10683 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 10684 // @alternative 10685 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 10686 // Creates a popup with the specified content and options and opens it in the given point on a map. 10687 openPopup: function (popup, latlng, options) { 10688 this._initOverlay(Popup, popup, latlng, options) 10689 .openOn(this); 10690 10691 return this; 10692 }, 10693 10694 // @method closePopup(popup?: Popup): this 10695 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 10696 closePopup: function (popup) { 10697 popup = arguments.length ? popup : this._popup; 10698 if (popup) { 10699 popup.close(); 10700 } 10701 return this; 10702 } 10703 }); 10704 10705 /* 10706 * @namespace Layer 10707 * @section Popup methods example 10708 * 10709 * All layers share a set of methods convenient for binding popups to it. 10710 * 10711 * ```js 10712 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 10713 * layer.openPopup(); 10714 * layer.closePopup(); 10715 * ``` 10716 * 10717 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened. 10718 */ 10719 10720 // @section Popup methods 10721 Layer.include({ 10722 10723 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 10724 // Binds a popup to the layer with the passed `content` and sets up the 10725 // necessary event listeners. If a `Function` is passed it will receive 10726 // the layer as the first argument and should return a `String` or `HTMLElement`. 10727 bindPopup: function (content, options) { 10728 this._popup = this._initOverlay(Popup, this._popup, content, options); 10729 if (!this._popupHandlersAdded) { 10730 this.on({ 10731 click: this._openPopup, 10732 keypress: this._onKeyPress, 10733 remove: this.closePopup, 10734 move: this._movePopup 10735 }); 10736 this._popupHandlersAdded = true; 10737 } 10738 10739 return this; 10740 }, 10741 10742 // @method unbindPopup(): this 10743 // Removes the popup previously bound with `bindPopup`. 10744 unbindPopup: function () { 10745 if (this._popup) { 10746 this.off({ 10747 click: this._openPopup, 10748 keypress: this._onKeyPress, 10749 remove: this.closePopup, 10750 move: this._movePopup 10751 }); 10752 this._popupHandlersAdded = false; 10753 this._popup = null; 10754 } 10755 return this; 10756 }, 10757 10758 // @method openPopup(latlng?: LatLng): this 10759 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 10760 openPopup: function (latlng) { 10761 if (this._popup) { 10762 if (!(this instanceof FeatureGroup)) { 10763 this._popup._source = this; 10764 } 10765 if (this._popup._prepareOpen(latlng || this._latlng)) { 10766 // open the popup on the map 10767 this._popup.openOn(this._map); 10768 } 10769 } 10770 return this; 10771 }, 10772 10773 // @method closePopup(): this 10774 // Closes the popup bound to this layer if it is open. 10775 closePopup: function () { 10776 if (this._popup) { 10777 this._popup.close(); 10778 } 10779 return this; 10780 }, 10781 10782 // @method togglePopup(): this 10783 // Opens or closes the popup bound to this layer depending on its current state. 10784 togglePopup: function () { 10785 if (this._popup) { 10786 this._popup.toggle(this); 10787 } 10788 return this; 10789 }, 10790 10791 // @method isPopupOpen(): boolean 10792 // Returns `true` if the popup bound to this layer is currently open. 10793 isPopupOpen: function () { 10794 return (this._popup ? this._popup.isOpen() : false); 10795 }, 10796 10797 // @method setPopupContent(content: String|HTMLElement|Popup): this 10798 // Sets the content of the popup bound to this layer. 10799 setPopupContent: function (content) { 10800 if (this._popup) { 10801 this._popup.setContent(content); 10802 } 10803 return this; 10804 }, 10805 10806 // @method getPopup(): Popup 10807 // Returns the popup bound to this layer. 10808 getPopup: function () { 10809 return this._popup; 10810 }, 10811 10812 _openPopup: function (e) { 10813 if (!this._popup || !this._map) { 10814 return; 10815 } 10816 // prevent map click 10817 stop(e); 10818 10819 var target = e.layer || e.target; 10820 if (this._popup._source === target && !(target instanceof Path)) { 10821 // treat it like a marker and figure out 10822 // if we should toggle it open/closed 10823 if (this._map.hasLayer(this._popup)) { 10824 this.closePopup(); 10825 } else { 10826 this.openPopup(e.latlng); 10827 } 10828 return; 10829 } 10830 this._popup._source = target; 10831 this.openPopup(e.latlng); 10832 }, 10833 10834 _movePopup: function (e) { 10835 this._popup.setLatLng(e.latlng); 10836 }, 10837 10838 _onKeyPress: function (e) { 10839 if (e.originalEvent.keyCode === 13) { 10840 this._openPopup(e); 10841 } 10842 } 10843 }); 10844 10845 /* 10846 * @class Tooltip 10847 * @inherits DivOverlay 10848 * @aka L.Tooltip 10849 * Used to display small texts on top of map layers. 10850 * 10851 * @example 10852 * If you want to just bind a tooltip to marker: 10853 * 10854 * ```js 10855 * marker.bindTooltip("my tooltip text").openTooltip(); 10856 * ``` 10857 * Path overlays like polylines also have a `bindTooltip` method. 10858 * 10859 * A tooltip can be also standalone: 10860 * 10861 * ```js 10862 * var tooltip = L.tooltip() 10863 * .setLatLng(latlng) 10864 * .setContent('Hello world!<br />This is a nice tooltip.') 10865 * .addTo(map); 10866 * ``` 10867 * or 10868 * ```js 10869 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'}) 10870 * .addTo(map); 10871 * ``` 10872 * 10873 * 10874 * Note about tooltip offset. Leaflet takes two options in consideration 10875 * for computing tooltip offsetting: 10876 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10877 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10878 * move it to the bottom. Negatives will move to the left and top. 10879 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10880 * should adapt this value if you use a custom icon. 10881 */ 10882 10883 10884 // @namespace Tooltip 10885 var Tooltip = DivOverlay.extend({ 10886 10887 // @section 10888 // @aka Tooltip options 10889 options: { 10890 // @option pane: String = 'tooltipPane' 10891 // `Map pane` where the tooltip will be added. 10892 pane: 'tooltipPane', 10893 10894 // @option offset: Point = Point(0, 0) 10895 // Optional offset of the tooltip position. 10896 offset: [0, 0], 10897 10898 // @option direction: String = 'auto' 10899 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10900 // `top`, `bottom`, `center`, `auto`. 10901 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10902 // position on the map. 10903 direction: 'auto', 10904 10905 // @option permanent: Boolean = false 10906 // Whether to open the tooltip permanently or only on mouseover. 10907 permanent: false, 10908 10909 // @option sticky: Boolean = false 10910 // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 10911 sticky: false, 10912 10913 // @option opacity: Number = 0.9 10914 // Tooltip container opacity. 10915 opacity: 0.9 10916 }, 10917 10918 onAdd: function (map) { 10919 DivOverlay.prototype.onAdd.call(this, map); 10920 this.setOpacity(this.options.opacity); 10921 10922 // @namespace Map 10923 // @section Tooltip events 10924 // @event tooltipopen: TooltipEvent 10925 // Fired when a tooltip is opened in the map. 10926 map.fire('tooltipopen', {tooltip: this}); 10927 10928 if (this._source) { 10929 this.addEventParent(this._source); 10930 10931 // @namespace Layer 10932 // @section Tooltip events 10933 // @event tooltipopen: TooltipEvent 10934 // Fired when a tooltip bound to this layer is opened. 10935 this._source.fire('tooltipopen', {tooltip: this}, true); 10936 } 10937 }, 10938 10939 onRemove: function (map) { 10940 DivOverlay.prototype.onRemove.call(this, map); 10941 10942 // @namespace Map 10943 // @section Tooltip events 10944 // @event tooltipclose: TooltipEvent 10945 // Fired when a tooltip in the map is closed. 10946 map.fire('tooltipclose', {tooltip: this}); 10947 10948 if (this._source) { 10949 this.removeEventParent(this._source); 10950 10951 // @namespace Layer 10952 // @section Tooltip events 10953 // @event tooltipclose: TooltipEvent 10954 // Fired when a tooltip bound to this layer is closed. 10955 this._source.fire('tooltipclose', {tooltip: this}, true); 10956 } 10957 }, 10958 10959 getEvents: function () { 10960 var events = DivOverlay.prototype.getEvents.call(this); 10961 10962 if (!this.options.permanent) { 10963 events.preclick = this.close; 10964 } 10965 10966 return events; 10967 }, 10968 10969 _initLayout: function () { 10970 var prefix = 'leaflet-tooltip', 10971 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10972 10973 this._contentNode = this._container = create$1('div', className); 10974 10975 this._container.setAttribute('role', 'tooltip'); 10976 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this)); 10977 }, 10978 10979 _updateLayout: function () {}, 10980 10981 _adjustPan: function () {}, 10982 10983 _setPosition: function (pos) { 10984 var subX, subY, 10985 map = this._map, 10986 container = this._container, 10987 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10988 tooltipPoint = map.layerPointToContainerPoint(pos), 10989 direction = this.options.direction, 10990 tooltipWidth = container.offsetWidth, 10991 tooltipHeight = container.offsetHeight, 10992 offset = toPoint(this.options.offset), 10993 anchor = this._getAnchor(); 10994 10995 if (direction === 'top') { 10996 subX = tooltipWidth / 2; 10997 subY = tooltipHeight; 10998 } else if (direction === 'bottom') { 10999 subX = tooltipWidth / 2; 11000 subY = 0; 11001 } else if (direction === 'center') { 11002 subX = tooltipWidth / 2; 11003 subY = tooltipHeight / 2; 11004 } else if (direction === 'right') { 11005 subX = 0; 11006 subY = tooltipHeight / 2; 11007 } else if (direction === 'left') { 11008 subX = tooltipWidth; 11009 subY = tooltipHeight / 2; 11010 } else if (tooltipPoint.x < centerPoint.x) { 11011 direction = 'right'; 11012 subX = 0; 11013 subY = tooltipHeight / 2; 11014 } else { 11015 direction = 'left'; 11016 subX = tooltipWidth + (offset.x + anchor.x) * 2; 11017 subY = tooltipHeight / 2; 11018 } 11019 11020 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor); 11021 11022 removeClass(container, 'leaflet-tooltip-right'); 11023 removeClass(container, 'leaflet-tooltip-left'); 11024 removeClass(container, 'leaflet-tooltip-top'); 11025 removeClass(container, 'leaflet-tooltip-bottom'); 11026 addClass(container, 'leaflet-tooltip-' + direction); 11027 setPosition(container, pos); 11028 }, 11029 11030 _updatePosition: function () { 11031 var pos = this._map.latLngToLayerPoint(this._latlng); 11032 this._setPosition(pos); 11033 }, 11034 11035 setOpacity: function (opacity) { 11036 this.options.opacity = opacity; 11037 11038 if (this._container) { 11039 setOpacity(this._container, opacity); 11040 } 11041 }, 11042 11043 _animateZoom: function (e) { 11044 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 11045 this._setPosition(pos); 11046 }, 11047 11048 _getAnchor: function () { 11049 // Where should we anchor the tooltip on the source layer? 11050 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 11051 } 11052 11053 }); 11054 11055 // @namespace Tooltip 11056 // @factory L.tooltip(options?: Tooltip options, source?: Layer) 11057 // Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers. 11058 // @alternative 11059 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options) 11060 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location. 11061 var tooltip = function (options, source) { 11062 return new Tooltip(options, source); 11063 }; 11064 11065 // @namespace Map 11066 // @section Methods for Layers and Controls 11067 Map.include({ 11068 11069 // @method openTooltip(tooltip: Tooltip): this 11070 // Opens the specified tooltip. 11071 // @alternative 11072 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 11073 // Creates a tooltip with the specified content and options and open it. 11074 openTooltip: function (tooltip, latlng, options) { 11075 this._initOverlay(Tooltip, tooltip, latlng, options) 11076 .openOn(this); 11077 11078 return this; 11079 }, 11080 11081 // @method closeTooltip(tooltip: Tooltip): this 11082 // Closes the tooltip given as parameter. 11083 closeTooltip: function (tooltip) { 11084 tooltip.close(); 11085 return this; 11086 } 11087 11088 }); 11089 11090 /* 11091 * @namespace Layer 11092 * @section Tooltip methods example 11093 * 11094 * All layers share a set of methods convenient for binding tooltips to it. 11095 * 11096 * ```js 11097 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 11098 * layer.openTooltip(); 11099 * layer.closeTooltip(); 11100 * ``` 11101 */ 11102 11103 // @section Tooltip methods 11104 Layer.include({ 11105 11106 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 11107 // Binds a tooltip to the layer with the passed `content` and sets up the 11108 // necessary event listeners. If a `Function` is passed it will receive 11109 // the layer as the first argument and should return a `String` or `HTMLElement`. 11110 bindTooltip: function (content, options) { 11111 11112 if (this._tooltip && this.isTooltipOpen()) { 11113 this.unbindTooltip(); 11114 } 11115 11116 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options); 11117 this._initTooltipInteractions(); 11118 11119 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 11120 this.openTooltip(); 11121 } 11122 11123 return this; 11124 }, 11125 11126 // @method unbindTooltip(): this 11127 // Removes the tooltip previously bound with `bindTooltip`. 11128 unbindTooltip: function () { 11129 if (this._tooltip) { 11130 this._initTooltipInteractions(true); 11131 this.closeTooltip(); 11132 this._tooltip = null; 11133 } 11134 return this; 11135 }, 11136 11137 _initTooltipInteractions: function (remove) { 11138 if (!remove && this._tooltipHandlersAdded) { return; } 11139 var onOff = remove ? 'off' : 'on', 11140 events = { 11141 remove: this.closeTooltip, 11142 move: this._moveTooltip 11143 }; 11144 if (!this._tooltip.options.permanent) { 11145 events.mouseover = this._openTooltip; 11146 events.mouseout = this.closeTooltip; 11147 events.click = this._openTooltip; 11148 if (this._map) { 11149 this._addFocusListeners(); 11150 } else { 11151 events.add = this._addFocusListeners; 11152 } 11153 } else { 11154 events.add = this._openTooltip; 11155 } 11156 if (this._tooltip.options.sticky) { 11157 events.mousemove = this._moveTooltip; 11158 } 11159 this[onOff](events); 11160 this._tooltipHandlersAdded = !remove; 11161 }, 11162 11163 // @method openTooltip(latlng?: LatLng): this 11164 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 11165 openTooltip: function (latlng) { 11166 if (this._tooltip) { 11167 if (!(this instanceof FeatureGroup)) { 11168 this._tooltip._source = this; 11169 } 11170 if (this._tooltip._prepareOpen(latlng)) { 11171 // open the tooltip on the map 11172 this._tooltip.openOn(this._map); 11173 11174 if (this.getElement) { 11175 this._setAriaDescribedByOnLayer(this); 11176 } else if (this.eachLayer) { 11177 this.eachLayer(this._setAriaDescribedByOnLayer, this); 11178 } 11179 } 11180 } 11181 return this; 11182 }, 11183 11184 // @method closeTooltip(): this 11185 // Closes the tooltip bound to this layer if it is open. 11186 closeTooltip: function () { 11187 if (this._tooltip) { 11188 return this._tooltip.close(); 11189 } 11190 }, 11191 11192 // @method toggleTooltip(): this 11193 // Opens or closes the tooltip bound to this layer depending on its current state. 11194 toggleTooltip: function () { 11195 if (this._tooltip) { 11196 this._tooltip.toggle(this); 11197 } 11198 return this; 11199 }, 11200 11201 // @method isTooltipOpen(): boolean 11202 // Returns `true` if the tooltip bound to this layer is currently open. 11203 isTooltipOpen: function () { 11204 return this._tooltip.isOpen(); 11205 }, 11206 11207 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this 11208 // Sets the content of the tooltip bound to this layer. 11209 setTooltipContent: function (content) { 11210 if (this._tooltip) { 11211 this._tooltip.setContent(content); 11212 } 11213 return this; 11214 }, 11215 11216 // @method getTooltip(): Tooltip 11217 // Returns the tooltip bound to this layer. 11218 getTooltip: function () { 11219 return this._tooltip; 11220 }, 11221 11222 _addFocusListeners: function () { 11223 if (this.getElement) { 11224 this._addFocusListenersOnLayer(this); 11225 } else if (this.eachLayer) { 11226 this.eachLayer(this._addFocusListenersOnLayer, this); 11227 } 11228 }, 11229 11230 _addFocusListenersOnLayer: function (layer) { 11231 var el = typeof layer.getElement === 'function' && layer.getElement(); 11232 if (el) { 11233 on(el, 'focus', function () { 11234 this._tooltip._source = layer; 11235 this.openTooltip(); 11236 }, this); 11237 on(el, 'blur', this.closeTooltip, this); 11238 } 11239 }, 11240 11241 _setAriaDescribedByOnLayer: function (layer) { 11242 var el = typeof layer.getElement === 'function' && layer.getElement(); 11243 if (el) { 11244 el.setAttribute('aria-describedby', this._tooltip._container.id); 11245 } 11246 }, 11247 11248 11249 _openTooltip: function (e) { 11250 if (!this._tooltip || !this._map) { 11251 return; 11252 } 11253 11254 // If the map is moving, we will show the tooltip after it's done. 11255 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) { 11256 this._openOnceFlag = true; 11257 var that = this; 11258 this._map.once('moveend', function () { 11259 that._openOnceFlag = false; 11260 that._openTooltip(e); 11261 }); 11262 return; 11263 } 11264 11265 this._tooltip._source = e.layer || e.target; 11266 11267 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined); 11268 }, 11269 11270 _moveTooltip: function (e) { 11271 var latlng = e.latlng, containerPoint, layerPoint; 11272 if (this._tooltip.options.sticky && e.originalEvent) { 11273 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 11274 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 11275 latlng = this._map.layerPointToLatLng(layerPoint); 11276 } 11277 this._tooltip.setLatLng(latlng); 11278 } 11279 }); 11280 11281 /* 11282 * @class DivIcon 11283 * @aka L.DivIcon 11284 * @inherits Icon 11285 * 11286 * Represents a lightweight icon for markers that uses a simple `<div>` 11287 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 11288 * 11289 * @example 11290 * ```js 11291 * var myIcon = L.divIcon({className: 'my-div-icon'}); 11292 * // you can set .my-div-icon styles in CSS 11293 * 11294 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 11295 * ``` 11296 * 11297 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 11298 */ 11299 11300 var DivIcon = Icon.extend({ 11301 options: { 11302 // @section 11303 // @aka DivIcon options 11304 iconSize: [12, 12], // also can be set through CSS 11305 11306 // iconAnchor: (Point), 11307 // popupAnchor: (Point), 11308 11309 // @option html: String|HTMLElement = '' 11310 // Custom HTML code to put inside the div element, empty by default. Alternatively, 11311 // an instance of `HTMLElement`. 11312 html: false, 11313 11314 // @option bgPos: Point = [0, 0] 11315 // Optional relative position of the background, in pixels 11316 bgPos: null, 11317 11318 className: 'leaflet-div-icon' 11319 }, 11320 11321 createIcon: function (oldIcon) { 11322 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 11323 options = this.options; 11324 11325 if (options.html instanceof Element) { 11326 empty(div); 11327 div.appendChild(options.html); 11328 } else { 11329 div.innerHTML = options.html !== false ? options.html : ''; 11330 } 11331 11332 if (options.bgPos) { 11333 var bgPos = toPoint(options.bgPos); 11334 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 11335 } 11336 this._setIconStyles(div, 'icon'); 11337 11338 return div; 11339 }, 11340 11341 createShadow: function () { 11342 return null; 11343 } 11344 }); 11345 11346 // @factory L.divIcon(options: DivIcon options) 11347 // Creates a `DivIcon` instance with the given options. 11348 function divIcon(options) { 11349 return new DivIcon(options); 11350 } 11351 11352 Icon.Default = IconDefault; 11353 11354 /* 11355 * @class GridLayer 11356 * @inherits Layer 11357 * @aka L.GridLayer 11358 * 11359 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 11360 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you. 11361 * 11362 * 11363 * @section Synchronous usage 11364 * @example 11365 * 11366 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile. 11367 * 11368 * ```js 11369 * var CanvasLayer = L.GridLayer.extend({ 11370 * createTile: function(coords){ 11371 * // create a <canvas> element for drawing 11372 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 11373 * 11374 * // setup tile width and height according to the options 11375 * var size = this.getTileSize(); 11376 * tile.width = size.x; 11377 * tile.height = size.y; 11378 * 11379 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 11380 * var ctx = tile.getContext('2d'); 11381 * 11382 * // return the tile so it can be rendered on screen 11383 * return tile; 11384 * } 11385 * }); 11386 * ``` 11387 * 11388 * @section Asynchronous usage 11389 * @example 11390 * 11391 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback. 11392 * 11393 * ```js 11394 * var CanvasLayer = L.GridLayer.extend({ 11395 * createTile: function(coords, done){ 11396 * var error; 11397 * 11398 * // create a <canvas> element for drawing 11399 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 11400 * 11401 * // setup tile width and height according to the options 11402 * var size = this.getTileSize(); 11403 * tile.width = size.x; 11404 * tile.height = size.y; 11405 * 11406 * // draw something asynchronously and pass the tile to the done() callback 11407 * setTimeout(function() { 11408 * done(error, tile); 11409 * }, 1000); 11410 * 11411 * return tile; 11412 * } 11413 * }); 11414 * ``` 11415 * 11416 * @section 11417 */ 11418 11419 11420 var GridLayer = Layer.extend({ 11421 11422 // @section 11423 // @aka GridLayer options 11424 options: { 11425 // @option tileSize: Number|Point = 256 11426 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 11427 tileSize: 256, 11428 11429 // @option opacity: Number = 1.0 11430 // Opacity of the tiles. Can be used in the `createTile()` function. 11431 opacity: 1, 11432 11433 // @option updateWhenIdle: Boolean = (depends) 11434 // Load new tiles only when panning ends. 11435 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 11436 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 11437 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 11438 updateWhenIdle: Browser.mobile, 11439 11440 // @option updateWhenZooming: Boolean = true 11441 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends. 11442 updateWhenZooming: true, 11443 11444 // @option updateInterval: Number = 200 11445 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 11446 updateInterval: 200, 11447 11448 // @option zIndex: Number = 1 11449 // The explicit zIndex of the tile layer. 11450 zIndex: 1, 11451 11452 // @option bounds: LatLngBounds = undefined 11453 // If set, tiles will only be loaded inside the set `LatLngBounds`. 11454 bounds: null, 11455 11456 // @option minZoom: Number = 0 11457 // The minimum zoom level down to which this layer will be displayed (inclusive). 11458 minZoom: 0, 11459 11460 // @option maxZoom: Number = undefined 11461 // The maximum zoom level up to which this layer will be displayed (inclusive). 11462 maxZoom: undefined, 11463 11464 // @option maxNativeZoom: Number = undefined 11465 // Maximum zoom number the tile source has available. If it is specified, 11466 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 11467 // from `maxNativeZoom` level and auto-scaled. 11468 maxNativeZoom: undefined, 11469 11470 // @option minNativeZoom: Number = undefined 11471 // Minimum zoom number the tile source has available. If it is specified, 11472 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 11473 // from `minNativeZoom` level and auto-scaled. 11474 minNativeZoom: undefined, 11475 11476 // @option noWrap: Boolean = false 11477 // Whether the layer is wrapped around the antimeridian. If `true`, the 11478 // GridLayer will only be displayed once at low zoom levels. Has no 11479 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 11480 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 11481 // tiles outside the CRS limits. 11482 noWrap: false, 11483 11484 // @option pane: String = 'tilePane' 11485 // `Map pane` where the grid layer will be added. 11486 pane: 'tilePane', 11487 11488 // @option className: String = '' 11489 // A custom class name to assign to the tile layer. Empty by default. 11490 className: '', 11491 11492 // @option keepBuffer: Number = 2 11493 // When panning the map, keep this many rows and columns of tiles before unloading them. 11494 keepBuffer: 2 11495 }, 11496 11497 initialize: function (options) { 11498 setOptions(this, options); 11499 }, 11500 11501 onAdd: function () { 11502 this._initContainer(); 11503 11504 this._levels = {}; 11505 this._tiles = {}; 11506 11507 this._resetView(); // implicit _update() call 11508 }, 11509 11510 beforeAdd: function (map) { 11511 map._addZoomLimit(this); 11512 }, 11513 11514 onRemove: function (map) { 11515 this._removeAllTiles(); 11516 remove(this._container); 11517 map._removeZoomLimit(this); 11518 this._container = null; 11519 this._tileZoom = undefined; 11520 }, 11521 11522 // @method bringToFront: this 11523 // Brings the tile layer to the top of all tile layers. 11524 bringToFront: function () { 11525 if (this._map) { 11526 toFront(this._container); 11527 this._setAutoZIndex(Math.max); 11528 } 11529 return this; 11530 }, 11531 11532 // @method bringToBack: this 11533 // Brings the tile layer to the bottom of all tile layers. 11534 bringToBack: function () { 11535 if (this._map) { 11536 toBack(this._container); 11537 this._setAutoZIndex(Math.min); 11538 } 11539 return this; 11540 }, 11541 11542 // @method getContainer: HTMLElement 11543 // Returns the HTML element that contains the tiles for this layer. 11544 getContainer: function () { 11545 return this._container; 11546 }, 11547 11548 // @method setOpacity(opacity: Number): this 11549 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 11550 setOpacity: function (opacity) { 11551 this.options.opacity = opacity; 11552 this._updateOpacity(); 11553 return this; 11554 }, 11555 11556 // @method setZIndex(zIndex: Number): this 11557 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 11558 setZIndex: function (zIndex) { 11559 this.options.zIndex = zIndex; 11560 this._updateZIndex(); 11561 11562 return this; 11563 }, 11564 11565 // @method isLoading: Boolean 11566 // Returns `true` if any tile in the grid layer has not finished loading. 11567 isLoading: function () { 11568 return this._loading; 11569 }, 11570 11571 // @method redraw: this 11572 // Causes the layer to clear all the tiles and request them again. 11573 redraw: function () { 11574 if (this._map) { 11575 this._removeAllTiles(); 11576 var tileZoom = this._clampZoom(this._map.getZoom()); 11577 if (tileZoom !== this._tileZoom) { 11578 this._tileZoom = tileZoom; 11579 this._updateLevels(); 11580 } 11581 this._update(); 11582 } 11583 return this; 11584 }, 11585 11586 getEvents: function () { 11587 var events = { 11588 viewprereset: this._invalidateAll, 11589 viewreset: this._resetView, 11590 zoom: this._resetView, 11591 moveend: this._onMoveEnd 11592 }; 11593 11594 if (!this.options.updateWhenIdle) { 11595 // update tiles on move, but not more often than once per given interval 11596 if (!this._onMove) { 11597 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 11598 } 11599 11600 events.move = this._onMove; 11601 } 11602 11603 if (this._zoomAnimated) { 11604 events.zoomanim = this._animateZoom; 11605 } 11606 11607 return events; 11608 }, 11609 11610 // @section Extension methods 11611 // Layers extending `GridLayer` shall reimplement the following method. 11612 // @method createTile(coords: Object, done?: Function): HTMLElement 11613 // Called only internally, must be overridden by classes extending `GridLayer`. 11614 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 11615 // is specified, it must be called when the tile has finished loading and drawing. 11616 createTile: function () { 11617 return document.createElement('div'); 11618 }, 11619 11620 // @section 11621 // @method getTileSize: Point 11622 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 11623 getTileSize: function () { 11624 var s = this.options.tileSize; 11625 return s instanceof Point ? s : new Point(s, s); 11626 }, 11627 11628 _updateZIndex: function () { 11629 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 11630 this._container.style.zIndex = this.options.zIndex; 11631 } 11632 }, 11633 11634 _setAutoZIndex: function (compare) { 11635 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 11636 11637 var layers = this.getPane().children, 11638 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 11639 11640 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 11641 11642 zIndex = layers[i].style.zIndex; 11643 11644 if (layers[i] !== this._container && zIndex) { 11645 edgeZIndex = compare(edgeZIndex, +zIndex); 11646 } 11647 } 11648 11649 if (isFinite(edgeZIndex)) { 11650 this.options.zIndex = edgeZIndex + compare(-1, 1); 11651 this._updateZIndex(); 11652 } 11653 }, 11654 11655 _updateOpacity: function () { 11656 if (!this._map) { return; } 11657 11658 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 11659 if (Browser.ielt9) { return; } 11660 11661 setOpacity(this._container, this.options.opacity); 11662 11663 var now = +new Date(), 11664 nextFrame = false, 11665 willPrune = false; 11666 11667 for (var key in this._tiles) { 11668 var tile = this._tiles[key]; 11669 if (!tile.current || !tile.loaded) { continue; } 11670 11671 var fade = Math.min(1, (now - tile.loaded) / 200); 11672 11673 setOpacity(tile.el, fade); 11674 if (fade < 1) { 11675 nextFrame = true; 11676 } else { 11677 if (tile.active) { 11678 willPrune = true; 11679 } else { 11680 this._onOpaqueTile(tile); 11681 } 11682 tile.active = true; 11683 } 11684 } 11685 11686 if (willPrune && !this._noPrune) { this._pruneTiles(); } 11687 11688 if (nextFrame) { 11689 cancelAnimFrame(this._fadeFrame); 11690 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11691 } 11692 }, 11693 11694 _onOpaqueTile: falseFn, 11695 11696 _initContainer: function () { 11697 if (this._container) { return; } 11698 11699 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 11700 this._updateZIndex(); 11701 11702 if (this.options.opacity < 1) { 11703 this._updateOpacity(); 11704 } 11705 11706 this.getPane().appendChild(this._container); 11707 }, 11708 11709 _updateLevels: function () { 11710 11711 var zoom = this._tileZoom, 11712 maxZoom = this.options.maxZoom; 11713 11714 if (zoom === undefined) { return undefined; } 11715 11716 for (var z in this._levels) { 11717 z = Number(z); 11718 if (this._levels[z].el.children.length || z === zoom) { 11719 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 11720 this._onUpdateLevel(z); 11721 } else { 11722 remove(this._levels[z].el); 11723 this._removeTilesAtZoom(z); 11724 this._onRemoveLevel(z); 11725 delete this._levels[z]; 11726 } 11727 } 11728 11729 var level = this._levels[zoom], 11730 map = this._map; 11731 11732 if (!level) { 11733 level = this._levels[zoom] = {}; 11734 11735 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 11736 level.el.style.zIndex = maxZoom; 11737 11738 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 11739 level.zoom = zoom; 11740 11741 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 11742 11743 // force the browser to consider the newly added element for transition 11744 falseFn(level.el.offsetWidth); 11745 11746 this._onCreateLevel(level); 11747 } 11748 11749 this._level = level; 11750 11751 return level; 11752 }, 11753 11754 _onUpdateLevel: falseFn, 11755 11756 _onRemoveLevel: falseFn, 11757 11758 _onCreateLevel: falseFn, 11759 11760 _pruneTiles: function () { 11761 if (!this._map) { 11762 return; 11763 } 11764 11765 var key, tile; 11766 11767 var zoom = this._map.getZoom(); 11768 if (zoom > this.options.maxZoom || 11769 zoom < this.options.minZoom) { 11770 this._removeAllTiles(); 11771 return; 11772 } 11773 11774 for (key in this._tiles) { 11775 tile = this._tiles[key]; 11776 tile.retain = tile.current; 11777 } 11778 11779 for (key in this._tiles) { 11780 tile = this._tiles[key]; 11781 if (tile.current && !tile.active) { 11782 var coords = tile.coords; 11783 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 11784 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 11785 } 11786 } 11787 } 11788 11789 for (key in this._tiles) { 11790 if (!this._tiles[key].retain) { 11791 this._removeTile(key); 11792 } 11793 } 11794 }, 11795 11796 _removeTilesAtZoom: function (zoom) { 11797 for (var key in this._tiles) { 11798 if (this._tiles[key].coords.z !== zoom) { 11799 continue; 11800 } 11801 this._removeTile(key); 11802 } 11803 }, 11804 11805 _removeAllTiles: function () { 11806 for (var key in this._tiles) { 11807 this._removeTile(key); 11808 } 11809 }, 11810 11811 _invalidateAll: function () { 11812 for (var z in this._levels) { 11813 remove(this._levels[z].el); 11814 this._onRemoveLevel(Number(z)); 11815 delete this._levels[z]; 11816 } 11817 this._removeAllTiles(); 11818 11819 this._tileZoom = undefined; 11820 }, 11821 11822 _retainParent: function (x, y, z, minZoom) { 11823 var x2 = Math.floor(x / 2), 11824 y2 = Math.floor(y / 2), 11825 z2 = z - 1, 11826 coords2 = new Point(+x2, +y2); 11827 coords2.z = +z2; 11828 11829 var key = this._tileCoordsToKey(coords2), 11830 tile = this._tiles[key]; 11831 11832 if (tile && tile.active) { 11833 tile.retain = true; 11834 return true; 11835 11836 } else if (tile && tile.loaded) { 11837 tile.retain = true; 11838 } 11839 11840 if (z2 > minZoom) { 11841 return this._retainParent(x2, y2, z2, minZoom); 11842 } 11843 11844 return false; 11845 }, 11846 11847 _retainChildren: function (x, y, z, maxZoom) { 11848 11849 for (var i = 2 * x; i < 2 * x + 2; i++) { 11850 for (var j = 2 * y; j < 2 * y + 2; j++) { 11851 11852 var coords = new Point(i, j); 11853 coords.z = z + 1; 11854 11855 var key = this._tileCoordsToKey(coords), 11856 tile = this._tiles[key]; 11857 11858 if (tile && tile.active) { 11859 tile.retain = true; 11860 continue; 11861 11862 } else if (tile && tile.loaded) { 11863 tile.retain = true; 11864 } 11865 11866 if (z + 1 < maxZoom) { 11867 this._retainChildren(i, j, z + 1, maxZoom); 11868 } 11869 } 11870 } 11871 }, 11872 11873 _resetView: function (e) { 11874 var animating = e && (e.pinch || e.flyTo); 11875 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 11876 }, 11877 11878 _animateZoom: function (e) { 11879 this._setView(e.center, e.zoom, true, e.noUpdate); 11880 }, 11881 11882 _clampZoom: function (zoom) { 11883 var options = this.options; 11884 11885 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 11886 return options.minNativeZoom; 11887 } 11888 11889 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 11890 return options.maxNativeZoom; 11891 } 11892 11893 return zoom; 11894 }, 11895 11896 _setView: function (center, zoom, noPrune, noUpdate) { 11897 var tileZoom = Math.round(zoom); 11898 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11899 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11900 tileZoom = undefined; 11901 } else { 11902 tileZoom = this._clampZoom(tileZoom); 11903 } 11904 11905 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11906 11907 if (!noUpdate || tileZoomChanged) { 11908 11909 this._tileZoom = tileZoom; 11910 11911 if (this._abortLoading) { 11912 this._abortLoading(); 11913 } 11914 11915 this._updateLevels(); 11916 this._resetGrid(); 11917 11918 if (tileZoom !== undefined) { 11919 this._update(center); 11920 } 11921 11922 if (!noPrune) { 11923 this._pruneTiles(); 11924 } 11925 11926 // Flag to prevent _updateOpacity from pruning tiles during 11927 // a zoom anim or a pinch gesture 11928 this._noPrune = !!noPrune; 11929 } 11930 11931 this._setZoomTransforms(center, zoom); 11932 }, 11933 11934 _setZoomTransforms: function (center, zoom) { 11935 for (var i in this._levels) { 11936 this._setZoomTransform(this._levels[i], center, zoom); 11937 } 11938 }, 11939 11940 _setZoomTransform: function (level, center, zoom) { 11941 var scale = this._map.getZoomScale(zoom, level.zoom), 11942 translate = level.origin.multiplyBy(scale) 11943 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11944 11945 if (Browser.any3d) { 11946 setTransform(level.el, translate, scale); 11947 } else { 11948 setPosition(level.el, translate); 11949 } 11950 }, 11951 11952 _resetGrid: function () { 11953 var map = this._map, 11954 crs = map.options.crs, 11955 tileSize = this._tileSize = this.getTileSize(), 11956 tileZoom = this._tileZoom; 11957 11958 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11959 if (bounds) { 11960 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11961 } 11962 11963 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11964 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11965 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11966 ]; 11967 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11968 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11969 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11970 ]; 11971 }, 11972 11973 _onMoveEnd: function () { 11974 if (!this._map || this._map._animatingZoom) { return; } 11975 11976 this._update(); 11977 }, 11978 11979 _getTiledPixelBounds: function (center) { 11980 var map = this._map, 11981 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11982 scale = map.getZoomScale(mapZoom, this._tileZoom), 11983 pixelCenter = map.project(center, this._tileZoom).floor(), 11984 halfSize = map.getSize().divideBy(scale * 2); 11985 11986 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11987 }, 11988 11989 // Private method to load tiles in the grid's active zoom level according to map bounds 11990 _update: function (center) { 11991 var map = this._map; 11992 if (!map) { return; } 11993 var zoom = this._clampZoom(map.getZoom()); 11994 11995 if (center === undefined) { center = map.getCenter(); } 11996 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11997 11998 var pixelBounds = this._getTiledPixelBounds(center), 11999 tileRange = this._pxBoundsToTileRange(pixelBounds), 12000 tileCenter = tileRange.getCenter(), 12001 queue = [], 12002 margin = this.options.keepBuffer, 12003 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 12004 tileRange.getTopRight().add([margin, -margin])); 12005 12006 // Sanity check: panic if the tile range contains Infinity somewhere. 12007 if (!(isFinite(tileRange.min.x) && 12008 isFinite(tileRange.min.y) && 12009 isFinite(tileRange.max.x) && 12010 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 12011 12012 for (var key in this._tiles) { 12013 var c = this._tiles[key].coords; 12014 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 12015 this._tiles[key].current = false; 12016 } 12017 } 12018 12019 // _update just loads more tiles. If the tile zoom level differs too much 12020 // from the map's, let _setView reset levels and prune old tiles. 12021 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 12022 12023 // create a queue of coordinates to load tiles from 12024 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 12025 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 12026 var coords = new Point(i, j); 12027 coords.z = this._tileZoom; 12028 12029 if (!this._isValidTile(coords)) { continue; } 12030 12031 var tile = this._tiles[this._tileCoordsToKey(coords)]; 12032 if (tile) { 12033 tile.current = true; 12034 } else { 12035 queue.push(coords); 12036 } 12037 } 12038 } 12039 12040 // sort tile queue to load tiles in order of their distance to center 12041 queue.sort(function (a, b) { 12042 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 12043 }); 12044 12045 if (queue.length !== 0) { 12046 // if it's the first batch of tiles to load 12047 if (!this._loading) { 12048 this._loading = true; 12049 // @event loading: Event 12050 // Fired when the grid layer starts loading tiles. 12051 this.fire('loading'); 12052 } 12053 12054 // create DOM fragment to append tiles in one batch 12055 var fragment = document.createDocumentFragment(); 12056 12057 for (i = 0; i < queue.length; i++) { 12058 this._addTile(queue[i], fragment); 12059 } 12060 12061 this._level.el.appendChild(fragment); 12062 } 12063 }, 12064 12065 _isValidTile: function (coords) { 12066 var crs = this._map.options.crs; 12067 12068 if (!crs.infinite) { 12069 // don't load tile if it's out of bounds and not wrapped 12070 var bounds = this._globalTileRange; 12071 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 12072 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 12073 } 12074 12075 if (!this.options.bounds) { return true; } 12076 12077 // don't load tile if it doesn't intersect the bounds in options 12078 var tileBounds = this._tileCoordsToBounds(coords); 12079 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 12080 }, 12081 12082 _keyToBounds: function (key) { 12083 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 12084 }, 12085 12086 _tileCoordsToNwSe: function (coords) { 12087 var map = this._map, 12088 tileSize = this.getTileSize(), 12089 nwPoint = coords.scaleBy(tileSize), 12090 sePoint = nwPoint.add(tileSize), 12091 nw = map.unproject(nwPoint, coords.z), 12092 se = map.unproject(sePoint, coords.z); 12093 return [nw, se]; 12094 }, 12095 12096 // converts tile coordinates to its geographical bounds 12097 _tileCoordsToBounds: function (coords) { 12098 var bp = this._tileCoordsToNwSe(coords), 12099 bounds = new LatLngBounds(bp[0], bp[1]); 12100 12101 if (!this.options.noWrap) { 12102 bounds = this._map.wrapLatLngBounds(bounds); 12103 } 12104 return bounds; 12105 }, 12106 // converts tile coordinates to key for the tile cache 12107 _tileCoordsToKey: function (coords) { 12108 return coords.x + ':' + coords.y + ':' + coords.z; 12109 }, 12110 12111 // converts tile cache key to coordinates 12112 _keyToTileCoords: function (key) { 12113 var k = key.split(':'), 12114 coords = new Point(+k[0], +k[1]); 12115 coords.z = +k[2]; 12116 return coords; 12117 }, 12118 12119 _removeTile: function (key) { 12120 var tile = this._tiles[key]; 12121 if (!tile) { return; } 12122 12123 remove(tile.el); 12124 12125 delete this._tiles[key]; 12126 12127 // @event tileunload: TileEvent 12128 // Fired when a tile is removed (e.g. when a tile goes off the screen). 12129 this.fire('tileunload', { 12130 tile: tile.el, 12131 coords: this._keyToTileCoords(key) 12132 }); 12133 }, 12134 12135 _initTile: function (tile) { 12136 addClass(tile, 'leaflet-tile'); 12137 12138 var tileSize = this.getTileSize(); 12139 tile.style.width = tileSize.x + 'px'; 12140 tile.style.height = tileSize.y + 'px'; 12141 12142 tile.onselectstart = falseFn; 12143 tile.onmousemove = falseFn; 12144 12145 // update opacity on tiles in IE7-8 because of filter inheritance problems 12146 if (Browser.ielt9 && this.options.opacity < 1) { 12147 setOpacity(tile, this.options.opacity); 12148 } 12149 }, 12150 12151 _addTile: function (coords, container) { 12152 var tilePos = this._getTilePos(coords), 12153 key = this._tileCoordsToKey(coords); 12154 12155 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 12156 12157 this._initTile(tile); 12158 12159 // if createTile is defined with a second argument ("done" callback), 12160 // we know that tile is async and will be ready later; otherwise 12161 if (this.createTile.length < 2) { 12162 // mark tile as ready, but delay one frame for opacity animation to happen 12163 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 12164 } 12165 12166 setPosition(tile, tilePos); 12167 12168 // save tile in cache 12169 this._tiles[key] = { 12170 el: tile, 12171 coords: coords, 12172 current: true 12173 }; 12174 12175 container.appendChild(tile); 12176 // @event tileloadstart: TileEvent 12177 // Fired when a tile is requested and starts loading. 12178 this.fire('tileloadstart', { 12179 tile: tile, 12180 coords: coords 12181 }); 12182 }, 12183 12184 _tileReady: function (coords, err, tile) { 12185 if (err) { 12186 // @event tileerror: TileErrorEvent 12187 // Fired when there is an error loading a tile. 12188 this.fire('tileerror', { 12189 error: err, 12190 tile: tile, 12191 coords: coords 12192 }); 12193 } 12194 12195 var key = this._tileCoordsToKey(coords); 12196 12197 tile = this._tiles[key]; 12198 if (!tile) { return; } 12199 12200 tile.loaded = +new Date(); 12201 if (this._map._fadeAnimated) { 12202 setOpacity(tile.el, 0); 12203 cancelAnimFrame(this._fadeFrame); 12204 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 12205 } else { 12206 tile.active = true; 12207 this._pruneTiles(); 12208 } 12209 12210 if (!err) { 12211 addClass(tile.el, 'leaflet-tile-loaded'); 12212 12213 // @event tileload: TileEvent 12214 // Fired when a tile loads. 12215 this.fire('tileload', { 12216 tile: tile.el, 12217 coords: coords 12218 }); 12219 } 12220 12221 if (this._noTilesToLoad()) { 12222 this._loading = false; 12223 // @event load: Event 12224 // Fired when the grid layer loaded all visible tiles. 12225 this.fire('load'); 12226 12227 if (Browser.ielt9 || !this._map._fadeAnimated) { 12228 requestAnimFrame(this._pruneTiles, this); 12229 } else { 12230 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 12231 // to trigger a pruning. 12232 setTimeout(bind(this._pruneTiles, this), 250); 12233 } 12234 } 12235 }, 12236 12237 _getTilePos: function (coords) { 12238 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 12239 }, 12240 12241 _wrapCoords: function (coords) { 12242 var newCoords = new Point( 12243 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 12244 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 12245 newCoords.z = coords.z; 12246 return newCoords; 12247 }, 12248 12249 _pxBoundsToTileRange: function (bounds) { 12250 var tileSize = this.getTileSize(); 12251 return new Bounds( 12252 bounds.min.unscaleBy(tileSize).floor(), 12253 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 12254 }, 12255 12256 _noTilesToLoad: function () { 12257 for (var key in this._tiles) { 12258 if (!this._tiles[key].loaded) { return false; } 12259 } 12260 return true; 12261 } 12262 }); 12263 12264 // @factory L.gridLayer(options?: GridLayer options) 12265 // Creates a new instance of GridLayer with the supplied options. 12266 function gridLayer(options) { 12267 return new GridLayer(options); 12268 } 12269 12270 /* 12271 * @class TileLayer 12272 * @inherits GridLayer 12273 * @aka L.TileLayer 12274 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`. 12275 * 12276 * @example 12277 * 12278 * ```js 12279 * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map); 12280 * ``` 12281 * 12282 * @section URL template 12283 * @example 12284 * 12285 * A string of the following form: 12286 * 12287 * ``` 12288 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 12289 * ``` 12290 * 12291 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` â zoom level, `{x}` and `{y}` â tile coordinates. `{r}` can be used to add "@2x" to the URL to load retina tiles. 12292 * 12293 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 12294 * 12295 * ``` 12296 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 12297 * ``` 12298 */ 12299 12300 12301 var TileLayer = GridLayer.extend({ 12302 12303 // @section 12304 // @aka TileLayer options 12305 options: { 12306 // @option minZoom: Number = 0 12307 // The minimum zoom level down to which this layer will be displayed (inclusive). 12308 minZoom: 0, 12309 12310 // @option maxZoom: Number = 18 12311 // The maximum zoom level up to which this layer will be displayed (inclusive). 12312 maxZoom: 18, 12313 12314 // @option subdomains: String|String[] = 'abc' 12315 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings. 12316 subdomains: 'abc', 12317 12318 // @option errorTileUrl: String = '' 12319 // URL to the tile image to show in place of the tile that failed to load. 12320 errorTileUrl: '', 12321 12322 // @option zoomOffset: Number = 0 12323 // The zoom number used in tile URLs will be offset with this value. 12324 zoomOffset: 0, 12325 12326 // @option tms: Boolean = false 12327 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 12328 tms: false, 12329 12330 // @option zoomReverse: Boolean = false 12331 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 12332 zoomReverse: false, 12333 12334 // @option detectRetina: Boolean = false 12335 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution. 12336 detectRetina: false, 12337 12338 // @option crossOrigin: Boolean|String = false 12339 // Whether the crossOrigin attribute will be added to the tiles. 12340 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. 12341 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 12342 crossOrigin: false, 12343 12344 // @option referrerPolicy: Boolean|String = false 12345 // Whether the referrerPolicy attribute will be added to the tiles. 12346 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided. 12347 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer 12348 // (e.g. to validate an API token). 12349 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values. 12350 referrerPolicy: false 12351 }, 12352 12353 initialize: function (url, options) { 12354 12355 this._url = url; 12356 12357 options = setOptions(this, options); 12358 12359 // detecting retina displays, adjusting tileSize and zoom levels 12360 if (options.detectRetina && Browser.retina && options.maxZoom > 0) { 12361 12362 options.tileSize = Math.floor(options.tileSize / 2); 12363 12364 if (!options.zoomReverse) { 12365 options.zoomOffset++; 12366 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1); 12367 } else { 12368 options.zoomOffset--; 12369 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1); 12370 } 12371 12372 options.minZoom = Math.max(0, options.minZoom); 12373 } else if (!options.zoomReverse) { 12374 // make sure maxZoom is gte minZoom 12375 options.maxZoom = Math.max(options.minZoom, options.maxZoom); 12376 } else { 12377 // make sure minZoom is lte maxZoom 12378 options.minZoom = Math.min(options.maxZoom, options.minZoom); 12379 } 12380 12381 if (typeof options.subdomains === 'string') { 12382 options.subdomains = options.subdomains.split(''); 12383 } 12384 12385 this.on('tileunload', this._onTileRemove); 12386 }, 12387 12388 // @method setUrl(url: String, noRedraw?: Boolean): this 12389 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 12390 // If the URL does not change, the layer will not be redrawn unless 12391 // the noRedraw parameter is set to false. 12392 setUrl: function (url, noRedraw) { 12393 if (this._url === url && noRedraw === undefined) { 12394 noRedraw = true; 12395 } 12396 12397 this._url = url; 12398 12399 if (!noRedraw) { 12400 this.redraw(); 12401 } 12402 return this; 12403 }, 12404 12405 // @method createTile(coords: Object, done?: Function): HTMLElement 12406 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 12407 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 12408 // callback is called when the tile has been loaded. 12409 createTile: function (coords, done) { 12410 var tile = document.createElement('img'); 12411 12412 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 12413 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 12414 12415 if (this.options.crossOrigin || this.options.crossOrigin === '') { 12416 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 12417 } 12418 12419 // for this new option we follow the documented behavior 12420 // more closely by only setting the property when string 12421 if (typeof this.options.referrerPolicy === 'string') { 12422 tile.referrerPolicy = this.options.referrerPolicy; 12423 } 12424 12425 // The alt attribute is set to the empty string, 12426 // allowing screen readers to ignore the decorative image tiles. 12427 // https://www.w3.org/WAI/tutorials/images/decorative/ 12428 // https://www.w3.org/TR/html-aria/#el-img-empty-alt 12429 tile.alt = ''; 12430 12431 tile.src = this.getTileUrl(coords); 12432 12433 return tile; 12434 }, 12435 12436 // @section Extension methods 12437 // @uninheritable 12438 // Layers extending `TileLayer` might reimplement the following method. 12439 // @method getTileUrl(coords: Object): String 12440 // Called only internally, returns the URL for a tile given its coordinates. 12441 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 12442 getTileUrl: function (coords) { 12443 var data = { 12444 r: Browser.retina ? '@2x' : '', 12445 s: this._getSubdomain(coords), 12446 x: coords.x, 12447 y: coords.y, 12448 z: this._getZoomForUrl() 12449 }; 12450 if (this._map && !this._map.options.crs.infinite) { 12451 var invertedY = this._globalTileRange.max.y - coords.y; 12452 if (this.options.tms) { 12453 data['y'] = invertedY; 12454 } 12455 data['-y'] = invertedY; 12456 } 12457 12458 return template(this._url, extend(data, this.options)); 12459 }, 12460 12461 _tileOnLoad: function (done, tile) { 12462 // For https://github.com/Leaflet/Leaflet/issues/3332 12463 if (Browser.ielt9) { 12464 setTimeout(bind(done, this, null, tile), 0); 12465 } else { 12466 done(null, tile); 12467 } 12468 }, 12469 12470 _tileOnError: function (done, tile, e) { 12471 var errorUrl = this.options.errorTileUrl; 12472 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 12473 tile.src = errorUrl; 12474 } 12475 done(e, tile); 12476 }, 12477 12478 _onTileRemove: function (e) { 12479 e.tile.onload = null; 12480 }, 12481 12482 _getZoomForUrl: function () { 12483 var zoom = this._tileZoom, 12484 maxZoom = this.options.maxZoom, 12485 zoomReverse = this.options.zoomReverse, 12486 zoomOffset = this.options.zoomOffset; 12487 12488 if (zoomReverse) { 12489 zoom = maxZoom - zoom; 12490 } 12491 12492 return zoom + zoomOffset; 12493 }, 12494 12495 _getSubdomain: function (tilePoint) { 12496 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 12497 return this.options.subdomains[index]; 12498 }, 12499 12500 // stops loading all tiles in the background layer 12501 _abortLoading: function () { 12502 var i, tile; 12503 for (i in this._tiles) { 12504 if (this._tiles[i].coords.z !== this._tileZoom) { 12505 tile = this._tiles[i].el; 12506 12507 tile.onload = falseFn; 12508 tile.onerror = falseFn; 12509 12510 if (!tile.complete) { 12511 tile.src = emptyImageUrl; 12512 var coords = this._tiles[i].coords; 12513 remove(tile); 12514 delete this._tiles[i]; 12515 // @event tileabort: TileEvent 12516 // Fired when a tile was loading but is now not wanted. 12517 this.fire('tileabort', { 12518 tile: tile, 12519 coords: coords 12520 }); 12521 } 12522 } 12523 } 12524 }, 12525 12526 _removeTile: function (key) { 12527 var tile = this._tiles[key]; 12528 if (!tile) { return; } 12529 12530 // Cancels any pending http requests associated with the tile 12531 tile.el.setAttribute('src', emptyImageUrl); 12532 12533 return GridLayer.prototype._removeTile.call(this, key); 12534 }, 12535 12536 _tileReady: function (coords, err, tile) { 12537 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 12538 return; 12539 } 12540 12541 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 12542 } 12543 }); 12544 12545 12546 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 12547 // Instantiates a tile layer object given a `URL template` and optionally an options object. 12548 12549 function tileLayer(url, options) { 12550 return new TileLayer(url, options); 12551 } 12552 12553 /* 12554 * @class TileLayer.WMS 12555 * @inherits TileLayer 12556 * @aka L.TileLayer.WMS 12557 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 12558 * 12559 * @example 12560 * 12561 * ```js 12562 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 12563 * layers: 'nexrad-n0r-900913', 12564 * format: 'image/png', 12565 * transparent: true, 12566 * attribution: "Weather data © 2012 IEM Nexrad" 12567 * }); 12568 * ``` 12569 */ 12570 12571 var TileLayerWMS = TileLayer.extend({ 12572 12573 // @section 12574 // @aka TileLayer.WMS options 12575 // If any custom options not documented here are used, they will be sent to the 12576 // WMS server as extra parameters in each request URL. This can be useful for 12577 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 12578 defaultWmsParams: { 12579 service: 'WMS', 12580 request: 'GetMap', 12581 12582 // @option layers: String = '' 12583 // **(required)** Comma-separated list of WMS layers to show. 12584 layers: '', 12585 12586 // @option styles: String = '' 12587 // Comma-separated list of WMS styles. 12588 styles: '', 12589 12590 // @option format: String = 'image/jpeg' 12591 // WMS image format (use `'image/png'` for layers with transparency). 12592 format: 'image/jpeg', 12593 12594 // @option transparent: Boolean = false 12595 // If `true`, the WMS service will return images with transparency. 12596 transparent: false, 12597 12598 // @option version: String = '1.1.1' 12599 // Version of the WMS service to use 12600 version: '1.1.1' 12601 }, 12602 12603 options: { 12604 // @option crs: CRS = null 12605 // Coordinate Reference System to use for the WMS requests, defaults to 12606 // map CRS. Don't change this if you're not sure what it means. 12607 crs: null, 12608 12609 // @option uppercase: Boolean = false 12610 // If `true`, WMS request parameter keys will be uppercase. 12611 uppercase: false 12612 }, 12613 12614 initialize: function (url, options) { 12615 12616 this._url = url; 12617 12618 var wmsParams = extend({}, this.defaultWmsParams); 12619 12620 // all keys that are not TileLayer options go to WMS params 12621 for (var i in options) { 12622 if (!(i in this.options)) { 12623 wmsParams[i] = options[i]; 12624 } 12625 } 12626 12627 options = setOptions(this, options); 12628 12629 var realRetina = options.detectRetina && Browser.retina ? 2 : 1; 12630 var tileSize = this.getTileSize(); 12631 wmsParams.width = tileSize.x * realRetina; 12632 wmsParams.height = tileSize.y * realRetina; 12633 12634 this.wmsParams = wmsParams; 12635 }, 12636 12637 onAdd: function (map) { 12638 12639 this._crs = this.options.crs || map.options.crs; 12640 this._wmsVersion = parseFloat(this.wmsParams.version); 12641 12642 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 12643 this.wmsParams[projectionKey] = this._crs.code; 12644 12645 TileLayer.prototype.onAdd.call(this, map); 12646 }, 12647 12648 getTileUrl: function (coords) { 12649 12650 var tileBounds = this._tileCoordsToNwSe(coords), 12651 crs = this._crs, 12652 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 12653 min = bounds.min, 12654 max = bounds.max, 12655 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 12656 [min.y, min.x, max.y, max.x] : 12657 [min.x, min.y, max.x, max.y]).join(','), 12658 url = TileLayer.prototype.getTileUrl.call(this, coords); 12659 return url + 12660 getParamString(this.wmsParams, url, this.options.uppercase) + 12661 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 12662 }, 12663 12664 // @method setParams(params: Object, noRedraw?: Boolean): this 12665 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 12666 setParams: function (params, noRedraw) { 12667 12668 extend(this.wmsParams, params); 12669 12670 if (!noRedraw) { 12671 this.redraw(); 12672 } 12673 12674 return this; 12675 } 12676 }); 12677 12678 12679 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 12680 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 12681 function tileLayerWMS(url, options) { 12682 return new TileLayerWMS(url, options); 12683 } 12684 12685 TileLayer.WMS = TileLayerWMS; 12686 tileLayer.wms = tileLayerWMS; 12687 12688 /* 12689 * @class Renderer 12690 * @inherits Layer 12691 * @aka L.Renderer 12692 * 12693 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 12694 * DOM container of the renderer, its bounds, and its zoom animation. 12695 * 12696 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 12697 * itself can be added or removed to the map. All paths use a renderer, which can 12698 * be implicit (the map will decide the type of renderer and use it automatically) 12699 * or explicit (using the [`renderer`](#path-renderer) option of the path). 12700 * 12701 * Do not use this class directly, use `SVG` and `Canvas` instead. 12702 * 12703 * @event update: Event 12704 * Fired when the renderer updates its bounds, center and zoom, for example when 12705 * its map has moved 12706 */ 12707 12708 var Renderer = Layer.extend({ 12709 12710 // @section 12711 // @aka Renderer options 12712 options: { 12713 // @option padding: Number = 0.1 12714 // How much to extend the clip area around the map view (relative to its size) 12715 // e.g. 0.1 would be 10% of map view in each direction 12716 padding: 0.1 12717 }, 12718 12719 initialize: function (options) { 12720 setOptions(this, options); 12721 stamp(this); 12722 this._layers = this._layers || {}; 12723 }, 12724 12725 onAdd: function () { 12726 if (!this._container) { 12727 this._initContainer(); // defined by renderer implementations 12728 12729 // always keep transform-origin as 0 0 12730 addClass(this._container, 'leaflet-zoom-animated'); 12731 } 12732 12733 this.getPane().appendChild(this._container); 12734 this._update(); 12735 this.on('update', this._updatePaths, this); 12736 }, 12737 12738 onRemove: function () { 12739 this.off('update', this._updatePaths, this); 12740 this._destroyContainer(); 12741 }, 12742 12743 getEvents: function () { 12744 var events = { 12745 viewreset: this._reset, 12746 zoom: this._onZoom, 12747 moveend: this._update, 12748 zoomend: this._onZoomEnd 12749 }; 12750 if (this._zoomAnimated) { 12751 events.zoomanim = this._onAnimZoom; 12752 } 12753 return events; 12754 }, 12755 12756 _onAnimZoom: function (ev) { 12757 this._updateTransform(ev.center, ev.zoom); 12758 }, 12759 12760 _onZoom: function () { 12761 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 12762 }, 12763 12764 _updateTransform: function (center, zoom) { 12765 var scale = this._map.getZoomScale(zoom, this._zoom), 12766 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 12767 currentCenterPoint = this._map.project(this._center, zoom), 12768 12769 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint) 12770 .subtract(this._map._getNewPixelOrigin(center, zoom)); 12771 12772 if (Browser.any3d) { 12773 setTransform(this._container, topLeftOffset, scale); 12774 } else { 12775 setPosition(this._container, topLeftOffset); 12776 } 12777 }, 12778 12779 _reset: function () { 12780 this._update(); 12781 this._updateTransform(this._center, this._zoom); 12782 12783 for (var id in this._layers) { 12784 this._layers[id]._reset(); 12785 } 12786 }, 12787 12788 _onZoomEnd: function () { 12789 for (var id in this._layers) { 12790 this._layers[id]._project(); 12791 } 12792 }, 12793 12794 _updatePaths: function () { 12795 for (var id in this._layers) { 12796 this._layers[id]._update(); 12797 } 12798 }, 12799 12800 _update: function () { 12801 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 12802 // Subclasses are responsible of firing the 'update' event. 12803 var p = this.options.padding, 12804 size = this._map.getSize(), 12805 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 12806 12807 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 12808 12809 this._center = this._map.getCenter(); 12810 this._zoom = this._map.getZoom(); 12811 } 12812 }); 12813 12814 /* 12815 * @class Canvas 12816 * @inherits Renderer 12817 * @aka L.Canvas 12818 * 12819 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 12820 * Inherits `Renderer`. 12821 * 12822 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not 12823 * available in all web browsers, notably IE8, and overlapping geometries might 12824 * not display properly in some edge cases. 12825 * 12826 * @example 12827 * 12828 * Use Canvas by default for all paths in the map: 12829 * 12830 * ```js 12831 * var map = L.map('map', { 12832 * renderer: L.canvas() 12833 * }); 12834 * ``` 12835 * 12836 * Use a Canvas renderer with extra padding for specific vector geometries: 12837 * 12838 * ```js 12839 * var map = L.map('map'); 12840 * var myRenderer = L.canvas({ padding: 0.5 }); 12841 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12842 * var circle = L.circle( center, { renderer: myRenderer } ); 12843 * ``` 12844 */ 12845 12846 var Canvas = Renderer.extend({ 12847 12848 // @section 12849 // @aka Canvas options 12850 options: { 12851 // @option tolerance: Number = 0 12852 // How much to extend the click tolerance around a path/object on the map. 12853 tolerance: 0 12854 }, 12855 12856 getEvents: function () { 12857 var events = Renderer.prototype.getEvents.call(this); 12858 events.viewprereset = this._onViewPreReset; 12859 return events; 12860 }, 12861 12862 _onViewPreReset: function () { 12863 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 12864 this._postponeUpdatePaths = true; 12865 }, 12866 12867 onAdd: function () { 12868 Renderer.prototype.onAdd.call(this); 12869 12870 // Redraw vectors since canvas is cleared upon removal, 12871 // in case of removing the renderer itself from the map. 12872 this._draw(); 12873 }, 12874 12875 _initContainer: function () { 12876 var container = this._container = document.createElement('canvas'); 12877 12878 on(container, 'mousemove', this._onMouseMove, this); 12879 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 12880 on(container, 'mouseout', this._handleMouseOut, this); 12881 container['_leaflet_disable_events'] = true; 12882 12883 this._ctx = container.getContext('2d'); 12884 }, 12885 12886 _destroyContainer: function () { 12887 cancelAnimFrame(this._redrawRequest); 12888 delete this._ctx; 12889 remove(this._container); 12890 off(this._container); 12891 delete this._container; 12892 }, 12893 12894 _updatePaths: function () { 12895 if (this._postponeUpdatePaths) { return; } 12896 12897 var layer; 12898 this._redrawBounds = null; 12899 for (var id in this._layers) { 12900 layer = this._layers[id]; 12901 layer._update(); 12902 } 12903 this._redraw(); 12904 }, 12905 12906 _update: function () { 12907 if (this._map._animatingZoom && this._bounds) { return; } 12908 12909 Renderer.prototype._update.call(this); 12910 12911 var b = this._bounds, 12912 container = this._container, 12913 size = b.getSize(), 12914 m = Browser.retina ? 2 : 1; 12915 12916 setPosition(container, b.min); 12917 12918 // set canvas size (also clearing it); use double size on retina 12919 container.width = m * size.x; 12920 container.height = m * size.y; 12921 container.style.width = size.x + 'px'; 12922 container.style.height = size.y + 'px'; 12923 12924 if (Browser.retina) { 12925 this._ctx.scale(2, 2); 12926 } 12927 12928 // translate so we use the same path coordinates after canvas element moves 12929 this._ctx.translate(-b.min.x, -b.min.y); 12930 12931 // Tell paths to redraw themselves 12932 this.fire('update'); 12933 }, 12934 12935 _reset: function () { 12936 Renderer.prototype._reset.call(this); 12937 12938 if (this._postponeUpdatePaths) { 12939 this._postponeUpdatePaths = false; 12940 this._updatePaths(); 12941 } 12942 }, 12943 12944 _initPath: function (layer) { 12945 this._updateDashArray(layer); 12946 this._layers[stamp(layer)] = layer; 12947 12948 var order = layer._order = { 12949 layer: layer, 12950 prev: this._drawLast, 12951 next: null 12952 }; 12953 if (this._drawLast) { this._drawLast.next = order; } 12954 this._drawLast = order; 12955 this._drawFirst = this._drawFirst || this._drawLast; 12956 }, 12957 12958 _addPath: function (layer) { 12959 this._requestRedraw(layer); 12960 }, 12961 12962 _removePath: function (layer) { 12963 var order = layer._order; 12964 var next = order.next; 12965 var prev = order.prev; 12966 12967 if (next) { 12968 next.prev = prev; 12969 } else { 12970 this._drawLast = prev; 12971 } 12972 if (prev) { 12973 prev.next = next; 12974 } else { 12975 this._drawFirst = next; 12976 } 12977 12978 delete layer._order; 12979 12980 delete this._layers[stamp(layer)]; 12981 12982 this._requestRedraw(layer); 12983 }, 12984 12985 _updatePath: function (layer) { 12986 // Redraw the union of the layer's old pixel 12987 // bounds and the new pixel bounds. 12988 this._extendRedrawBounds(layer); 12989 layer._project(); 12990 layer._update(); 12991 // The redraw will extend the redraw bounds 12992 // with the new pixel bounds. 12993 this._requestRedraw(layer); 12994 }, 12995 12996 _updateStyle: function (layer) { 12997 this._updateDashArray(layer); 12998 this._requestRedraw(layer); 12999 }, 13000 13001 _updateDashArray: function (layer) { 13002 if (typeof layer.options.dashArray === 'string') { 13003 var parts = layer.options.dashArray.split(/[, ]+/), 13004 dashArray = [], 13005 dashValue, 13006 i; 13007 for (i = 0; i < parts.length; i++) { 13008 dashValue = Number(parts[i]); 13009 // Ignore dash array containing invalid lengths 13010 if (isNaN(dashValue)) { return; } 13011 dashArray.push(dashValue); 13012 } 13013 layer.options._dashArray = dashArray; 13014 } else { 13015 layer.options._dashArray = layer.options.dashArray; 13016 } 13017 }, 13018 13019 _requestRedraw: function (layer) { 13020 if (!this._map) { return; } 13021 13022 this._extendRedrawBounds(layer); 13023 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 13024 }, 13025 13026 _extendRedrawBounds: function (layer) { 13027 if (layer._pxBounds) { 13028 var padding = (layer.options.weight || 0) + 1; 13029 this._redrawBounds = this._redrawBounds || new Bounds(); 13030 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 13031 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 13032 } 13033 }, 13034 13035 _redraw: function () { 13036 this._redrawRequest = null; 13037 13038 if (this._redrawBounds) { 13039 this._redrawBounds.min._floor(); 13040 this._redrawBounds.max._ceil(); 13041 } 13042 13043 this._clear(); // clear layers in redraw bounds 13044 this._draw(); // draw layers 13045 13046 this._redrawBounds = null; 13047 }, 13048 13049 _clear: function () { 13050 var bounds = this._redrawBounds; 13051 if (bounds) { 13052 var size = bounds.getSize(); 13053 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 13054 } else { 13055 this._ctx.save(); 13056 this._ctx.setTransform(1, 0, 0, 1, 0, 0); 13057 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 13058 this._ctx.restore(); 13059 } 13060 }, 13061 13062 _draw: function () { 13063 var layer, bounds = this._redrawBounds; 13064 this._ctx.save(); 13065 if (bounds) { 13066 var size = bounds.getSize(); 13067 this._ctx.beginPath(); 13068 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 13069 this._ctx.clip(); 13070 } 13071 13072 this._drawing = true; 13073 13074 for (var order = this._drawFirst; order; order = order.next) { 13075 layer = order.layer; 13076 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 13077 layer._updatePath(); 13078 } 13079 } 13080 13081 this._drawing = false; 13082 13083 this._ctx.restore(); // Restore state before clipping. 13084 }, 13085 13086 _updatePoly: function (layer, closed) { 13087 if (!this._drawing) { return; } 13088 13089 var i, j, len2, p, 13090 parts = layer._parts, 13091 len = parts.length, 13092 ctx = this._ctx; 13093 13094 if (!len) { return; } 13095 13096 ctx.beginPath(); 13097 13098 for (i = 0; i < len; i++) { 13099 for (j = 0, len2 = parts[i].length; j < len2; j++) { 13100 p = parts[i][j]; 13101 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 13102 } 13103 if (closed) { 13104 ctx.closePath(); 13105 } 13106 } 13107 13108 this._fillStroke(ctx, layer); 13109 13110 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 13111 }, 13112 13113 _updateCircle: function (layer) { 13114 13115 if (!this._drawing || layer._empty()) { return; } 13116 13117 var p = layer._point, 13118 ctx = this._ctx, 13119 r = Math.max(Math.round(layer._radius), 1), 13120 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 13121 13122 if (s !== 1) { 13123 ctx.save(); 13124 ctx.scale(1, s); 13125 } 13126 13127 ctx.beginPath(); 13128 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 13129 13130 if (s !== 1) { 13131 ctx.restore(); 13132 } 13133 13134 this._fillStroke(ctx, layer); 13135 }, 13136 13137 _fillStroke: function (ctx, layer) { 13138 var options = layer.options; 13139 13140 if (options.fill) { 13141 ctx.globalAlpha = options.fillOpacity; 13142 ctx.fillStyle = options.fillColor || options.color; 13143 ctx.fill(options.fillRule || 'evenodd'); 13144 } 13145 13146 if (options.stroke && options.weight !== 0) { 13147 if (ctx.setLineDash) { 13148 ctx.setLineDash(layer.options && layer.options._dashArray || []); 13149 } 13150 ctx.globalAlpha = options.opacity; 13151 ctx.lineWidth = options.weight; 13152 ctx.strokeStyle = options.color; 13153 ctx.lineCap = options.lineCap; 13154 ctx.lineJoin = options.lineJoin; 13155 ctx.stroke(); 13156 } 13157 }, 13158 13159 // Canvas obviously doesn't have mouse events for individual drawn objects, 13160 // so we emulate that by calculating what's under the mouse on mousemove/click manually 13161 13162 _onClick: function (e) { 13163 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 13164 13165 for (var order = this._drawFirst; order; order = order.next) { 13166 layer = order.layer; 13167 if (layer.options.interactive && layer._containsPoint(point)) { 13168 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) { 13169 clickedLayer = layer; 13170 } 13171 } 13172 } 13173 this._fireEvent(clickedLayer ? [clickedLayer] : false, e); 13174 }, 13175 13176 _onMouseMove: function (e) { 13177 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 13178 13179 var point = this._map.mouseEventToLayerPoint(e); 13180 this._handleMouseHover(e, point); 13181 }, 13182 13183 13184 _handleMouseOut: function (e) { 13185 var layer = this._hoveredLayer; 13186 if (layer) { 13187 // if we're leaving the layer, fire mouseout 13188 removeClass(this._container, 'leaflet-interactive'); 13189 this._fireEvent([layer], e, 'mouseout'); 13190 this._hoveredLayer = null; 13191 this._mouseHoverThrottled = false; 13192 } 13193 }, 13194 13195 _handleMouseHover: function (e, point) { 13196 if (this._mouseHoverThrottled) { 13197 return; 13198 } 13199 13200 var layer, candidateHoveredLayer; 13201 13202 for (var order = this._drawFirst; order; order = order.next) { 13203 layer = order.layer; 13204 if (layer.options.interactive && layer._containsPoint(point)) { 13205 candidateHoveredLayer = layer; 13206 } 13207 } 13208 13209 if (candidateHoveredLayer !== this._hoveredLayer) { 13210 this._handleMouseOut(e); 13211 13212 if (candidateHoveredLayer) { 13213 addClass(this._container, 'leaflet-interactive'); // change cursor 13214 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 13215 this._hoveredLayer = candidateHoveredLayer; 13216 } 13217 } 13218 13219 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e); 13220 13221 this._mouseHoverThrottled = true; 13222 setTimeout(bind(function () { 13223 this._mouseHoverThrottled = false; 13224 }, this), 32); 13225 }, 13226 13227 _fireEvent: function (layers, e, type) { 13228 this._map._fireDOMEvent(e, type || e.type, layers); 13229 }, 13230 13231 _bringToFront: function (layer) { 13232 var order = layer._order; 13233 13234 if (!order) { return; } 13235 13236 var next = order.next; 13237 var prev = order.prev; 13238 13239 if (next) { 13240 next.prev = prev; 13241 } else { 13242 // Already last 13243 return; 13244 } 13245 if (prev) { 13246 prev.next = next; 13247 } else if (next) { 13248 // Update first entry unless this is the 13249 // single entry 13250 this._drawFirst = next; 13251 } 13252 13253 order.prev = this._drawLast; 13254 this._drawLast.next = order; 13255 13256 order.next = null; 13257 this._drawLast = order; 13258 13259 this._requestRedraw(layer); 13260 }, 13261 13262 _bringToBack: function (layer) { 13263 var order = layer._order; 13264 13265 if (!order) { return; } 13266 13267 var next = order.next; 13268 var prev = order.prev; 13269 13270 if (prev) { 13271 prev.next = next; 13272 } else { 13273 // Already first 13274 return; 13275 } 13276 if (next) { 13277 next.prev = prev; 13278 } else if (prev) { 13279 // Update last entry unless this is the 13280 // single entry 13281 this._drawLast = prev; 13282 } 13283 13284 order.prev = null; 13285 13286 order.next = this._drawFirst; 13287 this._drawFirst.prev = order; 13288 this._drawFirst = order; 13289 13290 this._requestRedraw(layer); 13291 } 13292 }); 13293 13294 // @factory L.canvas(options?: Renderer options) 13295 // Creates a Canvas renderer with the given options. 13296 function canvas(options) { 13297 return Browser.canvas ? new Canvas(options) : null; 13298 } 13299 13300 /* 13301 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 13302 */ 13303 13304 13305 var vmlCreate = (function () { 13306 try { 13307 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 13308 return function (name) { 13309 return document.createElement('<lvml:' + name + ' class="lvml">'); 13310 }; 13311 } catch (e) { 13312 // Do not return fn from catch block so `e` can be garbage collected 13313 // See https://github.com/Leaflet/Leaflet/pull/7279 13314 } 13315 return function (name) { 13316 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 13317 }; 13318 })(); 13319 13320 13321 /* 13322 * @class SVG 13323 * 13324 * 13325 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 13326 * with old versions of Internet Explorer. 13327 */ 13328 13329 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 13330 var vmlMixin = { 13331 13332 _initContainer: function () { 13333 this._container = create$1('div', 'leaflet-vml-container'); 13334 }, 13335 13336 _update: function () { 13337 if (this._map._animatingZoom) { return; } 13338 Renderer.prototype._update.call(this); 13339 this.fire('update'); 13340 }, 13341 13342 _initPath: function (layer) { 13343 var container = layer._container = vmlCreate('shape'); 13344 13345 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 13346 13347 container.coordsize = '1 1'; 13348 13349 layer._path = vmlCreate('path'); 13350 container.appendChild(layer._path); 13351 13352 this._updateStyle(layer); 13353 this._layers[stamp(layer)] = layer; 13354 }, 13355 13356 _addPath: function (layer) { 13357 var container = layer._container; 13358 this._container.appendChild(container); 13359 13360 if (layer.options.interactive) { 13361 layer.addInteractiveTarget(container); 13362 } 13363 }, 13364 13365 _removePath: function (layer) { 13366 var container = layer._container; 13367 remove(container); 13368 layer.removeInteractiveTarget(container); 13369 delete this._layers[stamp(layer)]; 13370 }, 13371 13372 _updateStyle: function (layer) { 13373 var stroke = layer._stroke, 13374 fill = layer._fill, 13375 options = layer.options, 13376 container = layer._container; 13377 13378 container.stroked = !!options.stroke; 13379 container.filled = !!options.fill; 13380 13381 if (options.stroke) { 13382 if (!stroke) { 13383 stroke = layer._stroke = vmlCreate('stroke'); 13384 } 13385 container.appendChild(stroke); 13386 stroke.weight = options.weight + 'px'; 13387 stroke.color = options.color; 13388 stroke.opacity = options.opacity; 13389 13390 if (options.dashArray) { 13391 stroke.dashStyle = isArray(options.dashArray) ? 13392 options.dashArray.join(' ') : 13393 options.dashArray.replace(/( *, *)/g, ' '); 13394 } else { 13395 stroke.dashStyle = ''; 13396 } 13397 stroke.endcap = options.lineCap.replace('butt', 'flat'); 13398 stroke.joinstyle = options.lineJoin; 13399 13400 } else if (stroke) { 13401 container.removeChild(stroke); 13402 layer._stroke = null; 13403 } 13404 13405 if (options.fill) { 13406 if (!fill) { 13407 fill = layer._fill = vmlCreate('fill'); 13408 } 13409 container.appendChild(fill); 13410 fill.color = options.fillColor || options.color; 13411 fill.opacity = options.fillOpacity; 13412 13413 } else if (fill) { 13414 container.removeChild(fill); 13415 layer._fill = null; 13416 } 13417 }, 13418 13419 _updateCircle: function (layer) { 13420 var p = layer._point.round(), 13421 r = Math.round(layer._radius), 13422 r2 = Math.round(layer._radiusY || r); 13423 13424 this._setPath(layer, layer._empty() ? 'M0 0' : 13425 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 13426 }, 13427 13428 _setPath: function (layer, path) { 13429 layer._path.v = path; 13430 }, 13431 13432 _bringToFront: function (layer) { 13433 toFront(layer._container); 13434 }, 13435 13436 _bringToBack: function (layer) { 13437 toBack(layer._container); 13438 } 13439 }; 13440 13441 var create = Browser.vml ? vmlCreate : svgCreate; 13442 13443 /* 13444 * @class SVG 13445 * @inherits Renderer 13446 * @aka L.SVG 13447 * 13448 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 13449 * Inherits `Renderer`. 13450 * 13451 * Due to [technical limitations](https://caniuse.com/svg), SVG is not 13452 * available in all web browsers, notably Android 2.x and 3.x. 13453 * 13454 * Although SVG is not available on IE7 and IE8, these browsers support 13455 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 13456 * (a now deprecated technology), and the SVG renderer will fall back to VML in 13457 * this case. 13458 * 13459 * @example 13460 * 13461 * Use SVG by default for all paths in the map: 13462 * 13463 * ```js 13464 * var map = L.map('map', { 13465 * renderer: L.svg() 13466 * }); 13467 * ``` 13468 * 13469 * Use a SVG renderer with extra padding for specific vector geometries: 13470 * 13471 * ```js 13472 * var map = L.map('map'); 13473 * var myRenderer = L.svg({ padding: 0.5 }); 13474 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 13475 * var circle = L.circle( center, { renderer: myRenderer } ); 13476 * ``` 13477 */ 13478 13479 var SVG = Renderer.extend({ 13480 13481 _initContainer: function () { 13482 this._container = create('svg'); 13483 13484 // makes it possible to click through svg root; we'll reset it back in individual paths 13485 this._container.setAttribute('pointer-events', 'none'); 13486 13487 this._rootGroup = create('g'); 13488 this._container.appendChild(this._rootGroup); 13489 }, 13490 13491 _destroyContainer: function () { 13492 remove(this._container); 13493 off(this._container); 13494 delete this._container; 13495 delete this._rootGroup; 13496 delete this._svgSize; 13497 }, 13498 13499 _update: function () { 13500 if (this._map._animatingZoom && this._bounds) { return; } 13501 13502 Renderer.prototype._update.call(this); 13503 13504 var b = this._bounds, 13505 size = b.getSize(), 13506 container = this._container; 13507 13508 // set size of svg-container if changed 13509 if (!this._svgSize || !this._svgSize.equals(size)) { 13510 this._svgSize = size; 13511 container.setAttribute('width', size.x); 13512 container.setAttribute('height', size.y); 13513 } 13514 13515 // movement: update container viewBox so that we don't have to change coordinates of individual layers 13516 setPosition(container, b.min); 13517 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 13518 13519 this.fire('update'); 13520 }, 13521 13522 // methods below are called by vector layers implementations 13523 13524 _initPath: function (layer) { 13525 var path = layer._path = create('path'); 13526 13527 // @namespace Path 13528 // @option className: String = null 13529 // Custom class name set on an element. Only for SVG renderer. 13530 if (layer.options.className) { 13531 addClass(path, layer.options.className); 13532 } 13533 13534 if (layer.options.interactive) { 13535 addClass(path, 'leaflet-interactive'); 13536 } 13537 13538 this._updateStyle(layer); 13539 this._layers[stamp(layer)] = layer; 13540 }, 13541 13542 _addPath: function (layer) { 13543 if (!this._rootGroup) { this._initContainer(); } 13544 this._rootGroup.appendChild(layer._path); 13545 layer.addInteractiveTarget(layer._path); 13546 }, 13547 13548 _removePath: function (layer) { 13549 remove(layer._path); 13550 layer.removeInteractiveTarget(layer._path); 13551 delete this._layers[stamp(layer)]; 13552 }, 13553 13554 _updatePath: function (layer) { 13555 layer._project(); 13556 layer._update(); 13557 }, 13558 13559 _updateStyle: function (layer) { 13560 var path = layer._path, 13561 options = layer.options; 13562 13563 if (!path) { return; } 13564 13565 if (options.stroke) { 13566 path.setAttribute('stroke', options.color); 13567 path.setAttribute('stroke-opacity', options.opacity); 13568 path.setAttribute('stroke-width', options.weight); 13569 path.setAttribute('stroke-linecap', options.lineCap); 13570 path.setAttribute('stroke-linejoin', options.lineJoin); 13571 13572 if (options.dashArray) { 13573 path.setAttribute('stroke-dasharray', options.dashArray); 13574 } else { 13575 path.removeAttribute('stroke-dasharray'); 13576 } 13577 13578 if (options.dashOffset) { 13579 path.setAttribute('stroke-dashoffset', options.dashOffset); 13580 } else { 13581 path.removeAttribute('stroke-dashoffset'); 13582 } 13583 } else { 13584 path.setAttribute('stroke', 'none'); 13585 } 13586 13587 if (options.fill) { 13588 path.setAttribute('fill', options.fillColor || options.color); 13589 path.setAttribute('fill-opacity', options.fillOpacity); 13590 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 13591 } else { 13592 path.setAttribute('fill', 'none'); 13593 } 13594 }, 13595 13596 _updatePoly: function (layer, closed) { 13597 this._setPath(layer, pointsToPath(layer._parts, closed)); 13598 }, 13599 13600 _updateCircle: function (layer) { 13601 var p = layer._point, 13602 r = Math.max(Math.round(layer._radius), 1), 13603 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 13604 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 13605 13606 // drawing a circle with two half-arcs 13607 var d = layer._empty() ? 'M0 0' : 13608 'M' + (p.x - r) + ',' + p.y + 13609 arc + (r * 2) + ',0 ' + 13610 arc + (-r * 2) + ',0 '; 13611 13612 this._setPath(layer, d); 13613 }, 13614 13615 _setPath: function (layer, path) { 13616 layer._path.setAttribute('d', path); 13617 }, 13618 13619 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 13620 _bringToFront: function (layer) { 13621 toFront(layer._path); 13622 }, 13623 13624 _bringToBack: function (layer) { 13625 toBack(layer._path); 13626 } 13627 }); 13628 13629 if (Browser.vml) { 13630 SVG.include(vmlMixin); 13631 } 13632 13633 // @namespace SVG 13634 // @factory L.svg(options?: Renderer options) 13635 // Creates a SVG renderer with the given options. 13636 function svg(options) { 13637 return Browser.svg || Browser.vml ? new SVG(options) : null; 13638 } 13639 13640 Map.include({ 13641 // @namespace Map; @method getRenderer(layer: Path): Renderer 13642 // Returns the instance of `Renderer` that should be used to render the given 13643 // `Path`. It will ensure that the `renderer` options of the map and paths 13644 // are respected, and that the renderers do exist on the map. 13645 getRenderer: function (layer) { 13646 // @namespace Path; @option renderer: Renderer 13647 // Use this specific instance of `Renderer` for this path. Takes 13648 // precedence over the map's [default renderer](#map-renderer). 13649 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 13650 13651 if (!renderer) { 13652 renderer = this._renderer = this._createRenderer(); 13653 } 13654 13655 if (!this.hasLayer(renderer)) { 13656 this.addLayer(renderer); 13657 } 13658 return renderer; 13659 }, 13660 13661 _getPaneRenderer: function (name) { 13662 if (name === 'overlayPane' || name === undefined) { 13663 return false; 13664 } 13665 13666 var renderer = this._paneRenderers[name]; 13667 if (renderer === undefined) { 13668 renderer = this._createRenderer({pane: name}); 13669 this._paneRenderers[name] = renderer; 13670 } 13671 return renderer; 13672 }, 13673 13674 _createRenderer: function (options) { 13675 // @namespace Map; @option preferCanvas: Boolean = false 13676 // Whether `Path`s should be rendered on a `Canvas` renderer. 13677 // By default, all `Path`s are rendered in a `SVG` renderer. 13678 return (this.options.preferCanvas && canvas(options)) || svg(options); 13679 } 13680 }); 13681 13682 /* 13683 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 13684 */ 13685 13686 /* 13687 * @class Rectangle 13688 * @aka L.Rectangle 13689 * @inherits Polygon 13690 * 13691 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 13692 * 13693 * @example 13694 * 13695 * ```js 13696 * // define rectangle geographical bounds 13697 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 13698 * 13699 * // create an orange rectangle 13700 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 13701 * 13702 * // zoom the map to the rectangle bounds 13703 * map.fitBounds(bounds); 13704 * ``` 13705 * 13706 */ 13707 13708 13709 var Rectangle = Polygon.extend({ 13710 initialize: function (latLngBounds, options) { 13711 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 13712 }, 13713 13714 // @method setBounds(latLngBounds: LatLngBounds): this 13715 // Redraws the rectangle with the passed bounds. 13716 setBounds: function (latLngBounds) { 13717 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 13718 }, 13719 13720 _boundsToLatLngs: function (latLngBounds) { 13721 latLngBounds = toLatLngBounds(latLngBounds); 13722 return [ 13723 latLngBounds.getSouthWest(), 13724 latLngBounds.getNorthWest(), 13725 latLngBounds.getNorthEast(), 13726 latLngBounds.getSouthEast() 13727 ]; 13728 } 13729 }); 13730 13731 13732 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 13733 function rectangle(latLngBounds, options) { 13734 return new Rectangle(latLngBounds, options); 13735 } 13736 13737 SVG.create = create; 13738 SVG.pointsToPath = pointsToPath; 13739 13740 GeoJSON.geometryToLayer = geometryToLayer; 13741 GeoJSON.coordsToLatLng = coordsToLatLng; 13742 GeoJSON.coordsToLatLngs = coordsToLatLngs; 13743 GeoJSON.latLngToCoords = latLngToCoords; 13744 GeoJSON.latLngsToCoords = latLngsToCoords; 13745 GeoJSON.getFeature = getFeature; 13746 GeoJSON.asFeature = asFeature; 13747 13748 /* 13749 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 13750 * (zoom to a selected bounding box), enabled by default. 13751 */ 13752 13753 // @namespace Map 13754 // @section Interaction Options 13755 Map.mergeOptions({ 13756 // @option boxZoom: Boolean = true 13757 // Whether the map can be zoomed to a rectangular area specified by 13758 // dragging the mouse while pressing the shift key. 13759 boxZoom: true 13760 }); 13761 13762 var BoxZoom = Handler.extend({ 13763 initialize: function (map) { 13764 this._map = map; 13765 this._container = map._container; 13766 this._pane = map._panes.overlayPane; 13767 this._resetStateTimeout = 0; 13768 map.on('unload', this._destroy, this); 13769 }, 13770 13771 addHooks: function () { 13772 on(this._container, 'mousedown', this._onMouseDown, this); 13773 }, 13774 13775 removeHooks: function () { 13776 off(this._container, 'mousedown', this._onMouseDown, this); 13777 }, 13778 13779 moved: function () { 13780 return this._moved; 13781 }, 13782 13783 _destroy: function () { 13784 remove(this._pane); 13785 delete this._pane; 13786 }, 13787 13788 _resetState: function () { 13789 this._resetStateTimeout = 0; 13790 this._moved = false; 13791 }, 13792 13793 _clearDeferredResetState: function () { 13794 if (this._resetStateTimeout !== 0) { 13795 clearTimeout(this._resetStateTimeout); 13796 this._resetStateTimeout = 0; 13797 } 13798 }, 13799 13800 _onMouseDown: function (e) { 13801 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 13802 13803 // Clear the deferred resetState if it hasn't executed yet, otherwise it 13804 // will interrupt the interaction and orphan a box element in the container. 13805 this._clearDeferredResetState(); 13806 this._resetState(); 13807 13808 disableTextSelection(); 13809 disableImageDrag(); 13810 13811 this._startPoint = this._map.mouseEventToContainerPoint(e); 13812 13813 on(document, { 13814 contextmenu: stop, 13815 mousemove: this._onMouseMove, 13816 mouseup: this._onMouseUp, 13817 keydown: this._onKeyDown 13818 }, this); 13819 }, 13820 13821 _onMouseMove: function (e) { 13822 if (!this._moved) { 13823 this._moved = true; 13824 13825 this._box = create$1('div', 'leaflet-zoom-box', this._container); 13826 addClass(this._container, 'leaflet-crosshair'); 13827 13828 this._map.fire('boxzoomstart'); 13829 } 13830 13831 this._point = this._map.mouseEventToContainerPoint(e); 13832 13833 var bounds = new Bounds(this._point, this._startPoint), 13834 size = bounds.getSize(); 13835 13836 setPosition(this._box, bounds.min); 13837 13838 this._box.style.width = size.x + 'px'; 13839 this._box.style.height = size.y + 'px'; 13840 }, 13841 13842 _finish: function () { 13843 if (this._moved) { 13844 remove(this._box); 13845 removeClass(this._container, 'leaflet-crosshair'); 13846 } 13847 13848 enableTextSelection(); 13849 enableImageDrag(); 13850 13851 off(document, { 13852 contextmenu: stop, 13853 mousemove: this._onMouseMove, 13854 mouseup: this._onMouseUp, 13855 keydown: this._onKeyDown 13856 }, this); 13857 }, 13858 13859 _onMouseUp: function (e) { 13860 if ((e.which !== 1) && (e.button !== 1)) { return; } 13861 13862 this._finish(); 13863 13864 if (!this._moved) { return; } 13865 // Postpone to next JS tick so internal click event handling 13866 // still see it as "moved". 13867 this._clearDeferredResetState(); 13868 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 13869 13870 var bounds = new LatLngBounds( 13871 this._map.containerPointToLatLng(this._startPoint), 13872 this._map.containerPointToLatLng(this._point)); 13873 13874 this._map 13875 .fitBounds(bounds) 13876 .fire('boxzoomend', {boxZoomBounds: bounds}); 13877 }, 13878 13879 _onKeyDown: function (e) { 13880 if (e.keyCode === 27) { 13881 this._finish(); 13882 this._clearDeferredResetState(); 13883 this._resetState(); 13884 } 13885 } 13886 }); 13887 13888 // @section Handlers 13889 // @property boxZoom: Handler 13890 // Box (shift-drag with mouse) zoom handler. 13891 Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 13892 13893 /* 13894 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 13895 */ 13896 13897 // @namespace Map 13898 // @section Interaction Options 13899 13900 Map.mergeOptions({ 13901 // @option doubleClickZoom: Boolean|String = true 13902 // Whether the map can be zoomed in by double clicking on it and 13903 // zoomed out by double clicking while holding shift. If passed 13904 // `'center'`, double-click zoom will zoom to the center of the 13905 // view regardless of where the mouse was. 13906 doubleClickZoom: true 13907 }); 13908 13909 var DoubleClickZoom = Handler.extend({ 13910 addHooks: function () { 13911 this._map.on('dblclick', this._onDoubleClick, this); 13912 }, 13913 13914 removeHooks: function () { 13915 this._map.off('dblclick', this._onDoubleClick, this); 13916 }, 13917 13918 _onDoubleClick: function (e) { 13919 var map = this._map, 13920 oldZoom = map.getZoom(), 13921 delta = map.options.zoomDelta, 13922 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13923 13924 if (map.options.doubleClickZoom === 'center') { 13925 map.setZoom(zoom); 13926 } else { 13927 map.setZoomAround(e.containerPoint, zoom); 13928 } 13929 } 13930 }); 13931 13932 // @section Handlers 13933 // 13934 // Map properties include interaction handlers that allow you to control 13935 // interaction behavior in runtime, enabling or disabling certain features such 13936 // as dragging or touch zoom (see `Handler` methods). For example: 13937 // 13938 // ```js 13939 // map.doubleClickZoom.disable(); 13940 // ``` 13941 // 13942 // @property doubleClickZoom: Handler 13943 // Double click zoom handler. 13944 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13945 13946 /* 13947 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13948 */ 13949 13950 // @namespace Map 13951 // @section Interaction Options 13952 Map.mergeOptions({ 13953 // @option dragging: Boolean = true 13954 // Whether the map is draggable with mouse/touch or not. 13955 dragging: true, 13956 13957 // @section Panning Inertia Options 13958 // @option inertia: Boolean = * 13959 // If enabled, panning of the map will have an inertia effect where 13960 // the map builds momentum while dragging and continues moving in 13961 // the same direction for some time. Feels especially nice on touch 13962 // devices. Enabled by default. 13963 inertia: true, 13964 13965 // @option inertiaDeceleration: Number = 3000 13966 // The rate with which the inertial movement slows down, in pixels/second². 13967 inertiaDeceleration: 3400, // px/s^2 13968 13969 // @option inertiaMaxSpeed: Number = Infinity 13970 // Max speed of the inertial movement, in pixels/second. 13971 inertiaMaxSpeed: Infinity, // px/s 13972 13973 // @option easeLinearity: Number = 0.2 13974 easeLinearity: 0.2, 13975 13976 // TODO refactor, move to CRS 13977 // @option worldCopyJump: Boolean = false 13978 // With this option enabled, the map tracks when you pan to another "copy" 13979 // of the world and seamlessly jumps to the original one so that all overlays 13980 // like markers and vector layers are still visible. 13981 worldCopyJump: false, 13982 13983 // @option maxBoundsViscosity: Number = 0.0 13984 // If `maxBounds` is set, this option will control how solid the bounds 13985 // are when dragging the map around. The default value of `0.0` allows the 13986 // user to drag outside the bounds at normal speed, higher values will 13987 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13988 // solid, preventing the user from dragging outside the bounds. 13989 maxBoundsViscosity: 0.0 13990 }); 13991 13992 var Drag = Handler.extend({ 13993 addHooks: function () { 13994 if (!this._draggable) { 13995 var map = this._map; 13996 13997 this._draggable = new Draggable(map._mapPane, map._container); 13998 13999 this._draggable.on({ 14000 dragstart: this._onDragStart, 14001 drag: this._onDrag, 14002 dragend: this._onDragEnd 14003 }, this); 14004 14005 this._draggable.on('predrag', this._onPreDragLimit, this); 14006 if (map.options.worldCopyJump) { 14007 this._draggable.on('predrag', this._onPreDragWrap, this); 14008 map.on('zoomend', this._onZoomEnd, this); 14009 14010 map.whenReady(this._onZoomEnd, this); 14011 } 14012 } 14013 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 14014 this._draggable.enable(); 14015 this._positions = []; 14016 this._times = []; 14017 }, 14018 14019 removeHooks: function () { 14020 removeClass(this._map._container, 'leaflet-grab'); 14021 removeClass(this._map._container, 'leaflet-touch-drag'); 14022 this._draggable.disable(); 14023 }, 14024 14025 moved: function () { 14026 return this._draggable && this._draggable._moved; 14027 }, 14028 14029 moving: function () { 14030 return this._draggable && this._draggable._moving; 14031 }, 14032 14033 _onDragStart: function () { 14034 var map = this._map; 14035 14036 map._stop(); 14037 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 14038 var bounds = toLatLngBounds(this._map.options.maxBounds); 14039 14040 this._offsetLimit = toBounds( 14041 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 14042 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 14043 .add(this._map.getSize())); 14044 14045 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 14046 } else { 14047 this._offsetLimit = null; 14048 } 14049 14050 map 14051 .fire('movestart') 14052 .fire('dragstart'); 14053 14054 if (map.options.inertia) { 14055 this._positions = []; 14056 this._times = []; 14057 } 14058 }, 14059 14060 _onDrag: function (e) { 14061 if (this._map.options.inertia) { 14062 var time = this._lastTime = +new Date(), 14063 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 14064 14065 this._positions.push(pos); 14066 this._times.push(time); 14067 14068 this._prunePositions(time); 14069 } 14070 14071 this._map 14072 .fire('move', e) 14073 .fire('drag', e); 14074 }, 14075 14076 _prunePositions: function (time) { 14077 while (this._positions.length > 1 && time - this._times[0] > 50) { 14078 this._positions.shift(); 14079 this._times.shift(); 14080 } 14081 }, 14082 14083 _onZoomEnd: function () { 14084 var pxCenter = this._map.getSize().divideBy(2), 14085 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 14086 14087 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 14088 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 14089 }, 14090 14091 _viscousLimit: function (value, threshold) { 14092 return value - (value - threshold) * this._viscosity; 14093 }, 14094 14095 _onPreDragLimit: function () { 14096 if (!this._viscosity || !this._offsetLimit) { return; } 14097 14098 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 14099 14100 var limit = this._offsetLimit; 14101 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 14102 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 14103 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 14104 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 14105 14106 this._draggable._newPos = this._draggable._startPos.add(offset); 14107 }, 14108 14109 _onPreDragWrap: function () { 14110 // TODO refactor to be able to adjust map pane position after zoom 14111 var worldWidth = this._worldWidth, 14112 halfWidth = Math.round(worldWidth / 2), 14113 dx = this._initialWorldOffset, 14114 x = this._draggable._newPos.x, 14115 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 14116 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 14117 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 14118 14119 this._draggable._absPos = this._draggable._newPos.clone(); 14120 this._draggable._newPos.x = newX; 14121 }, 14122 14123 _onDragEnd: function (e) { 14124 var map = this._map, 14125 options = map.options, 14126 14127 noInertia = !options.inertia || e.noInertia || this._times.length < 2; 14128 14129 map.fire('dragend', e); 14130 14131 if (noInertia) { 14132 map.fire('moveend'); 14133 14134 } else { 14135 this._prunePositions(+new Date()); 14136 14137 var direction = this._lastPos.subtract(this._positions[0]), 14138 duration = (this._lastTime - this._times[0]) / 1000, 14139 ease = options.easeLinearity, 14140 14141 speedVector = direction.multiplyBy(ease / duration), 14142 speed = speedVector.distanceTo([0, 0]), 14143 14144 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 14145 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 14146 14147 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 14148 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 14149 14150 if (!offset.x && !offset.y) { 14151 map.fire('moveend'); 14152 14153 } else { 14154 offset = map._limitOffset(offset, map.options.maxBounds); 14155 14156 requestAnimFrame(function () { 14157 map.panBy(offset, { 14158 duration: decelerationDuration, 14159 easeLinearity: ease, 14160 noMoveStart: true, 14161 animate: true 14162 }); 14163 }); 14164 } 14165 } 14166 } 14167 }); 14168 14169 // @section Handlers 14170 // @property dragging: Handler 14171 // Map dragging handler (by both mouse and touch). 14172 Map.addInitHook('addHandler', 'dragging', Drag); 14173 14174 /* 14175 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 14176 */ 14177 14178 // @namespace Map 14179 // @section Keyboard Navigation Options 14180 Map.mergeOptions({ 14181 // @option keyboard: Boolean = true 14182 // Makes the map focusable and allows users to navigate the map with keyboard 14183 // arrows and `+`/`-` keys. 14184 keyboard: true, 14185 14186 // @option keyboardPanDelta: Number = 80 14187 // Amount of pixels to pan when pressing an arrow key. 14188 keyboardPanDelta: 80 14189 }); 14190 14191 var Keyboard = Handler.extend({ 14192 14193 keyCodes: { 14194 left: [37], 14195 right: [39], 14196 down: [40], 14197 up: [38], 14198 zoomIn: [187, 107, 61, 171], 14199 zoomOut: [189, 109, 54, 173] 14200 }, 14201 14202 initialize: function (map) { 14203 this._map = map; 14204 14205 this._setPanDelta(map.options.keyboardPanDelta); 14206 this._setZoomDelta(map.options.zoomDelta); 14207 }, 14208 14209 addHooks: function () { 14210 var container = this._map._container; 14211 14212 // make the container focusable by tabbing 14213 if (container.tabIndex <= 0) { 14214 container.tabIndex = '0'; 14215 } 14216 14217 on(container, { 14218 focus: this._onFocus, 14219 blur: this._onBlur, 14220 mousedown: this._onMouseDown 14221 }, this); 14222 14223 this._map.on({ 14224 focus: this._addHooks, 14225 blur: this._removeHooks 14226 }, this); 14227 }, 14228 14229 removeHooks: function () { 14230 this._removeHooks(); 14231 14232 off(this._map._container, { 14233 focus: this._onFocus, 14234 blur: this._onBlur, 14235 mousedown: this._onMouseDown 14236 }, this); 14237 14238 this._map.off({ 14239 focus: this._addHooks, 14240 blur: this._removeHooks 14241 }, this); 14242 }, 14243 14244 _onMouseDown: function () { 14245 if (this._focused) { return; } 14246 14247 var body = document.body, 14248 docEl = document.documentElement, 14249 top = body.scrollTop || docEl.scrollTop, 14250 left = body.scrollLeft || docEl.scrollLeft; 14251 14252 this._map._container.focus(); 14253 14254 window.scrollTo(left, top); 14255 }, 14256 14257 _onFocus: function () { 14258 this._focused = true; 14259 this._map.fire('focus'); 14260 }, 14261 14262 _onBlur: function () { 14263 this._focused = false; 14264 this._map.fire('blur'); 14265 }, 14266 14267 _setPanDelta: function (panDelta) { 14268 var keys = this._panKeys = {}, 14269 codes = this.keyCodes, 14270 i, len; 14271 14272 for (i = 0, len = codes.left.length; i < len; i++) { 14273 keys[codes.left[i]] = [-1 * panDelta, 0]; 14274 } 14275 for (i = 0, len = codes.right.length; i < len; i++) { 14276 keys[codes.right[i]] = [panDelta, 0]; 14277 } 14278 for (i = 0, len = codes.down.length; i < len; i++) { 14279 keys[codes.down[i]] = [0, panDelta]; 14280 } 14281 for (i = 0, len = codes.up.length; i < len; i++) { 14282 keys[codes.up[i]] = [0, -1 * panDelta]; 14283 } 14284 }, 14285 14286 _setZoomDelta: function (zoomDelta) { 14287 var keys = this._zoomKeys = {}, 14288 codes = this.keyCodes, 14289 i, len; 14290 14291 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 14292 keys[codes.zoomIn[i]] = zoomDelta; 14293 } 14294 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 14295 keys[codes.zoomOut[i]] = -zoomDelta; 14296 } 14297 }, 14298 14299 _addHooks: function () { 14300 on(document, 'keydown', this._onKeyDown, this); 14301 }, 14302 14303 _removeHooks: function () { 14304 off(document, 'keydown', this._onKeyDown, this); 14305 }, 14306 14307 _onKeyDown: function (e) { 14308 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 14309 14310 var key = e.keyCode, 14311 map = this._map, 14312 offset; 14313 14314 if (key in this._panKeys) { 14315 if (!map._panAnim || !map._panAnim._inProgress) { 14316 offset = this._panKeys[key]; 14317 if (e.shiftKey) { 14318 offset = toPoint(offset).multiplyBy(3); 14319 } 14320 14321 if (map.options.maxBounds) { 14322 offset = map._limitOffset(toPoint(offset), map.options.maxBounds); 14323 } 14324 14325 if (map.options.worldCopyJump) { 14326 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset))); 14327 map.panTo(newLatLng); 14328 } else { 14329 map.panBy(offset); 14330 } 14331 } 14332 } else if (key in this._zoomKeys) { 14333 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 14334 14335 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 14336 map.closePopup(); 14337 14338 } else { 14339 return; 14340 } 14341 14342 stop(e); 14343 } 14344 }); 14345 14346 // @section Handlers 14347 // @section Handlers 14348 // @property keyboard: Handler 14349 // Keyboard navigation handler. 14350 Map.addInitHook('addHandler', 'keyboard', Keyboard); 14351 14352 /* 14353 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 14354 */ 14355 14356 // @namespace Map 14357 // @section Interaction Options 14358 Map.mergeOptions({ 14359 // @section Mouse wheel options 14360 // @option scrollWheelZoom: Boolean|String = true 14361 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 14362 // it will zoom to the center of the view regardless of where the mouse was. 14363 scrollWheelZoom: true, 14364 14365 // @option wheelDebounceTime: Number = 40 14366 // Limits the rate at which a wheel can fire (in milliseconds). By default 14367 // user can't zoom via wheel more often than once per 40 ms. 14368 wheelDebounceTime: 40, 14369 14370 // @option wheelPxPerZoomLevel: Number = 60 14371 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 14372 // mean a change of one full zoom level. Smaller values will make wheel-zooming 14373 // faster (and vice versa). 14374 wheelPxPerZoomLevel: 60 14375 }); 14376 14377 var ScrollWheelZoom = Handler.extend({ 14378 addHooks: function () { 14379 on(this._map._container, 'wheel', this._onWheelScroll, this); 14380 14381 this._delta = 0; 14382 }, 14383 14384 removeHooks: function () { 14385 off(this._map._container, 'wheel', this._onWheelScroll, this); 14386 }, 14387 14388 _onWheelScroll: function (e) { 14389 var delta = getWheelDelta(e); 14390 14391 var debounce = this._map.options.wheelDebounceTime; 14392 14393 this._delta += delta; 14394 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 14395 14396 if (!this._startTime) { 14397 this._startTime = +new Date(); 14398 } 14399 14400 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 14401 14402 clearTimeout(this._timer); 14403 this._timer = setTimeout(bind(this._performZoom, this), left); 14404 14405 stop(e); 14406 }, 14407 14408 _performZoom: function () { 14409 var map = this._map, 14410 zoom = map.getZoom(), 14411 snap = this._map.options.zoomSnap || 0; 14412 14413 map._stop(); // stop panning and fly animations if any 14414 14415 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 14416 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 14417 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 14418 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 14419 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 14420 14421 this._delta = 0; 14422 this._startTime = null; 14423 14424 if (!delta) { return; } 14425 14426 if (map.options.scrollWheelZoom === 'center') { 14427 map.setZoom(zoom + delta); 14428 } else { 14429 map.setZoomAround(this._lastMousePos, zoom + delta); 14430 } 14431 } 14432 }); 14433 14434 // @section Handlers 14435 // @property scrollWheelZoom: Handler 14436 // Scroll wheel zoom handler. 14437 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 14438 14439 /* 14440 * L.Map.TapHold is used to simulate `contextmenu` event on long hold, 14441 * which otherwise is not fired by mobile Safari. 14442 */ 14443 14444 var tapHoldDelay = 600; 14445 14446 // @namespace Map 14447 // @section Interaction Options 14448 Map.mergeOptions({ 14449 // @section Touch interaction options 14450 // @option tapHold: Boolean 14451 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari. 14452 tapHold: Browser.touchNative && Browser.safari && Browser.mobile, 14453 14454 // @option tapTolerance: Number = 15 14455 // The max number of pixels a user can shift his finger during touch 14456 // for it to be considered a valid tap. 14457 tapTolerance: 15 14458 }); 14459 14460 var TapHold = Handler.extend({ 14461 addHooks: function () { 14462 on(this._map._container, 'touchstart', this._onDown, this); 14463 }, 14464 14465 removeHooks: function () { 14466 off(this._map._container, 'touchstart', this._onDown, this); 14467 }, 14468 14469 _onDown: function (e) { 14470 clearTimeout(this._holdTimeout); 14471 if (e.touches.length !== 1) { return; } 14472 14473 var first = e.touches[0]; 14474 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 14475 14476 this._holdTimeout = setTimeout(bind(function () { 14477 this._cancel(); 14478 if (!this._isTapValid()) { return; } 14479 14480 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events 14481 on(document, 'touchend', preventDefault); 14482 on(document, 'touchend touchcancel', this._cancelClickPrevent); 14483 this._simulateEvent('contextmenu', first); 14484 }, this), tapHoldDelay); 14485 14486 on(document, 'touchend touchcancel contextmenu', this._cancel, this); 14487 on(document, 'touchmove', this._onMove, this); 14488 }, 14489 14490 _cancelClickPrevent: function cancelClickPrevent() { 14491 off(document, 'touchend', preventDefault); 14492 off(document, 'touchend touchcancel', cancelClickPrevent); 14493 }, 14494 14495 _cancel: function () { 14496 clearTimeout(this._holdTimeout); 14497 off(document, 'touchend touchcancel contextmenu', this._cancel, this); 14498 off(document, 'touchmove', this._onMove, this); 14499 }, 14500 14501 _onMove: function (e) { 14502 var first = e.touches[0]; 14503 this._newPos = new Point(first.clientX, first.clientY); 14504 }, 14505 14506 _isTapValid: function () { 14507 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 14508 }, 14509 14510 _simulateEvent: function (type, e) { 14511 var simulatedEvent = new MouseEvent(type, { 14512 bubbles: true, 14513 cancelable: true, 14514 view: window, 14515 // detail: 1, 14516 screenX: e.screenX, 14517 screenY: e.screenY, 14518 clientX: e.clientX, 14519 clientY: e.clientY, 14520 // button: 2, 14521 // buttons: 2 14522 }); 14523 14524 simulatedEvent._simulated = true; 14525 14526 e.target.dispatchEvent(simulatedEvent); 14527 } 14528 }); 14529 14530 // @section Handlers 14531 // @property tapHold: Handler 14532 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari). 14533 Map.addInitHook('addHandler', 'tapHold', TapHold); 14534 14535 /* 14536 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 14537 */ 14538 14539 // @namespace Map 14540 // @section Interaction Options 14541 Map.mergeOptions({ 14542 // @section Touch interaction options 14543 // @option touchZoom: Boolean|String = * 14544 // Whether the map can be zoomed by touch-dragging with two fingers. If 14545 // passed `'center'`, it will zoom to the center of the view regardless of 14546 // where the touch events (fingers) were. Enabled for touch-capable web 14547 // browsers. 14548 touchZoom: Browser.touch, 14549 14550 // @option bounceAtZoomLimits: Boolean = true 14551 // Set it to false if you don't want the map to zoom beyond min/max zoom 14552 // and then bounce back when pinch-zooming. 14553 bounceAtZoomLimits: true 14554 }); 14555 14556 var TouchZoom = Handler.extend({ 14557 addHooks: function () { 14558 addClass(this._map._container, 'leaflet-touch-zoom'); 14559 on(this._map._container, 'touchstart', this._onTouchStart, this); 14560 }, 14561 14562 removeHooks: function () { 14563 removeClass(this._map._container, 'leaflet-touch-zoom'); 14564 off(this._map._container, 'touchstart', this._onTouchStart, this); 14565 }, 14566 14567 _onTouchStart: function (e) { 14568 var map = this._map; 14569 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 14570 14571 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 14572 p2 = map.mouseEventToContainerPoint(e.touches[1]); 14573 14574 this._centerPoint = map.getSize()._divideBy(2); 14575 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 14576 if (map.options.touchZoom !== 'center') { 14577 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 14578 } 14579 14580 this._startDist = p1.distanceTo(p2); 14581 this._startZoom = map.getZoom(); 14582 14583 this._moved = false; 14584 this._zooming = true; 14585 14586 map._stop(); 14587 14588 on(document, 'touchmove', this._onTouchMove, this); 14589 on(document, 'touchend touchcancel', this._onTouchEnd, this); 14590 14591 preventDefault(e); 14592 }, 14593 14594 _onTouchMove: function (e) { 14595 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 14596 14597 var map = this._map, 14598 p1 = map.mouseEventToContainerPoint(e.touches[0]), 14599 p2 = map.mouseEventToContainerPoint(e.touches[1]), 14600 scale = p1.distanceTo(p2) / this._startDist; 14601 14602 this._zoom = map.getScaleZoom(scale, this._startZoom); 14603 14604 if (!map.options.bounceAtZoomLimits && ( 14605 (this._zoom < map.getMinZoom() && scale < 1) || 14606 (this._zoom > map.getMaxZoom() && scale > 1))) { 14607 this._zoom = map._limitZoom(this._zoom); 14608 } 14609 14610 if (map.options.touchZoom === 'center') { 14611 this._center = this._startLatLng; 14612 if (scale === 1) { return; } 14613 } else { 14614 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 14615 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 14616 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 14617 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 14618 } 14619 14620 if (!this._moved) { 14621 map._moveStart(true, false); 14622 this._moved = true; 14623 } 14624 14625 cancelAnimFrame(this._animRequest); 14626 14627 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined); 14628 this._animRequest = requestAnimFrame(moveFn, this, true); 14629 14630 preventDefault(e); 14631 }, 14632 14633 _onTouchEnd: function () { 14634 if (!this._moved || !this._zooming) { 14635 this._zooming = false; 14636 return; 14637 } 14638 14639 this._zooming = false; 14640 cancelAnimFrame(this._animRequest); 14641 14642 off(document, 'touchmove', this._onTouchMove, this); 14643 off(document, 'touchend touchcancel', this._onTouchEnd, this); 14644 14645 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 14646 if (this._map.options.zoomAnimation) { 14647 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 14648 } else { 14649 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 14650 } 14651 } 14652 }); 14653 14654 // @section Handlers 14655 // @property touchZoom: Handler 14656 // Touch zoom handler. 14657 Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 14658 14659 Map.BoxZoom = BoxZoom; 14660 Map.DoubleClickZoom = DoubleClickZoom; 14661 Map.Drag = Drag; 14662 Map.Keyboard = Keyboard; 14663 Map.ScrollWheelZoom = ScrollWheelZoom; 14664 Map.TapHold = TapHold; 14665 Map.TouchZoom = TouchZoom; 14666 14667 exports.Bounds = Bounds; 14668 exports.Browser = Browser; 14669 exports.CRS = CRS; 14670 exports.Canvas = Canvas; 14671 exports.Circle = Circle; 14672 exports.CircleMarker = CircleMarker; 14673 exports.Class = Class; 14674 exports.Control = Control; 14675 exports.DivIcon = DivIcon; 14676 exports.DivOverlay = DivOverlay; 14677 exports.DomEvent = DomEvent; 14678 exports.DomUtil = DomUtil; 14679 exports.Draggable = Draggable; 14680 exports.Evented = Evented; 14681 exports.FeatureGroup = FeatureGroup; 14682 exports.GeoJSON = GeoJSON; 14683 exports.GridLayer = GridLayer; 14684 exports.Handler = Handler; 14685 exports.Icon = Icon; 14686 exports.ImageOverlay = ImageOverlay; 14687 exports.LatLng = LatLng; 14688 exports.LatLngBounds = LatLngBounds; 14689 exports.Layer = Layer; 14690 exports.LayerGroup = LayerGroup; 14691 exports.LineUtil = LineUtil; 14692 exports.Map = Map; 14693 exports.Marker = Marker; 14694 exports.Mixin = Mixin; 14695 exports.Path = Path; 14696 exports.Point = Point; 14697 exports.PolyUtil = PolyUtil; 14698 exports.Polygon = Polygon; 14699 exports.Polyline = Polyline; 14700 exports.Popup = Popup; 14701 exports.PosAnimation = PosAnimation; 14702 exports.Projection = index; 14703 exports.Rectangle = Rectangle; 14704 exports.Renderer = Renderer; 14705 exports.SVG = SVG; 14706 exports.SVGOverlay = SVGOverlay; 14707 exports.TileLayer = TileLayer; 14708 exports.Tooltip = Tooltip; 14709 exports.Transformation = Transformation; 14710 exports.Util = Util; 14711 exports.VideoOverlay = VideoOverlay; 14712 exports.bind = bind; 14713 exports.bounds = toBounds; 14714 exports.canvas = canvas; 14715 exports.circle = circle; 14716 exports.circleMarker = circleMarker; 14717 exports.control = control; 14718 exports.divIcon = divIcon; 14719 exports.extend = extend; 14720 exports.featureGroup = featureGroup; 14721 exports.geoJSON = geoJSON; 14722 exports.geoJson = geoJson; 14723 exports.gridLayer = gridLayer; 14724 exports.icon = icon; 14725 exports.imageOverlay = imageOverlay; 14726 exports.latLng = toLatLng; 14727 exports.latLngBounds = toLatLngBounds; 14728 exports.layerGroup = layerGroup; 14729 exports.map = createMap; 14730 exports.marker = marker; 14731 exports.point = toPoint; 14732 exports.polygon = polygon; 14733 exports.polyline = polyline; 14734 exports.popup = popup; 14735 exports.rectangle = rectangle; 14736 exports.setOptions = setOptions; 14737 exports.stamp = stamp; 14738 exports.svg = svg; 14739 exports.svgOverlay = svgOverlay; 14740 exports.tileLayer = tileLayer; 14741 exports.tooltip = tooltip; 14742 exports.transformation = toTransformation; 14743 exports.version = version; 14744 exports.videoOverlay = videoOverlay; 14745 14746 var oldL = window.L; 14747 exports.noConflict = function() { 14748 window.L = oldL; 14749 return this; 14750 } 14751 // Always export us to window global (see #2364) 14752 window.L = exports; 14753 14754})); 14755//# sourceMappingURL=leaflet-src.js.map 14756 14757 14758/***/ }), 14759
14760/***/ "./resources/css/app.css": 14761/*!*******************************!*\ 14762 !*** ./resources/css/app.css ***! 14763 \*******************************/ 14764/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 14765 14766"use strict"; 14767__webpack_require__.r(__webpack_exports__); 14768// extracted by mini-css-extract-plugin 14769 14770 14771/***/ }), 14772
14773/***/ "./resources/css/editor-style.css": 14774/*!****************************************!*\ 14775 !*** ./resources/css/editor-style.css ***! 14776 \****************************************/ 14777/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 14778 14779"use strict"; 14780__webpack_require__.r(__webpack_exports__); 14781// extracted by mini-css-extract-plugin 14782 14783 14784/***/ }), 14785
vendor: 2,242 bytes, lines 14786-14815
14786/***/ "./node_modules/leaflet/dist/leaflet.css": 14787/*!***********************************************!*\ 14788 !*** ./node_modules/leaflet/dist/leaflet.css ***! 14789 \***********************************************/ 14790/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 14791 14792"use strict"; 14793__webpack_require__.r(__webpack_exports__); 14794/* harmony export */ __webpack_require__.d(__webpack_exports__, { 14795/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) 14796/* harmony export */ }); 14797/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js"); 14798/* harmony import */ var _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__); 14799/* harmony import */ var _css_loader_dist_cjs_js_ruleSet_1_rules_7_oneOf_1_use_1_postcss_loader_dist_cjs_js_ruleSet_1_rules_7_oneOf_1_use_2_leaflet_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../../css-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[1]!../../postcss-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[2]!./leaflet.css */ "./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[7].oneOf[1].use[2]!./node_modules/leaflet/dist/leaflet.css"); 14800 14801 14802 14803var options = {}; 14804 14805options.insert = "head"; 14806options.singleton = false; 14807 14808var update = _style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_css_loader_dist_cjs_js_ruleSet_1_rules_7_oneOf_1_use_1_postcss_loader_dist_cjs_js_ruleSet_1_rules_7_oneOf_1_use_2_leaflet_css__WEBPACK_IMPORTED_MODULE_1__["default"], options); 14809 14810 14811 14812/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_css_loader_dist_cjs_js_ruleSet_1_rules_7_oneOf_1_use_1_postcss_loader_dist_cjs_js_ruleSet_1_rules_7_oneOf_1_use_2_leaflet_css__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {}); 14813 14814/***/ }), 14815
vendor: 7,479 bytes, lines 14816-15094
14816/***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js": 14817/*!****************************************************************************!*\ 14818 !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***! 14819 \****************************************************************************/ 14820/***/ ((module, __unused_webpack_exports, __webpack_require__) => { 14821 14822"use strict"; 14823 14824 14825var isOldIE = function isOldIE() { 14826 var memo; 14827 return function memorize() { 14828 if (typeof memo === 'undefined') { 14829 // Test for IE <= 9 as proposed by Browserhacks 14830 // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 14831 // Tests for existence of standard globals is to allow style-loader 14832 // to operate correctly into non-standard environments 14833 // @see https://github.com/webpack-contrib/style-loader/issues/177 14834 memo = Boolean(window && document && document.all && !window.atob); 14835 } 14836 14837 return memo; 14838 }; 14839}(); 14840 14841var getTarget = function getTarget() { 14842 var memo = {}; 14843 return function memorize(target) { 14844 if (typeof memo[target] === 'undefined') { 14845 var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself 14846 14847 if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) { 14848 try { 14849 // This will throw an exception if access to iframe is blocked 14850 // due to cross-origin restrictions 14851 styleTarget = styleTarget.contentDocument.head; 14852 } catch (e) { 14853 // istanbul ignore next 14854 styleTarget = null; 14855 } 14856 } 14857 14858 memo[target] = styleTarget; 14859 } 14860 14861 return memo[target]; 14862 }; 14863}(); 14864 14865var stylesInDom = []; 14866 14867function getIndexByIdentifier(identifier) { 14868 var result = -1; 14869 14870 for (var i = 0; i < stylesInDom.length; i++) { 14871 if (stylesInDom[i].identifier === identifier) { 14872 result = i; 14873 break; 14874 } 14875 } 14876 14877 return result; 14878} 14879 14880function modulesToDom(list, options) { 14881 var idCountMap = {}; 14882 var identifiers = []; 14883 14884 for (var i = 0; i < list.length; i++) { 14885 var item = list[i]; 14886 var id = options.base ? item[0] + options.base : item[0]; 14887 var count = idCountMap[id] || 0; 14888 var identifier = "".concat(id, " ").concat(count); 14889 idCountMap[id] = count + 1; 14890 var index = getIndexByIdentifier(identifier); 14891 var obj = { 14892 css: item[1], 14893 media: item[2], 14894 sourceMap: item[3] 14895 }; 14896 14897 if (index !== -1) { 14898 stylesInDom[index].references++; 14899 stylesInDom[index].updater(obj); 14900 } else { 14901 stylesInDom.push({ 14902 identifier: identifier, 14903 updater: addStyle(obj, options), 14904 references: 1 14905 }); 14906 } 14907 14908 identifiers.push(identifier); 14909 } 14910 14911 return identifiers; 14912} 14913 14914function insertStyleElement(options) { 14915 var style = document.createElement('style'); 14916 var attributes = options.attributes || {}; 14917 14918 if (typeof attributes.nonce === 'undefined') { 14919 var nonce = true ? __webpack_require__.nc : 0; 14920 14921 if (nonce) { 14922 attributes.nonce = nonce; 14923 } 14924 } 14925 14926 Object.keys(attributes).forEach(function (key) { 14927 style.setAttribute(key, attributes[key]); 14928 }); 14929 14930 if (typeof options.insert === 'function') { 14931 options.insert(style); 14932 } else { 14933 var target = getTarget(options.insert || 'head'); 14934 14935 if (!target) { 14936 throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."); 14937 } 14938 14939 target.appendChild(style); 14940 } 14941 14942 return style; 14943} 14944 14945function removeStyleElement(style) { 14946 // istanbul ignore if 14947 if (style.parentNode === null) { 14948 return false; 14949 } 14950 14951 style.parentNode.removeChild(style); 14952} 14953/* istanbul ignore next */ 14954 14955 14956var replaceText = function replaceText() { 14957 var textStore = []; 14958 return function replace(index, replacement) { 14959 textStore[index] = replacement; 14960 return textStore.filter(Boolean).join('\n'); 14961 }; 14962}(); 14963 14964function applyToSingletonTag(style, index, remove, obj) { 14965 var css = remove ? '' : obj.media ? "@media ".concat(obj.media, " {").concat(obj.css, "}") : obj.css; // For old IE 14966 14967 /* istanbul ignore if */ 14968 14969 if (style.styleSheet) { 14970 style.styleSheet.cssText = replaceText(index, css); 14971 } else { 14972 var cssNode = document.createTextNode(css); 14973 var childNodes = style.childNodes; 14974 14975 if (childNodes[index]) { 14976 style.removeChild(childNodes[index]); 14977 } 14978 14979 if (childNodes.length) { 14980 style.insertBefore(cssNode, childNodes[index]); 14981 } else { 14982 style.appendChild(cssNode); 14983 } 14984 } 14985} 14986 14987function applyToTag(style, options, obj) { 14988 var css = obj.css; 14989 var media = obj.media; 14990 var sourceMap = obj.sourceMap; 14991 14992 if (media) { 14993 style.setAttribute('media', media); 14994 } else { 14995 style.removeAttribute('media'); 14996 } 14997 14998 if (sourceMap && typeof btoa !== 'undefined') { 14999 css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */"); 15000 } // For old IE 15001 15002 /* istanbul ignore if */ 15003 15004 15005 if (style.styleSheet) { 15006 style.styleSheet.cssText = css; 15007 } else { 15008 while (style.firstChild) { 15009 style.removeChild(style.firstChild); 15010 } 15011 15012 style.appendChild(document.createTextNode(css)); 15013 } 15014} 15015 15016var singleton = null; 15017var singletonCounter = 0; 15018 15019function addStyle(obj, options) { 15020 var style; 15021 var update; 15022 var remove; 15023 15024 if (options.singleton) { 15025 var styleIndex = singletonCounter++; 15026 style = singleton || (singleton = insertStyleElement(options)); 15027 update = applyToSingletonTag.bind(null, style, styleIndex, false); 15028 remove = applyToSingletonTag.bind(null, style, styleIndex, true); 15029 } else { 15030 style = insertStyleElement(options); 15031 update = applyToTag.bind(null, style, options); 15032 15033 remove = function remove() { 15034 removeStyleElement(style); 15035 }; 15036 } 15037 15038 update(obj); 15039 return function updateStyle(newObj) { 15040 if (newObj) { 15041 if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) { 15042 return; 15043 } 15044 15045 update(obj = newObj); 15046 } else { 15047 remove(); 15048 } 15049 }; 15050} 15051 15052module.exports = function (list, options) { 15053 options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style> 15054 // tags it will allow on a page 15055 15056 if (!options.singleton && typeof options.singleton !== 'boolean') { 15057 options.singleton = isOldIE(); 15058 } 15059 15060 list = list || []; 15061 var lastIdentifiers = modulesToDom(list, options); 15062 return function update(newList) { 15063 newList = newList || []; 15064 15065 if (Object.prototype.toString.call(newList) !== '[object Array]') { 15066 return; 15067 } 15068 15069 for (var i = 0; i < lastIdentifiers.length; i++) { 15070 var identifier = lastIdentifiers[i]; 15071 var index = getIndexByIdentifier(identifier); 15072 stylesInDom[index].references--; 15073 } 15074 15075 var newLastIdentifiers = modulesToDom(newList, options); 15076 15077 for (var _i = 0; _i < lastIdentifiers.length; _i++) { 15078 var _identifier = lastIdentifiers[_i]; 15079 15080 var _index = getIndexByIdentifier(_identifier); 15081 15082 if (stylesInDom[_index].references === 0) { 15083 stylesInDom[_index].updater(); 15084 15085 stylesInDom.splice(_index, 1); 15086 } 15087 } 15088 15089 lastIdentifiers = newLastIdentifiers; 15090 }; 15091}; 15092 15093/***/ }), 15094
vendor: 8,158 bytes, lines 15095-15382
15095/***/ "./node_modules/superfish/dist/js/superfish.js": 15096/*!*****************************************************!*\ 15097 !*** ./node_modules/superfish/dist/js/superfish.js ***! 15098 \*****************************************************/ 15099/***/ (() => { 15100 15101/* 15102 * jQuery Superfish Menu Plugin - v1.7.10 15103 * Copyright (c) 2018 Joel Birch 15104 * 15105 * Dual licensed under the MIT and GPL licenses: 15106 * http://www.opensource.org/licenses/mit-license.php 15107 * http://www.gnu.org/licenses/gpl.html 15108 */ 15109 15110;(function ($, w) { 15111 "use strict"; 15112 15113 var methods = (function () { 15114 // private properties and methods go here 15115 var c = { 15116 bcClass: 'sf-breadcrumb', 15117 menuClass: 'sf-js-enabled', 15118 anchorClass: 'sf-with-ul', 15119 menuArrowClass: 'sf-arrows' 15120 }, 15121 ios = (function () { 15122 var ios = /^(?![\w\W]*Windows Phone)[\w\W]*(iPhone|iPad|iPod)/i.test(navigator.userAgent); 15123 if (ios) { 15124 // tap anywhere on iOS to unfocus a submenu 15125 $('html').css('cursor', 'pointer').on('click', $.noop); 15126 } 15127 return ios; 15128 })(), 15129 wp7 = (function () { 15130 var style = document.documentElement.style; 15131 return ('behavior' in style && 'fill' in style && /iemobile/i.test(navigator.userAgent)); 15132 })(), 15133 unprefixedPointerEvents = (function () { 15134 return (!!w.PointerEvent); 15135 })(), 15136 toggleMenuClasses = function ($menu, o, add) { 15137 var classes = c.menuClass, 15138 method; 15139 if (o.cssArrows) { 15140 classes += ' ' + c.menuArrowClass; 15141 } 15142 method = (add) ? 'addClass' : 'removeClass'; 15143 $menu[method](classes); 15144 }, 15145 setPathToCurrent = function ($menu, o) { 15146 return $menu.find('li.' + o.pathClass).slice(0, o.pathLevels) 15147 .addClass(o.hoverClass + ' ' + c.bcClass) 15148 .filter(function () { 15149 return ($(this).children(o.popUpSelector).hide().show().length); 15150 }).removeClass(o.pathClass); 15151 }, 15152 toggleAnchorClass = function ($li, add) { 15153 var method = (add) ? 'addClass' : 'removeClass'; 15154 $li.children('a')[method](c.anchorClass); 15155 }, 15156 toggleTouchAction = function ($menu) { 15157 var msTouchAction = $menu.css('ms-touch-action'); 15158 var touchAction = $menu.css('touch-action'); 15159 touchAction = touchAction || msTouchAction; 15160 touchAction = (touchAction === 'pan-y') ? 'auto' : 'pan-y'; 15161 $menu.css({ 15162 'ms-touch-action': touchAction, 15163 'touch-action': touchAction 15164 }); 15165 }, 15166 getMenu = function ($el) { 15167 return $el.closest('.' + c.menuClass); 15168 }, 15169 getOptions = function ($el) { 15170 return getMenu($el).data('sfOptions'); 15171 }, 15172 over = function () { 15173 var $this = $(this), 15174 o = getOptions($this); 15175 clearTimeout(o.sfTimer); 15176 $this.siblings().superfish('hide').end().superfish('show'); 15177 }, 15178 close = function (o) { 15179 o.retainPath = ($.inArray(this[0], o.$path) > -1); 15180 this.superfish('hide'); 15181 15182 if (!this.parents('.' + o.hoverClass).length) { 15183 o.onIdle.call(getMenu(this)); 15184 if (o.$path.length) { 15185 $.proxy(over, o.$path)(); 15186 } 15187 } 15188 }, 15189 out = function () { 15190 var $this = $(this), 15191 o = getOptions($this); 15192 if (ios) { 15193 $.proxy(close, $this, o)(); 15194 } 15195 else { 15196 clearTimeout(o.sfTimer); 15197 o.sfTimer = setTimeout($.proxy(close, $this, o), o.delay); 15198 } 15199 }, 15200 touchHandler = function (e) { 15201 var $this = $(this), 15202 o = getOptions($this), 15203 $ul = $this.siblings(e.data.popUpSelector); 15204 15205 if (o.onHandleTouch.call($ul) === false) { 15206 return this; 15207 } 15208 15209 if ($ul.length > 0 && $ul.is(':hidden')) { 15210 $this.one('click.superfish', false); 15211 if (e.type === 'MSPointerDown' || e.type === 'pointerdown') { 15212 $this.trigger('focus'); 15213 } else { 15214 $.proxy(over, $this.parent('li'))(); 15215 } 15216 } 15217 }, 15218 applyHandlers = function ($menu, o) { 15219 var targets = 'li:has(' + o.popUpSelector + ')'; 15220 if ($.fn.hoverIntent && !o.disableHI) { 15221 $menu.hoverIntent(over, out, targets); 15222 } 15223 else { 15224 $menu 15225 .on('mouseenter.superfish', targets, over) 15226 .on('mouseleave.superfish', targets, out); 15227 } 15228 var touchevent = 'MSPointerDown.superfish'; 15229 if (unprefixedPointerEvents) { 15230 touchevent = 'pointerdown.superfish'; 15231 } 15232 if (!ios) { 15233 touchevent += ' touchend.superfish'; 15234 } 15235 if (wp7) { 15236 touchevent += ' mousedown.superfish'; 15237 } 15238 $menu 15239 .on('focusin.superfish', 'li', over) 15240 .on('focusout.superfish', 'li', out) 15241 .on(touchevent, 'a', o, touchHandler); 15242 }; 15243 15244 return { 15245 // public methods 15246 hide: function (instant) { 15247 if (this.length) { 15248 var $this = this, 15249 o = getOptions($this); 15250 if (!o) { 15251 return this; 15252 } 15253 var not = (o.retainPath === true) ? o.$path : '', 15254 $ul = $this.find('li.' + o.hoverClass).add(this).not(not).removeClass(o.hoverClass).children(o.popUpSelector), 15255 speed = o.speedOut; 15256 15257 if (instant) { 15258 $ul.show(); 15259 speed = 0; 15260 } 15261 o.retainPath = false; 15262 15263 if (o.onBeforeHide.call($ul) === false) { 15264 return this; 15265 } 15266 15267 $ul.stop(true, true).animate(o.animationOut, speed, function () { 15268 var $this = $(this); 15269 o.onHide.call($this); 15270 }); 15271 } 15272 return this; 15273 }, 15274 show: function () { 15275 var o = getOptions(this); 15276 if (!o) { 15277 return this; 15278 } 15279 var $this = this.addClass(o.hoverClass), 15280 $ul = $this.children(o.popUpSelector); 15281 15282 if (o.onBeforeShow.call($ul) === false) { 15283 return this; 15284 } 15285 15286 $ul.stop(true, true).animate(o.animation, o.speed, function () { 15287 o.onShow.call($ul); 15288 }); 15289 return this; 15290 }, 15291 destroy: function () { 15292 return this.each(function () { 15293 var $this = $(this), 15294 o = $this.data('sfOptions'), 15295 $hasPopUp; 15296 if (!o) { 15297 return false; 15298 } 15299 $hasPopUp = $this.find(o.popUpSelector).parent('li'); 15300 clearTimeout(o.sfTimer); 15301 toggleMenuClasses($this, o); 15302 toggleAnchorClass($hasPopUp); 15303 toggleTouchAction($this); 15304 // remove event handlers 15305 $this.off('.superfish').off('.hoverIntent'); 15306 // clear animation's inline display style 15307 $hasPopUp.children(o.popUpSelector).attr('style', function (i, style) { 15308 if (typeof style !== 'undefined') { 15309 return style.replace(/display[^;]+;?/g, ''); 15310 } 15311 }); 15312 // reset 'current' path classes 15313 o.$path.removeClass(o.hoverClass + ' ' + c.bcClass).addClass(o.pathClass); 15314 $this.find('.' + o.hoverClass).removeClass(o.hoverClass); 15315 o.onDestroy.call($this); 15316 $this.removeData('sfOptions'); 15317 }); 15318 }, 15319 init: function (op) { 15320 return this.each(function () { 15321 var $this = $(this); 15322 if ($this.data('sfOptions')) { 15323 return false; 15324 } 15325 var o = $.extend({}, $.fn.superfish.defaults, op), 15326 $hasPopUp = $this.find(o.popUpSelector).parent('li'); 15327 o.$path = setPathToCurrent($this, o); 15328 15329 $this.data('sfOptions', o); 15330 15331 toggleMenuClasses($this, o, true); 15332 toggleAnchorClass($hasPopUp, true); 15333 toggleTouchAction($this); 15334 applyHandlers($this, o); 15335 15336 $hasPopUp.not('.' + c.bcClass).superfish('hide', true); 15337 15338 o.onInit.call(this); 15339 }); 15340 } 15341 }; 15342 })(); 15343 15344 $.fn.superfish = function (method, args) { 15345 if (methods[method]) { 15346 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 15347 } 15348 else if (typeof method === 'object' || ! method) { 15349 return methods.init.apply(this, arguments); 15350 } 15351 else { 15352 return $.error('Method ' + method + ' does not exist on jQuery.fn.superfish'); 15353 } 15354 }; 15355 15356 $.fn.superfish.defaults = { 15357 popUpSelector: 'ul,.sf-mega', // within menu context 15358 hoverClass: 'sfHover', 15359 pathClass: 'overrideThisToUse', 15360 pathLevels: 1, 15361 delay: 800, 15362 animation: {opacity: 'show'}, 15363 animationOut: {opacity: 'hide'}, 15364 speed: 'normal', 15365 speedOut: 'fast', 15366 cssArrows: true, 15367 disableHI: false, 15368 onInit: $.noop, 15369 onBeforeShow: $.noop, 15370 onShow: $.noop, 15371 onBeforeHide: $.noop, 15372 onHide: $.noop, 15373 onIdle: $.noop, 15374 onDestroy: $.noop, 15375 onHandleTouch: $.noop 15376 }; 15377 15378})(jQuery, window); 15379 15380 15381/***/ }), 15382
vendor: 94,925 bytes, lines 15383-19288
15383/***/ "./node_modules/@glidejs/glide/dist/glide.esm.js": 15384/*!*******************************************************!*\ 15385 !*** ./node_modules/@glidejs/glide/dist/glide.esm.js ***! 15386 \*******************************************************/ 15387/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { 15388 15389"use strict"; 15390__webpack_require__.r(__webpack_exports__); 15391/* harmony export */ __webpack_require__.d(__webpack_exports__, { 15392/* harmony export */ "default": () => (/* binding */ Glide) 15393/* harmony export */ }); 15394/*! 15395 * Glide.js v3.6.2 15396 * (c) 2013-2024 JÄdrzej ChaÅubek (https://github.com/jedrzejchalubek/) 15397 * Released under the MIT License. 15398 */ 15399 15400function ownKeys(object, enumerableOnly) { 15401 var keys = Object.keys(object); 15402 15403 if (Object.getOwnPropertySymbols) { 15404 var symbols = Object.getOwnPropertySymbols(object); 15405 15406 if (enumerableOnly) { 15407 symbols = symbols.filter(function (sym) { 15408 return Object.getOwnPropertyDescriptor(object, sym).enumerable; 15409 }); 15410 } 15411 15412 keys.push.apply(keys, symbols); 15413 } 15414 15415 return keys; 15416} 15417 15418function _objectSpread2(target) { 15419 for (var i = 1; i < arguments.length; i++) { 15420 var source = arguments[i] != null ? arguments[i] : {}; 15421 15422 if (i % 2) { 15423 ownKeys(Object(source), true).forEach(function (key) { 15424 _defineProperty(target, key, source[key]); 15425 }); 15426 } else if (Object.getOwnPropertyDescriptors) { 15427 Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); 15428 } else { 15429 ownKeys(Object(source)).forEach(function (key) { 15430 Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); 15431 }); 15432 } 15433 } 15434 15435 return target; 15436} 15437 15438function _typeof(obj) { 15439 "@babel/helpers - typeof"; 15440 15441 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 15442 _typeof = function (obj) { 15443 return typeof obj; 15444 }; 15445 } else { 15446 _typeof = function (obj) { 15447 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 15448 }; 15449 } 15450 15451 return _typeof(obj); 15452} 15453 15454function _classCallCheck(instance, Constructor) { 15455 if (!(instance instanceof Constructor)) { 15456 throw new TypeError("Cannot call a class as a function"); 15457 } 15458} 15459 15460function _defineProperties(target, props) { 15461 for (var i = 0; i < props.length; i++) { 15462 var descriptor = props[i]; 15463 descriptor.enumerable = descriptor.enumerable || false; 15464 descriptor.configurable = true; 15465 if ("value" in descriptor) descriptor.writable = true; 15466 Object.defineProperty(target, descriptor.key, descriptor); 15467 } 15468} 15469 15470function _createClass(Constructor, protoProps, staticProps) { 15471 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 15472 if (staticProps) _defineProperties(Constructor, staticProps); 15473 return Constructor; 15474} 15475 15476function _defineProperty(obj, key, value) { 15477 if (key in obj) { 15478 Object.defineProperty(obj, key, { 15479 value: value, 15480 enumerable: true, 15481 configurable: true, 15482 writable: true 15483 }); 15484 } else { 15485 obj[key] = value; 15486 } 15487 15488 return obj; 15489} 15490 15491function _inherits(subClass, superClass) { 15492 if (typeof superClass !== "function" && superClass !== null) { 15493 throw new TypeError("Super expression must either be null or a function"); 15494 } 15495 15496 subClass.prototype = Object.create(superClass && superClass.prototype, { 15497 constructor: { 15498 value: subClass, 15499 writable: true, 15500 configurable: true 15501 } 15502 }); 15503 if (superClass) _setPrototypeOf(subClass, superClass); 15504} 15505 15506function _getPrototypeOf(o) { 15507 _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 15508 return o.__proto__ || Object.getPrototypeOf(o); 15509 }; 15510 return _getPrototypeOf(o); 15511} 15512 15513function _setPrototypeOf(o, p) { 15514 _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 15515 o.__proto__ = p; 15516 return o; 15517 }; 15518 15519 return _setPrototypeOf(o, p); 15520} 15521 15522function _isNativeReflectConstruct() { 15523 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 15524 if (Reflect.construct.sham) return false; 15525 if (typeof Proxy === "function") return true; 15526 15527 try { 15528 Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 15529 return true; 15530 } catch (e) { 15531 return false; 15532 } 15533} 15534 15535function _assertThisInitialized(self) { 15536 if (self === void 0) { 15537 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 15538 } 15539 15540 return self; 15541} 15542 15543function _possibleConstructorReturn(self, call) { 15544 if (call && (typeof call === "object" || typeof call === "function")) { 15545 return call; 15546 } else if (call !== void 0) { 15547 throw new TypeError("Derived constructors may only return object or undefined"); 15548 } 15549 15550 return _assertThisInitialized(self); 15551} 15552 15553function _createSuper(Derived) { 15554 var hasNativeReflectConstruct = _isNativeReflectConstruct(); 15555 15556 return function _createSuperInternal() { 15557 var Super = _getPrototypeOf(Derived), 15558 result; 15559 15560 if (hasNativeReflectConstruct) { 15561 var NewTarget = _getPrototypeOf(this).constructor; 15562 15563 result = Reflect.construct(Super, arguments, NewTarget); 15564 } else { 15565 result = Super.apply(this, arguments); 15566 } 15567 15568 return _possibleConstructorReturn(this, result); 15569 }; 15570} 15571 15572function _superPropBase(object, property) { 15573 while (!Object.prototype.hasOwnProperty.call(object, property)) { 15574 object = _getPrototypeOf(object); 15575 if (object === null) break; 15576 } 15577 15578 return object; 15579} 15580 15581function _get() { 15582 if (typeof Reflect !== "undefined" && Reflect.get) { 15583 _get = Reflect.get; 15584 } else { 15585 _get = function _get(target, property, receiver) { 15586 var base = _superPropBase(target, property); 15587 15588 if (!base) return; 15589 var desc = Object.getOwnPropertyDescriptor(base, property); 15590 15591 if (desc.get) { 15592 return desc.get.call(arguments.length < 3 ? target : receiver); 15593 } 15594 15595 return desc.value; 15596 }; 15597 } 15598 15599 return _get.apply(this, arguments); 15600} 15601 15602var defaults = { 15603 /** 15604 * Type of the movement. 15605 * 15606 * Available types: 15607 * `slider` - Rewinds slider to the start/end when it reaches the first or last slide. 15608 * `carousel` - Changes slides without starting over when it reaches the first or last slide. 15609 * 15610 * @type {String} 15611 */ 15612 type: 'slider', 15613 15614 /** 15615 * Start at specific slide number defined with zero-based index. 15616 * 15617 * @type {Number} 15618 */ 15619 startAt: 0, 15620 15621 /** 15622 * A number of slides visible on the single viewport. 15623 * 15624 * @type {Number} 15625 */ 15626 perView: 1, 15627 15628 /** 15629 * Focus currently active slide at a specified position in the track. 15630 * 15631 * Available inputs: 15632 * `center` - Current slide will be always focused at the center of a track. 15633 * `0,1,2,3...` - Current slide will be focused on the specified zero-based index. 15634 * 15635 * @type {String|Number} 15636 */ 15637 focusAt: 0, 15638 15639 /** 15640 * A size of the gap added between slides. 15641 * 15642 * @type {Number} 15643 */ 15644 gap: 10, 15645 15646 /** 15647 * Change slides after a specified interval. Use `false` for turning off autoplay. 15648 * 15649 * @type {Number|Boolean} 15650 */ 15651 autoplay: false, 15652 15653 /** 15654 * Stop autoplay on mouseover event. 15655 * 15656 * @type {Boolean} 15657 */ 15658 hoverpause: true, 15659 15660 /** 15661 * Allow for changing slides with left and right keyboard arrows. 15662 * 15663 * @type {Boolean} 15664 */ 15665 keyboard: true, 15666 15667 /** 15668 * Stop running `perView` number of slides from the end. Use this 15669 * option if you don't want to have an empty space after 15670 * a slider. Works only with `slider` type and a 15671 * non-centered `focusAt` setting. 15672 * 15673 * @type {Boolean} 15674 */ 15675 bound: false, 15676 15677 /** 15678 * Minimal swipe distance needed to change the slide. Use `false` for turning off a swiping. 15679 * 15680 * @type {Number|Boolean} 15681 */ 15682 swipeThreshold: 80, 15683 15684 /** 15685 * Minimal mouse drag distance needed to change the slide. Use `false` for turning off a dragging. 15686 * 15687 * @type {Number|Boolean} 15688 */ 15689 dragThreshold: 120, 15690 15691 /** 15692 * A number of slides moved on single swipe. 15693 * 15694 * Available types: 15695 * `` - Moves slider by one slide per swipe 15696 * `|` - Moves slider between views per swipe (number of slides defined in `perView` options) 15697 * 15698 * @type {String} 15699 */ 15700 perSwipe: '', 15701 15702 /** 15703 * Moving distance ratio of the slides on a swiping and dragging. 15704 * 15705 * @type {Number} 15706 */ 15707 touchRatio: 0.5, 15708 15709 /** 15710 * Angle required to activate slides moving on swiping or dragging. 15711 * 15712 * @type {Number} 15713 */ 15714 touchAngle: 45, 15715 15716 /** 15717 * Duration of the animation in milliseconds. 15718 * 15719 * @type {Number} 15720 */ 15721 animationDuration: 400, 15722 15723 /** 15724 * Allows looping the `slider` type. Slider will rewind to the first/last slide when it's at the start/end. 15725 * 15726 * @type {Boolean} 15727 */ 15728 rewind: true, 15729 15730 /** 15731 * Duration of the rewinding animation of the `slider` type in milliseconds. 15732 * 15733 * @type {Number} 15734 */ 15735 rewindDuration: 800, 15736 15737 /** 15738 * Easing function for the animation. 15739 * 15740 * @type {String} 15741 */ 15742 animationTimingFunc: 'cubic-bezier(.165, .840, .440, 1)', 15743 15744 /** 15745 * Wait for the animation to finish until the next user input can be processed 15746 * 15747 * @type {boolean} 15748 */ 15749 waitForTransition: true, 15750 15751 /** 15752 * Throttle costly events at most once per every wait milliseconds. 15753 * 15754 * @type {Number} 15755 */ 15756 throttle: 10, 15757 15758 /** 15759 * Moving direction mode. 15760 * 15761 * Available inputs: 15762 * - 'ltr' - left to right movement, 15763 * - 'rtl' - right to left movement. 15764 * 15765 * @type {String} 15766 */ 15767 direction: 'ltr', 15768 15769 /** 15770 * The distance value of the next and previous viewports which 15771 * have to peek in the current view. Accepts number and 15772 * pixels as a string. Left and right peeking can be 15773 * set up separately with a directions object. 15774 * 15775 * For example: 15776 * `100` - Peek 100px on the both sides. 15777 * { before: 100, after: 50 }` - Peek 100px on the left side and 50px on the right side. 15778 * 15779 * @type {Number|String|Object} 15780 */ 15781 peek: 0, 15782 15783 /** 15784 * Defines how many clones of current viewport will be generated. 15785 * 15786 * @type {Number} 15787 */ 15788 cloningRatio: 1, 15789 15790 /** 15791 * Collection of options applied at specified media breakpoints. 15792 * For example: display two slides per view under 800px. 15793 * `{ 15794 * '800px': { 15795 * perView: 2 15796 * } 15797 * }` 15798 */ 15799 breakpoints: {}, 15800 15801 /** 15802 * Collection of internally used HTML classes. 15803 * 15804 * @todo Refactor `slider` and `carousel` properties to single `type: { slider: '', carousel: '' }` object 15805 * @type {Object} 15806 */ 15807 classes: { 15808 swipeable: 'glide--swipeable', 15809 dragging: 'glide--dragging', 15810 direction: { 15811 ltr: 'glide--ltr', 15812 rtl: 'glide--rtl' 15813 }, 15814 type: { 15815 slider: 'glide--slider', 15816 carousel: 'glide--carousel' 15817 }, 15818 slide: { 15819 clone: 'glide__slide--clone', 15820 active: 'glide__slide--active' 15821 }, 15822 arrow: { 15823 disabled: 'glide__arrow--disabled' 15824 }, 15825 nav: { 15826 active: 'glide__bullet--active' 15827 } 15828 } 15829}; 15830 15831/** 15832 * Outputs warning message to the bowser console. 15833 * 15834 * @param {String} msg 15835 * @return {Void} 15836 */ 15837function warn(msg) { 15838 console.error("[Glide warn]: ".concat(msg)); 15839} 15840 15841/** 15842 * Converts value entered as number 15843 * or string to integer value. 15844 * 15845 * @param {String} value 15846 * @returns {Number} 15847 */ 15848function toInt(value) { 15849 return parseInt(value); 15850} 15851/** 15852 * Converts value entered as number 15853 * or string to flat value. 15854 * 15855 * @param {String} value 15856 * @returns {Number} 15857 */ 15858 15859function toFloat(value) { 15860 return parseFloat(value); 15861} 15862/** 15863 * Indicates whether the specified value is a string. 15864 * 15865 * @param {*} value 15866 * @return {Boolean} 15867 */ 15868 15869function isString(value) { 15870 return typeof value === 'string'; 15871} 15872/** 15873 * Indicates whether the specified value is an object. 15874 * 15875 * @param {*} value 15876 * @return {Boolean} 15877 * 15878 * @see https://github.com/jashkenas/underscore 15879 */ 15880 15881function isObject(value) { 15882 var type = _typeof(value); 15883 15884 return type === 'function' || type === 'object' && !!value; // eslint-disable-line no-mixed-operators 15885} 15886/** 15887 * Indicates whether the specified value is a function. 15888 * 15889 * @param {*} value 15890 * @return {Boolean} 15891 */ 15892 15893function isFunction(value) { 15894 return typeof value === 'function'; 15895} 15896/** 15897 * Indicates whether the specified value is undefined. 15898 * 15899 * @param {*} value 15900 * @return {Boolean} 15901 */ 15902 15903function isUndefined(value) { 15904 return typeof value === 'undefined'; 15905} 15906/** 15907 * Indicates whether the specified value is an array. 15908 * 15909 * @param {*} value 15910 * @return {Boolean} 15911 */ 15912 15913function isArray(value) { 15914 return value.constructor === Array; 15915} 15916 15917/** 15918 * Creates and initializes specified collection of extensions. 15919 * Each extension receives access to instance of glide and rest of components. 15920 * 15921 * @param {Object} glide 15922 * @param {Object} extensions 15923 * 15924 * @returns {Object} 15925 */ 15926 15927function mount(glide, extensions, events) { 15928 var components = {}; 15929 15930 for (var name in extensions) { 15931 if (isFunction(extensions[name])) { 15932 components[name] = extensions[name](glide, components, events); 15933 } else { 15934 warn('Extension must be a function'); 15935 } 15936 } 15937 15938 for (var _name in components) { 15939 if (isFunction(components[_name].mount)) { 15940 components[_name].mount(); 15941 } 15942 } 15943 15944 return components; 15945} 15946 15947/** 15948 * Defines getter and setter property on the specified object. 15949 * 15950 * @param {Object} obj Object where property has to be defined. 15951 * @param {String} prop Name of the defined property. 15952 * @param {Object} definition Get and set definitions for the property. 15953 * @return {Void} 15954 */ 15955function define(obj, prop, definition) { 15956 Object.defineProperty(obj, prop, definition); 15957} 15958/** 15959 * Sorts aphabetically object keys. 15960 * 15961 * @param {Object} obj 15962 * @return {Object} 15963 */ 15964 15965function sortKeys(obj) { 15966 return Object.keys(obj).sort().reduce(function (r, k) { 15967 r[k] = obj[k]; 15968 return r[k], r; 15969 }, {}); 15970} 15971/** 15972 * Merges passed settings object with default options. 15973 * 15974 * @param {Object} defaults 15975 * @param {Object} settings 15976 * @return {Object} 15977 */ 15978 15979function mergeOptions(defaults, settings) { 15980 var options = Object.assign({}, defaults, settings); // `Object.assign` do not deeply merge objects, so we 15981 // have to do it manually for every nested object 15982 // in options. Although it does not look smart, 15983 // it's smaller and faster than some fancy 15984 // merging deep-merge algorithm script. 15985 15986 if (settings.hasOwnProperty('classes')) { 15987 options.classes = Object.assign({}, defaults.classes, settings.classes); 15988 var properties = ['direction', 'type', 'slide', 'arrow', 'nav']; 15989 properties.forEach(function (property) { 15990 if (settings.classes.hasOwnProperty(property)) { 15991 options.classes[property] = _objectSpread2(_objectSpread2({}, defaults.classes[property]), settings.classes[property]); 15992 } 15993 }); 15994 } 15995 15996 if (settings.hasOwnProperty('breakpoints')) { 15997 options.breakpoints = Object.assign({}, defaults.breakpoints, settings.breakpoints); 15998 } 15999 16000 return options; 16001} 16002 16003var EventsBus = /*#__PURE__*/function () { 16004 /** 16005 * Construct a EventBus instance. 16006 * 16007 * @param {Object} events 16008 */ 16009 function EventsBus() { 16010 var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 16011 16012 _classCallCheck(this, EventsBus); 16013 16014 this.events = events; 16015 this.hop = events.hasOwnProperty; 16016 } 16017 /** 16018 * Adds listener to the specifed event. 16019 * 16020 * @param {String|Array} event 16021 * @param {Function} handler 16022 */ 16023 16024 16025 _createClass(EventsBus, [{ 16026 key: "on", 16027 value: function on(event, handler) { 16028 if (isArray(event)) { 16029 for (var i = 0; i < event.length; i++) { 16030 this.on(event[i], handler); 16031 } 16032 16033 return; 16034 } // Create the event's object if not yet created 16035 16036 16037 if (!this.hop.call(this.events, event)) { 16038 this.events[event] = []; 16039 } // Add the handler to queue 16040 16041 16042 var index = this.events[event].push(handler) - 1; // Provide handle back for removal of event 16043 16044 return { 16045 remove: function remove() { 16046 delete this.events[event][index]; 16047 } 16048 }; 16049 } 16050 /** 16051 * Runs registered handlers for specified event. 16052 * 16053 * @param {String|Array} event 16054 * @param {Object=} context 16055 */ 16056 16057 }, { 16058 key: "emit", 16059 value: function emit(event, context) { 16060 if (isArray(event)) { 16061 for (var i = 0; i < event.length; i++) { 16062 this.emit(event[i], context); 16063 } 16064 16065 return; 16066 } // If the event doesn't exist, or there's no handlers in queue, just leave 16067 16068 16069 if (!this.hop.call(this.events, event)) { 16070 return; 16071 } // Cycle through events queue, fire! 16072 16073 16074 this.events[event].forEach(function (item) { 16075 item(context || {}); 16076 }); 16077 } 16078 }]); 16079 16080 return EventsBus; 16081}(); 16082 16083var Glide$1 = /*#__PURE__*/function () { 16084 /** 16085 * Construct glide. 16086 * 16087 * @param {String} selector 16088 * @param {Object} options 16089 */ 16090 function Glide(selector) { 16091 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 16092 16093 _classCallCheck(this, Glide); 16094 16095 this._c = {}; 16096 this._t = []; 16097 this._e = new EventsBus(); 16098 this.disabled = false; 16099 this.selector = selector; 16100 this.settings = mergeOptions(defaults, options); 16101 this.index = this.settings.startAt; 16102 } 16103 /** 16104 * Initializes glide. 16105 * 16106 * @param {Object} extensions Collection of extensions to initialize. 16107 * @return {Glide} 16108 */ 16109 16110 16111 _createClass(Glide, [{ 16112 key: "mount", 16113 value: function mount$1() { 16114 var extensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 16115 16116 this._e.emit('mount.before'); 16117 16118 if (isObject(extensions)) { 16119 this._c = mount(this, extensions, this._e); 16120 } else { 16121 warn('You need to provide a object on `mount()`'); 16122 } 16123 16124 this._e.emit('mount.after'); 16125 16126 return this; 16127 } 16128 /** 16129 * Collects an instance `translate` transformers. 16130 * 16131 * @param {Array} transformers Collection of transformers. 16132 * @return {Void} 16133 */ 16134 16135 }, { 16136 key: "mutate", 16137 value: function mutate() { 16138 var transformers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 16139 16140 if (isArray(transformers)) { 16141 this._t = transformers; 16142 } else { 16143 warn('You need to provide a array on `mutate()`'); 16144 } 16145 16146 return this; 16147 } 16148 /** 16149 * Updates glide with specified settings. 16150 * 16151 * @param {Object} settings 16152 * @return {Glide} 16153 */ 16154 16155 }, { 16156 key: "update", 16157 value: function update() { 16158 var settings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 16159 this.settings = mergeOptions(this.settings, settings); 16160 16161 if (settings.hasOwnProperty('startAt')) { 16162 this.index = settings.startAt; 16163 } 16164 16165 this._e.emit('update'); 16166 16167 return this; 16168 } 16169 /** 16170 * Change slide with specified pattern. A pattern must be in the special format: 16171 * `>` - Move one forward 16172 * `<` - Move one backward 16173 * `={i}` - Go to {i} zero-based slide (eq. '=1', will go to second slide) 16174 * `>>` - Rewinds to end (last slide) 16175 * `<<` - Rewinds to start (first slide) 16176 * `|>` - Move one viewport forward 16177 * `|<` - Move one viewport backward 16178 * 16179 * @param {String} pattern 16180 * @return {Glide} 16181 */ 16182 16183 }, { 16184 key: "go", 16185 value: function go(pattern) { 16186 this._c.Run.make(pattern); 16187 16188 return this; 16189 } 16190 /** 16191 * Move track by specified distance. 16192 * 16193 * @param {String} distance 16194 * @return {Glide} 16195 */ 16196 16197 }, { 16198 key: "move", 16199 value: function move(distance) { 16200 this._c.Transition.disable(); 16201 16202 this._c.Move.make(distance); 16203 16204 return this; 16205 } 16206 /** 16207 * Destroy instance and revert all changes done by this._c. 16208 * 16209 * @return {Glide} 16210 */ 16211 16212 }, { 16213 key: "destroy", 16214 value: function destroy() { 16215 this._e.emit('destroy'); 16216 16217 return this; 16218 } 16219 /** 16220 * Start instance autoplaying. 16221 * 16222 * @param {Boolean|Number} interval Run autoplaying with passed interval regardless of `autoplay` settings 16223 * @return {Glide} 16224 */ 16225 16226 }, { 16227 key: "play", 16228 value: function play() { 16229 var interval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 16230 16231 if (interval) { 16232 this.settings.autoplay = interval; 16233 } 16234 16235 this._e.emit('play'); 16236 16237 return this; 16238 } 16239 /** 16240 * Stop instance autoplaying. 16241 * 16242 * @return {Glide} 16243 */ 16244 16245 }, { 16246 key: "pause", 16247 value: function pause() { 16248 this._e.emit('pause'); 16249 16250 return this; 16251 } 16252 /** 16253 * Sets glide into a idle status. 16254 * 16255 * @return {Glide} 16256 */ 16257 16258 }, { 16259 key: "disable", 16260 value: function disable() { 16261 this.disabled = true; 16262 return this; 16263 } 16264 /** 16265 * Sets glide into a active status. 16266 * 16267 * @return {Glide} 16268 */ 16269 16270 }, { 16271 key: "enable", 16272 value: function enable() { 16273 this.disabled = false; 16274 return this; 16275 } 16276 /** 16277 * Adds cuutom event listener with handler. 16278 * 16279 * @param {String|Array} event 16280 * @param {Function} handler 16281 * @return {Glide} 16282 */ 16283 16284 }, { 16285 key: "on", 16286 value: function on(event, handler) { 16287 this._e.on(event, handler); 16288 16289 return this; 16290 } 16291 /** 16292 * Checks if glide is a precised type. 16293 * 16294 * @param {String} name 16295 * @return {Boolean} 16296 */ 16297 16298 }, { 16299 key: "isType", 16300 value: function isType(name) { 16301 return this.settings.type === name; 16302 } 16303 /** 16304 * Gets value of the core options. 16305 * 16306 * @return {Object} 16307 */ 16308 16309 }, { 16310 key: "settings", 16311 get: function get() { 16312 return this._o; 16313 } 16314 /** 16315 * Sets value of the core options. 16316 * 16317 * @param {Object} o 16318 * @return {Void} 16319 */ 16320 , 16321 set: function set(o) { 16322 if (isObject(o)) { 16323 this._o = o; 16324 } else { 16325 warn('Options must be an `object` instance.'); 16326 } 16327 } 16328 /** 16329 * Gets current index of the slider. 16330 * 16331 * @return {Object} 16332 */ 16333 16334 }, { 16335 key: "index", 16336 get: function get() { 16337 return this._i; 16338 } 16339 /** 16340 * Sets current index a slider. 16341 * 16342 * @return {Object} 16343 */ 16344 , 16345 set: function set(i) { 16346 this._i = toInt(i); 16347 } 16348 /** 16349 * Gets type name of the slider. 16350 * 16351 * @return {String} 16352 */ 16353 16354 }, { 16355 key: "type", 16356 get: function get() { 16357 return this.settings.type; 16358 } 16359 /** 16360 * Gets value of the idle status. 16361 * 16362 * @return {Boolean} 16363 */ 16364 16365 }, { 16366 key: "disabled", 16367 get: function get() { 16368 return this._d; 16369 } 16370 /** 16371 * Sets value of the idle status. 16372 * 16373 * @return {Boolean} 16374 */ 16375 , 16376 set: function set(status) { 16377 this._d = !!status; 16378 } 16379 }]); 16380 16381 return Glide; 16382}(); 16383 16384function Run (Glide, Components, Events) { 16385 var Run = { 16386 /** 16387 * Initializes autorunning of the glide. 16388 * 16389 * @return {Void} 16390 */ 16391 mount: function mount() { 16392 this._o = false; 16393 }, 16394 16395 /** 16396 * Makes glides running based on the passed moving schema. 16397 * 16398 * @param {String} move 16399 */ 16400 make: function make(move) { 16401 var _this = this; 16402 16403 if (!Glide.disabled) { 16404 !Glide.settings.waitForTransition || Glide.disable(); 16405 this.move = move; 16406 Events.emit('run.before', this.move); 16407 this.calculate(); 16408 Events.emit('run', this.move); 16409 Components.Transition.after(function () { 16410 if (_this.isStart()) { 16411 Events.emit('run.start', _this.move); 16412 } 16413 16414 if (_this.isEnd()) { 16415 Events.emit('run.end', _this.move); 16416 } 16417 16418 if (_this.isOffset()) { 16419 _this._o = false; 16420 Events.emit('run.offset', _this.move); 16421 } 16422 16423 Events.emit('run.after', _this.move); 16424 Glide.enable(); 16425 }); 16426 } 16427 }, 16428 16429 /** 16430 * Calculates current index based on defined move. 16431 * 16432 * @return {Number|Undefined} 16433 */ 16434 calculate: function calculate() { 16435 var move = this.move, 16436 length = this.length; 16437 var steps = move.steps, 16438 direction = move.direction; // By default assume that size of view is equal to one slide 16439 16440 var viewSize = 1; // While direction is `=` we want jump to 16441 // a specified index described in steps. 16442 16443 if (direction === '=') { 16444 // Check if bound is true, 16445 // as we want to avoid whitespaces. 16446 if (Glide.settings.bound && toInt(steps) > length) { 16447 Glide.index = length; 16448 return; 16449 } 16450 16451 Glide.index = steps; 16452 return; 16453 } // When pattern is equal to `>>` we want 16454 // fast forward to the last slide. 16455 16456 16457 if (direction === '>' && steps === '>') { 16458 Glide.index = length; 16459 return; 16460 } // When pattern is equal to `<<` we want 16461 // fast forward to the first slide. 16462 16463 16464 if (direction === '<' && steps === '<') { 16465 Glide.index = 0; 16466 return; 16467 } // pagination movement 16468 16469 16470 if (direction === '|') { 16471 viewSize = Glide.settings.perView || 1; 16472 } // we are moving forward 16473 16474 16475 if (direction === '>' || direction === '|' && steps === '>') { 16476 var index = calculateForwardIndex(viewSize); 16477 16478 if (index > length) { 16479 this._o = true; 16480 } 16481 16482 Glide.index = normalizeForwardIndex(index, viewSize); 16483 return; 16484 } // we are moving backward 16485 16486 16487 if (direction === '<' || direction === '|' && steps === '<') { 16488 var _index = calculateBackwardIndex(viewSize); 16489 16490 if (_index < 0) { 16491 this._o = true; 16492 } 16493 16494 Glide.index = normalizeBackwardIndex(_index, viewSize); 16495 return; 16496 } 16497 16498 warn("Invalid direction pattern [".concat(direction).concat(steps, "] has been used")); 16499 }, 16500 16501 /** 16502 * Checks if we are on the first slide. 16503 * 16504 * @return {Boolean} 16505 */ 16506 isStart: function isStart() { 16507 return Glide.index <= 0; 16508 }, 16509 16510 /** 16511 * Checks if we are on the last slide. 16512 * 16513 * @return {Boolean} 16514 */ 16515 isEnd: function isEnd() { 16516 return Glide.index >= this.length; 16517 }, 16518 16519 /** 16520 * Checks if we are making a offset run. 16521 * 16522 * @param {String} direction 16523 * @return {Boolean} 16524 */ 16525 isOffset: function isOffset() { 16526 var direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined; 16527 16528 if (!direction) { 16529 return this._o; 16530 } 16531 16532 if (!this._o) { 16533 return false; 16534 } // did we view to the right? 16535 16536 16537 if (direction === '|>') { 16538 return this.move.direction === '|' && this.move.steps === '>'; 16539 } // did we view to the left? 16540 16541 16542 if (direction === '|<') { 16543 return this.move.direction === '|' && this.move.steps === '<'; 16544 } 16545 16546 return this.move.direction === direction; 16547 }, 16548 16549 /** 16550 * Checks if bound mode is active 16551 * 16552 * @return {Boolean} 16553 */ 16554 isBound: function isBound() { 16555 return Glide.isType('slider') && Glide.settings.focusAt !== 'center' && Glide.settings.bound; 16556 } 16557 }; 16558 /** 16559 * Returns index value to move forward/to the right 16560 * 16561 * @param viewSize 16562 * @returns {Number} 16563 */ 16564 16565 function calculateForwardIndex(viewSize) { 16566 var index = Glide.index; 16567 16568 if (Glide.isType('carousel')) { 16569 return index + viewSize; 16570 } 16571 16572 return index + (viewSize - index % viewSize); 16573 } 16574 /** 16575 * Normalizes the given forward index based on glide settings, preventing it to exceed certain boundaries 16576 * 16577 * @param index 16578 * @param length 16579 * @param viewSize 16580 * @returns {Number} 16581 */ 16582 16583 16584 function normalizeForwardIndex(index, viewSize) { 16585 var length = Run.length; 16586 16587 if (index <= length) { 16588 return index; 16589 } 16590 16591 if (Glide.isType('carousel')) { 16592 return index - (length + 1); 16593 } 16594 16595 if (Glide.settings.rewind) { 16596 // bound does funny things with the length, therefor we have to be certain 16597 // that we are on the last possible index value given by bound 16598 if (Run.isBound() && !Run.isEnd()) { 16599 return length; 16600 } 16601 16602 return 0; 16603 } 16604 16605 if (Run.isBound()) { 16606 return length; 16607 } 16608 16609 return Math.floor(length / viewSize) * viewSize; 16610 } 16611 /** 16612 * Calculates index value to move backward/to the left 16613 * 16614 * @param viewSize 16615 * @returns {Number} 16616 */ 16617 16618 16619 function calculateBackwardIndex(viewSize) { 16620 var index = Glide.index; 16621 16622 if (Glide.isType('carousel')) { 16623 return index - viewSize; 16624 } // ensure our back navigation results in the same index as a forward navigation 16625 // to experience a homogeneous paging 16626 16627 16628 var view = Math.ceil(index / viewSize); 16629 return (view - 1) * viewSize; 16630 } 16631 /** 16632 * Normalizes the given backward index based on glide settings, preventing it to exceed certain boundaries 16633 * 16634 * @param index 16635 * @param length 16636 * @param viewSize 16637 * @returns {*} 16638 */ 16639 16640 16641 function normalizeBackwardIndex(index, viewSize) { 16642 var length = Run.length; 16643 16644 if (index >= 0) { 16645 return index; 16646 } 16647 16648 if (Glide.isType('carousel')) { 16649 return index + (length + 1); 16650 } 16651 16652 if (Glide.settings.rewind) { 16653 // bound does funny things with the length, therefor we have to be certain 16654 // that we are on first possible index value before we to rewind to the length given by bound 16655 if (Run.isBound() && Run.isStart()) { 16656 return length; 16657 } 16658 16659 return Math.floor(length / viewSize) * viewSize; 16660 } 16661 16662 return 0; 16663 } 16664 16665 define(Run, 'move', { 16666 /** 16667 * Gets value of the move schema. 16668 * 16669 * @returns {Object} 16670 */ 16671 get: function get() { 16672 return this._m; 16673 }, 16674 16675 /** 16676 * Sets value of the move schema. 16677 * 16678 * @returns {Object} 16679 */ 16680 set: function set(value) { 16681 var step = value.substr(1); 16682 this._m = { 16683 direction: value.substr(0, 1), 16684 steps: step ? toInt(step) ? toInt(step) : step : 0 16685 }; 16686 } 16687 }); 16688 define(Run, 'length', { 16689 /** 16690 * Gets value of the running distance based 16691 * on zero-indexing number of slides. 16692 * 16693 * @return {Number} 16694 */ 16695 get: function get() { 16696 var settings = Glide.settings; 16697 var length = Components.Html.slides.length; // If the `bound` option is active, a maximum running distance should be 16698 // reduced by `perView` and `focusAt` settings. Running distance 16699 // should end before creating an empty space after instance. 16700 16701 if (this.isBound()) { 16702 return length - 1 - (toInt(settings.perView) - 1) + toInt(settings.focusAt); 16703 } 16704 16705 return length - 1; 16706 } 16707 }); 16708 define(Run, 'offset', { 16709 /** 16710 * Gets status of the offsetting flag. 16711 * 16712 * @return {Boolean} 16713 */ 16714 get: function get() { 16715 return this._o; 16716 } 16717 }); 16718 return Run; 16719} 16720 16721/** 16722 * Returns a current time. 16723 * 16724 * @return {Number} 16725 */ 16726function now() { 16727 return new Date().getTime(); 16728} 16729 16730/** 16731 * Returns a function, that, when invoked, will only be triggered 16732 * at most once during a given window of time. 16733 * 16734 * @param {Function} func 16735 * @param {Number} wait 16736 * @param {Object=} options 16737 * @return {Function} 16738 * 16739 * @see https://github.com/jashkenas/underscore 16740 */ 16741 16742function throttle(func, wait) { 16743 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 16744 var timeout, context, args, result; 16745 var previous = 0; 16746 16747 var later = function later() { 16748 previous = options.leading === false ? 0 : now(); 16749 timeout = null; 16750 result = func.apply(context, args); 16751 if (!timeout) context = args = null; 16752 }; 16753 16754 var throttled = function throttled() { 16755 var at = now(); 16756 if (!previous && options.leading === false) previous = at; 16757 var remaining = wait - (at - previous); 16758 context = this; 16759 args = arguments; 16760 16761 if (remaining <= 0 || remaining > wait) { 16762 if (timeout) { 16763 clearTimeout(timeout); 16764 timeout = null; 16765 } 16766 16767 previous = at; 16768 result = func.apply(context, args); 16769 if (!timeout) context = args = null; 16770 } else if (!timeout && options.trailing !== false) { 16771 timeout = setTimeout(later, remaining); 16772 } 16773 16774 return result; 16775 }; 16776 16777 throttled.cancel = function () { 16778 clearTimeout(timeout); 16779 previous = 0; 16780 timeout = context = args = null; 16781 }; 16782 16783 return throttled; 16784} 16785 16786var MARGIN_TYPE = { 16787 ltr: ['marginLeft', 'marginRight'], 16788 rtl: ['marginRight', 'marginLeft'] 16789}; 16790function Gaps (Glide, Components, Events) { 16791 var Gaps = { 16792 /** 16793 * Applies gaps between slides. First and last 16794 * slides do not receive it's edge margins. 16795 * 16796 * @param {HTMLCollection} slides 16797 * @return {Void} 16798 */ 16799 apply: function apply(slides) { 16800 for (var i = 0, len = slides.length; i < len; i++) { 16801 var style = slides[i].style; 16802 var direction = Components.Direction.value; 16803 16804 if (i !== 0) { 16805 style[MARGIN_TYPE[direction][0]] = "".concat(this.value / 2, "px"); 16806 } else { 16807 style[MARGIN_TYPE[direction][0]] = ''; 16808 } 16809 16810 if (i !== slides.length - 1) { 16811 style[MARGIN_TYPE[direction][1]] = "".concat(this.value / 2, "px"); 16812 } else { 16813 style[MARGIN_TYPE[direction][1]] = ''; 16814 } 16815 } 16816 }, 16817 16818 /** 16819 * Removes gaps from the slides. 16820 * 16821 * @param {HTMLCollection} slides 16822 * @returns {Void} 16823 */ 16824 remove: function remove(slides) { 16825 for (var i = 0, len = slides.length; i < len; i++) { 16826 var style = slides[i].style; 16827 style.marginLeft = ''; 16828 style.marginRight = ''; 16829 } 16830 } 16831 }; 16832 define(Gaps, 'value', { 16833 /** 16834 * Gets value of the gap. 16835 * 16836 * @returns {Number} 16837 */ 16838 get: function get() { 16839 return toInt(Glide.settings.gap); 16840 } 16841 }); 16842 define(Gaps, 'grow', { 16843 /** 16844 * Gets additional dimensions value caused by gaps. 16845 * Used to increase width of the slides wrapper. 16846 * 16847 * @returns {Number} 16848 */ 16849 get: function get() { 16850 return Gaps.value * Components.Sizes.length; 16851 } 16852 }); 16853 define(Gaps, 'reductor', { 16854 /** 16855 * Gets reduction value caused by gaps. 16856 * Used to subtract width of the slides. 16857 * 16858 * @returns {Number} 16859 */ 16860 get: function get() { 16861 var perView = Glide.settings.perView; 16862 return Gaps.value * (perView - 1) / perView; 16863 } 16864 }); 16865 /** 16866 * Apply calculated gaps: 16867 * - after building, so slides (including clones) will receive proper margins 16868 * - on updating via API, to recalculate gaps with new options 16869 */ 16870 16871 Events.on(['build.after', 'update'], throttle(function () { 16872 Gaps.apply(Components.Html.wrapper.children); 16873 }, 30)); 16874 /** 16875 * Remove gaps: 16876 * - on destroying to bring markup to its inital state 16877 */ 16878 16879 Events.on('destroy', function () { 16880 Gaps.remove(Components.Html.wrapper.children); 16881 }); 16882 return Gaps; 16883} 16884 16885/** 16886 * Finds siblings nodes of the passed node. 16887 * 16888 * @param {Element} node 16889 * @return {Array} 16890 */ 16891function siblings(node) { 16892 if (node && node.parentNode) { 16893 var n = node.parentNode.firstChild; 16894 var matched = []; 16895 16896 for (; n; n = n.nextSibling) { 16897 if (n.nodeType === 1 && n !== node) { 16898 matched.push(n); 16899 } 16900 } 16901 16902 return matched; 16903 } 16904 16905 return []; 16906} 16907/** 16908 * Coerces a NodeList to an Array. 16909 * 16910 * @param {NodeList} nodeList 16911 * @return {Array} 16912 */ 16913 16914function toArray(nodeList) { 16915 return Array.prototype.slice.call(nodeList); 16916} 16917 16918var TRACK_SELECTOR = '[data-glide-el="track"]'; 16919function Html (Glide, Components, Events) { 16920 var Html = { 16921 /** 16922 * Setup slider HTML nodes. 16923 * 16924 * @param {Glide} glide 16925 */ 16926 mount: function mount() { 16927 this.root = Glide.selector; 16928 this.track = this.root.querySelector(TRACK_SELECTOR); 16929 this.collectSlides(); 16930 }, 16931 16932 /** 16933 * Collect slides 16934 */ 16935 collectSlides: function collectSlides() { 16936 this.slides = toArray(this.wrapper.children).filter(function (slide) { 16937 return !slide.classList.contains(Glide.settings.classes.slide.clone); 16938 }); 16939 } 16940 }; 16941 define(Html, 'root', { 16942 /** 16943 * Gets node of the glide main element. 16944 * 16945 * @return {Object} 16946 */ 16947 get: function get() { 16948 return Html._r; 16949 }, 16950 16951 /** 16952 * Sets node of the glide main element. 16953 * 16954 * @return {Object} 16955 */ 16956 set: function set(r) { 16957 if (isString(r)) { 16958 r = document.querySelector(r); 16959 } 16960 16961 if (r !== null) { 16962 Html._r = r; 16963 } else { 16964 warn('Root element must be a existing Html node'); 16965 } 16966 } 16967 }); 16968 define(Html, 'track', { 16969 /** 16970 * Gets node of the glide track with slides. 16971 * 16972 * @return {Object} 16973 */ 16974 get: function get() { 16975 return Html._t; 16976 }, 16977 16978 /** 16979 * Sets node of the glide track with slides. 16980 * 16981 * @return {Object} 16982 */ 16983 set: function set(t) { 16984 Html._t = t; 16985 } 16986 }); 16987 define(Html, 'wrapper', { 16988 /** 16989 * Gets node of the slides wrapper. 16990 * 16991 * @return {Object} 16992 */ 16993 get: function get() { 16994 return Html.track.children[0]; 16995 } 16996 }); 16997 /** 16998 * Add/remove/reorder dynamic slides 16999 */ 17000 17001 Events.on('update', function () { 17002 Html.collectSlides(); 17003 }); 17004 return Html; 17005} 17006 17007function Peek (Glide, Components, Events) { 17008 var Peek = { 17009 /** 17010 * Setups how much to peek based on settings. 17011 * 17012 * @return {Void} 17013 */ 17014 mount: function mount() { 17015 this.value = Glide.settings.peek; 17016 } 17017 }; 17018 define(Peek, 'value', { 17019 /** 17020 * Gets value of the peek. 17021 * 17022 * @returns {Number|Object} 17023 */ 17024 get: function get() { 17025 return Peek._v; 17026 }, 17027 17028 /** 17029 * Sets value of the peek. 17030 * 17031 * @param {Number|Object} value 17032 * @return {Void} 17033 */ 17034 set: function set(value) { 17035 if (isObject(value)) { 17036 value.before = toInt(value.before); 17037 value.after = toInt(value.after); 17038 } else { 17039 value = toInt(value); 17040 } 17041 17042 Peek._v = value; 17043 } 17044 }); 17045 define(Peek, 'reductor', { 17046 /** 17047 * Gets reduction value caused by peek. 17048 * 17049 * @returns {Number} 17050 */ 17051 get: function get() { 17052 var value = Peek.value; 17053 var perView = Glide.settings.perView; 17054 17055 if (isObject(value)) { 17056 return value.before / perView + value.after / perView; 17057 } 17058 17059 return value * 2 / perView; 17060 } 17061 }); 17062 /** 17063 * Recalculate peeking sizes on: 17064 * - when resizing window to update to proper percents 17065 */ 17066 17067 Events.on(['resize', 'update'], function () { 17068 Peek.mount(); 17069 }); 17070 return Peek; 17071} 17072 17073function Move (Glide, Components, Events) { 17074 var Move = { 17075 /** 17076 * Constructs move component. 17077 * 17078 * @returns {Void} 17079 */ 17080 mount: function mount() { 17081 this._o = 0; 17082 }, 17083 17084 /** 17085 * Calculates a movement value based on passed offset and currently active index. 17086 * 17087 * @param {Number} offset 17088 * @return {Void} 17089 */ 17090 make: function make() { 17091 var _this = this; 17092 17093 var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; 17094 this.offset = offset; 17095 Events.emit('move', { 17096 movement: this.value 17097 }); 17098 Components.Transition.after(function () { 17099 Events.emit('move.after', { 17100 movement: _this.value 17101 }); 17102 }); 17103 } 17104 }; 17105 define(Move, 'offset', { 17106 /** 17107 * Gets an offset value used to modify current translate. 17108 * 17109 * @return {Object} 17110 */ 17111 get: function get() { 17112 return Move._o; 17113 }, 17114 17115 /** 17116 * Sets an offset value used to modify current translate. 17117 * 17118 * @return {Object} 17119 */ 17120 set: function set(value) { 17121 Move._o = !isUndefined(value) ? toInt(value) : 0; 17122 } 17123 }); 17124 define(Move, 'translate', { 17125 /** 17126 * Gets a raw movement value. 17127 * 17128 * @return {Number} 17129 */ 17130 get: function get() { 17131 return Components.Sizes.slideWidth * Glide.index; 17132 } 17133 }); 17134 define(Move, 'value', { 17135 /** 17136 * Gets an actual movement value corrected by offset. 17137 * 17138 * @return {Number} 17139 */ 17140 get: function get() { 17141 var offset = this.offset; 17142 var translate = this.translate; 17143 17144 if (Components.Direction.is('rtl')) { 17145 return translate + offset; 17146 } 17147 17148 return translate - offset; 17149 } 17150 }); 17151 /** 17152 * Make movement to proper slide on: 17153 * - before build, so glide will start at `startAt` index 17154 * - on each standard run to move to newly calculated index 17155 */ 17156 17157 Events.on(['build.before', 'run'], function () { 17158 Move.make(); 17159 }); 17160 return Move; 17161} 17162 17163function Sizes (Glide, Components, Events) { 17164 var Sizes = { 17165 /** 17166 * Setups dimensions of slides. 17167 * 17168 * @return {Void} 17169 */ 17170 setupSlides: function setupSlides() { 17171 var width = "".concat(this.slideWidth, "px"); 17172 var slides = Components.Html.slides; 17173 17174 for (var i = 0; i < slides.length; i++) { 17175 slides[i].style.width = width; 17176 } 17177 }, 17178 17179 /** 17180 * Setups dimensions of slides wrapper. 17181 * 17182 * @return {Void} 17183 */ 17184 setupWrapper: function setupWrapper() { 17185 Components.Html.wrapper.style.width = "".concat(this.wrapperSize, "px"); 17186 }, 17187 17188 /** 17189 * Removes applied styles from HTML elements. 17190 * 17191 * @returns {Void} 17192 */ 17193 remove: function remove() { 17194 var slides = Components.Html.slides; 17195 17196 for (var i = 0; i < slides.length; i++) { 17197 slides[i].style.width = ''; 17198 } 17199 17200 Components.Html.wrapper.style.width = ''; 17201 } 17202 }; 17203 define(Sizes, 'length', { 17204 /** 17205 * Gets count number of the slides. 17206 * 17207 * @return {Number} 17208 */ 17209 get: function get() { 17210 return Components.Html.slides.length; 17211 } 17212 }); 17213 define(Sizes, 'width', { 17214 /** 17215 * Gets width value of the slider (visible area). 17216 * 17217 * @return {Number} 17218 */ 17219 get: function get() { 17220 return Components.Html.track.offsetWidth; 17221 } 17222 }); 17223 define(Sizes, 'wrapperSize', { 17224 /** 17225 * Gets size of the slides wrapper. 17226 * 17227 * @return {Number} 17228 */ 17229 get: function get() { 17230 return Sizes.slideWidth * Sizes.length + Components.Gaps.grow + Components.Clones.grow; 17231 } 17232 }); 17233 define(Sizes, 'slideWidth', { 17234 /** 17235 * Gets width value of a single slide. 17236 * 17237 * @return {Number} 17238 */ 17239 get: function get() { 17240 return Sizes.width / Glide.settings.perView - Components.Peek.reductor - Components.Gaps.reductor; 17241 } 17242 }); 17243 /** 17244 * Apply calculated glide's dimensions: 17245 * - before building, so other dimensions (e.g. translate) will be calculated propertly 17246 * - when resizing window to recalculate sildes dimensions 17247 * - on updating via API, to calculate dimensions based on new options 17248 */ 17249 17250 Events.on(['build.before', 'resize', 'update'], function () { 17251 Sizes.setupSlides(); 17252 Sizes.setupWrapper(); 17253 }); 17254 /** 17255 * Remove calculated glide's dimensions: 17256 * - on destoting to bring markup to its inital state 17257 */ 17258 17259 Events.on('destroy', function () { 17260 Sizes.remove(); 17261 }); 17262 return Sizes; 17263} 17264 17265function Build (Glide, Components, Events) { 17266 var Build = { 17267 /** 17268 * Init glide building. Adds classes, sets 17269 * dimensions and setups initial state. 17270 * 17271 * @return {Void} 17272 */ 17273 mount: function mount() { 17274 Events.emit('build.before'); 17275 this.typeClass(); 17276 this.activeClass(); 17277 Events.emit('build.after'); 17278 }, 17279 17280 /** 17281 * Adds `type` class to the glide element. 17282 * 17283 * @return {Void} 17284 */ 17285 typeClass: function typeClass() { 17286 Components.Html.root.classList.add(Glide.settings.classes.type[Glide.settings.type]); 17287 }, 17288 17289 /** 17290 * Sets active class to current slide. 17291 * 17292 * @return {Void} 17293 */ 17294 activeClass: function activeClass() { 17295 var classes = Glide.settings.classes; 17296 var slide = Components.Html.slides[Glide.index]; 17297 17298 if (slide) { 17299 slide.classList.add(classes.slide.active); 17300 siblings(slide).forEach(function (sibling) { 17301 sibling.classList.remove(classes.slide.active); 17302 }); 17303 } 17304 }, 17305 17306 /** 17307 * Removes HTML classes applied at building. 17308 * 17309 * @return {Void} 17310 */ 17311 removeClasses: function removeClasses() { 17312 var _Glide$settings$class = Glide.settings.classes, 17313 type = _Glide$settings$class.type, 17314 slide = _Glide$settings$class.slide; 17315 Components.Html.root.classList.remove(type[Glide.settings.type]); 17316 Components.Html.slides.forEach(function (sibling) { 17317 sibling.classList.remove(slide.active); 17318 }); 17319 } 17320 }; 17321 /** 17322 * Clear building classes: 17323 * - on destroying to bring HTML to its initial state 17324 * - on updating to remove classes before remounting component 17325 */ 17326 17327 Events.on(['destroy', 'update'], function () { 17328 Build.removeClasses(); 17329 }); 17330 /** 17331 * Remount component: 17332 * - on resizing of the window to calculate new dimensions 17333 * - on updating settings via API 17334 */ 17335 17336 Events.on(['resize', 'update'], function () { 17337 Build.mount(); 17338 }); 17339 /** 17340 * Swap active class of current slide: 17341 * - after each move to the new index 17342 */ 17343 17344 Events.on('move.after', function () { 17345 Build.activeClass(); 17346 }); 17347 return Build; 17348} 17349 17350function Clones (Glide, Components, Events) { 17351 var Clones = { 17352 /** 17353 * Create pattern map and collect slides to be cloned. 17354 */ 17355 mount: function mount() { 17356 this.items = []; 17357 17358 if (Glide.isType('carousel')) { 17359 this.items = this.collect(); 17360 } 17361 }, 17362 17363 /** 17364 * Collect clones with pattern. 17365 * 17366 * @return {[]} 17367 */ 17368 collect: function collect() { 17369 var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 17370 var slides = Components.Html.slides; 17371 var _Glide$settings = Glide.settings, 17372 perView = _Glide$settings.perView, 17373 classes = _Glide$settings.classes, 17374 cloningRatio = _Glide$settings.cloningRatio; 17375 17376 if (slides.length > 0) { 17377 var peekIncrementer = +!!Glide.settings.peek; 17378 var cloneCount = perView + peekIncrementer + Math.round(perView / 2); 17379 var append = slides.slice(0, cloneCount).reverse(); 17380 var prepend = slides.slice(cloneCount * -1); 17381 17382 for (var r = 0; r < Math.max(cloningRatio, Math.floor(perView / slides.length)); r++) { 17383 for (var i = 0; i < append.length; i++) { 17384 var clone = append[i].cloneNode(true); 17385 clone.classList.add(classes.slide.clone); 17386 items.push(clone); 17387 } 17388 17389 for (var _i = 0; _i < prepend.length; _i++) { 17390 var _clone = prepend[_i].cloneNode(true); 17391 17392 _clone.classList.add(classes.slide.clone); 17393 17394 items.unshift(_clone); 17395 } 17396 } 17397 } 17398 17399 return items; 17400 }, 17401 17402 /** 17403 * Append cloned slides with generated pattern. 17404 * 17405 * @return {Void} 17406 */ 17407 append: function append() { 17408 var items = this.items; 17409 var _Components$Html = Components.Html, 17410 wrapper = _Components$Html.wrapper, 17411 slides = _Components$Html.slides; 17412 var half = Math.floor(items.length / 2); 17413 var prepend = items.slice(0, half).reverse(); 17414 var append = items.slice(half * -1).reverse(); 17415 var width = "".concat(Components.Sizes.slideWidth, "px"); 17416 17417 for (var i = 0; i < append.length; i++) { 17418 wrapper.appendChild(append[i]); 17419 } 17420 17421 for (var _i2 = 0; _i2 < prepend.length; _i2++) { 17422 wrapper.insertBefore(prepend[_i2], slides[0]); 17423 } 17424 17425 for (var _i3 = 0; _i3 < items.length; _i3++) { 17426 items[_i3].style.width = width; 17427 } 17428 }, 17429 17430 /** 17431 * Remove all cloned slides. 17432 * 17433 * @return {Void} 17434 */ 17435 remove: function remove() { 17436 var items = this.items; 17437 17438 for (var i = 0; i < items.length; i++) { 17439 Components.Html.wrapper.removeChild(items[i]); 17440 } 17441 } 17442 }; 17443 define(Clones, 'grow', { 17444 /** 17445 * Gets additional dimensions value caused by clones. 17446 * 17447 * @return {Number} 17448 */ 17449 get: function get() { 17450 return (Components.Sizes.slideWidth + Components.Gaps.value) * Clones.items.length; 17451 } 17452 }); 17453 /** 17454 * Append additional slide's clones: 17455 * - while glide's type is `carousel` 17456 */ 17457 17458 Events.on('update', function () { 17459 Clones.remove(); 17460 Clones.mount(); 17461 Clones.append(); 17462 }); 17463 /** 17464 * Append additional slide's clones: 17465 * - while glide's type is `carousel` 17466 */ 17467 17468 Events.on('build.before', function () { 17469 if (Glide.isType('carousel')) { 17470 Clones.append(); 17471 } 17472 }); 17473 /** 17474 * Remove clones HTMLElements: 17475 * - on destroying, to bring HTML to its initial state 17476 */ 17477 17478 Events.on('destroy', function () { 17479 Clones.remove(); 17480 }); 17481 return Clones; 17482} 17483 17484var EventsBinder = /*#__PURE__*/function () { 17485 /** 17486 * Construct a EventsBinder instance. 17487 */ 17488 function EventsBinder() { 17489 var listeners = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 17490 17491 _classCallCheck(this, EventsBinder); 17492 17493 this.listeners = listeners; 17494 } 17495 /** 17496 * Adds events listeners to arrows HTML elements. 17497 * 17498 * @param {String|Array} events 17499 * @param {Element|Window|Document} el 17500 * @param {Function} closure 17501 * @param {Boolean|Object} capture 17502 * @return {Void} 17503 */ 17504 17505 17506 _createClass(EventsBinder, [{ 17507 key: "on", 17508 value: function on(events, el, closure) { 17509 var capture = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 17510 17511 if (isString(events)) { 17512 events = [events]; 17513 } 17514 17515 for (var i = 0; i < events.length; i++) { 17516 this.listeners[events[i]] = closure; 17517 el.addEventListener(events[i], this.listeners[events[i]], capture); 17518 } 17519 } 17520 /** 17521 * Removes event listeners from arrows HTML elements. 17522 * 17523 * @param {String|Array} events 17524 * @param {Element|Window|Document} el 17525 * @param {Boolean|Object} capture 17526 * @return {Void} 17527 */ 17528 17529 }, { 17530 key: "off", 17531 value: function off(events, el) { 17532 var capture = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 17533 17534 if (isString(events)) { 17535 events = [events]; 17536 } 17537 17538 for (var i = 0; i < events.length; i++) { 17539 el.removeEventListener(events[i], this.listeners[events[i]], capture); 17540 } 17541 } 17542 /** 17543 * Destroy collected listeners. 17544 * 17545 * @returns {Void} 17546 */ 17547 17548 }, { 17549 key: "destroy", 17550 value: function destroy() { 17551 delete this.listeners; 17552 } 17553 }]); 17554 17555 return EventsBinder; 17556}(); 17557 17558function Resize (Glide, Components, Events) { 17559 /** 17560 * Instance of the binder for DOM Events. 17561 * 17562 * @type {EventsBinder} 17563 */ 17564 var Binder = new EventsBinder(); 17565 var Resize = { 17566 /** 17567 * Initializes window bindings. 17568 */ 17569 mount: function mount() { 17570 this.bind(); 17571 }, 17572 17573 /** 17574 * Binds `rezsize` listener to the window. 17575 * It's a costly event, so we are debouncing it. 17576 * 17577 * @return {Void} 17578 */ 17579 bind: function bind() { 17580 Binder.on('resize', window, throttle(function () { 17581 Events.emit('resize'); 17582 }, Glide.settings.throttle)); 17583 }, 17584 17585 /** 17586 * Unbinds listeners from the window. 17587 * 17588 * @return {Void} 17589 */ 17590 unbind: function unbind() { 17591 Binder.off('resize', window); 17592 } 17593 }; 17594 /** 17595 * Remove bindings from window: 17596 * - on destroying, to remove added EventListener 17597 */ 17598 17599 Events.on('destroy', function () { 17600 Resize.unbind(); 17601 Binder.destroy(); 17602 }); 17603 return Resize; 17604} 17605 17606var VALID_DIRECTIONS = ['ltr', 'rtl']; 17607var FLIPED_MOVEMENTS = { 17608 '>': '<', 17609 '<': '>', 17610 '=': '=' 17611}; 17612function Direction (Glide, Components, Events) { 17613 var Direction = { 17614 /** 17615 * Setups gap value based on settings. 17616 * 17617 * @return {Void} 17618 */ 17619 mount: function mount() { 17620 this.value = Glide.settings.direction; 17621 }, 17622 17623 /** 17624 * Resolves pattern based on direction value 17625 * 17626 * @param {String} pattern 17627 * @returns {String} 17628 */ 17629 resolve: function resolve(pattern) { 17630 var token = pattern.slice(0, 1); 17631 17632 if (this.is('rtl')) { 17633 return pattern.split(token).join(FLIPED_MOVEMENTS[token]); 17634 } 17635 17636 return pattern; 17637 }, 17638 17639 /** 17640 * Checks value of direction mode. 17641 * 17642 * @param {String} direction 17643 * @returns {Boolean} 17644 */ 17645 is: function is(direction) { 17646 return this.value === direction; 17647 }, 17648 17649 /** 17650 * Applies direction class to the root HTML element. 17651 * 17652 * @return {Void} 17653 */ 17654 addClass: function addClass() { 17655 Components.Html.root.classList.add(Glide.settings.classes.direction[this.value]); 17656 }, 17657 17658 /** 17659 * Removes direction class from the root HTML element. 17660 * 17661 * @return {Void} 17662 */ 17663 removeClass: function removeClass() { 17664 Components.Html.root.classList.remove(Glide.settings.classes.direction[this.value]); 17665 } 17666 }; 17667 define(Direction, 'value', { 17668 /** 17669 * Gets value of the direction. 17670 * 17671 * @returns {Number} 17672 */ 17673 get: function get() { 17674 return Direction._v; 17675 }, 17676 17677 /** 17678 * Sets value of the direction. 17679 * 17680 * @param {String} value 17681 * @return {Void} 17682 */ 17683 set: function set(value) { 17684 if (VALID_DIRECTIONS.indexOf(value) > -1) { 17685 Direction._v = value; 17686 } else { 17687 warn('Direction value must be `ltr` or `rtl`'); 17688 } 17689 } 17690 }); 17691 /** 17692 * Clear direction class: 17693 * - on destroy to bring HTML to its initial state 17694 * - on update to remove class before reappling bellow 17695 */ 17696 17697 Events.on(['destroy', 'update'], function () { 17698 Direction.removeClass(); 17699 }); 17700 /** 17701 * Remount component: 17702 * - on update to reflect changes in direction value 17703 */ 17704 17705 Events.on('update', function () { 17706 Direction.mount(); 17707 }); 17708 /** 17709 * Apply direction class: 17710 * - before building to apply class for the first time 17711 * - on updating to reapply direction class that may changed 17712 */ 17713 17714 Events.on(['build.before', 'update'], function () { 17715 Direction.addClass(); 17716 }); 17717 return Direction; 17718} 17719 17720/** 17721 * Reflects value of glide movement. 17722 * 17723 * @param {Object} Glide 17724 * @param {Object} Components 17725 * @return {Object} 17726 */ 17727function Rtl (Glide, Components) { 17728 return { 17729 /** 17730 * Negates the passed translate if glide is in RTL option. 17731 * 17732 * @param {Number} translate 17733 * @return {Number} 17734 */ 17735 modify: function modify(translate) { 17736 if (Components.Direction.is('rtl')) { 17737 return -translate; 17738 } 17739 17740 return translate; 17741 } 17742 }; 17743} 17744 17745/** 17746 * Updates glide movement with a `gap` settings. 17747 * 17748 * @param {Object} Glide 17749 * @param {Object} Components 17750 * @return {Object} 17751 */ 17752function Gap (Glide, Components) { 17753 return { 17754 /** 17755 * Modifies passed translate value with number in the `gap` settings. 17756 * 17757 * @param {Number} translate 17758 * @return {Number} 17759 */ 17760 modify: function modify(translate) { 17761 var multiplier = Math.floor(translate / Components.Sizes.slideWidth); 17762 return translate + Components.Gaps.value * multiplier; 17763 } 17764 }; 17765} 17766 17767/** 17768 * Updates glide movement with width of additional clones width. 17769 * 17770 * @param {Object} Glide 17771 * @param {Object} Components 17772 * @return {Object} 17773 */ 17774function Grow (Glide, Components) { 17775 return { 17776 /** 17777 * Adds to the passed translate width of the half of clones. 17778 * 17779 * @param {Number} translate 17780 * @return {Number} 17781 */ 17782 modify: function modify(translate) { 17783 return translate + Components.Clones.grow / 2; 17784 } 17785 }; 17786} 17787 17788/** 17789 * Updates glide movement with a `peek` settings. 17790 * 17791 * @param {Object} Glide 17792 * @param {Object} Components 17793 * @return {Object} 17794 */ 17795 17796function Peeking (Glide, Components) { 17797 return { 17798 /** 17799 * Modifies passed translate value with a `peek` setting. 17800 * 17801 * @param {Number} translate 17802 * @return {Number} 17803 */ 17804 modify: function modify(translate) { 17805 if (Glide.settings.focusAt >= 0) { 17806 var peek = Components.Peek.value; 17807 17808 if (isObject(peek)) { 17809 return translate - peek.before; 17810 } 17811 17812 return translate - peek; 17813 } 17814 17815 return translate; 17816 } 17817 }; 17818} 17819 17820/** 17821 * Updates glide movement with a `focusAt` settings. 17822 * 17823 * @param {Object} Glide 17824 * @param {Object} Components 17825 * @return {Object} 17826 */ 17827function Focusing (Glide, Components) { 17828 return { 17829 /** 17830 * Modifies passed translate value with index in the `focusAt` setting. 17831 * 17832 * @param {Number} translate 17833 * @return {Number} 17834 */ 17835 modify: function modify(translate) { 17836 var gap = Components.Gaps.value; 17837 var width = Components.Sizes.width; 17838 var focusAt = Glide.settings.focusAt; 17839 var slideWidth = Components.Sizes.slideWidth; 17840 17841 if (focusAt === 'center') { 17842 return translate - (width / 2 - slideWidth / 2); 17843 } 17844 17845 return translate - slideWidth * focusAt - gap * focusAt; 17846 } 17847 }; 17848} 17849 17850/** 17851 * Applies diffrent transformers on translate value. 17852 * 17853 * @param {Object} Glide 17854 * @param {Object} Components 17855 * @return {Object} 17856 */ 17857 17858function mutator (Glide, Components, Events) { 17859 /** 17860 * Merge instance transformers with collection of default transformers. 17861 * It's important that the Rtl component be last on the list, 17862 * so it reflects all previous transformations. 17863 * 17864 * @type {Array} 17865 */ 17866 var TRANSFORMERS = [Gap, Grow, Peeking, Focusing].concat(Glide._t, [Rtl]); 17867 return { 17868 /** 17869 * Piplines translate value with registered transformers. 17870 * 17871 * @param {Number} translate 17872 * @return {Number} 17873 */ 17874 mutate: function mutate(translate) { 17875 for (var i = 0; i < TRANSFORMERS.length; i++) { 17876 var transformer = TRANSFORMERS[i]; 17877 17878 if (isFunction(transformer) && isFunction(transformer().modify)) { 17879 translate = transformer(Glide, Components, Events).modify(translate); 17880 } else { 17881 warn('Transformer should be a function that returns an object with `modify()` method'); 17882 } 17883 } 17884 17885 return translate; 17886 } 17887 }; 17888} 17889 17890function Translate (Glide, Components, Events) { 17891 var Translate = { 17892 /** 17893 * Sets value of translate on HTML element. 17894 * 17895 * @param {Number} value 17896 * @return {Void} 17897 */ 17898 set: function set(value) { 17899 var transform = mutator(Glide, Components).mutate(value); 17900 var translate3d = "translate3d(".concat(-1 * transform, "px, 0px, 0px)"); 17901 Components.Html.wrapper.style.mozTransform = translate3d; // needed for supported Firefox 10-15 17902 17903 Components.Html.wrapper.style.webkitTransform = translate3d; // needed for supported Chrome 10-35, Safari 5.1-8, and Opera 15-22 17904 17905 Components.Html.wrapper.style.transform = translate3d; 17906 }, 17907 17908 /** 17909 * Removes value of translate from HTML element. 17910 * 17911 * @return {Void} 17912 */ 17913 remove: function remove() { 17914 Components.Html.wrapper.style.transform = ''; 17915 }, 17916 17917 /** 17918 * @return {number} 17919 */ 17920 getStartIndex: function getStartIndex() { 17921 var length = Components.Sizes.length; 17922 var index = Glide.index; 17923 var perView = Glide.settings.perView; 17924 17925 if (Components.Run.isOffset('>') || Components.Run.isOffset('|>')) { 17926 return length + (index - perView); 17927 } // "modulo length" converts an index that equals length to zero 17928 17929 17930 return (index + perView) % length; 17931 }, 17932 17933 /** 17934 * @return {number} 17935 */ 17936 getTravelDistance: function getTravelDistance() { 17937 var travelDistance = Components.Sizes.slideWidth * Glide.settings.perView; 17938 17939 if (Components.Run.isOffset('>') || Components.Run.isOffset('|>')) { 17940 // reverse travel distance so that we don't have to change subtract operations 17941 return travelDistance * -1; 17942 } 17943 17944 return travelDistance; 17945 } 17946 }; 17947 /** 17948 * Set new translate value: 17949 * - on move to reflect index change 17950 * - on updating via API to reflect possible changes in options 17951 */ 17952 17953 Events.on('move', function (context) { 17954 if (!Glide.isType('carousel') || !Components.Run.isOffset()) { 17955 return Translate.set(context.movement); 17956 } 17957 17958 Components.Transition.after(function () { 17959 Events.emit('translate.jump'); 17960 Translate.set(Components.Sizes.slideWidth * Glide.index); 17961 }); 17962 var startWidth = Components.Sizes.slideWidth * Components.Translate.getStartIndex(); 17963 return Translate.set(startWidth - Components.Translate.getTravelDistance()); 17964 }); 17965 /** 17966 * Remove translate: 17967 * - on destroying to bring markup to its inital state 17968 */ 17969 17970 Events.on('destroy', function () { 17971 Translate.remove(); 17972 }); 17973 return Translate; 17974} 17975 17976function Transition (Glide, Components, Events) { 17977 /** 17978 * Holds inactivity status of transition. 17979 * When true transition is not applied. 17980 * 17981 * @type {Boolean} 17982 */ 17983 var disabled = false; 17984 var Transition = { 17985 /** 17986 * Composes string of the CSS transition. 17987 * 17988 * @param {String} property 17989 * @return {String} 17990 */ 17991 compose: function compose(property) { 17992 var settings = Glide.settings; 17993 17994 if (disabled) { 17995 return "".concat(property, " 0ms ").concat(settings.animationTimingFunc); 17996 } 17997 17998 return "".concat(property, " ").concat(this.duration, "ms ").concat(settings.animationTimingFunc); 17999 }, 18000 18001 /** 18002 * Sets value of transition on HTML element. 18003 * 18004 * @param {String=} property 18005 * @return {Void} 18006 */ 18007 set: function set() { 18008 var property = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'transform'; 18009 Components.Html.wrapper.style.transition = this.compose(property); 18010 }, 18011 18012 /** 18013 * Removes value of transition from HTML element. 18014 * 18015 * @return {Void} 18016 */ 18017 remove: function remove() { 18018 Components.Html.wrapper.style.transition = ''; 18019 }, 18020 18021 /** 18022 * Runs callback after animation. 18023 * 18024 * @param {Function} callback 18025 * @return {Void} 18026 */ 18027 after: function after(callback) { 18028 setTimeout(function () { 18029 callback(); 18030 }, this.duration); 18031 }, 18032 18033 /** 18034 * Enable transition. 18035 * 18036 * @return {Void} 18037 */ 18038 enable: function enable() { 18039 disabled = false; 18040 this.set(); 18041 }, 18042 18043 /** 18044 * Disable transition. 18045 * 18046 * @return {Void} 18047 */ 18048 disable: function disable() { 18049 disabled = true; 18050 this.set(); 18051 } 18052 }; 18053 define(Transition, 'duration', { 18054 /** 18055 * Gets duration of the transition based 18056 * on currently running animation type. 18057 * 18058 * @return {Number} 18059 */ 18060 get: function get() { 18061 var settings = Glide.settings; 18062 18063 if (Glide.isType('slider') && Components.Run.offset) { 18064 return settings.rewindDuration; 18065 } 18066 18067 return settings.animationDuration; 18068 } 18069 }); 18070 /** 18071 * Set transition `style` value: 18072 * - on each moving, because it may be cleared by offset move 18073 */ 18074 18075 Events.on('move', function () { 18076 Transition.set(); 18077 }); 18078 /** 18079 * Disable transition: 18080 * - before initial build to avoid transitioning from `0` to `startAt` index 18081 * - while resizing window and recalculating dimensions 18082 * - on jumping from offset transition at start and end edges in `carousel` type 18083 */ 18084 18085 Events.on(['build.before', 'resize', 'translate.jump'], function () { 18086 Transition.disable(); 18087 }); 18088 /** 18089 * Enable transition: 18090 * - on each running, because it may be disabled by offset move 18091 */ 18092 18093 Events.on('run', function () { 18094 Transition.enable(); 18095 }); 18096 /** 18097 * Remove transition: 18098 * - on destroying to bring markup to its inital state 18099 */ 18100 18101 Events.on('destroy', function () { 18102 Transition.remove(); 18103 }); 18104 return Transition; 18105} 18106 18107/** 18108 * Test via a getter in the options object to see 18109 * if the passive property is accessed. 18110 * 18111 * @see https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection 18112 */ 18113var supportsPassive = false; 18114 18115try { 18116 var opts = Object.defineProperty({}, 'passive', { 18117 get: function get() { 18118 supportsPassive = true; 18119 } 18120 }); 18121 window.addEventListener('testPassive', null, opts); 18122 window.removeEventListener('testPassive', null, opts); 18123} catch (e) {} 18124 18125var supportsPassive$1 = supportsPassive; 18126 18127var START_EVENTS = ['touchstart', 'mousedown']; 18128var MOVE_EVENTS = ['touchmove', 'mousemove']; 18129var END_EVENTS = ['touchend', 'touchcancel', 'mouseup', 'mouseleave']; 18130var MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'mouseleave']; 18131function Swipe (Glide, Components, Events) { 18132 /** 18133 * Instance of the binder for DOM Events. 18134 * 18135 * @type {EventsBinder} 18136 */ 18137 var Binder = new EventsBinder(); 18138 var swipeSin = 0; 18139 var swipeStartX = 0; 18140 var swipeStartY = 0; 18141 var disabled = false; 18142 var capture = supportsPassive$1 ? { 18143 passive: true 18144 } : false; 18145 var Swipe = { 18146 /** 18147 * Initializes swipe bindings. 18148 * 18149 * @return {Void} 18150 */ 18151 mount: function mount() { 18152 this.bindSwipeStart(); 18153 }, 18154 18155 /** 18156 * Handler for `swipestart` event. Calculates entry points of the user's tap. 18157 * 18158 * @param {Object} event 18159 * @return {Void} 18160 */ 18161 start: function start(event) { 18162 if (!disabled && !Glide.disabled) { 18163 this.disable(); 18164 var swipe = this.touches(event); 18165 swipeSin = null; 18166 swipeStartX = toInt(swipe.pageX); 18167 swipeStartY = toInt(swipe.pageY); 18168 this.bindSwipeMove(); 18169 this.bindSwipeEnd(); 18170 Events.emit('swipe.start'); 18171 } 18172 }, 18173 18174 /** 18175 * Handler for `swipemove` event. Calculates user's tap angle and distance. 18176 * 18177 * @param {Object} event 18178 */ 18179 move: function move(event) { 18180 if (!Glide.disabled) { 18181 var _Glide$settings = Glide.settings, 18182 touchAngle = _Glide$settings.touchAngle, 18183 touchRatio = _Glide$settings.touchRatio, 18184 classes = _Glide$settings.classes; 18185 var swipe = this.touches(event); 18186 var subExSx = toInt(swipe.pageX) - swipeStartX; 18187 var subEySy = toInt(swipe.pageY) - swipeStartY; 18188 var powEX = Math.abs(subExSx << 2); 18189 var powEY = Math.abs(subEySy << 2); 18190 var swipeHypotenuse = Math.sqrt(powEX + powEY); 18191 var swipeCathetus = Math.sqrt(powEY); 18192 swipeSin = Math.asin(swipeCathetus / swipeHypotenuse); 18193 18194 if (swipeSin * 180 / Math.PI < touchAngle) { 18195 event.stopPropagation(); 18196 Components.Move.make(subExSx * toFloat(touchRatio)); 18197 Components.Html.root.classList.add(classes.dragging); 18198 Events.emit('swipe.move'); 18199 } else { 18200 return false; 18201 } 18202 } 18203 }, 18204 18205 /** 18206 * Handler for `swipeend` event. Finitializes user's tap and decides about glide move. 18207 * 18208 * @param {Object} event 18209 * @return {Void} 18210 */ 18211 end: function end(event) { 18212 if (!Glide.disabled) { 18213 var _Glide$settings2 = Glide.settings, 18214 perSwipe = _Glide$settings2.perSwipe, 18215 touchAngle = _Glide$settings2.touchAngle, 18216 classes = _Glide$settings2.classes; 18217 var swipe = this.touches(event); 18218 var threshold = this.threshold(event); 18219 var swipeDistance = swipe.pageX - swipeStartX; 18220 var swipeDeg = swipeSin * 180 / Math.PI; 18221 this.enable(); 18222 18223 if (swipeDistance > threshold && swipeDeg < touchAngle) { 18224 Components.Run.make(Components.Direction.resolve("".concat(perSwipe, "<"))); 18225 } else if (swipeDistance < -threshold && swipeDeg < touchAngle) { 18226 Components.Run.make(Components.Direction.resolve("".concat(perSwipe, ">"))); 18227 } else { 18228 // While swipe don't reach distance apply previous transform. 18229 Components.Move.make(); 18230 } 18231 18232 Components.Html.root.classList.remove(classes.dragging); 18233 this.unbindSwipeMove(); 18234 this.unbindSwipeEnd(); 18235 Events.emit('swipe.end'); 18236 } 18237 }, 18238 18239 /** 18240 * Binds swipe's starting event. 18241 * 18242 * @return {Void} 18243 */ 18244 bindSwipeStart: function bindSwipeStart() { 18245 var _this = this; 18246 18247 var _Glide$settings3 = Glide.settings, 18248 swipeThreshold = _Glide$settings3.swipeThreshold, 18249 dragThreshold = _Glide$settings3.dragThreshold; 18250 18251 if (swipeThreshold) { 18252 Binder.on(START_EVENTS[0], Components.Html.wrapper, function (event) { 18253 _this.start(event); 18254 }, capture); 18255 } 18256 18257 if (dragThreshold) { 18258 Binder.on(START_EVENTS[1], Components.Html.wrapper, function (event) { 18259 _this.start(event); 18260 }, capture); 18261 } 18262 }, 18263 18264 /** 18265 * Unbinds swipe's starting event. 18266 * 18267 * @return {Void} 18268 */ 18269 unbindSwipeStart: function unbindSwipeStart() { 18270 Binder.off(START_EVENTS[0], Components.Html.wrapper, capture); 18271 Binder.off(START_EVENTS[1], Components.Html.wrapper, capture); 18272 }, 18273 18274 /** 18275 * Binds swipe's moving event. 18276 * 18277 * @return {Void} 18278 */ 18279 bindSwipeMove: function bindSwipeMove() { 18280 var _this2 = this; 18281 18282 Binder.on(MOVE_EVENTS, Components.Html.wrapper, throttle(function (event) { 18283 _this2.move(event); 18284 }, Glide.settings.throttle), capture); 18285 }, 18286 18287 /** 18288 * Unbinds swipe's moving event. 18289 * 18290 * @return {Void} 18291 */ 18292 unbindSwipeMove: function unbindSwipeMove() { 18293 Binder.off(MOVE_EVENTS, Components.Html.wrapper, capture); 18294 }, 18295 18296 /** 18297 * Binds swipe's ending event. 18298 * 18299 * @return {Void} 18300 */ 18301 bindSwipeEnd: function bindSwipeEnd() { 18302 var _this3 = this; 18303 18304 Binder.on(END_EVENTS, Components.Html.wrapper, function (event) { 18305 _this3.end(event); 18306 }); 18307 }, 18308 18309 /** 18310 * Unbinds swipe's ending event. 18311 * 18312 * @return {Void} 18313 */ 18314 unbindSwipeEnd: function unbindSwipeEnd() { 18315 Binder.off(END_EVENTS, Components.Html.wrapper); 18316 }, 18317 18318 /** 18319 * Normalizes event touches points accorting to different types. 18320 * 18321 * @param {Object} event 18322 */ 18323 touches: function touches(event) { 18324 if (MOUSE_EVENTS.indexOf(event.type) > -1) { 18325 return event; 18326 } 18327 18328 return event.touches[0] || event.changedTouches[0]; 18329 }, 18330 18331 /** 18332 * Gets value of minimum swipe distance settings based on event type. 18333 * 18334 * @return {Number} 18335 */ 18336 threshold: function threshold(event) { 18337 var settings = Glide.settings; 18338 18339 if (MOUSE_EVENTS.indexOf(event.type) > -1) { 18340 return settings.dragThreshold; 18341 } 18342 18343 return settings.swipeThreshold; 18344 }, 18345 18346 /** 18347 * Enables swipe event. 18348 * 18349 * @return {self} 18350 */ 18351 enable: function enable() { 18352 disabled = false; 18353 Components.Transition.enable(); 18354 return this; 18355 }, 18356 18357 /** 18358 * Disables swipe event. 18359 * 18360 * @return {self} 18361 */ 18362 disable: function disable() { 18363 disabled = true; 18364 Components.Transition.disable(); 18365 return this; 18366 } 18367 }; 18368 /** 18369 * Add component class: 18370 * - after initial building 18371 */ 18372 18373 Events.on('build.after', function () { 18374 Components.Html.root.classList.add(Glide.settings.classes.swipeable); 18375 }); 18376 /** 18377 * Remove swiping bindings: 18378 * - on destroying, to remove added EventListeners 18379 */ 18380 18381 Events.on('destroy', function () { 18382 Swipe.unbindSwipeStart(); 18383 Swipe.unbindSwipeMove(); 18384 Swipe.unbindSwipeEnd(); 18385 Binder.destroy(); 18386 }); 18387 return Swipe; 18388} 18389 18390function Images (Glide, Components, Events) { 18391 /** 18392 * Instance of the binder for DOM Events. 18393 * 18394 * @type {EventsBinder} 18395 */ 18396 var Binder = new EventsBinder(); 18397 var Images = { 18398 /** 18399 * Binds listener to glide wrapper. 18400 * 18401 * @return {Void} 18402 */ 18403 mount: function mount() { 18404 this.bind(); 18405 }, 18406 18407 /** 18408 * Binds `dragstart` event on wrapper to prevent dragging images. 18409 * 18410 * @return {Void} 18411 */ 18412 bind: function bind() { 18413 Binder.on('dragstart', Components.Html.wrapper, this.dragstart); 18414 }, 18415 18416 /** 18417 * Unbinds `dragstart` event on wrapper. 18418 * 18419 * @return {Void} 18420 */ 18421 unbind: function unbind() { 18422 Binder.off('dragstart', Components.Html.wrapper); 18423 }, 18424 18425 /** 18426 * Event handler. Prevents dragging. 18427 * 18428 * @return {Void} 18429 */ 18430 dragstart: function dragstart(event) { 18431 event.preventDefault(); 18432 } 18433 }; 18434 /** 18435 * Remove bindings from images: 18436 * - on destroying, to remove added EventListeners 18437 */ 18438 18439 Events.on('destroy', function () { 18440 Images.unbind(); 18441 Binder.destroy(); 18442 }); 18443 return Images; 18444} 18445 18446function Anchors (Glide, Components, Events) { 18447 /** 18448 * Instance of the binder for DOM Events. 18449 * 18450 * @type {EventsBinder} 18451 */ 18452 var Binder = new EventsBinder(); 18453 /** 18454 * Holds detaching status of anchors. 18455 * Prevents detaching of already detached anchors. 18456 * 18457 * @private 18458 * @type {Boolean} 18459 */ 18460 18461 var detached = false; 18462 /** 18463 * Holds preventing status of anchors. 18464 * If `true` redirection after click will be disabled. 18465 * 18466 * @private 18467 * @type {Boolean} 18468 */ 18469 18470 var prevented = false; 18471 var Anchors = { 18472 /** 18473 * Setups a initial state of anchors component. 18474 * 18475 * @returns {Void} 18476 */ 18477 mount: function mount() { 18478 /** 18479 * Holds collection of anchors elements. 18480 * 18481 * @private 18482 * @type {HTMLCollection} 18483 */ 18484 this._a = Components.Html.wrapper.querySelectorAll('a'); 18485 this.bind(); 18486 }, 18487 18488 /** 18489 * Binds events to anchors inside a track. 18490 * 18491 * @return {Void} 18492 */ 18493 bind: function bind() { 18494 Binder.on('click', Components.Html.wrapper, this.click); 18495 }, 18496 18497 /** 18498 * Unbinds events attached to anchors inside a track. 18499 * 18500 * @return {Void} 18501 */ 18502 unbind: function unbind() { 18503 Binder.off('click', Components.Html.wrapper); 18504 }, 18505 18506 /** 18507 * Handler for click event. Prevents clicks when glide is in `prevent` status. 18508 * 18509 * @param {Object} event 18510 * @return {Void} 18511 */ 18512 click: function click(event) { 18513 if (prevented) { 18514 event.stopPropagation(); 18515 event.preventDefault(); 18516 } 18517 }, 18518 18519 /** 18520 * Detaches anchors click event inside glide. 18521 * 18522 * @return {self} 18523 */ 18524 detach: function detach() { 18525 prevented = true; 18526 18527 if (!detached) { 18528 for (var i = 0; i < this.items.length; i++) { 18529 this.items[i].draggable = false; 18530 } 18531 18532 detached = true; 18533 } 18534 18535 return this; 18536 }, 18537 18538 /** 18539 * Attaches anchors click events inside glide. 18540 * 18541 * @return {self} 18542 */ 18543 attach: function attach() { 18544 prevented = false; 18545 18546 if (detached) { 18547 for (var i = 0; i < this.items.length; i++) { 18548 this.items[i].draggable = true; 18549 } 18550 18551 detached = false; 18552 } 18553 18554 return this; 18555 } 18556 }; 18557 define(Anchors, 'items', { 18558 /** 18559 * Gets collection of the arrows HTML elements. 18560 * 18561 * @return {HTMLElement[]} 18562 */ 18563 get: function get() { 18564 return Anchors._a; 18565 } 18566 }); 18567 /** 18568 * Detach anchors inside slides: 18569 * - on swiping, so they won't redirect to its `href` attributes 18570 */ 18571 18572 Events.on('swipe.move', function () { 18573 Anchors.detach(); 18574 }); 18575 /** 18576 * Attach anchors inside slides: 18577 * - after swiping and transitions ends, so they can redirect after click again 18578 */ 18579 18580 Events.on('swipe.end', function () { 18581 Components.Transition.after(function () { 18582 Anchors.attach(); 18583 }); 18584 }); 18585 /** 18586 * Unbind anchors inside slides: 18587 * - on destroying, to bring anchors to its initial state 18588 */ 18589 18590 Events.on('destroy', function () { 18591 Anchors.attach(); 18592 Anchors.unbind(); 18593 Binder.destroy(); 18594 }); 18595 return Anchors; 18596} 18597 18598var NAV_SELECTOR = '[data-glide-el="controls[nav]"]'; 18599var CONTROLS_SELECTOR = '[data-glide-el^="controls"]'; 18600var PREVIOUS_CONTROLS_SELECTOR = "".concat(CONTROLS_SELECTOR, " [data-glide-dir*=\"<\"]"); 18601var NEXT_CONTROLS_SELECTOR = "".concat(CONTROLS_SELECTOR, " [data-glide-dir*=\">\"]"); 18602function Controls (Glide, Components, Events) { 18603 /** 18604 * Instance of the binder for DOM Events. 18605 * 18606 * @type {EventsBinder} 18607 */ 18608 var Binder = new EventsBinder(); 18609 var capture = supportsPassive$1 ? { 18610 passive: true 18611 } : false; 18612 var Controls = { 18613 /** 18614 * Inits arrows. Binds events listeners 18615 * to the arrows HTML elements. 18616 * 18617 * @return {Void} 18618 */ 18619 mount: function mount() { 18620 /** 18621 * Collection of navigation HTML elements. 18622 * 18623 * @private 18624 * @type {HTMLCollection} 18625 */ 18626 this._n = Components.Html.root.querySelectorAll(NAV_SELECTOR); 18627 /** 18628 * Collection of controls HTML elements. 18629 * 18630 * @private 18631 * @type {HTMLCollection} 18632 */ 18633 18634 this._c = Components.Html.root.querySelectorAll(CONTROLS_SELECTOR); 18635 /** 18636 * Collection of arrow control HTML elements. 18637 * 18638 * @private 18639 * @type {Object} 18640 */ 18641 18642 this._arrowControls = { 18643 previous: Components.Html.root.querySelectorAll(PREVIOUS_CONTROLS_SELECTOR), 18644 next: Components.Html.root.querySelectorAll(NEXT_CONTROLS_SELECTOR) 18645 }; 18646 this.addBindings(); 18647 }, 18648 18649 /** 18650 * Sets active class to current slide. 18651 * 18652 * @return {Void} 18653 */ 18654 setActive: function setActive() { 18655 for (var i = 0; i < this._n.length; i++) { 18656 this.addClass(this._n[i].children); 18657 } 18658 }, 18659 18660 /** 18661 * Removes active class to current slide. 18662 * 18663 * @return {Void} 18664 */ 18665 removeActive: function removeActive() { 18666 for (var i = 0; i < this._n.length; i++) { 18667 this.removeClass(this._n[i].children); 18668 } 18669 }, 18670 18671 /** 18672 * Toggles active class on items inside navigation. 18673 * 18674 * @param {HTMLElement} controls 18675 * @return {Void} 18676 */ 18677 addClass: function addClass(controls) { 18678 var settings = Glide.settings; 18679 var item = controls[Glide.index]; 18680 18681 if (!item) { 18682 return; 18683 } 18684 18685 item.classList.add(settings.classes.nav.active); 18686 siblings(item).forEach(function (sibling) { 18687 sibling.classList.remove(settings.classes.nav.active); 18688 }); 18689 }, 18690 18691 /** 18692 * Removes active class from active control. 18693 * 18694 * @param {HTMLElement} controls 18695 * @return {Void} 18696 */ 18697 removeClass: function removeClass(controls) { 18698 var item = controls[Glide.index]; 18699 item === null || item === void 0 ? void 0 : item.classList.remove(Glide.settings.classes.nav.active); 18700 }, 18701 18702 /** 18703 * Calculates, removes or adds `Glide.settings.classes.disabledArrow` class on the control arrows 18704 */ 18705 setArrowState: function setArrowState() { 18706 if (Glide.settings.rewind) { 18707 return; 18708 } 18709 18710 var next = Controls._arrowControls.next; 18711 var previous = Controls._arrowControls.previous; 18712 this.resetArrowState(next, previous); 18713 18714 if (Glide.index === 0) { 18715 this.disableArrow(previous); 18716 } 18717 18718 if (Glide.index === Components.Run.length) { 18719 this.disableArrow(next); 18720 } 18721 }, 18722 18723 /** 18724 * Removes `Glide.settings.classes.disabledArrow` from given NodeList elements 18725 * 18726 * @param {NodeList[]} lists 18727 */ 18728 resetArrowState: function resetArrowState() { 18729 var settings = Glide.settings; 18730 18731 for (var _len = arguments.length, lists = new Array(_len), _key = 0; _key < _len; _key++) { 18732 lists[_key] = arguments[_key]; 18733 } 18734 18735 lists.forEach(function (list) { 18736 toArray(list).forEach(function (element) { 18737 element.classList.remove(settings.classes.arrow.disabled); 18738 }); 18739 }); 18740 }, 18741 18742 /** 18743 * Adds `Glide.settings.classes.disabledArrow` to given NodeList elements 18744 * 18745 * @param {NodeList[]} lists 18746 */ 18747 disableArrow: function disableArrow() { 18748 var settings = Glide.settings; 18749 18750 for (var _len2 = arguments.length, lists = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 18751 lists[_key2] = arguments[_key2]; 18752 } 18753 18754 lists.forEach(function (list) { 18755 toArray(list).forEach(function (element) { 18756 element.classList.add(settings.classes.arrow.disabled); 18757 }); 18758 }); 18759 }, 18760 18761 /** 18762 * Adds handles to the each group of controls. 18763 * 18764 * @return {Void} 18765 */ 18766 addBindings: function addBindings() { 18767 for (var i = 0; i < this._c.length; i++) { 18768 this.bind(this._c[i].children); 18769 } 18770 }, 18771 18772 /** 18773 * Removes handles from the each group of controls. 18774 * 18775 * @return {Void} 18776 */ 18777 removeBindings: function removeBindings() { 18778 for (var i = 0; i < this._c.length; i++) { 18779 this.unbind(this._c[i].children); 18780 } 18781 }, 18782 18783 /** 18784 * Binds events to arrows HTML elements. 18785 * 18786 * @param {HTMLCollection} elements 18787 * @return {Void} 18788 */ 18789 bind: function bind(elements) { 18790 for (var i = 0; i < elements.length; i++) { 18791 Binder.on('click', elements[i], this.click); 18792 Binder.on('touchstart', elements[i], this.click, capture); 18793 } 18794 }, 18795 18796 /** 18797 * Unbinds events binded to the arrows HTML elements. 18798 * 18799 * @param {HTMLCollection} elements 18800 * @return {Void} 18801 */ 18802 unbind: function unbind(elements) { 18803 for (var i = 0; i < elements.length; i++) { 18804 Binder.off(['click', 'touchstart'], elements[i]); 18805 } 18806 }, 18807 18808 /** 18809 * Handles `click` event on the arrows HTML elements. 18810 * Moves slider in direction given via the 18811 * `data-glide-dir` attribute. 18812 * 18813 * @param {Object} event 18814 * @return {void} 18815 */ 18816 click: function click(event) { 18817 if (!supportsPassive$1 && event.type === 'touchstart') { 18818 event.preventDefault(); 18819 } 18820 18821 var direction = event.currentTarget.getAttribute('data-glide-dir'); 18822 Components.Run.make(Components.Direction.resolve(direction)); 18823 } 18824 }; 18825 define(Controls, 'items', { 18826 /** 18827 * Gets collection of the controls HTML elements. 18828 * 18829 * @return {HTMLElement[]} 18830 */ 18831 get: function get() { 18832 return Controls._c; 18833 } 18834 }); 18835 /** 18836 * Swap active class of current navigation item: 18837 * - after mounting to set it to initial index 18838 * - after each move to the new index 18839 */ 18840 18841 Events.on(['mount.after', 'move.after'], function () { 18842 Controls.setActive(); 18843 }); 18844 /** 18845 * Add or remove disabled class of arrow elements 18846 */ 18847 18848 Events.on(['mount.after', 'run'], function () { 18849 Controls.setArrowState(); 18850 }); 18851 /** 18852 * Remove bindings and HTML Classes: 18853 * - on destroying, to bring markup to its initial state 18854 */ 18855 18856 Events.on('destroy', function () { 18857 Controls.removeBindings(); 18858 Controls.removeActive(); 18859 Binder.destroy(); 18860 }); 18861 return Controls; 18862} 18863 18864function Keyboard (Glide, Components, Events) { 18865 /** 18866 * Instance of the binder for DOM Events. 18867 * 18868 * @type {EventsBinder} 18869 */ 18870 var Binder = new EventsBinder(); 18871 var Keyboard = { 18872 /** 18873 * Binds keyboard events on component mount. 18874 * 18875 * @return {Void} 18876 */ 18877 mount: function mount() { 18878 if (Glide.settings.keyboard) { 18879 this.bind(); 18880 } 18881 }, 18882 18883 /** 18884 * Adds keyboard press events. 18885 * 18886 * @return {Void} 18887 */ 18888 bind: function bind() { 18889 Binder.on('keyup', document, this.press); 18890 }, 18891 18892 /** 18893 * Removes keyboard press events. 18894 * 18895 * @return {Void} 18896 */ 18897 unbind: function unbind() { 18898 Binder.off('keyup', document); 18899 }, 18900 18901 /** 18902 * Handles keyboard's arrows press and moving glide foward and backward. 18903 * 18904 * @param {Object} event 18905 * @return {Void} 18906 */ 18907 press: function press(event) { 18908 var perSwipe = Glide.settings.perSwipe; 18909 var arrowSymbols = { 18910 ArrowRight: '>', 18911 ArrowLeft: '<' 18912 }; 18913 18914 if (['ArrowRight', 'ArrowLeft'].includes(event.code)) { 18915 Components.Run.make(Components.Direction.resolve("".concat(perSwipe).concat(arrowSymbols[event.code]))); 18916 } 18917 } 18918 }; 18919 /** 18920 * Remove bindings from keyboard: 18921 * - on destroying to remove added events 18922 * - on updating to remove events before remounting 18923 */ 18924 18925 Events.on(['destroy', 'update'], function () { 18926 Keyboard.unbind(); 18927 }); 18928 /** 18929 * Remount component 18930 * - on updating to reflect potential changes in settings 18931 */ 18932 18933 Events.on('update', function () { 18934 Keyboard.mount(); 18935 }); 18936 /** 18937 * Destroy binder: 18938 * - on destroying to remove listeners 18939 */ 18940 18941 Events.on('destroy', function () { 18942 Binder.destroy(); 18943 }); 18944 return Keyboard; 18945} 18946 18947function Autoplay (Glide, Components, Events) { 18948 /** 18949 * Instance of the binder for DOM Events. 18950 * 18951 * @type {EventsBinder} 18952 */ 18953 var Binder = new EventsBinder(); 18954 var Autoplay = { 18955 /** 18956 * Initializes autoplaying and events. 18957 * 18958 * @return {Void} 18959 */ 18960 mount: function mount() { 18961 this.enable(); 18962 this.start(); 18963 18964 if (Glide.settings.hoverpause) { 18965 this.bind(); 18966 } 18967 }, 18968 18969 /** 18970 * Enables autoplaying 18971 * 18972 * @returns {Void} 18973 */ 18974 enable: function enable() { 18975 this._e = true; 18976 }, 18977 18978 /** 18979 * Disables autoplaying. 18980 * 18981 * @returns {Void} 18982 */ 18983 disable: function disable() { 18984 this._e = false; 18985 }, 18986 18987 /** 18988 * Starts autoplaying in configured interval. 18989 * 18990 * @param {Boolean|Number} force Run autoplaying with passed interval regardless of `autoplay` settings 18991 * @return {Void} 18992 */ 18993 start: function start() { 18994 var _this = this; 18995 18996 if (!this._e) { 18997 return; 18998 } 18999 19000 this.enable(); 19001 19002 if (Glide.settings.autoplay) { 19003 if (isUndefined(this._i)) { 19004 this._i = setInterval(function () { 19005 _this.stop(); 19006 19007 Components.Run.make('>'); 19008 19009 _this.start(); 19010 19011 Events.emit('autoplay'); 19012 }, this.time); 19013 } 19014 } 19015 }, 19016 19017 /** 19018 * Stops autorunning of the glide. 19019 * 19020 * @return {Void} 19021 */ 19022 stop: function stop() { 19023 this._i = clearInterval(this._i); 19024 }, 19025 19026 /** 19027 * Stops autoplaying while mouse is over glide's area. 19028 * 19029 * @return {Void} 19030 */ 19031 bind: function bind() { 19032 var _this2 = this; 19033 19034 Binder.on('mouseover', Components.Html.root, function () { 19035 if (_this2._e) { 19036 _this2.stop(); 19037 } 19038 }); 19039 Binder.on('mouseout', Components.Html.root, function () { 19040 if (_this2._e) { 19041 _this2.start(); 19042 } 19043 }); 19044 }, 19045 19046 /** 19047 * Unbind mouseover events. 19048 * 19049 * @returns {Void} 19050 */ 19051 unbind: function unbind() { 19052 Binder.off(['mouseover', 'mouseout'], Components.Html.root); 19053 } 19054 }; 19055 define(Autoplay, 'time', { 19056 /** 19057 * Gets time period value for the autoplay interval. Prioritizes 19058 * times in `data-glide-autoplay` attrubutes over options. 19059 * 19060 * @return {Number} 19061 */ 19062 get: function get() { 19063 var autoplay = Components.Html.slides[Glide.index].getAttribute('data-glide-autoplay'); 19064 19065 if (autoplay) { 19066 return toInt(autoplay); 19067 } 19068 19069 return toInt(Glide.settings.autoplay); 19070 } 19071 }); 19072 /** 19073 * Stop autoplaying and unbind events: 19074 * - on destroying, to clear defined interval 19075 * - on updating via API to reset interval that may changed 19076 */ 19077 19078 Events.on(['destroy', 'update'], function () { 19079 Autoplay.unbind(); 19080 }); 19081 /** 19082 * Stop autoplaying: 19083 * - before each run, to restart autoplaying 19084 * - on pausing via API 19085 * - on destroying, to clear defined interval 19086 * - while starting a swipe 19087 * - on updating via API to reset interval that may changed 19088 */ 19089 19090 Events.on(['run.before', 'swipe.start', 'update'], function () { 19091 Autoplay.stop(); 19092 }); 19093 Events.on(['pause', 'destroy'], function () { 19094 Autoplay.disable(); 19095 Autoplay.stop(); 19096 }); 19097 /** 19098 * Start autoplaying: 19099 * - after each run, to restart autoplaying 19100 * - on playing via API 19101 * - while ending a swipe 19102 */ 19103 19104 Events.on(['run.after', 'swipe.end'], function () { 19105 Autoplay.start(); 19106 }); 19107 /** 19108 * Start autoplaying: 19109 * - after each run, to restart autoplaying 19110 * - on playing via API 19111 * - while ending a swipe 19112 */ 19113 19114 Events.on(['play'], function () { 19115 Autoplay.enable(); 19116 Autoplay.start(); 19117 }); 19118 /** 19119 * Remount autoplaying: 19120 * - on updating via API to reset interval that may changed 19121 */ 19122 19123 Events.on('update', function () { 19124 Autoplay.mount(); 19125 }); 19126 /** 19127 * Destroy a binder: 19128 * - on destroying glide instance to clearup listeners 19129 */ 19130 19131 Events.on('destroy', function () { 19132 Binder.destroy(); 19133 }); 19134 return Autoplay; 19135} 19136 19137/** 19138 * Sorts keys of breakpoint object so they will be ordered from lower to bigger. 19139 * 19140 * @param {Object} points 19141 * @returns {Object} 19142 */ 19143 19144function sortBreakpoints(points) { 19145 if (isObject(points)) { 19146 return sortKeys(points); 19147 } else { 19148 warn("Breakpoints option must be an object"); 19149 } 19150 19151 return {}; 19152} 19153 19154function Breakpoints (Glide, Components, Events) { 19155 /** 19156 * Instance of the binder for DOM Events. 19157 * 19158 * @type {EventsBinder} 19159 */ 19160 var Binder = new EventsBinder(); 19161 /** 19162 * Holds reference to settings. 19163 * 19164 * @type {Object} 19165 */ 19166 19167 var settings = Glide.settings; 19168 /** 19169 * Holds reference to breakpoints object in settings. Sorts breakpoints 19170 * from smaller to larger. It is required in order to proper 19171 * matching currently active breakpoint settings. 19172 * 19173 * @type {Object} 19174 */ 19175 19176 var points = sortBreakpoints(settings.breakpoints); 19177 /** 19178 * Cache initial settings before overwritting. 19179 * 19180 * @type {Object} 19181 */ 19182 19183 var defaults = Object.assign({}, settings); 19184 var Breakpoints = { 19185 /** 19186 * Matches settings for currectly matching media breakpoint. 19187 * 19188 * @param {Object} points 19189 * @returns {Object} 19190 */ 19191 match: function match(points) { 19192 if (typeof window.matchMedia !== 'undefined') { 19193 for (var point in points) { 19194 if (points.hasOwnProperty(point)) { 19195 if (window.matchMedia("(max-width: ".concat(point, "px)")).matches) { 19196 return points[point]; 19197 } 19198 } 19199 } 19200 } 19201 19202 return defaults; 19203 } 19204 }; 19205 /** 19206 * Overwrite instance settings with currently matching breakpoint settings. 19207 * This happens right after component initialization. 19208 */ 19209 19210 Object.assign(settings, Breakpoints.match(points)); 19211 /** 19212 * Update glide with settings of matched brekpoint: 19213 * - window resize to update slider 19214 */ 19215 19216 Binder.on('resize', window, throttle(function () { 19217 Glide.settings = mergeOptions(settings, Breakpoints.match(points)); 19218 }, Glide.settings.throttle)); 19219 /** 19220 * Resort and update default settings: 19221 * - on reinit via API, so breakpoint matching will be performed with options 19222 */ 19223 19224 Events.on('update', function () { 19225 points = sortBreakpoints(points); 19226 defaults = Object.assign({}, settings); 19227 }); 19228 /** 19229 * Unbind resize listener: 19230 * - on destroying, to bring markup to its initial state 19231 */ 19232 19233 Events.on('destroy', function () { 19234 Binder.off('resize', window); 19235 }); 19236 return Breakpoints; 19237} 19238 19239var COMPONENTS = { 19240 // Required 19241 Html: Html, 19242 Translate: Translate, 19243 Transition: Transition, 19244 Direction: Direction, 19245 Peek: Peek, 19246 Sizes: Sizes, 19247 Gaps: Gaps, 19248 Move: Move, 19249 Clones: Clones, 19250 Resize: Resize, 19251 Build: Build, 19252 Run: Run, 19253 // Optional 19254 Swipe: Swipe, 19255 Images: Images, 19256 Anchors: Anchors, 19257 Controls: Controls, 19258 Keyboard: Keyboard, 19259 Autoplay: Autoplay, 19260 Breakpoints: Breakpoints 19261}; 19262 19263var Glide = /*#__PURE__*/function (_Core) { 19264 _inherits(Glide, _Core); 19265 19266 var _super = _createSuper(Glide); 19267 19268 function Glide() { 19269 _classCallCheck(this, Glide); 19270 19271 return _super.apply(this, arguments); 19272 } 19273 19274 _createClass(Glide, [{ 19275 key: "mount", 19276 value: function mount() { 19277 var extensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 19278 return _get(_getPrototypeOf(Glide.prototype), "mount", this).call(this, Object.assign({}, COMPONENTS, extensions)); 19279 } 19280 }]); 19281 19282 return Glide; 19283}(Glide$1); 19284 19285 19286 19287 19288/***/ })
vendor: 4,135 bytes, lines 19288-19388
19288 19289 19290/******/ }); 19291/************************************************************************/ 19292/******/ // The module cache 19293/******/ var __webpack_module_cache__ = {}; 19294/******/ 19295/******/ // The require function 19296/******/ function __webpack_require__(moduleId) { 19297/******/ // Check if module is in cache 19298/******/ var cachedModule = __webpack_module_cache__[moduleId]; 19299/******/ if (cachedModule !== undefined) { 19300/******/ return cachedModule.exports; 19301/******/ } 19302/******/ // Create a new module (and put it into the cache) 19303/******/ var module = __webpack_module_cache__[moduleId] = { 19304/******/ id: moduleId, 19305/******/ // no module.loaded needed 19306/******/ exports: {} 19307/******/ }; 19308/******/ 19309/******/ // Execute the module function 19310/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__); 19311/******/ 19312/******/ // Return the exports of the module 19313/******/ return module.exports; 19314/******/ } 19315/******/ 19316/******/ // expose the modules object (__webpack_modules__) 19317/******/ __webpack_require__.m = __webpack_modules__; 19318/******/ 19319/************************************************************************/ 19320/******/ /* webpack/runtime/chunk loaded */ 19321/******/ (() => { 19322/******/ var deferred = []; 19323/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { 19324/******/ if(chunkIds) { 19325/******/ priority = priority || 0; 19326/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; 19327/******/ deferred[i] = [chunkIds, fn, priority]; 19328/******/ return; 19329/******/ } 19330/******/ var notFulfilled = Infinity; 19331/******/ for (var i = 0; i < deferred.length; i++) { 19332/******/ var [chunkIds, fn, priority] = deferred[i]; 19333/******/ var fulfilled = true; 19334/******/ for (var j = 0; j < chunkIds.length; j++) { 19335/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { 19336/******/ chunkIds.splice(j--, 1); 19337/******/ } else { 19338/******/ fulfilled = false; 19339/******/ if(priority < notFulfilled) notFulfilled = priority; 19340/******/ } 19341/******/ } 19342/******/ if(fulfilled) { 19343/******/ deferred.splice(i--, 1) 19344/******/ var r = fn(); 19345/******/ if (r !== undefined) result = r; 19346/******/ } 19347/******/ } 19348/******/ return result; 19349/******/ }; 19350/******/ })(); 19351/******/ 19352/******/ /* webpack/runtime/compat get default export */ 19353/******/ (() => { 19354/******/ // getDefaultExport function for compatibility with non-harmony modules 19355/******/ __webpack_require__.n = (module) => { 19356/******/ var getter = module && module.__esModule ? 19357/******/ () => (module['default']) : 19358/******/ () => (module); 19359/******/ __webpack_require__.d(getter, { a: getter }); 19360/******/ return getter; 19361/******/ }; 19362/******/ })(); 19363/******/ 19364/******/ /* webpack/runtime/define property getters */ 19365/******/ (() => { 19366/******/ // define getter functions for harmony exports 19367/******/ __webpack_require__.d = (exports, definition) => { 19368/******/ for(var key in definition) { 19369/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 19370/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 19371/******/ } 19372/******/ } 19373/******/ }; 19374/******/ })(); 19375/******/ 19376/******/ /* webpack/runtime/hasOwnProperty shorthand */ 19377/******/ (() => { 19378/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 19379/******/ })(); 19380/******/ 19381/******/ /* webpack/runtime/make namespace object */ 19382/******/ (() => { 19383/******/ // define __esModule on exports 19384/******/ __webpack_require__.r = (exports) => { 19385/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 19386/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 19387/******/ } 19388/******/ Object.defineProperty(exports, '__esModule', { value: true });
19389/******/ }; 19390/******/ })(); 19391/******/ 19392/******/ /* webpack/runtime/jsonp chunk loading */ 19393/******/ (() => { 19394/******/ // no baseURI 19395/******/ 19396/******/ // object to store loaded and loading chunks 19397/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 19398/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded 19399/******/ var installedChunks = { 19400/******/ "/js/app": 0, 19401/******/ "editor-style": 0, 19402/******/ "css/app": 0 19403/******/ }; 19404/******/ 19405/******/ // no chunk on demand loading 19406/******/ 19407/******/ // no prefetching 19408/******/ 19409/******/ // no preloaded 19410/******/ 19411/******/ // no HMR 19412/******/ 19413/******/ // no HMR manifest 19414/******/ 19415/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); 19416/******/ 19417/******/ // install a JSONP callback for chunk loading 19418/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { 19419/******/ var [chunkIds, moreModules, runtime] = data; 19420/******/ // add "moreModules" to the modules object, 19421/******/ // then flag all "chunkIds" as loaded and fire callback 19422/******/ var moduleId, chunkId, i = 0;
19423/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { 19424/******/ for(moduleId in moreModules) { 19425/******/ if(__webpack_require__.o(moreModules, moduleId)) { 19426/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; 19427/******/ } 19428/******/ } 19429/******/ if(runtime) var result = runtime(__webpack_require__); 19430/******/ } 19431/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); 19432/******/ for(;i < chunkIds.length; i++) { 19433/******/ chunkId = chunkIds[i]; 19434/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { 19435/******/ installedChunks[chunkId][0](); 19436/******/ } 19437/******/ installedChunks[chunkId] = 0; 19438/******/ } 19439/******/ return __webpack_require__.O(result); 19440/******/ } 19441/******/ 19442/******/ var chunkLoadingGlobal = self["webpackChunktailpress"] = self["webpackChunktailpress"] || []; 19443/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); 19444/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); 19445/******/ })(); 19446/******/ 19447/******/ /* webpack/runtime/nonce */ 19448/******/ (() => { 19449/******/ __webpack_require__.nc = undefined; 19450/******/ })(); 19451/******/ 19452/************************************************************************/ 19453/******/ 19454/******/ // startup 19455/******/ // Load entry module and return exports 19456/******/ // This entry module depends on other loaded chunks and execution need to be delayed 19457/******/ __webpack_require__.O(undefined, ["editor-style","css/app"], () => (__webpack_require__("./resources/js/app.js"))) 19458/******/ __webpack_require__.O(undefined, ["editor-style","css/app"], () => (__webpack_require__("./resources/css/app.css"))) 19459/******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["editor-style","css/app"], () => (__webpack_require__("./resources/css/editor-style.css"))) 19460/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); 19461/******/ 19462/******/ })() 19463;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.