diff --git a/app.js b/app.js index 923702ff8..d83b1ff40 100644 --- a/app.js +++ b/app.js @@ -20,7 +20,7 @@ function getParams(req, resp, next) { req.query.isDebug = (req.query.mode === 'debug'); req.query.sprite = (req.query.sprite === 'true'); req.query.mobile = (req.query.mobile === 'true'); - var contentType = (req.path === '/js') ? 'application/x-javascript; charset=utf-8' : 'text/css'; + var contentType = (req.path === '/js/') ? 'application/javascript; charset=utf-8' : 'text/css'; req.dgCallback = function (stream, response) { response.set('Cache-Control', 'public, max-age=604800'); diff --git a/vendors/leaflet/dist/leaflet-src.js b/vendors/leaflet/dist/leaflet-src.js deleted file mode 100644 index 7c7fad499..000000000 --- a/vendors/leaflet/dist/leaflet-src.js +++ /dev/null @@ -1,8738 +0,0 @@ - -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 -} else { - 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 sources = Array.prototype.slice.call(arguments, 1), - i, j, len, src; - - for (j = 0, len = sources.length; j < len; j++) { - src = sources[j]; - for (i in src) { - 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, element) { - if (immediate && requestFn === timeoutDefer) { - fn.call(context); - } else { - return requestFn.call(window, L.bind(fn, context), element); - } - }; - - L.Util.cancelAnimFrame = function (id) { - if (id) { - cancelFn.call(window, id); - } - }; -})(); - -// shortcuts for most used utility functions -L.extend = L.Util.extend; -L.bind = L.Util.bind; -L.stamp = L.Util.stamp; -L.setOptions = L.Util.setOptions; - -/* - * L.Class powers the OOP facilities of the library. - * Thanks to John Resig and Dean Edwards for inspiration! - */ - -L.Class = function () {}; - -L.Class.extend = function (props) { - - // extended class with the new prototype - var NewClass = function () { - - // call the constructor - if (this.initialize) { - this.initialize.apply(this, arguments); - } - - // call all constructor hooks - if (this._initHooks.length) { - 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 retina = 'devicePixelRatio' in window && window.devicePixelRatio > 1; - - if (!retina && 'matchMedia' in window) { - var matches = window.matchMedia('(min-resolution:144dpi)'); - retina = matches && matches.matches; - } - - 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: !!retina - }; - -}()); - -/* - * L.Point represents a point with x and y coordinates. - */ - -L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) { - this.x = (round ? Math.round(x) : x); - this.y = (round ? Math.round(y) : y); -}; - -L.Point.prototype = { - - clone: function () { - return new L.Point(this.x, this.y); - }, - - // non-destructive, returns a new point - add: function (point) { - return this.clone()._add(L.point(point)); - }, - - // destructive, used directly for performance in situations where it's safe to modify existing point - _add: function (point) { - this.x += point.x; - this.y += point.y; - return this; - }, - - subtract: function (point) { - return this.clone()._subtract(L.point(point)); - }, - - _subtract: function (point) { - this.x -= point.x; - this.y -= point.y; - return this; - }, - - divideBy: function (num) { - return this.clone()._divideBy(num); - }, - - _divideBy: function (num) { - this.x /= num; - this.y /= num; - return this; - }, - - multiplyBy: function (num) { - return this.clone()._multiplyBy(num); - }, - - _multiplyBy: function (num) { - this.x *= num; - this.y *= num; - return this; - }, - - round: function () { - return this.clone()._round(); - }, - - _round: function () { - this.x = Math.round(this.x); - this.y = Math.round(this.y); - return this; - }, - - floor: function () { - return this.clone()._floor(); - }, - - _floor: function () { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); - return this; - }, - - 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); - } - }, - - 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), - is3d = L.Browser.webkit3d, - open = 'translate' + (is3d ? '3d' : '') + '(', - close = (is3d ? ',0' : '') + ')'; - - el.style[L.DomUtil.TRANSFORM] = - open + pos.x + 'px,' + pos.y + 'px' + close + (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); - } -}; - - -// constructs LatLng with different signatures -// (LatLng) or ([Number, Number]) or (Number, Number) or (Object) - -L.latLng = function (a, b) { - 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]); - } - return new L.LatLng(a[0], a[1]); - } - if (a === undefined || a === null) { - return a; - } - if (typeof a === 'object' && 'lat' in a) { - return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon); - } - if (b === undefined) { - return null; - } - return new L.LatLng(a, b); -}; - - -/* - * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. - */ - -L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) - if (!southWest) { return; } - - var latlngs = northEast ? [southWest, northEast] : southWest; - - for (var i = 0, len = latlngs.length; i < len; i++) { - this.extend(latlngs[i]); - } -}; - -L.LatLngBounds.prototype = { - - // extend the bounds to contain the given point or bounds - extend: function (obj) { // (LatLng) or (LatLngBounds) - 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; - - return this.R * Math.acos(Math.sin(lat1) * Math.sin(lat2) + - Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad)); - } -}); - -/* - * 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.center && options.zoom !== undefined) { - this.setView(L.latLng(options.center), options.zoom, {reset: true}); - } - - this._handlers = []; - this._layers = {}; - this._zoomBoundLayers = {}; - - 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 - }); - }, - - // 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); - zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y; - - } while (zoomNotFound && zoom <= maxZoom); - - if (zoomNotFound && inside) { - return null; - } - - return inside ? zoom : zoom - 1; - }, - - getSize: function () { - if (!this._size || this._sizeChanged) { - this._size = new L.Point( - this._container.clientWidth, - this._container.clientHeight); - - this._sizeChanged = false; - } - return this._size.clone(); - }, - - getPixelBounds: function () { - var topLeftPoint = this._getTopLeftPoint(); - return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); - }, - - getPixelOrigin: function () { - this._checkIfLoaded(); - return this._initialTopLeftPoint; - }, - - getPixelWorldBounds: function () { - return this.options.crs.getProjectedBounds(this.getZoom()); - }, - - 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) { - var crs = this.options.crs; - return crs.scale(toZoom) / crs.scale(this._zoom); - }, - - getScaleZoom: function (scale) { - return this._zoom + (Math.log(scale) / Math.LN2); - }, - - - // conversion methods - - project: function (latlng, zoom) { // (LatLng[, Number]) -> Point - zoom = zoom === undefined ? this._zoom : zoom; - return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); - }, - - unproject: function (point, zoom) { // (Point[, Number]) -> LatLng - zoom = zoom === undefined ? this._zoom : zoom; - return this.options.crs.pointToLatLng(L.point(point), zoom); - }, - - layerPointToLatLng: function (point) { // (Point) - var projectedPoint = L.point(point).add(this.getPixelOrigin()); - return this.unproject(projectedPoint); - }, - - latLngToLayerPoint: function (latlng) { // (LatLng) - var projectedPoint = this.project(L.latLng(latlng))._round(); - return projectedPoint._subtract(this.getPixelOrigin()); - }, - - 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; - - this._initialTopLeftPoint = this._getNewTopLeftPoint(center); - - if (!preserveMapOffset) { - L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); - } else { - this._initialTopLeftPoint._add(this._getMapPanePos()); - } - - 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') { - if (!e._simulated && ((this.dragging && this.dragging.moved()) || - (this.boxZoom && this.boxZoom.moved()))) { 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); - }, - - _moved: function () { - var pos = this._getMapPanePos(); - return pos && !pos.equals([0, 0]); - }, - - _getTopLeftPoint: function () { - return this.getPixelOrigin().subtract(this._getMapPanePos()); - }, - - _getNewTopLeftPoint: 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)._round(); - }, - - _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) { - var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos()); - return this.project(latlng, newZoom)._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: 150 - - /* - minZoom: , - maxZoom: , - attribution: , - zIndex: , - bounds: - */ - }, - - initialize: function (options) { - options = L.setOptions(this, options); - }, - - onAdd: function () { - this._initContainer(); - - if (!this.options.updateWhenIdle) { - // update tiles on move, but not more often than once per given interval - this._update = L.Util.throttle(this._update, this.options.updateInterval, this); - } - - this._reset(); - this._update(); - }, - - beforeAdd: function (map) { - map._addZoomLimit(this); - }, - - onRemove: function (map) { - this._clearBgBuffer(); - L.DomUtil.remove(this._container); - - map._removeZoomLimit(this); - - this._container = 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._reset({hard: true}); - this._update(); - } - return this; - }, - - getEvents: function () { - var events = { - viewreset: this._reset, - moveend: this._update - }; - - if (!this.options.updateWhenIdle) { - events.move = this._update; - } - - if (this._zoomAnimated) { - events.zoomstart = this._startZoomAnim; - events.zoomanim = this._animateZoom; - events.zoomend = this._endZoomAnim; - } - - return events; - }, - - _updateZIndex: function () { - if (this._container && this.options.zIndex !== undefined) { - 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], 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._zoomAnimated) { - var className = 'leaflet-tile-container leaflet-zoom-animated'; - - this._bgBuffer = L.DomUtil.create('div', className, this._container); - this._tileContainer = L.DomUtil.create('div', className, this._container); - - L.DomUtil.setTransform(this._tileContainer); - - } else { - this._tileContainer = this._container; - } - - if (this.options.opacity < 1) { - this._updateOpacity(); - } - - this.getPane().appendChild(this._container); - }, - - _reset: function (e) { - for (var key in this._tiles) { - this.fire('tileunload', { - tile: this._tiles[key] - }); - } - - this._tiles = {}; - this._tilesToLoad = 0; - this._tilesTotal = 0; - - this._tileContainer.innerHTML = ''; - - if (this._zoomAnimated && e && e.hard) { - this._clearBgBuffer(); - } - - this._tileNumBounds = this._getTileNumBounds(); - this._resetWrap(); - }, - - _resetWrap: function () { - var map = this._map, - crs = map.options.crs; - - if (crs.infinite) { return; } - - var tileSize = this._getTileSize(); - - if (crs.wrapLng) { - this._wrapLng = [ - Math.floor(map.project([0, crs.wrapLng[0]]).x / tileSize), - Math.ceil(map.project([0, crs.wrapLng[1]]).x / tileSize) - ]; - } - - if (crs.wrapLat) { - this._wrapLat = [ - Math.floor(map.project([crs.wrapLat[0], 0]).y / tileSize), - Math.ceil(map.project([crs.wrapLat[1], 0]).y / tileSize) - ]; - } - }, - - _getTileSize: function () { - return this.options.tileSize; - }, - - _update: function () { - - if (!this._map) { return; } - - var bounds = this._map.getPixelBounds(), - zoom = this._map.getZoom(), - tileSize = this._getTileSize(); - - if (zoom > this.options.maxZoom || - zoom < this.options.minZoom) { return; } - - // tile coordinates range for the current view - var tileBounds = L.bounds( - bounds.min.divideBy(tileSize).floor(), - bounds.max.divideBy(tileSize).floor()); - - this._addTiles(tileBounds); - - if (this.options.unloadInvisibleTiles) { - this._removeOtherTiles(tileBounds); - } - }, - - _addTiles: function (bounds) { - var queue = [], - center = bounds.getCenter(), - zoom = this._map.getZoom(); - - var j, i, coords; - - // create a queue of coordinates to load tiles from - for (j = bounds.min.y; j <= bounds.max.y; j++) { - for (i = bounds.min.x; i <= bounds.max.x; i++) { - - coords = new L.Point(i, j); - coords.z = zoom; - - // 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); - } - } - } - - var tilesToLoad = queue.length; - - if (tilesToLoad === 0) { return; } - - // if its the first batch of tiles to load - if (!this._tilesToLoad) { - this.fire('loading'); - } - - this._tilesToLoad += tilesToLoad; - this._tilesTotal += tilesToLoad; - - // 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); - }); - - // create DOM fragment to append tiles in one batch - var fragment = document.createDocumentFragment(); - - for (i = 0; i < tilesToLoad; i++) { - this._addTile(queue[i], fragment); - } - - this._tileContainer.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._tileNumBounds; - 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); - }, - - // converts tile coordinates to its geographical bounds - _tileCoordsToBounds: function (coords) { - - var map = this._map, - tileSize = this.options.tileSize, - - 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; - }, - - // converts tile cache key to coordiantes - _keyToTileCoords: function (key) { - var kArr = key.split(':'), - x = parseInt(kArr[0], 10), - y = parseInt(kArr[1], 10); - - return new L.Point(x, y); - }, - - // remove any present tiles that are off the specified bounds - _removeOtherTiles: function (bounds) { - for (var key in this._tiles) { - if (!bounds.contains(this._keyToTileCoords(key))) { - this._removeTile(key); - } - } - }, - - _removeTile: function (key) { - var tile = this._tiles[key]; - - L.DomUtil.remove(tile); - - delete this._tiles[key]; - - this.fire('tileunload', {tile: tile}); - }, - - _initTile: function (tile) { - var size = this._getTileSize(); - - L.DomUtil.addClass(tile, 'leaflet-tile'); - - tile.style.width = size + 'px'; - tile.style.height = size + '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); - - // wrap tile coords if necessary (depending on CRS) - this._wrapCoords(coords); - - var tile = this.createTile(coords, L.bind(this._tileReady, this)); - - 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, 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[this._tileCoordsToKey(coords)] = tile; - - container.appendChild(tile); - this.fire('tileloadstart', {tile: tile}); - }, - - _tileReady: function (err, tile) { - if (err) { - this.fire('tileerror', { - error: err, - tile: tile - }); - } - - L.DomUtil.addClass(tile, 'leaflet-tile-loaded'); - - this.fire('tileload', {tile: tile}); - - this._tilesToLoad--; - - if (this._tilesToLoad === 0) { - this._visibleTilesReady(); - } - }, - - _visibleTilesReady: function () { - this.fire('load'); - - if (this._zoomAnimated) { - // clear scaled tiles after all new tiles are loaded (for performance) - clearTimeout(this._clearBgBufferTimer); - this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 300); - } - }, - - _getTilePos: function (coords) { - return coords - .multiplyBy(this._getTileSize()) - .subtract(this._map.getPixelOrigin()); - }, - - _wrapCoords: function (coords) { - coords.x = this._wrapLng ? L.Util.wrapNum(coords.x, this._wrapLng) : coords.x; - coords.y = this._wrapLat ? L.Util.wrapNum(coords.y, this._wrapLat) : coords.y; - }, - - // get the global tile coordinates range for the current zoom - _getTileNumBounds: function () { - var bounds = this._map.getPixelWorldBounds(), - size = this._getTileSize(); - - return bounds ? L.bounds( - bounds.min.divideBy(size).floor(), - bounds.max.divideBy(size).ceil().subtract([1, 1])) : null; - }, - - _startZoomAnim: function () { - this._prepareBgBuffer(); - this._prevTranslate = this._translate || new L.Point(0, 0); - this._prevScale = this._scale; - }, - - _animateZoom: function (e) { - // avoid stacking transforms by calculating cumulating translate/scale sequence - this._translate = this._prevTranslate.multiplyBy(e.scale).add(e.origin.multiplyBy(1 - e.scale)); - this._scale = this._prevScale * e.scale; - - L.DomUtil.setTransform(this._bgBuffer, this._translate, this._scale); - }, - - _endZoomAnim: function () { - var front = this._tileContainer; - front.style.visibility = ''; - L.DomUtil.toFront(front); // bring to front - }, - - _clearBgBuffer: function () { - var map = this._map, - bg = this._bgBuffer; - - if (map && !map._animatingZoom && !map.touchZoom._zooming && bg) { - bg.innerHTML = ''; - L.DomUtil.setTransform(bg); - } - }, - - _prepareBgBuffer: function () { - - var front = this._tileContainer, - bg = this._bgBuffer; - - if (this._abortLoading) { - this._abortLoading(); - } - - if (this._tilesToLoad / this._tilesTotal > 0.5) { - // if foreground layer doesn't have many tiles loaded, - // keep the existing bg layer and just zoom it some more - front.style.visibility = 'hidden'; - return; - } - - // prepare the buffer to become the front tile pane - bg.style.visibility = 'hidden'; - L.DomUtil.setTransform(bg); - - // switch out the current layer to be the new bg layer (and vice-versa) - this._tileContainer = bg; - this._bgBuffer = front; - - // reset bg layer transform info - this._translate = new L.Point(0, 0); - this._scale = 1; - - // prevent bg buffer from clearing right after zoom - clearTimeout(this._clearBgBufferTimer); - } -}); - -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: { - minZoom: 0, - maxZoom: 18, - - subdomains: 'abc', - // errorTileUrl: '', - zoomOffset: 0 - - /* - maxNativeZoom: , - tms: , - zoomReverse: , - detectRetina: , - */ - }, - - 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(''); - } - }, - - 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); - - /* - 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._tileNumBounds.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 && zoom > zoomN ? - Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * options.tileSize) : - options.tileSize; - }, - - _removeTile: function (key) { - var tile = this._tiles[key]; - - L.GridLayer.prototype._removeTile.call(this, key); - - // for https://github.com/Leaflet/Leaflet/issues/137 - if (!L.Browser.android) { - tile.onload = null; - tile.src = L.Util.emptyImageUrl; - } - }, - - _getZoomForUrl: function () { - - var options = this.options, - zoom = this._map.getZoom(); - - 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]; - - if (!tile.complete) { - tile.onload = L.Util.falseFn; - tile.onerror = L.Util.falseFn; - 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 - }, - - 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 (!this.options.hasOwnProperty(i) && i !== 'crs') { - 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.Util.template(this._url, {s: this._getSubdomain(coords)}); - - return url + L.Util.getParamString(this.wmsParams, url, true) + '&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 - }, - - 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._reset(); - }, - - onRemove: function () { - L.DomUtil.remove(this._image); - }, - - setOpacity: function (opacity) { - this.options.opacity = opacity; - - if (this._image) { - this._updateOpacity(); - } - 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; - }, - - 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; - }, - - _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; - }, - - _animateZoom: function (e) { - var topLeft = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center), - size = this._map._latLngToNewLayerPoint(this._bounds.getSouthEast(), e.zoom, e.center).subtract(topLeft), - offset = topLeft.add(size._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: '', - clickable: 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.disable(); - } - - 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); - } - - 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 (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 (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.clickable) { return; } - - L.DomUtil.addClass(this._icon, 'leaflet-clickable'); - - L.DomEvent.on(this._icon, 'click dblclick mousedown mouseup mouseover mouseout contextmenu keypress', - this._fireMouseEvent, this); - - if (L.Handler.MarkerDrag) { - this.dragging = new L.Handler.MarkerDrag(this); - - if (this.options.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 === 'click' && this.dragging && this.dragging.moved()) { - L.DomEvent.stopPropagation(e); - return; - } - - 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, - // 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; - } - - 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.on({ - 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)); - - 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, - - // className: '' - clickable: 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 () { - this._renderer._bringToFront(this); - return this; - }, - - bringToBack: function () { - 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, - pane = this.getPane(); - - // hack to make flicker on drag end on mobile webkit less irritating - if (L.Browser.mobileWebkit) { - pane.removeChild(container); - } - - L.DomUtil.setPosition(container, b.min); - - // update container viewBox so that we don't have to change coordinates of individual layers - container.setAttribute('width', size.x); - container.setAttribute('height', size.y); - container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); - - if (L.Browser.mobileWebkit) { - pane.appendChild(container); - } - }, - - // 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.clickable) { - L.DomUtil.addClass(path, 'leaflet-clickable'); - } - - 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', 'evenodd'); - } else { - path.setAttribute('fill', 'none'); - } - - path.setAttribute('pointer-events', options.pointerEvents || (options.clickable ? '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) { - this._paths[L.stamp(e.target || e.srcElement)] && this._paths[L.stamp(e.target || e.srcElement)]._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, - 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._path.parentNode); - }, - - _bringToBack: function (layer) { - L.DomUtil.toBack(layer._path.parentNode); - } -}); - -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('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.clickable) { return; } - - if (layer._containsPoint(point)) { - // if we just got inside the layer, fire mouseover - if (!layer._mouseInside) { - L.DomUtil.addClass(this._container, 'leaflet-clickable'); // 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-clickable'); - 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); - }, - - setStyle: function (style) { - 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 && this._latlngs.length === 1) { - multi = true; - coords = [coords]; - } - 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) { - return this.addPointerListener(obj, type, handler, id); - } - if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { - this.addDoubleTapListener(obj, handler, id); - } - - 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.Draggable._disabled) { 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'); - L.DomUtil.addClass(e.target || e.srcElement, '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 (e) { - L.DomUtil.removeClass(document.body, 'leaflet-dragging'); - L.DomUtil.removeClass(e.target || e.srcElement, 'leaflet-drag-target'); - - 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 - inertiaThreshold: L.Browser.touch ? 32 : 18, // ms - easeLinearity: 0.25, - - // 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 () { - if (this._map._panAnim) { - this._map._panAnim.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._newPos; - - this._positions.push(pos); - this._times.push(time); - - if (time - this._times[0] > 200) { - 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._newPos.x = newX; - }, - - _onDragEnd: function (e) { - var map = this._map, - options = map.options, - delay = +new Date() - this._lastTime, - - noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; - - map.fire('dragend', e); - - if (noInertia) { - map.fire('moveend'); - - } else { - - var direction = this._lastPos.subtract(this._positions[0]), - duration = (this._lastTime + delay - 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 - }); - }); - } - } - } -}); - -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, - zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 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 -}); - -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); - - this._delta += delta; - this._lastMousePos = this._map.mouseEventToContainerPoint(e); - - if (!this._startTime) { - this._startTime = +new Date(); - } - - var left = Math.max(40 - (+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(); - - 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); - -/* - * L.PosAnimation is used by Leaflet internally for pan animations. - */ - -L.PosAnimation = L.Evented.extend({ - - run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number]) - this.stop(); - - this._el = el; - this._inProgress = true; - this._newPos = newPos; - - this.fire('start'); - - el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) + - 's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)'; - - L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this); - L.DomUtil.setPosition(el, newPos); - - // toggle reflow, Chrome flickers for some reason if you don't do this - L.Util.falseFn(el.offsetWidth); - - // there's no native way to track value updates of transitioned properties, so we imitate this - this._stepTimer = setInterval(L.bind(this._onStep, this), 50); - }, - - stop: function () { - if (!this._inProgress) { return; } - - // if we just removed the transition property, the element would jump to its final position, - // so we need to make it stay at the current position - - this._newPos = this._getPos(); - L.DomUtil.setPosition(this._el, this._newPos); - - this._onTransitionEnd(); - L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation - }, - - _onStep: function () { - var stepPos = this._getPos(); - if (!stepPos) { - this._onTransitionEnd(); - return; - } - // jshint camelcase: false - // make L.DomUtil.getPosition return intermediate position value during animation - this._el._leaflet_pos = stepPos; - - this.fire('step'); - }, - - // you can't easily get intermediate values of properties animated with CSS3 Transitions, - // we need to parse computed style (in case of transform it returns matrix string) - - _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/, - - _getPos: function () { - var left, top, matches, - el = this._el, - style = window.getComputedStyle(el); - - if (L.Browser.any3d) { - matches = style[L.DomUtil.TRANSFORM].match(this._transformRe); - if (!matches) { return; } - left = parseFloat(matches[1]); - top = parseFloat(matches[2]); - } else { - left = parseFloat(style.left); - top = parseFloat(style.top); - } - - return new L.Point(left, top, true); - }, - - _onTransitionEnd: function () { - L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this); - - if (!this._inProgress) { return; } - this._inProgress = false; - - this._el.style[L.DomUtil.TRANSITION] = ''; - - // jshint camelcase: false - // make sure L.DomUtil.getPosition returns the final position value after animation - this._el._leaflet_pos = this._newPos; - - clearInterval(this._stepTimer); - - this.fire('step').fire('end'); - } - -}); - -/* - * 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 || {}; - - if (this._panAnim) { - this._panAnim.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 (!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 true; - } -}); - -/* - * 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) { - L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); - } - }); -} - -L.Map.include(!zoomAnimated ? {} : { - - _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; - - // disable any dragging during animation - if (L.Draggable) { - L.Draggable._disabled = true; - } - - L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); - } - - var scale = this.getZoomScale(zoom), - origin = this._getCenterLayerPoint().add(this._getCenterOffset(center)._divideBy(1 - 1 / scale)); - - this.fire('zoomanim', { - center: center, - zoom: zoom, - origin: origin, - scale: scale - }); - }, - - _onZoomTransitionEnd: function () { - - this._animatingZoom = false; - - L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); - - this._resetView(this._animateToCenter, this._animateToZoom, true, true); - - if (L.Draggable) { - L.Draggable._disabled = false; - } - } -}); - -/* - * 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, - trackedTouches = []; - - function onTouchStart(e) { - var count; - - if (L.Browser.pointer) { - trackedTouches.push(e.pointerId); - count = trackedTouches.length; - } 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(e) { - if (L.Browser.pointer) { - var idx = trackedTouches.indexOf(e.pointerId); - if (idx === -1) { return; } - trackedTouches.splice(idx, 1); - } - - 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; - - // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen - // will not come through to us, so we will lose track of how many touches are ongoing - var endElement = L.Browser.pointer ? document.documentElement : obj; - - obj.addEventListener(touchstart, onTouchStart, false); - - endElement.addEventListener(touchend, onTouchEnd, false); - if (L.Browser.pointer) { - endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false); - } - - return this; - }, - - removeDoubleTapListener: function (obj, id) { - var pre = '_leaflet_', - endElement = L.Browser.pointer ? document.documentElement : obj, - touchend = obj[pre + this._touchend + id]; - - obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); - - endElement.removeEventListener(this._touchend, touchend, false); - if (L.Browser.pointer) { - endElement.removeEventListener(L.DomEvent.POINTER_CANCEL, 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: {}, - - // 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._pointers[e.pointerId] = e; - this._handlePointer(e, handler); - }, this); - - obj['_leaflet_touchstart' + id] = onDown; - obj.addEventListener(this.POINTER_DOWN, onDown, false); - - // need to also listen for end events to keep the _pointers object accurate - if (!this._pointerDocListener) { - var removePointer = L.bind(function (e) { - delete this._pointers[e.pointerId]; - }, this); - - // we listen documentElement as any drags that end by moving the touch off the screen get fired there - document.documentElement.addEventListener(this.POINTER_UP, removePointer, false); - document.documentElement.addEventListener(this.POINTER_CANCEL, removePointer, false); - - this._pointerDocListener = true; - } - }, - - _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._pointers[e.pointerId] = e; - 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) { - delete this._pointers[e.pointerId]; - 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); - - if (map._panAnim) { - map._panAnim.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 && - ((map.getZoom() === map.getMinZoom() && this._scale < 1) || - (map.getZoom() === 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(oldZoom + (zoomDelta > 0 ? Math.ceil(zoomDelta) : Math.floor(zoomDelta))); - - 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) { - this._moved = false; - - if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } - - L.DomUtil.disableTextSelection(); - L.DomUtil.disableImageDrag(); - - this._startPoint = this._map.mouseEventToContainerPoint(e); - - L.DomEvent.on(document, { - 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, { - mousemove: this._onMouseMove, - mouseup: this._onMouseUp, - keydown: this._onKeyDown - }, this); - }, - - _onMouseUp: function () { - - 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() + this._zoomKeys[key]); - - } 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._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 () { - 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.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'); - 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.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.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) - 10) + '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 - }, - - 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; } - - this._baseLayersList.innerHTML = ''; - this._overlaysList.innerHTML = ''; - - var baseLayersPresent, overlaysPresent, i, obj; - - for (i in this._layers) { - obj = this._layers[i]; - this._addItem(obj); - overlaysPresent = overlaysPresent || obj.overlay; - baseLayersPresent = baseLayersPresent || !obj.overlay; - } - - 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; - - 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) { - this._map.addLayer(layer); - - } else if (!input.checked && hasLayer) { - this._map.removeLayer(layer); - } - } - - 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 fallback implementation that powers Leaflet pan animations - * in browsers that don't support CSS3 Transitions. - */ - -L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.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(); - this._complete(); - }, - - _animate: function () { - // animation loop - this._animId = L.Util.requestAnimFrame(this._animate, this); - this._step(); - }, - - _step: function () { - var elapsed = (+new Date()) - this._startTime, - duration = this._duration * 1000; - - if (elapsed < duration) { - this._runFrame(this._easeOut(elapsed / duration)); - } else { - this._runFrame(1); - this._complete(); - } - }, - - _runFrame: function (progress) { - var pos = this._startPos.add(this._offset.multiplyBy(progress)); - 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); - } -}); - -/* - * 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), - - latAccuracy = 180 * pos.coords.accuracy / 40075017, - lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * lat), - - bounds = L.latLngBounds( - [lat - latAccuracy, lng - lngAccuracy], - [lat + latAccuracy, lng + lngAccuracy]), - - 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); - } -});