UNPKG

leaflet-draw

Version:
2,053 lines (1,543 loc) 215 kB
/* Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com (c) 2010-2013, Vladimir Agafonkin (c) 2010-2011, CloudMade */ (function (window, document, undefined) { var oldL = window.L, L = {}; L.version = '0.7.2'; // 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 L.noConflict = function () { window.L = oldL; return this; }; window.L = L; /* * L.Util contains various utility functions used throughout Leaflet code. */ L.Util = { extend: function (dest) { // (Object[, Object, ...]) -> var sources = Array.prototype.slice.call(arguments, 1), i, j, len, src; for (j = 0, len = sources.length; j < len; j++) { src = sources[j] || {}; for (i in src) { if (src.hasOwnProperty(i)) { dest[i] = src[i]; } } } return dest; }, bind: function (fn, obj) { // (Function, Object) -> Function var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null; return function () { return fn.apply(obj, args || arguments); }; }, stamp: (function () { var lastId = 0, key = '_leaflet_id'; return function (obj) { obj[key] = obj[key] || ++lastId; return obj[key]; }; }()), invokeEach: function (obj, method, context) { var i, args; if (typeof obj === 'object') { args = Array.prototype.slice.call(arguments, 3); for (i in obj) { method.apply(context, [i, obj[i]].concat(args)); } return true; } return false; }, limitExecByInterval: function (fn, time, context) { var lock, execOnUnlock; return function wrapperFn() { var args = arguments; if (lock) { execOnUnlock = true; return; } lock = true; setTimeout(function () { lock = false; if (execOnUnlock) { wrapperFn.apply(context, args); execOnUnlock = false; } }, time); fn.apply(context, args); }; }, falseFn: function () { return false; }, formatNum: function (num, digits) { var pow = Math.pow(10, digits || 5); return Math.round(num * pow) / pow; }, trim: function (str) { return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); }, splitWords: function (str) { return L.Util.trim(str).split(/\s+/); }, setOptions: function (obj, options) { obj.options = L.extend({}, obj.options, options); return obj.options; }, 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('&'); }, template: function (str, data) { return str.replace(/\{ *([\w_]+) *\}/g, 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; }); }, isArray: Array.isArray || function (obj) { return (Object.prototype.toString.call(obj) === '[object Array]'); }, emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' }; (function () { // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ function getPrefixed(name) { var i, fn, prefixes = ['webkit', 'moz', 'o', 'ms']; for (i = 0; i < prefixes.length && !fn; i++) { fn = window[prefixes[i] + name]; } return fn; } var lastTime = 0; 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; var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; L.Util.requestAnimFrame = function (fn, context, immediate, element) { fn = L.bind(fn, context); if (immediate && requestFn === timeoutDefer) { fn(); } else { return requestFn.call(window, fn, element); } }; 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 if (this._initHooks) { this.callInitHooks(); } }; // instantiate class without calling constructor var F = function () {}; F.prototype = this.prototype; var proto = new F(); 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 (props.options && proto.options) { props.options = L.extend({}, proto.options, props.options); } // mix given properties into the prototype L.extend(proto, props); proto._initHooks = []; var parent = this; // jshint camelcase: false NewClass.__super__ = parent.prototype; // add method for calling all hooks proto.callInitHooks = function () { if (this._initHooksCalled) { return; } if (parent.prototype.callInitHooks) { parent.prototype.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.Mixin.Events is used to add custom events functionality to Leaflet classes. */ var eventsKey = '_leaflet_events'; L.Mixin = {}; L.Mixin.Events = { addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object]) // types can be a map of types/handlers if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; } var events = this[eventsKey] = this[eventsKey] || {}, contextId = context && context !== this && L.stamp(context), i, len, event, type, indexKey, indexLenKey, typeIndex; // types can be a string of space-separated words types = L.Util.splitWords(types); for (i = 0, len = types.length; i < len; i++) { event = { action: fn, context: context || this }; type = types[i]; if (contextId) { // store listeners of a particular context in a separate hash (if it has an id) // gives a major performance boost when removing thousands of map layers indexKey = type + '_idx'; indexLenKey = indexKey + '_len'; typeIndex = events[indexKey] = events[indexKey] || {}; if (!typeIndex[contextId]) { typeIndex[contextId] = []; // keep track of the number of keys in the index to quickly check if it's empty events[indexLenKey] = (events[indexLenKey] || 0) + 1; } typeIndex[contextId].push(event); } else { events[type] = events[type] || []; events[type].push(event); } } return this; }, hasEventListeners: function (type) { // (String) -> Boolean var events = this[eventsKey]; return !!events && ((type in events && events[type].length > 0) || (type + '_idx' in events && events[type + '_idx_len'] > 0)); }, removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object]) if (!this[eventsKey]) { return this; } if (!types) { return this.clearAllEventListeners(); } if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; } var events = this[eventsKey], contextId = context && context !== this && L.stamp(context), i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed; types = L.Util.splitWords(types); for (i = 0, len = types.length; i < len; i++) { type = types[i]; indexKey = type + '_idx'; indexLenKey = indexKey + '_len'; typeIndex = events[indexKey]; if (!fn) { // clear all listeners for a type if function isn't specified delete events[type]; delete events[indexKey]; delete events[indexLenKey]; } else { listeners = contextId && typeIndex ? typeIndex[contextId] : events[type]; if (listeners) { for (j = listeners.length - 1; j >= 0; j--) { if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) { removed = listeners.splice(j, 1); // set the old action to a no-op, because it is possible // that the listener is being iterated over as part of a dispatch removed[0].action = L.Util.falseFn; } } if (context && typeIndex && (listeners.length === 0)) { delete typeIndex[contextId]; events[indexLenKey]--; } } } } return this; }, clearAllEventListeners: function () { delete this[eventsKey]; return this; }, fireEvent: function (type, data) { // (String[, Object]) if (!this.hasEventListeners(type)) { return this; } var event = L.Util.extend({}, data, { type: type, target: this }); var events = this[eventsKey], listeners, i, len, typeIndex, contextId; 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].action.call(listeners[i].context, event); } } // fire event for the context-indexed listeners as well typeIndex = events[type + '_idx']; for (contextId in typeIndex) { listeners = typeIndex[contextId].slice(); if (listeners) { for (i = 0, len = listeners.length; i < len; i++) { listeners[i].action.call(listeners[i].context, event); } } } return this; }, addOneTimeEventListener: function (types, fn, context) { if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; } var handler = L.bind(function () { this .removeEventListener(types, fn, context) .removeEventListener(types, handler, context); }, this); return this .addEventListener(types, fn, context) .addEventListener(types, handler, context); } }; L.Mixin.Events.on = L.Mixin.Events.addEventListener; L.Mixin.Events.off = L.Mixin.Events.removeEventListener; L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener; L.Mixin.Events.fire = L.Mixin.Events.fireEvent; /* * L.Browser handles different browser and feature detections for internal Leaflet use. */ (function () { var ie = 'ActiveXObject' in window, ielt9 = ie && !document.addEventListener, // terrible browser detection to work around Safari / iOS / Android browser bugs ua = navigator.userAgent.toLowerCase(), webkit = ua.indexOf('webkit') !== -1, chrome = ua.indexOf('chrome') !== -1, phantomjs = ua.indexOf('phantom') !== -1, android = ua.indexOf('android') !== -1, android23 = ua.search('android [23]') !== -1, gecko = ua.indexOf('gecko') !== -1, mobile = typeof orientation !== undefined + '', msPointer = window.navigator && window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints && !window.PointerEvent, pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) || msPointer, retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && window.matchMedia('(min-resolution:144dpi)').matches), doc = document.documentElement, ie3d = ie && ('transition' in doc.style), webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, gecko3d = 'MozPerspective' in doc.style, opera3d = 'OTransition' in doc.style, any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs; // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch. // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151 var touch = !window.L_NO_TOUCH && !phantomjs && (function () { var startName = 'ontouchstart'; // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc. if (pointer || (startName in doc)) { return true; } // Firefox/Gecko var div = document.createElement('div'), supported = false; if (!div.setAttribute) { return false; } div.setAttribute(startName, 'return;'); if (typeof div[startName] === 'function') { supported = true; } div.removeAttribute(startName); div = null; return supported; }()); L.Browser = { ie: ie, ielt9: ielt9, webkit: webkit, gecko: gecko && !webkit && !window.opera && !ie, android: android, android23: android23, chrome: chrome, ie3d: ie3d, webkit3d: webkit3d, gecko3d: gecko3d, opera3d: opera3d, any3d: any3d, mobile: mobile, mobileWebkit: mobile && webkit, mobileWebkit3d: mobile && webkit3d, mobileOpera: mobile && window.opera, touch: touch, msPointer: msPointer, pointer: pointer, retina: retina }; }()); /* * L.Point represents a point with x and y coordinates. */ L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ 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; }, 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; }, 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; }, 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]; if (!value && el.currentStyle) { value = 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; }, getViewportOffset: function (element) { var top = 0, left = 0, el = element, docBody = document.body, docEl = document.documentElement, pos; do { top += el.offsetTop || 0; left += el.offsetLeft || 0; //add borders top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0; left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0; pos = L.DomUtil.getStyle(el, 'position'); if (el.offsetParent === docBody && pos === 'absolute') { break; } if (pos === 'fixed') { top += docBody.scrollTop || docEl.scrollTop || 0; left += docBody.scrollLeft || docEl.scrollLeft || 0; break; } if (pos === 'relative' && !el.offsetLeft) { var width = L.DomUtil.getStyle(el, 'width'), maxWidth = L.DomUtil.getStyle(el, 'max-width'), r = el.getBoundingClientRect(); if (width !== 'none' || maxWidth !== 'none') { left += r.left + el.clientLeft; } //calculate full y offset since we're breaking out of the loop top += r.top + (docBody.scrollTop || docEl.scrollTop || 0); break; } el = el.offsetParent; } while (el); el = element; do { if (el === docBody) { break; } top -= el.scrollTop || 0; left -= el.scrollLeft || 0; el = el.parentNode; } while (el); return new L.Point(left, top); }, documentIsLtr: function () { if (!L.DomUtil._docIsLtrCached) { L.DomUtil._docIsLtrCached = true; L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr'; } return L.DomUtil._docIsLtr; }, create: function (tagName, className, container) { var el = document.createElement(tagName); el.className = className; if (container) { container.appendChild(el); } return el; }, 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) { 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; }, getTranslateString: function (point) { // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care // (same speed either way), Opera 12 doesn't support translate3d var is3d = L.Browser.webkit3d, open = 'translate' + (is3d ? '3d' : '') + '(', close = (is3d ? ',0' : '') + ')'; return open + point.x + 'px,' + point.y + 'px' + close; }, getScaleString: function (scale, origin) { var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))), scaleStr = ' scale(' + scale + ') '; return preTranslateStr + scaleStr; }, setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean]) // jshint camelcase: false el._leaflet_pos = point; if (!disable3D && L.Browser.any3d) { el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(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 // jshint camelcase: false return el._leaflet_pos; } }; // 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 L.DomUtil.TRANSITION = L.DomUtil.testProp( ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); L.DomUtil.TRANSITION_END = L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? L.DomUtil.TRANSITION + 'End' : 'transitionend'; (function () { if ('onselectstart' in document) { L.extend(L.DomUtil, { disableTextSelection: function () { L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); }, enableTextSelection: function () { L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); } }); } else { var userSelectProperty = L.DomUtil.testProp( ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); L.extend(L.DomUtil, { disableTextSelection: function () { if (userSelectProperty) { var style = document.documentElement.style; this._userSelect = style[userSelectProperty]; style[userSelectProperty] = 'none'; } }, enableTextSelection: function () { if (userSelectProperty) { document.documentElement.style[userSelectProperty] = this._userSelect; delete this._userSelect; } } }); } L.extend(L.DomUtil, { disableImageDrag: function () { L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); }, enableImageDrag: function () { L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); } }); })(); /* * L.LatLng represents a geographical point with latitude and longitude coordinates. */ L.LatLng = function (lat, lng, alt) { // (Number, Number, Number) lat = parseFloat(lat); lng = parseFloat(lng); if (isNaN(lat) || isNaN(lng)) { throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); } this.lat = lat; this.lng = lng; if (alt !== undefined) { this.alt = parseFloat(alt); } }; L.extend(L.LatLng, { DEG_TO_RAD: Math.PI / 180, RAD_TO_DEG: 180 / Math.PI, MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check }); L.LatLng.prototype = { equals: function (obj) { // (LatLng) -> Boolean 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 <= L.LatLng.MAX_MARGIN; }, toString: function (precision) { // (Number) -> String return 'LatLng(' + L.Util.formatNum(this.lat, precision) + ', ' + L.Util.formatNum(this.lng, precision) + ')'; }, // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula // TODO move to projection code, LatLng shouldn't know about Earth distanceTo: function (other) { // (LatLng) -> Number other = L.latLng(other); var R = 6378137, // earth radius in meters d2r = L.LatLng.DEG_TO_RAD, dLat = (other.lat - this.lat) * d2r, dLon = (other.lng - this.lng) * d2r, lat1 = this.lat * d2r, lat2 = other.lat * d2r, sin1 = Math.sin(dLat / 2), sin2 = Math.sin(dLon / 2); var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); }, wrap: function (a, b) { // (Number, Number) -> LatLng var lng = this.lng; a = a || -180; b = b || 180; lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a); return new L.LatLng(this.lat, lng); } }; L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number) if (a instanceof L.LatLng) { return a; } if (L.Util.isArray(a)) { if (typeof a[0] === 'number' || typeof a[0] === 'string') { return new L.LatLng(a[0], a[1], a[2]); } else { 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); } if (b === undefined) { return null; } return new L.LatLng(a, b); }; /* * 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) if (!obj) { return this; } var latLng = L.latLng(obj); if (latLng !== null) { obj = latLng; } else { obj = L.latLngBounds(obj); } if (obj instanceof L.LatLng) { if (!this._southWest && !this._northEast) { this._southWest = new L.LatLng(obj.lat, obj.lng); this._northEast = new L.LatLng(obj.lat, obj.lng); } else { this._southWest.lat = Math.min(obj.lat, this._southWest.lat); this._southWest.lng = Math.min(obj.lng, this._southWest.lng); this._northEast.lat = Math.max(obj.lat, this._northEast.lat); this._northEast.lng = Math.max(obj.lng, this._northEast.lng); } } else if (obj instanceof L.LatLngBounds) { this.extend(obj._southWest); this.extend(obj._northEast); } 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) 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; }, 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); }; /* * L.Projection contains various geographical projections used by CRS classes. */ L.Projection = {}; /* * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. */ L.Projection.SphericalMercator = { MAX_LATITUDE: 85.0511287798, project: function (latlng) { // (LatLng) -> Point var d = L.LatLng.DEG_TO_RAD, max = this.MAX_LATITUDE, lat = Math.max(Math.min(max, latlng.lat), -max), x = latlng.lng * d, y = lat * d; y = Math.log(Math.tan((Math.PI / 4) + (y / 2))); return new L.Point(x, y); }, unproject: function (point) { // (Point, Boolean) -> LatLng var d = L.LatLng.RAD_TO_DEG, lng = point.x * d, lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d; return new L.LatLng(lat, lng); } }; /* * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. */ 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); } }; /* * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet. */ L.CRS = { latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point var projectedPoint = this.projection.project(latlng), scale = this.scale(zoom); return this.transformation._transform(projectedPoint, scale); }, pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng var scale = this.scale(zoom), untransformedPoint = this.transformation.untransform(point, scale); return this.projection.unproject(untransformedPoint); }, project: function (latlng) { return this.projection.project(latlng); }, scale: function (zoom) { return 256 * Math.pow(2, zoom); }, getSize: function (zoom) { var s = this.scale(zoom); return L.point(s, s); } }; /* * 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); } }); /* * 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, { code: 'EPSG:3857', projection: L.Projection.SphericalMercator, transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5), project: function (latlng) { // (LatLng) -> Point var projectedPoint = this.projection.project(latlng), earthRadius = 6378137; return projectedPoint.multiplyBy(earthRadius); } }); 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, { code: 'EPSG:4326', projection: L.Projection.LonLat, transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5) }); /* * L.Map is the central class of the API - it is used to create a map. */ L.Map = L.Class.extend({ includes: L.Mixin.Events, options: { crs: L.CRS.EPSG3857, /* center: LatLng, zoom: Number, layers: Array, */ fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, trackResize: true, markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d }, 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.center && options.zoom !== undefined) { this.setView(L.latLng(options.center), options.zoom, {reset: true}); } this._handlers = []; this._layers = {}; this._zoomBoundLayers = {}; this._tileLayersNum = 0; this.callInitHooks(); this._addLayers(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), this._limitZoom(zoom)); return this; }, setZoom: function (zoom, options) { if (!this._loaded) { this._zoom = this._limitZoom(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}); }, fitBounds: 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)), 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); zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom; return this.setView(center, 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); this.options.maxBounds = bounds; if (!bounds) { return this.off('moveend', this._panInsideMaxBounds, this); } if (this._loaded) { this._panInsideMaxBounds(); } return this.on('moveend', this._panInsideMaxBounds, this); }, panInsideBounds: function (bounds, options) { var center = this.getCenter(), newCenter = this._limitCenter(center, this._zoom, bounds); if (center.equals(newCenter)) { return this; } return this.panTo(newCenter, options); }, addLayer: function (layer) { // TODO method is too big, refactor var id = L.stamp(layer); if (this._layers[id]) { return this; } this._layers[id] = layer; // TODO getMaxZoom, getMinZoom in ILayer (instead of options) if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { this._zoomBoundLayers[id] = layer; this._updateZoomLevels(); } // TODO looks ugly, refactor!!! if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { this._tileLayersNum++; this._tileLayersToLoad++; layer.on('load', this._onTileLayerLoad, this); } if (this._loaded) { this._layerAdd(layer); } return this; }, removeLayer: function (layer) { var id = L.stamp(layer); if (!this._layers[id]) { return this; } if (this._loaded) { layer.onRemove(this); } delete this._layers[id]; if (this._loaded) { this.fire('layerremove', {layer: layer}); } if (this._zoomBoundLayers[id]) { delete this._zoomBoundLayers[id]; this._updateZoomLevels(); } // TODO looks ugly, refactor if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { this._tileLayersNum--; this._tileLayersToLoad--; layer.off('load', this._onTileLayerLoad, this); } return this; }, hasLayer: function (layer) { if (!layer) { return false; } return (L.stamp(layer) in this._layers); }, eachLayer: function (method, context) { for (var i in this._layers) { method.call(context, this._layers[i]); } 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._initialCenter = 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 }); }, // 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 () { if (this._loaded) { this.fire('unload'); } this._initEvents('off'); try { // throws error in IE6-8 delete this._container._leaflet; } catch (e) { this._container._leaflet = undefined; } this._clearPanes(); if (this._clearControlPos) { this._clearControlPos(); } this._clearHandlers(); return this; }, // public methods for getting map state getCenter: function () { // (Boolean) -> LatLng this._checkIfLoaded(); if (this._initialCenter && !this._moved()) { return this._initialCenter; } 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 === undefined ? 0 : this._layersMinZoom) : 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); 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 () { var topLeftPoint = this._getTopLeftPoint(); return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); }, getPixelOrigin: function () { this._checkIfLoaded(); return this._initialTopLeftPoint; }, getPanes: function () { return this._panes; }, getContainer: function () { return this._container; }, // TODO replace with universal implementation after refactoring projections getZoomScale: function (toZoom) { var crs = this.options.crs; return crs.scale(toZoom) / crs.scale(this._zoom); }, getScaleZoom: function (scale) { return this._zoom + (Math.log(scale) / Math.LN2); }, // conversion methods project: function (latlng, zoom) { // (LatLng[, Number]) -> Point zoom = zoom === undefined ? this._zoom : zoom; return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); }, unproject: function (point, zoom) { // (Point[, Number]) -> LatLng zoom = zoom === undefined ? this._zoom : zoom; return this.options.crs.pointToLatLng(L.point(point), zoom); }, layerPointToLatLng: function (point) { // (Point) var projectedPoint = L.point(point).add(this.getPixelOrigin()); return this.unproject(projectedPoint); }, latLngToLayerPoint: function (latlng) { // (LatLng) var projectedPoint = this.project(L.latLng(latlng))._round(); return projectedPoint._subtract(this.getPixelOrigin()); }, containerPointToLayerPoint: function (point) { // (Point) return L.point(point).subtract(this._getMapPanePos()); }, layerPointToContainerPoint: function (point) { // (Point) return L.point(point).add(this._getMapPanePos()); }, containerPointToLatLng: function (point) { var layerPoint = this.containerPointToLayerPoint(L.point(point)); return this.layerPointToLatLng(layerPoint); }, latLngToContainerPoint: function (latlng) { return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); }, mouseEventToContainerPoint: function (e) { // (MouseEvent) return L.DomEvent.getMousePosition(e, this._container); }, mouseEventToLayerPoint: function (e) { // (MouseEvent) return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); }, mouseEventToLatLng: function (e) { // (MouseEvent) return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); }, // map initialization methods _initContainer: function (id) { var container = this._container = L.DomUtil.get(id); if (!container) { throw new Error('Map container not found.'); } else if (container._leaflet) { throw new Error('Map container is already initialized.'); } container._leaflet = true; }, _initLayout: function () { var container = this._container; L.DomUtil.addClass(container, 'leaflet-container' + (L.Browser.touch ? ' leaflet-touch' : '') + (L.Browser.retina ? ' leaflet-retina' : '') + (L.Browser.ielt9 ? ' leaflet-oldie' : '') + (this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); var position = L.DomUtil.getStyle(container, 'position'); if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { container.style.position = 'relative'; } this._initPanes(); if (this._initControlPos) { this._initControlPos(); } }, _initPanes: function () { var panes = this._panes = {}; this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); panes.shadowPane = this._createPane('leaflet-shadow-pane'); panes.overlayPane = this._createPane('leaflet-overlay-pane'); panes.markerPane = this._createPane('leaflet-marker-pane'); panes.popupPane = this._createPane('leaflet-popup-pane'); var zoomHide = ' leaflet-zoom-hide'; if (!this.options.marke