/* Leaflet 0.8-dev (06d354e), 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: '0.8-dev' }; 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) { // jshint camelcase: false obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId; return obj._leaflet_id; }, 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 an 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]'); }, // 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(); }; // jshint camelcase: false 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, mobile = typeof orientation !== 'undefined', msPointer = navigator.msPointerEnabled && navigator.msMaxTouchPoints && !window.PointerEvent, pointer = (window.PointerEvent && navigator.pointerEnabled && navigator.maxTouchPoints) || msPointer, 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; 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: (ua.indexOf('gecko') !== -1) && !webkit && !window.opera && !ie, android: ua.indexOf('android') !== -1, android23: android23, chrome: chrome, safari: !chrome && ua.indexOf('safari') !== -1, ie3d: ie3d, webkit3d: webkit3d, gecko3d: gecko3d, opera3d: opera3d, any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs, mobile: mobile, mobileWebkit: mobile && webkit, mobileWebkit3d: mobile && webkit3d, mobileOpera: mobile && window.opera, 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 (/*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; }, 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; }, 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) { 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] = 'translate3d(' + pos.x + 'px,' + pos.y + 'px' + ',0)' + (scale ? ' scale(' + scale + ')' : ''); }, setPosition: function (el, point, no3d) { // (HTMLElement, Point[, Boolean]) // jshint camelcase: false el._leaflet_pos = point; if (L.Browser.any3d && !no3d) { 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 // jshint camelcase: false 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.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]); } }; // 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) 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); }; /* * 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, project: function (latlng) { var d = Math.PI / 180, max = 1 - 1E-15, sin = Math.max(Math.min(Math.sin(latlng.lat * d), max), -max); 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); }, // 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; return L.latLng(lat, lng); } }; /* * 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); }, 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, // distane 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 }, 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), 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)); 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 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); } if (this._loaded) { this._panInsideMaxBounds(); } return this.on('moveend', this._panInsideMaxBounds); }, 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); }, 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 }); }, 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('off'); 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'); } 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._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 || 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 () { var topLeftPoint = this._getTopLeftPoint(); return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); }, getPixelOrigin: function () { this._checkIfLoaded(); return this._pixelOrigin; }, getPixelWorldBounds: function (zoom) { return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); }, getPane: function (pane) { return typeof pane === 'string' ? this._panes[pane] : pane; }, getPanes: function () { return this._panes; }, getContainer: function () { return this._container; }, // TODO replace with universal implementation after refactoring projections getZoomScale: function (toZoom, fromZoom) { var crs = this.options.crs; fromZoom = fromZoom === undefined ? this._zoom : fromZoom; return crs.scale(toZoom) / crs.scale(fromZoom); }, getScaleZoom: function (scale, fromZoom) { fromZoom = fromZoom === undefined ? this._zoom : fromZoom; return fromZoom + (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()); }, wrapLatLng: function (latlng) { return this.options.crs.wrapLatLng(L.latLng(latlng)); }, distance: function (latlng1, latlng2) { return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2)); }, 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; this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d; L.DomUtil.addClass(container, 'leaflet-container' + (L.Browser.touch ? ' leaflet-touch' : '') + (L.Browser.retina ? ' leaflet-retina' : '') + (L.Browser.ielt9 ? ' leaflet-oldie' : '') + (L.Browser.safari ? ' leaflet-safari' : '') + (this._fadeAnimated ? ' 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 = this.createPane('mapPane', this._container); this.createPane('tilePane'); this.createPane('shadowPane'); this.createPane('overlayPane'); this.createPane('markerPane'); this.createPane('popupPane'); if (!this.options.markerZoomAnimation) { L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); } }, // private methods that modify map state _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { var zoomChanged = (this._zoom !== zoom); if (!afterZoomAnim) { this.fire('movestart'); if (zoomChanged) { this.fire('zoomstart'); } } this._zoom = zoom; this._initialCenter = center; if (!preserveMapOffset) { L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); } this._pixelOrigin = this._getNewPixelOrigin(center); var loading = !this._loaded; this._loaded = true; this.fire('viewreset', {hard: !preserveMapOffset}); if (loading) { this.fire('load'); } this.fire('move'); if (zoomChanged || afterZoomAnim) { this.fire('zoomend'); } this.fire('moveend', {hard: !preserveMapOffset}); }, _rawPanBy: function (offset) { L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); }, _getZoomSpan: function () { return this.getMaxZoom() - this.getMinZoom(); }, _panInsideMaxBounds: function () { this.panInsideBounds(this.options.maxBounds); }, _checkIfLoaded: function () { if (!this._loaded) { throw new Error('Set map center and zoom first.'); } }, // map events _initEvents: function (onOff) { if (!L.DomEvent) { return; } onOff = onOff || 'on'; L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup mouseenter mouseleave mousemove contextmenu', this._handleMouseEvent, this); if (this.options.trackResize) { L.DomEvent[onOff](window, 'resize', this._onResize, this); } }, _onResize: function () { L.Util.cancelAnimFrame(this._resizeRequest); this._resizeRequest = L.Util.requestAnimFrame( function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container); }, _handleMouseEvent: function (e) { if (!this._loaded) { return; } this._fireMouseEvent(this, e, e.type === 'mouseenter' ? 'mouseover' : e.type === 'mouseleave' ? 'mouseout' : e.type); }, _fireMouseEvent: function (obj, e, type, propagate, latlng) { type = type || e.type; if (L.DomEvent._skipped(e)) { return; } if (type === 'click') { var draggableObj = obj.options.draggable === true ? obj : this; if (!e._simulated && ((draggableObj.dragging && draggableObj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()))) { L.DomEvent.stopPropagation(e); return; } obj.fire('preclick'); } if (!obj.listens(type, propagate)) { return; } if (type === 'contextmenu') { L.DomEvent.preventDefault(e); } if (type === 'click' || type === 'dblclick' || type === 'contextmenu') { L.DomEvent.stopPropagation(e); } var data = { originalEvent: e, containerPoint: this.mouseEventToContainerPoint(e) }; data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); data.latlng = latlng || this.layerPointToLatLng(data.layerPoint); obj.fire(type, data, propagate); }, _clearHandlers: function () { for (var i = 0, len = this._handlers.length; i < len; i++) { this._handlers[i].disable(); } }, whenReady: function (callback, context) { if (this._loaded) { callback.call(context || this, {target: this}); } else { this.on('load', callback, context); } return this; }, // private methods for getting map state _getMapPanePos: function () { return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0); }, _moved: function () { var pos = this._getMapPanePos(); return pos && !pos.equals([0, 0]); }, _getTopLeftPoint: function () { return this.getPixelOrigin().subtract(this._getMapPanePos()); }, _getNewPixelOrigin: function (center, zoom) { var viewHalf = this.getSize()._divideBy(2); // TODO round on display, not calculation to increase precision? return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); }, _latLngToNewLayerPoint: function (latlng, zoom, center) { var topLeft = this._getNewPixelOrigin(center, zoom); return this.project(latlng, zoom)._subtract(topLeft); }, // layer point of the current center _getCenterLayerPoint: function () { return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); }, // offset of the specified place to the current center in pixels _getCenterOffset: function (latlng) { return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); }, // adjust center for view to get inside bounds _limitCenter: function (center, zoom, bounds) { if (!bounds) { return center; } var centerPoint = this.project(center, zoom), viewHalf = this.getSize().divideBy(2), viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), offset = this._getBoundsOffset(viewBounds, bounds, zoom); return this.unproject(centerPoint.add(offset), zoom); }, // adjust offset for view to get inside bounds _limitOffset: function (offset, bounds) { if (!bounds) { return offset; } var viewBounds = this.getPixelBounds(), newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); return offset.add(this._getBoundsOffset(newBounds, bounds)); }, // returns offset needed for pxBounds to get inside maxBounds at a specified zoom _getBoundsOffset: function (pxBounds, maxBounds, zoom) { var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min), seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max), dx = this._rebound(nwOffset.x, -seOffset.x), dy = this._rebound(nwOffset.y, -seOffset.y); return new L.Point(dx, dy); }, _rebound: function (left, right) { return left + right > 0 ? Math.round(left - right) / 2 : Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); }, _limitZoom: function (zoom) { var min = this.getMinZoom(), max = this.getMaxZoom(); return Math.max(min, Math.min(max, zoom)); } }); L.map = function (id, options) { return new L.Map(id, options); }; L.Layer = L.Evented.extend({ options: { pane: 'overlayPane' }, addTo: function (map) { map.addLayer(this); return this; }, remove: function () { return this.removeFrom(this._map || this._mapToAdd); }, removeFrom: function (obj) { if (obj) { obj.removeLayer(this); } return this; }, getPane: function (name) { return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); }, _layerAdd: function (e) { var map = e.target; // check in case layer gets added and then removed before the map is ready if (!map.hasLayer(this)) { return; } this._map = map; this._zoomAnimated = map._zoomAnimated; this.onAdd(map); if (this.getAttribution && this._map.attributionControl) { this._map.attributionControl.addAttribution(this.getAttribution()); } if (this.getEvents) { map.on(this.getEvents(), this); } this.fire('add'); map.fire('layeradd', {layer: this}); } }); L.Map.include({ addLayer: function (layer) { var id = L.stamp(layer); if (this._layers[id]) { return layer; } this._layers[id] = layer; layer._mapToAdd = this; if (layer.beforeAdd) { layer.beforeAdd(this); } this.whenReady(layer._layerAdd, layer); return this; }, removeLayer: function (layer) { var id = L.stamp(layer); if (!this._layers[id]) { return this; } if (this._loaded) { layer.onRemove(this); } if (layer.getAttribution && this.attributionControl) { this.attributionControl.removeAttribution(layer.getAttribution()); } if (layer.getEvents) { this.off(layer.getEvents(), layer); } delete this._layers[id]; if (this._loaded) { this.fire('layerremove', {layer: layer}); layer.fire('remove'); } layer._map = layer._mapToAdd = null; return this; }, hasLayer: function (layer) { return !!layer && (L.stamp(layer) in this._layers); }, eachLayer: function (method, context) { for (var i in this._layers) { method.call(context, this._layers[i]); } return this; }, _addLayers: function (layers) { layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; for (var i = 0, len = layers.length; i < len; i++) { this.addLayer(layers[i]); } }, _addZoomLimit: function (layer) { if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { this._zoomBoundLayers[L.stamp(layer)] = layer; this._updateZoomLevels(); } }, _removeZoomLimit: function (layer) { var id = L.stamp(layer); if (this._zoomBoundLayers[id]) { delete this._zoomBoundLayers[id]; this._updateZoomLevels(); } }, _updateZoomLevels: function () { var minZoom = Infinity, maxZoom = -Infinity, oldZoomSpan = this._getZoomSpan(); for (var i in this._zoomBoundLayers) { var options = this._zoomBoundLayers[i].options; minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); } this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; if (oldZoomSpan !== this._getZoomSpan()) { this.fire('zoomlevelschange'); } } }); /* * Mercator projection that takes into account that the Earth is not a perfect sphere. * Less popular than spherical mercator; used by projections like EPSG:3395. */ L.Projection.Mercator = { R: 6378137, R_MINOR: 6356752.314245179, bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), project: function (latlng) { var d = Math.PI / 180, r = this.R, y = latlng.lat * d, tmp = this.R_MINOR / r, e = Math.sqrt(1 - tmp * tmp), con = e * Math.sin(y); var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); y = -r * Math.log(Math.max(ts, 1E-10)); return new L.Point(latlng.lng * d * r, y); }, unproject: function (point) { var d = 180 / Math.PI, r = this.R, tmp = this.R_MINOR / r, e = Math.sqrt(1 - tmp * tmp), ts = Math.exp(-point.y / r), phi = Math.PI / 2 - 2 * Math.atan(ts); for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { con = e * Math.sin(phi); con = Math.pow((1 - con) / (1 + con), e / 2); dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; phi += dphi; } return new L.LatLng(phi * d, point.x * d / r); } }; /* * L.CRS.EPSG3857 (World Mercator) CRS implementation. */ L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, { code: 'EPSG:3395', projection: L.Projection.Mercator, transformation: (function () { var scale = 0.5 / (Math.PI * L.Projection.Mercator.R); return new L.Transformation(scale, 0.5, -scale, 0.5); }()) }); /* * L.GridLayer is used as base class for grid-like layers like TileLayer. */ L.GridLayer = L.Layer.extend({ options: { pane: 'tilePane', tileSize: 256, opacity: 1, unloadInvisibleTiles: L.Browser.mobile, updateWhenIdle: L.Browser.mobile, updateInterval: 200, attribution: null, zIndex: null, bounds: null, minZoom: 0 // maxZoom: }, initialize: function (options) { options = L.setOptions(this, options); }, onAdd: function () { this._initContainer(); this._pruneTiles = L.Util.throttle(this._pruneTiles, 200, this); this._levels = {}; this._tiles = {}; this._reset(); this._update(); }, beforeAdd: function (map) { map._addZoomLimit(this); }, onRemove: function (map) { L.DomUtil.remove(this._container); map._removeZoomLimit(this); this._container = null; this._tileZoom = null; }, bringToFront: function () { if (this._map) { L.DomUtil.toFront(this._container); this._setAutoZIndex(Math.max); } return this; }, bringToBack: function () { if (this._map) { L.DomUtil.toBack(this._container); this._setAutoZIndex(Math.min); } return this; }, getAttribution: function () { return this.options.attribution; }, getContainer: function () { return this._container; }, setOpacity: function (opacity) { this.options.opacity = opacity; if (this._map) { this._updateOpacity(); } return this; }, setZIndex: function (zIndex) { this.options.zIndex = zIndex; this._updateZIndex(); return this; }, redraw: function () { if (this._map) { this._removeAllTiles(); this._update(); } return this; }, getEvents: function () { var events = { viewreset: this._reset, moveend: this._update }; if (!this.options.updateWhenIdle) { // update tiles on move, but not more often than once per given interval events.move = L.Util.throttle(this._update, this.options.updateInterval, this); } if (this._zoomAnimated) { events.zoomanim = this._animateZoom; } return events; }, createTile: function () { return document.createElement('div'); }, _updateZIndex: function () { if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { this._container.style.zIndex = this.options.zIndex; } }, _setAutoZIndex: function (compare) { // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) var layers = this.getPane().children, edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min for (var i = 0, len = layers.length, zIndex; i < len; i++) { zIndex = layers[i].style.zIndex; if (layers[i] !== this._container && zIndex) { edgeZIndex = compare(edgeZIndex, +zIndex); } } if (isFinite(edgeZIndex)) { this.options.zIndex = edgeZIndex + compare(-1, 1); this._updateZIndex(); } }, _updateOpacity: function () { var opacity = this.options.opacity; if (L.Browser.ielt9) { // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles for (var i in this._tiles) { L.DomUtil.setOpacity(this._tiles[i].el, opacity); } } else { L.DomUtil.setOpacity(this._container, opacity); } }, _initContainer: function () { if (this._container) { return; } this._container = L.DomUtil.create('div', 'leaflet-layer'); this._updateZIndex(); if (this.options.opacity < 1) { this._updateOpacity(); } this.getPane().appendChild(this._container); }, _updateLevels: function () { var zoom = this._tileZoom; for (var z in this._levels) { this._levels[z].el.style.zIndex = -Math.abs(zoom - z); } var level = this._levels[zoom], map = this._map; if (!level) { level = this._levels[zoom] = {}; level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); level.el.style.zIndex = 0; level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); level.zoom = zoom; } this._level = level; return level; }, _pruneTiles: function () { if (!this._map) { return; } var bounds = this._map.getBounds(), z = this._tileZoom, range = this._getTileRange(bounds, z), i, j, key, tile, found; for (key in this._tiles) { this._tiles[key].retain = false; } for (i = range.min.x; i <= range.max.x; i++) { for (j = range.min.y; j <= range.max.y; j++) { key = i + ':' + j + ':' + z; tile = this._tiles[key]; if (!tile) { continue; } tile.retain = true; if (!tile.loaded) { found = this._retainParent(i, j, z, z - 5) || this._retainChildren(i, j, z, z + 2); } } } for (key in this._tiles) { tile = this._tiles[key]; if (!tile.retain) { if (!tile.loaded) { this._removeTile(key); } else if (this._map._fadeAnimated) { setTimeout(L.bind(this._deferRemove, this, key), 250); } else { this._removeTile(key); } } } }, _removeAllTiles: function () { for (var key in this._tiles) { this._removeTile(key); } }, _deferRemove: function (key) { var tile = this._tiles[key]; if (tile && !tile.retain) { this._removeTile(key); } }, _retainParent: function (x, y, z, minZoom) { var x2 = Math.floor(x / 2), y2 = Math.floor(y / 2), z2 = z - 1; var key = x2 + ':' + y2 + ':' + z2, tile = this._tiles[key]; if (tile && tile.loaded) { tile.retain = true; return true; } else if (z2 > minZoom) { return this._retainParent(x2, y2, z2, minZoom); } return false; }, _retainChildren: function (x, y, z, maxZoom) { for (var i = 2 * x; i < 2 * x + 2; i++) { for (var j = 2 * y; j < 2 * y + 2; j++) { var key = i + ':' + j + ':' + (z + 1), tile = this._tiles[key]; if (tile && tile.loaded) { tile.retain = true; } else if (z + 1 < maxZoom) { this._retainChildren(i, j, z + 1, maxZoom); } } } }, _reset: function (e) { var map = this._map, zoom = map.getZoom(), tileZoom = Math.round(zoom), tileZoomChanged = this._tileZoom !== tileZoom; if (tileZoomChanged || (e && e.hard)) { if (this._abortLoading) { this._abortLoading(); } this._tileZoom = tileZoom; this._updateLevels(); this._resetGrid(); } this._setZoomTransforms(map.getCenter(), zoom); }, _setZoomTransforms: function (center, zoom) { for (var i in this._levels) { this._setZoomTransform(this._levels[i], center, zoom); } }, _setZoomTransform: function (level, center, zoom) { var scale = this._map.getZoomScale(zoom, level.zoom), translate = level.origin.multiplyBy(scale) .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); L.DomUtil.setTransform(level.el, translate, scale); }, _resetGrid: function () { var map = this._map, crs = map.options.crs, tileSize = this._tileSize = this._getTileSize(), tileZoom = this._tileZoom; var bounds = this._map.getPixelWorldBounds(this._tileZoom); if (bounds) { this._globalTileRange = this._pxBoundsToTileRange(bounds); } this._wrapX = crs.wrapLng && [ Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize), Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize) ]; this._wrapY = crs.wrapLat && [ Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize), Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize) ]; }, _getTileSize: function () { return this.options.tileSize; }, _update: function () { if (!this._map) { return; } // TODO move to reset // var zoom = this._map.getZoom(); // if (zoom > this.options.maxZoom || // zoom < this.options.minZoom) { return; } var bounds = this._map.getBounds(); if (this.options.unloadInvisibleTiles) { this._removeOtherTiles(bounds); } this._addTiles(bounds); this._pruneTiles(); }, // tile coordinates range for particular geo bounds and zoom _getTileRange: function (bounds, zoom) { var pxBounds = new L.Bounds( this._map.project(bounds.getNorthWest(), zoom), this._map.project(bounds.getSouthEast(), zoom)); return this._pxBoundsToTileRange(pxBounds); }, _addTiles: function (bounds) { var queue = [], tileRange = this._getTileRange(bounds, this._tileZoom), center = tileRange.getCenter(), j, i, coords; // create a queue of coordinates to load tiles from for (j = tileRange.min.y; j <= tileRange.max.y; j++) { for (i = tileRange.min.x; i <= tileRange.max.x; i++) { coords = new L.Point(i, j); coords.z = this._tileZoom; // add tile to queue if it's not in cache or out of bounds if (!(this._tileCoordsToKey(coords) in this._tiles) && this._isValidTile(coords)) { queue.push(coords); } } } // sort tile queue to load tiles in order of their distance to center queue.sort(function (a, b) { return a.distanceTo(center) - b.distanceTo(center); }); if (queue.length !== 0) { // if its the first batch of tiles to load if (this._noTilesToLoad()) { this.fire('loading'); } // create DOM fragment to append tiles in one batch var fragment = document.createDocumentFragment(); for (i = 0; i < queue.length; i++) { this._addTile(queue[i], fragment); } this._level.el.appendChild(fragment); } }, _isValidTile: function (coords) { var crs = this._map.options.crs; if (!crs.infinite) { // don't load tile if it's out of bounds and not wrapped var bounds = this._globalTileRange; if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } } if (!this.options.bounds) { return true; } // don't load tile if it doesn't intersect the bounds in options var tileBounds = this._tileCoordsToBounds(coords); return L.latLngBounds(this.options.bounds).intersects(tileBounds); }, _keyToBounds: function (key) { return this._tileCoordsToBounds(this._keyToTileCoords(key)); }, // converts tile coordinates to its geographical bounds _tileCoordsToBounds: function (coords) { var map = this._map, tileSize = this._getTileSize(), nwPoint = coords.multiplyBy(tileSize), sePoint = nwPoint.add([tileSize, tileSize]), nw = map.wrapLatLng(map.unproject(nwPoint, coords.z)), se = map.wrapLatLng(map.unproject(sePoint, coords.z)); return new L.LatLngBounds(nw, se); }, // converts tile coordinates to key for the tile cache _tileCoordsToKey: function (coords) { return coords.x + ':' + coords.y + ':' + coords.z; }, // converts tile cache key to coordinates _keyToTileCoords: function (key) { var k = key.split(':'), coords = new L.Point(+k[0], +k[1]); coords.z = +k[2]; return coords; }, // remove any present tiles that are off the specified bounds _removeOtherTiles: function (bounds) { for (var key in this._tiles) { var tileBounds = this._keyToBounds(key); if (!bounds.intersects(tileBounds)) { this._removeTile(key); } } }, _removeTile: function (key) { var tile = this._tiles[key]; if (!tile) { return; } L.DomUtil.remove(tile.el); delete this._tiles[key]; this.fire('tileunload', { tile: tile.el, coords: this._keyToTileCoords(key) }); }, _initTile: function (tile) { L.DomUtil.addClass(tile, 'leaflet-tile'); tile.style.width = this._tileSize + 'px'; tile.style.height = this._tileSize + 'px'; tile.onselectstart = L.Util.falseFn; tile.onmousemove = L.Util.falseFn; // update opacity on tiles in IE7-8 because of filter inheritance problems if (L.Browser.ielt9 && this.options.opacity < 1) { L.DomUtil.setOpacity(tile, this.options.opacity); } // without this hack, tiles disappear after zoom on Chrome for Android // https://github.com/Leaflet/Leaflet/issues/2078 if (L.Browser.android && !L.Browser.android23) { tile.style.WebkitBackfaceVisibility = 'hidden'; } }, _addTile: function (coords, container) { var tilePos = this._getTilePos(coords), key = this._tileCoordsToKey(coords); var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords)); this._initTile(tile); // if createTile is defined with a second argument ("done" callback), // we know that tile is async and will be ready later; otherwise if (this.createTile.length < 2) { // mark tile as ready, but delay one frame for opacity animation to happen setTimeout(L.bind(this._tileReady, this, coords, null, tile), 0); } // we prefer top/left over translate3d so that we don't create a HW-accelerated layer from each tile // which is slow, and it also fixes gaps between tiles in Safari L.DomUtil.setPosition(tile, tilePos, true); // save tile in cache this._tiles[key] = { el: tile }; container.appendChild(tile); this.fire('tileloadstart', { tile: tile, coords: coords }); }, _tileReady: function (coords, err, tile) { if (err) { this.fire('tileerror', { error: err, tile: tile, coords: coords }); } var key = this._tileCoordsToKey(coords); tile = this._tiles[key]; if (!tile) { return; } tile.loaded = true; this._pruneTiles(); L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); this.fire('tileload', { tile: tile.el, coords: coords }); if (this._noTilesToLoad()) { this.fire('load'); } }, _getTilePos: function (coords) { return coords.multiplyBy(this._tileSize).subtract(this._level.origin); }, _wrapCoords: function (coords) { var newCoords = new L.Point( this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x, this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y); newCoords.z = coords.z; return newCoords; }, _pxBoundsToTileRange: function (bounds) { return new L.Bounds( bounds.min.divideBy(this._tileSize).floor(), bounds.max.divideBy(this._tileSize).ceil().subtract([1, 1])); }, _animateZoom: function (e) { this._setZoomTransforms(e.center, e.zoom); }, _noTilesToLoad: function () { for (var key in this._tiles) { if (!this._tiles[key].loaded) { return false; } } return true; } }); L.gridLayer = function (options) { return new L.GridLayer(options); }; /* * L.TileLayer is used for standard xyz-numbered tile layers. */ L.TileLayer = L.GridLayer.extend({ options: { maxZoom: 18, subdomains: 'abc', errorTileUrl: '', zoomOffset: 0, maxNativeZoom: null, // Number tms: false, zoomReverse: false, detectRetina: false, crossOrigin: false }, initialize: function (url, options) { this._url = url; options = L.setOptions(this, options); // detecting retina displays, adjusting tileSize and zoom levels if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { options.tileSize = Math.floor(options.tileSize / 2); options.zoomOffset++; options.minZoom = Math.max(0, options.minZoom); options.maxZoom--; } if (typeof options.subdomains === 'string') { options.subdomains = options.subdomains.split(''); } // for https://github.com/Leaflet/Leaflet/issues/137 if (!L.Browser.android) { this.on('tileunload', this._onTileRemove); } }, setUrl: function (url, noRedraw) { this._url = url; if (!noRedraw) { this.redraw(); } return this; }, createTile: function (coords, done) { var tile = document.createElement('img'); tile.onload = L.bind(this._tileOnLoad, this, done, tile); tile.onerror = L.bind(this._tileOnError, this, done, tile); if (this.options.crossOrigin) { tile.crossOrigin = ''; } /* Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons http://www.w3.org/TR/WCAG20-TECHS/H67 */ tile.alt = ''; tile.src = this.getTileUrl(coords); return tile; }, getTileUrl: function (coords) { return L.Util.template(this._url, L.extend({ r: this.options.detectRetina && L.Browser.retina && this.options.maxZoom > 0 ? '@2x' : '', s: this._getSubdomain(coords), x: coords.x, y: this.options.tms ? this._globalTileRange.max.y - coords.y : coords.y, z: this._getZoomForUrl() }, this.options)); }, _tileOnLoad: function (done, tile) { done(null, tile); }, _tileOnError: function (done, tile, e) { var errorUrl = this.options.errorTileUrl; if (errorUrl) { tile.src = errorUrl; } done(e, tile); }, _getTileSize: function () { var map = this._map, options = this.options, zoom = map.getZoom() + options.zoomOffset, zoomN = options.maxNativeZoom; // increase tile size when overscaling return zoomN !== null && zoom > zoomN ? Math.round(map.getZoomScale(zoomN, zoom) * options.tileSize) : options.tileSize; }, _onTileRemove: function (e) { e.tile.onload = null; e.tile.src = L.Util.emptyImageUrl; }, _getZoomForUrl: function () { var options = this.options, zoom = this._tileZoom; if (options.zoomReverse) { zoom = options.maxZoom - zoom; } zoom += options.zoomOffset; return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom; }, _getSubdomain: function (tilePoint) { var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; return this.options.subdomains[index]; }, // stops loading all tiles in the background layer _abortLoading: function () { var i, tile; for (i in this._tiles) { tile = this._tiles[i].el; tile.onload = L.Util.falseFn; tile.onerror = L.Util.falseFn; if (!tile.complete) { tile.src = L.Util.emptyImageUrl; L.DomUtil.remove(tile); } } } }); L.tileLayer = function (url, options) { return new L.TileLayer(url, options); }; /* * L.TileLayer.WMS is used for WMS tile layers. */ L.TileLayer.WMS = L.TileLayer.extend({ defaultWmsParams: { service: 'WMS', request: 'GetMap', version: '1.1.1', layers: '', styles: '', format: 'image/jpeg', transparent: false }, options: { crs: null, uppercase: false }, initialize: function (url, options) { this._url = url; var wmsParams = L.extend({}, this.defaultWmsParams); // all keys that are not TileLayer options go to WMS params for (var i in options) { if (!(i in this.options)) { wmsParams[i] = options[i]; } } options = L.setOptions(this, options); wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1); this.wmsParams = wmsParams; }, onAdd: function (map) { this._crs = this.options.crs || map.options.crs; this._wmsVersion = parseFloat(this.wmsParams.version); var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; this.wmsParams[projectionKey] = this._crs.code; L.TileLayer.prototype.onAdd.call(this, map); }, getTileUrl: function (coords) { var tileBounds = this._tileCoordsToBounds(coords), nw = this._crs.project(tileBounds.getNorthWest()), se = this._crs.project(tileBounds.getSouthEast()), bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? [se.y, nw.x, nw.y, se.x] : [nw.x, se.y, se.x, nw.y]).join(','), url = L.TileLayer.prototype.getTileUrl.call(this, coords); return url + L.Util.getParamString(this.wmsParams, url, this.options.uppercase) + (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; }, setParams: function (params, noRedraw) { L.extend(this.wmsParams, params); if (!noRedraw) { this.redraw(); } return this; } }); L.tileLayer.wms = function (url, options) { return new L.TileLayer.WMS(url, options); }; /* * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). */ L.ImageOverlay = L.Layer.extend({ options: { opacity: 1, alt: '', interactive: false }, initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) this._url = url; this._bounds = L.latLngBounds(bounds); L.setOptions(this, options); }, onAdd: function () { if (!this._image) { this._initImage(); if (this.options.opacity < 1) { this._updateOpacity(); } } this.getPane().appendChild(this._image); this._initInteraction(); this._reset(); }, onRemove: function () { L.DomUtil.remove(this._image); }, setOpacity: function (opacity) { this.options.opacity = opacity; if (this._image) { this._updateOpacity(); } return this; }, setStyle: function (styleOpts) { if (styleOpts.opacity) { this.setOpacity(styleOpts.opacity); } return this; }, bringToFront: function () { if (this._map) { L.DomUtil.toFront(this._image); } return this; }, bringToBack: function () { if (this._map) { L.DomUtil.toBack(this._image); } return this; }, _initInteraction: function () { if (!this.options.interactive) { return; } L.DomUtil.addClass(this._image, 'leaflet-interactive'); L.DomEvent.on(this._image, 'click dblclick mousedown mouseup mouseover mousemove mouseout contextmenu', this._fireMouseEvent, this); }, _fireMouseEvent: function (e, type) { if (this._map) { this._map._fireMouseEvent(this, e, type, true); } }, setUrl: function (url) { this._url = url; if (this._image) { this._image.src = url; } return this; }, getAttribution: function () { return this.options.attribution; }, getEvents: function () { var events = { viewreset: this._reset }; if (this._zoomAnimated) { events.zoomanim = this._animateZoom; } return events; }, getBounds: function () { return this._bounds; }, _initImage: function () { var img = this._image = L.DomUtil.create('img', 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '')); img.onselectstart = L.Util.falseFn; img.onmousemove = L.Util.falseFn; img.onload = L.bind(this.fire, this, 'load'); img.src = this._url; img.alt = this.options.alt; }, _animateZoom: function (e) { var bounds = new L.Bounds( this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center), this._map._latLngToNewLayerPoint(this._bounds.getSouthEast(), e.zoom, e.center)); var offset = bounds.min.add(bounds.getSize()._multiplyBy((1 - 1 / e.scale) / 2)); L.DomUtil.setTransform(this._image, offset, e.scale); }, _reset: function () { var image = this._image, bounds = new L.Bounds( this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getSouthEast())), size = bounds.getSize(); L.DomUtil.setPosition(image, bounds.min); image.style.width = size.x + 'px'; image.style.height = size.y + 'px'; }, _updateOpacity: function () { L.DomUtil.setOpacity(this._image, this.options.opacity); } }); L.imageOverlay = function (url, bounds, options) { return new L.ImageOverlay(url, bounds, options); }; /* * L.Icon is an image-based icon class that you can use with L.Marker for custom markers. */ L.Icon = L.Class.extend({ /* options: { iconUrl: (String) (required) iconRetinaUrl: (String) (optional, used for retina devices if detected) iconSize: (Point) (can be set through CSS) iconAnchor: (Point) (centered by default, can be set in CSS with negative margins) popupAnchor: (Point) (if not specified, popup opens in the anchor point) shadowUrl: (String) (no shadow by default) shadowRetinaUrl: (String) (optional, used for retina devices if detected) shadowSize: (Point) shadowAnchor: (Point) className: (String) }, */ initialize: function (options) { L.setOptions(this, options); }, createIcon: function (oldIcon) { return this._createIcon('icon', oldIcon); }, createShadow: function (oldIcon) { return this._createIcon('shadow', oldIcon); }, _createIcon: function (name, oldIcon) { var src = this._getIconUrl(name); if (!src) { if (name === 'icon') { throw new Error('iconUrl not set in Icon options (see the docs).'); } return null; } var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); this._setIconStyles(img, name); return img; }, _setIconStyles: function (img, name) { var options = this.options, size = L.point(options[name + 'Size']), anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || size && size.divideBy(2, true)); img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); if (anchor) { img.style.marginLeft = (-anchor.x) + 'px'; img.style.marginTop = (-anchor.y) + 'px'; } if (size) { img.style.width = size.x + 'px'; img.style.height = size.y + 'px'; } }, _createImg: function (src, el) { el = el || document.createElement('img'); el.src = src; return el; }, _getIconUrl: function (name) { return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; } }); L.icon = function (options) { return new L.Icon(options); }; /* * L.Icon.Default is the blue marker icon used by default in Leaflet. */ L.Icon.Default = L.Icon.extend({ options: { iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }, _getIconUrl: function (name) { var key = name + 'Url'; if (this.options[key]) { return this.options[key]; } var path = L.Icon.Default.imagePath; if (!path) { throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.'); } return path + '/marker-' + name + (L.Browser.retina && name === 'icon' ? '-2x' : '') + '.png'; } }); L.Icon.Default.imagePath = (function () { var scripts = document.getElementsByTagName('script'), leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/; var i, len, src, path; for (i = 0, len = scripts.length; i < len; i++) { src = scripts[i].src; if (src.match(leafletRe)) { path = src.split(leafletRe)[0]; return (path ? path + '/' : '') + 'images'; } } }()); /* * L.Marker is used to display clickable/draggable icons on the map. */ L.Marker = L.Layer.extend({ options: { pane: 'markerPane', icon: new L.Icon.Default(), // title: '', // alt: '', interactive: true, // draggable: false, keyboard: true, zIndexOffset: 0, opacity: 1, // riseOnHover: false, riseOffset: 250 }, initialize: function (latlng, options) { L.setOptions(this, options); this._latlng = L.latLng(latlng); }, onAdd: function (map) { this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; this._initIcon(); this.update(); }, onRemove: function () { if (this.dragging && this.dragging.enabled()) { this.dragging.removeHooks(); } this._removeIcon(); this._removeShadow(); }, getEvents: function () { var events = {viewreset: this.update}; if (this._zoomAnimated) { events.zoomanim = this._animateZoom; } return events; }, getLatLng: function () { return this._latlng; }, setLatLng: function (latlng) { var oldLatLng = this._latlng; this._latlng = L.latLng(latlng); this.update(); return this.fire('move', { oldLatLng: oldLatLng, latlng: this._latlng }); }, setZIndexOffset: function (offset) { this.options.zIndexOffset = offset; return this.update(); }, setIcon: function (icon) { this.options.icon = icon; if (this._map) { this._initIcon(); this.update(); } if (this._popup) { this.bindPopup(this._popup, this._popup.options); } return this; }, update: function () { if (this._icon) { var pos = this._map.latLngToLayerPoint(this._latlng).round(); this._setPos(pos); } return this; }, _initIcon: function () { var options = this.options, classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); var icon = options.icon.createIcon(this._icon), addIcon = false; // if we're not reusing the icon, remove the old one and init new one if (icon !== this._icon) { if (this._icon) { this._removeIcon(); } addIcon = true; if (options.title) { icon.title = options.title; } if (options.alt) { icon.alt = options.alt; } } L.DomUtil.addClass(icon, classToAdd); if (options.keyboard) { icon.tabIndex = '0'; } this._icon = icon; this._initInteraction(); if (L.DomEvent && options.riseOnHover) { L.DomEvent.on(icon, { mouseover: this._bringToFront, mouseout: this._resetZIndex }, this); } var newShadow = options.icon.createShadow(this._shadow), addShadow = false; if (newShadow !== this._shadow) { this._removeShadow(); addShadow = true; } if (newShadow) { L.DomUtil.addClass(newShadow, classToAdd); } this._shadow = newShadow; if (options.opacity < 1) { this._updateOpacity(); } if (addIcon) { this.getPane().appendChild(this._icon); } if (newShadow && addShadow) { this.getPane('shadowPane').appendChild(this._shadow); } }, _removeIcon: function () { if (L.DomEvent && this.options.riseOnHover) { L.DomEvent.off(this._icon, { mouseover: this._bringToFront, mouseout: this._resetZIndex }, this); } L.DomUtil.remove(this._icon); this._icon = null; }, _removeShadow: function () { if (this._shadow) { L.DomUtil.remove(this._shadow); } this._shadow = null; }, _setPos: function (pos) { L.DomUtil.setPosition(this._icon, pos); if (this._shadow) { L.DomUtil.setPosition(this._shadow, pos); } this._zIndex = pos.y + this.options.zIndexOffset; this._resetZIndex(); }, _updateZIndex: function (offset) { this._icon.style.zIndex = this._zIndex + offset; }, _animateZoom: function (opt) { var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); this._setPos(pos); }, _initInteraction: function () { if (!this.options.interactive) { return; } L.DomUtil.addClass(this._icon, 'leaflet-interactive'); if (L.DomEvent) { L.DomEvent.on(this._icon, 'click dblclick mousedown mouseup mouseover mousemove mouseout contextmenu keypress', this._fireMouseEvent, this); } if (L.Handler.MarkerDrag) { var draggable = this.options.draggable; if (this.dragging) { draggable = this.dragging.enabled(); this.dragging.disable(); } this.dragging = new L.Handler.MarkerDrag(this); if (draggable) { this.dragging.enable(); } } }, _fireMouseEvent: function (e, type) { // to prevent outline when clicking on keyboard-focusable marker if (e.type === 'mousedown') { L.DomEvent.preventDefault(e); } if (e.type === 'keypress' && e.keyCode === 13) { type = 'click'; } if (this._map) { this._map._fireMouseEvent(this, e, type, true, this._latlng); } }, setOpacity: function (opacity) { this.options.opacity = opacity; if (this._map) { this._updateOpacity(); } return this; }, _updateOpacity: function () { var opacity = this.options.opacity; L.DomUtil.setOpacity(this._icon, opacity); if (this._shadow) { L.DomUtil.setOpacity(this._shadow, opacity); } }, _bringToFront: function () { this._updateZIndex(this.options.riseOffset); }, _resetZIndex: function () { this._updateZIndex(0); } }); L.marker = function (latlng, options) { return new L.Marker(latlng, options); }; /* * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon) * to use with L.Marker. */ L.DivIcon = L.Icon.extend({ options: { iconSize: [12, 12], // also can be set through CSS /* iconAnchor: (Point) popupAnchor: (Point) html: (String) bgPos: (Point) */ className: 'leaflet-div-icon', html: false }, createIcon: function (oldIcon) { var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), options = this.options; div.innerHTML = options.html !== false ? options.html : ''; if (options.bgPos) { div.style.backgroundPosition = (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px'; } this._setIconStyles(div, 'icon'); return div; }, createShadow: function () { return null; } }); L.divIcon = function (options) { return new L.DivIcon(options); }; /* * L.Popup is used for displaying popups on the map. */ L.Map.mergeOptions({ closePopupOnClick: true }); L.Popup = L.Layer.extend({ options: { pane: 'popupPane', minWidth: 50, maxWidth: 300, // maxHeight: , offset: [0, 7], autoPan: true, autoPanPadding: [5, 5], // autoPanPaddingTopLeft: , // autoPanPaddingBottomRight: , closeButton: true, autoClose: true, // keepInView: false, // className: '', zoomAnimation: true }, initialize: function (options, source) { L.setOptions(this, options); this._source = source; }, onAdd: function (map) { this._zoomAnimated = this._zoomAnimated && this.options.zoomAnimation; if (!this._container) { this._initLayout(); } if (map._fadeAnimated) { L.DomUtil.setOpacity(this._container, 0); } clearTimeout(this._removeTimeout); this.getPane().appendChild(this._container); this.update(); if (map._fadeAnimated) { L.DomUtil.setOpacity(this._container, 1); } map.fire('popupopen', {popup: this}); if (this._source) { this._source.fire('popupopen', {popup: this}, true); } }, openOn: function (map) { map.openPopup(this); return this; }, onRemove: function (map) { if (map._fadeAnimated) { L.DomUtil.setOpacity(this._container, 0); this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); } else { L.DomUtil.remove(this._container); } map.fire('popupclose', {popup: this}); if (this._source) { this._source.fire('popupclose', {popup: this}, true); } }, getLatLng: function () { return this._latlng; }, setLatLng: function (latlng) { this._latlng = L.latLng(latlng); if (this._map) { this._updatePosition(); this._adjustPan(); } return this; }, getContent: function () { return this._content; }, setContent: function (content) { this._content = content; this.update(); return this; }, update: function () { if (!this._map) { return; } this._container.style.visibility = 'hidden'; this._updateContent(); this._updateLayout(); this._updatePosition(); this._container.style.visibility = ''; this._adjustPan(); }, getEvents: function () { var events = {viewreset: this._updatePosition}, options = this.options; if (this._zoomAnimated) { events.zoomanim = this._animateZoom; } if ('closeOnClick' in options ? options.closeOnClick : this._map.options.closePopupOnClick) { events.preclick = this._close; } if (options.keepInView) { events.moveend = this._adjustPan; } return events; }, isOpen: function () { return !!this._map && this._map.hasLayer(this); }, _close: function () { if (this._map) { this._map.closePopup(this); } }, _initLayout: function () { var prefix = 'leaflet-popup', container = this._container = L.DomUtil.create('div', prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide')); if (this.options.closeButton) { var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container); closeButton.href = '#close'; closeButton.innerHTML = '×'; L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); } var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container); this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); L.DomEvent .disableClickPropagation(wrapper) .disableScrollPropagation(this._contentNode) .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); }, _updateContent: function () { if (!this._content) { return; } var node = this._contentNode; if (typeof this._content === 'string') { node.innerHTML = this._content; } else { while (node.hasChildNodes()) { node.removeChild(node.firstChild); } node.appendChild(this._content); } this.fire('contentupdate'); }, _updateLayout: function () { var container = this._contentNode, style = container.style; style.width = ''; style.whiteSpace = 'nowrap'; var width = container.offsetWidth; width = Math.min(width, this.options.maxWidth); width = Math.max(width, this.options.minWidth); style.width = (width + 1) + 'px'; style.whiteSpace = ''; style.height = ''; var height = container.offsetHeight, maxHeight = this.options.maxHeight, scrolledClass = 'leaflet-popup-scrolled'; if (maxHeight && height > maxHeight) { style.height = maxHeight + 'px'; L.DomUtil.addClass(container, scrolledClass); } else { L.DomUtil.removeClass(container, scrolledClass); } this._containerWidth = this._container.offsetWidth; }, _updatePosition: function () { if (!this._map) { return; } var pos = this._map.latLngToLayerPoint(this._latlng), offset = L.point(this.options.offset); if (this._zoomAnimated) { L.DomUtil.setPosition(this._container, pos); } else { offset = offset.add(pos); } var bottom = this._containerBottom = -offset.y, left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; // bottom position the popup in case the height of the popup changes (images loading etc) this._container.style.bottom = bottom + 'px'; this._container.style.left = left + 'px'; }, _animateZoom: function (e) { var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); L.DomUtil.setPosition(this._container, pos); }, _adjustPan: function () { if (!this.options.autoPan) { return; } var map = this._map, containerHeight = this._container.offsetHeight, containerWidth = this._containerWidth, layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); if (this._zoomAnimated) { layerPos._add(L.DomUtil.getPosition(this._container)); } var containerPos = map.layerPointToContainerPoint(layerPos), padding = L.point(this.options.autoPanPadding), paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), size = map.getSize(), dx = 0, dy = 0; if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right dx = containerPos.x + containerWidth - size.x + paddingBR.x; } if (containerPos.x - dx - paddingTL.x < 0) { // left dx = containerPos.x - paddingTL.x; } if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom dy = containerPos.y + containerHeight - size.y + paddingBR.y; } if (containerPos.y - dy - paddingTL.y < 0) { // top dy = containerPos.y - paddingTL.y; } if (dx || dy) { map .fire('autopanstart') .panBy([dx, dy]); } }, _onCloseButtonClick: function (e) { this._close(); L.DomEvent.stop(e); } }); L.popup = function (options, source) { return new L.Popup(options, source); }; L.Map.include({ openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object]) if (!(popup instanceof L.Popup)) { var content = popup; popup = new L.Popup(options).setContent(content); } if (latlng) { popup.setLatLng(latlng); } if (this.hasLayer(popup)) { return this; } if (this._popup && this._popup.options.autoClose) { this.closePopup(); } this._popup = popup; return this.addLayer(popup); }, closePopup: function (popup) { if (!popup || popup === this._popup) { popup = this._popup; this._popup = null; } if (popup) { this.removeLayer(popup); } return this; } }); /* * Adds popup-related methods to all layers. */ L.Layer.include({ bindPopup: function (content, options) { if (content instanceof L.Popup) { this._popup = content; content._source = this; } else { if (!this._popup || options) { this._popup = new L.Popup(options, this); } this._popup.setContent(content); } if (!this._popupHandlersAdded) { this.on({ click: this._openPopup, remove: this.closePopup, move: this._movePopup }); this._popupHandlersAdded = true; } return this; }, unbindPopup: function () { if (this._popup) { this.off({ click: this._openPopup, remove: this.closePopup, move: this._movePopup }); this._popupHandlersAdded = false; this._popup = null; } return this; }, openPopup: function (latlng) { if (this._popup && this._map) { this._map.openPopup(this._popup, latlng || this._latlng || this.getCenter()); } return this; }, closePopup: function () { if (this._popup) { this._popup._close(); } return this; }, togglePopup: function () { if (this._popup) { if (this._popup._map) { this.closePopup(); } else { this.openPopup(); } } return this; }, setPopupContent: function (content) { if (this._popup) { this._popup.setContent(content); } return this; }, getPopup: function () { return this._popup; }, _openPopup: function (e) { this._map.openPopup(this._popup, e.latlng); }, _movePopup: function (e) { this._popup.setLatLng(e.latlng); } }); /* * Popup extension to L.Marker, adding popup-related methods. */ L.Marker.include({ bindPopup: function (content, options) { var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]) .add(L.Popup.prototype.options.offset); options = L.extend({offset: anchor}, options); return L.Layer.prototype.bindPopup.call(this, content, options); }, _openPopup: L.Layer.prototype.togglePopup }); /* * L.LayerGroup is a class to combine several layers into one so that * you can manipulate the group (e.g. add/remove it) as one layer. */ L.LayerGroup = L.Layer.extend({ initialize: function (layers) { this._layers = {}; var i, len; if (layers) { for (i = 0, len = layers.length; i < len; i++) { this.addLayer(layers[i]); } } }, addLayer: function (layer) { var id = this.getLayerId(layer); this._layers[id] = layer; if (this._map) { this._map.addLayer(layer); } return this; }, removeLayer: function (layer) { var id = layer in this._layers ? layer : this.getLayerId(layer); if (this._map && this._layers[id]) { this._map.removeLayer(this._layers[id]); } delete this._layers[id]; return this; }, hasLayer: function (layer) { return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); }, clearLayers: function () { for (var i in this._layers) { this.removeLayer(this._layers[i]); } return this; }, invoke: function (methodName) { var args = Array.prototype.slice.call(arguments, 1), i, layer; for (i in this._layers) { layer = this._layers[i]; if (layer[methodName]) { layer[methodName].apply(layer, args); } } return this; }, onAdd: function (map) { for (var i in this._layers) { map.addLayer(this._layers[i]); } }, onRemove: function (map) { for (var i in this._layers) { map.removeLayer(this._layers[i]); } }, eachLayer: function (method, context) { for (var i in this._layers) { method.call(context, this._layers[i]); } return this; }, getLayer: function (id) { return this._layers[id]; }, getLayers: function () { var layers = []; for (var i in this._layers) { layers.push(this._layers[i]); } return layers; }, setZIndex: function (zIndex) { return this.invoke('setZIndex', zIndex); }, getLayerId: function (layer) { return L.stamp(layer); } }); L.layerGroup = function (layers) { return new L.LayerGroup(layers); }; /* * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods * shared between a group of interactive layers (like vectors or markers). */ L.FeatureGroup = L.LayerGroup.extend({ addLayer: function (layer) { if (this.hasLayer(layer)) { return this; } layer.addEventParent(this); L.LayerGroup.prototype.addLayer.call(this, layer); if (this._popupContent && layer.bindPopup) { layer.bindPopup(this._popupContent, this._popupOptions); } return this.fire('layeradd', {layer: layer}); }, removeLayer: function (layer) { if (!this.hasLayer(layer)) { return this; } if (layer in this._layers) { layer = this._layers[layer]; } layer.removeEventParent(this); L.LayerGroup.prototype.removeLayer.call(this, layer); if (this._popupContent) { this.invoke('unbindPopup'); } return this.fire('layerremove', {layer: layer}); }, bindPopup: function (content, options) { this._popupContent = content; this._popupOptions = options; return this.invoke('bindPopup', content, options); }, openPopup: function (latlng) { // open popup on the first layer for (var id in this._layers) { this._layers[id].openPopup(latlng); break; } return this; }, setStyle: function (style) { return this.invoke('setStyle', style); }, bringToFront: function () { return this.invoke('bringToFront'); }, bringToBack: function () { return this.invoke('bringToBack'); }, getBounds: function () { var bounds = new L.LatLngBounds(); this.eachLayer(function (layer) { bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); }); return bounds; } }); L.featureGroup = function (layers) { return new L.FeatureGroup(layers); }; /* * L.Renderer is a base class for renderer implementations (SVG, Canvas); * handles renderer container, bounds and zoom animation. */ L.Renderer = L.Layer.extend({ options: { // 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; defaults to clip with the map view padding: 0 }, initialize: function (options) { L.setOptions(this, options); L.stamp(this); }, onAdd: function () { if (!this._container) { this._initContainer(); // defined by renderer implementations if (this._zoomAnimated) { L.DomUtil.addClass(this._container, 'leaflet-zoom-animated'); } } this.getPane().appendChild(this._container); this._update(); }, onRemove: function () { L.DomUtil.remove(this._container); }, getEvents: function () { var events = { moveend: this._update }; if (this._zoomAnimated) { events.zoomanim = this._animateZoom; } return events; }, _animateZoom: function (e) { var origin = e.origin.subtract(this._map._getCenterLayerPoint()), offset = this._bounds.min.add(origin.multiplyBy(1 - e.scale)).add(e.offset).round(); L.DomUtil.setTransform(this._container, offset, e.scale); }, _update: function () { // update pixel bounds of renderer container (for positioning/sizing/clipping later) var p = this.options.padding, size = this._map.getSize(), min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); } }); L.Map.include({ // used by each vector layer to decide which renderer to use getRenderer: function (layer) { var renderer = layer.options.renderer || this.options.renderer || this._renderer; if (!renderer) { renderer = this._renderer = (L.SVG && L.svg()) || (L.Canvas && L.canvas()); } if (!this.hasLayer(renderer)) { this.addLayer(renderer); } return renderer; } }); /* * L.Path is the base class for all Leaflet vector layers like polygons and circles. */ L.Path = L.Layer.extend({ options: { stroke: true, color: '#3388ff', weight: 3, opacity: 1, lineCap: 'round', lineJoin: 'round', // dashArray: null // dashOffset: null // fill: false // fillColor: same as color by default fillOpacity: 0.2, fillRule: 'evenodd', // className: '' interactive: true }, onAdd: function () { this._renderer = this._map.getRenderer(this); this._renderer._initPath(this); // defined in children classes this._project(); this._update(); this._renderer._addPath(this); }, onRemove: function () { this._renderer._removePath(this); }, getEvents: function () { return { viewreset: this._project, moveend: this._update }; }, redraw: function () { if (this._map) { this._renderer._updatePath(this); } return this; }, setStyle: function (style) { L.setOptions(this, style); if (this._renderer) { this._renderer._updateStyle(this); } return this; }, bringToFront: function () { if (this._renderer) { this._renderer._bringToFront(this); } return this; }, bringToBack: function () { if (this._renderer) { this._renderer._bringToBack(this); } return this; }, _fireMouseEvent: function (e, type) { this._map._fireMouseEvent(this, e, type, true); }, _clickTolerance: function () { // used when doing hit detection for Canvas layers return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0); } }); /* * L.LineUtil contains different utility functions for line segments * and polylines (clipping, simplification, distances, etc.) */ /*jshint bitwise:false */ // allow bitwise operations for this file L.LineUtil = { // Simplify polyline with vertex reduction and Douglas-Peucker simplification. // Improves rendering performance dramatically by lessening the number of points to draw. simplify: function (/*Point[]*/ points, /*Number*/ tolerance) { if (!tolerance || !points.length) { return points.slice(); } var sqTolerance = tolerance * tolerance; // stage 1: vertex reduction points = this._reducePoints(points, sqTolerance); // stage 2: Douglas-Peucker simplification points = this._simplifyDP(points, sqTolerance); return points; }, // distance from a point to a segment between two points pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); }, closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { return this._sqClosestPointOnSegment(p, p1, p2); }, // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm _simplifyDP: function (points, sqTolerance) { var len = points.length, ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, markers = new ArrayConstructor(len); markers[0] = markers[len - 1] = 1; this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); var i, newPoints = []; for (i = 0; i < len; i++) { if (markers[i]) { newPoints.push(points[i]); } } return newPoints; }, _simplifyDPStep: function (points, markers, sqTolerance, first, last) { var maxSqDist = 0, index, i, sqDist; for (i = first + 1; i <= last - 1; i++) { sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); if (sqDist > maxSqDist) { index = i; maxSqDist = sqDist; } } if (maxSqDist > sqTolerance) { markers[index] = 1; this._simplifyDPStep(points, markers, sqTolerance, first, index); this._simplifyDPStep(points, markers, sqTolerance, index, last); } }, // reduce points that are too close to each other to a single point _reducePoints: function (points, sqTolerance) { var reducedPoints = [points[0]]; for (var i = 1, prev = 0, len = points.length; i < len; i++) { if (this._sqDist(points[i], points[prev]) > sqTolerance) { reducedPoints.push(points[i]); prev = i; } } if (prev < len - 1) { reducedPoints.push(points[len - 1]); } return reducedPoints; }, // Cohen-Sutherland line clipping algorithm. // Used to avoid rendering parts of a polyline that are not currently visible. clipSegment: function (a, b, bounds, useLastCode) { var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), codeB = this._getBitCode(b, bounds), codeOut, p, newCode; // save 2nd code to avoid calculating it on the next segment this._lastCode = codeB; while (true) { // if a,b is inside the clip window (trivial accept) if (!(codeA | codeB)) { return [a, b]; // if a,b is outside the clip window (trivial reject) } else if (codeA & codeB) { return false; // other cases } else { codeOut = codeA || codeB; p = this._getEdgeIntersection(a, b, codeOut, bounds); newCode = this._getBitCode(p, bounds); if (codeOut === codeA) { a = p; codeA = newCode; } else { b = p; codeB = newCode; } } } }, _getEdgeIntersection: function (a, b, code, bounds) { var dx = b.x - a.x, dy = b.y - a.y, min = bounds.min, max = bounds.max, x, y; if (code & 8) { // top x = a.x + dx * (max.y - a.y) / dy; y = max.y; } else if (code & 4) { // bottom x = a.x + dx * (min.y - a.y) / dy; y = min.y; } else if (code & 2) { // right x = max.x; y = a.y + dy * (max.x - a.x) / dx; } else if (code & 1) { // left x = min.x; y = a.y + dy * (min.x - a.x) / dx; } return new L.Point(x, y, true); }, _getBitCode: function (/*Point*/ p, bounds) { var code = 0; if (p.x < bounds.min.x) { // left code |= 1; } else if (p.x > bounds.max.x) { // right code |= 2; } if (p.y < bounds.min.y) { // bottom code |= 4; } else if (p.y > bounds.max.y) { // top code |= 8; } return code; }, // square distance (to avoid unnecessary Math.sqrt calls) _sqDist: function (p1, p2) { var dx = p2.x - p1.x, dy = p2.y - p1.y; return dx * dx + dy * dy; }, // return closest point on segment or distance to that point _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { var x = p1.x, y = p1.y, dx = p2.x - x, dy = p2.y - y, dot = dx * dx + dy * dy, t; if (dot > 0) { t = ((p.x - x) * dx + (p.y - y) * dy) / dot; if (t > 1) { x = p2.x; y = p2.y; } else if (t > 0) { x += dx * t; y += dy * t; } } dx = p.x - x; dy = p.y - y; return sqDist ? dx * dx + dy * dy : new L.Point(x, y); } }; /* * L.Polyline implements polyline vector layer (a set of points connected with lines) */ L.Polyline = L.Path.extend({ options: { // how much to simplify the polyline on each zoom level // more = better performance and smoother look, less = more accurate smoothFactor: 1.0 // noClip: false }, initialize: function (latlngs, options) { L.setOptions(this, options); this._setLatLngs(latlngs); }, getLatLngs: function () { // TODO rings return this._latlngs; }, setLatLngs: function (latlngs) { this._setLatLngs(latlngs); return this.redraw(); }, addLatLng: function (latlng) { // TODO rings latlng = L.latLng(latlng); this._latlngs.push(latlng); this._bounds.extend(latlng); return this.redraw(); }, spliceLatLngs: function () { // TODO rings var removed = [].splice.apply(this._latlngs, arguments); this._setLatLngs(this._latlngs); this.redraw(); return removed; }, closestLayerPoint: function (p) { var minDistance = Infinity, minPoint = null, closest = L.LineUtil._sqClosestPointOnSegment, p1, p2; for (var j = 0, jLen = this._parts.length; j < jLen; j++) { var points = this._parts[j]; for (var i = 1, len = points.length; i < len; i++) { p1 = points[i - 1]; p2 = points[i]; var sqDist = closest(p, p1, p2, true); if (sqDist < minDistance) { minDistance = sqDist; minPoint = closest(p, p1, p2); } } } if (minPoint) { minPoint.distance = Math.sqrt(minDistance); } return minPoint; }, getCenter: function () { var i, halfDist, segDist, dist, p1, p2, ratio, points = this._rings[0], len = points.length; // polyline centroid algorithm; only uses the first ring if there are multiple for (i = 0, halfDist = 0; i < len - 1; i++) { halfDist += points[i].distanceTo(points[i + 1]) / 2; } for (i = 0, dist = 0; i < len - 1; i++) { p1 = points[i]; p2 = points[i + 1]; segDist = p1.distanceTo(p2); dist += segDist; if (dist > halfDist) { ratio = (dist - halfDist) / segDist; return this._map.layerPointToLatLng([ p2.x - ratio * (p2.x - p1.x), p2.y - ratio * (p2.y - p1.y) ]); } } }, getBounds: function () { return this._bounds; }, _setLatLngs: function (latlngs) { this._bounds = new L.LatLngBounds(); this._latlngs = this._convertLatLngs(latlngs); }, // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way _convertLatLngs: function (latlngs) { var result = [], flat = this._flat(latlngs); for (var i = 0, len = latlngs.length; i < len; i++) { if (flat) { result[i] = L.latLng(latlngs[i]); this._bounds.extend(result[i]); } else { result[i] = this._convertLatLngs(latlngs[i]); } } return result; }, _flat: function (latlngs) { // true if it's a flat array of latlngs; false if nested return !L.Util.isArray(latlngs[0]) || typeof latlngs[0][0] !== 'object'; }, _project: function () { this._rings = []; this._projectLatlngs(this._latlngs, this._rings); // project bounds as well to use later for Canvas hit detection/etc. var w = this._clickTolerance(), p = new L.Point(w, -w); if (this._latlngs.length) { this._pxBounds = new L.Bounds( this._map.latLngToLayerPoint(this._bounds.getSouthWest())._subtract(p), this._map.latLngToLayerPoint(this._bounds.getNorthEast())._add(p)); } }, // recursively turns latlngs into a set of rings with projected coordinates _projectLatlngs: function (latlngs, result) { var flat = latlngs[0] instanceof L.LatLng, len = latlngs.length, i, ring; if (flat) { ring = []; for (i = 0; i < len; i++) { ring[i] = this._map.latLngToLayerPoint(latlngs[i]); } result.push(ring); } else { for (i = 0; i < len; i++) { this._projectLatlngs(latlngs[i], result); } } }, // clip polyline by renderer bounds so that we have less to render for performance _clipPoints: function () { if (this.options.noClip) { this._parts = this._rings; return; } this._parts = []; var parts = this._parts, bounds = this._renderer._bounds, i, j, k, len, len2, segment, points; for (i = 0, k = 0, len = this._rings.length; i < len; i++) { points = this._rings[i]; for (j = 0, len2 = points.length; j < len2 - 1; j++) { segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j); if (!segment) { continue; } parts[k] = parts[k] || []; parts[k].push(segment[0]); // if segment goes out of screen, or it's the last one, it's the end of the line part if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { parts[k].push(segment[1]); k++; } } } }, // simplify each clipped part of the polyline for performance _simplifyPoints: function () { var parts = this._parts, tolerance = this.options.smoothFactor; for (var i = 0, len = parts.length; i < len; i++) { parts[i] = L.LineUtil.simplify(parts[i], tolerance); } }, _update: function () { if (!this._map) { return; } this._clipPoints(); this._simplifyPoints(); this._updatePath(); }, _updatePath: function () { this._renderer._updatePoly(this); } }); L.polyline = function (latlngs, options) { return new L.Polyline(latlngs, options); }; /* * L.PolyUtil contains utility functions for polygons (clipping, etc.). */ /*jshint bitwise:false */ // allow bitwise operations here L.PolyUtil = {}; /* * Sutherland-Hodgeman polygon clipping algorithm. * Used to avoid rendering parts of a polygon that are not currently visible. */ L.PolyUtil.clipPolygon = function (points, bounds) { var clippedPoints, edges = [1, 4, 2, 8], i, j, k, a, b, len, edge, p, lu = L.LineUtil; for (i = 0, len = points.length; i < len; i++) { points[i]._code = lu._getBitCode(points[i], bounds); } // for each edge (left, bottom, right, top) for (k = 0; k < 4; k++) { edge = edges[k]; clippedPoints = []; for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { a = points[i]; b = points[j]; // if a is inside the clip window if (!(a._code & edge)) { // if b is outside the clip window (a->b goes out of screen) if (b._code & edge) { p = lu._getEdgeIntersection(b, a, edge, bounds); p._code = lu._getBitCode(p, bounds); clippedPoints.push(p); } clippedPoints.push(a); // else if b is inside the clip window (a->b enters the screen) } else if (!(b._code & edge)) { p = lu._getEdgeIntersection(b, a, edge, bounds); p._code = lu._getBitCode(p, bounds); clippedPoints.push(p); } } points = clippedPoints; } return points; }; /* * L.Polygon implements polygon vector layer (closed polyline with a fill inside). */ L.Polygon = L.Polyline.extend({ options: { fill: true }, getCenter: function () { var i, j, len, p1, p2, f, area, x, y, points = this._rings[0]; // polygon centroid algorithm; only uses the first ring if there are multiple area = x = y = 0; for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { p1 = points[i]; p2 = points[j]; f = p1.y * p2.x - p2.y * p1.x; x += (p1.x + p2.x) * f; y += (p1.y + p2.y) * f; area += f * 3; } return this._map.layerPointToLatLng([x / area, y / area]); }, _convertLatLngs: function (latlngs) { var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs), len = result.length; // remove last point if it equals first one if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) { result.pop(); } return result; }, _clipPoints: function () { if (this.options.noClip) { this._parts = this._rings; return; } // polygons need a different clipping algorithm so we redefine that var bounds = this._renderer._bounds, w = this.options.weight, p = new L.Point(w, w); // increase clip padding by stroke width to avoid stroke on clip edges bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p)); this._parts = []; for (var i = 0, len = this._rings.length, clipped; i < len; i++) { clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds); if (clipped.length) { this._parts.push(clipped); } } }, _updatePath: function () { this._renderer._updatePoly(this, true); } }); L.polygon = function (latlngs, options) { return new L.Polygon(latlngs, options); }; /* * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. */ L.Rectangle = L.Polygon.extend({ initialize: function (latLngBounds, options) { L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); }, setBounds: function (latLngBounds) { this.setLatLngs(this._boundsToLatLngs(latLngBounds)); }, _boundsToLatLngs: function (latLngBounds) { latLngBounds = L.latLngBounds(latLngBounds); return [ latLngBounds.getSouthWest(), latLngBounds.getNorthWest(), latLngBounds.getNorthEast(), latLngBounds.getSouthEast() ]; } }); L.rectangle = function (latLngBounds, options) { return new L.Rectangle(latLngBounds, options); }; /* * L.CircleMarker is a circle overlay with a permanent pixel radius. */ L.CircleMarker = L.Path.extend({ options: { fill: true, radius: 10 }, initialize: function (latlng, options) { L.setOptions(this, options); this._latlng = L.latLng(latlng); this._radius = this.options.radius; }, setLatLng: function (latlng) { this._latlng = L.latLng(latlng); this.redraw(); return this.fire('move', {latlng: this._latlng}); }, getLatLng: function () { return this._latlng; }, setRadius: function (radius) { this.options.radius = this._radius = radius; return this.redraw(); }, getRadius: function () { return this._radius; }, setStyle : function (options) { var radius = options && options.radius || this._radius; L.Path.prototype.setStyle.call(this, options); this.setRadius(radius); return this; }, _project: function () { this._point = this._map.latLngToLayerPoint(this._latlng); this._updateBounds(); }, _updateBounds: function () { var r = this._radius, r2 = this._radiusY || r, w = this._clickTolerance(), p = [r + w, r2 + w]; this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); }, _update: function () { if (this._map) { this._updatePath(); } }, _updatePath: function () { this._renderer._updateCircle(this); }, _empty: function () { return this._radius && !this._renderer._bounds.intersects(this._pxBounds); } }); L.circleMarker = function (latlng, options) { return new L.CircleMarker(latlng, options); }; /* * L.Circle is a circle overlay (with a certain radius in meters). * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion) */ L.Circle = L.CircleMarker.extend({ initialize: function (latlng, radius, options) { L.setOptions(this, options); this._latlng = L.latLng(latlng); this._mRadius = radius; }, setRadius: function (radius) { this._mRadius = radius; return this.redraw(); }, getRadius: function () { return this._mRadius; }, getBounds: function () { var half = [this._radius, this._radiusY]; return new L.LatLngBounds( this._map.layerPointToLatLng(this._point.subtract(half)), this._map.layerPointToLatLng(this._point.add(half))); }, setStyle: L.Path.prototype.setStyle, _project: function () { var lng = this._latlng.lng, lat = this._latlng.lat, map = this._map, crs = map.options.crs; if (crs.distance === L.CRS.Earth.distance) { var d = Math.PI / 180, latR = (this._mRadius / L.CRS.Earth.R) / d, top = map.project([lat + latR, lng]), bottom = map.project([lat - latR, lng]), p = top.add(bottom).divideBy(2), lat2 = map.unproject(p).lat, lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 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); } else { var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); this._point = map.latLngToLayerPoint(this._latlng); this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; } this._updateBounds(); } }); L.circle = function (latlng, radius, options) { return new L.Circle(latlng, radius, options); }; /* * L.SVG renders vector layers with SVG. All SVG-specific code goes here. */ L.SVG = L.Renderer.extend({ _initContainer: function () { this._container = L.SVG.create('svg'); this._paths = {}; this._initEvents(); // makes it possible to click through svg root; we'll reset it back in individual paths this._container.setAttribute('pointer-events', 'none'); }, _update: function () { if (this._map._animatingZoom && this._bounds) { return; } L.Renderer.prototype._update.call(this); var b = this._bounds, size = b.getSize(), container = this._container; L.DomUtil.setPosition(container, b.min); // set size of svg-container if changed if (!this._svgSize || !this._svgSize.equals(size)) { this._svgSize = size; container.setAttribute('width', size.x); container.setAttribute('height', size.y); } // movement: update container viewBox so that we don't have to change coordinates of individual layers L.DomUtil.setPosition(container, b.min); container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); }, // methods below are called by vector layers implementations _initPath: function (layer) { var path = layer._path = L.SVG.create('path'); if (layer.options.className) { L.DomUtil.addClass(path, layer.options.className); } if (layer.options.interactive) { L.DomUtil.addClass(path, 'leaflet-interactive'); } this._updateStyle(layer); }, _addPath: function (layer) { var path = layer._path; this._container.appendChild(path); this._paths[L.stamp(path)] = layer; }, _removePath: function (layer) { var path = layer._path; L.DomUtil.remove(path); delete this._paths[L.stamp(path)]; }, _updatePath: function (layer) { layer._project(); layer._update(); }, _updateStyle: function (layer) { var path = layer._path, options = layer.options; if (!path) { return; } if (options.stroke) { path.setAttribute('stroke', options.color); path.setAttribute('stroke-opacity', options.opacity); path.setAttribute('stroke-width', options.weight); path.setAttribute('stroke-linecap', options.lineCap); path.setAttribute('stroke-linejoin', options.lineJoin); if (options.dashArray) { path.setAttribute('stroke-dasharray', options.dashArray); } else { path.removeAttribute('stroke-dasharray'); } if (options.dashOffset) { path.setAttribute('stroke-dashoffset', options.dashOffset); } else { path.removeAttribute('stroke-dashoffset'); } } else { path.setAttribute('stroke', 'none'); } if (options.fill) { path.setAttribute('fill', options.fillColor || options.color); path.setAttribute('fill-opacity', options.fillOpacity); path.setAttribute('fill-rule', options.fillRule || 'evenodd'); } else { path.setAttribute('fill', 'none'); } path.setAttribute('pointer-events', options.pointerEvents || (options.interactive ? 'visiblePainted' : 'none')); }, _updatePoly: function (layer, closed) { this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed)); }, _updateCircle: function (layer) { var p = layer._point, r = layer._radius, r2 = layer._radiusY || 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 '; this._setPath(layer, d); }, _setPath: function (layer, path) { layer._path.setAttribute('d', path); }, // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements _bringToFront: function (layer) { L.DomUtil.toFront(layer._path); }, _bringToBack: function (layer) { L.DomUtil.toBack(layer._path); }, // TODO remove duplication with L.Map _initEvents: function () { L.DomEvent.on(this._container, 'click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu', this._fireMouseEvent, this); }, _fireMouseEvent: function (e) { var path = this._paths[L.stamp(e.target || e.srcElement)]; if (path) { path._fireMouseEvent(e); } } }); L.extend(L.SVG, { create: function (name) { return document.createElementNS('http://www.w3.org/2000/svg', name); }, // generates SVG path string for multiple rings, with each ring turning into "M..L..L.." instructions pointsToPath: function (rings, closed) { var str = '', i, j, len, len2, points, p; for (i = 0, len = rings.length; i < len; i++) { points = rings[i]; for (j = 0, len2 = points.length; j < len2; j++) { p = points[j]; str += (j ? 'L' : 'M') + p.x + ' ' + p.y; } // closes the ring for polygons; "x" is VML syntax str += closed ? (L.Browser.svg ? 'z' : 'x') : ''; } // SVG complains about empty path strings return str || 'M0 0'; } }); L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect); L.svg = function (options) { return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null; }; /* * Vector rendering for IE7-8 through VML. * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! */ L.Browser.vml = !L.Browser.svg && (function () { try { var div = document.createElement('div'); div.innerHTML = ''; var shape = div.firstChild; shape.style.behavior = 'url(#default#VML)'; return shape && (typeof shape.adj === 'object'); } catch (e) { return false; } }()); // redefine some SVG methods to handle VML syntax which is similar but with some differences L.SVG.include(!L.Browser.vml ? {} : { _initContainer: function () { this._container = L.DomUtil.create('div', 'leaflet-vml-container'); this._paths = {}; this._initEvents(); }, _update: function () { if (this._map._animatingZoom) { return; } L.Renderer.prototype._update.call(this); }, _initPath: function (layer) { var container = layer._container = L.SVG.create('shape'); L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); container.coordsize = '1 1'; layer._path = L.SVG.create('path'); container.appendChild(layer._path); this._updateStyle(layer); }, _addPath: function (layer) { var container = layer._container; this._container.appendChild(container); this._paths[L.stamp(container)] = layer; }, _removePath: function (layer) { var container = layer._container; L.DomUtil.remove(container); delete this._paths[L.stamp(container)]; }, _updateStyle: function (layer) { var stroke = layer._stroke, fill = layer._fill, options = layer.options, container = layer._container; container.stroked = !!options.stroke; container.filled = !!options.fill; if (options.stroke) { if (!stroke) { stroke = layer._stroke = L.SVG.create('stroke'); container.appendChild(stroke); } stroke.weight = options.weight + 'px'; stroke.color = options.color; stroke.opacity = options.opacity; if (options.dashArray) { stroke.dashStyle = L.Util.isArray(options.dashArray) ? options.dashArray.join(' ') : options.dashArray.replace(/( *, *)/g, ' '); } else { stroke.dashStyle = ''; } stroke.endcap = options.lineCap.replace('butt', 'flat'); stroke.joinstyle = options.lineJoin; } else if (stroke) { container.removeChild(stroke); layer._stroke = null; } if (options.fill) { if (!fill) { fill = layer._fill = L.SVG.create('fill'); container.appendChild(fill); } fill.color = options.fillColor || options.color; fill.opacity = options.fillOpacity; } else if (fill) { container.removeChild(fill); layer._fill = null; } }, _updateCircle: function (layer) { var p = layer._point.round(), r = Math.round(layer._radius), r2 = Math.round(layer._radiusY || r); this._setPath(layer, layer._empty() ? 'M0 0' : 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); }, _setPath: function (layer, path) { layer._path.v = path; }, _bringToFront: function (layer) { L.DomUtil.toFront(layer._container); }, _bringToBack: function (layer) { L.DomUtil.toBack(layer._container); } }); if (L.Browser.vml) { L.SVG.create = (function () { try { document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); return function (name) { return document.createElement(''); }; } catch (e) { return function (name) { return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); }; } })(); } /* * L.Canvas handles Canvas vector layers rendering and mouse events handling. All Canvas-specific code goes here. */ L.Canvas = L.Renderer.extend({ onAdd: function () { L.Renderer.prototype.onAdd.call(this); this._layers = this._layers || {}; // redraw vectors since canvas is cleared upon removal this._draw(); }, _initContainer: function () { var container = this._container = document.createElement('canvas'); L.DomEvent .on(container, 'mousemove', this._onMouseMove, this) .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); this._ctx = container.getContext('2d'); }, _update: function () { if (this._map._animatingZoom && this._bounds) { return; } L.Renderer.prototype._update.call(this); var b = this._bounds, container = this._container, size = b.getSize(), m = L.Browser.retina ? 2 : 1; L.DomUtil.setPosition(container, b.min); // set canvas size (also clearing it); use double size on retina container.width = m * size.x; container.height = m * size.y; container.style.width = size.x + 'px'; container.style.height = size.y + 'px'; if (L.Browser.retina) { this._ctx.scale(2, 2); } // translate so we use the same path coordinates after canvas element moves this._ctx.translate(-b.min.x, -b.min.y); }, _initPath: function (layer) { this._layers[L.stamp(layer)] = layer; }, _addPath: L.Util.falseFn, _removePath: function (layer) { layer._removed = true; this._requestRedraw(layer); }, _updatePath: function (layer) { this._redrawBounds = layer._pxBounds; this._draw(true); layer._project(); layer._update(); this._draw(); this._redrawBounds = null; }, _updateStyle: function (layer) { this._requestRedraw(layer); }, _requestRedraw: function (layer) { if (!this._map) { return; } this._redrawBounds = this._redrawBounds || new L.Bounds(); this._redrawBounds.extend(layer._pxBounds.min).extend(layer._pxBounds.max); this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this); }, _redraw: function () { this._redrawRequest = null; this._draw(true); // clear layers in redraw bounds this._draw(); // draw layers this._redrawBounds = null; }, _draw: function (clear) { this._clear = clear; var layer; for (var id in this._layers) { layer = this._layers[id]; if (!this._redrawBounds || layer._pxBounds.intersects(this._redrawBounds)) { layer._updatePath(); } if (clear && layer._removed) { delete layer._removed; delete this._layers[id]; } } }, _updatePoly: function (layer, closed) { var i, j, len2, p, parts = layer._parts, len = parts.length, ctx = this._ctx; if (!len) { return; } ctx.beginPath(); for (i = 0; i < len; i++) { for (j = 0, len2 = parts[i].length; j < len2; j++) { p = parts[i][j]; ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); } if (closed) { ctx.closePath(); } } this._fillStroke(ctx, layer); // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature }, _updateCircle: function (layer) { if (layer._empty()) { return; } var p = layer._point, ctx = this._ctx, r = layer._radius, s = (layer._radiusY || r) / r; if (s !== 1) { ctx.save(); ctx.scale(1, s); } ctx.beginPath(); ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); if (s !== 1) { ctx.restore(); } this._fillStroke(ctx, layer); }, _fillStroke: function (ctx, layer) { var clear = this._clear, options = layer.options; ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over'; if (options.fill) { ctx.globalAlpha = clear ? 1 : options.fillOpacity; ctx.fillStyle = options.fillColor || options.color; ctx.fill(options.fillRule || 'evenodd'); } if (options.stroke) { ctx.globalAlpha = clear ? 1 : options.opacity; // if clearing shape, do it with the previously drawn line width layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight; ctx.strokeStyle = options.color; ctx.lineCap = options.lineCap; ctx.lineJoin = options.lineJoin; ctx.stroke(); } }, // Canvas obviously doesn't have mouse events for individual drawn objects, // so we emulate that by calculating what's under the mouse on mousemove/click manually _onClick: function (e) { var point = this._map.mouseEventToLayerPoint(e); for (var id in this._layers) { if (this._layers[id]._containsPoint(point)) { this._layers[id]._fireMouseEvent(e); } } }, _onMouseMove: function (e) { if (!this._map || this._map._animatingZoom) { return; } var point = this._map.mouseEventToLayerPoint(e); // TODO don't do on each move event, throttle since it's expensive for (var id in this._layers) { this._handleHover(this._layers[id], e, point); } }, _handleHover: function (layer, e, point) { if (!layer.options.interactive) { return; } if (layer._containsPoint(point)) { // if we just got inside the layer, fire mouseover if (!layer._mouseInside) { L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor layer._fireMouseEvent(e, 'mouseover'); layer._mouseInside = true; } // fire mousemove layer._fireMouseEvent(e); } else if (layer._mouseInside) { // if we're leaving the layer, fire mouseout L.DomUtil.removeClass(this._container, 'leaflet-interactive'); layer._fireMouseEvent(e, 'mouseout'); layer._mouseInside = false; } }, // TODO _bringToFront & _bringToBack, pretty tricky _bringToFront: L.Util.falseFn, _bringToBack: L.Util.falseFn }); L.Browser.canvas = (function () { return !!document.createElement('canvas').getContext; }()); L.canvas = function (options) { return L.Browser.canvas ? new L.Canvas(options) : null; }; L.Polyline.prototype._containsPoint = function (p, closed) { var i, j, k, len, len2, part, w = this._clickTolerance(); if (!this._pxBounds.contains(p)) { return false; } // hit detection for polylines for (i = 0, len = this._parts.length; i < len; i++) { part = this._parts[i]; for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { if (!closed && (j === 0)) { continue; } if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) { return true; } } } return false; }; L.Polygon.prototype._containsPoint = function (p) { var inside = false, part, p1, p2, i, j, k, len, len2; if (!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++) { part = this._parts[i]; for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { p1 = part[j]; p2 = part[k]; if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { inside = !inside; } } } // also check if it's on polygon stroke return inside || L.Polyline.prototype._containsPoint.call(this, p, true); }; L.CircleMarker.prototype._containsPoint = function (p) { return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); }; /* * L.GeoJSON turns any GeoJSON data into a Leaflet layer. */ L.GeoJSON = L.FeatureGroup.extend({ initialize: function (geojson, options) { L.setOptions(this, options); this._layers = {}; if (geojson) { this.addData(geojson); } }, addData: function (geojson) { var features = L.Util.isArray(geojson) ? geojson : geojson.features, i, len, feature; if (features) { for (i = 0, len = features.length; i < len; i++) { // only add this if geometry or geometries are set and not null feature = features[i]; if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { this.addData(feature); } } return this; } var options = this.options; if (options.filter && !options.filter(geojson)) { return; } var layer = L.GeoJSON.geometryToLayer(geojson, options); layer.feature = L.GeoJSON.asFeature(geojson); layer.defaultOptions = layer.options; this.resetStyle(layer); if (options.onEachFeature) { options.onEachFeature(geojson, layer); } return this.addLayer(layer); }, resetStyle: function (layer) { // reset any custom styles layer.options = layer.defaultOptions; this._setLayerStyle(layer, this.options.style); return this; }, setStyle: function (style) { return this.eachLayer(function (layer) { this._setLayerStyle(layer, style); }, this); }, _setLayerStyle: function (layer, style) { if (typeof style === 'function') { style = style(layer.feature); } if (layer.setStyle) { layer.setStyle(style); } } }); L.extend(L.GeoJSON, { geometryToLayer: function (geojson, options) { var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, coords = geometry.coordinates, layers = [], pointToLayer = options && options.pointToLayer, coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng, latlng, latlngs, i, len; switch (geometry.type) { case 'Point': latlng = coordsToLatLng(coords); return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); case 'MultiPoint': for (i = 0, len = coords.length; i < len; i++) { latlng = coordsToLatLng(coords[i]); layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); } return new L.FeatureGroup(layers); case 'LineString': case 'MultiLineString': latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng); return new L.Polyline(latlngs, options); case 'Polygon': case 'MultiPolygon': latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng); return new L.Polygon(latlngs, options); case 'GeometryCollection': for (i = 0, len = geometry.geometries.length; i < len; i++) { layers.push(this.geometryToLayer({ geometry: geometry.geometries[i], type: 'Feature', properties: geojson.properties }, options)); } return new L.FeatureGroup(layers); default: throw new Error('Invalid GeoJSON object.'); } }, coordsToLatLng: function (coords) { return new L.LatLng(coords[1], coords[0], coords[2]); }, coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { var latlngs = []; for (var i = 0, len = coords.length, latlng; i < len; i++) { latlng = levelsDeep ? this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : (coordsToLatLng || this.coordsToLatLng)(coords[i]); latlngs.push(latlng); } return latlngs; }, latLngToCoords: function (latlng) { return latlng.alt !== undefined ? [latlng.lng, latlng.lat, latlng.alt] : [latlng.lng, latlng.lat]; }, latLngsToCoords: function (latlngs, levelsDeep, closed) { var coords = []; for (var i = 0, len = latlngs.length; i < len; i++) { coords.push(levelsDeep ? L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed): L.GeoJSON.latLngToCoords(latlngs[i])); } if (!levelsDeep && closed) { coords.push(coords[0]); } return coords; }, getFeature: function (layer, newGeometry) { return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry); }, asFeature: function (geoJSON) { if (geoJSON.type === 'Feature') { return geoJSON; } return { type: 'Feature', properties: {}, geometry: geoJSON }; } }); var PointToGeoJSON = { toGeoJSON: function () { return L.GeoJSON.getFeature(this, { type: 'Point', coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) }); } }; L.Marker.include(PointToGeoJSON); L.Circle.include(PointToGeoJSON); L.CircleMarker.include(PointToGeoJSON); L.Polyline.prototype.toGeoJSON = function () { var multi = !this._flat(this._latlngs); var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0); return L.GeoJSON.getFeature(this, { type: (multi ? 'Multi' : '') + 'LineString', coordinates: coords }); }; L.Polygon.prototype.toGeoJSON = function () { var holes = !this._flat(this._latlngs), multi = holes && !this._flat(this._latlngs[0]); var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true); if (!holes) { coords = [coords]; } return L.GeoJSON.getFeature(this, { type: (multi ? 'Multi' : '') + 'Polygon', coordinates: coords }); }; L.LayerGroup.include({ toMultiPoint: function () { var coords = []; this.eachLayer(function (layer) { coords.push(layer.toGeoJSON().geometry.coordinates); }); return L.GeoJSON.getFeature(this, { type: 'MultiPoint', coordinates: coords }); }, toGeoJSON: function () { var type = this.feature && this.feature.geometry && this.feature.geometry.type; if (type === 'MultiPoint') { return this.toMultiPoint(); } var isGeometryCollection = type === 'GeometryCollection', jsons = []; this.eachLayer(function (layer) { if (layer.toGeoJSON) { var json = layer.toGeoJSON(); jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); } }); if (isGeometryCollection) { return L.GeoJSON.getFeature(this, { geometries: jsons, type: 'GeometryCollection' }); } return { type: 'FeatureCollection', features: jsons }; } }); L.geoJson = function (geojson, options) { return new L.GeoJSON(geojson, options); }; /* * L.DomEvent contains functions for working with DOM events. * Inspired by John Resig, Dean Edwards and YUI addEvent implementations. */ var eventsKey = '_leaflet_events'; L.DomEvent = { on: function (obj, types, fn, context) { if (typeof types === 'object') { for (var type in types) { this._on(obj, type, types[type], fn); } } else { types = L.Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._on(obj, types[i], fn, context); } } return this; }, off: function (obj, types, fn, context) { if (typeof types === 'object') { for (var type in types) { this._off(obj, type, types[type], fn); } } else { types = L.Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._off(obj, types[i], fn, context); } } return this; }, _on: function (obj, type, fn, context) { var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''); if (obj[eventsKey] && obj[eventsKey][id]) { return this; } var handler = function (e) { return fn.call(context || obj, e || window.event); }; var originalHandler = handler; if (L.Browser.pointer && type.indexOf('touch') === 0) { this.addPointerListener(obj, type, handler, id); } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { this.addDoubleTapListener(obj, handler, id); } else if ('addEventListener' in obj) { if (type === 'mousewheel') { obj.addEventListener('DOMMouseScroll', handler, false); obj.addEventListener(type, handler, false); } else if ((type === 'mouseenter') || (type === 'mouseleave')) { handler = function (e) { e = e || window.event; if (!L.DomEvent._checkMouse(obj, e)) { return; } return originalHandler(e); }; obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); } else { if (type === 'click' && L.Browser.android) { handler = function (e) { return L.DomEvent._filterClick(e, originalHandler); }; } obj.addEventListener(type, handler, false); } } else if ('attachEvent' in obj) { obj.attachEvent('on' + type, handler); } obj[eventsKey] = obj[eventsKey] || {}; obj[eventsKey][id] = handler; return this; }, _off: function (obj, type, fn, context) { var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''), handler = obj[eventsKey] && obj[eventsKey][id]; if (!handler) { return this; } if (L.Browser.pointer && type.indexOf('touch') === 0) { this.removePointerListener(obj, type, id); } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { this.removeDoubleTapListener(obj, id); } else if ('removeEventListener' in obj) { if (type === 'mousewheel') { obj.removeEventListener('DOMMouseScroll', handler, false); obj.removeEventListener(type, handler, false); } else { obj.removeEventListener( type === 'mouseenter' ? 'mouseover' : type === 'mouseleave' ? 'mouseout' : type, handler, false); } } else if ('detachEvent' in obj) { obj.detachEvent('on' + type, handler); } obj[eventsKey][id] = null; return this; }, stopPropagation: function (e) { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } L.DomEvent._skipped(e); return this; }, disableScrollPropagation: function (el) { return L.DomEvent.on(el, 'mousewheel MozMousePixelScroll', L.DomEvent.stopPropagation); }, disableClickPropagation: function (el) { var stop = L.DomEvent.stopPropagation; L.DomEvent.on(el, L.Draggable.START.join(' '), stop); return L.DomEvent.on(el, { click: L.DomEvent._fakeStop, dblclick: stop }); }, preventDefault: function (e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } return this; }, stop: function (e) { return L.DomEvent .preventDefault(e) .stopPropagation(e); }, getMousePosition: function (e, container) { if (!container) { return new L.Point(e.clientX, e.clientY); } var rect = container.getBoundingClientRect(); return new L.Point( e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop); }, getWheelDelta: function (e) { var delta = 0; if (e.wheelDelta) { delta = e.wheelDelta / 120; } if (e.detail) { delta = -e.detail / 3; } return delta; }, _skipEvents: {}, _fakeStop: function (e) { // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) L.DomEvent._skipEvents[e.type] = true; }, _skipped: function (e) { var skipped = this._skipEvents[e.type]; // reset when checking, as it's only used in map container and propagates outside of the map this._skipEvents[e.type] = false; return skipped; }, // check if element really left/entered the event target (for mouseenter/mouseleave) _checkMouse: function (el, e) { var related = e.relatedTarget; if (!related) { return true; } try { while (related && (related !== el)) { related = related.parentNode; } } catch (err) { return false; } return (related !== el); }, // this is a horrible workaround for a bug in Android where a single touch triggers two click events _filterClick: function (e, handler) { var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); // 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)) { L.DomEvent.stop(e); return; } L.DomEvent._lastClick = timeStamp; return handler(e); } }; L.DomEvent.addListener = L.DomEvent.on; L.DomEvent.removeListener = L.DomEvent.off; /* * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too. */ L.Draggable = L.Evented.extend({ statics: { START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], END: { mousedown: 'mouseup', touchstart: 'touchend', pointerdown: 'touchend', MSPointerDown: 'touchend' }, MOVE: { mousedown: 'mousemove', touchstart: 'touchmove', pointerdown: 'touchmove', MSPointerDown: 'touchmove' } }, initialize: function (element, dragStartTarget) { this._element = element; this._dragStartTarget = dragStartTarget || element; }, enable: function () { if (this._enabled) { return; } L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); this._enabled = true; }, disable: function () { if (!this._enabled) { return; } L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); this._enabled = false; this._moved = false; }, _onDown: function (e) { this._moved = false; if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } L.DomEvent.stopPropagation(e); if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } L.DomUtil.disableImageDrag(); L.DomUtil.disableTextSelection(); if (this._moving) { return; } this.fire('down'); var first = e.touches ? e.touches[0] : e; this._startPoint = new L.Point(first.clientX, first.clientY); this._startPos = this._newPos = L.DomUtil.getPosition(this._element); L.DomEvent .on(document, L.Draggable.MOVE[e.type], this._onMove, this) .on(document, L.Draggable.END[e.type], this._onUp, this); }, _onMove: function (e) { if (e.touches && e.touches.length > 1) { this._moved = true; return; } var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), newPoint = new L.Point(first.clientX, first.clientY), offset = newPoint.subtract(this._startPoint); if (!offset.x && !offset.y) { return; } if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; } L.DomEvent.preventDefault(e); if (!this._moved) { this.fire('dragstart'); this._moved = true; this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); L.DomUtil.addClass(document.body, 'leaflet-dragging'); this._lastTarget = e.target || e.srcElement; L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); } this._newPos = this._startPos.add(offset); this._moving = true; L.Util.cancelAnimFrame(this._animRequest); this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); }, _updatePosition: function () { this.fire('predrag'); L.DomUtil.setPosition(this._element, this._newPos); this.fire('drag'); }, _onUp: function () { L.DomUtil.removeClass(document.body, 'leaflet-dragging'); if (this._lastTarget) { L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); this._lastTarget = null; } for (var i in L.Draggable.MOVE) { L.DomEvent .off(document, L.Draggable.MOVE[i], this._onMove, this) .off(document, L.Draggable.END[i], this._onUp, this); } L.DomUtil.enableImageDrag(); L.DomUtil.enableTextSelection(); if (this._moved && this._moving) { // ensure drag is not fired after dragend L.Util.cancelAnimFrame(this._animRequest); this.fire('dragend', { distance: this._newPos.distanceTo(this._startPos) }); } this._moving = false; } }); /* L.Handler is a base class for handler classes that are used internally to inject interaction features like dragging to classes like Map and Marker. */ L.Handler = L.Class.extend({ initialize: function (map) { this._map = map; }, enable: function () { if (this._enabled) { return; } this._enabled = true; this.addHooks(); }, disable: function () { if (!this._enabled) { return; } this._enabled = false; this.removeHooks(); }, enabled: function () { return !!this._enabled; } }); /* * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. */ L.Map.mergeOptions({ dragging: true, inertia: !L.Browser.android23, inertiaDeceleration: 3400, // px/s^2 inertiaMaxSpeed: Infinity, // px/s easeLinearity: 0.2, // TODO refactor, move to CRS worldCopyJump: false }); L.Map.Drag = L.Handler.extend({ addHooks: function () { if (!this._draggable) { var map = this._map; this._draggable = new L.Draggable(map._mapPane, map._container); this._draggable.on({ down: this._onDown, dragstart: this._onDragStart, drag: this._onDrag, dragend: this._onDragEnd }, this); if (map.options.worldCopyJump) { this._draggable.on('predrag', this._onPreDrag, this); map.on('viewreset', this._onViewReset, this); map.whenReady(this._onViewReset, this); } } this._draggable.enable(); }, removeHooks: function () { this._draggable.disable(); }, moved: function () { return this._draggable && this._draggable._moved; }, _onDown: function () { this._map.stop(); }, _onDragStart: function () { var map = this._map; map .fire('movestart') .fire('dragstart'); if (map.options.inertia) { this._positions = []; this._times = []; } }, _onDrag: function () { if (this._map.options.inertia) { var time = this._lastTime = +new Date(), pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; this._positions.push(pos); this._times.push(time); if (time - this._times[0] > 50) { this._positions.shift(); this._times.shift(); } } this._map .fire('move') .fire('drag'); }, _onViewReset: function () { var pxCenter = this._map.getSize().divideBy(2), pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; this._worldWidth = this._map.getPixelWorldBounds().getSize().x; }, _onPreDrag: function () { // TODO refactor to be able to adjust map pane position after zoom var worldWidth = this._worldWidth, halfWidth = Math.round(worldWidth / 2), dx = this._initialWorldOffset, x = this._draggable._newPos.x, newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; this._draggable._absPos = this._draggable._newPos.clone(); this._draggable._newPos.x = newX; }, _onDragEnd: function (e) { var map = this._map, options = map.options, noInertia = !options.inertia || this._times.length < 2; map.fire('dragend', e); if (noInertia) { map.fire('moveend'); } else { var direction = this._lastPos.subtract(this._positions[0]), duration = (this._lastTime - this._times[0]) / 1000, ease = options.easeLinearity, speedVector = direction.multiplyBy(ease / duration), speed = speedVector.distanceTo([0, 0]), limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); if (!offset.x || !offset.y) { map.fire('moveend'); } else { offset = map._limitOffset(offset, map.options.maxBounds); L.Util.requestAnimFrame(function () { map.panBy(offset, { duration: decelerationDuration, easeLinearity: ease, noMoveStart: true, animate: true }); }); } } } }); L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); /* * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. */ L.Map.mergeOptions({ doubleClickZoom: true }); L.Map.DoubleClickZoom = L.Handler.extend({ addHooks: function () { this._map.on('dblclick', this._onDoubleClick, this); }, removeHooks: function () { this._map.off('dblclick', this._onDoubleClick, this); }, _onDoubleClick: function (e) { var map = this._map, oldZoom = map.getZoom(), zoom = e.originalEvent.shiftKey ? Math.ceil(oldZoom) - 1 : Math.floor(oldZoom) + 1; if (map.options.doubleClickZoom === 'center') { map.setZoom(zoom); } else { map.setZoomAround(e.containerPoint, zoom); } } }); L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); /* * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. */ L.Map.mergeOptions({ scrollWheelZoom: true, wheelDebounceTime: 40 }); L.Map.ScrollWheelZoom = L.Handler.extend({ addHooks: function () { L.DomEvent.on(this._map._container, { mousewheel: this._onWheelScroll, MozMousePixelScroll: L.DomEvent.preventDefault }, this); this._delta = 0; }, removeHooks: function () { L.DomEvent.off(this._map._container, { mousewheel: this._onWheelScroll, MozMousePixelScroll: L.DomEvent.preventDefault }, this); }, _onWheelScroll: function (e) { var delta = L.DomEvent.getWheelDelta(e); var debounce = this._map.options.wheelDebounceTime; this._delta += delta; this._lastMousePos = this._map.mouseEventToContainerPoint(e); if (!this._startTime) { this._startTime = +new Date(); } var left = Math.max(debounce - (+new Date() - this._startTime), 0); clearTimeout(this._timer); this._timer = setTimeout(L.bind(this._performZoom, this), left); L.DomEvent.stop(e); }, _performZoom: function () { var map = this._map, delta = this._delta, zoom = map.getZoom(); map.stop(); // stop panning and fly animations if any delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta); delta = Math.max(Math.min(delta, 4), -4); delta = map._limitZoom(zoom + delta) - zoom; this._delta = 0; this._startTime = null; if (!delta) { return; } if (map.options.scrollWheelZoom === 'center') { map.setZoom(zoom + delta); } else { map.setZoomAround(this._lastMousePos, zoom + delta); } } }); L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); /* * Extends the event handling code with double tap support for mobile browsers. */ L.extend(L.DomEvent, { _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', // inspired by Zepto touch code by Thomas Fuchs addDoubleTapListener: function (obj, handler, id) { var last, touch, doubleTap = false, delay = 250; function onTouchStart(e) { var count; if (L.Browser.pointer) { count = L.DomEvent._pointersCount; } else { count = e.touches.length; } if (count > 1) { return; } var now = Date.now(), delta = now - (last || now); touch = e.touches ? e.touches[0] : e; doubleTap = (delta > 0 && delta <= delay); last = now; } function onTouchEnd() { if (doubleTap) { if (L.Browser.pointer) { // work around .type being readonly with MSPointer* events var newTouch = {}, prop, i; for (i in touch) { prop = touch[i]; newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop; } touch = newTouch; } touch.type = 'dblclick'; handler(touch); last = null; } } var pre = '_leaflet_', touchstart = this._touchstart, touchend = this._touchend; obj[pre + touchstart + id] = onTouchStart; obj[pre + touchend + id] = onTouchEnd; obj.addEventListener(touchstart, onTouchStart, false); obj.addEventListener(touchend, onTouchEnd, false); return this; }, removeDoubleTapListener: function (obj, id) { var pre = '_leaflet_', touchend = obj[pre + this._touchend + id]; obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); obj.removeEventListener(this._touchend, touchend, false); return this; } }); /* * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. */ L.extend(L.DomEvent, { POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown', POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', _pointers: {}, _pointersCount: 0, // Provides a touch events wrapper for (ms)pointer events. // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 addPointerListener: function (obj, type, handler, id) { if (type === 'touchstart') { this._addPointerStart(obj, handler, id); } else if (type === 'touchmove') { this._addPointerMove(obj, handler, id); } else if (type === 'touchend') { this._addPointerEnd(obj, handler, id); } return this; }, removePointerListener: function (obj, type, id) { var handler = obj['_leaflet_' + type + id]; if (type === 'touchstart') { obj.removeEventListener(this.POINTER_DOWN, handler, false); } else if (type === 'touchmove') { obj.removeEventListener(this.POINTER_MOVE, handler, false); } else if (type === 'touchend') { obj.removeEventListener(this.POINTER_UP, handler, false); obj.removeEventListener(this.POINTER_CANCEL, handler, false); } return this; }, _addPointerStart: function (obj, handler, id) { var onDown = L.bind(function (e) { L.DomEvent.preventDefault(e); this._handlePointer(e, handler); }, this); obj['_leaflet_touchstart' + id] = onDown; obj.addEventListener(this.POINTER_DOWN, onDown, false); // need to keep track of what pointers and how many are active to provide e.touches emulation if (!this._pointerDocListener) { var pointerUp = L.bind(this._globalPointerUp, this); // we listen documentElement as any drags that end by moving the touch off the screen get fired there document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true); document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true); document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true); document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true); this._pointerDocListener = true; } }, _globalPointerDown: function (e) { this._pointers[e.pointerId] = e; this._pointersCount++; }, _globalPointerMove: function (e) { if (this._pointers[e.pointerId]) { this._pointers[e.pointerId] = e; } }, _globalPointerUp: function (e) { delete this._pointers[e.pointerId]; this._pointersCount--; }, _handlePointer: function (e, handler) { e.touches = []; for (var i in this._pointers) { e.touches.push(this._pointers[i]); } e.changedTouches = [e]; handler(e); }, _addPointerMove: function (obj, handler, id) { var onMove = L.bind(function (e) { // don't fire touch moves when mouse isn't down if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } this._handlePointer(e, handler); }, this); obj['_leaflet_touchmove' + id] = onMove; obj.addEventListener(this.POINTER_MOVE, onMove, false); }, _addPointerEnd: function (obj, handler, id) { var onUp = L.bind(function (e) { this._handlePointer(e, handler); }, this); obj['_leaflet_touchend' + id] = onUp; obj.addEventListener(this.POINTER_UP, onUp, false); obj.addEventListener(this.POINTER_CANCEL, onUp, false); } }); /* * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. */ L.Map.mergeOptions({ touchZoom: L.Browser.touch && !L.Browser.android23, bounceAtZoomLimits: true }); L.Map.TouchZoom = L.Handler.extend({ addHooks: function () { L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); }, removeHooks: function () { L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); }, _onTouchStart: function (e) { var map = this._map; if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } var p1 = map.mouseEventToLayerPoint(e.touches[0]), p2 = map.mouseEventToLayerPoint(e.touches[1]), viewCenter = map._getCenterLayerPoint(); this._startCenter = p1.add(p2)._divideBy(2); this._startDist = p1.distanceTo(p2); this._moved = false; this._zooming = true; this._centerOffset = viewCenter.subtract(this._startCenter); map.stop(); L.DomEvent .on(document, 'touchmove', this._onTouchMove, this) .on(document, 'touchend', this._onTouchEnd, this); L.DomEvent.preventDefault(e); }, _onTouchMove: function (e) { if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } var map = this._map, p1 = map.mouseEventToLayerPoint(e.touches[0]), p2 = map.mouseEventToLayerPoint(e.touches[1]); this._scale = p1.distanceTo(p2) / this._startDist; this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter); if (!map.options.bounceAtZoomLimits) { var currentZoom = map.getScaleZoom(this._scale); if ((currentZoom <= map.getMinZoom() && this._scale < 1) || (currentZoom >= map.getMaxZoom() && this._scale > 1)) { return; } } if (!this._moved) { map .fire('movestart') .fire('zoomstart'); this._moved = true; } L.Util.cancelAnimFrame(this._animRequest); this._animRequest = L.Util.requestAnimFrame(this._updateOnMove, this, true, this._map._container); L.DomEvent.preventDefault(e); }, _updateOnMove: function () { var map = this._map; if (map.options.touchZoom === 'center') { this._center = map.getCenter(); } else { this._center = map.layerPointToLatLng(this._getTargetCenter()); } this._zoom = map.getScaleZoom(this._scale); map._animateZoom(this._center, this._zoom); }, _onTouchEnd: function () { if (!this._moved || !this._zooming) { this._zooming = false; return; } this._zooming = false; L.Util.cancelAnimFrame(this._animRequest); L.DomEvent .off(document, 'touchmove', this._onTouchMove) .off(document, 'touchend', this._onTouchEnd); var map = this._map, oldZoom = map.getZoom(), zoomDelta = this._zoom - oldZoom, finalZoom = map._limitZoom(zoomDelta > 0 ? Math.ceil(this._zoom) : Math.floor(this._zoom)); map._animateZoom(this._center, finalZoom, true); }, _getTargetCenter: function () { var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale); return this._startCenter.add(centerOffset); } }); L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); /* * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. */ L.Map.mergeOptions({ tap: true, tapTolerance: 15 }); L.Map.Tap = L.Handler.extend({ addHooks: function () { L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); }, removeHooks: function () { L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); }, _onDown: function (e) { if (!e.touches) { return; } L.DomEvent.preventDefault(e); this._fireClick = true; // don't simulate click or track longpress if more than 1 touch if (e.touches.length > 1) { this._fireClick = false; clearTimeout(this._holdTimeout); return; } var first = e.touches[0], el = first.target; this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); // if touching a link, highlight it if (el.tagName && el.tagName.toLowerCase() === 'a') { L.DomUtil.addClass(el, 'leaflet-active'); } // simulate long hold but setting a timeout this._holdTimeout = setTimeout(L.bind(function () { if (this._isTapValid()) { this._fireClick = false; this._onUp(); this._simulateEvent('contextmenu', first); } }, this), 1000); this._simulateEvent('mousedown', first); L.DomEvent.on(document, { touchmove: this._onMove, touchend: this._onUp }, this); }, _onUp: function (e) { clearTimeout(this._holdTimeout); L.DomEvent.off(document, { touchmove: this._onMove, touchend: this._onUp }, this); if (this._fireClick && e && e.changedTouches) { var first = e.changedTouches[0], el = first.target; if (el && el.tagName && el.tagName.toLowerCase() === 'a') { L.DomUtil.removeClass(el, 'leaflet-active'); } this._simulateEvent('mouseup', first); // simulate click if the touch didn't move too much if (this._isTapValid()) { this._simulateEvent('click', first); } } }, _isTapValid: function () { return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; }, _onMove: function (e) { var first = e.touches[0]; this._newPos = new L.Point(first.clientX, first.clientY); }, _simulateEvent: function (type, e) { var simulatedEvent = document.createEvent('MouseEvents'); simulatedEvent._simulated = true; e.target._simulatedClick = true; simulatedEvent.initMouseEvent( type, true, true, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, false, false, false, false, 0, null); e.target.dispatchEvent(simulatedEvent); } }); if (L.Browser.touch && !L.Browser.pointer) { L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); } /* * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map * (zoom to a selected bounding box), enabled by default. */ L.Map.mergeOptions({ boxZoom: true }); L.Map.BoxZoom = L.Handler.extend({ initialize: function (map) { this._map = map; this._container = map._container; this._pane = map._panes.overlayPane; }, addHooks: function () { L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); }, removeHooks: function () { L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); }, moved: function () { return this._moved; }, _onMouseDown: function (e) { if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } this._moved = false; L.DomUtil.disableTextSelection(); L.DomUtil.disableImageDrag(); this._startPoint = this._map.mouseEventToContainerPoint(e); L.DomEvent.on(document, { contextmenu: L.DomEvent.stop, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown }, this); }, _onMouseMove: function (e) { if (!this._moved) { this._moved = true; this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container); L.DomUtil.addClass(this._container, 'leaflet-crosshair'); this._map.fire('boxzoomstart'); } this._point = this._map.mouseEventToContainerPoint(e); var bounds = new L.Bounds(this._point, this._startPoint), size = bounds.getSize(); L.DomUtil.setPosition(this._box, bounds.min); this._box.style.width = size.x + 'px'; this._box.style.height = size.y + 'px'; }, _finish: function () { if (this._moved) { L.DomUtil.remove(this._box); L.DomUtil.removeClass(this._container, 'leaflet-crosshair'); } L.DomUtil.enableTextSelection(); L.DomUtil.enableImageDrag(); L.DomEvent.off(document, { contextmenu: L.DomEvent.stop, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown }, this); }, _onMouseUp: function (e) { if ((e.which !== 1) && (e.button !== 1)) { return false; } this._finish(); if (!this._moved) { return; } var bounds = new L.LatLngBounds( this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); this._map .fitBounds(bounds) .fire('boxzoomend', {boxZoomBounds: bounds}); }, _onKeyDown: function (e) { if (e.keyCode === 27) { this._finish(); } } }); L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); /* * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. */ L.Map.mergeOptions({ keyboard: true, keyboardPanOffset: 80, keyboardZoomOffset: 1 }); L.Map.Keyboard = L.Handler.extend({ keyCodes: { left: [37], right: [39], down: [40], up: [38], zoomIn: [187, 107, 61, 171], zoomOut: [189, 109, 173] }, initialize: function (map) { this._map = map; this._setPanOffset(map.options.keyboardPanOffset); this._setZoomOffset(map.options.keyboardZoomOffset); }, addHooks: function () { var container = this._map._container; // make the container focusable by tabbing if (container.tabIndex === -1) { container.tabIndex = '0'; } L.DomEvent.on(container, { focus: this._onFocus, blur: this._onBlur, mousedown: this._onMouseDown }, this); this._map.on({ focus: this._addHooks, blur: this._removeHooks }, this); }, removeHooks: function () { this._removeHooks(); L.DomEvent.off(this._map._container, { focus: this._onFocus, blur: this._onBlur, mousedown: this._onMouseDown }, this); this._map.off({ focus: this._addHooks, blur: this._removeHooks }, this); }, _onMouseDown: function () { if (this._focused) { return; } var body = document.body, docEl = document.documentElement, top = body.scrollTop || docEl.scrollTop, left = body.scrollLeft || docEl.scrollLeft; this._map._container.focus(); window.scrollTo(left, top); }, _onFocus: function () { this._focused = true; this._map.fire('focus'); }, _onBlur: function () { this._focused = false; this._map.fire('blur'); }, _setPanOffset: function (pan) { var keys = this._panKeys = {}, codes = this.keyCodes, i, len; for (i = 0, len = codes.left.length; i < len; i++) { keys[codes.left[i]] = [-1 * pan, 0]; } for (i = 0, len = codes.right.length; i < len; i++) { keys[codes.right[i]] = [pan, 0]; } for (i = 0, len = codes.down.length; i < len; i++) { keys[codes.down[i]] = [0, pan]; } for (i = 0, len = codes.up.length; i < len; i++) { keys[codes.up[i]] = [0, -1 * pan]; } }, _setZoomOffset: function (zoom) { var keys = this._zoomKeys = {}, codes = this.keyCodes, i, len; for (i = 0, len = codes.zoomIn.length; i < len; i++) { keys[codes.zoomIn[i]] = zoom; } for (i = 0, len = codes.zoomOut.length; i < len; i++) { keys[codes.zoomOut[i]] = -zoom; } }, _addHooks: function () { L.DomEvent.on(document, 'keydown', this._onKeyDown, this); }, _removeHooks: function () { L.DomEvent.off(document, 'keydown', this._onKeyDown, this); }, _onKeyDown: function (e) { if (e.altKey || e.ctrlKey || e.metaKey) { return; } var key = e.keyCode, map = this._map; if (key in this._panKeys) { if (map._panAnim && map._panAnim._inProgress) { return; } map.panBy(this._panKeys[key]); 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.closePopup(); } else { return; } L.DomEvent.stop(e); } }); L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); /* * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. */ L.Handler.MarkerDrag = L.Handler.extend({ initialize: function (marker) { this._marker = marker; }, addHooks: function () { var icon = this._marker._icon; if (!this._draggable) { this._draggable = new L.Draggable(icon, icon); } this._draggable.on({ dragstart: this._onDragStart, drag: this._onDrag, dragend: this._onDragEnd }, this).enable(); L.DomUtil.addClass(icon, 'leaflet-marker-draggable'); }, removeHooks: function () { this._draggable.off({ dragstart: this._onDragStart, drag: this._onDrag, dragend: this._onDragEnd }, this).disable(); L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); }, moved: function () { return this._draggable && this._draggable._moved; }, _onDragStart: function () { this._marker .closePopup() .fire('movestart') .fire('dragstart'); }, _onDrag: function () { var marker = this._marker, shadow = marker._shadow, iconPos = L.DomUtil.getPosition(marker._icon), latlng = marker._map.layerPointToLatLng(iconPos); // update shadow position if (shadow) { L.DomUtil.setPosition(shadow, iconPos); } marker._latlng = latlng; marker .fire('move', {latlng: latlng}) .fire('drag'); }, _onDragEnd: function (e) { this._marker .fire('moveend') .fire('dragend', e); } }); /* * L.Control is a base class for implementing map controls. Handles positioning. * All other controls extend from this class. */ L.Control = L.Class.extend({ options: { position: 'topright' }, initialize: function (options) { L.setOptions(this, options); }, getPosition: function () { return this.options.position; }, setPosition: function (position) { var map = this._map; if (map) { map.removeControl(this); } this.options.position = position; if (map) { map.addControl(this); } return this; }, getContainer: function () { return this._container; }, addTo: function (map) { this.remove(); this._map = map; var container = this._container = this.onAdd(map), pos = this.getPosition(), corner = map._controlCorners[pos]; L.DomUtil.addClass(container, 'leaflet-control'); if (pos.indexOf('bottom') !== -1) { corner.insertBefore(container, corner.firstChild); } else { corner.appendChild(container); } return this; }, remove: function () { if (!this._map) { return this; } L.DomUtil.remove(this._container); if (this.onRemove) { this.onRemove(this._map); } this._map = null; return this; }, _refocusOnMap: function () { if (this._map) { this._map.getContainer().focus(); } } }); L.control = function (options) { return new L.Control(options); }; // adds control-related methods to L.Map L.Map.include({ addControl: function (control) { control.addTo(this); return this; }, removeControl: function (control) { control.remove(); return this; }, _initControlPos: function () { var corners = this._controlCorners = {}, l = 'leaflet-', container = this._controlContainer = L.DomUtil.create('div', l + 'control-container', this._container); function createCorner(vSide, hSide) { var className = l + vSide + ' ' + l + hSide; corners[vSide + hSide] = L.DomUtil.create('div', className, container); } createCorner('top', 'left'); createCorner('top', 'right'); createCorner('bottom', 'left'); createCorner('bottom', 'right'); }, _clearControlPos: function () { L.DomUtil.remove(this._controlContainer); } }); /* * L.Control.Zoom is used for the default zoom buttons on the map. */ L.Control.Zoom = L.Control.extend({ options: { position: 'topleft', zoomInText: '+', zoomInTitle: 'Zoom in', zoomOutText: '-', zoomOutTitle: 'Zoom out' }, onAdd: function (map) { var zoomName = 'leaflet-control-zoom', container = L.DomUtil.create('div', zoomName + ' leaflet-bar'), options = this.options; this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, zoomName + '-in', container, this._zoomIn); this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, zoomName + '-out', container, this._zoomOut); this._updateDisabled(); map.on('zoomend zoomlevelschange', this._updateDisabled, this); return container; }, onRemove: function (map) { map.off('zoomend zoomlevelschange', this._updateDisabled, this); }, _zoomIn: function (e) { this._map.zoomIn(e.shiftKey ? 3 : 1); }, _zoomOut: function (e) { this._map.zoomOut(e.shiftKey ? 3 : 1); }, _createButton: function (html, title, className, container, fn) { var link = L.DomUtil.create('a', className, container); link.innerHTML = html; link.href = '#'; link.title = title; L.DomEvent .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) .on(link, 'click', L.DomEvent.stop) .on(link, 'click', fn, this) .on(link, 'click', this._refocusOnMap, this); return link; }, _updateDisabled: function () { var map = this._map, className = 'leaflet-disabled'; L.DomUtil.removeClass(this._zoomInButton, className); L.DomUtil.removeClass(this._zoomOutButton, className); if (map._zoom === map.getMinZoom()) { L.DomUtil.addClass(this._zoomOutButton, className); } if (map._zoom === map.getMaxZoom()) { L.DomUtil.addClass(this._zoomInButton, className); } } }); L.Map.mergeOptions({ zoomControl: true }); L.Map.addInitHook(function () { if (this.options.zoomControl) { this.zoomControl = new L.Control.Zoom(); this.addControl(this.zoomControl); } }); L.control.zoom = function (options) { return new L.Control.Zoom(options); }; /* * L.Control.Attribution is used for displaying attribution on the map (added by default). */ L.Control.Attribution = L.Control.extend({ options: { position: 'bottomright', prefix: 'Leaflet' }, initialize: function (options) { L.setOptions(this, options); this._attributions = {}; }, onAdd: function (map) { this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); if (L.DomEvent) { L.DomEvent.disableClickPropagation(this._container); } // TODO ugly, refactor for (var i in map._layers) { if (map._layers[i].getAttribution) { this.addAttribution(map._layers[i].getAttribution()); } } this._update(); return this._container; }, setPrefix: function (prefix) { this.options.prefix = prefix; this._update(); return this; }, addAttribution: function (text) { if (!text) { return; } if (!this._attributions[text]) { this._attributions[text] = 0; } this._attributions[text]++; this._update(); return this; }, removeAttribution: function (text) { if (!text) { return; } if (this._attributions[text]) { this._attributions[text]--; this._update(); } return this; }, _update: function () { if (!this._map) { return; } var attribs = []; for (var i in this._attributions) { if (this._attributions[i]) { attribs.push(i); } } var prefixAndAttribs = []; if (this.options.prefix) { prefixAndAttribs.push(this.options.prefix); } if (attribs.length) { prefixAndAttribs.push(attribs.join(', ')); } this._container.innerHTML = prefixAndAttribs.join(' | '); } }); L.Map.mergeOptions({ attributionControl: true }); L.Map.addInitHook(function () { if (this.options.attributionControl) { this.attributionControl = (new L.Control.Attribution()).addTo(this); } }); L.control.attribution = function (options) { return new L.Control.Attribution(options); }; /* * L.Control.Scale is used for displaying metric/imperial scale on the map. */ L.Control.Scale = L.Control.extend({ options: { position: 'bottomleft', maxWidth: 100, metric: true, imperial: true // updateWhenIdle: false }, onAdd: function (map) { var className = 'leaflet-control-scale', container = L.DomUtil.create('div', className), options = this.options; this._addScales(options, className + '-line', container); map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); map.whenReady(this._update, this); return container; }, onRemove: function (map) { map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); }, _addScales: function (options, className, container) { if (options.metric) { this._mScale = L.DomUtil.create('div', className, container); } if (options.imperial) { this._iScale = L.DomUtil.create('div', className, container); } }, _update: function () { var map = this._map, y = map.getSize().y / 2; var maxMeters = L.CRS.Earth.distance( map.containerPointToLatLng([0, y]), map.containerPointToLatLng([this.options.maxWidth, y])); this._updateScales(maxMeters); }, _updateScales: function (maxMeters) { if (this.options.metric && maxMeters) { this._updateMetric(maxMeters); } if (this.options.imperial && maxMeters) { this._updateImperial(maxMeters); } }, _updateMetric: function (maxMeters) { var meters = this._getRoundNum(maxMeters), label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; this._updateScale(this._mScale, label, meters / maxMeters); }, _updateImperial: function (maxMeters) { var maxFeet = maxMeters * 3.2808399, maxMiles, miles, feet; if (maxFeet > 5280) { maxMiles = maxFeet / 5280; miles = this._getRoundNum(maxMiles); this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); } else { feet = this._getRoundNum(maxFeet); this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); } }, _updateScale: function (scale, text, ratio) { scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; scale.innerHTML = text; }, _getRoundNum: function (num) { var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), d = num / pow10; d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1; return pow10 * d; } }); L.control.scale = function (options) { return new L.Control.Scale(options); }; /* * L.Control.Layers is a control to allow users to switch between different layers on the map. */ L.Control.Layers = L.Control.extend({ options: { collapsed: true, position: 'topright', autoZIndex: true, hideSingleBase: false }, initialize: function (baseLayers, overlays, options) { L.setOptions(this, options); this._layers = {}; this._lastZIndex = 0; this._handlingClick = false; for (var i in baseLayers) { this._addLayer(baseLayers[i], i); } for (i in overlays) { this._addLayer(overlays[i], i, true); } }, onAdd: function () { this._initLayout(); this._update(); return this._container; }, addBaseLayer: function (layer, name) { this._addLayer(layer, name); return this._update(); }, addOverlay: function (layer, name) { this._addLayer(layer, name, true); return this._update(); }, removeLayer: function (layer) { layer.off('add remove', this._onLayerChange, this); delete this._layers[L.stamp(layer)]; return this._update(); }, _initLayout: function () { var className = 'leaflet-control-layers', container = this._container = L.DomUtil.create('div', className); // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released container.setAttribute('aria-haspopup', true); if (!L.Browser.touch) { L.DomEvent .disableClickPropagation(container) .disableScrollPropagation(container); } else { L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); } var form = this._form = L.DomUtil.create('form', className + '-list'); if (this.options.collapsed) { if (!L.Browser.android) { L.DomEvent.on(container, { mouseenter: this._expand, mouseleave: this._collapse }, this); } var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); link.href = '#'; link.title = 'Layers'; if (L.Browser.touch) { L.DomEvent .on(link, 'click', L.DomEvent.stop) .on(link, 'click', this._expand, this); } else { L.DomEvent.on(link, 'focus', this._expand, this); } // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033 L.DomEvent.on(form, 'click', function () { setTimeout(L.bind(this._onInputClick, this), 0); }, this); this._map.on('click', this._collapse, this); // TODO keyboard accessibility } else { this._expand(); } this._baseLayersList = L.DomUtil.create('div', className + '-base', form); this._separator = L.DomUtil.create('div', className + '-separator', form); this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); container.appendChild(form); }, _addLayer: function (layer, name, overlay) { layer.on('add remove', this._onLayerChange, this); var id = L.stamp(layer); this._layers[id] = { layer: layer, name: name, overlay: overlay }; if (this.options.autoZIndex && layer.setZIndex) { this._lastZIndex++; layer.setZIndex(this._lastZIndex); } }, _update: function () { if (!this._container) { return; } L.DomUtil.empty(this._baseLayersList); L.DomUtil.empty(this._overlaysList); var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; for (i in this._layers) { obj = this._layers[i]; this._addItem(obj); overlaysPresent = overlaysPresent || obj.overlay; baseLayersPresent = baseLayersPresent || !obj.overlay; baseLayersCount += !obj.overlay ? 1 : 0; } // Hide base layers section if there's only one layer. if (this.options.hideSingleBase) { baseLayersPresent = baseLayersPresent && baseLayersCount > 1; this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; } this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; return this; }, _onLayerChange: function (e) { if (!this._handlingClick) { this._update(); } var overlay = this._layers[L.stamp(e.target)].overlay; var type = overlay ? (e.type === 'add' ? 'overlayadd' : 'overlayremove') : (e.type === 'add' ? 'baselayerchange' : null); if (type) { this._map.fire(type, e.target); } }, // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) _createRadioElement: function (name, checked) { var radioHtml = ''; var radioFragment = document.createElement('div'); radioFragment.innerHTML = radioHtml; return radioFragment.firstChild; }, _addItem: function (obj) { var label = document.createElement('label'), checked = this._map.hasLayer(obj.layer), input; if (obj.overlay) { input = document.createElement('input'); input.type = 'checkbox'; input.className = 'leaflet-control-layers-selector'; input.defaultChecked = checked; } else { input = this._createRadioElement('leaflet-base-layers', checked); } input.layerId = L.stamp(obj.layer); L.DomEvent.on(input, 'click', this._onInputClick, this); var name = document.createElement('span'); name.innerHTML = ' ' + obj.name; label.appendChild(input); label.appendChild(name); var container = obj.overlay ? this._overlaysList : this._baseLayersList; container.appendChild(label); return label; }, _onInputClick: function () { var inputs = this._form.getElementsByTagName('input'), input, layer, hasLayer; var addedLayers = [], removedLayers = []; this._handlingClick = true; for (var i = 0, len = inputs.length; i < len; i++) { input = inputs[i]; layer = this._layers[input.layerId].layer; hasLayer = this._map.hasLayer(layer); if (input.checked && !hasLayer) { addedLayers.push(layer); } else if (!input.checked && hasLayer) { removedLayers.push(layer); } } // Bugfix issue 2318: Should remove all old layers before readding new ones for (i = 0; i < removedLayers.length; i++) { this._map.removeLayer(removedLayers[i]); } for (i = 0; i < addedLayers.length; i++) { this._map.addLayer(addedLayers[i]); } this._handlingClick = false; this._refocusOnMap(); }, _expand: function () { L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); }, _collapse: function () { L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); } }); L.control.layers = function (baseLayers, overlays, options) { return new L.Control.Layers(baseLayers, overlays, options); }; /* * L.PosAnimation powers Leaflet pan animations internally. */ L.PosAnimation = L.Evented.extend({ run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number]) this.stop(); this._el = el; this._inProgress = true; this._duration = duration || 0.25; this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); this._startPos = L.DomUtil.getPosition(el); this._offset = newPos.subtract(this._startPos); this._startTime = +new Date(); this.fire('start'); this._animate(); }, stop: function () { if (!this._inProgress) { return; } this._step(true); this._complete(); }, _animate: function () { // animation loop this._animId = L.Util.requestAnimFrame(this._animate, this); this._step(); }, _step: function (round) { var elapsed = (+new Date()) - this._startTime, duration = this._duration * 1000; if (elapsed < duration) { this._runFrame(this._easeOut(elapsed / duration), round); } else { this._runFrame(1); this._complete(); } }, _runFrame: function (progress, round) { var pos = this._startPos.add(this._offset.multiplyBy(progress)); if (round) { pos._round(); } L.DomUtil.setPosition(this._el, pos); this.fire('step'); }, _complete: function () { L.Util.cancelAnimFrame(this._animId); this._inProgress = false; this.fire('end'); }, _easeOut: function (t) { return 1 - Math.pow(1 - t, this._easeOutPower); } }); /* * Extends L.Map to handle panning animations. */ L.Map.include({ setView: function (center, zoom, options) { zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds); options = options || {}; this.stop(); if (this._loaded && !options.reset && options !== true) { if (options.animate !== undefined) { options.zoom = L.extend({animate: options.animate}, options.zoom); options.pan = L.extend({animate: options.animate}, options.pan); } // try animating pan or zoom var animated = (this._zoom !== zoom) ? this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : this._tryAnimatedPan(center, options.pan); if (animated) { // prevent resize handler call, the view will refresh after animation anyway clearTimeout(this._sizeTimer); return this; } } // animation didn't start, just reset the map view this._resetView(center, zoom); return this; }, panBy: function (offset, options) { offset = L.point(offset).round(); options = options || {}; if (!offset.x && !offset.y) { return this; } //If we pan too far then chrome gets issues with tiles // and makes them disappear or appear in the wrong place (slightly offset) #2602 if (options.animate !== true && !this.getSize().contains(offset)) { return this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); } if (!this._panAnim) { this._panAnim = new L.PosAnimation(); this._panAnim.on({ 'step': this._onPanTransitionStep, 'end': this._onPanTransitionEnd }, this); } // don't fire movestart if animating inertia if (!options.noMoveStart) { this.fire('movestart'); } // animate pan unless animate: false specified if (options.animate !== false) { L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); var newPos = this._getMapPanePos().subtract(offset); this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); } else { this._rawPanBy(offset); this.fire('move').fire('moveend'); } return this; }, _onPanTransitionStep: function () { this.fire('move'); }, _onPanTransitionEnd: function () { L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); this.fire('moveend'); }, _tryAnimatedPan: function (center, options) { // difference between the new and current centers in pixels var offset = this._getCenterOffset(center)._floor(); // don't animate too far unless animate: true specified in options if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } this.panBy(offset, options); return (options && options.animate) !== false; } }); /* * Extends L.Map to handle zoom animations. */ L.Map.mergeOptions({ zoomAnimation: true, zoomAnimationThreshold: 4 }); var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera; if (zoomAnimated) { L.Map.addInitHook(function () { // don't animate on browsers without hardware-accelerated transitions or old Android/Opera this._zoomAnimated = this.options.zoomAnimation; // zoom transitions run with the same duration for all layers, so if one of transitionend events // happens after starting zoom animation (propagating to the map pane), we know that it ended globally if (this._zoomAnimated) { this._createAnimProxy(); L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); } }); } L.Map.include(!zoomAnimated ? {} : { _createAnimProxy: function () { var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated'); this._panes.mapPane.appendChild(proxy); this.on('zoomanim', function (e) { L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); }, this); this.on('load moveend', function () { var c = this.getCenter(), z = this.getZoom(); L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); }, this); }, _catchTransitionEnd: function (e) { if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { this._onZoomTransitionEnd(); } }, _nothingToAnimate: function () { return !this._container.getElementsByClassName('leaflet-zoom-animated').length; }, _tryAnimatedZoom: function (center, zoom, options) { if (this._animatingZoom) { return true; } options = options || {}; // don't animate if disabled, not supported or zoom difference is too large if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } // offset is the pixel coords of the zoom origin relative to the current center var scale = this.getZoomScale(zoom), offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); // don't animate if the zoom origin isn't within one screen from the current center, unless forced if (options.animate !== true && !this.getSize().contains(offset)) { return false; } L.Util.requestAnimFrame(function () { this .fire('movestart') .fire('zoomstart') ._animateZoom(center, zoom, true); }, this); return true; }, _animateZoom: function (center, zoom, startAnim) { if (startAnim) { this._animatingZoom = true; // remember what center/zoom to set after animation this._animateToCenter = center; this._animateToZoom = zoom; L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); } this.fire('zoomanim', { center: center, zoom: zoom, scale: this.getZoomScale(zoom), origin: this.latLngToLayerPoint(center), offset: this._getCenterOffset(center).multiplyBy(-1) }); }, _onZoomTransitionEnd: function () { this._animatingZoom = false; L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); this._resetView(this._animateToCenter, this._animateToZoom, true, true); } }); L.Map.include({ flyTo: function (targetCenter, targetZoom) { this.stop(); var from = this.project(this.getCenter()), to = this.project(targetCenter), size = this.getSize(), startZoom = this._zoom; targetCenter = L.latLng(targetCenter); targetZoom = targetZoom === undefined ? startZoom : targetZoom; var w0 = Math.max(size.x, size.y), w1 = w0 * this.getZoomScale(startZoom, targetZoom), u1 = to.distanceTo(from), rho = 1.42, rho2 = rho * rho; function r(i) { var b = (w1 * w1 - w0 * w0 + (i ? -1 : 1) * rho2 * rho2 * u1 * u1) / (2 * (i ? w1 : w0) * rho2 * u1); return Math.log(Math.sqrt(b * b + 1) - b); } function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } function tanh(n) { return sinh(n) / cosh(n); } var r0 = r(0); function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } var start = Date.now(), S = (r(1) - r0) / rho, duration = 1000 * S * 0.8; function frame() { var t = (Date.now() - start) / duration, s = easeOut(t) * S; if (t <= 1) { this._flyToFrame = L.Util.requestAnimFrame(frame, this); this._resetView( this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), this.getScaleZoom(w0 / w(s), startZoom), true, true); } else { this._resetView(targetCenter, targetZoom, true, true); } } this.fire('zoomstart'); frame.call(this); } }); /* * Provides L.Map with convenient shortcuts for using browser geolocation features. */ L.Map.include({ _defaultLocateOptions: { timeout: 10000, watch: false // setView: false // maxZoom: // maximumAge: 0 // enableHighAccuracy: false }, locate: function (/*Object*/ options) { options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options); if (!navigator.geolocation) { this._handleGeolocationError({ code: 0, message: 'Geolocation not supported.' }); return this; } var onResponse = L.bind(this._handleGeolocationResponse, this), onError = L.bind(this._handleGeolocationError, this); if (options.watch) { this._locationWatchId = navigator.geolocation.watchPosition(onResponse, onError, options); } else { navigator.geolocation.getCurrentPosition(onResponse, onError, options); } return this; }, stopLocate: function () { if (navigator.geolocation) { navigator.geolocation.clearWatch(this._locationWatchId); } if (this._locateOptions) { this._locateOptions.setView = false; } return this; }, _handleGeolocationError: function (error) { var c = error.code, message = error.message || (c === 1 ? 'permission denied' : (c === 2 ? 'position unavailable' : 'timeout')); if (this._locateOptions.setView && !this._loaded) { this.fitWorld(); } this.fire('locationerror', { code: c, message: 'Geolocation error: ' + message + '.' }); }, _handleGeolocationResponse: function (pos) { var lat = pos.coords.latitude, lng = pos.coords.longitude, latlng = new L.LatLng(lat, lng), bounds = latlng.toBounds(pos.coords.accuracy), options = this._locateOptions; if (options.setView) { var zoom = this.getBoundsZoom(bounds); this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); } var data = { latlng: latlng, bounds: bounds, timestamp: pos.timestamp }; for (var i in pos.coords) { if (typeof pos.coords[i] === 'number') { data[i] = pos.coords[i]; } } this.fire('locationfound', data); } }); }(window, document));