UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

2,026 lines (1,540 loc) 233 kB
/* Leaflet 1.0.0-beta.2 (55fe462), a JS library for interactive maps. http://leafletjs.com (c) 2010-2015 Vladimir Agafonkin, (c) 2010-2011 CloudMade */ (function (window, document, undefined) { var L = { version: '1.0.0-beta.2' }; function expose() { var oldL = window.L; L.noConflict = function () { window.L = oldL; return this; }; window.L = L; } // define Leaflet for Node module pattern loaders, including Browserify if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = L; // define Leaflet as an AMD module } else if (typeof define === 'function' && define.amd) { define(L); } // define Leaflet as a global L variable, saving the original L to restore later if needed if (typeof window !== 'undefined') { expose(); } /* * L.Util contains various utility functions used throughout Leaflet code. */ L.Util = { // extend an object with properties of one or more other objects extend: function (dest) { var i, j, len, src; for (j = 1, len = arguments.length; j < len; j++) { src = arguments[j]; for (i in src) { dest[i] = src[i]; } } return dest; }, // create an object from a given prototype create: Object.create || (function () { function F() {} return function (proto) { F.prototype = proto; return new F(); }; })(), // bind a function to be called with a given context bind: function (fn, obj) { var slice = Array.prototype.slice; if (fn.bind) { return fn.bind.apply(fn, slice.call(arguments, 1)); } var args = slice.call(arguments, 2); return function () { return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); }; }, // return unique ID of an object stamp: function (obj) { /*eslint-disable */ obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId; return obj._leaflet_id; /*eslint-enable */ }, lastId: 0, // return a function that won't be called more often than the given interval throttle: function (fn, time, context) { var lock, args, wrapperFn, later; later = function () { // reset lock and call if queued lock = false; if (args) { wrapperFn.apply(context, args); args = false; } }; wrapperFn = function () { if (lock) { // called too soon, queue to call later args = arguments; } else { // call and lock until later fn.apply(context, arguments); setTimeout(later, time); lock = true; } }; return wrapperFn; }, // wrap the given number to lie within a certain range (used for wrapping longitude) wrapNum: function (x, range, includeMax) { var max = range[1], min = range[0], d = max - min; return x === max && includeMax ? x : ((x - min) % d + d) % d + min; }, // do nothing (used as a noop throughout the code) falseFn: function () { return false; }, // round a given number to a given precision formatNum: function (num, digits) { var pow = Math.pow(10, digits || 5); return Math.round(num * pow) / pow; }, // trim whitespace from both sides of a string trim: function (str) { return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); }, // split a string into words splitWords: function (str) { return L.Util.trim(str).split(/\s+/); }, // set options to an object, inheriting parent's options as well setOptions: function (obj, options) { if (!obj.hasOwnProperty('options')) { obj.options = obj.options ? L.Util.create(obj.options) : {}; } for (var i in options) { obj.options[i] = options[i]; } return obj.options; }, // make a URL with GET parameters out of a set of properties/values getParamString: function (obj, existingUrl, uppercase) { var params = []; for (var i in obj) { params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); } return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); }, // super-simple templating facility, used for TileLayer URLs template: function (str, data) { return str.replace(L.Util.templateRe, function (str, key) { var value = data[key]; if (value === undefined) { throw new Error('No value provided for variable ' + str); } else if (typeof value === 'function') { value = value(data); } return value; }); }, templateRe: /\{ *([\w_]+) *\}/g, isArray: Array.isArray || function (obj) { return (Object.prototype.toString.call(obj) === '[object Array]'); }, indexOf: function (array, el) { for (var i = 0; i < array.length; i++) { if (array[i] === el) { return i; } } return -1; }, // minimal image URI, set to an image when disposing to flush memory emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' }; (function () { // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ function getPrefixed(name) { return window['webkit' + name] || window['moz' + name] || window['ms' + name]; } var lastTime = 0; // fallback for IE 7-8 function timeoutDefer(fn) { var time = +new Date(), timeToCall = Math.max(0, 16 - (time - lastTime)); lastTime = time + timeToCall; return window.setTimeout(fn, timeToCall); } var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer, cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; L.Util.requestAnimFrame = function (fn, context, immediate) { if (immediate && requestFn === timeoutDefer) { fn.call(context); } else { return requestFn.call(window, L.bind(fn, context)); } }; L.Util.cancelAnimFrame = function (id) { if (id) { cancelFn.call(window, id); } }; })(); // shortcuts for most used utility functions L.extend = L.Util.extend; L.bind = L.Util.bind; L.stamp = L.Util.stamp; L.setOptions = L.Util.setOptions; /* * L.Class powers the OOP facilities of the library. * Thanks to John Resig and Dean Edwards for inspiration! */ L.Class = function () {}; L.Class.extend = function (props) { // extended class with the new prototype var NewClass = function () { // call the constructor if (this.initialize) { this.initialize.apply(this, arguments); } // call all constructor hooks this.callInitHooks(); }; var parentProto = NewClass.__super__ = this.prototype; var proto = L.Util.create(parentProto); proto.constructor = NewClass; NewClass.prototype = proto; // inherit parent's statics for (var i in this) { if (this.hasOwnProperty(i) && i !== 'prototype') { NewClass[i] = this[i]; } } // mix static properties into the class if (props.statics) { L.extend(NewClass, props.statics); delete props.statics; } // mix includes into the prototype if (props.includes) { L.Util.extend.apply(null, [proto].concat(props.includes)); delete props.includes; } // merge options if (proto.options) { props.options = L.Util.extend(L.Util.create(proto.options), props.options); } // mix given properties into the prototype L.extend(proto, props); proto._initHooks = []; // add method for calling all hooks proto.callInitHooks = function () { if (this._initHooksCalled) { return; } if (parentProto.callInitHooks) { parentProto.callInitHooks.call(this); } this._initHooksCalled = true; for (var i = 0, len = proto._initHooks.length; i < len; i++) { proto._initHooks[i].call(this); } }; return NewClass; }; // method for adding properties to prototype L.Class.include = function (props) { L.extend(this.prototype, props); }; // merge new default options to the Class L.Class.mergeOptions = function (options) { L.extend(this.prototype.options, options); }; // add a constructor hook L.Class.addInitHook = function (fn) { // (Function) || (String, args...) var args = Array.prototype.slice.call(arguments, 1); var init = typeof fn === 'function' ? fn : function () { this[fn].apply(this, args); }; this.prototype._initHooks = this.prototype._initHooks || []; this.prototype._initHooks.push(init); }; /* * L.Evented is a base class that Leaflet classes inherit from to handle custom events. */ L.Evented = L.Class.extend({ on: function (types, fn, context) { // types can be a map of types/handlers if (typeof types === 'object') { for (var type in types) { // we don't process space-separated events here for performance; // it's a hot path since Layer uses the on(obj) syntax this._on(type, types[type], fn); } } else { // types can be a string of space-separated words types = L.Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._on(types[i], fn, context); } } return this; }, off: function (types, fn, context) { if (!types) { // clear all listeners if called without arguments delete this._events; } else if (typeof types === 'object') { for (var type in types) { this._off(type, types[type], fn); } } else { types = L.Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._off(types[i], fn, context); } } return this; }, // attach listener (without syntactic sugar now) _on: function (type, fn, context) { var events = this._events = this._events || {}, contextId = context && context !== this && L.stamp(context); if (contextId) { // store listeners with custom context in a separate hash (if it has an id); // gives a major performance boost when firing and removing events (e.g. on map object) var indexKey = type + '_idx', indexLenKey = type + '_len', typeIndex = events[indexKey] = events[indexKey] || {}, id = L.stamp(fn) + '_' + contextId; if (!typeIndex[id]) { typeIndex[id] = {fn: fn, ctx: context}; // keep track of the number of keys in the index to quickly check if it's empty events[indexLenKey] = (events[indexLenKey] || 0) + 1; } } else { // individual layers mostly use "this" for context and don't fire listeners too often // so simple array makes the memory footprint better while not degrading performance events[type] = events[type] || []; events[type].push({fn: fn}); } }, _off: function (type, fn, context) { var events = this._events, indexKey = type + '_idx', indexLenKey = type + '_len'; if (!events) { return; } if (!fn) { // clear all listeners for a type if function isn't specified delete events[type]; delete events[indexKey]; delete events[indexLenKey]; return; } var contextId = context && context !== this && L.stamp(context), listeners, i, len, listener, id; if (contextId) { id = L.stamp(fn) + '_' + contextId; listeners = events[indexKey]; if (listeners && listeners[id]) { listener = listeners[id]; delete listeners[id]; events[indexLenKey]--; } } else { listeners = events[type]; if (listeners) { for (i = 0, len = listeners.length; i < len; i++) { if (listeners[i].fn === fn) { listener = listeners[i]; listeners.splice(i, 1); break; } } } } // set the removed listener to noop so that's not called if remove happens in fire if (listener) { listener.fn = L.Util.falseFn; } }, fire: function (type, data, propagate) { if (!this.listens(type, propagate)) { return this; } var event = L.Util.extend({}, data, {type: type, target: this}), events = this._events; if (events) { var typeIndex = events[type + '_idx'], i, len, listeners, id; if (events[type]) { // make sure adding/removing listeners inside other listeners won't cause infinite loop listeners = events[type].slice(); for (i = 0, len = listeners.length; i < len; i++) { listeners[i].fn.call(this, event); } } // fire event for the context-indexed listeners as well for (id in typeIndex) { typeIndex[id].fn.call(typeIndex[id].ctx, event); } } if (propagate) { // propagate the event to parents (set with addEventParent) this._propagateEvent(event); } return this; }, listens: function (type, propagate) { var events = this._events; if (events && (events[type] || events[type + '_len'])) { return true; } if (propagate) { // also check parents for listeners if event propagates for (var id in this._eventParents) { if (this._eventParents[id].listens(type, propagate)) { return true; } } } return false; }, once: function (types, fn, context) { if (typeof types === 'object') { for (var type in types) { this.once(type, types[type], fn); } return this; } var handler = L.bind(function () { this .off(types, fn, context) .off(types, handler, context); }, this); // add a listener that's executed once and removed after that return this .on(types, fn, context) .on(types, handler, context); }, // adds a parent to propagate events to (when you fire with true as a 3rd argument) addEventParent: function (obj) { this._eventParents = this._eventParents || {}; this._eventParents[L.stamp(obj)] = obj; return this; }, removeEventParent: function (obj) { if (this._eventParents) { delete this._eventParents[L.stamp(obj)]; } return this; }, _propagateEvent: function (e) { for (var id in this._eventParents) { this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true); } } }); var proto = L.Evented.prototype; // aliases; we should ditch those eventually proto.addEventListener = proto.on; proto.removeEventListener = proto.clearAllEventListeners = proto.off; proto.addOneTimeEventListener = proto.once; proto.fireEvent = proto.fire; proto.hasEventListeners = proto.listens; L.Mixin = {Events: proto}; /* * L.Browser handles different browser and feature detections for internal Leaflet use. */ (function () { var ua = navigator.userAgent.toLowerCase(), doc = document.documentElement, ie = 'ActiveXObject' in window, webkit = ua.indexOf('webkit') !== -1, phantomjs = ua.indexOf('phantom') !== -1, android23 = ua.search('android [23]') !== -1, chrome = ua.indexOf('chrome') !== -1, gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie, mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1, msPointer = !window.PointerEvent && window.MSPointerEvent, pointer = (window.PointerEvent && navigator.pointerEnabled) || msPointer, ie3d = ie && ('transition' in doc.style), webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, gecko3d = 'MozPerspective' in doc.style, opera12 = 'OTransition' in doc.style; var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch)); L.Browser = { ie: ie, ielt9: ie && !document.addEventListener, webkit: webkit, gecko: gecko, android: ua.indexOf('android') !== -1, android23: android23, chrome: chrome, safari: !chrome && ua.indexOf('safari') !== -1, ie3d: ie3d, webkit3d: webkit3d, gecko3d: gecko3d, opera12: opera12, any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs, mobile: mobile, mobileWebkit: mobile && webkit, mobileWebkit3d: mobile && webkit3d, mobileOpera: mobile && window.opera, mobileGecko: mobile && gecko, touch: !!touch, msPointer: !!msPointer, pointer: !!pointer, retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1 }; }()); /* * L.Point represents a point with x and y coordinates. */ L.Point = function (x, y, round) { this.x = (round ? Math.round(x) : x); this.y = (round ? Math.round(y) : y); }; L.Point.prototype = { clone: function () { return new L.Point(this.x, this.y); }, // non-destructive, returns a new point add: function (point) { return this.clone()._add(L.point(point)); }, // destructive, used directly for performance in situations where it's safe to modify existing point _add: function (point) { this.x += point.x; this.y += point.y; return this; }, subtract: function (point) { return this.clone()._subtract(L.point(point)); }, _subtract: function (point) { this.x -= point.x; this.y -= point.y; return this; }, divideBy: function (num) { return this.clone()._divideBy(num); }, _divideBy: function (num) { this.x /= num; this.y /= num; return this; }, multiplyBy: function (num) { return this.clone()._multiplyBy(num); }, _multiplyBy: function (num) { this.x *= num; this.y *= num; return this; }, scaleBy: function (point) { return new L.Point(this.x * point.x, this.y * point.y); }, unscaleBy: function (point) { return new L.Point(this.x / point.x, this.y / point.y); }, round: function () { return this.clone()._round(); }, _round: function () { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; }, floor: function () { return this.clone()._floor(); }, _floor: function () { this.x = Math.floor(this.x); this.y = Math.floor(this.y); return this; }, ceil: function () { return this.clone()._ceil(); }, _ceil: function () { this.x = Math.ceil(this.x); this.y = Math.ceil(this.y); return this; }, distanceTo: function (point) { point = L.point(point); var x = point.x - this.x, y = point.y - this.y; return Math.sqrt(x * x + y * y); }, equals: function (point) { point = L.point(point); return point.x === this.x && point.y === this.y; }, contains: function (point) { point = L.point(point); return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <= Math.abs(this.y); }, toString: function () { return 'Point(' + L.Util.formatNum(this.x) + ', ' + L.Util.formatNum(this.y) + ')'; } }; L.point = function (x, y, round) { if (x instanceof L.Point) { return x; } if (L.Util.isArray(x)) { return new L.Point(x[0], x[1]); } if (x === undefined || x === null) { return x; } return new L.Point(x, y, round); }; /* * L.Bounds represents a rectangular area on the screen in pixel coordinates. */ L.Bounds = function (a, b) { // (Point, Point) or Point[] if (!a) { return; } var points = b ? [a, b] : a; for (var i = 0, len = points.length; i < len; i++) { this.extend(points[i]); } }; L.Bounds.prototype = { // extend the bounds to contain the given point extend: function (point) { // (Point) point = L.point(point); if (!this.min && !this.max) { this.min = point.clone(); this.max = point.clone(); } else { this.min.x = Math.min(point.x, this.min.x); this.max.x = Math.max(point.x, this.max.x); this.min.y = Math.min(point.y, this.min.y); this.max.y = Math.max(point.y, this.max.y); } return this; }, getCenter: function (round) { // (Boolean) -> Point return new L.Point( (this.min.x + this.max.x) / 2, (this.min.y + this.max.y) / 2, round); }, getBottomLeft: function () { // -> Point return new L.Point(this.min.x, this.max.y); }, getTopRight: function () { // -> Point return new L.Point(this.max.x, this.min.y); }, getSize: function () { return this.max.subtract(this.min); }, contains: function (obj) { // (Bounds) or (Point) -> Boolean var min, max; if (typeof obj[0] === 'number' || obj instanceof L.Point) { obj = L.point(obj); } else { obj = L.bounds(obj); } if (obj instanceof L.Bounds) { min = obj.min; max = obj.max; } else { min = max = obj; } return (min.x >= this.min.x) && (max.x <= this.max.x) && (min.y >= this.min.y) && (max.y <= this.max.y); }, intersects: function (bounds) { // (Bounds) -> Boolean bounds = L.bounds(bounds); var min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xIntersects = (max2.x >= min.x) && (min2.x <= max.x), yIntersects = (max2.y >= min.y) && (min2.y <= max.y); return xIntersects && yIntersects; }, overlaps: function (bounds) { // (Bounds) -> Boolean bounds = L.bounds(bounds); var min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xOverlaps = (max2.x > min.x) && (min2.x < max.x), yOverlaps = (max2.y > min.y) && (min2.y < max.y); return xOverlaps && yOverlaps; }, isValid: function () { return !!(this.min && this.max); } }; L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) if (!a || a instanceof L.Bounds) { return a; } return new L.Bounds(a, b); }; /* * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix. */ L.Transformation = function (a, b, c, d) { this._a = a; this._b = b; this._c = c; this._d = d; }; L.Transformation.prototype = { transform: function (point, scale) { // (Point, Number) -> Point return this._transform(point.clone(), scale); }, // destructive transform (faster) _transform: function (point, scale) { scale = scale || 1; point.x = scale * (this._a * point.x + this._b); point.y = scale * (this._c * point.y + this._d); return point; }, untransform: function (point, scale) { scale = scale || 1; return new L.Point( (point.x / scale - this._b) / this._a, (point.y / scale - this._d) / this._c); } }; /* * L.DomUtil contains various utility functions for working with DOM. */ L.DomUtil = { get: function (id) { return typeof id === 'string' ? document.getElementById(id) : id; }, getStyle: function (el, style) { var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); if ((!value || value === 'auto') && document.defaultView) { var css = document.defaultView.getComputedStyle(el, null); value = css ? css[style] : null; } return value === 'auto' ? null : value; }, create: function (tagName, className, container) { var el = document.createElement(tagName); el.className = className; if (container) { container.appendChild(el); } return el; }, remove: function (el) { var parent = el.parentNode; if (parent) { parent.removeChild(el); } }, empty: function (el) { while (el.firstChild) { el.removeChild(el.firstChild); } }, toFront: function (el) { el.parentNode.appendChild(el); }, toBack: function (el) { var parent = el.parentNode; parent.insertBefore(el, parent.firstChild); }, hasClass: function (el, name) { if (el.classList !== undefined) { return el.classList.contains(name); } var className = L.DomUtil.getClass(el); return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); }, addClass: function (el, name) { if (el.classList !== undefined) { var classes = L.Util.splitWords(name); for (var i = 0, len = classes.length; i < len; i++) { el.classList.add(classes[i]); } } else if (!L.DomUtil.hasClass(el, name)) { var className = L.DomUtil.getClass(el); L.DomUtil.setClass(el, (className ? className + ' ' : '') + name); } }, removeClass: function (el, name) { if (el.classList !== undefined) { el.classList.remove(name); } else { L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' '))); } }, setClass: function (el, name) { if (el.className.baseVal === undefined) { el.className = name; } else { // in case of SVG element el.className.baseVal = name; } }, getClass: function (el) { return el.className.baseVal === undefined ? el.className : el.className.baseVal; }, setOpacity: function (el, value) { if ('opacity' in el.style) { el.style.opacity = value; } else if ('filter' in el.style) { L.DomUtil._setOpacityIE(el, value); } }, _setOpacityIE: function (el, value) { var filter = false, filterName = 'DXImageTransform.Microsoft.Alpha'; // 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; } } value = Math.round(value * 100); if (filter) { filter.Enabled = (value !== 100); filter.Opacity = value; } else { el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; } }, testProp: function (props) { var style = document.documentElement.style; for (var i = 0; i < props.length; i++) { if (props[i] in style) { return props[i]; } } return false; }, setTransform: function (el, offset, scale) { var pos = offset || new L.Point(0, 0); el.style[L.DomUtil.TRANSFORM] = (L.Browser.ie3d ? 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + (scale ? ' scale(' + scale + ')' : ''); }, setPosition: function (el, point) { // (HTMLElement, Point[, Boolean]) /*eslint-disable */ el._leaflet_pos = point; /*eslint-enable */ if (L.Browser.any3d) { L.DomUtil.setTransform(el, point); } else { el.style.left = point.x + 'px'; el.style.top = point.y + 'px'; } }, getPosition: function (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; } }; (function () { // prefix style property names L.DomUtil.TRANSFORM = L.DomUtil.testProp( ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); // 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 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp( ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); L.DomUtil.TRANSITION_END = transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend'; if ('onselectstart' in document) { L.DomUtil.disableTextSelection = function () { L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); }; L.DomUtil.enableTextSelection = function () { L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); }; } else { var userSelectProperty = L.DomUtil.testProp( ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); L.DomUtil.disableTextSelection = function () { if (userSelectProperty) { var style = document.documentElement.style; this._userSelect = style[userSelectProperty]; style[userSelectProperty] = 'none'; } }; L.DomUtil.enableTextSelection = function () { if (userSelectProperty) { document.documentElement.style[userSelectProperty] = this._userSelect; delete this._userSelect; } }; } L.DomUtil.disableImageDrag = function () { L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); }; L.DomUtil.enableImageDrag = function () { L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); }; L.DomUtil.preventOutline = function (element) { while (element.tabIndex === -1) { element = element.parentNode; } if (!element || !element.style) { return; } L.DomUtil.restoreOutline(); this._outlineElement = element; this._outlineStyle = element.style.outline; element.style.outline = 'none'; L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this); }; L.DomUtil.restoreOutline = function () { if (!this._outlineElement) { return; } this._outlineElement.style.outline = this._outlineStyle; delete this._outlineElement; delete this._outlineStyle; L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); }; })(); /* * L.LatLng represents a geographical point with latitude and longitude coordinates. */ L.LatLng = function (lat, lng, alt) { if (isNaN(lat) || isNaN(lng)) { throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); } this.lat = +lat; this.lng = +lng; if (alt !== undefined) { this.alt = +alt; } }; L.LatLng.prototype = { equals: function (obj, maxMargin) { if (!obj) { return false; } obj = L.latLng(obj); var margin = Math.max( Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng)); return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); }, toString: function (precision) { return 'LatLng(' + L.Util.formatNum(this.lat, precision) + ', ' + L.Util.formatNum(this.lng, precision) + ')'; }, distanceTo: function (other) { return L.CRS.Earth.distance(this, L.latLng(other)); }, wrap: function () { return L.CRS.Earth.wrapLatLng(this); }, toBounds: function (sizeInMeters) { var latAccuracy = 180 * sizeInMeters / 40075017, lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); return L.latLngBounds( [this.lat - latAccuracy, this.lng - lngAccuracy], [this.lat + latAccuracy, this.lng + lngAccuracy]); }, clone: function () { return new L.LatLng(this.lat, this.lng, this.alt); } }; // constructs LatLng with different signatures // (LatLng) or ([Number, Number]) or (Number, Number) or (Object) L.latLng = function (a, b, c) { if (a instanceof L.LatLng) { return a; } if (L.Util.isArray(a) && typeof a[0] !== 'object') { if (a.length === 3) { return new L.LatLng(a[0], a[1], a[2]); } if (a.length === 2) { return new L.LatLng(a[0], a[1]); } return null; } if (a === undefined || a === null) { return a; } if (typeof a === 'object' && 'lat' in a) { return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); } if (b === undefined) { return null; } return new L.LatLng(a, b, c); }; /* * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. */ L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) if (!southWest) { return; } var latlngs = northEast ? [southWest, northEast] : southWest; for (var i = 0, len = latlngs.length; i < len; i++) { this.extend(latlngs[i]); } }; L.LatLngBounds.prototype = { // extend the bounds to contain the given point or bounds extend: function (obj) { // (LatLng) or (LatLngBounds) var sw = this._southWest, ne = this._northEast, sw2, ne2; if (obj instanceof L.LatLng) { sw2 = obj; ne2 = obj; } else if (obj instanceof L.LatLngBounds) { sw2 = obj._southWest; ne2 = obj._northEast; if (!sw2 || !ne2) { return this; } } else { return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this; } if (!sw && !ne) { this._southWest = new L.LatLng(sw2.lat, sw2.lng); this._northEast = new L.LatLng(ne2.lat, ne2.lng); } else { sw.lat = Math.min(sw2.lat, sw.lat); sw.lng = Math.min(sw2.lng, sw.lng); ne.lat = Math.max(ne2.lat, ne.lat); ne.lng = Math.max(ne2.lng, ne.lng); } return this; }, // extend the bounds by a percentage pad: function (bufferRatio) { // (Number) -> LatLngBounds var sw = this._southWest, ne = this._northEast, heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; return new L.LatLngBounds( new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); }, getCenter: function () { // -> LatLng return new L.LatLng( (this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lng + this._northEast.lng) / 2); }, getSouthWest: function () { return this._southWest; }, getNorthEast: function () { return this._northEast; }, getNorthWest: function () { return new L.LatLng(this.getNorth(), this.getWest()); }, getSouthEast: function () { return new L.LatLng(this.getSouth(), this.getEast()); }, getWest: function () { return this._southWest.lng; }, getSouth: function () { return this._southWest.lat; }, getEast: function () { return this._northEast.lng; }, getNorth: function () { return this._northEast.lat; }, contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { obj = L.latLng(obj); } else { obj = L.latLngBounds(obj); } var sw = this._southWest, ne = this._northEast, sw2, ne2; if (obj instanceof L.LatLngBounds) { sw2 = obj.getSouthWest(); ne2 = obj.getNorthEast(); } else { sw2 = ne2 = obj; } return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); }, intersects: function (bounds) { // (LatLngBounds) -> Boolean bounds = L.latLngBounds(bounds); var sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); return latIntersects && lngIntersects; }, overlaps: function (bounds) { // (LatLngBounds) -> Boolean bounds = L.latLngBounds(bounds); var sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); return latOverlaps && lngOverlaps; }, toBBoxString: function () { return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); }, equals: function (bounds) { // (LatLngBounds) if (!bounds) { return false; } bounds = L.latLngBounds(bounds); return this._southWest.equals(bounds.getSouthWest()) && this._northEast.equals(bounds.getNorthEast()); }, isValid: function () { return !!(this._southWest && this._northEast); } }; // TODO International date line? L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) if (!a || a instanceof L.LatLngBounds) { return a; } return new L.LatLngBounds(a, b); }; /* * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. */ L.Projection = {}; L.Projection.LonLat = { project: function (latlng) { return new L.Point(latlng.lng, latlng.lat); }, unproject: function (point) { return new L.LatLng(point.y, point.x); }, bounds: L.bounds([-180, -90], [180, 90]) }; /* * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. */ L.Projection.SphericalMercator = { R: 6378137, MAX_LATITUDE: 85.0511287798, project: function (latlng) { var d = Math.PI / 180, max = this.MAX_LATITUDE, lat = Math.max(Math.min(max, latlng.lat), -max), sin = Math.sin(lat * d); return new L.Point( this.R * latlng.lng * d, this.R * Math.log((1 + sin) / (1 - sin)) / 2); }, unproject: function (point) { var d = 180 / Math.PI; return new L.LatLng( (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, point.x * d / this.R); }, bounds: (function () { var d = 6378137 * Math.PI; return L.bounds([-d, -d], [d, d]); })() }; /* * L.CRS is the base object for all defined CRS (Coordinate Reference Systems) in Leaflet. */ L.CRS = { // converts geo coords to pixel ones latLngToPoint: function (latlng, zoom) { var projectedPoint = this.projection.project(latlng), scale = this.scale(zoom); return this.transformation._transform(projectedPoint, scale); }, // converts pixel coords to geo coords pointToLatLng: function (point, zoom) { var scale = this.scale(zoom), untransformedPoint = this.transformation.untransform(point, scale); return this.projection.unproject(untransformedPoint); }, // converts geo coords to projection-specific coords (e.g. in meters) project: function (latlng) { return this.projection.project(latlng); }, // converts projected coords to geo coords unproject: function (point) { return this.projection.unproject(point); }, // defines how the world scales with zoom scale: function (zoom) { return 256 * Math.pow(2, zoom); }, zoom: function (scale) { return Math.log(scale / 256) / Math.LN2; }, // returns the bounds of the world in projected coords if applicable getProjectedBounds: function (zoom) { if (this.infinite) { return null; } var b = this.projection.bounds, s = this.scale(zoom), min = this.transformation.transform(b.min, s), max = this.transformation.transform(b.max, s); return L.bounds(min, max); }, // whether a coordinate axis wraps in a given range (e.g. longitude from -180 to 180); depends on CRS // wrapLng: [min, max], // wrapLat: [min, max], // if true, the coordinate space will be unbounded (infinite in all directions) // infinite: false, // wraps geo coords in certain ranges if applicable wrapLatLng: function (latlng) { var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, alt = latlng.alt; return L.latLng(lat, lng, alt); } }; /* * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps. */ L.CRS.Simple = L.extend({}, L.CRS, { projection: L.Projection.LonLat, transformation: new L.Transformation(1, 0, -1, 0), scale: function (zoom) { return Math.pow(2, zoom); }, zoom: function (scale) { return Math.log(scale) / Math.LN2; }, distance: function (latlng1, latlng2) { var dx = latlng2.lng - latlng1.lng, dy = latlng2.lat - latlng1.lat; return Math.sqrt(dx * dx + dy * dy); }, infinite: true }); /* * L.CRS.Earth is the base class for all CRS representing Earth. */ L.CRS.Earth = L.extend({}, L.CRS, { wrapLng: [-180, 180], R: 6378137, // distance between two geographical points using spherical law of cosines approximation distance: function (latlng1, latlng2) { 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)); } }); /* * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping and is used by Leaflet by default. */ L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, { code: 'EPSG:3857', projection: L.Projection.SphericalMercator, transformation: (function () { var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R); return new L.Transformation(scale, 0.5, -scale, 0.5); }()) }); L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { code: 'EPSG:900913' }); /* * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists. */ L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, { code: 'EPSG:4326', projection: L.Projection.LonLat, transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5) }); /* * L.Map is the central class of the API - it is used to create a map. */ L.Map = L.Evented.extend({ options: { crs: L.CRS.EPSG3857, /* center: LatLng, zoom: Number, layers: Array, */ fadeAnimation: true, trackResize: true, markerZoomAnimation: true, maxBoundsViscosity: 0.0, transform3DLimit: 8388608 // Precision limit of a 32-bit float }, initialize: function (id, options) { // (HTMLElement or String, Object) options = L.setOptions(this, options); this._initContainer(id); this._initLayout(); // hack for https://github.com/Leaflet/Leaflet/issues/1980 this._onResize = L.bind(this._onResize, this); this._initEvents(); if (options.maxBounds) { this.setMaxBounds(options.maxBounds); } if (options.zoom !== undefined) { this._zoom = this._limitZoom(options.zoom); } if (options.center && options.zoom !== undefined) { this.setView(L.latLng(options.center), options.zoom, {reset: true}); } this._handlers = []; this._layers = {}; this._zoomBoundLayers = {}; this._sizeChanged = true; this.callInitHooks(); this._addLayers(this.options.layers); }, // public methods that modify map state // replaced by animation-powered implementation in Map.PanAnimation.js setView: function (center, zoom) { zoom = zoom === undefined ? this.getZoom() : zoom; this._resetView(L.latLng(center), zoom); return this; }, setZoom: function (zoom, options) { if (!this._loaded) { this._zoom = zoom; return this; } return this.setView(this.getCenter(), zoom, {zoom: options}); }, zoomIn: function (delta, options) { return this.setZoom(this._zoom + (delta || 1), options); }, zoomOut: function (delta, options) { return this.setZoom(this._zoom - (delta || 1), options); }, setZoomAround: function (latlng, zoom, options) { var scale = this.getZoomScale(zoom), viewHalf = this.getSize().divideBy(2), containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); return this.setView(newCenter, zoom, {zoom: options}); }, _getBoundsCenterZoom: function (bounds, options) { options = options || {}; bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); zoom = options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom; var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), swPoint = this.project(bounds.getSouthWest(), zoom), nePoint = this.project(bounds.getNorthEast(), zoom), center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); return { center: center, zoom: zoom }; }, fitBounds: function (bounds, options) { var target = this._getBoundsCenterZoom(bounds, options); return this.setView(target.center, target.zoom, options); }, fitWorld: function (options) { return this.fitBounds([[-90, -180], [90, 180]], options); }, panTo: function (center, options) { // (LatLng) return this.setView(center, this._zoom, {pan: options}); }, panBy: function (offset) { // (Point) // replaced with animated panBy in Map.PanAnimation.js this.fire('movestart'); this._rawPanBy(L.point(offset)); this.fire('move'); return this.fire('moveend'); }, setMaxBounds: function (bounds) { bounds = L.latLngBounds(bounds); if (!bounds) { return this.off('moveend', this._panInsideMaxBounds); } else if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); } this.options.maxBounds = bounds; if (this._loaded) { this._panInsideMaxBounds(); } return this.on('moveend', this._panInsideMaxBounds); }, setMinZoom: function (zoom) { this.options.minZoom = zoom; if (this._loaded && this.getZoom() < this.options.minZoom) { return this.setZoom(zoom); } return this; }, setMaxZoom: function (zoom) { this.options.maxZoom = zoom; if (this._loaded && (this.getZoom() > this.options.maxZoom)) { return this.setZoom(zoom); } return this; }, panInsideBounds: function (bounds, options) { this._enforcingBounds = true; var center = this.getCenter(), newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds)); if (center.equals(newCenter)) { return this; } this.panTo(newCenter, options); this._enforcingBounds = false; return this; }, invalidateSize: function (options) { if (!this._loaded) { return this; } options = L.extend({ animate: false, pan: true }, options === true ? {animate: true} : options); var oldSize = this.getSize(); this._sizeChanged = true; this._lastCenter = null; var newSize = this.getSize(), oldCenter = oldSize.divideBy(2).round(), newCenter = newSize.divideBy(2).round(), offset = oldCenter.subtract(newCenter); if (!offset.x && !offset.y) { return this; } if (options.animate && options.pan) { this.panBy(offset); } else { if (options.pan) { this._rawPanBy(offset); } this.fire('move'); if (options.debounceMoveend) { clearTimeout(this._sizeTimer); this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); } else { this.fire('moveend'); } } return this.fire('resize', { oldSize: oldSize, newSize: newSize }); }, stop: function () { L.Util.cancelAnimFrame(this._flyToFrame); if (this._panAnim) { this._panAnim.stop(); } return this; }, // TODO handler.addTo addHandler: function (name, HandlerClass) { if (!HandlerClass) { return this; } var handler = this[name] = new HandlerClass(this); this._handlers.push(handler); if (this.options[name]) { handler.enable(); } return this; }, remove: function () { this._initEvents(true); try { // throws error in IE6-8 delete this._container._leaflet; } catch (e) { this._container._leaflet = undefined; } L.DomUtil.remove(this._mapPane); if (this._clearControlPos) { this._clearControlPos(); } this._clearHandlers(); if (this._loaded) { this.fire('unload'); } for (var i in this._layers) { this._layers[i].remove(); } return this; }, createPane: function (name, container) { var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), pane = L.DomUtil.create('div', className, container || this._mapPane); if (name) { this._panes[name] = pane; } return pane; }, // public methods for getting map state getCenter: function () { // (Boolean) -> LatLng this._checkIfLoaded(); if (this._lastCenter && !this._moved()) { return this._lastCenter; } return this.layerPointToLatLng(this._getCenterLayerPoint()); }, getZoom: function () { return this._zoom; }, getBounds: function () { var bounds = this.getPixelBounds(), sw = this.unproject(bounds.getBottomLeft()), ne = this.unproject(bounds.getTopRight()); return new L.LatLngBounds(sw, ne); }, getMinZoom: function () { return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; }, getMaxZoom: function () { return this.options.maxZoom === undefined ? (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : this.options.maxZoom; }, getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number bounds = L.latLngBounds(bounds); var zoom = this.getMinZoom() - (inside ? 1 : 0), maxZoom = this.getMaxZoom(), size = this.getSize(), nw = bounds.getNorthWest(), se = bounds.getSouthEast(), zoomNotFound = true, boundsSize; padding = L.point(padding || [0, 0]); do { zoom++; boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding).floor(); zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y; } while (zoomNotFound && zoom <= maxZoom); if (zoomNotFound && inside) { return null; } return inside ? zoom : zoom - 1; }, getSize: function () { if (!this._size || this._sizeChanged) { this._size = new L.Point( this._container.clientWidth, this._container.clientHeight); this._sizeChanged = false; } return this._size.clone(); }, getPixelBounds: function (center, zoom) { var topLeftPoint = this._getTopLeftPoint(center, zoom); return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); }, getPixelOrigin: function () { this._checkIfLoaded(); return this._pixelOrigin; }, getPixelWorldBounds: function (zoom) { return th