/* @preserve
- * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com
- * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ * Leaflet 1.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
+
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.L = {})));
}(this, (function (exports) { 'use strict';
-var version = "1.2.0";
+var version = "1.5.1+build.2e3e0ffb";
/*
* @namespace Util
var lastId = 0;
// @function stamp(obj: Object): Number
-// Returns the unique ID of an object, assiging it one if it doesn't have it.
+// Returns the unique ID of an object, assigning it one if it doesn't have it.
function stamp(obj) {
/*eslint-disable */
obj._leaflet_id = obj._leaflet_id || ++lastId;
return obj._leaflet_id;
- /*eslint-enable */
+ /* eslint-enable */
}
// @function throttle(fn: Function, time: Number, context: Object): Function
function falseFn() { return false; }
// @function formatNum(num: Number, digits?: Number): Number
-// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
+// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
function formatNum(num, digits) {
- var pow = Math.pow(10, digits || 5);
- return Math.round(num * pow) / pow;
+ digits = (digits === undefined ? 6 : digits);
+ return +(Math.round(num + ('e+' + digits)) + ('e-' + digits));
}
// @function trim(str: String): String
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
}
-var templateRe = /\{ *([\w_\-]+) *\}/g;
+var templateRe = /\{ *([\w_-]+) *\}/g;
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
};
function checkDeprecatedMixinEvents(includes) {
- if (!L || !L.Mixin) { return; }
+ if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
includes = isArray(includes) ? includes : [includes];
*
* @alternative
* @method off: this
- * Removes all listeners to all events on the object.
+ * Removes all listeners to all events on the object. This includes implicitly attached events.
*/
off: function (types, fn, context) {
fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; }
- var event = extend({}, data, {type: type, target: this});
+ var event = extend({}, data, {
+ type: type,
+ target: this,
+ sourceTarget: data && data.sourceTarget || this
+ });
if (this._events) {
var listeners = this._events[type];
_propagateEvent: function (e) {
for (var id in this._eventParents) {
- this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
+ this._eventParents[id].fire(e.type, extend({
+ layer: e.target,
+ propagatedFrom: e.target
+ }, e), true);
}
}
};
* map.panBy([200, 300]);
* map.panBy(L.point(200, 300));
* ```
+ *
+ * Note that `Point` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function Point(x, y, round) {
this.y = (round ? Math.round(y) : y);
}
+var trunc = Math.trunc || function (v) {
+ return v > 0 ? Math.floor(v) : Math.ceil(v);
+};
+
Point.prototype = {
// @method clone(): Point
return this;
},
+ // @method trunc(): Point
+ // Returns a copy of the current point with truncated coordinates (rounded towards zero).
+ trunc: function () {
+ return this.clone()._trunc();
+ },
+
+ _trunc: function () {
+ this.x = trunc(this.x);
+ this.y = trunc(this.y);
+ return this;
+ },
+
// @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points.
distanceTo: function (point) {
* ```js
* otherBounds.intersects([[10, 10], [40, 60]]);
* ```
+ *
+ * Note that `Bounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function Bounds(a, b) {
* ```
*
* 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.
+ *
+ * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
},
// @method pad(bufferRatio: Number): LatLngBounds
- // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
+ // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
+ // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
+ // Negative values will retract the bounds.
pad: function (bufferRatio) {
var sw = this._southWest,
ne = this._northEast,
},
// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
- // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
+ // 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.
equals: function (bounds, maxMargin) {
if (!bounds) { return false; }
* map.panTo({lat: 50, lng: 30});
* map.panTo(L.latLng(50, 30));
* ```
+ *
+ * Note that `LatLng` does not inherit from Leaflet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function LatLng(lat, lng, alt) {
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
- // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
+ // 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.
equals: function (obj, maxMargin) {
if (!obj) { return false; }
},
// @method distanceTo(otherLatLng: LatLng): Number
- // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
+ // 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).
distanceTo: function (other) {
return Earth.distance(this, toLatLng(other));
},
* Leaflet defines the most usual CRSs by default. If you want to use a
* CRS not defined by default, take a look at the
* [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
+ *
+ * Note that the CRS instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
*/
var CRS = {
var rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
- a = Math.sin(lat1) * Math.sin(lat2) +
- Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
-
- return this.R * Math.acos(Math.min(a, 1));
+ sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
+ sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
+ a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ return this.R * c;
}
});
* a sphere. Used by the `EPSG:3857` CRS.
*/
+var earthRadius = 6378137;
+
var SphericalMercator = {
- R: 6378137,
+ R: earthRadius,
MAX_LATITUDE: 85.0511287798,
project: function (latlng) {
sin = Math.sin(lat * d);
return new Point(
- this.R * latlng.lng * d,
- this.R * Math.log((1 + sin) / (1 - sin)) / 2);
+ this.R * latlng.lng * d,
+ this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject: function (point) {
},
bounds: (function () {
- var d = 6378137 * Math.PI;
+ var d = earthRadius * Math.PI;
return new Bounds([-d, -d], [d, d]);
})()
};
// @alternative
// @factory L.transformation(coefficients: Array): Transformation
-// Expects an coeficients array of the form
+// Expects an coefficients array of the form
// `[a: Number, b: Number, c: Number, d: Number]`.
function toTransformation(a, b, c, d) {
// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
var android23 = userAgentContains('android 2') || userAgentContains('android 3');
+/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
+var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
+// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
+var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
+
// @property opera: Boolean; `true` for the Opera browser
var opera = !!window.opera;
var mobileGecko = mobile && gecko;
// @property retina: Boolean
-// `true` for browsers on a high-resolution "retina" screen.
+// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
webkit: webkit,
android: android,
android23: android23,
+ androidStock: androidStock,
opera: opera,
chrome: chrome,
gecko: gecko,
var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
+
var _pointers = {};
var _pointerDocListener = false;
function _addPointerStart(obj, handler, id) {
var onDown = bind(function (e) {
- if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+ if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
// In IE11, some touch events needs to fire for form controls, or
// the controls will stop working. We keep a whitelist of tag names that
// need these events. For other target tags, we prevent default on the event.
touch$$1 = newTouch;
}
touch$$1.type = 'dblclick';
+ touch$$1.button = 0;
handler(touch$$1);
last = null;
}
}
/*
- * @namespace DomEvent
- * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ * @namespace DomUtil
+ *
+ * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
+ * tree, used by Leaflet internally.
+ *
+ * Most functions expecting or returning a `HTMLElement` also work for
+ * SVG elements. The only difference is that classes refer to CSS classes
+ * in HTML and SVG classes in SVG.
*/
-// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
-// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Adds a listener function (`fn`) to a particular DOM event type of the
-// element `el`. 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'`).
+// @property TRANSFORM: String
+// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
+var TRANSFORM = testProp(
+ ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
-// @alternative
-// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
-// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-function on(obj, types, fn, context) {
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
- if (typeof types === 'object') {
- for (var type in types) {
- addOne(obj, type, types[type], fn);
- }
- } else {
- types = splitWords(types);
+// @property TRANSITION: String
+// Vendor-prefixed transition style name.
+var TRANSITION = testProp(
+ ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
- for (var i = 0, len = types.length; i < len; i++) {
- addOne(obj, types[i], fn, context);
- }
- }
+// @property TRANSITION_END: String
+// Vendor-prefixed transitionend event name.
+var TRANSITION_END =
+ TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
- return this;
+
+// @function get(id: String|HTMLElement): HTMLElement
+// Returns an element given its DOM id, or returns the element itself
+// if it was passed directly.
+function get(id) {
+ return typeof id === 'string' ? document.getElementById(id) : id;
}
-var eventsKey = '_leaflet_events';
+// @function getStyle(el: HTMLElement, styleAttrib: String): String
+// Returns the value for a certain style attribute on an element,
+// including computed values or values set through CSS.
+function getStyle(el, style) {
+ var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
-// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Removes a previously added listener function. If no function is specified,
-// it will remove all the listeners of that particular DOM event from the element.
-// Note that if you passed a custom context to on, you must pass the same
-// context to `off` in order to remove the listener.
+ if ((!value || value === 'auto') && document.defaultView) {
+ var css = document.defaultView.getComputedStyle(el, null);
+ value = css ? css[style] : null;
+ }
+ return value === 'auto' ? null : value;
+}
-// @alternative
-// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
-// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
+// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
+function create$1(tagName, className, container) {
+ var el = document.createElement(tagName);
+ el.className = className || '';
-// @alternative
-// @function off(el: HTMLElement): this
-// Removes all known event listeners
-function off(obj, types, fn, context) {
+ if (container) {
+ container.appendChild(el);
+ }
+ return el;
+}
- if (typeof types === 'object') {
- for (var type in types) {
- removeOne(obj, type, types[type], fn);
- }
- } else if (types) {
- types = splitWords(types);
+// @function remove(el: HTMLElement)
+// Removes `el` from its parent element
+function remove(el) {
+ var parent = el.parentNode;
+ if (parent) {
+ parent.removeChild(el);
+ }
+}
- for (var i = 0, len = types.length; i < len; i++) {
- removeOne(obj, types[i], fn, context);
- }
- } else {
- for (var j in obj[eventsKey]) {
- removeOne(obj, j, obj[eventsKey][j]);
- }
- delete obj[eventsKey];
+// @function empty(el: HTMLElement)
+// Removes all of `el`'s children elements from `el`
+function empty(el) {
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
}
+}
- return this;
+// @function toFront(el: HTMLElement)
+// Makes `el` the last child of its parent, so it renders in front of the other children.
+function toFront(el) {
+ var parent = el.parentNode;
+ if (parent && parent.lastChild !== el) {
+ parent.appendChild(el);
+ }
}
-function addOne(obj, type, fn, context) {
- var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
+// @function toBack(el: HTMLElement)
+// Makes `el` the first child of its parent, so it renders behind the other children.
+function toBack(el) {
+ var parent = el.parentNode;
+ if (parent && parent.firstChild !== el) {
+ parent.insertBefore(el, parent.firstChild);
+ }
+}
- if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
+// @function hasClass(el: HTMLElement, name: String): Boolean
+// Returns `true` if the element's class attribute contains `name`.
+function hasClass(el, name) {
+ if (el.classList !== undefined) {
+ return el.classList.contains(name);
+ }
+ var className = getClass(el);
+ return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+}
- var handler = function (e) {
- return fn.call(context || obj, e || window.event);
- };
+// @function addClass(el: HTMLElement, name: String)
+// Adds `name` to the element's class attribute.
+function addClass(el, name) {
+ if (el.classList !== undefined) {
+ var classes = splitWords(name);
+ for (var i = 0, len = classes.length; i < len; i++) {
+ el.classList.add(classes[i]);
+ }
+ } else if (!hasClass(el, name)) {
+ var className = getClass(el);
+ setClass(el, (className ? className + ' ' : '') + name);
+ }
+}
- var originalHandler = handler;
+// @function removeClass(el: HTMLElement, name: String)
+// Removes `name` from the element's class attribute.
+function removeClass(el, name) {
+ if (el.classList !== undefined) {
+ el.classList.remove(name);
+ } else {
+ setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+ }
+}
- if (pointer && type.indexOf('touch') === 0) {
- // Needs DomEvent.Pointer.js
- addPointerListener(obj, type, handler, id);
+// @function setClass(el: HTMLElement, name: String)
+// Sets the element's class.
+function setClass(el, name) {
+ if (el.className.baseVal === undefined) {
+ el.className = name;
+ } else {
+ // in case of SVG element
+ el.className.baseVal = name;
+ }
+}
- } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
- !(pointer && chrome)) {
- // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
- // See #5180
- addDoubleTapListener(obj, handler, id);
+// @function getClass(el: HTMLElement): String
+// Returns the element's class.
+function getClass(el) {
+ // Check if the element is an SVGElementInstance and use the correspondingElement instead
+ // (Required for linked SVG elements in IE11.)
+ if (el.correspondingElement) {
+ el = el.correspondingElement;
+ }
+ return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+}
- } else if ('addEventListener' in obj) {
+// @function setOpacity(el: HTMLElement, opacity: Number)
+// Set the opacity of an element (including old IE support).
+// `opacity` must be a number from `0` to `1`.
+function setOpacity(el, value) {
+ if ('opacity' in el.style) {
+ el.style.opacity = value;
+ } else if ('filter' in el.style) {
+ _setOpacityIE(el, value);
+ }
+}
- if (type === 'mousewheel') {
- obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+function _setOpacityIE(el, value) {
+ var filter = false,
+ filterName = 'DXImageTransform.Microsoft.Alpha';
- } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
- handler = function (e) {
- e = e || window.event;
- if (isExternalTarget(obj, e)) {
- originalHandler(e);
- }
- };
- obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
+ // filters collection throws an error if we try to retrieve a filter that doesn't exist
+ try {
+ filter = el.filters.item(filterName);
+ } catch (e) {
+ // don't set opacity to 1 if we haven't already set an opacity,
+ // it isn't needed and breaks transparent pngs.
+ if (value === 1) { return; }
+ }
- } else {
- if (type === 'click' && android) {
- handler = function (e) {
- filterClick(e, originalHandler);
- };
- }
- obj.addEventListener(type, handler, false);
- }
+ value = Math.round(value * 100);
- } else if ('attachEvent' in obj) {
- obj.attachEvent('on' + type, handler);
+ if (filter) {
+ filter.Enabled = (value !== 100);
+ filter.Opacity = value;
+ } else {
+ el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
-
- obj[eventsKey] = obj[eventsKey] || {};
- obj[eventsKey][id] = handler;
}
-function removeOne(obj, type, fn, context) {
-
- var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
- handler = obj[eventsKey] && obj[eventsKey][id];
-
- if (!handler) { return this; }
+// @function testProp(props: String[]): String|false
+// Goes through the array of style names and returns the first name
+// that is a valid style name for an element. If no such name is found,
+// it returns false. Useful for vendor-prefixed styles like `transform`.
+function testProp(props) {
+ var style = document.documentElement.style;
- if (pointer && type.indexOf('touch') === 0) {
- removePointerListener(obj, type, id);
+ for (var i = 0; i < props.length; i++) {
+ if (props[i] in style) {
+ return props[i];
+ }
+ }
+ return false;
+}
- } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
- removeDoubleTapListener(obj, id);
+// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
+// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
+// and optionally scaled by `scale`. Does not have an effect if the
+// browser doesn't support 3D CSS transforms.
+function setTransform(el, offset, scale) {
+ var pos = offset || new Point(0, 0);
- } else if ('removeEventListener' in obj) {
+ el.style[TRANSFORM] =
+ (ie3d ?
+ 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
+ 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
+ (scale ? ' scale(' + scale + ')' : '');
+}
- if (type === 'mousewheel') {
- obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+// @function setPosition(el: HTMLElement, position: Point)
+// Sets the position of `el` to coordinates specified by `position`,
+// using CSS translate or top/left positioning depending on the browser
+// (used by Leaflet internally to position its layers).
+function setPosition(el, point) {
- } else {
- obj.removeEventListener(
- type === 'mouseenter' ? 'mouseover' :
- type === 'mouseleave' ? 'mouseout' : type, handler, false);
- }
+ /*eslint-disable */
+ el._leaflet_pos = point;
+ /* eslint-enable */
- } else if ('detachEvent' in obj) {
- obj.detachEvent('on' + type, handler);
+ if (any3d) {
+ setTransform(el, point);
+ } else {
+ el.style.left = point.x + 'px';
+ el.style.top = point.y + 'px';
}
+}
- obj[eventsKey][id] = null;
+// @function getPosition(el: HTMLElement): Point
+// Returns the coordinates of an element previously positioned with setPosition.
+function getPosition(el) {
+ // this method is only used for elements previously positioned using setPosition,
+ // so it's safe to cache the position for performance
+
+ return el._leaflet_pos || new Point(0, 0);
}
-// @function stopPropagation(ev: DOMEvent): this
-// Stop the given event from propagation to parent elements. Used inside the listener functions:
-// ```js
-// L.DomEvent.on(div, 'click', function (ev) {
-// L.DomEvent.stopPropagation(ev);
-// });
-// ```
-function stopPropagation(e) {
+// @function disableTextSelection()
+// Prevents the user from generating `selectstart` DOM events, usually generated
+// when the user drags the mouse through a page with text. Used internally
+// by Leaflet to override the behaviour of any click-and-drag interaction on
+// the map. Affects drag interactions on the whole document.
- if (e.stopPropagation) {
- e.stopPropagation();
- } else if (e.originalEvent) { // In case of Leaflet event.
- e.originalEvent._stopped = true;
- } else {
- e.cancelBubble = true;
- }
- skipped(e);
+// @function enableTextSelection()
+// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
+var disableTextSelection;
+var enableTextSelection;
+var _userSelect;
+if ('onselectstart' in document) {
+ disableTextSelection = function () {
+ on(window, 'selectstart', preventDefault);
+ };
+ enableTextSelection = function () {
+ off(window, 'selectstart', preventDefault);
+ };
+} else {
+ var userSelectProperty = testProp(
+ ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
- return this;
+ disableTextSelection = function () {
+ if (userSelectProperty) {
+ var style = document.documentElement.style;
+ _userSelect = style[userSelectProperty];
+ style[userSelectProperty] = 'none';
+ }
+ };
+ enableTextSelection = function () {
+ if (userSelectProperty) {
+ document.documentElement.style[userSelectProperty] = _userSelect;
+ _userSelect = undefined;
+ }
+ };
}
-// @function disableScrollPropagation(el: HTMLElement): this
-// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
-function disableScrollPropagation(el) {
- addOne(el, 'mousewheel', stopPropagation);
- return this;
+// @function disableImageDrag()
+// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
+// for `dragstart` DOM events, usually generated when the user drags an image.
+function disableImageDrag() {
+ on(window, 'dragstart', preventDefault);
}
-// @function disableClickPropagation(el: HTMLElement): this
-// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
-// `'mousedown'` and `'touchstart'` events (plus browser variants).
-function disableClickPropagation(el) {
- on(el, 'mousedown touchstart dblclick', stopPropagation);
- addOne(el, 'click', fakeStop);
- return this;
+// @function enableImageDrag()
+// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
+function enableImageDrag() {
+ off(window, 'dragstart', preventDefault);
}
-// @function preventDefault(ev: DOMEvent): this
-// Prevents the default action of the DOM Event `ev` from happening (such as
-// following a link in the href of the a element, or doing a POST request
-// with page reload when a `<form>` is submitted).
-// Use it inside listener functions.
-function preventDefault(e) {
- if (e.preventDefault) {
- e.preventDefault();
- } else {
- e.returnValue = false;
+var _outlineElement;
+var _outlineStyle;
+// @function preventOutline(el: HTMLElement)
+// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
+// of the element `el` invisible. Used internally by Leaflet to prevent
+// focusable elements from displaying an outline when the user performs a
+// drag interaction on them.
+function preventOutline(element) {
+ while (element.tabIndex === -1) {
+ element = element.parentNode;
}
- return this;
+ if (!element.style) { return; }
+ restoreOutline();
+ _outlineElement = element;
+ _outlineStyle = element.style.outline;
+ element.style.outline = 'none';
+ on(window, 'keydown', restoreOutline);
}
-// @function stop(ev): this
-// Does `stopPropagation` and `preventDefault` at the same time.
-function stop(e) {
- preventDefault(e);
- stopPropagation(e);
- return this;
+// @function restoreOutline()
+// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
+function restoreOutline() {
+ if (!_outlineElement) { return; }
+ _outlineElement.style.outline = _outlineStyle;
+ _outlineElement = undefined;
+ _outlineStyle = undefined;
+ off(window, 'keydown', restoreOutline);
}
-// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
-// Gets normalized mouse position from a DOM event relative to the
-// `container` or to the whole page if not specified.
-function getMousePosition(e, container) {
- if (!container) {
- return new Point(e.clientX, e.clientY);
- }
-
- var rect = container.getBoundingClientRect();
-
- return new Point(
- e.clientX - rect.left - container.clientLeft,
- e.clientY - rect.top - container.clientTop);
+// @function getSizedParentNode(el: HTMLElement): HTMLElement
+// Finds the closest parent node which size (width and height) is not null.
+function getSizedParentNode(element) {
+ do {
+ element = element.parentNode;
+ } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
+ return element;
}
-// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
-// and Firefox scrolls device pixels, not CSS pixels
-var wheelPxFactor =
- (win && chrome) ? 2 * window.devicePixelRatio :
- gecko ? window.devicePixelRatio : 1;
+// @function getScale(el: HTMLElement): Object
+// Computes the CSS scale currently applied on the element.
+// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
+// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
+function getScale(element) {
+ var rect = element.getBoundingClientRect(); // Read-only in old browsers.
-// @function getWheelDelta(ev: DOMEvent): Number
-// Gets normalized wheel delta from a mousewheel DOM event, in vertical
-// pixels scrolled (negative if scrolling down).
-// Events from pointing devices without precise scrolling are mapped to
-// a best guess of 60 pixels.
-function getWheelDelta(e) {
- return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
- (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
- (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
- (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
- (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
- e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
- (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
- e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
- 0;
+ return {
+ x: rect.width / element.offsetWidth || 1,
+ y: rect.height / element.offsetHeight || 1,
+ boundingClientRect: rect
+ };
}
-var skipEvents = {};
-function fakeStop(e) {
- // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
- skipEvents[e.type] = true;
-}
+var DomUtil = (Object.freeze || Object)({
+ TRANSFORM: TRANSFORM,
+ TRANSITION: TRANSITION,
+ TRANSITION_END: TRANSITION_END,
+ get: get,
+ getStyle: getStyle,
+ create: create$1,
+ remove: remove,
+ empty: empty,
+ toFront: toFront,
+ toBack: toBack,
+ hasClass: hasClass,
+ addClass: addClass,
+ removeClass: removeClass,
+ setClass: setClass,
+ getClass: getClass,
+ setOpacity: setOpacity,
+ testProp: testProp,
+ setTransform: setTransform,
+ setPosition: setPosition,
+ getPosition: getPosition,
+ disableTextSelection: disableTextSelection,
+ enableTextSelection: enableTextSelection,
+ disableImageDrag: disableImageDrag,
+ enableImageDrag: enableImageDrag,
+ preventOutline: preventOutline,
+ restoreOutline: restoreOutline,
+ getSizedParentNode: getSizedParentNode,
+ getScale: getScale
+});
-function skipped(e) {
- var events = skipEvents[e.type];
- // reset when checking, as it's only used in map container and propagates outside of the map
- skipEvents[e.type] = false;
- return events;
-}
+/*
+ * @namespace DomEvent
+ * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ */
-// check if element really left/entered the event target (for mouseenter/mouseleave)
-function isExternalTarget(el, e) {
+// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
- var related = e.relatedTarget;
+// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Adds a listener function (`fn`) to a particular DOM event type of the
+// element `el`. 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'`).
- if (!related) { return true; }
+// @alternative
+// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
+// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function on(obj, types, fn, context) {
- try {
- while (related && (related !== el)) {
- related = related.parentNode;
+ if (typeof types === 'object') {
+ for (var type in types) {
+ addOne(obj, type, types[type], fn);
+ }
+ } else {
+ types = splitWords(types);
+
+ for (var i = 0, len = types.length; i < len; i++) {
+ addOne(obj, types[i], fn, context);
}
- } catch (err) {
- return false;
}
- return (related !== el);
+
+ return this;
}
-var lastClick;
+var eventsKey = '_leaflet_events';
-// this is a horrible workaround for a bug in Android where a single touch triggers two click events
-function filterClick(e, handler) {
- var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
- elapsed = lastClick && (timeStamp - lastClick);
+// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Removes a previously added listener function.
+// Note that if you passed a custom context to on, you must pass the same
+// context to `off` in order to remove the listener.
- // are they closer together than 500ms yet more than 100ms?
- // Android typically triggers them ~300ms apart while multiple listeners
- // on the same event should be triggered far faster;
- // or check if click is simulated on the element, and if it is, reject any non-simulated events
+// @alternative
+// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
+// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function off(obj, types, fn, context) {
+
+ if (typeof types === 'object') {
+ for (var type in types) {
+ removeOne(obj, type, types[type], fn);
+ }
+ } else if (types) {
+ types = splitWords(types);
- if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
- stop(e);
- return;
+ for (var i = 0, len = types.length; i < len; i++) {
+ removeOne(obj, types[i], fn, context);
+ }
+ } else {
+ for (var j in obj[eventsKey]) {
+ removeOne(obj, j, obj[eventsKey][j]);
+ }
+ delete obj[eventsKey];
}
- lastClick = timeStamp;
- handler(e);
+ return this;
}
+function addOne(obj, type, fn, context) {
+ var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
+ if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
+ var handler = function (e) {
+ return fn.call(context || obj, e || window.event);
+ };
-var DomEvent = (Object.freeze || Object)({
- on: on,
- off: off,
- stopPropagation: stopPropagation,
- disableScrollPropagation: disableScrollPropagation,
- disableClickPropagation: disableClickPropagation,
- preventDefault: preventDefault,
- stop: stop,
- getMousePosition: getMousePosition,
- getWheelDelta: getWheelDelta,
- fakeStop: fakeStop,
- skipped: skipped,
- isExternalTarget: isExternalTarget,
- addListener: on,
- removeListener: off
-});
+ var originalHandler = handler;
-/*
- * @namespace DomUtil
- *
- * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
- * tree, used by Leaflet internally.
- *
- * Most functions expecting or returning a `HTMLElement` also work for
- * SVG elements. The only difference is that classes refer to CSS classes
- * in HTML and SVG classes in SVG.
- */
+ if (pointer && type.indexOf('touch') === 0) {
+ // Needs DomEvent.Pointer.js
+ addPointerListener(obj, type, handler, id);
+ } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
+ !(pointer && chrome)) {
+ // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
+ // See #5180
+ addDoubleTapListener(obj, handler, id);
-// @property TRANSFORM: String
-// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
-var TRANSFORM = testProp(
- ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+ } else if ('addEventListener' in obj) {
-// webkitTransition comes first because some browser versions that drop vendor prefix don't do
-// the same for the transitionend event, in particular the Android 4.1 stock browser
+ if (type === 'mousewheel') {
+ obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
-// @property TRANSITION: String
-// Vendor-prefixed transition style name.
-var TRANSITION = testProp(
- ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+ } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+ handler = function (e) {
+ e = e || window.event;
+ if (isExternalTarget(obj, e)) {
+ originalHandler(e);
+ }
+ };
+ obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
-// @property TRANSITION_END: String
-// Vendor-prefixed transitionend event name.
-var TRANSITION_END =
- TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
+ } else {
+ if (type === 'click' && android) {
+ handler = function (e) {
+ filterClick(e, originalHandler);
+ };
+ }
+ obj.addEventListener(type, handler, false);
+ }
+ } else if ('attachEvent' in obj) {
+ obj.attachEvent('on' + type, handler);
+ }
-// @function get(id: String|HTMLElement): HTMLElement
-// Returns an element given its DOM id, or returns the element itself
-// if it was passed directly.
-function get(id) {
- return typeof id === 'string' ? document.getElementById(id) : id;
+ obj[eventsKey] = obj[eventsKey] || {};
+ obj[eventsKey][id] = handler;
}
-// @function getStyle(el: HTMLElement, styleAttrib: String): String
-// Returns the value for a certain style attribute on an element,
-// including computed values or values set through CSS.
-function getStyle(el, style) {
- var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
+function removeOne(obj, type, fn, context) {
- if ((!value || value === 'auto') && document.defaultView) {
- var css = document.defaultView.getComputedStyle(el, null);
- value = css ? css[style] : null;
- }
- return value === 'auto' ? null : value;
-}
+ var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
+ handler = obj[eventsKey] && obj[eventsKey][id];
-// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
-// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
-function create$1(tagName, className, container) {
- var el = document.createElement(tagName);
- el.className = className || '';
+ if (!handler) { return this; }
- if (container) {
- container.appendChild(el);
- }
- return el;
-}
+ if (pointer && type.indexOf('touch') === 0) {
+ removePointerListener(obj, type, id);
-// @function remove(el: HTMLElement)
-// Removes `el` from its parent element
-function remove(el) {
- var parent = el.parentNode;
- if (parent) {
- parent.removeChild(el);
- }
-}
+ } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
+ !(pointer && chrome)) {
+ removeDoubleTapListener(obj, id);
-// @function empty(el: HTMLElement)
-// Removes all of `el`'s children elements from `el`
-function empty(el) {
- while (el.firstChild) {
- el.removeChild(el.firstChild);
- }
-}
+ } else if ('removeEventListener' in obj) {
-// @function toFront(el: HTMLElement)
-// Makes `el` the last child of its parent, so it renders in front of the other children.
-function toFront(el) {
- var parent = el.parentNode;
- if (parent.lastChild !== el) {
- parent.appendChild(el);
- }
-}
+ if (type === 'mousewheel') {
+ obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
-// @function toBack(el: HTMLElement)
-// Makes `el` the first child of its parent, so it renders behind the other children.
-function toBack(el) {
- var parent = el.parentNode;
- if (parent.firstChild !== el) {
- parent.insertBefore(el, parent.firstChild);
- }
-}
+ } else {
+ obj.removeEventListener(
+ type === 'mouseenter' ? 'mouseover' :
+ type === 'mouseleave' ? 'mouseout' : type, handler, false);
+ }
-// @function hasClass(el: HTMLElement, name: String): Boolean
-// Returns `true` if the element's class attribute contains `name`.
-function hasClass(el, name) {
- if (el.classList !== undefined) {
- return el.classList.contains(name);
+ } else if ('detachEvent' in obj) {
+ obj.detachEvent('on' + type, handler);
}
- var className = getClass(el);
- return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
-}
-// @function addClass(el: HTMLElement, name: String)
-// Adds `name` to the element's class attribute.
-function addClass(el, name) {
- if (el.classList !== undefined) {
- var classes = splitWords(name);
- for (var i = 0, len = classes.length; i < len; i++) {
- el.classList.add(classes[i]);
- }
- } else if (!hasClass(el, name)) {
- var className = getClass(el);
- setClass(el, (className ? className + ' ' : '') + name);
- }
+ obj[eventsKey][id] = null;
}
-// @function removeClass(el: HTMLElement, name: String)
-// Removes `name` from the element's class attribute.
-function removeClass(el, name) {
- if (el.classList !== undefined) {
- el.classList.remove(name);
+// @function stopPropagation(ev: DOMEvent): this
+// Stop the given event from propagation to parent elements. Used inside the listener functions:
+// ```js
+// L.DomEvent.on(div, 'click', function (ev) {
+// L.DomEvent.stopPropagation(ev);
+// });
+// ```
+function stopPropagation(e) {
+
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ } else if (e.originalEvent) { // In case of Leaflet event.
+ e.originalEvent._stopped = true;
} else {
- setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+ e.cancelBubble = true;
}
+ skipped(e);
+
+ return this;
}
-// @function setClass(el: HTMLElement, name: String)
-// Sets the element's class.
-function setClass(el, name) {
- if (el.className.baseVal === undefined) {
- el.className = name;
- } else {
- // in case of SVG element
- el.className.baseVal = name;
- }
+// @function disableScrollPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
+function disableScrollPropagation(el) {
+ addOne(el, 'mousewheel', stopPropagation);
+ return this;
}
-// @function getClass(el: HTMLElement): String
-// Returns the element's class.
-function getClass(el) {
- return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+// @function disableClickPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
+// `'mousedown'` and `'touchstart'` events (plus browser variants).
+function disableClickPropagation(el) {
+ on(el, 'mousedown touchstart dblclick', stopPropagation);
+ addOne(el, 'click', fakeStop);
+ return this;
}
-// @function setOpacity(el: HTMLElement, opacity: Number)
-// Set the opacity of an element (including old IE support).
-// `opacity` must be a number from `0` to `1`.
-function setOpacity(el, value) {
- if ('opacity' in el.style) {
- el.style.opacity = value;
- } else if ('filter' in el.style) {
- _setOpacityIE(el, value);
+// @function preventDefault(ev: DOMEvent): this
+// Prevents the default action of the DOM Event `ev` from happening (such as
+// following a link in the href of the a element, or doing a POST request
+// with page reload when a `<form>` is submitted).
+// Use it inside listener functions.
+function preventDefault(e) {
+ if (e.preventDefault) {
+ e.preventDefault();
+ } else {
+ e.returnValue = false;
}
+ return this;
}
-function _setOpacityIE(el, value) {
- var filter = false,
- filterName = 'DXImageTransform.Microsoft.Alpha';
+// @function stop(ev: DOMEvent): this
+// Does `stopPropagation` and `preventDefault` at the same time.
+function stop(e) {
+ preventDefault(e);
+ stopPropagation(e);
+ return this;
+}
- // filters collection throws an error if we try to retrieve a filter that doesn't exist
- try {
- filter = el.filters.item(filterName);
- } catch (e) {
- // don't set opacity to 1 if we haven't already set an opacity,
- // it isn't needed and breaks transparent pngs.
- if (value === 1) { return; }
+// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
+// Gets normalized mouse position from a DOM event relative to the
+// `container` (border excluded) or to the whole page if not specified.
+function getMousePosition(e, container) {
+ if (!container) {
+ return new Point(e.clientX, e.clientY);
}
- value = Math.round(value * 100);
+ var scale = getScale(container),
+ offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
- if (filter) {
- filter.Enabled = (value !== 100);
- filter.Opacity = value;
- } else {
- el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
- }
+ return new Point(
+ // offset.left/top values are in page scale (like clientX/Y),
+ // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
+ (e.clientX - offset.left) / scale.x - container.clientLeft,
+ (e.clientY - offset.top) / scale.y - container.clientTop
+ );
}
-// @function testProp(props: String[]): String|false
-// Goes through the array of style names and returns the first name
-// that is a valid style name for an element. If no such name is found,
-// it returns false. Useful for vendor-prefixed styles like `transform`.
-function testProp(props) {
- var style = document.documentElement.style;
+// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
+// and Firefox scrolls device pixels, not CSS pixels
+var wheelPxFactor =
+ (win && chrome) ? 2 * window.devicePixelRatio :
+ gecko ? window.devicePixelRatio : 1;
- for (var i = 0; i < props.length; i++) {
- if (props[i] in style) {
- return props[i];
- }
- }
- return false;
+// @function getWheelDelta(ev: DOMEvent): Number
+// Gets normalized wheel delta from a mousewheel DOM event, in vertical
+// pixels scrolled (negative if scrolling down).
+// Events from pointing devices without precise scrolling are mapped to
+// a best guess of 60 pixels.
+function getWheelDelta(e) {
+ return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
+ (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
+ (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
+ (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
+ (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
+ e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
+ (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
+ e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
+ 0;
}
-// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
-// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
-// and optionally scaled by `scale`. Does not have an effect if the
-// browser doesn't support 3D CSS transforms.
-function setTransform(el, offset, scale) {
- var pos = offset || new Point(0, 0);
+var skipEvents = {};
- el.style[TRANSFORM] =
- (ie3d ?
- 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
- 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
- (scale ? ' scale(' + scale + ')' : '');
+function fakeStop(e) {
+ // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
+ skipEvents[e.type] = true;
}
-// @function setPosition(el: HTMLElement, position: Point)
-// Sets the position of `el` to coordinates specified by `position`,
-// using CSS translate or top/left positioning depending on the browser
-// (used by Leaflet internally to position its layers).
-function setPosition(el, point) {
-
- /*eslint-disable */
- el._leaflet_pos = point;
- /*eslint-enable */
-
- if (any3d) {
- setTransform(el, point);
- } else {
- el.style.left = point.x + 'px';
- el.style.top = point.y + 'px';
- }
+function skipped(e) {
+ var events = skipEvents[e.type];
+ // reset when checking, as it's only used in map container and propagates outside of the map
+ skipEvents[e.type] = false;
+ return events;
}
-// @function getPosition(el: HTMLElement): Point
-// Returns the coordinates of an element previously positioned with setPosition.
-function getPosition(el) {
- // this method is only used for elements previously positioned using setPosition,
- // so it's safe to cache the position for performance
-
- return el._leaflet_pos || new Point(0, 0);
-}
+// check if element really left/entered the event target (for mouseenter/mouseleave)
+function isExternalTarget(el, e) {
-// @function disableTextSelection()
-// Prevents the user from generating `selectstart` DOM events, usually generated
-// when the user drags the mouse through a page with text. Used internally
-// by Leaflet to override the behaviour of any click-and-drag interaction on
-// the map. Affects drag interactions on the whole document.
+ var related = e.relatedTarget;
-// @function enableTextSelection()
-// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
-var disableTextSelection;
-var enableTextSelection;
-var _userSelect;
-if ('onselectstart' in document) {
- disableTextSelection = function () {
- on(window, 'selectstart', preventDefault);
- };
- enableTextSelection = function () {
- off(window, 'selectstart', preventDefault);
- };
-} else {
- var userSelectProperty = testProp(
- ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+ if (!related) { return true; }
- disableTextSelection = function () {
- if (userSelectProperty) {
- var style = document.documentElement.style;
- _userSelect = style[userSelectProperty];
- style[userSelectProperty] = 'none';
- }
- };
- enableTextSelection = function () {
- if (userSelectProperty) {
- document.documentElement.style[userSelectProperty] = _userSelect;
- _userSelect = undefined;
+ try {
+ while (related && (related !== el)) {
+ related = related.parentNode;
}
- };
+ } catch (err) {
+ return false;
+ }
+ return (related !== el);
}
-// @function disableImageDrag()
-// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
-// for `dragstart` DOM events, usually generated when the user drags an image.
-function disableImageDrag() {
- on(window, 'dragstart', preventDefault);
-}
+var lastClick;
-// @function enableImageDrag()
-// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
-function enableImageDrag() {
- off(window, 'dragstart', preventDefault);
-}
+// this is a horrible workaround for a bug in Android where a single touch triggers two click events
+function filterClick(e, handler) {
+ var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
+ elapsed = lastClick && (timeStamp - lastClick);
-var _outlineElement;
-var _outlineStyle;
-// @function preventOutline(el: HTMLElement)
-// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
-// of the element `el` invisible. Used internally by Leaflet to prevent
-// focusable elements from displaying an outline when the user performs a
-// drag interaction on them.
-function preventOutline(element) {
- while (element.tabIndex === -1) {
- element = element.parentNode;
+ // are they closer together than 500ms yet more than 100ms?
+ // Android typically triggers them ~300ms apart while multiple listeners
+ // on the same event should be triggered far faster;
+ // or check if click is simulated on the element, and if it is, reject any non-simulated events
+
+ if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+ stop(e);
+ return;
}
- if (!element.style) { return; }
- restoreOutline();
- _outlineElement = element;
- _outlineStyle = element.style.outline;
- element.style.outline = 'none';
- on(window, 'keydown', restoreOutline);
-}
+ lastClick = timeStamp;
-// @function restoreOutline()
-// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
-function restoreOutline() {
- if (!_outlineElement) { return; }
- _outlineElement.style.outline = _outlineStyle;
- _outlineElement = undefined;
- _outlineStyle = undefined;
- off(window, 'keydown', restoreOutline);
+ handler(e);
}
-var DomUtil = (Object.freeze || Object)({
- TRANSFORM: TRANSFORM,
- TRANSITION: TRANSITION,
- TRANSITION_END: TRANSITION_END,
- get: get,
- getStyle: getStyle,
- create: create$1,
- remove: remove,
- empty: empty,
- toFront: toFront,
- toBack: toBack,
- hasClass: hasClass,
- addClass: addClass,
- removeClass: removeClass,
- setClass: setClass,
- getClass: getClass,
- setOpacity: setOpacity,
- testProp: testProp,
- setTransform: setTransform,
- setPosition: setPosition,
- getPosition: getPosition,
- disableTextSelection: disableTextSelection,
- enableTextSelection: enableTextSelection,
- disableImageDrag: disableImageDrag,
- enableImageDrag: enableImageDrag,
- preventOutline: preventOutline,
- restoreOutline: restoreOutline
+
+
+var DomEvent = (Object.freeze || Object)({
+ on: on,
+ off: off,
+ stopPropagation: stopPropagation,
+ disableScrollPropagation: disableScrollPropagation,
+ disableClickPropagation: disableClickPropagation,
+ preventDefault: preventDefault,
+ stop: stop,
+ getMousePosition: getMousePosition,
+ getWheelDelta: getWheelDelta,
+ fakeStop: fakeStop,
+ skipped: skipped,
+ isExternalTarget: isExternalTarget,
+ addListener: on,
+ removeListener: off
});
/*
initialize: function (id, options) { // (HTMLElement or String, Object)
options = setOptions(this, options);
+ // Make sure to assign internal flags at the beginning,
+ // to avoid inconsistent state in some edge cases.
+ this._handlers = [];
+ this._layers = {};
+ this._zoomBoundLayers = {};
+ this._sizeChanged = true;
+
this._initContainer(id);
this._initLayout();
this.setView(toLatLng(options.center), options.zoom, {reset: true});
}
- this._handlers = [];
- this._layers = {};
- this._zoomBoundLayers = {};
- this._sizeChanged = true;
-
this.callInitHooks();
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
}
}
- this._moveStart(true);
+ this._moveStart(true, options.noMoveStart);
frame.call(this);
return this;
// @method setMinZoom(zoom: Number): this
// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
setMinZoom: function (zoom) {
+ var oldZoom = this.options.minZoom;
this.options.minZoom = zoom;
- if (this._loaded && this.getZoom() < this.options.minZoom) {
- return this.setZoom(zoom);
+ if (this._loaded && oldZoom !== zoom) {
+ this.fire('zoomlevelschange');
+
+ if (this.getZoom() < this.options.minZoom) {
+ return this.setZoom(zoom);
+ }
}
return this;
// @method setMaxZoom(zoom: Number): this
// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
setMaxZoom: function (zoom) {
+ var oldZoom = this.options.maxZoom;
this.options.maxZoom = zoom;
- if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
- return this.setZoom(zoom);
+ if (this._loaded && oldZoom !== zoom) {
+ this.fire('zoomlevelschange');
+
+ if (this.getZoom() > this.options.maxZoom) {
+ return this.setZoom(zoom);
+ }
}
return this;
return this;
},
- // @method invalidateSize(options: Zoom/Pan options): this
+ // @method panInside(latlng: LatLng, options?: options): this
+ // Pans the map the minimum amount to make the `latlng` visible. Use
+ // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
+ // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
+ // If `latlng` is already within the (optionally padded) display bounds,
+ // the map will not be panned.
+ panInside: function (latlng, options) {
+ options = options || {};
+
+ var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
+ paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
+ center = this.getCenter(),
+ pixelCenter = this.project(center),
+ pixelPoint = this.project(latlng),
+ pixelBounds = this.getPixelBounds(),
+ halfPixelBounds = pixelBounds.getSize().divideBy(2),
+ paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
+
+ if (!paddedBounds.contains(pixelPoint)) {
+ this._enforcingBounds = true;
+ var diff = pixelCenter.subtract(pixelPoint),
+ newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
+
+ if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
+ newCenter.x = pixelCenter.x - diff.x;
+ if (diff.x > 0) {
+ newCenter.x += halfPixelBounds.x - paddingTL.x;
+ } else {
+ newCenter.x -= halfPixelBounds.x - paddingBR.x;
+ }
+ }
+ if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
+ newCenter.y = pixelCenter.y - diff.y;
+ if (diff.y > 0) {
+ newCenter.y += halfPixelBounds.y - paddingTL.y;
+ } else {
+ newCenter.y -= halfPixelBounds.y - paddingBR.y;
+ }
+ }
+ this.panTo(this.unproject(newCenter), options);
+ this._enforcingBounds = false;
+ }
+ return this;
+ },
+
+ // @method invalidateSize(options: Zoom/pan options): this
// Checks if the map container size changed and updates the map if so —
// call it after you've changed the map size dynamically, also animating
// pan by default. If `options.pan` is `false`, panning will not occur.
var lat = pos.coords.latitude,
lng = pos.coords.longitude,
latlng = new LatLng(lat, lng),
- bounds = latlng.toBounds(pos.coords.accuracy),
+ bounds = latlng.toBounds(pos.coords.accuracy * 2),
options = this._locateOptions;
if (options.setView) {
this.fire('locationfound', data);
},
- // TODO handler.addTo
- // TODO Appropiate docs section?
+ // TODO Appropriate docs section?
// @section Other Methods
// @method addHandler(name: String, HandlerClass: Function): this
// Adds a new `Handler` to the map, given its name and constructor function.
} catch (e) {
/*eslint-disable */
this._container._leaflet_id = undefined;
- /*eslint-enable */
+ /* eslint-enable */
this._containerId = undefined;
}
+ if (this._locationWatchId !== undefined) {
+ this.stopLocate();
+ }
+
+ this._stop();
+
remove(this._mapPane);
if (this._clearControlPos) {
this._clearControlPos();
}
+ if (this._resizeRequest) {
+ cancelAnimFrame(this._resizeRequest);
+ this._resizeRequest = null;
+ }
this._clearHandlers();
this.options.maxZoom;
},
- // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
+ // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
// Returns the maximum zoom level on which the given bounds fit to the map
// view in its entirety. If `inside` (optional) is set to `true`, the method
// instead returns the minimum zoom level on which the map view fits into
// Pane for `GridLayer`s and `TileLayer`s
this.createPane('tilePane');
// @pane overlayPane: HTMLElement = 400
- // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
+ // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
this.createPane('shadowPane');
// @pane shadowPane: HTMLElement = 500
// Pane for overlay shadows (e.g. `Marker` shadows)
// Pane for `Icon`s of `Marker`s
this.createPane('markerPane');
// @pane tooltipPane: HTMLElement = 650
- // Pane for tooltip.
+ // Pane for `Tooltip`s.
this.createPane('tooltipPane');
// @pane popupPane: HTMLElement = 700
// Pane for `Popup`s.
var zoomChanged = this._zoom !== zoom;
this
- ._moveStart(zoomChanged)
+ ._moveStart(zoomChanged, false)
._move(center, zoom)
._moveEnd(zoomChanged);
}
},
- _moveStart: function (zoomChanged) {
+ _moveStart: function (zoomChanged, noMoveStart) {
// @event zoomstart: Event
// Fired when the map zoom is about to change (e.g. before zoom animation).
// @event movestart: Event
if (zoomChanged) {
this.fire('zoomstart');
}
- return this.fire('movestart');
+ if (!noMoveStart) {
+ this.fire('movestart');
+ }
+ return this;
},
_move: function (center, zoom, data) {
// this event. Also fired on mobile when the user holds a single touch
// for a second (also called long press).
// @event keypress: KeyboardEvent
- // Fired when the user presses a key from the keyboard while the map is focused.
+ // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
+ // @event keydown: KeyboardEvent
+ // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
+ // the `keydown` event is fired for keys that produce a character value and for keys
+ // that do not produce a character value.
+ // @event keyup: KeyboardEvent
+ // Fired when the user releases a key from the keyboard while the map is focused.
onOff(this._container, 'click dblclick mousedown mouseup ' +
- 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
+ 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
if (this.options.trackResize) {
onOff(window, 'resize', this._onResize, this);
var type = e.type;
- if (type === 'mousedown' || type === 'keypress') {
+ if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
// prevents outline when clicking on keyboard-focusable element
preventOutline(e.target || e.srcElement);
}
originalEvent: e
};
- if (e.type !== 'keypress') {
- var isMarker = (target.options && 'icon' in target.options);
+ if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
+ var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
data.containerPoint = isMarker ?
- this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
+ this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
}
_tryAnimatedPan: function (center, options) {
// difference between the new and current centers in pixels
- var offset = this._getCenterOffset(center)._floor();
+ var offset = this._getCenterOffset(center)._trunc();
// don't animate too far unless animate: true specified in options
if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
requestAnimFrame(function () {
this
- ._moveStart(true)
+ ._moveStart(true, false)
._animateZoom(center, zoom, true);
}, this);
},
_animateZoom: function (center, zoom, startAnim, noUpdate) {
+ if (!this._mapPane) { return; }
+
if (startAnim) {
this._animatingZoom = true;
}
// @event zoomanim: ZoomAnimEvent
- // Fired on every frame of a zoom animation
+ // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
this.fire('zoomanim', {
center: center,
zoom: zoom,
_onZoomTransitionEnd: function () {
if (!this._animatingZoom) { return; }
- removeClass(this._mapPane, 'leaflet-zoom-anim');
+ if (this._mapPane) {
+ removeClass(this._mapPane, 'leaflet-zoom-anim');
+ }
this._animatingZoom = false;
corner.appendChild(container);
}
+ this._map.on('unload', this.remove, this);
+
return this;
},
this.onRemove(this._map);
}
+ this._map.off('unload', this.remove, this);
this._map = null;
return this;
// Expand the control container if collapsed.
expand: function () {
addClass(this._container, 'leaflet-control-layers-expanded');
- this._form.style.height = null;
+ this._section.style.height = null;
var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
- if (acceptableHeight < this._form.clientHeight) {
- addClass(this._form, 'leaflet-control-layers-scrollbar');
- this._form.style.height = acceptableHeight + 'px';
+ if (acceptableHeight < this._section.clientHeight) {
+ addClass(this._section, 'leaflet-control-layers-scrollbar');
+ this._section.style.height = acceptableHeight + 'px';
} else {
- removeClass(this._form, 'leaflet-control-layers-scrollbar');
+ removeClass(this._section, 'leaflet-control-layers-scrollbar');
}
this._checkDisabledLayers();
return this;
disableClickPropagation(container);
disableScrollPropagation(container);
- var form = this._form = create$1('form', className + '-list');
+ var section = this._section = create$1('section', className + '-list');
if (collapsed) {
this._map.on('click', this.collapse, this);
this.expand();
}
- this._baseLayersList = create$1('div', className + '-base', form);
- this._separator = create$1('div', className + '-separator', form);
- this._overlaysList = create$1('div', className + '-overlays', form);
+ this._baseLayersList = create$1('div', className + '-base', section);
+ this._separator = create$1('div', className + '-separator', section);
+ this._overlaysList = create$1('div', className + '-overlays', section);
- container.appendChild(form);
+ container.appendChild(section);
},
_getLayer: function (id) {
input.className = 'leaflet-control-layers-selector';
input.defaultChecked = checked;
} else {
- input = this._createRadioElement('leaflet-base-layers', checked);
+ input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
}
this._layerControlInputs.push(input);
Map.addInitHook(function () {
if (this.options.zoomControl) {
+ // @section Controls
+ // @property zoomControl: Control.Zoom
+ // The default zoom control (only available if the
+ // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
this.zoomControl = new Zoom();
this.addControl(this.zoomControl);
}
y = map.getSize().y / 2;
var maxMeters = map.distance(
- map.containerPointToLatLng([0, y]),
- map.containerPointToLatLng([this.options.maxWidth, y]));
+ map.containerPointToLatLng([0, y]),
+ map.containerPointToLatLng([this.options.maxWidth, y]));
this._updateScales(maxMeters);
},
// @option prefix: String = 'Leaflet'
// The HTML text shown before the attributions. Pass `false` to disable.
- prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
+ prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
},
initialize: function (options) {
// Called when the handler is disabled, should remove the event hooks added previously.
});
+// @section There is static function which can be called without instantiating L.Handler:
+// @function addTo(map: Map, name: String): this
+// Adds a new Handler to the given map with the given name.
+Handler.addTo = function (map, name) {
+ map.addHandler(name, this);
+ return this;
+};
+
var Mixin = {Events: Events};
/*
// Fired when a drag is about to start.
this.fire('down');
- var first = e.touches ? e.touches[0] : e;
+ var first = e.touches ? e.touches[0] : e,
+ sizedParent = getSizedParentNode(this._element);
this._startPoint = new Point(first.clientX, first.clientY);
+ // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
+ this._parentScale = getScale(sizedParent);
+
on(document, MOVE[e.type], this._onMove, this);
on(document, END[e.type], this._onUp, this);
},
}
var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
- newPoint = new Point(first.clientX, first.clientY),
- offset = newPoint.subtract(this._startPoint);
+ offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
if (!offset.x && !offset.y) { return; }
if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
+ // We assume that the parent container's position, border and scale do not change for the duration of the drag.
+ // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
+ // and we can use the cached value for the scale.
+ offset.x /= this._parentScale.x;
+ offset.y /= this._parentScale.y;
+
preventDefault(e);
if (!this._moved) {
/*
* @namespace LineUtil
*
- * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
+ * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
*/
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
*/
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
- * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
+ * 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)).
* Used by Leaflet to only show polygon points that are on the screen or near, increasing
* performance. Note that polygon points needs different algorithm for clipping
- * than polyline, so there's a seperate method for it.
+ * than polyline, so there's a separate method for it.
*/
function clipPolygon(points, bounds, round) {
var clippedPoints,
* @namespace Projection
* @projection L.Projection.Mercator
*
- * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
+ * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
*/
var Mercator = {
* The inverse of `project`. Projects a 2D point into a geographical location.
* Only accepts actual `L.Point` instances, not arrays.
+ * Note that the projection instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
+
*/
* @example
*
* ```js
- * var layer = L.Marker(latlng).addTo(map);
+ * var layer = L.marker(latlng).addTo(map);
* layer.addTo(map);
* layer.remove();
* ```
pane: 'overlayPane',
// @option attribution: String = null
- // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
+ // 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.
attribution: null,
bubblingMouseEvents: true
var LayerGroup = Layer.extend({
- initialize: function (layers) {
+ initialize: function (layers, options) {
+ setOptions(this, options);
+
this._layers = {};
var i, len;
// @method clearLayers(): this
// Removes all the layers from the group.
clearLayers: function () {
- for (var i in this._layers) {
- this.removeLayer(this._layers[i]);
- }
- return this;
+ return this.eachLayer(this.removeLayer, this);
},
// @method invoke(methodName: String, …): this
},
onAdd: function (map) {
- for (var i in this._layers) {
- map.addLayer(this._layers[i]);
- }
+ this.eachLayer(map.addLayer, map);
},
onRemove: function (map) {
- for (var i in this._layers) {
- map.removeLayer(this._layers[i]);
- }
+ this.eachLayer(map.removeLayer, map);
},
// @method eachLayer(fn: Function, context?: Object): this
// Returns an array of all the layers added to the group.
getLayers: function () {
var layers = [];
-
- for (var i in this._layers) {
- layers.push(this._layers[i]);
- }
+ this.eachLayer(layers.push, layers);
return layers;
},
});
-// @factory L.layerGroup(layers?: Layer[])
-// Create a layer group, optionally given an initial set of layers.
-var layerGroup = function (layers) {
- return new LayerGroup(layers);
+// @factory L.layerGroup(layers?: Layer[], options?: Object)
+// Create a layer group, optionally given an initial set of layers and an `options` object.
+var layerGroup = function (layers, options) {
+ return new LayerGroup(layers, options);
};
/*
},
// @method bringToBack(): this
- // Brings the layer group to the top of all other layers
+ // Brings the layer group to the back of all other layers
bringToBack: function () {
return this.invoke('bringToBack');
},
* will be aligned so that this point is at the marker's geographical location. Centered
* by default if size is specified, also can be set in CSS with negative margins.
*
- * @option popupAnchor: Point = null
+ * @option popupAnchor: Point = [0, 0]
* The coordinates of the point from which popups will "open", relative to the icon anchor.
*
+ * @option tooltipAnchor: Point = [0, 0]
+ * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
+ *
* @option shadowUrl: String = null
* The URL to the icon shadow image. If not specified, no shadow image will be created.
*
* A custom class name to assign to both icon and shadow images. Empty by default.
*/
+ options: {
+ popupAnchor: [0, 0],
+ tooltipAnchor: [0, 0]
+ },
+
initialize: function (options) {
setOptions(this, options);
},
}
// @option imagePath: String
- // `Icon.Default` will try to auto-detect the absolute location of the
+ // `Icon.Default` will try to auto-detect the location of the
// blue icon images. If you are placing these images in a non-standard
- // way, set this option to point to the right absolute path.
+ // way, set this option to point to the right path.
return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
},
if (path === null || path.indexOf('url') !== 0) {
path = '';
} else {
- path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
+ path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
}
return path;
this._draggable.on({
dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).enable();
removeHooks: function () {
this._draggable.off({
dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).disable();
return this._draggable && this._draggable._moved;
},
+ _adjustPan: function (e) {
+ var marker = this._marker,
+ map = marker._map,
+ speed = this._marker.options.autoPanSpeed,
+ padding = this._marker.options.autoPanPadding,
+ iconPos = getPosition(marker._icon),
+ bounds = map.getPixelBounds(),
+ origin = map.getPixelOrigin();
+
+ var panBounds = toBounds(
+ bounds.min._subtract(origin).add(padding),
+ bounds.max._subtract(origin).subtract(padding)
+ );
+
+ if (!panBounds.contains(iconPos)) {
+ // Compute incremental movement
+ var movement = toPoint(
+ (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
+ (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
+
+ (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
+ (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
+ ).multiplyBy(speed);
+
+ map.panBy(movement, {animate: false});
+
+ this._draggable._newPos._add(movement);
+ this._draggable._startPos._add(movement);
+
+ setPosition(marker._icon, this._draggable._newPos);
+ this._onDrag(e);
+
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
_onDragStart: function () {
// @section Dragging events
// @event dragstart: Event
.fire('dragstart');
},
+ _onPreDrag: function (e) {
+ if (this._marker.options.autoPan) {
+ cancelAnimFrame(this._panRequest);
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
_onDrag: function (e) {
var marker = this._marker,
shadow = marker._shadow,
- iconPos = getPosition(marker._icon),
+ iconPos = getPosition(marker._icon),
latlng = marker._map.layerPointToLatLng(iconPos);
// update shadow position
// @event dragend: DragEndEvent
// Fired when the user stops dragging the marker.
+ cancelAnimFrame(this._panRequest);
+
// @event moveend: Event
// Fired when the marker stops moving (because of dragging).
delete this._oldLatLng;
// Option inherited from "Interactive layer" abstract class
interactive: true,
- // @option draggable: Boolean = false
- // Whether the marker is draggable with mouse/touch or not.
- draggable: false,
-
// @option keyboard: Boolean = true
// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
keyboard: true,
// `Map pane` where the markers icon will be added.
pane: 'markerPane',
+ // @option pane: String = 'shadowPane'
+ // `Map pane` where the markers shadow will be added.
+ shadowPane: 'shadowPane',
+
// @option bubblingMouseEvents: Boolean = false
// When `true`, a mouse event on this marker will trigger the same event on the map
// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
- bubblingMouseEvents: false
+ bubblingMouseEvents: false,
+
+ // @section Draggable marker options
+ // @option draggable: Boolean = false
+ // Whether the marker is draggable with mouse/touch or not.
+ draggable: false,
+
+ // @option autoPan: Boolean = false
+ // Whether to pan the map when dragging this marker near its edge or not.
+ autoPan: false,
+
+ // @option autoPanPadding: Point = Point(50, 50)
+ // Distance (in pixels to the left/right and to the top/bottom) of the
+ // map edge to start panning the map.
+ autoPanPadding: [50, 50],
+
+ // @option autoPanSpeed: Number = 10
+ // Number of pixels the map should pan by.
+ autoPanSpeed: 10
},
/* @section
return this.update();
},
+ // @method getIcon: Icon
+ // Returns the current icon used by the marker
+ getIcon: function () {
+ return this.options.icon;
+ },
+
// @method setIcon(icon: Icon): this
// Changes the marker icon.
setIcon: function (icon) {
update: function () {
- if (this._icon) {
+ if (this._icon && this._map) {
var pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos);
}
if (options.title) {
icon.title = options.title;
}
- if (options.alt) {
- icon.alt = options.alt;
+
+ if (icon.tagName === 'IMG') {
+ icon.alt = options.alt || '';
}
}
}
this._initInteraction();
if (newShadow && addShadow) {
- this.getPane('shadowPane').appendChild(this._shadow);
+ this.getPane(options.shadowPane).appendChild(this._shadow);
}
},
_updateOpacity: function () {
var opacity = this.options.opacity;
- setOpacity(this._icon, opacity);
+ if (this._icon) {
+ setOpacity(this._icon, opacity);
+ }
if (this._shadow) {
setOpacity(this._shadow, opacity);
},
_getPopupAnchor: function () {
- return this.options.icon.options.popupAnchor || [0, 0];
+ return this.options.icon.options.popupAnchor;
},
_getTooltipAnchor: function () {
- return this.options.icon.options.tooltipAnchor || [0, 0];
+ return this.options.icon.options.tooltipAnchor;
}
});
setOptions(this, style);
if (this._renderer) {
this._renderer._updateStyle(this);
+ if (this.options.stroke && style.hasOwnProperty('weight')) {
+ this._updateBounds();
+ }
}
return this;
},
_clickTolerance: function () {
// used when doing hit detection for Canvas layers
- return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
+ return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
}
});
}
this._point = p.subtract(map.getPixelOrigin());
- this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
- this._radiusY = Math.max(Math.round(p.y - top.y), 1);
+ this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
+ this._radiusY = p.y - top.y;
} else {
var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
return !this._latlngs.length;
},
+ // @method closestLayerPoint(p: Point): Point
+ // Returns the point closest to `p` on the Polyline.
closestLayerPoint: function (p) {
var minDistance = Infinity,
minPoint = null,
this._rings = [];
this._projectLatlngs(this._latlngs, this._rings, pxBounds);
- var w = this._clickTolerance(),
- p = new Point(w, w);
-
if (this._bounds.isValid() && pxBounds.isValid()) {
- pxBounds.min._subtract(p);
- pxBounds.max._add(p);
- this._pxBounds = pxBounds;
+ this._rawPxBounds = pxBounds;
+ this._updateBounds();
}
},
+ _updateBounds: function () {
+ var w = this._clickTolerance(),
+ p = new Point(w, w);
+ this._pxBounds = new Bounds([
+ this._rawPxBounds.min.subtract(p),
+ this._rawPxBounds.max.add(p)
+ ]);
+ },
+
// recursively turns latlngs into a set of rings with projected coordinates
_projectLatlngs: function (latlngs, result, projectedBounds) {
var flat = latlngs[0] instanceof LatLng,
var inside = false,
part, p1, p2, i, j, k, len, len2;
- if (!this._pxBounds.contains(p)) { return false; }
+ if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
// ray casting algorithm for detecting if point is in polygon
for (i = 0, len = this._parts.length; i < len; i++) {
},
_setLayerStyle: function (layer, style) {
- if (typeof style === 'function') {
- style = style(layer.feature);
- }
if (layer.setStyle) {
+ if (typeof style === 'function') {
+ style = style(layer.feature);
+ }
layer.setStyle(style);
}
}
for (var i = 0, len = coords.length, latlng; i < len; i++) {
latlng = levelsDeep ?
- coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
- (_coordsToLatLng || coordsToLatLng)(coords[i]);
+ coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
+ (_coordsToLatLng || coordsToLatLng)(coords[i]);
latlngs.push(latlng);
}
function latLngToCoords(latlng, precision) {
precision = typeof precision === 'number' ? precision : 6;
return latlng.alt !== undefined ?
- [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
- [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
}
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
function getFeature(layer, newGeometry) {
return layer.feature ?
- extend({}, layer.feature, {geometry: newGeometry}) :
- asFeature(newGeometry);
+ extend({}, layer.feature, {geometry: newGeometry}) :
+ asFeature(newGeometry);
}
// @function asFeature(geojson: Object): Object
};
// @namespace Marker
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
Marker.include(PointToGeoJSON);
// @namespace CircleMarker
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
Circle.include(PointToGeoJSON);
CircleMarker.include(PointToGeoJSON);
// @namespace Polyline
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
Polyline.include({
toGeoJSON: function (precision) {
});
// @namespace Polygon
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
Polygon.include({
toGeoJSON: function (precision) {
});
},
- // @method toGeoJSON(): Object
+ // @method toGeoJSON(precision?: Number): Object
+ // `precision` is the number of decimal places for coordinates.
+ // The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
toGeoJSON: function (precision) {
// @namespace GeoJSON
// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
// Creates a GeoJSON layer. Optionally accepts an object in
-// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
+// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
// (you can alternatively add it later with `addData` method) and an `options` object.
function geoJSON(geojson, options) {
return new GeoJSON(geojson, options);
// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
interactive: false,
- // @option crossOrigin: Boolean = false
- // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
+ // @option crossOrigin: Boolean|String = false
+ // Whether the crossOrigin attribute will be added to the image.
+ // 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.
+ // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
crossOrigin: false,
// @option errorOverlayUrl: String = ''
errorOverlayUrl: '',
// @option zIndex: Number = 1
- // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
+ // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
zIndex: 1,
// @option className: String = ''
// A custom class name to assign to the image. Empty by default.
- className: '',
+ className: ''
},
initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
return events;
},
- // @method: setZIndex(value: Number) : this
+ // @method setZIndex(value: Number): this
// Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
setZIndex: function (value) {
this.options.zIndex = value;
},
_initImage: function () {
- var img = this._image = create$1('img',
- 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +
- (this.options.className || ''));
+ var wasElementSupplied = this._url.tagName === 'IMG';
+ var img = this._image = wasElementSupplied ? this._url : create$1('img');
+
+ addClass(img, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(img, this.options.className); }
img.onselectstart = falseFn;
img.onmousemove = falseFn;
img.onload = bind(this.fire, this, 'load');
img.onerror = bind(this._overlayOnError, this, 'error');
- if (this.options.crossOrigin) {
- img.crossOrigin = '';
+ if (this.options.crossOrigin || this.options.crossOrigin === '') {
+ img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
if (this.options.zIndex) {
this._updateZIndex();
}
+ if (wasElementSupplied) {
+ this._url = img.src;
+ return;
+ }
+
img.src = this._url;
img.alt = this.options.alt;
},
_overlayOnError: function () {
// @event error: Event
- // Fired when the ImageOverlay layer has loaded its image
+ // Fired when the ImageOverlay layer fails to load its image
this.fire('error');
var errorUrl = this.options.errorOverlayUrl;
* ```js
* var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
* videoBounds = [[ 32, -130], [ 13, -100]];
- * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
+ * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
* ```
*/
// @option loop: Boolean = true
// Whether the video will loop back to the beginning when played.
- loop: true
+ loop: true,
+
+ // @option keepAspectRatio: Boolean = true
+ // Whether the video will save aspect ratio after the projection.
+ // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
+ keepAspectRatio: true
},
_initImage: function () {
var wasElementSupplied = this._url.tagName === 'VIDEO';
var vid = this._image = wasElementSupplied ? this._url : create$1('video');
- vid.class = vid.class || '';
- vid.class += 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '');
+ addClass(vid, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
vid.onselectstart = falseFn;
vid.onmousemove = falseFn;
// Fired when the video has finished loading the first frame
vid.onloadeddata = bind(this.fire, this, 'load');
- if (wasElementSupplied) { return; }
+ if (wasElementSupplied) {
+ var sourceElements = vid.getElementsByTagName('source');
+ var sources = [];
+ for (var j = 0; j < sourceElements.length; j++) {
+ sources.push(sourceElements[j].src);
+ }
+
+ this._url = (sourceElements.length > 0) ? sources : [vid.src];
+ return;
+ }
if (!isArray(this._url)) { this._url = [this._url]; }
+ if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
vid.autoplay = !!this.options.autoplay;
vid.loop = !!this.options.loop;
for (var i = 0; i < this._url.length; i++) {
return new VideoOverlay(video, bounds, options);
}
+/*
+ * @class SVGOverlay
+ * @aka L.SVGOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
+ *
+ * @example
+ *
+ * ```js
+ * var element = '<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/></svg>',
+ * elementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
+ * L.svgOverlay(element, elementBounds).addTo(map);
+ * ```
+ */
+
+var SVGOverlay = ImageOverlay.extend({
+ _initImage: function () {
+ var el = this._image = this._url;
+
+ addClass(el, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
+
+ el.onselectstart = falseFn;
+ el.onmousemove = falseFn;
+ }
+
+ // @method getElement(): SVGElement
+ // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
+ // used by this overlay.
+});
+
+
+// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
+// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
+// A viewBox attribute is required on the SVG element to zoom in and out properly.
+
+function svgOverlay(el, bounds, options) {
+ return new SVGOverlay(el, bounds, options);
+}
+
/*
* @class DivOverlay
* @inherits Layer
return this;
},
+ _prepareOpen: function (parent, layer, latlng) {
+ if (!(layer instanceof Layer)) {
+ latlng = layer;
+ layer = parent;
+ }
+
+ if (layer instanceof FeatureGroup) {
+ for (var id in parent._layers) {
+ layer = parent._layers[id];
+ break;
+ }
+ }
+
+ if (!latlng) {
+ if (layer.getCenter) {
+ latlng = layer.getCenter();
+ } else if (layer.getLatLng) {
+ latlng = layer.getLatLng();
+ } else {
+ throw new Error('Unable to get source layer LatLng.');
+ }
+ }
+
+ // set overlay source to this layer
+ this._source = layer;
+
+ // update the overlay (content, layout, ect...)
+ this.update();
+
+ return latlng;
+ },
+
_updateContent: function () {
if (!this._content) { return; }
// the popup closing when another popup is opened.
autoClose: true,
+ // @option closeOnEscapeKey: Boolean = true
+ // Set it to `false` if you want to override the default behavior of
+ // the ESC key for closing of the popup.
+ closeOnEscapeKey: true,
+
// @option closeOnClick: Boolean = *
// Set it if you want to override the default behavior of the popup closing when user clicks
// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
},
_adjustPan: function () {
- if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
+ if (!this.options.autoPan) { return; }
+ if (this._map._panAnim) { this._map._panAnim.stop(); }
var map = this._map,
marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
},
// @method openPopup(latlng?: LatLng): this
- // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
+ // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
openPopup: function (layer, latlng) {
- if (!(layer instanceof Layer)) {
- latlng = layer;
- layer = this;
- }
-
- if (layer instanceof FeatureGroup) {
- for (var id in this._layers) {
- layer = this._layers[id];
- break;
- }
- }
-
- if (!latlng) {
- latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
- }
-
if (this._popup && this._map) {
- // set popup source to this layer
- this._popup._source = layer;
-
- // update the popup (content, layout, ect...)
- this._popup.update();
+ latlng = this._popup._prepareOpen(this, layer, latlng);
// open the popup on the map
this._map.openPopup(this._popup, latlng);
* marker.bindTooltip("my tooltip text").openTooltip();
* ```
* Note about tooltip offset. Leaflet takes two options in consideration
- * for computing tooltip offseting:
+ * for computing tooltip offsetting:
* - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
* Add a positive x offset to move the tooltip to the right, and a positive y offset to
* move it to the bottom. Negatives will move to the left and top.
// @option direction: String = 'auto'
// Direction where to open the tooltip. Possible values are: `right`, `left`,
// `top`, `bottom`, `center`, `auto`.
- // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
+ // `auto` will dynamically switch between `right` and `left` according to the tooltip
// position on the map.
direction: 'auto',
},
// @method openTooltip(latlng?: LatLng): this
- // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
+ // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
openTooltip: function (layer, latlng) {
- if (!(layer instanceof Layer)) {
- latlng = layer;
- layer = this;
- }
-
- if (layer instanceof FeatureGroup) {
- for (var id in this._layers) {
- layer = this._layers[id];
- break;
- }
- }
-
- if (!latlng) {
- latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
- }
-
if (this._tooltip && this._map) {
-
- // set tooltip source to this layer
- this._tooltip._source = layer;
-
- // update the tooltip (content, layout, ect...)
- this._tooltip.update();
+ latlng = this._tooltip._prepareOpen(this, layer, latlng);
// open the tooltip on the map
this._map.openTooltip(this._tooltip, latlng);
// iconAnchor: (Point),
// popupAnchor: (Point),
- // @option html: String = ''
- // Custom HTML code to put inside the div element, empty by default.
+ // @option html: String|HTMLElement = ''
+ // Custom HTML code to put inside the div element, empty by default. Alternatively,
+ // an instance of `HTMLElement`.
html: false,
// @option bgPos: Point = [0, 0]
var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
options = this.options;
- div.innerHTML = options.html !== false ? options.html : '';
+ if (options.html instanceof Element) {
+ empty(div);
+ div.appendChild(options.html);
+ } else {
+ div.innerHTML = options.html !== false ? options.html : '';
+ }
if (options.bgPos) {
var bgPos = toPoint(options.bgPos);
remove(this._container);
map._removeZoomLimit(this);
this._container = null;
- this._tileZoom = null;
+ this._tileZoom = undefined;
},
// @method bringToFront: this
// @section Extension methods
// Layers extending `GridLayer` shall reimplement the following method.
// @method createTile(coords: Object, done?: Function): HTMLElement
- // Called only internally, must be overriden by classes extending `GridLayer`.
+ // Called only internally, must be overridden by classes extending `GridLayer`.
// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
// is specified, it must be called when the tile has finished loading and drawing.
createTile: function () {
}
this._removeAllTiles();
- this._tileZoom = null;
+ this._tileZoom = undefined;
},
_retainParent: function (x, y, z, minZoom) {
if (!this._isValidTile(coords)) { continue; }
- if (!this._tiles[this._tileCoordsToKey(coords)]) {
+ var tile = this._tiles[this._tileCoordsToKey(coords)];
+ if (tile) {
+ tile.current = true;
+ } else {
queue.push(coords);
}
}
return this._tileCoordsToBounds(this._keyToTileCoords(key));
},
- // converts tile coordinates to its geographical bounds
- _tileCoordsToBounds: function (coords) {
-
+ _tileCoordsToNwSe: function (coords) {
var map = this._map,
tileSize = this.getTileSize(),
-
nwPoint = coords.scaleBy(tileSize),
sePoint = nwPoint.add(tileSize),
-
nw = map.unproject(nwPoint, coords.z),
- se = map.unproject(sePoint, coords.z),
- bounds = new LatLngBounds(nw, se);
+ se = map.unproject(sePoint, coords.z);
+ return [nw, se];
+ },
+
+ // converts tile coordinates to its geographical bounds
+ _tileCoordsToBounds: function (coords) {
+ var bp = this._tileCoordsToNwSe(coords),
+ bounds = new LatLngBounds(bp[0], bp[1]);
if (!this.options.noWrap) {
- map.wrapLatLngBounds(bounds);
+ bounds = this._map.wrapLatLngBounds(bounds);
}
-
return bounds;
},
-
// converts tile coordinates to key for the tile cache
_tileCoordsToKey: function (coords) {
return coords.x + ':' + coords.y + ':' + coords.z;
},
_tileReady: function (coords, err, tile) {
- if (!this._map) { return; }
-
if (err) {
// @event tileerror: TileErrorEvent
// Fired when there is an error loading a tile.
* @class TileLayer
* @inherits GridLayer
* @aka L.TileLayer
- * Used to load and display tile layers on the map. Extends `GridLayer`.
+ * 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`.
*
* @example
*
* ```js
- * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
+ * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
* ```
*
* @section URL template
// 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.
detectRetina: false,
- // @option crossOrigin: Boolean = false
- // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
+ // @option crossOrigin: Boolean|String = false
+ // Whether the crossOrigin attribute will be added to the tiles.
+ // 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.
+ // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
crossOrigin: false
},
// @method setUrl(url: String, noRedraw?: Boolean): this
// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
+ // If the URL does not change, the layer will not be redrawn unless
+ // the noRedraw parameter is set to false.
setUrl: function (url, noRedraw) {
+ if (this._url === url && noRedraw === undefined) {
+ noRedraw = true;
+ }
+
this._url = url;
if (!noRedraw) {
// @method createTile(coords: Object, done?: Function): HTMLElement
// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
- // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
+ // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
// callback is called when the tile has been loaded.
createTile: function (coords, done) {
var tile = document.createElement('img');
on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
on(tile, 'error', bind(this._tileOnError, this, done, tile));
- if (this.options.crossOrigin) {
- tile.crossOrigin = '';
+ if (this.options.crossOrigin || this.options.crossOrigin === '') {
+ tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
/*
_tileOnError: function (done, tile, e) {
var errorUrl = this.options.errorTileUrl;
- if (errorUrl && tile.src !== errorUrl) {
+ if (errorUrl && tile.getAttribute('src') !== errorUrl) {
tile.src = errorUrl;
}
done(e, tile);
if (!tile.complete) {
tile.src = emptyImageUrl;
remove(tile);
+ delete this._tiles[i];
}
}
}
+ },
+
+ _removeTile: function (key) {
+ var tile = this._tiles[key];
+ if (!tile) { return; }
+
+ // Cancels any pending http requests associated with the tile
+ // unless we're on Android's stock browser,
+ // see https://github.com/Leaflet/Leaflet/issues/137
+ if (!androidStock) {
+ tile.el.setAttribute('src', emptyImageUrl);
+ }
+
+ return GridLayer.prototype._removeTile.call(this, key);
+ },
+
+ _tileReady: function (coords, err, tile) {
+ if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
+ return;
+ }
+
+ return GridLayer.prototype._tileReady.call(this, coords, err, tile);
}
});
options = setOptions(this, options);
- wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
+ var realRetina = options.detectRetina && retina ? 2 : 1;
+ var tileSize = this.getTileSize();
+ wmsParams.width = tileSize.x * realRetina;
+ wmsParams.height = tileSize.y * realRetina;
this.wmsParams = wmsParams;
},
getTileUrl: function (coords) {
- var tileBounds = this._tileCoordsToBounds(coords),
- nw = this._crs.project(tileBounds.getNorthWest()),
- se = this._crs.project(tileBounds.getSouthEast()),
-
+ var tileBounds = this._tileCoordsToNwSe(coords),
+ crs = this._crs,
+ bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
+ min = bounds.min,
+ max = bounds.max,
bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
- [se.y, nw.x, nw.y, se.x] :
- [nw.x, se.y, se.x, nw.y]).join(','),
-
+ [min.y, min.x, max.y, max.x] :
+ [min.x, min.y, max.x, max.y]).join(','),
url = TileLayer.prototype.getTileUrl.call(this, coords);
-
return url +
getParamString(this.wmsParams, url, this.options.uppercase) +
(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
// @option padding: Number = 0.1
// How much to extend the clip area around the map view (relative to its size)
// e.g. 0.1 would be 10% of map view in each direction
- padding: 0.1
+ padding: 0.1,
+
+ // @option tolerance: Number = 0
+ // How much to extend click tolerance round a path/object on the map
+ tolerance : 0
},
initialize: function (options) {
},
_destroyContainer: function () {
+ cancelAnimFrame(this._redrawRequest);
delete this._ctx;
remove(this._container);
off(this._container);
_update: function () {
if (this._map._animatingZoom && this._bounds) { return; }
- this._drawnLayers = {};
-
Renderer.prototype._update.call(this);
var b = this._bounds,
delete layer._order;
- delete this._layers[L.stamp(layer)];
+ delete this._layers[stamp(layer)];
this._requestRedraw(layer);
},
},
_updateDashArray: function (layer) {
- if (layer.options.dashArray) {
- var parts = layer.options.dashArray.split(','),
+ if (typeof layer.options.dashArray === 'string') {
+ var parts = layer.options.dashArray.split(/[, ]+/),
dashArray = [],
+ dashValue,
i;
for (i = 0; i < parts.length; i++) {
- dashArray.push(Number(parts[i]));
+ dashValue = Number(parts[i]);
+ // Ignore dash array containing invalid lengths
+ if (isNaN(dashValue)) { return; }
+ dashArray.push(dashValue);
}
layer.options._dashArray = dashArray;
+ } else {
+ layer.options._dashArray = layer.options.dashArray;
}
},
if (!len) { return; }
- this._drawnLayers[layer._leaflet_id] = layer;
-
ctx.beginPath();
for (i = 0; i < len; i++) {
var p = layer._point,
ctx = this._ctx,
- r = layer._radius,
- s = (layer._radiusY || r) / r;
-
- this._drawnLayers[layer._leaflet_id] = layer;
+ r = Math.max(Math.round(layer._radius), 1),
+ s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
_bringToFront: function (layer) {
var order = layer._order;
+
+ if (!order) { return; }
+
var next = order.next;
var prev = order.prev;
prev.next = next;
} else if (next) {
// Update first entry unless this is the
- // signle entry
+ // single entry
this._drawFirst = next;
}
_bringToBack: function (layer) {
var order = layer._order;
+
+ if (!order) { return; }
+
var next = order.next;
var prev = order.prev;
next.prev = prev;
} else if (prev) {
// Update last entry unless this is the
- // signle entry
+ // single entry
this._drawLast = prev;
}
/*
* @class SVG
*
- * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
*
* VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
* with old versions of Internet Explorer.
r2 = Math.round(layer._radiusY || r);
this._setPath(layer, layer._empty() ? 'M0 0' :
- 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
+ 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
},
_setPath: function (layer, path) {
off(this._container);
delete this._container;
delete this._rootGroup;
+ delete this._svgSize;
},
_onZoomStart: function () {
_updateCircle: function (layer) {
var p = layer._point,
- r = layer._radius,
- r2 = layer._radiusY || r,
+ r = Math.max(Math.round(layer._radius), 1),
+ r2 = Math.max(Math.round(layer._radiusY), 1) || r,
arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
// drawing a circle with two half-arcs
var d = layer._empty() ? 'M0 0' :
- 'M' + (p.x - r) + ',' + p.y +
- arc + (r * 2) + ',0 ' +
- arc + (-r * 2) + ',0 ';
+ 'M' + (p.x - r) + ',' + p.y +
+ arc + (r * 2) + ',0 ' +
+ arc + (-r * 2) + ',0 ';
this._setPath(layer, d);
},
SVG.include(vmlMixin);
}
+// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
function svg$1(options) {
var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
if (!renderer) {
- // @namespace Map; @option preferCanvas: Boolean = false
- // Whether `Path`s should be rendered on a `Canvas` renderer.
- // By default, all `Path`s are rendered in a `SVG` renderer.
- renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
+ renderer = this._renderer = this._createRenderer();
}
if (!this.hasLayer(renderer)) {
var renderer = this._paneRenderers[name];
if (renderer === undefined) {
- renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
+ renderer = this._createRenderer({pane: name});
this._paneRenderers[name] = renderer;
}
return renderer;
+ },
+
+ _createRenderer: function (options) {
+ // @namespace Map; @option preferCanvas: Boolean = false
+ // Whether `Path`s should be rendered on a `Canvas` renderer.
+ // By default, all `Path`s are rendered in a `SVG` renderer.
+ return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
}
});
/*
* @class Rectangle
- * @aka L.Retangle
+ * @aka L.Rectangle
* @inherits Polygon
*
* A class for drawing rectangle overlays on a map. Extends `Polygon`.
this._positions.push(pos);
this._times.push(time);
- if (time - this._times[0] > 50) {
- this._positions.shift();
- this._times.shift();
- }
+ this._prunePositions(time);
}
this._map
.fire('drag', e);
},
+ _prunePositions: function (time) {
+ while (this._positions.length > 1 && time - this._times[0] > 50) {
+ this._positions.shift();
+ this._times.shift();
+ }
+ },
+
_onZoomEnd: function () {
var pxCenter = this._map.getSize().divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
map.fire('moveend');
} else {
+ this._prunePositions(+new Date());
var direction = this._lastPos.subtract(this._positions[0]),
duration = (this._lastTime - this._times[0]) / 1000,
offset;
if (key in this._panKeys) {
+ if (!map._panAnim || !map._panAnim._inProgress) {
+ offset = this._panKeys[key];
+ if (e.shiftKey) {
+ offset = toPoint(offset).multiplyBy(3);
+ }
- if (map._panAnim && map._panAnim._inProgress) { return; }
-
- offset = this._panKeys[key];
- if (e.shiftKey) {
- offset = toPoint(offset).multiplyBy(3);
- }
-
- map.panBy(offset);
+ map.panBy(offset);
- if (map.options.maxBounds) {
- map.panInsideBounds(map.options.maxBounds);
+ if (map.options.maxBounds) {
+ map.panInsideBounds(map.options.maxBounds);
+ }
}
-
} else if (key in this._zoomKeys) {
map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
- } else if (key === 27 && map._popup) {
+ } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
map.closePopup();
} else {
}
if (!this._moved) {
- map._moveStart(true);
+ map._moveStart(true, false);
this._moved = true;
}
Map.Tap = Tap;
Map.TouchZoom = TouchZoom;
-// misc
-
-var oldL = window.L;
-function noConflict() {
- window.L = oldL;
- return this;
-}
-
-// Always export us to window global (see #2364)
-window.L = exports;
-
Object.freeze = freeze;
exports.version = version;
-exports.noConflict = noConflict;
exports.Control = Control;
exports.control = control;
exports.Browser = Browser;
exports.imageOverlay = imageOverlay;
exports.VideoOverlay = VideoOverlay;
exports.videoOverlay = videoOverlay;
+exports.SVGOverlay = SVGOverlay;
+exports.svgOverlay = svgOverlay;
exports.DivOverlay = DivOverlay;
exports.Popup = Popup;
exports.popup = popup;
exports.Map = Map;
exports.map = createMap;
+var oldL = window.L;
+exports.noConflict = function() {
+ window.L = oldL;
+ return this;
+}
+
+// Always export us to window global (see #2364)
+window.L = exports;
+
})));
//# sourceMappingURL=leaflet-src.js.map