nyc-bookstores/js/sheetsee.js

16277 lines
434 KiB
JavaScript
Raw Normal View History

2015-03-08 03:02:36 +00:00
;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
if (typeof window.Sheetsee === 'undefined') window.Sheetsee = {}; window.Sheetsee = require('sheetsee-core'); var extend = require('lodash.assign'); extend(window.Sheetsee, require('sheetsee-maps'), require('sheetsee-tables')); module.exports = Sheetsee;
},{"lodash.assign":3,"sheetsee-core":28,"sheetsee-maps":29,"sheetsee-tables":56}],2:[function(require,module,exports){
/*!
ICanHaz.js version 0.10.2 -- by @HenrikJoreteg
More info at: http://icanhazjs.com
*/
(function () {
/*
mustache.js Logic-less templates in JavaScript
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
See http://mustache.github.com/ for more info.
*/
var Mustache = function () {
var _toString = Object.prototype.toString;
Array.isArray = Array.isArray || function (obj) {
return _toString.call(obj) == "[object Array]";
}
var _trim = String.prototype.trim, trim;
if (_trim) {
trim = function (text) {
return text == null ? "" : _trim.call(text);
}
} else {
var trimLeft, trimRight;
// IE doesn't match non-breaking spaces with \s.
if ((/\S/).test("\xA0")) {
trimLeft = /^[\s\xA0]+/;
trimRight = /[\s\xA0]+$/;
} else {
trimLeft = /^\s+/;
trimRight = /\s+$/;
}
trim = function (text) {
return text == null ? "" :
text.toString().replace(trimLeft, "").replace(trimRight, "");
}
}
var escapeMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;'
};
function escapeHTML(string) {
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
return escapeMap[s] || s;
});
}
var regexCache = {};
var Renderer = function () {};
Renderer.prototype = {
otag: "{{",
ctag: "}}",
pragmas: {},
buffer: [],
pragmas_implemented: {
"IMPLICIT-ITERATOR": true
},
context: {},
render: function (template, context, partials, in_recursion) {
// reset buffer & set context
if (!in_recursion) {
this.context = context;
this.buffer = []; // TODO: make this non-lazy
}
// fail fast
if (!this.includes("", template)) {
if (in_recursion) {
return template;
} else {
this.send(template);
return;
}
}
// get the pragmas together
template = this.render_pragmas(template);
// render the template
var html = this.render_section(template, context, partials);
// render_section did not find any sections, we still need to render the tags
if (html === false) {
html = this.render_tags(template, context, partials, in_recursion);
}
if (in_recursion) {
return html;
} else {
this.sendLines(html);
}
},
/*
Sends parsed lines
*/
send: function (line) {
if (line !== "") {
this.buffer.push(line);
}
},
sendLines: function (text) {
if (text) {
var lines = text.split("\n");
for (var i = 0; i < lines.length; i++) {
this.send(lines[i]);
}
}
},
/*
Looks for %PRAGMAS
*/
render_pragmas: function (template) {
// no pragmas
if (!this.includes("%", template)) {
return template;
}
var that = this;
var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) {
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
});
return template.replace(regex, function (match, pragma, options) {
if (!that.pragmas_implemented[pragma]) {
throw({message:
"This implementation of mustache doesn't understand the '" +
pragma + "' pragma"});
}
that.pragmas[pragma] = {};
if (options) {
var opts = options.split("=");
that.pragmas[pragma][opts[0]] = opts[1];
}
return "";
// ignore unknown pragmas silently
});
},
/*
Tries to find a partial in the curent scope and render it
*/
render_partial: function (name, context, partials) {
name = trim(name);
if (!partials || partials[name] === undefined) {
throw({message: "unknown_partial '" + name + "'"});
}
if (!context || typeof context[name] != "object") {
return this.render(partials[name], context, partials, true);
}
return this.render(partials[name], context[name], partials, true);
},
/*
Renders inverted (^) and normal (#) sections
*/
render_section: function (template, context, partials) {
if (!this.includes("#", template) && !this.includes("^", template)) {
// did not render anything, there were no sections
return false;
}
var that = this;
var regex = this.getCachedRegex("render_section", function (otag, ctag) {
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
return new RegExp(
"^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
otag + // {{
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
ctag + // }}
"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
otag + // {{
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
ctag + // }}
"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
"g");
});
// for each {{#foo}}{{/foo}} section do...
return template.replace(regex, function (match, before, type, name, content, after) {
// before contains only tags, no sections
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
// after may contain both sections and tags, so use full rendering function
renderedAfter = after ? that.render(after, context, partials, true) : "",
// will be computed below
renderedContent,
value = that.find(name, context);
if (type === "^") { // inverted section
if (!value || Array.isArray(value) && value.length === 0) {
// false or empty list, render it
renderedContent = that.render(content, context, partials, true);
} else {
renderedContent = "";
}
} else if (type === "#") { // normal section
if (Array.isArray(value)) { // Enumerable, Let's loop!
renderedContent = that.map(value, function (row) {
return that.render(content, that.create_context(row), partials, true);
}).join("");
} else if (that.is_object(value)) { // Object, Use it as subcontext!
renderedContent = that.render(content, that.create_context(value),
partials, true);
} else if (typeof value == "function") {
// higher order section
renderedContent = value.call(context, content, function (text) {
return that.render(text, context, partials, true);
});
} else if (value) { // boolean section
renderedContent = that.render(content, context, partials, true);
} else {
renderedContent = "";
}
}
return renderedBefore + renderedContent + renderedAfter;
});
},
/*
Replace {{foo}} and friends with values from our view
*/
render_tags: function (template, context, partials, in_recursion) {
// tit for tat
var that = this;
var new_regex = function () {
return that.getCachedRegex("render_tags", function (otag, ctag) {
return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g");
});
};
var regex = new_regex();
var tag_replace_callback = function (match, operator, name) {
switch(operator) {
case "!": // ignore comments
return "";
case "=": // set new delimiters, rebuild the replace regexp
that.set_delimiters(name);
regex = new_regex();
return "";
case ">": // render partial
return that.render_partial(name, context, partials);
case "{": // the triple mustache is unescaped
case "&": // & operator is an alternative unescape method
return that.find(name, context);
default: // escape the value
return escapeHTML(that.find(name, context));
}
};
var lines = template.split("\n");
for(var i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
if (!in_recursion) {
this.send(lines[i]);
}
}
if (in_recursion) {
return lines.join("\n");
}
},
set_delimiters: function (delimiters) {
var dels = delimiters.split(" ");
this.otag = this.escape_regex(dels[0]);
this.ctag = this.escape_regex(dels[1]);
},
escape_regex: function (text) {
// thank you Simon Willison
if (!arguments.callee.sRE) {
var specials = [
'/', '.', '*', '+', '?', '|',
'(', ')', '[', ']', '{', '}', '\\'
];
arguments.callee.sRE = new RegExp(
'(\\' + specials.join('|\\') + ')', 'g'
);
}
return text.replace(arguments.callee.sRE, '\\$1');
},
/*
find `name` in current `context`. That is find me a value
from the view object
*/
find: function (name, context) {
name = trim(name);
// Checks whether a value is thruthy or false or 0
function is_kinda_truthy(bool) {
return bool === false || bool === 0 || bool;
}
var value;
// check for dot notation eg. foo.bar
if (name.match(/([a-z_]+)\./ig)) {
var childValue = this.walk_context(name, context);
if (is_kinda_truthy(childValue)) {
value = childValue;
}
} else {
if (is_kinda_truthy(context[name])) {
value = context[name];
} else if (is_kinda_truthy(this.context[name])) {
value = this.context[name];
}
}
if (typeof value == "function") {
return value.apply(context);
}
if (value !== undefined) {
return value;
}
// silently ignore unkown variables
return "";
},
walk_context: function (name, context) {
var path = name.split('.');
// if the var doesn't exist in current context, check the top level context
var value_context = (context[path[0]] != undefined) ? context : this.context;
var value = value_context[path.shift()];
while (value != undefined && path.length > 0) {
value_context = value;
value = value[path.shift()];
}
// if the value is a function, call it, binding the correct context
if (typeof value == "function") {
return value.apply(value_context);
}
return value;
},
// Utility methods
/* includes tag */
includes: function (needle, haystack) {
return haystack.indexOf(this.otag + needle) != -1;
},
// by @langalex, support for arrays of strings
create_context: function (_context) {
if (this.is_object(_context)) {
return _context;
} else {
var iterator = ".";
if (this.pragmas["IMPLICIT-ITERATOR"]) {
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
}
},
is_object: function (a) {
return a && typeof a == "object";
},
/*
Why, why, why? Because IE. Cry, cry cry.
*/
map: function (array, fn) {
if (typeof array.map == "function") {
return array.map(fn);
} else {
var r = [];
var l = array.length;
for(var i = 0; i < l; i++) {
r.push(fn(array[i]));
}
return r;
}
},
getCachedRegex: function (name, generator) {
var byOtag = regexCache[this.otag];
if (!byOtag) {
byOtag = regexCache[this.otag] = {};
}
var byCtag = byOtag[this.ctag];
if (!byCtag) {
byCtag = byOtag[this.ctag] = {};
}
var regex = byCtag[name];
if (!regex) {
regex = byCtag[name] = generator(this.otag, this.ctag);
}
return regex;
}
};
return({
name: "mustache.js",
version: "0.4.0",
/*
Turns a template and view into HTML
*/
to_html: function (template, view, partials, send_fun) {
var renderer = new Renderer();
if (send_fun) {
renderer.send = send_fun;
}
renderer.render(template, view || {}, partials);
if (!send_fun) {
return renderer.buffer.join("\n");
}
}
});
}();
/*!
ICanHaz.js -- by @HenrikJoreteg
*/
/*global */
(function () {
function trim(stuff) {
if (''.trim) return stuff.trim();
else return stuff.replace(/^\s+/, '').replace(/\s+$/, '');
}
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
var ich = {
VERSION: "0.10.2",
templates: {},
// grab jquery or zepto if it's there
$: (typeof window !== 'undefined') ? window.jQuery || window.Zepto || null : null,
// public function for adding templates
// can take a name and template string arguments
// or can take an object with name/template pairs
// We're enforcing uniqueness to avoid accidental template overwrites.
// If you want a different template, it should have a different name.
addTemplate: function (name, templateString) {
if (typeof name === 'object') {
for (var template in name) {
this.addTemplate(template, name[template]);
}
return;
}
if (ich[name]) {
console.error("Invalid name: " + name + ".");
} else if (ich.templates[name]) {
console.error("Template \"" + name + " \" exists");
} else {
ich.templates[name] = templateString;
ich[name] = function (data, raw) {
data = data || {};
var result = Mustache.to_html(ich.templates[name], data, ich.templates);
return (ich.$ && !raw) ? ich.$(trim(result)) : result;
};
}
},
// clears all retrieval functions and empties cache
clearAll: function () {
for (var key in ich.templates) {
delete ich[key];
}
ich.templates = {};
},
// clears/grabs
refresh: function () {
ich.clearAll();
ich.grabTemplates();
},
// grabs templates from the DOM and caches them.
// Loop through and add templates.
// Whitespace at beginning and end of all templates inside <script> tags will
// be trimmed. If you want whitespace around a partial, add it in the parent,
// not the partial. Or do it explicitly using <br/> or &nbsp;
grabTemplates: function () {
var i,
l,
scripts = document.getElementsByTagName('script'),
script,
trash = [];
for (i = 0, l = scripts.length; i < l; i++) {
script = scripts[i];
if (script && script.innerHTML && script.id && (script.type === "text/html" || script.type === "text/x-icanhaz")) {
ich.addTemplate(script.id, trim(script.innerHTML));
trash.unshift(script);
}
}
for (i = 0, l = trash.length; i < l; i++) {
trash[i].parentNode.removeChild(trash[i]);
}
}
};
// Export the ICanHaz object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `ich` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = ich;
}
exports.ich = ich;
} else {
root['ich'] = ich;
}
if (typeof document !== 'undefined') {
if (ich.$) {
ich.$(function () {
ich.grabTemplates();
});
} else {
document.addEventListener('DOMContentLoaded', function () {
ich.grabTemplates();
}, true);
}
}
})();
})();
},{}],3:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var baseCreateCallback = require('lodash._basecreatecallback'),
keys = require('lodash.keys'),
objectTypes = require('lodash._objecttypes');
/**
* Assigns own enumerable properties of source object(s) to the destination
* object. Subsequent sources will overwrite property assignments of previous
* sources. If a callback is provided it will be executed to produce the
* assigned values. The callback is bound to `thisArg` and invoked with two
* arguments; (objectValue, sourceValue).
*
* @static
* @memberOf _
* @type Function
* @alias extend
* @category Objects
* @param {Object} object The destination object.
* @param {...Object} [source] The source objects.
* @param {Function} [callback] The function to customize assigning values.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns the destination object.
* @example
*
* _.assign({ 'name': 'moe' }, { 'age': 40 });
* // => { 'name': 'moe', 'age': 40 }
*
* var defaults = _.partialRight(_.assign, function(a, b) {
* return typeof a == 'undefined' ? b : a;
* });
*
* var food = { 'name': 'apple' };
* defaults(food, { 'name': 'banana', 'type': 'fruit' });
* // => { 'name': 'apple', 'type': 'fruit' }
*/
var assign = function(object, source, guard) {
var index, iterable = object, result = iterable;
if (!iterable) return result;
var args = arguments,
argsIndex = 0,
argsLength = typeof guard == 'number' ? 2 : args.length;
if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
callback = args[--argsLength];
}
while (++argsIndex < argsLength) {
iterable = args[argsIndex];
if (iterable && objectTypes[typeof iterable]) {
var ownIndex = -1,
ownProps = objectTypes[typeof iterable] && keys(iterable),
length = ownProps ? ownProps.length : 0;
while (++ownIndex < length) {
index = ownProps[ownIndex];
result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
}
}
}
return result
};
module.exports = assign;
},{"lodash._basecreatecallback":4,"lodash._objecttypes":23,"lodash.keys":24}],4:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var bind = require('lodash.bind'),
identity = require('lodash.identity'),
setBindData = require('lodash._setbinddata'),
support = require('lodash.support');
/** Used to detected named functions */
var reFuncName = /^function[ \n\r\t]+\w/;
/** Used to detect functions containing a `this` reference */
var reThis = /\bthis\b/;
/** Native method shortcuts */
var fnToString = Function.prototype.toString;
/**
* The base implementation of `_.createCallback` without support for creating
* "_.pluck" or "_.where" style callbacks.
*
* @private
* @param {*} [func=identity] The value to convert to a callback.
* @param {*} [thisArg] The `this` binding of the created callback.
* @param {number} [argCount] The number of arguments the callback accepts.
* @returns {Function} Returns a callback function.
*/
function baseCreateCallback(func, thisArg, argCount) {
if (typeof func != 'function') {
return identity;
}
// exit early if there is no `thisArg`
if (typeof thisArg == 'undefined') {
return func;
}
var bindData = func.__bindData__ || (support.funcNames && !func.name);
if (typeof bindData == 'undefined') {
var source = reThis && fnToString.call(func);
if (!support.funcNames && source && !reFuncName.test(source)) {
bindData = true;
}
if (support.funcNames || !bindData) {
// checks if `func` references the `this` keyword and stores the result
bindData = !support.funcDecomp || reThis.test(source);
setBindData(func, bindData);
}
}
// exit early if there are no `this` references or `func` is bound
if (bindData !== true && (bindData && bindData[1] & 1)) {
return func;
}
switch (argCount) {
case 1: return function(value) {
return func.call(thisArg, value);
};
case 2: return function(a, b) {
return func.call(thisArg, a, b);
};
case 3: return function(value, index, collection) {
return func.call(thisArg, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(thisArg, accumulator, value, index, collection);
};
}
return bind(func, thisArg);
}
module.exports = baseCreateCallback;
},{"lodash._setbinddata":5,"lodash.bind":13,"lodash.identity":20,"lodash.support":21}],5:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var getObject = require('lodash._getobject'),
noop = require('lodash._noop'),
reNative = require('lodash._renative'),
releaseObject = require('lodash._releaseobject');
/** Used for native method references */
var objectProto = Object.prototype;
var defineProperty = (function() {
try {
var o = {},
func = reNative.test(func = Object.defineProperty) && func,
result = func(o, o, o) && func;
} catch(e) { }
return result;
}());
/**
* Sets `this` binding data on a given function.
*
* @private
* @param {Function} func The function to set data on.
* @param {*} value The value to set.
*/
var setBindData = !defineProperty ? noop : function(func, value) {
var descriptor = getObject();
descriptor.value = value;
defineProperty(func, '__bindData__', descriptor);
releaseObject(descriptor);
};
module.exports = setBindData;
},{"lodash._getobject":6,"lodash._noop":8,"lodash._releaseobject":9,"lodash._renative":12}],6:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var objectPool = require('lodash._objectpool');
/**
* Gets an object from the object pool or creates a new one if the pool is empty.
*
* @private
* @returns {Object} The object from the pool.
*/
function getObject() {
return objectPool.pop() || {
'array': null,
'cache': null,
'configurable': false,
'criteria': null,
'enumerable': false,
'false': false,
'index': 0,
'leading': false,
'maxWait': 0,
'null': false,
'number': null,
'object': null,
'push': null,
'string': null,
'trailing': false,
'true': false,
'undefined': false,
'value': null,
'writable': false
};
}
module.exports = getObject;
},{"lodash._objectpool":7}],7:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used to pool arrays and objects used internally */
var objectPool = [];
module.exports = objectPool;
},{}],8:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/**
* A no-operation function.
*
* @private
*/
function noop() {
// no operation performed
}
module.exports = noop;
},{}],9:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var maxPoolSize = require('lodash._maxpoolsize'),
objectPool = require('lodash._objectpool');
/**
* Releases the given object back to the object pool.
*
* @private
* @param {Object} [object] The object to release.
*/
function releaseObject(object) {
var cache = object.cache;
if (cache) {
releaseObject(cache);
}
object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
if (objectPool.length < maxPoolSize) {
objectPool.push(object);
}
}
module.exports = releaseObject;
},{"lodash._maxpoolsize":10,"lodash._objectpool":11}],10:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used as the max size of the `arrayPool` and `objectPool` */
var maxPoolSize = 40;
module.exports = maxPoolSize;
},{}],11:[function(require,module,exports){
module.exports=require(7)
},{}],12:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used for native method references */
var objectProto = Object.prototype;
/** Used to detect if a method is native */
var reNative = RegExp('^' +
String(objectProto.valueOf)
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$'
);
module.exports = reNative;
},{}],13:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var createBound = require('lodash._createbound'),
reNative = require('lodash._renative');
/**
* Used for `Array` method references.
*
* Normally `Array.prototype` would suffice, however, using an array literal
* avoids issues in Narwhal.
*/
var arrayRef = [];
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeSlice = arrayRef.slice;
/**
* Creates a function that, when called, invokes `func` with the `this`
* binding of `thisArg` and prepends any additional `bind` arguments to those
* provided to the bound function.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to bind.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {...*} [arg] Arguments to be partially applied.
* @returns {Function} Returns the new bound function.
* @example
*
* var func = function(greeting) {
* return greeting + ' ' + this.name;
* };
*
* func = _.bind(func, { 'name': 'moe' }, 'hi');
* func();
* // => 'hi moe'
*/
function bind(func, thisArg) {
return arguments.length > 2
? createBound(func, 17, nativeSlice.call(arguments, 2), null, thisArg)
: createBound(func, 1, null, null, thisArg);
}
module.exports = bind;
},{"lodash._createbound":14,"lodash._renative":19}],14:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var createObject = require('lodash._createobject'),
isFunction = require('lodash.isfunction'),
isObject = require('lodash.isobject'),
reNative = require('lodash._renative'),
setBindData = require('lodash._setbinddata'),
support = require('lodash.support');
/**
* Used for `Array` method references.
*
* Normally `Array.prototype` would suffice, however, using an array literal
* avoids issues in Narwhal.
*/
var arrayRef = [];
/** Used for native method references */
var objectProto = Object.prototype;
/** Native method shortcuts */
var push = arrayRef.push,
toString = objectProto.toString,
unshift = arrayRef.unshift;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind,
nativeSlice = arrayRef.slice;
/**
* Creates a function that, when called, either curries or invokes `func`
* with an optional `this` binding and partially applied arguments.
*
* @private
* @param {Function|string} func The function or method name to reference.
* @param {number} bitmask The bitmask of method flags to compose.
* The bitmask may be composed of the following flags:
* 1 - `_.bind`
* 2 - `_.bindKey`
* 4 - `_.curry`
* 8 - `_.curry` (bound)
* 16 - `_.partial`
* 32 - `_.partialRight`
* @param {Array} [partialArgs] An array of arguments to prepend to those
* provided to the new function.
* @param {Array} [partialRightArgs] An array of arguments to append to those
* provided to the new function.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {number} [arity] The arity of `func`.
* @returns {Function} Returns the new bound function.
*/
function createBound(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
var isBind = bitmask & 1,
isBindKey = bitmask & 2,
isCurry = bitmask & 4,
isCurryBound = bitmask & 8,
isPartial = bitmask & 16,
isPartialRight = bitmask & 32,
key = func;
if (!isBindKey && !isFunction(func)) {
throw new TypeError;
}
if (isPartial && !partialArgs.length) {
bitmask &= ~16;
isPartial = partialArgs = false;
}
if (isPartialRight && !partialRightArgs.length) {
bitmask &= ~32;
isPartialRight = partialRightArgs = false;
}
var bindData = func && func.__bindData__;
if (bindData) {
if (isBind && !(bindData[1] & 1)) {
bindData[4] = thisArg;
}
if (!isBind && bindData[1] & 1) {
bitmask |= 8;
}
if (isCurry && !(bindData[1] & 4)) {
bindData[5] = arity;
}
if (isPartial) {
push.apply(bindData[2] || (bindData[2] = []), partialArgs);
}
if (isPartialRight) {
push.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
}
bindData[1] |= bitmask;
return createBound.apply(null, bindData);
}
// use `Function#bind` if it exists and is fast
// (in V8 `Function#bind` is slower except when partially applied)
if (isBind && !(isBindKey || isCurry || isPartialRight) &&
(support.fastBind || (nativeBind && isPartial))) {
if (isPartial) {
var args = [thisArg];
push.apply(args, partialArgs);
}
var bound = isPartial
? nativeBind.apply(func, args)
: nativeBind.call(func, thisArg);
}
else {
bound = function() {
// `Function#bind` spec
// http://es5.github.io/#x15.3.4.5
var args = arguments,
thisBinding = isBind ? thisArg : this;
if (isCurry || isPartial || isPartialRight) {
args = nativeSlice.call(args);
if (isPartial) {
unshift.apply(args, partialArgs);
}
if (isPartialRight) {
push.apply(args, partialRightArgs);
}
if (isCurry && args.length < arity) {
bitmask |= 16 & ~32;
return createBound(func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity);
}
}
if (isBindKey) {
func = thisBinding[key];
}
if (this instanceof bound) {
// ensure `new bound` is an instance of `func`
thisBinding = createObject(func.prototype);
// mimic the constructor's `return` behavior
// http://es5.github.io/#x13.2.2
var result = func.apply(thisBinding, args);
return isObject(result) ? result : thisBinding;
}
return func.apply(thisBinding, args);
};
}
setBindData(bound, nativeSlice.call(arguments));
return bound;
}
module.exports = createBound;
},{"lodash._createobject":15,"lodash._renative":19,"lodash._setbinddata":5,"lodash.isfunction":17,"lodash.isobject":18,"lodash.support":21}],15:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var isObject = require('lodash.isobject'),
noop = require('lodash._noop'),
reNative = require('lodash._renative');
/** Used for native method references */
var objectProto = Object.prototype;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate;
/**
* Creates a new object with the specified `prototype`.
*
* @private
* @param {Object} prototype The prototype object.
* @returns {Object} Returns the new object.
*/
function createObject(prototype) {
return isObject(prototype) ? nativeCreate(prototype) : {};
}
module.exports = createObject;
},{"lodash._noop":16,"lodash._renative":19,"lodash.isobject":18}],16:[function(require,module,exports){
module.exports=require(8)
},{}],17:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/**
* Checks if `value` is a function.
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*/
function isFunction(value) {
return typeof value == 'function';
}
module.exports = isFunction;
},{}],18:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var objectTypes = require('lodash._objecttypes');
/**
* Checks if `value` is the language type of Object.
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @category Objects
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if the `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(1);
* // => false
*/
function isObject(value) {
// check if the value is the ECMAScript language type of Object
// http://es5.github.io/#x8
// and avoid a V8 bug
// http://code.google.com/p/v8/issues/detail?id=2291
return !!(value && objectTypes[typeof value]);
}
module.exports = isObject;
},{"lodash._objecttypes":23}],19:[function(require,module,exports){
module.exports=require(12)
},{}],20:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/**
* This method returns the first argument provided to it.
*
* @static
* @memberOf _
* @category Utilities
* @param {*} value Any value.
* @returns {*} Returns `value`.
* @example
*
* var moe = { 'name': 'moe' };
* moe === _.identity(moe);
* // => true
*/
function identity(value) {
return value;
}
module.exports = identity;
},{}],21:[function(require,module,exports){
var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var reNative = require('lodash._renative');
/** Used to detect functions containing a `this` reference */
var reThis = /\bthis\b/;
/** Used for native method references */
var objectProto = Object.prototype;
/** Native method shortcuts */
var toString = objectProto.toString;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeBind = reNative.test(nativeBind = toString.bind) && nativeBind;
/** Detect various environments */
var isIeOpera = reNative.test(global.attachEvent),
isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera);
/**
* An object used to flag environments features.
*
* @static
* @memberOf _
* @type Object
*/
var support = {};
/**
* Detect if `Function#bind` exists and is inferred to be fast (all but V8).
*
* @memberOf _.support
* @type boolean
*/
support.fastBind = nativeBind && !isV8;
/**
* Detect if functions can be decompiled by `Function#toString`
* (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
*
* @memberOf _.support
* @type boolean
*/
support.funcDecomp = !reNative.test(global.WinRTError) && reThis.test(function() { return this; });
/**
* Detect if `Function#name` is supported (all but IE).
*
* @memberOf _.support
* @type boolean
*/
support.funcNames = typeof Function.name == 'string';
module.exports = support;
},{"lodash._renative":22}],22:[function(require,module,exports){
module.exports=require(12)
},{}],23:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used to determine if values are of the language type Object */
var objectTypes = {
'boolean': false,
'function': true,
'object': true,
'number': false,
'string': false,
'undefined': false
};
module.exports = objectTypes;
},{}],24:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var isObject = require('lodash.isobject'),
reNative = require('lodash._renative'),
shimKeys = require('lodash._shimkeys');
/** Used for native method references */
var objectProto = Object.prototype;
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys;
/**
* Creates an array composed of the own enumerable property names of an object.
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property names.
* @example
*
* _.keys({ 'one': 1, 'two': 2, 'three': 3 });
* // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
*/
var keys = !nativeKeys ? shimKeys : function(object) {
if (!isObject(object)) {
return [];
}
return nativeKeys(object);
};
module.exports = keys;
},{"lodash._renative":25,"lodash._shimkeys":26,"lodash.isobject":27}],25:[function(require,module,exports){
module.exports=require(12)
},{}],26:[function(require,module,exports){
/**
* Lo-Dash 2.1.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var objectTypes = require('lodash._objecttypes');
/** Used for native method references */
var objectProto = Object.prototype;
/** Native method shortcuts */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* A fallback implementation of `Object.keys` which produces an array of the
* given object's own enumerable property names.
*
* @private
* @type Function
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property names.
*/
var shimKeys = function(object) {
var index, iterable = object, result = [];
if (!iterable) return result;
if (!(objectTypes[typeof object])) return result;
for (index in iterable) {
if (hasOwnProperty.call(iterable, index)) {
result.push(index);
}
}
return result
};
module.exports = shimKeys;
},{"lodash._objecttypes":23}],27:[function(require,module,exports){
module.exports=require(18)
},{"lodash._objecttypes":23}],28:[function(require,module,exports){
var ich = require('icanhaz')
module.exports.ich = ich
module.exports.getKeywordCount = function(data, keyword) {
var group = []
data.forEach(function (d) {
for(var key in d) {
var value = d[key].toString().toLowerCase()
if (value.match(keyword.toLowerCase())) group.push(d)
}
})
return group.length
if (group = []) return "0"
}
module.exports.getKeyword = function(data, keyword) {
var group = []
data.forEach(function (d) {
for(var key in d) {
var value = d[key].toString().toLowerCase()
if (value.match(keyword.toLowerCase())) group.push(d)
}
})
return group
if (group = []) return "no matches"
}
module.exports.getColumnTotal = function(data, column) {
var total = []
data.forEach(function (d) {
if (d[column] === "") return
total.push(+d[column])
})
return total.reduce(function(a,b) {
return a + b
})
}
module.exports.getColumnAverage = function(data, column) {
var total = getColumnTotal(data, column)
var average = total / data.length
return average
}
module.exports.getMax = function(data, column) {
var result = []
data.forEach(function (element){
if (result.length === 0) return result.push(element)
else {
if (element[column].valueOf() > result[0][column].valueOf()) {
result.length = 0
return result.push(element)
}
if (element[column].valueOf() === result[0][column].valueOf()) {
return result.push(element)
}
}
})
return result
}
module.exports.getMin = function(data, column) {
var result = []
data.forEach(function (element){
if (result.length === 0) return result.push(element)
else {
if (element[column].valueOf() < result[0][column].valueOf()) {
result.length = 0
return result.push(element)
}
if (element[column].valueOf() === result[0][column].valueOf()) {
return result.push(element)
}
}
})
return result
}
// out of the data, filter something from a category
module.exports.getMatches = function (data, filter, category) {
var matches = []
data.forEach(function (element) {
var projectType = element[category].toString().toLowerCase()
if (projectType === filter.toLowerCase()) matches.push(element)
})
return matches
}
module.exports.mostFrequent = function(data, category) {
var count = {}
for (var i = 0; i < data.length; i++) {
if (!count[data[i][category]]) {
count[data[i][category]] = 0
}
count[data[i][category]]++
}
var sortable = []
for (var category in count) {
sortable.push([category, count[category]])
}
sortable.sort(function(a, b) {return b[1] - a[1]})
return sortable
// returns array of arrays, in order
}
// thank you! http://james.padolsey.com/javascript/deep-copying-of-objects-and-arrays/
module.exports.deepCopy = function(obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
var out = [], i = 0, len = obj.length;
for ( ; i < len; i++ ) {
out[i] = arguments.callee(obj[i]);
}
return out;
}
if (typeof obj === 'object') {
var out = {}, i;
for ( i in obj ) {
out[i] = arguments.callee(obj[i]);
}
return out;
}
return obj;
}
module.exports.getOccurance = function(data, category) {
var occuranceCount = {}
for (var i = 0; i < data.length; i++) {
if (!occuranceCount[data[i][category]]) {
occuranceCount[data[i][category]] = 0
}
occuranceCount[data[i][category]]++
}
return occuranceCount
// returns object, keys alphabetical
}
module.exports.makeColorArrayOfObject = function(data, colors, category) {
var category = category
var keys = Object.keys(data)
var counter = 1
var colorIndex
return keys.map(function(key){
if (keys.length > colors.length || keys.length <= colors.length ) {
colorIndex = counter % colors.length
}
var h = {units: data[key], hexcolor: colors[colorIndex]}
h[category] = key
counter++
colorIndex = counter
return h
})
}
module.exports.makeArrayOfObject = function(data) {
var keys = Object.keys(data)
return keys.map(function(key){
// var h = {label: key, units: data[key], hexcolor: "#FDBDBD"}
var h = {label: key, units: data[key]}
return h
})
}
},{"icanhaz":2}],29:[function(require,module,exports){
var mapbox = require('mapbox.js')
var ich = require('icanhaz')
module.exports.buildOptionObject = buildOptionObject
function buildOptionObject(optionsJSON, lineItem) {
var newObj = {}
optionsJSON.forEach(function(option) {
newObj[option] = lineItem[option]
})
return newObj
}
module.exports.makeupOptionObject = function(lineItem) {
var options = []
for (var i in lineItem) {
options.push(i);
}
return options
}
module.exports.createGeoJSON = function(data, optionsJSON) {
var geoJSON = []
data.forEach(function(lineItem){
var hasGeo = confirmGeo(lineItem)
if (hasGeo && !lineItem.lat && !lineItem.long) handleLatLong(lineItem)
if (lineItem.linestring || lineItem.multipolygon) hasGeo = true
if (!hasGeo) return
if (!optionsJSON) {
optionsJSON = makeupOptionObject(lineItem)
var optionObj = buildOptionObject(optionsJSON, lineItem)
} else {
optionObj = buildOptionObject(optionsJSON, lineItem)
}
var type = determineType(lineItem)
if (lineItem.polygon || lineItem.multipolygon || lineItem.linestring) {
var shapeFeature = shapeJSON(lineItem, type, optionObj)
geoJSON.push(shapeFeature)
} else {
var pointFeature = pointJSON(lineItem, type, optionObj)
geoJSON.push(pointFeature)
}
})
return geoJSON
}
module.exports.confirmGeo = confirmGeo
function confirmGeo(lineItem) {
var hasGeo = false
if (lineItem.lat && lineItem.long || lineItem.polygon) hasGeo = true
if (lineItem.latitude && lineItem.longitude || lineItem.polygon) hasGeo = true
if (lineItem.geolatitude && lineItem.geolongitude || lineItem.polygon) hasGeo = true
return hasGeo
}
module.exports.handleLatLong = handleLatLong
function handleLatLong(lineItem) {
if (lineItem.latitude && lineItem.longitude || lineItem.polygon) {
lineItem.lat = lineItem.latitude
lineItem.long = lineItem.longitude
delete lineItem.latitude
delete lineItem.longitude
return lineItem
}
if (lineItem.geolatitude && lineItem.geolongitude || lineItem.polygon) {
lineItem.lat = lineItem.geolatitude
lineItem.long = lineItem.geolongitude
delete lineItem.geolatitude
delete lineItem.geolongitude
return lineItem
}
}
module.exports.pointJSON = pointJSON
function pointJSON(lineItem, type, optionObj) {
var lowercaseType = type.toLowerCase()
var pointFeature = {
type: "Feature",
"geometry": {
"type": type,
"coordinates": [+lineItem.long, +lineItem.lat]
},
"properties": {
"marker-size": "small",
"marker-color": lineItem.hexcolor
},
"opts": optionObj
}
return pointFeature
}
module.exports.shapeJSON = shapeJSON
function shapeJSON(lineItem, type, optionObj) {
var lowercaseType = type.toLowerCase()
var coords
if (type !== "LineString") {
coords = JSON.parse( "[[" + lineItem[lowercaseType] + "]]" )
} else { coords = JSON.parse("[" + lineItem[lowercaseType] + "]") }
var shapeFeature = {
type: "Feature",
"geometry": {
"type": type,
"coordinates": coords
},
"properties": {
"fillColor": lineItem.hexcolor,
"color": lineItem.hexcolor
},
"opts": optionObj
}
return shapeFeature
}
module.exports.determineType = determineType
function determineType(lineItem) {
var type = ""
if (lineItem.lat && lineItem.long) type = "Point"
if (lineItem.polygon) type = "Polygon"
if (lineItem.multipolygon) type = "MultiPolygon"
if (lineItem.linestring) type = "LineString"
return type
}
module.exports.loadMap = function(mapDiv) {
var map = L.mapbox.map(mapDiv)
map.touchZoom.disable()
map.doubleClickZoom.disable()
map.scrollWheelZoom.disable()
return map
}
module.exports.addTileLayer = function(map, tileLayer) {
var layer = L.mapbox.tileLayer(tileLayer)
layer.addTo(map)
}
module.exports.makePopupTemplate = makePopupTemplate
function makePopupTemplate(geoJSON) {
var allOptions = geoJSON[0].opts
var keys = []
for (var i in allOptions) keys.push(i)
var mustacheKeys = mustachify(keys)
var template = {}
template.name = "popup" + Math.random()
template.template = templateString(mustacheKeys)
return template
}
module.exports.templateString = templateString
function templateString(mustacheKeys) {
var template = "<ul>"
var counter = mustacheKeys.length
mustacheKeys.forEach(function(key) {
counter--
if (counter === 0) template = template.concat(key, "</ul>")
else template = template.concat(key)
})
return template
}
module.exports.mustachify = mustachify
function mustachify(array) {
var newArray = []
array.forEach(function(item) {
item = "<li><b>" + item + ":</b> {{" + item + "}}</li>"
newArray.push(item)
})
return newArray
}
module.exports.addMarkerLayer = function(geoJSON, map, template) {
if (!template) {
template = makePopupTemplate(geoJSON)
ich.addTemplate(template.name, template.template)
}
else {
var template = {"template": template}
template.name = "popup" + Math.random()
ich.addTemplate(template.name, template.template)
}
var features = {
"type": "FeatureCollection",
"features": geoJSON
}
var layer = L.geoJson(features, {
pointToLayer: L.mapbox.marker.style,
style: function(feature) { return feature.properties }
})
var bounds = layer.getBounds()
layer.addTo(map)
map.fitBounds(bounds)
layer.eachLayer(function(marker) {
var popupContent = ich[template.name](marker.feature.opts)
marker.bindPopup(popupContent.html(), {closeButton: false})
})
return layer
}
},{"icanhaz":2,"mapbox.js":31}],30:[function(require,module,exports){
// Copyright (C) 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Implements RFC 3986 for parsing/formatting URIs.
*
* @author mikesamuel@gmail.com
* \@provides URI
* \@overrides window
*/
var URI = (function () {
/**
* creates a uri from the string form. The parser is relaxed, so special
* characters that aren't escaped but don't cause ambiguities will not cause
* parse failures.
*
* @return {URI|null}
*/
function parse(uriStr) {
var m = ('' + uriStr).match(URI_RE_);
if (!m) { return null; }
return new URI(
nullIfAbsent(m[1]),
nullIfAbsent(m[2]),
nullIfAbsent(m[3]),
nullIfAbsent(m[4]),
nullIfAbsent(m[5]),
nullIfAbsent(m[6]),
nullIfAbsent(m[7]));
}
/**
* creates a uri from the given parts.
*
* @param scheme {string} an unencoded scheme such as "http" or null
* @param credentials {string} unencoded user credentials or null
* @param domain {string} an unencoded domain name or null
* @param port {number} a port number in [1, 32768].
* -1 indicates no port, as does null.
* @param path {string} an unencoded path
* @param query {Array.<string>|string|null} a list of unencoded cgi
* parameters where even values are keys and odds the corresponding values
* or an unencoded query.
* @param fragment {string} an unencoded fragment without the "#" or null.
* @return {URI}
*/
function create(scheme, credentials, domain, port, path, query, fragment) {
var uri = new URI(
encodeIfExists2(scheme, URI_DISALLOWED_IN_SCHEME_OR_CREDENTIALS_),
encodeIfExists2(
credentials, URI_DISALLOWED_IN_SCHEME_OR_CREDENTIALS_),
encodeIfExists(domain),
port > 0 ? port.toString() : null,
encodeIfExists2(path, URI_DISALLOWED_IN_PATH_),
null,
encodeIfExists(fragment));
if (query) {
if ('string' === typeof query) {
uri.setRawQuery(query.replace(/[^?&=0-9A-Za-z_\-~.%]/g, encodeOne));
} else {
uri.setAllParameters(query);
}
}
return uri;
}
function encodeIfExists(unescapedPart) {
if ('string' == typeof unescapedPart) {
return encodeURIComponent(unescapedPart);
}
return null;
};
/**
* if unescapedPart is non null, then escapes any characters in it that aren't
* valid characters in a url and also escapes any special characters that
* appear in extra.
*
* @param unescapedPart {string}
* @param extra {RegExp} a character set of characters in [\01-\177].
* @return {string|null} null iff unescapedPart == null.
*/
function encodeIfExists2(unescapedPart, extra) {
if ('string' == typeof unescapedPart) {
return encodeURI(unescapedPart).replace(extra, encodeOne);
}
return null;
};
/** converts a character in [\01-\177] to its url encoded equivalent. */
function encodeOne(ch) {
var n = ch.charCodeAt(0);
return '%' + '0123456789ABCDEF'.charAt((n >> 4) & 0xf) +
'0123456789ABCDEF'.charAt(n & 0xf);
}
/**
* {@updoc
* $ normPath('foo/./bar')
* # 'foo/bar'
* $ normPath('./foo')
* # 'foo'
* $ normPath('foo/.')
* # 'foo'
* $ normPath('foo//bar')
* # 'foo/bar'
* }
*/
function normPath(path) {
return path.replace(/(^|\/)\.(?:\/|$)/g, '$1').replace(/\/{2,}/g, '/');
}
var PARENT_DIRECTORY_HANDLER = new RegExp(
''
// A path break
+ '(/|^)'
// followed by a non .. path element
// (cannot be . because normPath is used prior to this RegExp)
+ '(?:[^./][^/]*|\\.{2,}(?:[^./][^/]*)|\\.{3,}[^/]*)'
// followed by .. followed by a path break.
+ '/\\.\\.(?:/|$)');
var PARENT_DIRECTORY_HANDLER_RE = new RegExp(PARENT_DIRECTORY_HANDLER);
var EXTRA_PARENT_PATHS_RE = /^(?:\.\.\/)*(?:\.\.$)?/;
/**
* Normalizes its input path and collapses all . and .. sequences except for
* .. sequences that would take it above the root of the current parent
* directory.
* {@updoc
* $ collapse_dots('foo/../bar')
* # 'bar'
* $ collapse_dots('foo/./bar')
* # 'foo/bar'
* $ collapse_dots('foo/../bar/./../../baz')
* # 'baz'
* $ collapse_dots('../foo')
* # '../foo'
* $ collapse_dots('../foo').replace(EXTRA_PARENT_PATHS_RE, '')
* # 'foo'
* }
*/
function collapse_dots(path) {
if (path === null) { return null; }
var p = normPath(path);
// Only /../ left to flatten
var r = PARENT_DIRECTORY_HANDLER_RE;
// We replace with $1 which matches a / before the .. because this
// guarantees that:
// (1) we have at most 1 / between the adjacent place,
// (2) always have a slash if there is a preceding path section, and
// (3) we never turn a relative path into an absolute path.
for (var q; (q = p.replace(r, '$1')) != p; p = q) {};
return p;
}
/**
* resolves a relative url string to a base uri.
* @return {URI}
*/
function resolve(baseUri, relativeUri) {
// there are several kinds of relative urls:
// 1. //foo - replaces everything from the domain on. foo is a domain name
// 2. foo - replaces the last part of the path, the whole query and fragment
// 3. /foo - replaces the the path, the query and fragment
// 4. ?foo - replace the query and fragment
// 5. #foo - replace the fragment only
var absoluteUri = baseUri.clone();
// we satisfy these conditions by looking for the first part of relativeUri
// that is not blank and applying defaults to the rest
var overridden = relativeUri.hasScheme();
if (overridden) {
absoluteUri.setRawScheme(relativeUri.getRawScheme());
} else {
overridden = relativeUri.hasCredentials();
}
if (overridden) {
absoluteUri.setRawCredentials(relativeUri.getRawCredentials());
} else {
overridden = relativeUri.hasDomain();
}
if (overridden) {
absoluteUri.setRawDomain(relativeUri.getRawDomain());
} else {
overridden = relativeUri.hasPort();
}
var rawPath = relativeUri.getRawPath();
var simplifiedPath = collapse_dots(rawPath);
if (overridden) {
absoluteUri.setPort(relativeUri.getPort());
simplifiedPath = simplifiedPath
&& simplifiedPath.replace(EXTRA_PARENT_PATHS_RE, '');
} else {
overridden = !!rawPath;
if (overridden) {
// resolve path properly
if (simplifiedPath.charCodeAt(0) !== 0x2f /* / */) { // path is relative
var absRawPath = collapse_dots(absoluteUri.getRawPath() || '')
.replace(EXTRA_PARENT_PATHS_RE, '');
var slash = absRawPath.lastIndexOf('/') + 1;
simplifiedPath = collapse_dots(
(slash ? absRawPath.substring(0, slash) : '')
+ collapse_dots(rawPath))
.replace(EXTRA_PARENT_PATHS_RE, '');
}
} else {
simplifiedPath = simplifiedPath
&& simplifiedPath.replace(EXTRA_PARENT_PATHS_RE, '');
if (simplifiedPath !== rawPath) {
absoluteUri.setRawPath(simplifiedPath);
}
}
}
if (overridden) {
absoluteUri.setRawPath(simplifiedPath);
} else {
overridden = relativeUri.hasQuery();
}
if (overridden) {
absoluteUri.setRawQuery(relativeUri.getRawQuery());
} else {
overridden = relativeUri.hasFragment();
}
if (overridden) {
absoluteUri.setRawFragment(relativeUri.getRawFragment());
}
return absoluteUri;
}
/**
* a mutable URI.
*
* This class contains setters and getters for the parts of the URI.
* The <tt>getXYZ</tt>/<tt>setXYZ</tt> methods return the decoded part -- so
* <code>uri.parse('/foo%20bar').getPath()</code> will return the decoded path,
* <tt>/foo bar</tt>.
*
* <p>The raw versions of fields are available too.
* <code>uri.parse('/foo%20bar').getRawPath()</code> will return the raw path,
* <tt>/foo%20bar</tt>. Use the raw setters with care, since
* <code>URI::toString</code> is not guaranteed to return a valid url if a
* raw setter was used.
*
* <p>All setters return <tt>this</tt> and so may be chained, a la
* <code>uri.parse('/foo').setFragment('part').toString()</code>.
*
* <p>You should not use this constructor directly -- please prefer the factory
* functions {@link uri.parse}, {@link uri.create}, {@link uri.resolve}
* instead.</p>
*
* <p>The parameters are all raw (assumed to be properly escaped) parts, and
* any (but not all) may be null. Undefined is not allowed.</p>
*
* @constructor
*/
function URI(
rawScheme,
rawCredentials, rawDomain, port,
rawPath, rawQuery, rawFragment) {
this.scheme_ = rawScheme;
this.credentials_ = rawCredentials;
this.domain_ = rawDomain;
this.port_ = port;
this.path_ = rawPath;
this.query_ = rawQuery;
this.fragment_ = rawFragment;
/**
* @type {Array|null}
*/
this.paramCache_ = null;
}
/** returns the string form of the url. */
URI.prototype.toString = function () {
var out = [];
if (null !== this.scheme_) { out.push(this.scheme_, ':'); }
if (null !== this.domain_) {
out.push('//');
if (null !== this.credentials_) { out.push(this.credentials_, '@'); }
out.push(this.domain_);
if (null !== this.port_) { out.push(':', this.port_.toString()); }
}
if (null !== this.path_) { out.push(this.path_); }
if (null !== this.query_) { out.push('?', this.query_); }
if (null !== this.fragment_) { out.push('#', this.fragment_); }
return out.join('');
};
URI.prototype.clone = function () {
return new URI(this.scheme_, this.credentials_, this.domain_, this.port_,
this.path_, this.query_, this.fragment_);
};
URI.prototype.getScheme = function () {
// HTML5 spec does not require the scheme to be lowercased but
// all common browsers except Safari lowercase the scheme.
return this.scheme_ && decodeURIComponent(this.scheme_).toLowerCase();
};
URI.prototype.getRawScheme = function () {
return this.scheme_;
};
URI.prototype.setScheme = function (newScheme) {
this.scheme_ = encodeIfExists2(
newScheme, URI_DISALLOWED_IN_SCHEME_OR_CREDENTIALS_);
return this;
};
URI.prototype.setRawScheme = function (newScheme) {
this.scheme_ = newScheme ? newScheme : null;
return this;
};
URI.prototype.hasScheme = function () {
return null !== this.scheme_;
};
URI.prototype.getCredentials = function () {
return this.credentials_ && decodeURIComponent(this.credentials_);
};
URI.prototype.getRawCredentials = function () {
return this.credentials_;
};
URI.prototype.setCredentials = function (newCredentials) {
this.credentials_ = encodeIfExists2(
newCredentials, URI_DISALLOWED_IN_SCHEME_OR_CREDENTIALS_);
return this;
};
URI.prototype.setRawCredentials = function (newCredentials) {
this.credentials_ = newCredentials ? newCredentials : null;
return this;
};
URI.prototype.hasCredentials = function () {
return null !== this.credentials_;
};
URI.prototype.getDomain = function () {
return this.domain_ && decodeURIComponent(this.domain_);
};
URI.prototype.getRawDomain = function () {
return this.domain_;
};
URI.prototype.setDomain = function (newDomain) {
return this.setRawDomain(newDomain && encodeURIComponent(newDomain));
};
URI.prototype.setRawDomain = function (newDomain) {
this.domain_ = newDomain ? newDomain : null;
// Maintain the invariant that paths must start with a slash when the URI
// is not path-relative.
return this.setRawPath(this.path_);
};
URI.prototype.hasDomain = function () {
return null !== this.domain_;
};
URI.prototype.getPort = function () {
return this.port_ && decodeURIComponent(this.port_);
};
URI.prototype.setPort = function (newPort) {
if (newPort) {
newPort = Number(newPort);
if (newPort !== (newPort & 0xffff)) {
throw new Error('Bad port number ' + newPort);
}
this.port_ = '' + newPort;
} else {
this.port_ = null;
}
return this;
};
URI.prototype.hasPort = function () {
return null !== this.port_;
};
URI.prototype.getPath = function () {
return this.path_ && decodeURIComponent(this.path_);
};
URI.prototype.getRawPath = function () {
return this.path_;
};
URI.prototype.setPath = function (newPath) {
return this.setRawPath(encodeIfExists2(newPath, URI_DISALLOWED_IN_PATH_));
};
URI.prototype.setRawPath = function (newPath) {
if (newPath) {
newPath = String(newPath);
this.path_ =
// Paths must start with '/' unless this is a path-relative URL.
(!this.domain_ || /^\//.test(newPath)) ? newPath : '/' + newPath;
} else {
this.path_ = null;
}
return this;
};
URI.prototype.hasPath = function () {
return null !== this.path_;
};
URI.prototype.getQuery = function () {
// From http://www.w3.org/Addressing/URL/4_URI_Recommentations.html
// Within the query string, the plus sign is reserved as shorthand notation
// for a space.
return this.query_ && decodeURIComponent(this.query_).replace(/\+/g, ' ');
};
URI.prototype.getRawQuery = function () {
return this.query_;
};
URI.prototype.setQuery = function (newQuery) {
this.paramCache_ = null;
this.query_ = encodeIfExists(newQuery);
return this;
};
URI.prototype.setRawQuery = function (newQuery) {
this.paramCache_ = null;
this.query_ = newQuery ? newQuery : null;
return this;
};
URI.prototype.hasQuery = function () {
return null !== this.query_;
};
/**
* sets the query given a list of strings of the form
* [ key0, value0, key1, value1, ... ].
*
* <p><code>uri.setAllParameters(['a', 'b', 'c', 'd']).getQuery()</code>
* will yield <code>'a=b&c=d'</code>.
*/
URI.prototype.setAllParameters = function (params) {
if (typeof params === 'object') {
if (!(params instanceof Array)
&& (params instanceof Object
|| Object.prototype.toString.call(params) !== '[object Array]')) {
var newParams = [];
var i = -1;
for (var k in params) {
var v = params[k];
if ('string' === typeof v) {
newParams[++i] = k;
newParams[++i] = v;
}
}
params = newParams;
}
}
this.paramCache_ = null;
var queryBuf = [];
var separator = '';
for (var j = 0; j < params.length;) {
var k = params[j++];
var v = params[j++];
queryBuf.push(separator, encodeURIComponent(k.toString()));
separator = '&';
if (v) {
queryBuf.push('=', encodeURIComponent(v.toString()));
}
}
this.query_ = queryBuf.join('');
return this;
};
URI.prototype.checkParameterCache_ = function () {
if (!this.paramCache_) {
var q = this.query_;
if (!q) {
this.paramCache_ = [];
} else {
var cgiParams = q.split(/[&\?]/);
var out = [];
var k = -1;
for (var i = 0; i < cgiParams.length; ++i) {
var m = cgiParams[i].match(/^([^=]*)(?:=(.*))?$/);
// From http://www.w3.org/Addressing/URL/4_URI_Recommentations.html
// Within the query string, the plus sign is reserved as shorthand
// notation for a space.
out[++k] = decodeURIComponent(m[1]).replace(/\+/g, ' ');
out[++k] = decodeURIComponent(m[2] || '').replace(/\+/g, ' ');
}
this.paramCache_ = out;
}
}
};
/**
* sets the values of the named cgi parameters.
*
* <p>So, <code>uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])
* </code> yields <tt>foo?a=b&c=new&e=f</tt>.</p>
*
* @param key {string}
* @param values {Array.<string>} the new values. If values is a single string
* then it will be treated as the sole value.
*/
URI.prototype.setParameterValues = function (key, values) {
// be nice and avoid subtle bugs where [] operator on string performs charAt
// on some browsers and crashes on IE
if (typeof values === 'string') {
values = [ values ];
}
this.checkParameterCache_();
var newValueIndex = 0;
var pc = this.paramCache_;
var params = [];
for (var i = 0, k = 0; i < pc.length; i += 2) {
if (key === pc[i]) {
if (newValueIndex < values.length) {
params.push(key, values[newValueIndex++]);
}
} else {
params.push(pc[i], pc[i + 1]);
}
}
while (newValueIndex < values.length) {
params.push(key, values[newValueIndex++]);
}
this.setAllParameters(params);
return this;
};
URI.prototype.removeParameter = function (key) {
return this.setParameterValues(key, []);
};
/**
* returns the parameters specified in the query part of the uri as a list of
* keys and values like [ key0, value0, key1, value1, ... ].
*
* @return {Array.<string>}
*/
URI.prototype.getAllParameters = function () {
this.checkParameterCache_();
return this.paramCache_.slice(0, this.paramCache_.length);
};
/**
* returns the value<b>s</b> for a given cgi parameter as a list of decoded
* query parameter values.
* @return {Array.<string>}
*/
URI.prototype.getParameterValues = function (paramNameUnescaped) {
this.checkParameterCache_();
var values = [];
for (var i = 0; i < this.paramCache_.length; i += 2) {
if (paramNameUnescaped === this.paramCache_[i]) {
values.push(this.paramCache_[i + 1]);
}
}
return values;
};
/**
* returns a map of cgi parameter names to (non-empty) lists of values.
* @return {Object.<string,Array.<string>>}
*/
URI.prototype.getParameterMap = function (paramNameUnescaped) {
this.checkParameterCache_();
var paramMap = {};
for (var i = 0; i < this.paramCache_.length; i += 2) {
var key = this.paramCache_[i++],
value = this.paramCache_[i++];
if (!(key in paramMap)) {
paramMap[key] = [value];
} else {
paramMap[key].push(value);
}
}
return paramMap;
};
/**
* returns the first value for a given cgi parameter or null if the given
* parameter name does not appear in the query string.
* If the given parameter name does appear, but has no '<tt>=</tt>' following
* it, then the empty string will be returned.
* @return {string|null}
*/
URI.prototype.getParameterValue = function (paramNameUnescaped) {
this.checkParameterCache_();
for (var i = 0; i < this.paramCache_.length; i += 2) {
if (paramNameUnescaped === this.paramCache_[i]) {
return this.paramCache_[i + 1];
}
}
return null;
};
URI.prototype.getFragment = function () {
return this.fragment_ && decodeURIComponent(this.fragment_);
};
URI.prototype.getRawFragment = function () {
return this.fragment_;
};
URI.prototype.setFragment = function (newFragment) {
this.fragment_ = newFragment ? encodeURIComponent(newFragment) : null;
return this;
};
URI.prototype.setRawFragment = function (newFragment) {
this.fragment_ = newFragment ? newFragment : null;
return this;
};
URI.prototype.hasFragment = function () {
return null !== this.fragment_;
};
function nullIfAbsent(matchPart) {
return ('string' == typeof matchPart) && (matchPart.length > 0)
? matchPart
: null;
}
/**
* a regular expression for breaking a URI into its component parts.
*
* <p>http://www.gbiv.com/protocols/uri/rfc/rfc3986.html#RFC2234 says
* As the "first-match-wins" algorithm is identical to the "greedy"
* disambiguation method used by POSIX regular expressions, it is natural and
* commonplace to use a regular expression for parsing the potential five
* components of a URI reference.
*
* <p>The following line is the regular expression for breaking-down a
* well-formed URI reference into its components.
*
* <pre>
* ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
* 12 3 4 5 6 7 8 9
* </pre>
*
* <p>The numbers in the second line above are only to assist readability; they
* indicate the reference points for each subexpression (i.e., each paired
* parenthesis). We refer to the value matched for subexpression <n> as $<n>.
* For example, matching the above expression to
* <pre>
* http://www.ics.uci.edu/pub/ietf/uri/#Related
* </pre>
* results in the following subexpression matches:
* <pre>
* $1 = http:
* $2 = http
* $3 = //www.ics.uci.edu
* $4 = www.ics.uci.edu
* $5 = /pub/ietf/uri/
* $6 = <undefined>
* $7 = <undefined>
* $8 = #Related
* $9 = Related
* </pre>
* where <undefined> indicates that the component is not present, as is the
* case for the query component in the above example. Therefore, we can
* determine the value of the five components as
* <pre>
* scheme = $2
* authority = $4
* path = $5
* query = $7
* fragment = $9
* </pre>
*
* <p>msamuel: I have modified the regular expression slightly to expose the
* credentials, domain, and port separately from the authority.
* The modified version yields
* <pre>
* $1 = http scheme
* $2 = <undefined> credentials -\
* $3 = www.ics.uci.edu domain | authority
* $4 = <undefined> port -/
* $5 = /pub/ietf/uri/ path
* $6 = <undefined> query without ?
* $7 = Related fragment without #
* </pre>
*/
var URI_RE_ = new RegExp(
"^" +
"(?:" +
"([^:/?#]+)" + // scheme
":)?" +
"(?://" +
"(?:([^/?#]*)@)?" + // credentials
"([^/?#:@]*)" + // domain
"(?::([0-9]+))?" + // port
")?" +
"([^?#]+)?" + // path
"(?:\\?([^#]*))?" + // query
"(?:#(.*))?" + // fragment
"$"
);
var URI_DISALLOWED_IN_SCHEME_OR_CREDENTIALS_ = /[#\/\?@]/g;
var URI_DISALLOWED_IN_PATH_ = /[\#\?]/g;
URI.parse = parse;
URI.create = create;
URI.resolve = resolve;
URI.collapse_dots = collapse_dots; // Visible for testing.
// lightweight string-based api for loadModuleMaker
URI.utils = {
mimeTypeOf: function (uri) {
var uriObj = parse(uri);
if (/\.html$/.test(uriObj.getPath())) {
return 'text/html';
} else {
return 'application/javascript';
}
},
resolve: function (base, uri) {
if (base) {
return resolve(parse(base), parse(uri)).toString();
} else {
return '' + uri;
}
}
};
return URI;
})();
// Copyright Google Inc.
// Licensed under the Apache Licence Version 2.0
// Autogenerated at Mon Feb 25 13:05:42 EST 2013
// @overrides window
// @provides html4
var html4 = {};
html4.atype = {
'NONE': 0,
'URI': 1,
'URI_FRAGMENT': 11,
'SCRIPT': 2,
'STYLE': 3,
'HTML': 12,
'ID': 4,
'IDREF': 5,
'IDREFS': 6,
'GLOBAL_NAME': 7,
'LOCAL_NAME': 8,
'CLASSES': 9,
'FRAME_TARGET': 10,
'MEDIA_QUERY': 13
};
html4[ 'atype' ] = html4.atype;
html4.ATTRIBS = {
'*::class': 9,
'*::dir': 0,
'*::draggable': 0,
'*::hidden': 0,
'*::id': 4,
'*::inert': 0,
'*::itemprop': 0,
'*::itemref': 6,
'*::itemscope': 0,
'*::lang': 0,
'*::onblur': 2,
'*::onchange': 2,
'*::onclick': 2,
'*::ondblclick': 2,
'*::onfocus': 2,
'*::onkeydown': 2,
'*::onkeypress': 2,
'*::onkeyup': 2,
'*::onload': 2,
'*::onmousedown': 2,
'*::onmousemove': 2,
'*::onmouseout': 2,
'*::onmouseover': 2,
'*::onmouseup': 2,
'*::onreset': 2,
'*::onscroll': 2,
'*::onselect': 2,
'*::onsubmit': 2,
'*::onunload': 2,
'*::spellcheck': 0,
'*::style': 3,
'*::title': 0,
'*::translate': 0,
'a::accesskey': 0,
'a::coords': 0,
'a::href': 1,
'a::hreflang': 0,
'a::name': 7,
'a::onblur': 2,
'a::onfocus': 2,
'a::shape': 0,
'a::tabindex': 0,
'a::target': 10,
'a::type': 0,
'area::accesskey': 0,
'area::alt': 0,
'area::coords': 0,
'area::href': 1,
'area::nohref': 0,
'area::onblur': 2,
'area::onfocus': 2,
'area::shape': 0,
'area::tabindex': 0,
'area::target': 10,
'audio::controls': 0,
'audio::loop': 0,
'audio::mediagroup': 5,
'audio::muted': 0,
'audio::preload': 0,
'bdo::dir': 0,
'blockquote::cite': 1,
'br::clear': 0,
'button::accesskey': 0,
'button::disabled': 0,
'button::name': 8,
'button::onblur': 2,
'button::onfocus': 2,
'button::tabindex': 0,
'button::type': 0,
'button::value': 0,
'canvas::height': 0,
'canvas::width': 0,
'caption::align': 0,
'col::align': 0,
'col::char': 0,
'col::charoff': 0,
'col::span': 0,
'col::valign': 0,
'col::width': 0,
'colgroup::align': 0,
'colgroup::char': 0,
'colgroup::charoff': 0,
'colgroup::span': 0,
'colgroup::valign': 0,
'colgroup::width': 0,
'command::checked': 0,
'command::command': 5,
'command::disabled': 0,
'command::icon': 1,
'command::label': 0,
'command::radiogroup': 0,
'command::type': 0,
'data::value': 0,
'del::cite': 1,
'del::datetime': 0,
'details::open': 0,
'dir::compact': 0,
'div::align': 0,
'dl::compact': 0,
'fieldset::disabled': 0,
'font::color': 0,
'font::face': 0,
'font::size': 0,
'form::accept': 0,
'form::action': 1,
'form::autocomplete': 0,
'form::enctype': 0,
'form::method': 0,
'form::name': 7,
'form::novalidate': 0,
'form::onreset': 2,
'form::onsubmit': 2,
'form::target': 10,
'h1::align': 0,
'h2::align': 0,
'h3::align': 0,
'h4::align': 0,
'h5::align': 0,
'h6::align': 0,
'hr::align': 0,
'hr::noshade': 0,
'hr::size': 0,
'hr::width': 0,
'iframe::align': 0,
'iframe::frameborder': 0,
'iframe::height': 0,
'iframe::marginheight': 0,
'iframe::marginwidth': 0,
'iframe::width': 0,
'img::align': 0,
'img::alt': 0,
'img::border': 0,
'img::height': 0,
'img::hspace': 0,
'img::ismap': 0,
'img::name': 7,
'img::src': 1,
'img::usemap': 11,
'img::vspace': 0,
'img::width': 0,
'input::accept': 0,
'input::accesskey': 0,
'input::align': 0,
'input::alt': 0,
'input::autocomplete': 0,
'input::checked': 0,
'input::disabled': 0,
'input::inputmode': 0,
'input::ismap': 0,
'input::list': 5,
'input::max': 0,
'input::maxlength': 0,
'input::min': 0,
'input::multiple': 0,
'input::name': 8,
'input::onblur': 2,
'input::onchange': 2,
'input::onfocus': 2,
'input::onselect': 2,
'input::placeholder': 0,
'input::readonly': 0,
'input::required': 0,
'input::size': 0,
'input::src': 1,
'input::step': 0,
'input::tabindex': 0,
'input::type': 0,
'input::usemap': 11,
'input::value': 0,
'ins::cite': 1,
'ins::datetime': 0,
'label::accesskey': 0,
'label::for': 5,
'label::onblur': 2,
'label::onfocus': 2,
'legend::accesskey': 0,
'legend::align': 0,
'li::type': 0,
'li::value': 0,
'map::name': 7,
'menu::compact': 0,
'menu::label': 0,
'menu::type': 0,
'meter::high': 0,
'meter::low': 0,
'meter::max': 0,
'meter::min': 0,
'meter::value': 0,
'ol::compact': 0,
'ol::reversed': 0,
'ol::start': 0,
'ol::type': 0,
'optgroup::disabled': 0,
'optgroup::label': 0,
'option::disabled': 0,
'option::label': 0,
'option::selected': 0,
'option::value': 0,
'output::for': 6,
'output::name': 8,
'p::align': 0,
'pre::width': 0,
'progress::max': 0,
'progress::min': 0,
'progress::value': 0,
'q::cite': 1,
'select::autocomplete': 0,
'select::disabled': 0,
'select::multiple': 0,
'select::name': 8,
'select::onblur': 2,
'select::onchange': 2,
'select::onfocus': 2,
'select::required': 0,
'select::size': 0,
'select::tabindex': 0,
'source::type': 0,
'table::align': 0,
'table::bgcolor': 0,
'table::border': 0,
'table::cellpadding': 0,
'table::cellspacing': 0,
'table::frame': 0,
'table::rules': 0,
'table::summary': 0,
'table::width': 0,
'tbody::align': 0,
'tbody::char': 0,
'tbody::charoff': 0,
'tbody::valign': 0,
'td::abbr': 0,
'td::align': 0,
'td::axis': 0,
'td::bgcolor': 0,
'td::char': 0,
'td::charoff': 0,
'td::colspan': 0,
'td::headers': 6,
'td::height': 0,
'td::nowrap': 0,
'td::rowspan': 0,
'td::scope': 0,
'td::valign': 0,
'td::width': 0,
'textarea::accesskey': 0,
'textarea::autocomplete': 0,
'textarea::cols': 0,
'textarea::disabled': 0,
'textarea::inputmode': 0,
'textarea::name': 8,
'textarea::onblur': 2,
'textarea::onchange': 2,
'textarea::onfocus': 2,
'textarea::onselect': 2,
'textarea::placeholder': 0,
'textarea::readonly': 0,
'textarea::required': 0,
'textarea::rows': 0,
'textarea::tabindex': 0,
'textarea::wrap': 0,
'tfoot::align': 0,
'tfoot::char': 0,
'tfoot::charoff': 0,
'tfoot::valign': 0,
'th::abbr': 0,
'th::align': 0,
'th::axis': 0,
'th::bgcolor': 0,
'th::char': 0,
'th::charoff': 0,
'th::colspan': 0,
'th::headers': 6,
'th::height': 0,
'th::nowrap': 0,
'th::rowspan': 0,
'th::scope': 0,
'th::valign': 0,
'th::width': 0,
'thead::align': 0,
'thead::char': 0,
'thead::charoff': 0,
'thead::valign': 0,
'tr::align': 0,
'tr::bgcolor': 0,
'tr::char': 0,
'tr::charoff': 0,
'tr::valign': 0,
'track::default': 0,
'track::kind': 0,
'track::label': 0,
'track::srclang': 0,
'ul::compact': 0,
'ul::type': 0,
'video::controls': 0,
'video::height': 0,
'video::loop': 0,
'video::mediagroup': 5,
'video::muted': 0,
'video::poster': 1,
'video::preload': 0,
'video::width': 0
};
html4[ 'ATTRIBS' ] = html4.ATTRIBS;
html4.eflags = {
'OPTIONAL_ENDTAG': 1,
'EMPTY': 2,
'CDATA': 4,
'RCDATA': 8,
'UNSAFE': 16,
'FOLDABLE': 32,
'SCRIPT': 64,
'STYLE': 128,
'VIRTUALIZED': 256
};
html4[ 'eflags' ] = html4.eflags;
html4.ELEMENTS = {
'a': 0,
'abbr': 0,
'acronym': 0,
'address': 0,
'applet': 272,
'area': 2,
'article': 0,
'aside': 0,
'audio': 0,
'b': 0,
'base': 274,
'basefont': 274,
'bdi': 0,
'bdo': 0,
'big': 0,
'blockquote': 0,
'body': 305,
'br': 2,
'button': 0,
'canvas': 0,
'caption': 0,
'center': 0,
'cite': 0,
'code': 0,
'col': 2,
'colgroup': 1,
'command': 2,
'data': 0,
'datalist': 0,
'dd': 1,
'del': 0,
'details': 0,
'dfn': 0,
'dialog': 272,
'dir': 0,
'div': 0,
'dl': 0,
'dt': 1,
'em': 0,
'fieldset': 0,
'figcaption': 0,
'figure': 0,
'font': 0,
'footer': 0,
'form': 0,
'frame': 274,
'frameset': 272,
'h1': 0,
'h2': 0,
'h3': 0,
'h4': 0,
'h5': 0,
'h6': 0,
'head': 305,
'header': 0,
'hgroup': 0,
'hr': 2,
'html': 305,
'i': 0,
'iframe': 4,
'img': 2,
'input': 2,
'ins': 0,
'isindex': 274,
'kbd': 0,
'keygen': 274,
'label': 0,
'legend': 0,
'li': 1,
'link': 274,
'map': 0,
'mark': 0,
'menu': 0,
'meta': 274,
'meter': 0,
'nav': 0,
'nobr': 0,
'noembed': 276,
'noframes': 276,
'noscript': 276,
'object': 272,
'ol': 0,
'optgroup': 0,
'option': 1,
'output': 0,
'p': 1,
'param': 274,
'pre': 0,
'progress': 0,
'q': 0,
's': 0,
'samp': 0,
'script': 84,
'section': 0,
'select': 0,
'small': 0,
'source': 2,
'span': 0,
'strike': 0,
'strong': 0,
'style': 148,
'sub': 0,
'summary': 0,
'sup': 0,
'table': 0,
'tbody': 1,
'td': 1,
'textarea': 8,
'tfoot': 1,
'th': 1,
'thead': 1,
'time': 0,
'title': 280,
'tr': 1,
'track': 2,
'tt': 0,
'u': 0,
'ul': 0,
'var': 0,
'video': 0,
'wbr': 2
};
html4[ 'ELEMENTS' ] = html4.ELEMENTS;
html4.ELEMENT_DOM_INTERFACES = {
'a': 'HTMLAnchorElement',
'abbr': 'HTMLElement',
'acronym': 'HTMLElement',
'address': 'HTMLElement',
'applet': 'HTMLAppletElement',
'area': 'HTMLAreaElement',
'article': 'HTMLElement',
'aside': 'HTMLElement',
'audio': 'HTMLAudioElement',
'b': 'HTMLElement',
'base': 'HTMLBaseElement',
'basefont': 'HTMLBaseFontElement',
'bdi': 'HTMLElement',
'bdo': 'HTMLElement',
'big': 'HTMLElement',
'blockquote': 'HTMLQuoteElement',
'body': 'HTMLBodyElement',
'br': 'HTMLBRElement',
'button': 'HTMLButtonElement',
'canvas': 'HTMLCanvasElement',
'caption': 'HTMLTableCaptionElement',
'center': 'HTMLElement',
'cite': 'HTMLElement',
'code': 'HTMLElement',
'col': 'HTMLTableColElement',
'colgroup': 'HTMLTableColElement',
'command': 'HTMLCommandElement',
'data': 'HTMLElement',
'datalist': 'HTMLDataListElement',
'dd': 'HTMLElement',
'del': 'HTMLModElement',
'details': 'HTMLDetailsElement',
'dfn': 'HTMLElement',
'dialog': 'HTMLDialogElement',
'dir': 'HTMLDirectoryElement',
'div': 'HTMLDivElement',
'dl': 'HTMLDListElement',
'dt': 'HTMLElement',
'em': 'HTMLElement',
'fieldset': 'HTMLFieldSetElement',
'figcaption': 'HTMLElement',
'figure': 'HTMLElement',
'font': 'HTMLFontElement',
'footer': 'HTMLElement',
'form': 'HTMLFormElement',
'frame': 'HTMLFrameElement',
'frameset': 'HTMLFrameSetElement',
'h1': 'HTMLHeadingElement',
'h2': 'HTMLHeadingElement',
'h3': 'HTMLHeadingElement',
'h4': 'HTMLHeadingElement',
'h5': 'HTMLHeadingElement',
'h6': 'HTMLHeadingElement',
'head': 'HTMLHeadElement',
'header': 'HTMLElement',
'hgroup': 'HTMLElement',
'hr': 'HTMLHRElement',
'html': 'HTMLHtmlElement',
'i': 'HTMLElement',
'iframe': 'HTMLIFrameElement',
'img': 'HTMLImageElement',
'input': 'HTMLInputElement',
'ins': 'HTMLModElement',
'isindex': 'HTMLUnknownElement',
'kbd': 'HTMLElement',
'keygen': 'HTMLKeygenElement',
'label': 'HTMLLabelElement',
'legend': 'HTMLLegendElement',
'li': 'HTMLLIElement',
'link': 'HTMLLinkElement',
'map': 'HTMLMapElement',
'mark': 'HTMLElement',
'menu': 'HTMLMenuElement',
'meta': 'HTMLMetaElement',
'meter': 'HTMLMeterElement',
'nav': 'HTMLElement',
'nobr': 'HTMLElement',
'noembed': 'HTMLElement',
'noframes': 'HTMLElement',
'noscript': 'HTMLElement',
'object': 'HTMLObjectElement',
'ol': 'HTMLOListElement',
'optgroup': 'HTMLOptGroupElement',
'option': 'HTMLOptionElement',
'output': 'HTMLOutputElement',
'p': 'HTMLParagraphElement',
'param': 'HTMLParamElement',
'pre': 'HTMLPreElement',
'progress': 'HTMLProgressElement',
'q': 'HTMLQuoteElement',
's': 'HTMLElement',
'samp': 'HTMLElement',
'script': 'HTMLScriptElement',
'section': 'HTMLElement',
'select': 'HTMLSelectElement',
'small': 'HTMLElement',
'source': 'HTMLSourceElement',
'span': 'HTMLSpanElement',
'strike': 'HTMLElement',
'strong': 'HTMLElement',
'style': 'HTMLStyleElement',
'sub': 'HTMLElement',
'summary': 'HTMLElement',
'sup': 'HTMLElement',
'table': 'HTMLTableElement',
'tbody': 'HTMLTableSectionElement',
'td': 'HTMLTableDataCellElement',
'textarea': 'HTMLTextAreaElement',
'tfoot': 'HTMLTableSectionElement',
'th': 'HTMLTableHeaderCellElement',
'thead': 'HTMLTableSectionElement',
'time': 'HTMLTimeElement',
'title': 'HTMLTitleElement',
'tr': 'HTMLTableRowElement',
'track': 'HTMLTrackElement',
'tt': 'HTMLElement',
'u': 'HTMLElement',
'ul': 'HTMLUListElement',
'var': 'HTMLElement',
'video': 'HTMLVideoElement',
'wbr': 'HTMLElement'
};
html4[ 'ELEMENT_DOM_INTERFACES' ] = html4.ELEMENT_DOM_INTERFACES;
html4.ueffects = {
'NOT_LOADED': 0,
'SAME_DOCUMENT': 1,
'NEW_DOCUMENT': 2
};
html4[ 'ueffects' ] = html4.ueffects;
html4.URIEFFECTS = {
'a::href': 2,
'area::href': 2,
'blockquote::cite': 0,
'command::icon': 1,
'del::cite': 0,
'form::action': 2,
'img::src': 1,
'input::src': 1,
'ins::cite': 0,
'q::cite': 0,
'video::poster': 1
};
html4[ 'URIEFFECTS' ] = html4.URIEFFECTS;
html4.ltypes = {
'UNSANDBOXED': 2,
'SANDBOXED': 1,
'DATA': 0
};
html4[ 'ltypes' ] = html4.ltypes;
html4.LOADERTYPES = {
'a::href': 2,
'area::href': 2,
'blockquote::cite': 2,
'command::icon': 1,
'del::cite': 2,
'form::action': 2,
'img::src': 1,
'input::src': 1,
'ins::cite': 2,
'q::cite': 2,
'video::poster': 1
};
html4[ 'LOADERTYPES' ] = html4.LOADERTYPES;
// Copyright (C) 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* An HTML sanitizer that can satisfy a variety of security policies.
*
* <p>
* The HTML sanitizer is built around a SAX parser and HTML element and
* attributes schemas.
*
* If the cssparser is loaded, inline styles are sanitized using the
* css property and value schemas. Else they are remove during
* sanitization.
*
* If it exists, uses parseCssDeclarations, sanitizeCssProperty, cssSchema
*
* @author mikesamuel@gmail.com
* @author jasvir@gmail.com
* \@requires html4, URI
* \@overrides window
* \@provides html, html_sanitize
*/
// The Turkish i seems to be a non-issue, but abort in case it is.
if ('I'.toLowerCase() !== 'i') { throw 'I/i problem'; }
/**
* \@namespace
*/
var html = (function(html4) {
// For closure compiler
var parseCssDeclarations, sanitizeCssProperty, cssSchema;
if ('undefined' !== typeof window) {
parseCssDeclarations = window['parseCssDeclarations'];
sanitizeCssProperty = window['sanitizeCssProperty'];
cssSchema = window['cssSchema'];
}
// The keys of this object must be 'quoted' or JSCompiler will mangle them!
// This is a partial list -- lookupEntity() uses the host browser's parser
// (when available) to implement full entity lookup.
// Note that entities are in general case-sensitive; the uppercase ones are
// explicitly defined by HTML5 (presumably as compatibility).
var ENTITIES = {
'lt': '<',
'LT': '<',
'gt': '>',
'GT': '>',
'amp': '&',
'AMP': '&',
'quot': '"',
'apos': '\'',
'nbsp': '\240'
};
// Patterns for types of entity/character reference names.
var decimalEscapeRe = /^#(\d+)$/;
var hexEscapeRe = /^#x([0-9A-Fa-f]+)$/;
// contains every entity per http://www.w3.org/TR/2011/WD-html5-20110113/named-character-references.html
var safeEntityNameRe = /^[A-Za-z][A-za-z0-9]+$/;
// Used as a hook to invoke the browser's entity parsing. <textarea> is used
// because its content is parsed for entities but not tags.
// TODO(kpreid): This retrieval is a kludge and leads to silent loss of
// functionality if the document isn't available.
var entityLookupElement =
('undefined' !== typeof window && window['document'])
? window['document'].createElement('textarea') : null;
/**
* Decodes an HTML entity.
*
* {\@updoc
* $ lookupEntity('lt')
* # '<'
* $ lookupEntity('GT')
* # '>'
* $ lookupEntity('amp')
* # '&'
* $ lookupEntity('nbsp')
* # '\xA0'
* $ lookupEntity('apos')
* # "'"
* $ lookupEntity('quot')
* # '"'
* $ lookupEntity('#xa')
* # '\n'
* $ lookupEntity('#10')
* # '\n'
* $ lookupEntity('#x0a')
* # '\n'
* $ lookupEntity('#010')
* # '\n'
* $ lookupEntity('#x00A')
* # '\n'
* $ lookupEntity('Pi') // Known failure
* # '\u03A0'
* $ lookupEntity('pi') // Known failure
* # '\u03C0'
* }
*
* @param {string} name the content between the '&' and the ';'.
* @return {string} a single unicode code-point as a string.
*/
function lookupEntity(name) {
// TODO: entity lookup as specified by HTML5 actually depends on the
// presence of the ";".
if (ENTITIES.hasOwnProperty(name)) { return ENTITIES[name]; }
var m = name.match(decimalEscapeRe);
if (m) {
return String.fromCharCode(parseInt(m[1], 10));
} else if (!!(m = name.match(hexEscapeRe))) {
return String.fromCharCode(parseInt(m[1], 16));
} else if (entityLookupElement && safeEntityNameRe.test(name)) {
entityLookupElement.innerHTML = '&' + name + ';';
var text = entityLookupElement.textContent;
ENTITIES[name] = text;
return text;
} else {
return '&' + name + ';';
}
}
function decodeOneEntity(_, name) {
return lookupEntity(name);
}
var nulRe = /\0/g;
function stripNULs(s) {
return s.replace(nulRe, '');
}
var ENTITY_RE_1 = /&(#[0-9]+|#[xX][0-9A-Fa-f]+|\w+);/g;
var ENTITY_RE_2 = /^(#[0-9]+|#[xX][0-9A-Fa-f]+|\w+);/;
/**
* The plain text of a chunk of HTML CDATA which possibly containing.
*
* {\@updoc
* $ unescapeEntities('')
* # ''
* $ unescapeEntities('hello World!')
* # 'hello World!'
* $ unescapeEntities('1 &lt; 2 &amp;&AMP; 4 &gt; 3&#10;')
* # '1 < 2 && 4 > 3\n'
* $ unescapeEntities('&lt;&lt <- unfinished entity&gt;')
* # '<&lt <- unfinished entity>'
* $ unescapeEntities('/foo?bar=baz&copy=true') // & often unescaped in URLS
* # '/foo?bar=baz&copy=true'
* $ unescapeEntities('pi=&pi;&#x3c0;, Pi=&Pi;\u03A0') // FIXME: known failure
* # 'pi=\u03C0\u03c0, Pi=\u03A0\u03A0'
* }
*
* @param {string} s a chunk of HTML CDATA. It must not start or end inside
* an HTML entity.
*/
function unescapeEntities(s) {
return s.replace(ENTITY_RE_1, decodeOneEntity);
}
var ampRe = /&/g;
var looseAmpRe = /&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi;
var ltRe = /[<]/g;
var gtRe = />/g;
var quotRe = /\"/g;
/**
* Escapes HTML special characters in attribute values.
*
* {\@updoc
* $ escapeAttrib('')
* # ''
* $ escapeAttrib('"<<&==&>>"') // Do not just escape the first occurrence.
* # '&#34;&lt;&lt;&amp;&#61;&#61;&amp;&gt;&gt;&#34;'
* $ escapeAttrib('Hello <World>!')
* # 'Hello &lt;World&gt;!'
* }
*/
function escapeAttrib(s) {
return ('' + s).replace(ampRe, '&amp;').replace(ltRe, '&lt;')
.replace(gtRe, '&gt;').replace(quotRe, '&#34;');
}
/**
* Escape entities in RCDATA that can be escaped without changing the meaning.
* {\@updoc
* $ normalizeRCData('1 < 2 &&amp; 3 > 4 &amp;& 5 &lt; 7&8')
* # '1 &lt; 2 &amp;&amp; 3 &gt; 4 &amp;&amp; 5 &lt; 7&amp;8'
* }
*/
function normalizeRCData(rcdata) {
return rcdata
.replace(looseAmpRe, '&amp;$1')
.replace(ltRe, '&lt;')
.replace(gtRe, '&gt;');
}
// TODO(felix8a): validate sanitizer regexs against the HTML5 grammar at
// http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html
// We initially split input so that potentially meaningful characters
// like '<' and '>' are separate tokens, using a fast dumb process that
// ignores quoting. Then we walk that token stream, and when we see a
// '<' that's the start of a tag, we use ATTR_RE to extract tag
// attributes from the next token. That token will never have a '>'
// character. However, it might have an unbalanced quote character, and
// when we see that, we combine additional tokens to balance the quote.
var ATTR_RE = new RegExp(
'^\\s*' +
'([-.:\\w]+)' + // 1 = Attribute name
'(?:' + (
'\\s*(=)\\s*' + // 2 = Is there a value?
'(' + ( // 3 = Attribute value
// TODO(felix8a): maybe use backref to match quotes
'(\")[^\"]*(\"|$)' + // 4, 5 = Double-quoted string
'|' +
'(\')[^\']*(\'|$)' + // 6, 7 = Single-quoted string
'|' +
// Positive lookahead to prevent interpretation of
// <foo a= b=c> as <foo a='b=c'>
// TODO(felix8a): might be able to drop this case
'(?=[a-z][-\\w]*\\s*=)' +
'|' +
// Unquoted value that isn't an attribute name
// (since we didn't match the positive lookahead above)
'[^\"\'\\s]*' ) +
')' ) +
')?',
'i');
// false on IE<=8, true on most other browsers
var splitWillCapture = ('a,b'.split(/(,)/).length === 3);
// bitmask for tags with special parsing, like <script> and <textarea>
var EFLAGS_TEXT = html4.eflags['CDATA'] | html4.eflags['RCDATA'];
/**
* Given a SAX-like event handler, produce a function that feeds those
* events and a parameter to the event handler.
*
* The event handler has the form:{@code
* {
* // Name is an upper-case HTML tag name. Attribs is an array of
* // alternating upper-case attribute names, and attribute values. The
* // attribs array is reused by the parser. Param is the value passed to
* // the saxParser.
* startTag: function (name, attribs, param) { ... },
* endTag: function (name, param) { ... },
* pcdata: function (text, param) { ... },
* rcdata: function (text, param) { ... },
* cdata: function (text, param) { ... },
* startDoc: function (param) { ... },
* endDoc: function (param) { ... }
* }}
*
* @param {Object} handler a record containing event handlers.
* @return {function(string, Object)} A function that takes a chunk of HTML
* and a parameter. The parameter is passed on to the handler methods.
*/
function makeSaxParser(handler) {
// Accept quoted or unquoted keys (Closure compat)
var hcopy = {
cdata: handler.cdata || handler['cdata'],
comment: handler.comment || handler['comment'],
endDoc: handler.endDoc || handler['endDoc'],
endTag: handler.endTag || handler['endTag'],
pcdata: handler.pcdata || handler['pcdata'],
rcdata: handler.rcdata || handler['rcdata'],
startDoc: handler.startDoc || handler['startDoc'],
startTag: handler.startTag || handler['startTag']
};
return function(htmlText, param) {
return parse(htmlText, hcopy, param);
};
}
// Parsing strategy is to split input into parts that might be lexically
// meaningful (every ">" becomes a separate part), and then recombine
// parts if we discover they're in a different context.
// TODO(felix8a): Significant performance regressions from -legacy,
// tested on
// Chrome 18.0
// Firefox 11.0
// IE 6, 7, 8, 9
// Opera 11.61
// Safari 5.1.3
// Many of these are unusual patterns that are linearly slower and still
// pretty fast (eg 1ms to 5ms), so not necessarily worth fixing.
// TODO(felix8a): "<script> && && && ... <\/script>" is slower on all
// browsers. The hotspot is htmlSplit.
// TODO(felix8a): "<p title='>>>>...'><\/p>" is slower on all browsers.
// This is partly htmlSplit, but the hotspot is parseTagAndAttrs.
// TODO(felix8a): "<a><\/a><a><\/a>..." is slower on IE9.
// "<a>1<\/a><a>1<\/a>..." is faster, "<a><\/a>2<a><\/a>2..." is faster.
// TODO(felix8a): "<p<p<p..." is slower on IE[6-8]
var continuationMarker = {};
function parse(htmlText, handler, param) {
var m, p, tagName;
var parts = htmlSplit(htmlText);
var state = {
noMoreGT: false,
noMoreEndComments: false
};
parseCPS(handler, parts, 0, state, param);
}
function continuationMaker(h, parts, initial, state, param) {
return function () {
parseCPS(h, parts, initial, state, param);
};
}
function parseCPS(h, parts, initial, state, param) {
try {
if (h.startDoc && initial == 0) { h.startDoc(param); }
var m, p, tagName;
for (var pos = initial, end = parts.length; pos < end;) {
var current = parts[pos++];
var next = parts[pos];
switch (current) {
case '&':
if (ENTITY_RE_2.test(next)) {
if (h.pcdata) {
h.pcdata('&' + next, param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
pos++;
} else {
if (h.pcdata) { h.pcdata("&amp;", param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
}
break;
case '<\/':
if (m = /^([-\w:]+)[^\'\"]*/.exec(next)) {
if (m[0].length === next.length && parts[pos + 1] === '>') {
// fast case, no attribute parsing needed
pos += 2;
tagName = m[1].toLowerCase();
if (h.endTag) {
h.endTag(tagName, param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
} else {
// slow case, need to parse attributes
// TODO(felix8a): do we really care about misparsing this?
pos = parseEndTag(
parts, pos, h, param, continuationMarker, state);
}
} else {
if (h.pcdata) {
h.pcdata('&lt;/', param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
}
break;
case '<':
if (m = /^([-\w:]+)\s*\/?/.exec(next)) {
if (m[0].length === next.length && parts[pos + 1] === '>') {
// fast case, no attribute parsing needed
pos += 2;
tagName = m[1].toLowerCase();
if (h.startTag) {
h.startTag(tagName, [], param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
// tags like <script> and <textarea> have special parsing
var eflags = html4.ELEMENTS[tagName];
if (eflags & EFLAGS_TEXT) {
var tag = { name: tagName, next: pos, eflags: eflags };
pos = parseText(
parts, tag, h, param, continuationMarker, state);
}
} else {
// slow case, need to parse attributes
pos = parseStartTag(
parts, pos, h, param, continuationMarker, state);
}
} else {
if (h.pcdata) {
h.pcdata('&lt;', param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
}
break;
case '<\!--':
// The pathological case is n copies of '<\!--' without '-->', and
// repeated failure to find '-->' is quadratic. We avoid that by
// remembering when search for '-->' fails.
if (!state.noMoreEndComments) {
// A comment <\!--x--> is split into three tokens:
// '<\!--', 'x--', '>'
// We want to find the next '>' token that has a preceding '--'.
// pos is at the 'x--'.
for (p = pos + 1; p < end; p++) {
if (parts[p] === '>' && /--$/.test(parts[p - 1])) { break; }
}
if (p < end) {
if (h.comment) {
var comment = parts.slice(pos, p).join('');
h.comment(
comment.substr(0, comment.length - 2), param,
continuationMarker,
continuationMaker(h, parts, p + 1, state, param));
}
pos = p + 1;
} else {
state.noMoreEndComments = true;
}
}
if (state.noMoreEndComments) {
if (h.pcdata) {
h.pcdata('&lt;!--', param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
}
break;
case '<\!':
if (!/^\w/.test(next)) {
if (h.pcdata) {
h.pcdata('&lt;!', param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
} else {
// similar to noMoreEndComment logic
if (!state.noMoreGT) {
for (p = pos + 1; p < end; p++) {
if (parts[p] === '>') { break; }
}
if (p < end) {
pos = p + 1;
} else {
state.noMoreGT = true;
}
}
if (state.noMoreGT) {
if (h.pcdata) {
h.pcdata('&lt;!', param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
}
}
break;
case '<?':
// similar to noMoreEndComment logic
if (!state.noMoreGT) {
for (p = pos + 1; p < end; p++) {
if (parts[p] === '>') { break; }
}
if (p < end) {
pos = p + 1;
} else {
state.noMoreGT = true;
}
}
if (state.noMoreGT) {
if (h.pcdata) {
h.pcdata('&lt;?', param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
}
break;
case '>':
if (h.pcdata) {
h.pcdata("&gt;", param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
break;
case '':
break;
default:
if (h.pcdata) {
h.pcdata(current, param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
break;
}
}
if (h.endDoc) { h.endDoc(param); }
} catch (e) {
if (e !== continuationMarker) { throw e; }
}
}
// Split str into parts for the html parser.
function htmlSplit(str) {
// can't hoist this out of the function because of the re.exec loop.
var re = /(<\/|<\!--|<[!?]|[&<>])/g;
str += '';
if (splitWillCapture) {
return str.split(re);
} else {
var parts = [];
var lastPos = 0;
var m;
while ((m = re.exec(str)) !== null) {
parts.push(str.substring(lastPos, m.index));
parts.push(m[0]);
lastPos = m.index + m[0].length;
}
parts.push(str.substring(lastPos));
return parts;
}
}
function parseEndTag(parts, pos, h, param, continuationMarker, state) {
var tag = parseTagAndAttrs(parts, pos);
// drop unclosed tags
if (!tag) { return parts.length; }
if (h.endTag) {
h.endTag(tag.name, param, continuationMarker,
continuationMaker(h, parts, pos, state, param));
}
return tag.next;
}
function parseStartTag(parts, pos, h, param, continuationMarker, state) {
var tag = parseTagAndAttrs(parts, pos);
// drop unclosed tags
if (!tag) { return parts.length; }
if (h.startTag) {
h.startTag(tag.name, tag.attrs, param, continuationMarker,
continuationMaker(h, parts, tag.next, state, param));
}
// tags like <script> and <textarea> have special parsing
if (tag.eflags & EFLAGS_TEXT) {
return parseText(parts, tag, h, param, continuationMarker, state);
} else {
return tag.next;
}
}
var endTagRe = {};
// Tags like <script> and <textarea> are flagged as CDATA or RCDATA,
// which means everything is text until we see the correct closing tag.
function parseText(parts, tag, h, param, continuationMarker, state) {
var end = parts.length;
if (!endTagRe.hasOwnProperty(tag.name)) {
endTagRe[tag.name] = new RegExp('^' + tag.name + '(?:[\\s\\/]|$)', 'i');
}
var re = endTagRe[tag.name];
var first = tag.next;
var p = tag.next + 1;
for (; p < end; p++) {
if (parts[p - 1] === '<\/' && re.test(parts[p])) { break; }
}
if (p < end) { p -= 1; }
var buf = parts.slice(first, p).join('');
if (tag.eflags & html4.eflags['CDATA']) {
if (h.cdata) {
h.cdata(buf, param, continuationMarker,
continuationMaker(h, parts, p, state, param));
}
} else if (tag.eflags & html4.eflags['RCDATA']) {
if (h.rcdata) {
h.rcdata(normalizeRCData(buf), param, continuationMarker,
continuationMaker(h, parts, p, state, param));
}
} else {
throw new Error('bug');
}
return p;
}
// at this point, parts[pos-1] is either "<" or "<\/".
function parseTagAndAttrs(parts, pos) {
var m = /^([-\w:]+)/.exec(parts[pos]);
var tag = {};
tag.name = m[1].toLowerCase();
tag.eflags = html4.ELEMENTS[tag.name];
var buf = parts[pos].substr(m[0].length);
// Find the next '>'. We optimistically assume this '>' is not in a
// quoted context, and further down we fix things up if it turns out to
// be quoted.
var p = pos + 1;
var end = parts.length;
for (; p < end; p++) {
if (parts[p] === '>') { break; }
buf += parts[p];
}
if (end <= p) { return void 0; }
var attrs = [];
while (buf !== '') {
m = ATTR_RE.exec(buf);
if (!m) {
// No attribute found: skip garbage
buf = buf.replace(/^[\s\S][^a-z\s]*/, '');
} else if ((m[4] && !m[5]) || (m[6] && !m[7])) {
// Unterminated quote: slurp to the next unquoted '>'
var quote = m[4] || m[6];
var sawQuote = false;
var abuf = [buf, parts[p++]];
for (; p < end; p++) {
if (sawQuote) {
if (parts[p] === '>') { break; }
} else if (0 <= parts[p].indexOf(quote)) {
sawQuote = true;
}
abuf.push(parts[p]);
}
// Slurp failed: lose the garbage
if (end <= p) { break; }
// Otherwise retry attribute parsing
buf = abuf.join('');
continue;
} else {
// We have an attribute
var aName = m[1].toLowerCase();
var aValue = m[2] ? decodeValue(m[3]) : '';
attrs.push(aName, aValue);
buf = buf.substr(m[0].length);
}
}
tag.attrs = attrs;
tag.next = p + 1;
return tag;
}
function decodeValue(v) {
var q = v.charCodeAt(0);
if (q === 0x22 || q === 0x27) { // " or '
v = v.substr(1, v.length - 2);
}
return unescapeEntities(stripNULs(v));
}
/**
* Returns a function that strips unsafe tags and attributes from html.
* @param {function(string, Array.<string>): ?Array.<string>} tagPolicy
* A function that takes (tagName, attribs[]), where tagName is a key in
* html4.ELEMENTS and attribs is an array of alternating attribute names
* and values. It should return a record (as follows), or null to delete
* the element. It's okay for tagPolicy to modify the attribs array,
* but the same array is reused, so it should not be held between calls.
* Record keys:
* attribs: (required) Sanitized attributes array.
* tagName: Replacement tag name.
* @return {function(string, Array)} A function that sanitizes a string of
* HTML and appends result strings to the second argument, an array.
*/
function makeHtmlSanitizer(tagPolicy) {
var stack;
var ignoring;
var emit = function (text, out) {
if (!ignoring) { out.push(text); }
};
return makeSaxParser({
'startDoc': function(_) {
stack = [];
ignoring = false;
},
'startTag': function(tagNameOrig, attribs, out) {
if (ignoring) { return; }
if (!html4.ELEMENTS.hasOwnProperty(tagNameOrig)) { return; }
var eflagsOrig = html4.ELEMENTS[tagNameOrig];
if (eflagsOrig & html4.eflags['FOLDABLE']) {
return;
}
var decision = tagPolicy(tagNameOrig, attribs);
if (!decision) {
ignoring = !(eflagsOrig & html4.eflags['EMPTY']);
return;
} else if (typeof decision !== 'object') {
throw new Error('tagPolicy did not return object (old API?)');
}
if ('attribs' in decision) {
attribs = decision['attribs'];
} else {
throw new Error('tagPolicy gave no attribs');
}
var eflagsRep;
var tagNameRep;
if ('tagName' in decision) {
tagNameRep = decision['tagName'];
eflagsRep = html4.ELEMENTS[tagNameRep];
} else {
tagNameRep = tagNameOrig;
eflagsRep = eflagsOrig;
}
// TODO(mikesamuel): relying on tagPolicy not to insert unsafe
// attribute names.
// If this is an optional-end-tag element and either this element or its
// previous like sibling was rewritten, then insert a close tag to
// preserve structure.
if (eflagsOrig & html4.eflags['OPTIONAL_ENDTAG']) {
var onStack = stack[stack.length - 1];
if (onStack && onStack.orig === tagNameOrig &&
(onStack.rep !== tagNameRep || tagNameOrig !== tagNameRep)) {
out.push('<\/', onStack.rep, '>');
}
}
if (!(eflagsOrig & html4.eflags['EMPTY'])) {
stack.push({orig: tagNameOrig, rep: tagNameRep});
}
out.push('<', tagNameRep);
for (var i = 0, n = attribs.length; i < n; i += 2) {
var attribName = attribs[i],
value = attribs[i + 1];
if (value !== null && value !== void 0) {
out.push(' ', attribName, '="', escapeAttrib(value), '"');
}
}
out.push('>');
if ((eflagsOrig & html4.eflags['EMPTY'])
&& !(eflagsRep & html4.eflags['EMPTY'])) {
// replacement is non-empty, synthesize end tag
out.push('<\/', tagNameRep, '>');
}
},
'endTag': function(tagName, out) {
if (ignoring) {
ignoring = false;
return;
}
if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; }
var eflags = html4.ELEMENTS[tagName];
if (!(eflags & (html4.eflags['EMPTY'] | html4.eflags['FOLDABLE']))) {
var index;
if (eflags & html4.eflags['OPTIONAL_ENDTAG']) {
for (index = stack.length; --index >= 0;) {
var stackElOrigTag = stack[index].orig;
if (stackElOrigTag === tagName) { break; }
if (!(html4.ELEMENTS[stackElOrigTag] &
html4.eflags['OPTIONAL_ENDTAG'])) {
// Don't pop non optional end tags looking for a match.
return;
}
}
} else {
for (index = stack.length; --index >= 0;) {
if (stack[index].orig === tagName) { break; }
}
}
if (index < 0) { return; } // Not opened.
for (var i = stack.length; --i > index;) {
var stackElRepTag = stack[i].rep;
if (!(html4.ELEMENTS[stackElRepTag] &
html4.eflags['OPTIONAL_ENDTAG'])) {
out.push('<\/', stackElRepTag, '>');
}
}
if (index < stack.length) {
tagName = stack[index].rep;
}
stack.length = index;
out.push('<\/', tagName, '>');
}
},
'pcdata': emit,
'rcdata': emit,
'cdata': emit,
'endDoc': function(out) {
for (; stack.length; stack.length--) {
out.push('<\/', stack[stack.length - 1].rep, '>');
}
}
});
}
var ALLOWED_URI_SCHEMES = /^(?:https?|mailto|data)$/i;
function safeUri(uri, effect, ltype, hints, naiveUriRewriter) {
if (!naiveUriRewriter) { return null; }
try {
var parsed = URI.parse('' + uri);
if (parsed) {
if (!parsed.hasScheme() ||
ALLOWED_URI_SCHEMES.test(parsed.getScheme())) {
var safe = naiveUriRewriter(parsed, effect, ltype, hints);
return safe ? safe.toString() : null;
}
}
} catch (e) {
return null;
}
return null;
}
function log(logger, tagName, attribName, oldValue, newValue) {
if (!attribName) {
logger(tagName + " removed", {
change: "removed",
tagName: tagName
});
}
if (oldValue !== newValue) {
var changed = "changed";
if (oldValue && !newValue) {
changed = "removed";
} else if (!oldValue && newValue) {
changed = "added";
}
logger(tagName + "." + attribName + " " + changed, {
change: changed,
tagName: tagName,
attribName: attribName,
oldValue: oldValue,
newValue: newValue
});
}
}
function lookupAttribute(map, tagName, attribName) {
var attribKey;
attribKey = tagName + '::' + attribName;
if (map.hasOwnProperty(attribKey)) {
return map[attribKey];
}
attribKey = '*::' + attribName;
if (map.hasOwnProperty(attribKey)) {
return map[attribKey];
}
return void 0;
}
function getAttributeType(tagName, attribName) {
return lookupAttribute(html4.ATTRIBS, tagName, attribName);
}
function getLoaderType(tagName, attribName) {
return lookupAttribute(html4.LOADERTYPES, tagName, attribName);
}
function getUriEffect(tagName, attribName) {
return lookupAttribute(html4.URIEFFECTS, tagName, attribName);
}
/**
* Sanitizes attributes on an HTML tag.
* @param {string} tagName An HTML tag name in lowercase.
* @param {Array.<?string>} attribs An array of alternating names and values.
* @param {?function(?string): ?string} opt_naiveUriRewriter A transform to
* apply to URI attributes; it can return a new string value, or null to
* delete the attribute. If unspecified, URI attributes are deleted.
* @param {function(?string): ?string} opt_nmTokenPolicy A transform to apply
* to attributes containing HTML names, element IDs, and space-separated
* lists of classes; it can return a new string value, or null to delete
* the attribute. If unspecified, these attributes are kept unchanged.
* @return {Array.<?string>} The sanitized attributes as a list of alternating
* names and values, where a null value means to omit the attribute.
*/
function sanitizeAttribs(tagName, attribs,
opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
// TODO(felix8a): it's obnoxious that domado duplicates much of this
// TODO(felix8a): maybe consistently enforce constraints like target=
for (var i = 0; i < attribs.length; i += 2) {
var attribName = attribs[i];
var value = attribs[i + 1];
var oldValue = value;
var atype = null, attribKey;
if ((attribKey = tagName + '::' + attribName,
html4.ATTRIBS.hasOwnProperty(attribKey)) ||
(attribKey = '*::' + attribName,
html4.ATTRIBS.hasOwnProperty(attribKey))) {
atype = html4.ATTRIBS[attribKey];
}
if (atype !== null) {
switch (atype) {
case html4.atype['NONE']: break;
case html4.atype['SCRIPT']:
value = null;
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
case html4.atype['STYLE']:
if ('undefined' === typeof parseCssDeclarations) {
value = null;
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
}
var sanitizedDeclarations = [];
parseCssDeclarations(
value,
{
declaration: function (property, tokens) {
var normProp = property.toLowerCase();
var schema = cssSchema[normProp];
if (!schema) {
return;
}
sanitizeCssProperty(
normProp, schema, tokens,
opt_naiveUriRewriter
? function (url) {
return safeUri(
url, html4.ueffects.SAME_DOCUMENT,
html4.ltypes.SANDBOXED,
{
"TYPE": "CSS",
"CSS_PROP": normProp
}, opt_naiveUriRewriter);
}
: null);
sanitizedDeclarations.push(property + ': ' + tokens.join(' '));
}
});
value = sanitizedDeclarations.length > 0 ?
sanitizedDeclarations.join(' ; ') : null;
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
case html4.atype['ID']:
case html4.atype['IDREF']:
case html4.atype['IDREFS']:
case html4.atype['GLOBAL_NAME']:
case html4.atype['LOCAL_NAME']:
case html4.atype['CLASSES']:
value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value;
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
case html4.atype['URI']:
value = safeUri(value,
getUriEffect(tagName, attribName),
getLoaderType(tagName, attribName),
{
"TYPE": "MARKUP",
"XML_ATTR": attribName,
"XML_TAG": tagName
}, opt_naiveUriRewriter);
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
case html4.atype['URI_FRAGMENT']:
if (value && '#' === value.charAt(0)) {
value = value.substring(1); // remove the leading '#'
value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value;
if (value !== null && value !== void 0) {
value = '#' + value; // restore the leading '#'
}
} else {
value = null;
}
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
default:
value = null;
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
break;
}
} else {
value = null;
if (opt_logger) {
log(opt_logger, tagName, attribName, oldValue, value);
}
}
attribs[i + 1] = value;
}
return attribs;
}
/**
* Creates a tag policy that omits all tags marked UNSAFE in html4-defs.js
* and applies the default attribute sanitizer with the supplied policy for
* URI attributes and NMTOKEN attributes.
* @param {?function(?string): ?string} opt_naiveUriRewriter A transform to
* apply to URI attributes. If not given, URI attributes are deleted.
* @param {function(?string): ?string} opt_nmTokenPolicy A transform to apply
* to attributes containing HTML names, element IDs, and space-separated
* lists of classes. If not given, such attributes are left unchanged.
* @return {function(string, Array.<?string>)} A tagPolicy suitable for
* passing to html.sanitize.
*/
function makeTagPolicy(
opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
return function(tagName, attribs) {
if (!(html4.ELEMENTS[tagName] & html4.eflags['UNSAFE'])) {
return {
'attribs': sanitizeAttribs(tagName, attribs,
opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger)
};
} else {
if (opt_logger) {
log(opt_logger, tagName, undefined, undefined, undefined);
}
}
};
}
/**
* Sanitizes HTML tags and attributes according to a given policy.
* @param {string} inputHtml The HTML to sanitize.
* @param {function(string, Array.<?string>)} tagPolicy A function that
* decides which tags to accept and sanitizes their attributes (see
* makeHtmlSanitizer above for details).
* @return {string} The sanitized HTML.
*/
function sanitizeWithPolicy(inputHtml, tagPolicy) {
var outputArray = [];
makeHtmlSanitizer(tagPolicy)(inputHtml, outputArray);
return outputArray.join('');
}
/**
* Strips unsafe tags and attributes from HTML.
* @param {string} inputHtml The HTML to sanitize.
* @param {?function(?string): ?string} opt_naiveUriRewriter A transform to
* apply to URI attributes. If not given, URI attributes are deleted.
* @param {function(?string): ?string} opt_nmTokenPolicy A transform to apply
* to attributes containing HTML names, element IDs, and space-separated
* lists of classes. If not given, such attributes are left unchanged.
*/
function sanitize(inputHtml,
opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
var tagPolicy = makeTagPolicy(
opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
return sanitizeWithPolicy(inputHtml, tagPolicy);
}
// Export both quoted and unquoted names for Closure linkage.
var html = {};
html.escapeAttrib = html['escapeAttrib'] = escapeAttrib;
html.makeHtmlSanitizer = html['makeHtmlSanitizer'] = makeHtmlSanitizer;
html.makeSaxParser = html['makeSaxParser'] = makeSaxParser;
html.makeTagPolicy = html['makeTagPolicy'] = makeTagPolicy;
html.normalizeRCData = html['normalizeRCData'] = normalizeRCData;
html.sanitize = html['sanitize'] = sanitize;
html.sanitizeAttribs = html['sanitizeAttribs'] = sanitizeAttribs;
html.sanitizeWithPolicy = html['sanitizeWithPolicy'] = sanitizeWithPolicy;
html.unescapeEntities = html['unescapeEntities'] = unescapeEntities;
return html;
})(html4);
var html_sanitize = html['sanitize'];
// Loosen restrictions of Caja's
// html-sanitizer to allow for styling
html4.ATTRIBS['*::style'] = 0;
html4.ELEMENTS['style'] = 0;
html4.ATTRIBS['a::target'] = 0;
html4.ELEMENTS['video'] = 0;
html4.ATTRIBS['video::src'] = 0;
html4.ATTRIBS['video::poster'] = 0;
html4.ATTRIBS['video::controls'] = 0;
html4.ELEMENTS['audio'] = 0;
html4.ATTRIBS['audio::src'] = 0;
html4.ATTRIBS['video::autoplay'] = 0;
html4.ATTRIBS['video::controls'] = 0;
if (typeof module !== 'undefined') {
module.exports = html_sanitize;
}
},{}],31:[function(require,module,exports){
require('./leaflet');
require('./mapbox');
},{"./leaflet":32,"./mapbox":33}],32:[function(require,module,exports){
window.L = require('leaflet/dist/leaflet-src');
},{"leaflet/dist/leaflet-src":36}],33:[function(require,module,exports){
// Hardcode image path, because Leaflet's autodetection
// fails, because mapbox.js is not named leaflet.js
window.L.Icon.Default.imagePath = '//api.tiles.mapbox.com/mapbox.js/' + 'v' +
require('./package.json').version + '/images';
L.mapbox = module.exports = {
VERSION: require('./package.json').version,
geocoder: require('./src/geocoder'),
marker: require('./src/marker'),
tileLayer: require('./src/tile_layer'),
shareControl: require('./src/share_control'),
legendControl: require('./src/legend_control'),
geocoderControl: require('./src/geocoder_control'),
gridControl: require('./src/grid_control'),
gridLayer: require('./src/grid_layer'),
markerLayer: require('./src/marker_layer'),
map: require('./src/map'),
config: require('./src/config'),
sanitize: require('./src/sanitize'),
template: require('mustache').to_html
};
},{"./package.json":38,"./src/config":39,"./src/geocoder":40,"./src/geocoder_control":41,"./src/grid_control":43,"./src/grid_layer":44,"./src/legend_control":45,"./src/map":47,"./src/marker":48,"./src/marker_layer":49,"./src/sanitize":51,"./src/share_control":52,"./src/tile_layer":53,"mustache":37}],34:[function(require,module,exports){
function xhr(url, callback, cors) {
if (typeof window.XMLHttpRequest === 'undefined') {
return callback(Error('Browser not supported'));
}
if (typeof cors === 'undefined') {
var m = url.match(/^\s*https?:\/\/[^\/]*/);
cors = m && (m[0] !== location.protocol + '//' + location.domain +
(location.port ? ':' + location.port : ''));
}
var x;
function isSuccessful(status) {
return status >= 200 && status < 300 || status === 304;
}
if (cors && (
// IE7-9 Quirks & Compatibility
typeof window.XDomainRequest === 'object' ||
// IE9 Standards mode
typeof window.XDomainRequest === 'function'
)) {
// IE8-10
x = new window.XDomainRequest();
} else {
x = new window.XMLHttpRequest();
}
function loaded() {
if (
// XDomainRequest
x.status === undefined ||
// modern browsers
isSuccessful(x.status)) callback.call(x, null, x);
else callback.call(x, x, null);
}
// Both `onreadystatechange` and `onload` can fire. `onreadystatechange`
// has [been supported for longer](http://stackoverflow.com/a/9181508/229001).
if ('onload' in x) {
x.onload = loaded;
} else {
x.onreadystatechange = function readystate() {
if (x.readyState === 4) {
loaded();
}
};
}
// Call the callback with the XMLHttpRequest object as an error and prevent
// it from ever being called again by reassigning it to `noop`
x.onerror = function error(evt) {
callback.call(this, evt, null);
callback = function() { };
};
// IE9 must have onprogress be set to a unique function.
x.onprogress = function() { };
x.ontimeout = function(evt) {
callback.call(this, evt, null);
callback = function() { };
};
x.onabort = function(evt) {
callback.call(this, evt, null);
callback = function() { };
};
// GET is the only supported HTTP Verb by XDomainRequest and is the
// only one supported here.
x.open('GET', url, true);
// Send the request. Sending data is not supported.
x.send(null);
return xhr;
}
if (typeof module !== 'undefined') module.exports = xhr;
},{}],35:[function(require,module,exports){
/*! JSON v3.2.6 | http://bestiejs.github.io/json3 | Copyright 2012-2013, Kit Cambridge | http://kit.mit-license.org */
;(function (window) {
// Convenience aliases.
var getClass = {}.toString, isProperty, forEach, undef;
// Detect the `define` function exposed by asynchronous module loaders. The
// strict `define` check is necessary for compatibility with `r.js`.
var isLoader = typeof define === "function" && define.amd;
// Detect native implementations.
var nativeJSON = typeof JSON == "object" && JSON;
// Set up the JSON 3 namespace, preferring the CommonJS `exports` object if
// available.
var JSON3 = typeof exports == "object" && exports && !exports.nodeType && exports;
if (JSON3 && nativeJSON) {
// Explicitly delegate to the native `stringify` and `parse`
// implementations in CommonJS environments.
JSON3.stringify = nativeJSON.stringify;
JSON3.parse = nativeJSON.parse;
} else {
// Export for web browsers, JavaScript engines, and asynchronous module
// loaders, using the global `JSON` object if available.
JSON3 = window.JSON = nativeJSON || {};
}
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
var isExtended = new Date(-3509827334573292);
try {
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
// results for certain dates in Opera >= 10.53.
isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 &&
// Safari < 2.0.2 stores the internal millisecond time value correctly,
// but clips the values returned by the date methods to the range of
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
} catch (exception) {}
// Internal: Determines whether the native `JSON.stringify` and `parse`
// implementations are spec-compliant. Based on work by Ken Snyder.
function has(name) {
if (has[name] !== undef) {
// Return cached feature test result.
return has[name];
}
var isSupported;
if (name == "bug-string-char-index") {
// IE <= 7 doesn't support accessing string characters using square
// bracket notation. IE 8 only supports this for primitives.
isSupported = "a"[0] != "a";
} else if (name == "json") {
// Indicates whether both `JSON.stringify` and `JSON.parse` are
// supported.
isSupported = has("json-stringify") && has("json-parse");
} else {
var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
// Test `JSON.stringify`.
if (name == "json-stringify") {
var stringify = JSON3.stringify, stringifySupported = typeof stringify == "function" && isExtended;
if (stringifySupported) {
// A test function object with a custom `toJSON` method.
(value = function () {
return 1;
}).toJSON = value;
try {
stringifySupported =
// Firefox 3.1b1 and b2 serialize string, number, and boolean
// primitives as object literals.
stringify(0) === "0" &&
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
// literals.
stringify(new Number()) === "0" &&
stringify(new String()) == '""' &&
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
// does not define a canonical JSON representation (this applies to
// objects with `toJSON` properties as well, *unless* they are nested
// within an object or array).
stringify(getClass) === undef &&
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
// FF 3.1b3 pass this test.
stringify(undef) === undef &&
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
// respectively, if the value is omitted entirely.
stringify() === undef &&
// FF 3.1b1, 2 throw an error if the given value is not a number,
// string, array, object, Boolean, or `null` literal. This applies to
// objects with custom `toJSON` methods as well, unless they are nested
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
// methods entirely.
stringify(value) === "1" &&
stringify([value]) == "[1]" &&
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
// `"[null]"`.
stringify([undef]) == "[null]" &&
// YUI 3.0.0b1 fails to serialize `null` literals.
stringify(null) == "null" &&
// FF 3.1b1, 2 halts serialization if an array contains a function:
// `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
// elides non-JSON values from objects and arrays, unless they
// define custom `toJSON` methods.
stringify([undef, getClass, null]) == "[null,null,null]" &&
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
// where character escape codes are expected (e.g., `\b` => `\u0008`).
stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
stringify(null, value) === "1" &&
stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
// serialize extended years.
stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
// The milliseconds are optional in ES 5, but required in 5.1.
stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
// four-digit years instead of six-digit years. Credits: @Yaffle.
stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
// values less than 1000. Credits: @Yaffle.
stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
} catch (exception) {
stringifySupported = false;
}
}
isSupported = stringifySupported;
}
// Test `JSON.parse`.
if (name == "json-parse") {
var parse = JSON3.parse;
if (typeof parse == "function") {
try {
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
// Conforming implementations should also coerce the initial argument to
// a string prior to parsing.
if (parse("0") === 0 && !parse(false)) {
// Simple parsing test.
value = parse(serialized);
var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
if (parseSupported) {
try {
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
parseSupported = !parse('"\t"');
} catch (exception) {}
if (parseSupported) {
try {
// FF 4.0 and 4.0.1 allow leading `+` signs and leading
// decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
// certain octal literals.
parseSupported = parse("01") !== 1;
} catch (exception) {}
}
if (parseSupported) {
try {
// FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
// points. These environments, along with FF 3.1b1 and 2,
// also allow trailing commas in JSON objects and arrays.
parseSupported = parse("1.") !== 1;
} catch (exception) {}
}
}
}
} catch (exception) {
parseSupported = false;
}
}
isSupported = parseSupported;
}
}
return has[name] = !!isSupported;
}
if (!has("json")) {
// Common `[[Class]]` name aliases.
var functionClass = "[object Function]";
var dateClass = "[object Date]";
var numberClass = "[object Number]";
var stringClass = "[object String]";
var arrayClass = "[object Array]";
var booleanClass = "[object Boolean]";
// Detect incomplete support for accessing string characters by index.
var charIndexBuggy = has("bug-string-char-index");
// Define additional utility methods if the `Date` methods are buggy.
if (!isExtended) {
var floor = Math.floor;
// A mapping between the months of the year and the number of days between
// January 1st and the first of the respective month.
var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
// Internal: Calculates the number of days between the Unix epoch and the
// first day of the given month.
var getDay = function (year, month) {
return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
};
}
// Internal: Determines if a property is a direct property of the given
// object. Delegates to the native `Object#hasOwnProperty` method.
if (!(isProperty = {}.hasOwnProperty)) {
isProperty = function (property) {
var members = {}, constructor;
if ((members.__proto__ = null, members.__proto__ = {
// The *proto* property cannot be set multiple times in recent
// versions of Firefox and SeaMonkey.
"toString": 1
}, members).toString != getClass) {
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
// supports the mutable *proto* property.
isProperty = function (property) {
// Capture and break the object's prototype chain (see section 8.6.2
// of the ES 5.1 spec). The parenthesized expression prevents an
// unsafe transformation by the Closure Compiler.
var original = this.__proto__, result = property in (this.__proto__ = null, this);
// Restore the original prototype chain.
this.__proto__ = original;
return result;
};
} else {
// Capture a reference to the top-level `Object` constructor.
constructor = members.constructor;
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
// other environments.
isProperty = function (property) {
var parent = (this.constructor || constructor).prototype;
return property in this && !(property in parent && this[property] === parent[property]);
};
}
members = null;
return isProperty.call(this, property);
};
}
// Internal: A set of primitive types used by `isHostType`.
var PrimitiveTypes = {
'boolean': 1,
'number': 1,
'string': 1,
'undefined': 1
};
// Internal: Determines if the given object `property` value is a
// non-primitive.
var isHostType = function (object, property) {
var type = typeof object[property];
return type == 'object' ? !!object[property] : !PrimitiveTypes[type];
};
// Internal: Normalizes the `for...in` iteration algorithm across
// environments. Each enumerated key is yielded to a `callback` function.
forEach = function (object, callback) {
var size = 0, Properties, members, property;
// Tests for bugs in the current environment's `for...in` algorithm. The
// `valueOf` property inherits the non-enumerable flag from
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
(Properties = function () {
this.valueOf = 0;
}).prototype.valueOf = 0;
// Iterate over a new instance of the `Properties` class.
members = new Properties();
for (property in members) {
// Ignore all properties inherited from `Object.prototype`.
if (isProperty.call(members, property)) {
size++;
}
}
Properties = members = null;
// Normalize the iteration algorithm.
if (!size) {
// A list of non-enumerable properties inherited from `Object.prototype`.
members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
// properties.
forEach = function (object, callback) {
var isFunction = getClass.call(object) == functionClass, property, length;
var hasProperty = !isFunction && typeof object.constructor != 'function' && isHostType(object, 'hasOwnProperty') ? object.hasOwnProperty : isProperty;
for (property in object) {
// Gecko <= 1.0 enumerates the `prototype` property of functions under
// certain conditions; IE does not.
if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
callback(property);
}
}
// Manually invoke the callback for each non-enumerable property.
for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
};
} else if (size == 2) {
// Safari <= 2.0.4 enumerates shadowed properties twice.
forEach = function (object, callback) {
// Create a set of iterated properties.
var members = {}, isFunction = getClass.call(object) == functionClass, property;
for (property in object) {
// Store each property name to prevent double enumeration. The
// `prototype` property of functions is not enumerated due to cross-
// environment inconsistencies.
if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
callback(property);
}
}
};
} else {
// No bugs detected; use the standard `for...in` algorithm.
forEach = function (object, callback) {
var isFunction = getClass.call(object) == functionClass, property, isConstructor;
for (property in object) {
if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
callback(property);
}
}
// Manually invoke the callback for the `constructor` property due to
// cross-environment inconsistencies.
if (isConstructor || isProperty.call(object, (property = "constructor"))) {
callback(property);
}
};
}
return forEach(object, callback);
};
// Public: Serializes a JavaScript `value` as a JSON string. The optional
// `filter` argument may specify either a function that alters how object and
// array members are serialized, or an array of strings and numbers that
// indicates which properties should be serialized. The optional `width`
// argument may be either a string or number that specifies the indentation
// level of the output.
if (!has("json-stringify")) {
// Internal: A map of control characters and their escaped equivalents.
var Escapes = {
92: "\\\\",
34: '\\"',
8: "\\b",
12: "\\f",
10: "\\n",
13: "\\r",
9: "\\t"
};
// Internal: Converts `value` into a zero-padded string such that its
// length is at least equal to `width`. The `width` must be <= 6.
var leadingZeroes = "000000";
var toPaddedString = function (width, value) {
// The `|| 0` expression is necessary to work around a bug in
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
return (leadingZeroes + (value || 0)).slice(-width);
};
// Internal: Double-quotes a string `value`, replacing all ASCII control
// characters (characters with code unit values between 0 and 31) with
// their escaped equivalents. This is an implementation of the
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
var unicodePrefix = "\\u00";
var quote = function (value) {
var result = '"', index = 0, length = value.length, isLarge = length > 10 && charIndexBuggy, symbols;
if (isLarge) {
symbols = value.split("");
}
for (; index < length; index++) {
var charCode = value.charCodeAt(index);
// If the character is a control character, append its Unicode or
// shorthand escape sequence; otherwise, append the character as-is.
switch (charCode) {
case 8: case 9: case 10: case 12: case 13: case 34: case 92:
result += Escapes[charCode];
break;
default:
if (charCode < 32) {
result += unicodePrefix + toPaddedString(2, charCode.toString(16));
break;
}
result += isLarge ? symbols[index] : charIndexBuggy ? value.charAt(index) : value[index];
}
}
return result + '"';
};
// Internal: Recursively serializes an object. Implements the
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
var serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result;
try {
// Necessary for host object support.
value = object[property];
} catch (exception) {}
if (typeof value == "object" && value) {
className = getClass.call(value);
if (className == dateClass && !isProperty.call(value, "toJSON")) {
if (value > -1 / 0 && value < 1 / 0) {
// Dates are serialized according to the `Date#toJSON` method
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
// for the ISO 8601 date time string format.
if (getDay) {
// Manually compute the year, month, date, hours, minutes,
// seconds, and milliseconds if the `getUTC*` methods are
// buggy. Adapted from @Yaffle's `date-shim` project.
date = floor(value / 864e5);
for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
date = 1 + date - getDay(year, month);
// The `time` value specifies the time within the day (see ES
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
// to compute `A modulo B`, as the `%` operator does not
// correspond to the `modulo` operation for negative numbers.
time = (value % 864e5 + 864e5) % 864e5;
// The hours, minutes, seconds, and milliseconds are obtained by
// decomposing the time within the day. See section 15.9.1.10.
hours = floor(time / 36e5) % 24;
minutes = floor(time / 6e4) % 60;
seconds = floor(time / 1e3) % 60;
milliseconds = time % 1e3;
} else {
year = value.getUTCFullYear();
month = value.getUTCMonth();
date = value.getUTCDate();
hours = value.getUTCHours();
minutes = value.getUTCMinutes();
seconds = value.getUTCSeconds();
milliseconds = value.getUTCMilliseconds();
}
// Serialize extended years correctly.
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
// Months, dates, hours, minutes, and seconds should have two
// digits; milliseconds should have three.
"T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
// Milliseconds are optional in ES 5.0, but required in 5.1.
"." + toPaddedString(3, milliseconds) + "Z";
} else {
value = null;
}
} else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
// ignores all `toJSON` methods on these objects unless they are
// defined directly on an instance.
value = value.toJSON(property);
}
}
if (callback) {
// If a replacement function was provided, call it to obtain the value
// for serialization.
value = callback.call(object, property, value);
}
if (value === null) {
return "null";
}
className = getClass.call(value);
if (className == booleanClass) {
// Booleans are represented literally.
return "" + value;
} else if (className == numberClass) {
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
// `"null"`.
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
} else if (className == stringClass) {
// Strings are double-quoted and escaped.
return quote("" + value);
}
// Recursively serialize objects and arrays.
if (typeof value == "object") {
// Check for cyclic structures. This is a linear search; performance
// is inversely proportional to the number of unique nested objects.
for (length = stack.length; length--;) {
if (stack[length] === value) {
// Cyclic structures cannot be serialized by `JSON.stringify`.
throw TypeError();
}
}
// Add the object to the stack of traversed objects.
stack.push(value);
results = [];
// Save the current indentation level and indent one additional level.
prefix = indentation;
indentation += whitespace;
if (className == arrayClass) {
// Recursively serialize array elements.
for (index = 0, length = value.length; index < length; index++) {
element = serialize(index, value, callback, properties, whitespace, indentation, stack);
results.push(element === undef ? "null" : element);
}
result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
} else {
// Recursively serialize object members. Members are selected from
// either a user-specified list of property names, or the object
// itself.
forEach(properties || value, function (property) {
var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
if (element !== undef) {
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
// is not the empty string, let `member` {quote(property) + ":"}
// be the concatenation of `member` and the `space` character."
// The "`space` character" refers to the literal space
// character, not the `space` {width} argument provided to
// `JSON.stringify`.
results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
}
});
result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
}
// Remove the object from the traversed object stack.
stack.pop();
return result;
}
};
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
JSON3.stringify = function (source, filter, width) {
var whitespace, callback, properties, className;
if (typeof filter == "function" || typeof filter == "object" && filter) {
if ((className = getClass.call(filter)) == functionClass) {
callback = filter;
} else if (className == arrayClass) {
// Convert the property names array into a makeshift set.
properties = {};
for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
}
}
if (width) {
if ((className = getClass.call(width)) == numberClass) {
// Convert the `width` to an integer and create a string containing
// `width` number of space characters.
if ((width -= width % 1) > 0) {
for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
}
} else if (className == stringClass) {
whitespace = width.length <= 10 ? width : width.slice(0, 10);
}
}
// Opera <= 7.54u2 discards the values associated with empty string keys
// (`""`) only if they are used directly within an object member list
// (e.g., `!("" in { "": 1})`).
return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
};
}
// Public: Parses a JSON source string.
if (!has("json-parse")) {
var fromCharCode = String.fromCharCode;
// Internal: A map of escaped control characters and their unescaped
// equivalents.
var Unescapes = {
92: "\\",
34: '"',
47: "/",
98: "\b",
116: "\t",
110: "\n",
102: "\f",
114: "\r"
};
// Internal: Stores the parser state.
var Index, Source;
// Internal: Resets the parser state and throws a `SyntaxError`.
var abort = function() {
Index = Source = null;
throw SyntaxError();
};
// Internal: Returns the next token, or `"$"` if the parser has reached
// the end of the source string. A token may be a string, number, `null`
// literal, or Boolean literal.
var lex = function () {
var source = Source, length = source.length, value, begin, position, isSigned, charCode;
while (Index < length) {
charCode = source.charCodeAt(Index);
switch (charCode) {
case 9: case 10: case 13: case 32:
// Skip whitespace tokens, including tabs, carriage returns, line
// feeds, and space characters.
Index++;
break;
case 123: case 125: case 91: case 93: case 58: case 44:
// Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
// the current position.
value = charIndexBuggy ? source.charAt(Index) : source[Index];
Index++;
return value;
case 34:
// `"` delimits a JSON string; advance to the next character and
// begin parsing the string. String tokens are prefixed with the
// sentinel `@` character to distinguish them from punctuators and
// end-of-string tokens.
for (value = "@", Index++; Index < length;) {
charCode = source.charCodeAt(Index);
if (charCode < 32) {
// Unescaped ASCII control characters (those with a code unit
// less than the space character) are not permitted.
abort();
} else if (charCode == 92) {
// A reverse solidus (`\`) marks the beginning of an escaped
// control character (including `"`, `\`, and `/`) or Unicode
// escape sequence.
charCode = source.charCodeAt(++Index);
switch (charCode) {
case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114:
// Revive escaped control characters.
value += Unescapes[charCode];
Index++;
break;
case 117:
// `\u` marks the beginning of a Unicode escape sequence.
// Advance to the first character and validate the
// four-digit code point.
begin = ++Index;
for (position = Index + 4; Index < position; Index++) {
charCode = source.charCodeAt(Index);
// A valid sequence comprises four hexdigits (case-
// insensitive) that form a single hexadecimal value.
if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) {
// Invalid Unicode escape sequence.
abort();
}
}
// Revive the escaped character.
value += fromCharCode("0x" + source.slice(begin, Index));
break;
default:
// Invalid escape sequence.
abort();
}
} else {
if (charCode == 34) {
// An unescaped double-quote character marks the end of the
// string.
break;
}
charCode = source.charCodeAt(Index);
begin = Index;
// Optimize for the common case where a string is valid.
while (charCode >= 32 && charCode != 92 && charCode != 34) {
charCode = source.charCodeAt(++Index);
}
// Append the string as-is.
value += source.slice(begin, Index);
}
}
if (source.charCodeAt(Index) == 34) {
// Advance to the next character and return the revived string.
Index++;
return value;
}
// Unterminated string.
abort();
default:
// Parse numbers and literals.
begin = Index;
// Advance past the negative sign, if one is specified.
if (charCode == 45) {
isSigned = true;
charCode = source.charCodeAt(++Index);
}
// Parse an integer or floating-point value.
if (charCode >= 48 && charCode <= 57) {
// Leading zeroes are interpreted as octal literals.
if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) {
// Illegal octal literal.
abort();
}
isSigned = false;
// Parse the integer component.
for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++);
// Floats cannot contain a leading decimal point; however, this
// case is already accounted for by the parser.
if (source.charCodeAt(Index) == 46) {
position = ++Index;
// Parse the decimal component.
for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
if (position == Index) {
// Illegal trailing decimal.
abort();
}
Index = position;
}
// Parse exponents. The `e` denoting the exponent is
// case-insensitive.
charCode = source.charCodeAt(Index);
if (charCode == 101 || charCode == 69) {
charCode = source.charCodeAt(++Index);
// Skip past the sign following the exponent, if one is
// specified.
if (charCode == 43 || charCode == 45) {
Index++;
}
// Parse the exponential component.
for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
if (position == Index) {
// Illegal empty exponent.
abort();
}
Index = position;
}
// Coerce the parsed value to a JavaScript number.
return +source.slice(begin, Index);
}
// A negative sign may only precede numbers.
if (isSigned) {
abort();
}
// `true`, `false`, and `null` literals.
if (source.slice(Index, Index + 4) == "true") {
Index += 4;
return true;
} else if (source.slice(Index, Index + 5) == "false") {
Index += 5;
return false;
} else if (source.slice(Index, Index + 4) == "null") {
Index += 4;
return null;
}
// Unrecognized token.
abort();
}
}
// Return the sentinel `$` character if the parser has reached the end
// of the source string.
return "$";
};
// Internal: Parses a JSON `value` token.
var get = function (value) {
var results, hasMembers;
if (value == "$") {
// Unexpected end of input.
abort();
}
if (typeof value == "string") {
if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
// Remove the sentinel `@` character.
return value.slice(1);
}
// Parse object and array literals.
if (value == "[") {
// Parses a JSON array, returning a new JavaScript array.
results = [];
for (;; hasMembers || (hasMembers = true)) {
value = lex();
// A closing square bracket marks the end of the array literal.
if (value == "]") {
break;
}
// If the array literal contains elements, the current token
// should be a comma separating the previous element from the
// next.
if (hasMembers) {
if (value == ",") {
value = lex();
if (value == "]") {
// Unexpected trailing `,` in array literal.
abort();
}
} else {
// A `,` must separate each array element.
abort();
}
}
// Elisions and leading commas are not permitted.
if (value == ",") {
abort();
}
results.push(get(value));
}
return results;
} else if (value == "{") {
// Parses a JSON object, returning a new JavaScript object.
results = {};
for (;; hasMembers || (hasMembers = true)) {
value = lex();
// A closing curly brace marks the end of the object literal.
if (value == "}") {
break;
}
// If the object literal contains members, the current token
// should be a comma separator.
if (hasMembers) {
if (value == ",") {
value = lex();
if (value == "}") {
// Unexpected trailing `,` in object literal.
abort();
}
} else {
// A `,` must separate each object member.
abort();
}
}
// Leading commas are not permitted, object property names must be
// double-quoted strings, and a `:` must separate each property
// name and value.
if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") {
abort();
}
results[value.slice(1)] = get(lex());
}
return results;
}
// Unexpected token encountered.
abort();
}
return value;
};
// Internal: Updates a traversed object member.
var update = function(source, property, callback) {
var element = walk(source, property, callback);
if (element === undef) {
delete source[property];
} else {
source[property] = element;
}
};
// Internal: Recursively traverses a parsed JSON object, invoking the
// `callback` function for each value. This is an implementation of the
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
var walk = function (source, property, callback) {
var value = source[property], length;
if (typeof value == "object" && value) {
// `forEach` can't be used to traverse an array in Opera <= 8.54
// because its `Object#hasOwnProperty` implementation returns `false`
// for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
if (getClass.call(value) == arrayClass) {
for (length = value.length; length--;) {
update(value, length, callback);
}
} else {
forEach(value, function (property) {
update(value, property, callback);
});
}
}
return callback.call(source, property, value);
};
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
JSON3.parse = function (source, callback) {
var result, value;
Index = 0;
Source = "" + source;
result = get(lex());
// If a JSON string contains multiple tokens, it is invalid.
if (lex() != "$") {
abort();
}
// Reset the parser state.
Index = Source = null;
return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result;
};
}
}
// Export for asynchronous module loaders.
if (isLoader) {
define(function () {
return JSON3;
});
}
}(this));
},{}],36:[function(require,module,exports){
/*
Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
(c) 2010-2013, Vladimir Agafonkin
(c) 2010-2011, CloudMade
*/
(function (window, document, undefined) {
var oldL = window.L,
L = {};
L.version = '0.6.2';
// define Leaflet for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = L;
// define Leaflet as an AMD module
} else if (typeof define === 'function' && define.amd) {
define(L);
}
// define Leaflet as a global L variable, saving the original L to restore later if needed
L.noConflict = function () {
window.L = oldL;
return this;
};
window.L = L;
/*
* L.Util contains various utility functions used throughout Leaflet code.
*/
L.Util = {
extend: function (dest) { // (Object[, Object, ...]) ->
var sources = Array.prototype.slice.call(arguments, 1),
i, j, len, src;
for (j = 0, len = sources.length; j < len; j++) {
src = sources[j] || {};
for (i in src) {
if (src.hasOwnProperty(i)) {
dest[i] = src[i];
}
}
}
return dest;
},
bind: function (fn, obj) { // (Function, Object) -> Function
var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
return function () {
return fn.apply(obj, args || arguments);
};
},
stamp: (function () {
var lastId = 0,
key = '_leaflet_id';
return function (obj) {
obj[key] = obj[key] || ++lastId;
return obj[key];
};
}()),
invokeEach: function (obj, method, context) {
var i, args;
if (typeof obj === 'object') {
args = Array.prototype.slice.call(arguments, 3);
for (i in obj) {
method.apply(context, [i, obj[i]].concat(args));
}
return true;
}
return false;
},
limitExecByInterval: function (fn, time, context) {
var lock, execOnUnlock;
return function wrapperFn() {
var args = arguments;
if (lock) {
execOnUnlock = true;
return;
}
lock = true;
setTimeout(function () {
lock = false;
if (execOnUnlock) {
wrapperFn.apply(context, args);
execOnUnlock = false;
}
}, time);
fn.apply(context, args);
};
},
falseFn: function () {
return false;
},
formatNum: function (num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
},
trim: function (str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
},
splitWords: function (str) {
return L.Util.trim(str).split(/\s+/);
},
setOptions: function (obj, options) {
obj.options = L.extend({}, obj.options, options);
return obj.options;
},
getParamString: function (obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
},
template: function (str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
},
isArray: function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
},
emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
};
(function () {
// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
var i, fn,
prefixes = ['webkit', 'moz', 'o', 'ms'];
for (i = 0; i < prefixes.length && !fn; i++) {
fn = window[prefixes[i] + name];
}
return fn;
}
var lastTime = 0;
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
var requestFn = window.requestAnimationFrame ||
getPrefixed('RequestAnimationFrame') || timeoutDefer;
var cancelFn = window.cancelAnimationFrame ||
getPrefixed('CancelAnimationFrame') ||
getPrefixed('CancelRequestAnimationFrame') ||
function (id) { window.clearTimeout(id); };
L.Util.requestAnimFrame = function (fn, context, immediate, element) {
fn = L.bind(fn, context);
if (immediate && requestFn === timeoutDefer) {
fn();
} else {
return requestFn.call(window, fn, element);
}
};
L.Util.cancelAnimFrame = function (id) {
if (id) {
cancelFn.call(window, id);
}
};
}());
// shortcuts for most used utility functions
L.extend = L.Util.extend;
L.bind = L.Util.bind;
L.stamp = L.Util.stamp;
L.setOptions = L.Util.setOptions;
/*
* L.Class powers the OOP facilities of the library.
* Thanks to John Resig and Dean Edwards for inspiration!
*/
L.Class = function () {};
L.Class.extend = function (props) {
// extended class with the new prototype
var NewClass = function () {
// call the constructor
if (this.initialize) {
this.initialize.apply(this, arguments);
}
// call all constructor hooks
if (this._initHooks) {
this.callInitHooks();
}
};
// instantiate class without calling constructor
var F = function () {};
F.prototype = this.prototype;
var proto = new F();
proto.constructor = NewClass;
NewClass.prototype = proto;
//inherit parent's statics
for (var i in this) {
if (this.hasOwnProperty(i) && i !== 'prototype') {
NewClass[i] = this[i];
}
}
// mix static properties into the class
if (props.statics) {
L.extend(NewClass, props.statics);
delete props.statics;
}
// mix includes into the prototype
if (props.includes) {
L.Util.extend.apply(null, [proto].concat(props.includes));
delete props.includes;
}
// merge options
if (props.options && proto.options) {
props.options = L.extend({}, proto.options, props.options);
}
// mix given properties into the prototype
L.extend(proto, props);
proto._initHooks = [];
var parent = this;
// jshint camelcase: false
NewClass.__super__ = parent.prototype;
// add method for calling all hooks
proto.callInitHooks = function () {
if (this._initHooksCalled) { return; }
if (parent.prototype.callInitHooks) {
parent.prototype.callInitHooks.call(this);
}
this._initHooksCalled = true;
for (var i = 0, len = proto._initHooks.length; i < len; i++) {
proto._initHooks[i].call(this);
}
};
return NewClass;
};
// method for adding properties to prototype
L.Class.include = function (props) {
L.extend(this.prototype, props);
};
// merge new default options to the Class
L.Class.mergeOptions = function (options) {
L.extend(this.prototype.options, options);
};
// add a constructor hook
L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
var args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () {
this[fn].apply(this, args);
};
this.prototype._initHooks = this.prototype._initHooks || [];
this.prototype._initHooks.push(init);
};
/*
* L.Mixin.Events is used to add custom events functionality to Leaflet classes.
*/
var eventsKey = '_leaflet_events';
L.Mixin = {};
L.Mixin.Events = {
addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
// types can be a map of types/handlers
if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
var events = this[eventsKey] = this[eventsKey] || {},
contextId = context && L.stamp(context),
i, len, event, type, indexKey, indexLenKey, typeIndex;
// types can be a string of space-separated words
types = L.Util.splitWords(types);
for (i = 0, len = types.length; i < len; i++) {
event = {
action: fn,
context: context || this
};
type = types[i];
if (context) {
// store listeners of a particular context in a separate hash (if it has an id)
// gives a major performance boost when removing thousands of map layers
indexKey = type + '_idx';
indexLenKey = indexKey + '_len';
typeIndex = events[indexKey] = events[indexKey] || {};
if (!typeIndex[contextId]) {
typeIndex[contextId] = [];
// keep track of the number of keys in the index to quickly check if it's empty
events[indexLenKey] = (events[indexLenKey] || 0) + 1;
}
typeIndex[contextId].push(event);
} else {
events[type] = events[type] || [];
events[type].push(event);
}
}
return this;
},
hasEventListeners: function (type) { // (String) -> Boolean
var events = this[eventsKey];
return !!events && ((type in events && events[type].length > 0) ||
(type + '_idx' in events && events[type + '_idx_len'] > 0));
},
removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
if (!this[eventsKey]) {
return this;
}
if (!types) {
return this.clearAllEventListeners();
}
if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
var events = this[eventsKey],
contextId = context && L.stamp(context),
i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
types = L.Util.splitWords(types);
for (i = 0, len = types.length; i < len; i++) {
type = types[i];
indexKey = type + '_idx';
indexLenKey = indexKey + '_len';
typeIndex = events[indexKey];
if (!fn) {
// clear all listeners for a type if function isn't specified
delete events[type];
delete events[indexKey];
} else {
listeners = context && typeIndex ? typeIndex[contextId] : events[type];
if (listeners) {
for (j = listeners.length - 1; j >= 0; j--) {
if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
removed = listeners.splice(j, 1);
// set the old action to a no-op, because it is possible
// that the listener is being iterated over as part of a dispatch
removed[0].action = L.Util.falseFn;
}
}
if (context && typeIndex && (listeners.length === 0)) {
delete typeIndex[contextId];
events[indexLenKey]--;
}
}
}
}
return this;
},
clearAllEventListeners: function () {
delete this[eventsKey];
return this;
},
fireEvent: function (type, data) { // (String[, Object])
if (!this.hasEventListeners(type)) {
return this;
}
var event = L.Util.extend({}, data, { type: type, target: this });
var events = this[eventsKey],
listeners, i, len, typeIndex, contextId;
if (events[type]) {
// make sure adding/removing listeners inside other listeners won't cause infinite loop
listeners = events[type].slice();
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context || this, event);
}
}
// fire event for the context-indexed listeners as well
typeIndex = events[type + '_idx'];
for (contextId in typeIndex) {
listeners = typeIndex[contextId].slice();
if (listeners) {
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context || this, event);
}
}
}
return this;
},
addOneTimeEventListener: function (types, fn, context) {
if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
var handler = L.bind(function () {
this
.removeEventListener(types, fn, context)
.removeEventListener(types, handler, context);
}, this);
return this
.addEventListener(types, fn, context)
.addEventListener(types, handler, context);
}
};
L.Mixin.Events.on = L.Mixin.Events.addEventListener;
L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
/*
* L.Browser handles different browser and feature detections for internal Leaflet use.
*/
(function () {
var ie = !!window.ActiveXObject,
ie6 = ie && !window.XMLHttpRequest,
ie7 = ie && !document.querySelector,
ielt9 = ie && !document.addEventListener,
// terrible browser detection to work around Safari / iOS / Android browser bugs
ua = navigator.userAgent.toLowerCase(),
webkit = ua.indexOf('webkit') !== -1,
chrome = ua.indexOf('chrome') !== -1,
phantomjs = ua.indexOf('phantom') !== -1,
android = ua.indexOf('android') !== -1,
android23 = ua.search('android [23]') !== -1,
mobile = typeof orientation !== undefined + '',
msTouch = window.navigator && window.navigator.msPointerEnabled &&
window.navigator.msMaxTouchPoints,
retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
window.matchMedia('(min-resolution:144dpi)').matches),
doc = document.documentElement,
ie3d = ie && ('transition' in doc.style),
webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
gecko3d = 'MozPerspective' in doc.style,
opera3d = 'OTransition' in doc.style,
any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
// PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch.
// https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151
var touch = !window.L_NO_TOUCH && !phantomjs && (function () {
var startName = 'ontouchstart';
// IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
if (msTouch || (startName in doc)) {
return true;
}
// Firefox/Gecko
var div = document.createElement('div'),
supported = false;
if (!div.setAttribute) {
return false;
}
div.setAttribute(startName, 'return;');
if (typeof div[startName] === 'function') {
supported = true;
}
div.removeAttribute(startName);
div = null;
return supported;
}());
L.Browser = {
ie: ie,
ie6: ie6,
ie7: ie7,
ielt9: ielt9,
webkit: webkit,
android: android,
android23: android23,
chrome: chrome,
ie3d: ie3d,
webkit3d: webkit3d,
gecko3d: gecko3d,
opera3d: opera3d,
any3d: any3d,
mobile: mobile,
mobileWebkit: mobile && webkit,
mobileWebkit3d: mobile && webkit3d,
mobileOpera: mobile && window.opera,
touch: touch,
msTouch: msTouch,
retina: retina
};
}());
/*
* L.Point represents a point with x and y coordinates.
*/
L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
this.x = (round ? Math.round(x) : x);
this.y = (round ? Math.round(y) : y);
};
L.Point.prototype = {
clone: function () {
return new L.Point(this.x, this.y);
},
// non-destructive, returns a new point
add: function (point) {
return this.clone()._add(L.point(point));
},
// destructive, used directly for performance in situations where it's safe to modify existing point
_add: function (point) {
this.x += point.x;
this.y += point.y;
return this;
},
subtract: function (point) {
return this.clone()._subtract(L.point(point));
},
_subtract: function (point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
divideBy: function (num) {
return this.clone()._divideBy(num);
},
_divideBy: function (num) {
this.x /= num;
this.y /= num;
return this;
},
multiplyBy: function (num) {
return this.clone()._multiplyBy(num);
},
_multiplyBy: function (num) {
this.x *= num;
this.y *= num;
return this;
},
round: function () {
return this.clone()._round();
},
_round: function () {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
floor: function () {
return this.clone()._floor();
},
_floor: function () {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
},
distanceTo: function (point) {
point = L.point(point);
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
equals: function (point) {
point = L.point(point);
return point.x === this.x &&
point.y === this.y;
},
contains: function (point) {
point = L.point(point);
return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y);
},
toString: function () {
return 'Point(' +
L.Util.formatNum(this.x) + ', ' +
L.Util.formatNum(this.y) + ')';
}
};
L.point = function (x, y, round) {
if (x instanceof L.Point) {
return x;
}
if (L.Util.isArray(x)) {
return new L.Point(x[0], x[1]);
}
if (x === undefined || x === null) {
return x;
}
return new L.Point(x, y, round);
};
/*
* L.Bounds represents a rectangular area on the screen in pixel coordinates.
*/
L.Bounds = function (a, b) { //(Point, Point) or Point[]
if (!a) { return; }
var points = b ? [a, b] : a;
for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
};
L.Bounds.prototype = {
// extend the bounds to contain the given point
extend: function (point) { // (Point)
point = L.point(point);
if (!this.min && !this.max) {
this.min = point.clone();
this.max = point.clone();
} else {
this.min.x = Math.min(point.x, this.min.x);
this.max.x = Math.max(point.x, this.max.x);
this.min.y = Math.min(point.y, this.min.y);
this.max.y = Math.max(point.y, this.max.y);
}
return this;
},
getCenter: function (round) { // (Boolean) -> Point
return new L.Point(
(this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round);
},
getBottomLeft: function () { // -> Point
return new L.Point(this.min.x, this.max.y);
},
getTopRight: function () { // -> Point
return new L.Point(this.max.x, this.min.y);
},
getSize: function () {
return this.max.subtract(this.min);
},
contains: function (obj) { // (Bounds) or (Point) -> Boolean
var min, max;
if (typeof obj[0] === 'number' || obj instanceof L.Point) {
obj = L.point(obj);
} else {
obj = L.bounds(obj);
}
if (obj instanceof L.Bounds) {
min = obj.min;
max = obj.max;
} else {
min = max = obj;
}
return (min.x >= this.min.x) &&
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
intersects: function (bounds) { // (Bounds) -> Boolean
bounds = L.bounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
},
isValid: function () {
return !!(this.min && this.max);
}
};
L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
if (!a || a instanceof L.Bounds) {
return a;
}
return new L.Bounds(a, b);
};
/*
* L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
*/
L.Transformation = function (a, b, c, d) {
this._a = a;
this._b = b;
this._c = c;
this._d = d;
};
L.Transformation.prototype = {
transform: function (point, scale) { // (Point, Number) -> Point
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
_transform: function (point, scale) {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
untransform: function (point, scale) {
scale = scale || 1;
return new L.Point(
(point.x / scale - this._b) / this._a,
(point.y / scale - this._d) / this._c);
}
};
/*
* L.DomUtil contains various utility functions for working with DOM.
*/
L.DomUtil = {
get: function (id) {
return (typeof id === 'string' ? document.getElementById(id) : id);
},
getStyle: function (el, style) {
var value = el.style[style];
if (!value && el.currentStyle) {
value = el.currentStyle[style];
}
if ((!value || value === 'auto') && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
return value === 'auto' ? null : value;
},
getViewportOffset: function (element) {
var top = 0,
left = 0,
el = element,
docBody = document.body,
docEl = document.documentElement,
pos,
ie7 = L.Browser.ie7;
do {
top += el.offsetTop || 0;
left += el.offsetLeft || 0;
//add borders
top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
pos = L.DomUtil.getStyle(el, 'position');
if (el.offsetParent === docBody && pos === 'absolute') { break; }
if (pos === 'fixed') {
top += docBody.scrollTop || docEl.scrollTop || 0;
left += docBody.scrollLeft || docEl.scrollLeft || 0;
break;
}
if (pos === 'relative' && !el.offsetLeft) {
var width = L.DomUtil.getStyle(el, 'width'),
maxWidth = L.DomUtil.getStyle(el, 'max-width'),
r = el.getBoundingClientRect();
if (width !== 'none' || maxWidth !== 'none') {
left += r.left + el.clientLeft;
}
//calculate full y offset since we're breaking out of the loop
top += r.top + (docBody.scrollTop || docEl.scrollTop || 0);
break;
}
el = el.offsetParent;
} while (el);
el = element;
do {
if (el === docBody) { break; }
top -= el.scrollTop || 0;
left -= el.scrollLeft || 0;
// webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
// https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
left += el.scrollWidth - el.clientWidth;
// ie7 shows the scrollbar by default and provides clientWidth counting it, so we
// need to add it back in if it is visible; scrollbar is on the left as we are RTL
if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
left += 17;
}
}
el = el.parentNode;
} while (el);
return new L.Point(left, top);
},
documentIsLtr: function () {
if (!L.DomUtil._docIsLtrCached) {
L.DomUtil._docIsLtrCached = true;
L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
}
return L.DomUtil._docIsLtr;
},
create: function (tagName, className, container) {
var el = document.createElement(tagName);
el.className = className;
if (container) {
container.appendChild(el);
}
return el;
},
hasClass: function (el, name) {
return (el.className.length > 0) &&
new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className);
},
addClass: function (el, name) {
if (!L.DomUtil.hasClass(el, name)) {
el.className += (el.className ? ' ' : '') + name;
}
},
removeClass: function (el, name) {
el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' '));
},
setOpacity: function (el, value) {
if ('opacity' in el.style) {
el.style.opacity = value;
} else if ('filter' in el.style) {
var filter = false,
filterName = 'DXImageTransform.Microsoft.Alpha';
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
// don't set opacity to 1 if we haven't already set an opacity,
// it isn't needed and breaks transparent pngs.
if (value === 1) { return; }
}
value = Math.round(value * 100);
if (filter) {
filter.Enabled = (value !== 100);
filter.Opacity = value;
} else {
el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
}
},
testProp: function (props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
},
getTranslateString: function (point) {
// on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
// makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
// (same speed either way), Opera 12 doesn't support translate3d
var is3d = L.Browser.webkit3d,
open = 'translate' + (is3d ? '3d' : '') + '(',
close = (is3d ? ',0' : '') + ')';
return open + point.x + 'px,' + point.y + 'px' + close;
},
getScaleString: function (scale, origin) {
var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
scaleStr = ' scale(' + scale + ') ';
return preTranslateStr + scaleStr;
},
setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
// jshint camelcase: false
el._leaflet_pos = point;
if (!disable3D && L.Browser.any3d) {
el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
// workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
if (L.Browser.mobileWebkit3d) {
el.style.WebkitBackfaceVisibility = 'hidden';
}
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
},
getPosition: function (el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
// jshint camelcase: false
return el._leaflet_pos;
}
};
// prefix style property names
L.DomUtil.TRANSFORM = L.DomUtil.testProp(
['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
// webkitTransition comes first because some browser versions that drop vendor prefix don't do
// the same for the transitionend event, in particular the Android 4.1 stock browser
L.DomUtil.TRANSITION = L.DomUtil.testProp(
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
L.DomUtil.TRANSITION_END =
L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
L.DomUtil.TRANSITION + 'End' : 'transitionend';
(function () {
var userSelectProperty = L.DomUtil.testProp(
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
var userDragProperty = L.DomUtil.testProp(
['userDrag', 'WebkitUserDrag', 'OUserDrag', 'MozUserDrag', 'msUserDrag']);
L.extend(L.DomUtil, {
disableTextSelection: function () {
if (userSelectProperty) {
var style = document.documentElement.style;
this._userSelect = style[userSelectProperty];
style[userSelectProperty] = 'none';
} else {
L.DomEvent.on(window, 'selectstart', L.DomEvent.stop);
}
},
enableTextSelection: function () {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = this._userSelect;
delete this._userSelect;
} else {
L.DomEvent.off(window, 'selectstart', L.DomEvent.stop);
}
},
disableImageDrag: function () {
if (userDragProperty) {
var style = document.documentElement.style;
this._userDrag = style[userDragProperty];
style[userDragProperty] = 'none';
} else {
L.DomEvent.on(window, 'dragstart', L.DomEvent.stop);
}
},
enableImageDrag: function () {
if (userDragProperty) {
document.documentElement.style[userDragProperty] = this._userDrag;
delete this._userDrag;
} else {
L.DomEvent.off(window, 'dragstart', L.DomEvent.stop);
}
}
});
})();
/*
* L.LatLng represents a geographical point with latitude and longitude coordinates.
*/
L.LatLng = function (rawLat, rawLng) { // (Number, Number)
var lat = parseFloat(rawLat),
lng = parseFloat(rawLng);
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
}
this.lat = lat;
this.lng = lng;
};
L.extend(L.LatLng, {
DEG_TO_RAD: Math.PI / 180,
RAD_TO_DEG: 180 / Math.PI,
MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
});
L.LatLng.prototype = {
equals: function (obj) { // (LatLng) -> Boolean
if (!obj) { return false; }
obj = L.latLng(obj);
var margin = Math.max(
Math.abs(this.lat - obj.lat),
Math.abs(this.lng - obj.lng));
return margin <= L.LatLng.MAX_MARGIN;
},
toString: function (precision) { // (Number) -> String
return 'LatLng(' +
L.Util.formatNum(this.lat, precision) + ', ' +
L.Util.formatNum(this.lng, precision) + ')';
},
// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
// TODO move to projection code, LatLng shouldn't know about Earth
distanceTo: function (other) { // (LatLng) -> Number
other = L.latLng(other);
var R = 6378137, // earth radius in meters
d2r = L.LatLng.DEG_TO_RAD,
dLat = (other.lat - this.lat) * d2r,
dLon = (other.lng - this.lng) * d2r,
lat1 = this.lat * d2r,
lat2 = other.lat * d2r,
sin1 = Math.sin(dLat / 2),
sin2 = Math.sin(dLon / 2);
var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
},
wrap: function (a, b) { // (Number, Number) -> LatLng
var lng = this.lng;
a = a || -180;
b = b || 180;
lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
return new L.LatLng(this.lat, lng);
}
};
L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
if (a instanceof L.LatLng) {
return a;
}
if (L.Util.isArray(a)) {
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);
}
return new L.LatLng(a, b);
};
/*
* L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
*/
L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
if (!southWest) { return; }
var latlngs = northEast ? [southWest, northEast] : southWest;
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
};
L.LatLngBounds.prototype = {
// extend the bounds to contain the given point or bounds
extend: function (obj) { // (LatLng) or (LatLngBounds)
if (!obj) { return this; }
if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
obj = L.latLng(obj);
} else {
obj = L.latLngBounds(obj);
}
if (obj instanceof L.LatLng) {
if (!this._southWest && !this._northEast) {
this._southWest = new L.LatLng(obj.lat, obj.lng);
this._northEast = new L.LatLng(obj.lat, obj.lng);
} else {
this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
}
} else if (obj instanceof L.LatLngBounds) {
this.extend(obj._southWest);
this.extend(obj._northEast);
}
return this;
},
// extend the bounds by a percentage
pad: function (bufferRatio) { // (Number) -> LatLngBounds
var sw = this._southWest,
ne = this._northEast,
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new L.LatLngBounds(
new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
getCenter: function () { // -> LatLng
return new L.LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
getSouthWest: function () {
return this._southWest;
},
getNorthEast: function () {
return this._northEast;
},
getNorthWest: function () {
return new L.LatLng(this.getNorth(), this.getWest());
},
getSouthEast: function () {
return new L.LatLng(this.getSouth(), this.getEast());
},
getWest: function () {
return this._southWest.lng;
},
getSouth: function () {
return this._southWest.lat;
},
getEast: function () {
return this._northEast.lng;
},
getNorth: function () {
return this._northEast.lat;
},
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
obj = L.latLng(obj);
} else {
obj = L.latLngBounds(obj);
}
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof L.LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
},
intersects: function (bounds) { // (LatLngBounds)
bounds = L.latLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
return latIntersects && lngIntersects;
},
toBBoxString: function () {
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
},
equals: function (bounds) { // (LatLngBounds)
if (!bounds) { return false; }
bounds = L.latLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest()) &&
this._northEast.equals(bounds.getNorthEast());
},
isValid: function () {
return !!(this._southWest && this._northEast);
}
};
//TODO International date line?
L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
if (!a || a instanceof L.LatLngBounds) {
return a;
}
return new L.LatLngBounds(a, b);
};
/*
* L.Projection contains various geographical projections used by CRS classes.
*/
L.Projection = {};
/*
* Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
*/
L.Projection.SphericalMercator = {
MAX_LATITUDE: 85.0511287798,
project: function (latlng) { // (LatLng) -> Point
var d = L.LatLng.DEG_TO_RAD,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
x = latlng.lng * d,
y = lat * d;
y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
return new L.Point(x, y);
},
unproject: function (point) { // (Point, Boolean) -> LatLng
var d = L.LatLng.RAD_TO_DEG,
lng = point.x * d,
lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
return new L.LatLng(lat, lng);
}
};
/*
* Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
*/
L.Projection.LonLat = {
project: function (latlng) {
return new L.Point(latlng.lng, latlng.lat);
},
unproject: function (point) {
return new L.LatLng(point.y, point.x);
}
};
/*
* L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
*/
L.CRS = {
latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
var projectedPoint = this.projection.project(latlng),
scale = this.scale(zoom);
return this.transformation._transform(projectedPoint, scale);
},
pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
var scale = this.scale(zoom),
untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint);
},
project: function (latlng) {
return this.projection.project(latlng);
},
scale: function (zoom) {
return 256 * Math.pow(2, zoom);
}
};
/*
* A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
*/
L.CRS.Simple = L.extend({}, L.CRS, {
projection: L.Projection.LonLat,
transformation: new L.Transformation(1, 0, -1, 0),
scale: function (zoom) {
return Math.pow(2, zoom);
}
});
/*
* L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
* and is used by Leaflet by default.
*/
L.CRS.EPSG3857 = L.extend({}, L.CRS, {
code: 'EPSG:3857',
projection: L.Projection.SphericalMercator,
transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
project: function (latlng) { // (LatLng) -> Point
var projectedPoint = this.projection.project(latlng),
earthRadius = 6378137;
return projectedPoint.multiplyBy(earthRadius);
}
});
L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
code: 'EPSG:900913'
});
/*
* L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
*/
L.CRS.EPSG4326 = L.extend({}, L.CRS, {
code: 'EPSG:4326',
projection: L.Projection.LonLat,
transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
});
/*
* L.Map is the central class of the API - it is used to create a map.
*/
L.Map = L.Class.extend({
includes: L.Mixin.Events,
options: {
crs: L.CRS.EPSG3857,
/*
center: LatLng,
zoom: Number,
layers: Array,
*/
fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
trackResize: true,
markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
},
initialize: function (id, options) { // (HTMLElement or String, Object)
options = L.setOptions(this, options);
this._initContainer(id);
this._initLayout();
this._initEvents();
if (options.maxBounds) {
this.setMaxBounds(options.maxBounds);
}
if (options.center && options.zoom !== undefined) {
this.setView(L.latLng(options.center), options.zoom, {reset: true});
}
this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._tileLayersNum = 0;
this.callInitHooks();
this._addLayers(options.layers);
},
// public methods that modify map state
// replaced by animation-powered implementation in Map.PanAnimation.js
setView: function (center, zoom) {
this._resetView(L.latLng(center), this._limitZoom(zoom));
return this;
},
setZoom: function (zoom, options) {
return this.setView(this.getCenter(), zoom, {zoom: options});
},
zoomIn: function (delta, options) {
return this.setZoom(this._zoom + (delta || 1), options);
},
zoomOut: function (delta, options) {
return this.setZoom(this._zoom - (delta || 1), options);
},
setZoomAround: function (latlng, zoom, options) {
var scale = this.getZoomScale(zoom),
viewHalf = this.getSize().divideBy(2),
containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
return this.setView(newCenter, zoom, {zoom: options});
},
fitBounds: function (bounds, options) {
options = options || {};
bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
swPoint = this.project(bounds.getSouthWest(), zoom),
nePoint = this.project(bounds.getNorthEast(), zoom),
center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
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.Animation.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) {
this._boundsMinZoom = null;
this.off('moveend', this._panInsideMaxBounds, this);
return this;
}
var minZoom = this.getBoundsZoom(bounds, true);
this._boundsMinZoom = minZoom;
if (this._loaded) {
if (this._zoom < minZoom) {
this.setView(bounds.getCenter(), minZoom);
} else {
this.panInsideBounds(bounds);
}
}
this.on('moveend', this._panInsideMaxBounds, this);
return this;
},
panInsideBounds: function (bounds) {
bounds = L.latLngBounds(bounds);
var viewBounds = this.getPixelBounds(),
viewSw = viewBounds.getBottomLeft(),
viewNe = viewBounds.getTopRight(),
sw = this.project(bounds.getSouthWest()),
ne = this.project(bounds.getNorthEast()),
dx = 0,
dy = 0;
if (viewNe.y < ne.y) { // north
dy = Math.ceil(ne.y - viewNe.y);
}
if (viewNe.x > ne.x) { // east
dx = Math.floor(ne.x - viewNe.x);
}
if (viewSw.y > sw.y) { // south
dy = Math.floor(sw.y - viewSw.y);
}
if (viewSw.x < sw.x) { // west
dx = Math.ceil(sw.x - viewSw.x);
}
if (dx || dy) {
return this.panBy([dx, dy]);
}
return this;
},
addLayer: function (layer) {
// TODO method is too big, refactor
var id = L.stamp(layer);
if (this._layers[id]) { return this; }
this._layers[id] = layer;
// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
this._zoomBoundLayers[id] = layer;
this._updateZoomLevels();
}
// TODO looks ugly, refactor!!!
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum++;
this._tileLayersToLoad++;
layer.on('load', this._onTileLayerLoad, this);
}
if (this._loaded) {
this._layerAdd(layer);
}
return this;
},
removeLayer: function (layer) {
var id = L.stamp(layer);
if (!this._layers[id]) { return; }
if (this._loaded) {
layer.onRemove(this);
this.fire('layerremove', {layer: layer});
}
delete this._layers[id];
if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id];
this._updateZoomLevels();
}
// TODO looks ugly, refactor
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum--;
this._tileLayersToLoad--;
layer.off('load', this._onTileLayerLoad, this);
}
return this;
},
hasLayer: function (layer) {
if (!layer) { return false; }
return (L.stamp(layer) in this._layers);
},
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
invalidateSize: function (options) {
options = L.extend({
animate: false,
pan: true
}, options === true ? {animate: true} : options);
var oldSize = this.getSize();
this._sizeChanged = true;
if (this.options.maxBounds) {
this.setMaxBounds(this.options.maxBounds);
}
if (!this._loaded) { return this; }
var newSize = this.getSize(),
offset = oldSize.subtract(newSize).divideBy(2).round();
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');
// make sure moveend is not fired too often on resize
clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
}
return this.fire('resize', {
oldSize: oldSize,
newSize: newSize
});
},
// TODO handler.addTo
addHandler: function (name, HandlerClass) {
if (!HandlerClass) { return; }
var handler = this[name] = new HandlerClass(this);
this._handlers.push(handler);
if (this.options[name]) {
handler.enable();
}
return this;
},
remove: function () {
if (this._loaded) {
this.fire('unload');
}
this._initEvents('off');
delete this._container._leaflet;
this._clearPanes();
if (this._clearControlPos) {
this._clearControlPos();
}
this._clearHandlers();
return this;
},
// public methods for getting map state
getCenter: function () { // (Boolean) -> LatLng
this._checkIfLoaded();
if (!this._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 () {
var z1 = this.options.minZoom || 0,
z2 = this._layersMinZoom || 0,
z3 = this._boundsMinZoom || 0;
return Math.max(z1, z2, z3);
},
getMaxZoom: function () {
var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom;
return Math.min(z1, z2);
},
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
bounds = L.latLngBounds(bounds);
var zoom = this.getMinZoom() - (inside ? 1 : 0),
maxZoom = this.getMaxZoom(),
size = this.getSize(),
nw = bounds.getNorthWest(),
se = bounds.getSouthEast(),
zoomNotFound = true,
boundsSize;
padding = L.point(padding || [0, 0]);
do {
zoom++;
boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
} while (zoomNotFound && zoom <= maxZoom);
if (zoomNotFound && inside) {
return null;
}
return inside ? zoom : zoom - 1;
},
getSize: function () {
if (!this._size || this._sizeChanged) {
this._size = new L.Point(
this._container.clientWidth,
this._container.clientHeight);
this._sizeChanged = false;
}
return this._size.clone();
},
getPixelBounds: function () {
var topLeftPoint = this._getTopLeftPoint();
return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
},
getPixelOrigin: function () {
this._checkIfLoaded();
return this._initialTopLeftPoint;
},
getPanes: function () {
return this._panes;
},
getContainer: function () {
return this._container;
},
// TODO replace with universal implementation after refactoring projections
getZoomScale: function (toZoom) {
var crs = this.options.crs;
return crs.scale(toZoom) / crs.scale(this._zoom);
},
getScaleZoom: function (scale) {
return this._zoom + (Math.log(scale) / Math.LN2);
},
// conversion methods
project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
},
unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.pointToLatLng(L.point(point), zoom);
},
layerPointToLatLng: function (point) { // (Point)
var projectedPoint = L.point(point).add(this.getPixelOrigin());
return this.unproject(projectedPoint);
},
latLngToLayerPoint: function (latlng) { // (LatLng)
var projectedPoint = this.project(L.latLng(latlng))._round();
return projectedPoint._subtract(this.getPixelOrigin());
},
containerPointToLayerPoint: function (point) { // (Point)
return L.point(point).subtract(this._getMapPanePos());
},
layerPointToContainerPoint: function (point) { // (Point)
return L.point(point).add(this._getMapPanePos());
},
containerPointToLatLng: function (point) {
var layerPoint = this.containerPointToLayerPoint(L.point(point));
return this.layerPointToLatLng(layerPoint);
},
latLngToContainerPoint: function (latlng) {
return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
},
mouseEventToContainerPoint: function (e) { // (MouseEvent)
return L.DomEvent.getMousePosition(e, this._container);
},
mouseEventToLayerPoint: function (e) { // (MouseEvent)
return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
},
mouseEventToLatLng: function (e) { // (MouseEvent)
return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
},
// map initialization methods
_initContainer: function (id) {
var container = this._container = L.DomUtil.get(id);
if (!container) {
throw new Error('Map container not found.');
} else if (container._leaflet) {
throw new Error('Map container is already initialized.');
}
container._leaflet = true;
},
_initLayout: function () {
var container = this._container;
L.DomUtil.addClass(container, 'leaflet-container' +
(L.Browser.touch ? ' leaflet-touch' : '') +
(L.Browser.retina ? ' leaflet-retina' : '') +
(this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
var position = L.DomUtil.getStyle(container, 'position');
if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
container.style.position = 'relative';
}
this._initPanes();
if (this._initControlPos) {
this._initControlPos();
}
},
_initPanes: function () {
var panes = this._panes = {};
this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
panes.shadowPane = this._createPane('leaflet-shadow-pane');
panes.overlayPane = this._createPane('leaflet-overlay-pane');
panes.markerPane = this._createPane('leaflet-marker-pane');
panes.popupPane = this._createPane('leaflet-popup-pane');
var zoomHide = ' leaflet-zoom-hide';
if (!this.options.markerZoomAnimation) {
L.DomUtil.addClass(panes.markerPane, zoomHide);
L.DomUtil.addClass(panes.shadowPane, zoomHide);
L.DomUtil.addClass(panes.popupPane, zoomHide);
}
},
_createPane: function (className, container) {
return L.DomUtil.create('div', className, container || this._panes.objectsPane);
},
_clearPanes: function () {
this._container.removeChild(this._mapPane);
},
_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]);
}
},
// 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());
}
this._tileLayersToLoad = this._tileLayersNum;
var loading = !this._loaded;
this._loaded = true;
if (loading) {
this.fire('load');
this.eachLayer(this._layerAdd, this);
}
this.fire('viewreset', {hard: !preserveMapOffset});
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();
},
_updateZoomLevels: function () {
var i,
minZoom = Infinity,
maxZoom = -Infinity,
oldZoomSpan = this._getZoomSpan();
for (i in this._zoomBoundLayers) {
var layer = this._zoomBoundLayers[i];
if (!isNaN(layer.options.minZoom)) {
minZoom = Math.min(minZoom, layer.options.minZoom);
}
if (!isNaN(layer.options.maxZoom)) {
maxZoom = Math.max(maxZoom, layer.options.maxZoom);
}
}
if (i === undefined) { // we have no tilelayers
this._layersMaxZoom = this._layersMinZoom = undefined;
} else {
this._layersMaxZoom = maxZoom;
this._layersMinZoom = minZoom;
}
if (oldZoomSpan !== this._getZoomSpan()) {
this.fire('zoomlevelschange');
}
},
_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', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
'mouseleave', 'mousemove', 'contextmenu'],
i, len;
for (i = 0, len = events.length; i < len; i++) {
L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, 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(
this.invalidateSize, this, false, this._container);
},
_onMouseClick: function (e) {
// jshint camelcase: false
if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) || e._leaflet_stop) { return; }
this.fire('preclick');
this._fireMouseEvent(e);
},
_fireMouseEvent: function (e) {
// jshint camelcase: false
if (!this._loaded || e._leaflet_stop) { return; }
var type = e.type;
type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
if (!this.hasEventListeners(type)) { return; }
if (type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
var containerPoint = this.mouseEventToContainerPoint(e),
layerPoint = this.containerPointToLayerPoint(containerPoint),
latlng = this.layerPointToLatLng(layerPoint);
this.fire(type, {
latlng: latlng,
layerPoint: layerPoint,
containerPoint: containerPoint,
originalEvent: e
});
},
_onTileLayerLoad: function () {
this._tileLayersToLoad--;
if (this._tileLayersNum && !this._tileLayersToLoad) {
this.fire('tilelayersload');
}
},
_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, this);
} else {
this.on('load', callback, context);
}
return this;
},
_layerAdd: function (layer) {
layer.onAdd(this);
this.fire('layeradd', {layer: layer});
},
// 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());
},
_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);
};
/*
* 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 = {
MAX_LATITUDE: 85.0840591556,
R_MINOR: 6356752.314245179,
R_MAJOR: 6378137,
project: function (latlng) { // (LatLng) -> Point
var d = L.LatLng.DEG_TO_RAD,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
r = this.R_MAJOR,
r2 = this.R_MINOR,
x = latlng.lng * d * r,
y = lat * d,
tmp = r2 / r,
eccent = Math.sqrt(1.0 - tmp * tmp),
con = eccent * Math.sin(y);
con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
y = -r * Math.log(ts);
return new L.Point(x, y);
},
unproject: function (point) { // (Point, Boolean) -> LatLng
var d = L.LatLng.RAD_TO_DEG,
r = this.R_MAJOR,
r2 = this.R_MINOR,
lng = point.x * d / r,
tmp = r2 / r,
eccent = Math.sqrt(1 - (tmp * tmp)),
ts = Math.exp(- point.y / r),
phi = (Math.PI / 2) - 2 * Math.atan(ts),
numIter = 15,
tol = 1e-7,
i = numIter,
dphi = 0.1,
con;
while ((Math.abs(dphi) > tol) && (--i > 0)) {
con = eccent * Math.sin(phi);
dphi = (Math.PI / 2) - 2 * Math.atan(ts *
Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
phi += dphi;
}
return new L.LatLng(phi * d, lng);
}
};
L.CRS.EPSG3395 = L.extend({}, L.CRS, {
code: 'EPSG:3395',
projection: L.Projection.Mercator,
transformation: (function () {
var m = L.Projection.Mercator,
r = m.R_MAJOR,
r2 = m.R_MINOR;
return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
}())
});
/*
* L.TileLayer is used for standard xyz-numbered tile layers.
*/
L.TileLayer = L.Class.extend({
includes: L.Mixin.Events,
options: {
minZoom: 0,
maxZoom: 18,
tileSize: 256,
subdomains: 'abc',
errorTileUrl: '',
attribution: '',
zoomOffset: 0,
opacity: 1,
/* (undefined works too)
zIndex: null,
tms: false,
continuousWorld: false,
noWrap: false,
zoomReverse: false,
detectRetina: false,
reuseTiles: false,
bounds: false,
*/
unloadInvisibleTiles: L.Browser.mobile,
updateWhenIdle: L.Browser.mobile
},
initialize: function (url, options) {
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++;
if (options.minZoom > 0) {
options.minZoom--;
}
this.options.maxZoom--;
}
if (options.bounds) {
options.bounds = L.latLngBounds(options.bounds);
}
this._url = url;
var subdomains = this.options.subdomains;
if (typeof subdomains === 'string') {
this.options.subdomains = subdomains.split('');
}
},
onAdd: function (map) {
this._map = map;
this._animated = map._zoomAnimated;
// create a container div for tiles
this._initContainer();
// create an image to clone for tiles
this._createTileProto();
// set up events
map.on({
'viewreset': this._reset,
'moveend': this._update
}, this);
if (this._animated) {
map.on({
'zoomanim': this._animateZoom,
'zoomend': this._endZoomAnim
}, this);
}
if (!this.options.updateWhenIdle) {
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
map.on('move', this._limitedUpdate, this);
}
this._reset();
this._update();
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
this._container.parentNode.removeChild(this._container);
map.off({
'viewreset': this._reset,
'moveend': this._update
}, this);
if (this._animated) {
map.off({
'zoomanim': this._animateZoom,
'zoomend': this._endZoomAnim
}, this);
}
if (!this.options.updateWhenIdle) {
map.off('move', this._limitedUpdate, this);
}
this._container = null;
this._map = null;
},
bringToFront: function () {
var pane = this._map._panes.tilePane;
if (this._container) {
pane.appendChild(this._container);
this._setAutoZIndex(pane, Math.max);
}
return this;
},
bringToBack: function () {
var pane = this._map._panes.tilePane;
if (this._container) {
pane.insertBefore(this._container, pane.firstChild);
this._setAutoZIndex(pane, 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;
},
setUrl: function (url, noRedraw) {
this._url = url;
if (!noRedraw) {
this.redraw();
}
return this;
},
redraw: function () {
if (this._map) {
this._reset({hard: true});
this._update();
}
return this;
},
_updateZIndex: function () {
if (this._container && this.options.zIndex !== undefined) {
this._container.style.zIndex = this.options.zIndex;
}
},
_setAutoZIndex: function (pane, compare) {
var layers = pane.children,
edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
zIndex, i, len;
for (i = 0, len = layers.length; i < len; i++) {
if (layers[i] !== this._container) {
zIndex = parseInt(layers[i].style.zIndex, 10);
if (!isNaN(zIndex)) {
edgeZIndex = compare(edgeZIndex, zIndex);
}
}
}
this.options.zIndex = this._container.style.zIndex =
(isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
},
_updateOpacity: function () {
var i,
tiles = this._tiles;
if (L.Browser.ielt9) {
for (i in tiles) {
L.DomUtil.setOpacity(tiles[i], this.options.opacity);
}
} else {
L.DomUtil.setOpacity(this._container, this.options.opacity);
}
},
_initContainer: function () {
var tilePane = this._map._panes.tilePane;
if (!this._container) {
this._container = L.DomUtil.create('div', 'leaflet-layer');
this._updateZIndex();
if (this._animated) {
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);
} else {
this._tileContainer = this._container;
}
tilePane.appendChild(this._container);
if (this.options.opacity < 1) {
this._updateOpacity();
}
}
},
_reset: function (e) {
for (var key in this._tiles) {
this.fire('tileunload', {tile: this._tiles[key]});
}
this._tiles = {};
this._tilesToLoad = 0;
if (this.options.reuseTiles) {
this._unusedTiles = [];
}
this._tileContainer.innerHTML = '';
if (this._animated && e && e.hard) {
this._clearBgBuffer();
}
this._initContainer();
},
_update: function () {
if (!this._map) { return; }
var bounds = this._map.getPixelBounds(),
zoom = this._map.getZoom(),
tileSize = this.options.tileSize;
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
return;
}
var tileBounds = L.bounds(
bounds.min.divideBy(tileSize)._floor(),
bounds.max.divideBy(tileSize)._floor());
this._addTilesFromCenterOut(tileBounds);
if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
this._removeOtherTiles(tileBounds);
}
},
_addTilesFromCenterOut: function (bounds) {
var queue = [],
center = bounds.getCenter();
var j, i, point;
for (j = bounds.min.y; j <= bounds.max.y; j++) {
for (i = bounds.min.x; i <= bounds.max.x; i++) {
point = new L.Point(i, j);
if (this._tileShouldBeLoaded(point)) {
queue.push(point);
}
}
}
var tilesToLoad = queue.length;
if (tilesToLoad === 0) { return; }
// load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(center) - b.distanceTo(center);
});
var fragment = document.createDocumentFragment();
// if its the first batch of tiles to load
if (!this._tilesToLoad) {
this.fire('loading');
}
this._tilesToLoad += tilesToLoad;
for (i = 0; i < tilesToLoad; i++) {
this._addTile(queue[i], fragment);
}
this._tileContainer.appendChild(fragment);
},
_tileShouldBeLoaded: function (tilePoint) {
if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
return false; // already loaded
}
var options = this.options;
if (!options.continuousWorld) {
var limit = this._getWrapTileNum();
// don't load if exceeds world bounds
if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) ||
tilePoint.y < 0 || tilePoint.y >= limit) { return false; }
}
if (options.bounds) {
var tileSize = options.tileSize,
nwPoint = tilePoint.multiplyBy(tileSize),
sePoint = nwPoint.add([tileSize, tileSize]),
nw = this._map.unproject(nwPoint),
se = this._map.unproject(sePoint);
// TODO temporary hack, will be removed after refactoring projections
// https://github.com/Leaflet/Leaflet/issues/1618
if (!options.continuousWorld && !options.noWrap) {
nw = nw.wrap();
se = se.wrap();
}
if (!options.bounds.intersects([nw, se])) { return false; }
}
return true;
},
_removeOtherTiles: function (bounds) {
var kArr, x, y, key;
for (key in this._tiles) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
// remove tile if it's out of bounds
if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
this._removeTile(key);
}
}
},
_removeTile: function (key) {
var tile = this._tiles[key];
this.fire('tileunload', {tile: tile, url: tile.src});
if (this.options.reuseTiles) {
L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
this._unusedTiles.push(tile);
} else if (tile.parentNode === this._tileContainer) {
this._tileContainer.removeChild(tile);
}
// for https://github.com/CloudMade/Leaflet/issues/137
if (!L.Browser.android) {
tile.onload = null;
tile.src = L.Util.emptyImageUrl;
}
delete this._tiles[key];
},
_addTile: function (tilePoint, container) {
var tilePos = this._getTilePos(tilePoint);
// get unused tile - or create a new tile
var tile = this._getTile();
/*
Chrome 20 layouts much faster with top/left (verify with timeline, frames)
Android 4 browser has display issues with top/left and requires transform instead
Android 2 browser requires top/left or tiles disappear on load or first drag
(reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
(other browsers don't currently care) - see debug/hacks/jitter.html for an example
*/
L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
this._loadTile(tile, tilePoint);
if (tile.parentNode !== this._tileContainer) {
container.appendChild(tile);
}
},
_getZoomForUrl: function () {
var options = this.options,
zoom = this._map.getZoom();
if (options.zoomReverse) {
zoom = options.maxZoom - zoom;
}
return zoom + options.zoomOffset;
},
_getTilePos: function (tilePoint) {
var origin = this._map.getPixelOrigin(),
tileSize = this.options.tileSize;
return tilePoint.multiplyBy(tileSize).subtract(origin);
},
// image-specific code (override to implement e.g. Canvas or SVG tile layer)
getTileUrl: function (tilePoint) {
return L.Util.template(this._url, L.extend({
s: this._getSubdomain(tilePoint),
z: tilePoint.z,
x: tilePoint.x,
y: tilePoint.y
}, this.options));
},
_getWrapTileNum: function () {
// TODO refactor, limit is not valid for non-standard projections
return Math.pow(2, this._getZoomForUrl());
},
_adjustTilePoint: function (tilePoint) {
var limit = this._getWrapTileNum();
// wrap tile coordinates
if (!this.options.continuousWorld && !this.options.noWrap) {
tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
}
if (this.options.tms) {
tilePoint.y = limit - tilePoint.y - 1;
}
tilePoint.z = this._getZoomForUrl();
},
_getSubdomain: function (tilePoint) {
var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
return this.options.subdomains[index];
},
_createTileProto: function () {
var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
img.style.width = img.style.height = this.options.tileSize + 'px';
img.galleryimg = 'no';
},
_getTile: function () {
if (this.options.reuseTiles && this._unusedTiles.length > 0) {
var tile = this._unusedTiles.pop();
this._resetTile(tile);
return tile;
}
return this._createTile();
},
// Override if data stored on a tile needs to be cleaned up before reuse
_resetTile: function (/*tile*/) {},
_createTile: function () {
var tile = this._tileImg.cloneNode(false);
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
if (L.Browser.ielt9 && this.options.opacity !== undefined) {
L.DomUtil.setOpacity(tile, this.options.opacity);
}
return tile;
},
_loadTile: function (tile, tilePoint) {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
this._adjustTilePoint(tilePoint);
tile.src = this.getTileUrl(tilePoint);
},
_tileLoaded: function () {
this._tilesToLoad--;
if (!this._tilesToLoad) {
this.fire('load');
if (this._animated) {
// clear scaled tiles after all new tiles are loaded (for performance)
clearTimeout(this._clearBgBufferTimer);
this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
}
}
},
_tileOnLoad: function () {
var layer = this._layer;
//Only if we are loading an actual image
if (this.src !== L.Util.emptyImageUrl) {
L.DomUtil.addClass(this, 'leaflet-tile-loaded');
layer.fire('tileload', {
tile: this,
url: this.src
});
}
layer._tileLoaded();
},
_tileOnError: function () {
var layer = this._layer;
layer.fire('tileerror', {
tile: this,
url: this.src
});
var newUrl = layer.options.errorTileUrl;
if (newUrl) {
this.src = newUrl;
}
layer._tileLoaded();
}
});
L.tileLayer = function (url, options) {
return new L.TileLayer(url, options);
};
/*
* L.TileLayer.WMS is used for putting WMS tile layers on the map.
*/
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) { // (String, Object)
this._url = url;
var wmsParams = L.extend({}, this.defaultWmsParams),
tileSize = options.tileSize || this.options.tileSize;
if (options.detectRetina && L.Browser.retina) {
wmsParams.width = wmsParams.height = tileSize * 2;
} else {
wmsParams.width = wmsParams.height = tileSize;
}
for (var i in options) {
// all keys that are not TileLayer options go to WMS params
if (!this.options.hasOwnProperty(i) && i !== 'crs') {
wmsParams[i] = options[i];
}
}
this.wmsParams = wmsParams;
L.setOptions(this, options);
},
onAdd: function (map) {
this._crs = this.options.crs || map.options.crs;
var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
this.wmsParams[projectionKey] = this._crs.code;
L.TileLayer.prototype.onAdd.call(this, map);
},
getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
var map = this._map,
tileSize = this.options.tileSize,
nwPoint = tilePoint.multiplyBy(tileSize),
sePoint = nwPoint.add([tileSize, tileSize]),
nw = this._crs.project(map.unproject(nwPoint, zoom)),
se = this._crs.project(map.unproject(sePoint, zoom)),
bbox = [nw.x, se.y, se.x, nw.y].join(','),
url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
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.TileLayer.Canvas is a class that you can use as a base for creating
* dynamically drawn Canvas-based tile layers.
*/
L.TileLayer.Canvas = L.TileLayer.extend({
options: {
async: false
},
initialize: function (options) {
L.setOptions(this, options);
},
redraw: function () {
for (var i in this._tiles) {
this._redrawTile(this._tiles[i]);
}
return this;
},
_redrawTile: function (tile) {
this.drawTile(tile, tile._tilePoint, this._map._zoom);
},
_createTileProto: function () {
var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
proto.width = proto.height = this.options.tileSize;
},
_createTile: function () {
var tile = this._canvasProto.cloneNode(false);
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
return tile;
},
_loadTile: function (tile, tilePoint) {
tile._layer = this;
tile._tilePoint = tilePoint;
this._redrawTile(tile);
if (!this.options.async) {
this.tileDrawn(tile);
}
},
drawTile: function (/*tile, tilePoint*/) {
// override with rendering code
},
tileDrawn: function (tile) {
this._tileOnLoad.call(tile);
}
});
L.tileLayer.canvas = function (options) {
return new L.TileLayer.Canvas(options);
};
/*
* L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
*/
L.ImageOverlay = L.Class.extend({
includes: L.Mixin.Events,
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 (map) {
this._map = map;
if (!this._image) {
this._initImage();
}
map._panes.overlayPane.appendChild(this._image);
map.on('viewreset', this._reset, this);
if (map.options.zoomAnimation && L.Browser.any3d) {
map.on('zoomanim', this._animateZoom, this);
}
this._reset();
},
onRemove: function (map) {
map.getPanes().overlayPane.removeChild(this._image);
map.off('viewreset', this._reset, this);
if (map.options.zoomAnimation) {
map.off('zoomanim', this._animateZoom, this);
}
},
addTo: function (map) {
map.addLayer(this);
return this;
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
},
// TODO remove bringToFront/bringToBack duplication from TileLayer/Path
bringToFront: function () {
if (this._image) {
this._map._panes.overlayPane.appendChild(this._image);
}
return this;
},
bringToBack: function () {
var pane = this._map._panes.overlayPane;
if (this._image) {
pane.insertBefore(this._image, pane.firstChild);
}
return this;
},
_initImage: function () {
this._image = L.DomUtil.create('img', 'leaflet-image-layer');
if (this._map.options.zoomAnimation && L.Browser.any3d) {
L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
} else {
L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
}
this._updateOpacity();
//TODO createImage util method to remove duplication
L.extend(this._image, {
galleryimg: 'no',
onselectstart: L.Util.falseFn,
onmousemove: L.Util.falseFn,
onload: L.bind(this._onImageLoad, this),
src: this._url
});
},
_animateZoom: function (e) {
var map = this._map,
image = this._image,
scale = map.getZoomScale(e.zoom),
nw = this._bounds.getNorthWest(),
se = this._bounds.getSouthEast(),
topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
image.style[L.DomUtil.TRANSFORM] =
L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
},
_reset: function () {
var image = this._image,
topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
L.DomUtil.setPosition(image, topLeft);
image.style.width = size.x + 'px';
image.style.height = size.y + 'px';
},
_onImageLoad: function () {
this.fire('load');
},
_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: ''
},
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;
if (!oldIcon || oldIcon.tagName !== 'IMG') {
img = this._createImg(src);
} else {
img = this._createImg(src, oldIcon);
}
this._setIconStyles(img, name);
return img;
},
_setIconStyles: function (img, name) {
var options = this.options,
size = L.point(options[name + 'Size']),
anchor;
if (name === 'shadow') {
anchor = L.point(options.shadowAnchor || options.iconAnchor);
} else {
anchor = L.point(options.iconAnchor);
}
if (!anchor && size) {
anchor = 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) {
if (!L.Browser.ie6) {
if (!el) {
el = document.createElement('img');
}
el.src = src;
} else {
if (!el) {
el = document.createElement('div');
}
el.style.filter =
'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
}
return el;
},
_getIconUrl: function (name) {
if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
return this.options[name + 'RetinaUrl'];
}
return 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];
}
if (L.Browser.retina && name === 'icon') {
name += '-2x';
}
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 + '.png';
}
});
L.Icon.Default.imagePath = (function () {
var scripts = document.getElementsByTagName('script'),
leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
var i, len, src, matches, path;
for (i = 0, len = scripts.length; i < len; i++) {
src = scripts[i].src;
matches = src.match(leafletRe);
if (matches) {
path = src.split(leafletRe)[0];
return (path ? path + '/' : '') + 'images';
}
}
}());
/*
* L.Marker is used to display clickable/draggable icons on the map.
*/
L.Marker = L.Class.extend({
includes: L.Mixin.Events,
options: {
icon: new L.Icon.Default(),
title: '',
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._map = map;
map.on('viewreset', this.update, this);
this._initIcon();
this.update();
if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
map.on('zoomanim', this._animateZoom, this);
}
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
if (this.dragging) {
this.dragging.disable();
}
this._removeIcon();
this._removeShadow();
this.fire('remove');
map.off({
'viewreset': this.update,
'zoomanim': this._animateZoom
}, this);
this._map = null;
},
getLatLng: function () {
return this._latlng;
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this.update();
return this.fire('move', { latlng: this._latlng });
},
setZIndexOffset: function (offset) {
this.options.zIndexOffset = offset;
this.update();
return this;
},
setIcon: function (icon) {
this.options.icon = icon;
if (this._map) {
this._initIcon();
this.update();
}
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,
map = this._map,
animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-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;
}
}
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, this)
.on(icon, '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();
}
var panes = this._map._panes;
if (addIcon) {
panes.markerPane.appendChild(this._icon);
}
if (newShadow && addShadow) {
panes.shadowPane.appendChild(this._shadow);
}
},
_removeIcon: function () {
if (this.options.riseOnHover) {
L.DomEvent
.off(this._icon, 'mouseover', this._bringToFront)
.off(this._icon, 'mouseout', this._resetZIndex);
}
this._map._panes.markerPane.removeChild(this._icon);
this._icon = null;
},
_removeShadow: function () {
if (this._shadow) {
this._map._panes.shadowPane.removeChild(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);
this._setPos(pos);
},
_initInteraction: function () {
if (!this.options.clickable) { return; }
// TODO refactor into something shared with Map/Path/etc. to DRY it up
var icon = this._icon,
events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
L.DomUtil.addClass(icon, 'leaflet-clickable');
L.DomEvent.on(icon, 'click', this._onMouseClick, this);
L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
for (var i = 0; i < events.length; i++) {
L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
}
if (L.Handler.MarkerDrag) {
this.dragging = new L.Handler.MarkerDrag(this);
if (this.options.draggable) {
this.dragging.enable();
}
}
},
_onMouseClick: function (e) {
var wasDragged = this.dragging && this.dragging.moved();
if (this.hasEventListeners(e.type) || wasDragged) {
L.DomEvent.stopPropagation(e);
}
if (wasDragged) { return; }
if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
this.fire(e.type, {
originalEvent: e,
latlng: this._latlng
});
},
_onKeyPress: function (e) {
if (e.keyCode === 13) {
this.fire('click', {
originalEvent: e,
latlng: this._latlng
});
}
},
_fireMouseEvent: function (e) {
this.fire(e.type, {
originalEvent: e,
latlng: this._latlng
});
// TODO proper custom event propagation
// this line will always be called if marker is in a FeatureGroup
if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
L.DomEvent.preventDefault(e);
}
if (e.type !== 'mousedown') {
L.DomEvent.stopPropagation(e);
} else {
L.DomEvent.preventDefault(e);
}
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
if (this._map) {
this._updateOpacity();
}
},
_updateOpacity: function () {
L.DomUtil.setOpacity(this._icon, this.options.opacity);
if (this._shadow) {
L.DomUtil.setOpacity(this._shadow, this.options.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;
if (options.html !== false) {
div.innerHTML = options.html;
} else {
div.innerHTML = '';
}
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.Class.extend({
includes: L.Mixin.Events,
options: {
minWidth: 50,
maxWidth: 300,
maxHeight: null,
autoPan: true,
closeButton: true,
offset: [0, 7],
autoPanPadding: [5, 5],
keepInView: false,
className: '',
zoomAnimation: true
},
initialize: function (options, source) {
L.setOptions(this, options);
this._source = source;
this._animated = L.Browser.any3d && this.options.zoomAnimation;
this._isOpen = false;
},
onAdd: function (map) {
this._map = map;
if (!this._container) {
this._initLayout();
}
this._updateContent();
var animFade = map.options.fadeAnimation;
if (animFade) {
L.DomUtil.setOpacity(this._container, 0);
}
map._panes.popupPane.appendChild(this._container);
map.on(this._getEvents(), this);
this._update();
if (animFade) {
L.DomUtil.setOpacity(this._container, 1);
}
this.fire('open');
map.fire('popupopen', {popup: this});
if (this._source) {
this._source.fire('popupopen', {popup: this});
}
},
addTo: function (map) {
map.addLayer(this);
return this;
},
openOn: function (map) {
map.openPopup(this);
return this;
},
onRemove: function (map) {
map._panes.popupPane.removeChild(this._container);
L.Util.falseFn(this._container.offsetWidth); // force reflow
map.off(this._getEvents(), this);
if (map.options.fadeAnimation) {
L.DomUtil.setOpacity(this._container, 0);
}
this._map = null;
this.fire('close');
map.fire('popupclose', {popup: this});
if (this._source) {
this._source.fire('popupclose', {popup: this});
}
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this._update();
return this;
},
setContent: function (content) {
this._content = content;
this._update();
return this;
},
_getEvents: function () {
var events = {
viewreset: this._updatePosition
};
if (this._animated) {
events.zoomanim = this._zoomAnimation;
}
if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this._close;
}
if (this.options.keepInView) {
events.moveend = this._adjustPan;
}
return events;
},
_close: function () {
if (this._map) {
this._map.closePopup(this);
}
},
_initLayout: function () {
var prefix = 'leaflet-popup',
containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
(this._animated ? 'animated' : 'hide'),
container = this._container = L.DomUtil.create('div', containerClass),
closeButton;
if (this.options.closeButton) {
closeButton = this._closeButton =
L.DomUtil.create('a', prefix + '-close-button', container);
closeButton.href = '#close';
closeButton.innerHTML = '&#215;';
L.DomEvent.disableClickPropagation(closeButton);
L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
}
var wrapper = this._wrapper =
L.DomUtil.create('div', prefix + '-content-wrapper', container);
L.DomEvent.disableClickPropagation(wrapper);
this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
L.DomEvent.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);
},
_update: function () {
if (!this._map) { return; }
this._container.style.visibility = 'hidden';
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = '';
this._adjustPan();
},
_updateContent: function () {
if (!this._content) { return; }
if (typeof this._content === 'string') {
this._contentNode.innerHTML = this._content;
} else {
while (this._contentNode.hasChildNodes()) {
this._contentNode.removeChild(this._contentNode.firstChild);
}
this._contentNode.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),
animated = this._animated,
offset = L.point(this.options.offset);
if (animated) {
L.DomUtil.setPosition(this._container, pos);
}
this._containerBottom = -offset.y - (animated ? 0 : pos.y);
this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
// bottom position the popup in case the height of the popup changes (images loading etc)
this._container.style.bottom = this._containerBottom + 'px';
this._container.style.left = this._containerLeft + 'px';
},
_zoomAnimation: function (opt) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.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._animated) {
layerPos._add(L.DomUtil.getPosition(this._container));
}
var containerPos = map.layerPointToContainerPoint(layerPos),
padding = L.point(this.options.autoPanPadding),
size = map.getSize(),
dx = 0,
dy = 0;
if (containerPos.x + containerWidth > size.x) { // right
dx = containerPos.x + containerWidth - size.x + padding.x;
}
if (containerPos.x - dx < 0) { // left
dx = containerPos.x - padding.x;
}
if (containerPos.y + containerHeight > size.y) { // bottom
dy = containerPos.y + containerHeight - size.y + padding.y;
}
if (containerPos.y - dy < 0) { // top
dy = containerPos.y - padding.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])
this.closePopup();
if (!(popup instanceof L.Popup)) {
var content = popup;
popup = new L.Popup(options)
.setLatLng(latlng)
.setContent(content);
}
popup._isOpen = true;
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);
popup._isOpen = false;
}
return this;
}
});
/*
* Popup extension to L.Marker, adding popup-related methods.
*/
L.Marker.include({
openPopup: function () {
if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
this._popup.setLatLng(this._latlng);
this._map.openPopup(this._popup);
}
return this;
},
closePopup: function () {
if (this._popup) {
this._popup._close();
}
return this;
},
togglePopup: function () {
if (this._popup) {
if (this._popup._isOpen) {
this.closePopup();
} else {
this.openPopup();
}
}
return this;
},
bindPopup: function (content, options) {
var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
anchor = anchor.add(L.Popup.prototype.options.offset);
if (options && options.offset) {
anchor = anchor.add(options.offset);
}
options = L.extend({offset: anchor}, options);
if (!this._popup) {
this
.on('click', this.togglePopup, this)
.on('remove', this.closePopup, this)
.on('move', this._movePopup, this);
}
if (content instanceof L.Popup) {
L.setOptions(content, options);
this._popup = content;
} else {
this._popup = new L.Popup(options, this)
.setContent(content);
}
return this;
},
setPopupContent: function (content) {
if (this._popup) {
this._popup.setContent(content);
}
return this;
},
unbindPopup: function () {
if (this._popup) {
this._popup = null;
this
.off('click', this.togglePopup)
.off('remove', this.closePopup)
.off('move', this._movePopup);
}
return this;
},
_movePopup: function (e) {
this._popup.setLatLng(e.latlng);
}
});
/*
* 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.Class.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) {
if (!layer) { return false; }
return (layer in this._layers || this.getLayerId(layer) in this._layers);
},
clearLayers: function () {
this.eachLayer(this.removeLayer, this);
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) {
this._map = map;
this.eachLayer(map.addLayer, map);
},
onRemove: function (map) {
this.eachLayer(map.removeLayer, map);
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
},
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({
includes: L.Mixin.Events,
statics: {
EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
},
addLayer: function (layer) {
if (this.hasLayer(layer)) {
return this;
}
layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, 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 (layer in this._layers) {
layer = this._layers[layer];
}
layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, 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);
},
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 instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
});
return bounds;
},
_propagateEvent: function (e) {
if (!e.layer) {
e.layer = e.target;
}
e.target = this;
this.fire(e.type, e);
}
});
L.featureGroup = function (layers) {
return new L.FeatureGroup(layers);
};
/*
* L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
*/
L.Path = L.Class.extend({
includes: [L.Mixin.Events],
statics: {
// how much to extend the clip area around the map view
// (relative to its size, e.g. 0.5 is half the screen in each direction)
// set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
CLIP_PADDING: L.Browser.mobile ?
Math.max(0, Math.min(0.5,
(1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
},
options: {
stroke: true,
color: '#0033ff',
dashArray: null,
weight: 5,
opacity: 0.5,
fill: false,
fillColor: null, //same as color by default
fillOpacity: 0.2,
clickable: true
},
initialize: function (options) {
L.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
if (!this._container) {
this._initElements();
this._initEvents();
}
this.projectLatlngs();
this._updatePath();
if (this._container) {
this._map._pathRoot.appendChild(this._container);
}
this.fire('add');
map.on({
'viewreset': this.projectLatlngs,
'moveend': this._updatePath
}, this);
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
map._pathRoot.removeChild(this._container);
// Need to fire remove event before we set _map to null as the event hooks might need the object
this.fire('remove');
this._map = null;
if (L.Browser.vml) {
this._container = null;
this._stroke = null;
this._fill = null;
}
map.off({
'viewreset': this.projectLatlngs,
'moveend': this._updatePath
}, this);
},
projectLatlngs: function () {
// do all projection stuff here
},
setStyle: function (style) {
L.setOptions(this, style);
if (this._container) {
this._updateStyle();
}
return this;
},
redraw: function () {
if (this._map) {
this.projectLatlngs();
this._updatePath();
}
return this;
}
});
L.Map.include({
_updatePathViewport: function () {
var p = L.Path.CLIP_PADDING,
size = this.getSize(),
panePos = L.DomUtil.getPosition(this._mapPane),
min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
max = min.add(size.multiplyBy(1 + p * 2)._round());
this._pathViewport = new L.Bounds(min, max);
}
});
/*
* Extends L.Path with SVG-specific rendering code.
*/
L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
L.Path = L.Path.extend({
statics: {
SVG: L.Browser.svg
},
bringToFront: function () {
var root = this._map._pathRoot,
path = this._container;
if (path && root.lastChild !== path) {
root.appendChild(path);
}
return this;
},
bringToBack: function () {
var root = this._map._pathRoot,
path = this._container,
first = root.firstChild;
if (path && first !== path) {
root.insertBefore(path, first);
}
return this;
},
getPathString: function () {
// form path string here
},
_createElement: function (name) {
return document.createElementNS(L.Path.SVG_NS, name);
},
_initElements: function () {
this._map._initPathRoot();
this._initPath();
this._initStyle();
},
_initPath: function () {
this._container = this._createElement('g');
this._path = this._createElement('path');
this._container.appendChild(this._path);
},
_initStyle: function () {
if (this.options.stroke) {
this._path.setAttribute('stroke-linejoin', 'round');
this._path.setAttribute('stroke-linecap', 'round');
}
if (this.options.fill) {
this._path.setAttribute('fill-rule', 'evenodd');
}
if (this.options.pointerEvents) {
this._path.setAttribute('pointer-events', this.options.pointerEvents);
}
if (!this.options.clickable && !this.options.pointerEvents) {
this._path.setAttribute('pointer-events', 'none');
}
this._updateStyle();
},
_updateStyle: function () {
if (this.options.stroke) {
this._path.setAttribute('stroke', this.options.color);
this._path.setAttribute('stroke-opacity', this.options.opacity);
this._path.setAttribute('stroke-width', this.options.weight);
if (this.options.dashArray) {
this._path.setAttribute('stroke-dasharray', this.options.dashArray);
} else {
this._path.removeAttribute('stroke-dasharray');
}
} else {
this._path.setAttribute('stroke', 'none');
}
if (this.options.fill) {
this._path.setAttribute('fill', this.options.fillColor || this.options.color);
this._path.setAttribute('fill-opacity', this.options.fillOpacity);
} else {
this._path.setAttribute('fill', 'none');
}
},
_updatePath: function () {
var str = this.getPathString();
if (!str) {
// fix webkit empty string parsing bug
str = 'M0 0';
}
this._path.setAttribute('d', str);
},
// TODO remove duplication with L.Map
_initEvents: function () {
if (this.options.clickable) {
if (L.Browser.svg || !L.Browser.vml) {
this._path.setAttribute('class', 'leaflet-clickable');
}
L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseover',
'mouseout', 'mousemove', 'contextmenu'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
}
}
},
_onMouseClick: function (e) {
if (this._map.dragging && this._map.dragging.moved()) { return; }
this._fireMouseEvent(e);
},
_fireMouseEvent: function (e) {
if (!this.hasEventListeners(e.type)) { return; }
var map = this._map,
containerPoint = map.mouseEventToContainerPoint(e),
layerPoint = map.containerPointToLayerPoint(containerPoint),
latlng = map.layerPointToLatLng(layerPoint);
this.fire(e.type, {
latlng: latlng,
layerPoint: layerPoint,
containerPoint: containerPoint,
originalEvent: e
});
if (e.type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
if (e.type !== 'mousemove') {
L.DomEvent.stopPropagation(e);
}
}
});
L.Map.include({
_initPathRoot: function () {
if (!this._pathRoot) {
this._pathRoot = L.Path.prototype._createElement('svg');
this._panes.overlayPane.appendChild(this._pathRoot);
if (this.options.zoomAnimation && L.Browser.any3d) {
this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
this.on({
'zoomanim': this._animatePathZoom,
'zoomend': this._endPathZoom
});
} else {
this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
}
this.on('moveend', this._updateSvgViewport);
this._updateSvgViewport();
}
},
_animatePathZoom: function (e) {
var scale = this.getZoomScale(e.zoom),
offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
this._pathRoot.style[L.DomUtil.TRANSFORM] =
L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
this._pathZooming = true;
},
_endPathZoom: function () {
this._pathZooming = false;
},
_updateSvgViewport: function () {
if (this._pathZooming) {
// Do not update SVGs while a zoom animation is going on otherwise the animation will break.
// When the zoom animation ends we will be updated again anyway
// This fixes the case where you do a momentum move and zoom while the move is still ongoing.
return;
}
this._updatePathViewport();
var vp = this._pathViewport,
min = vp.min,
max = vp.max,
width = max.x - min.x,
height = max.y - min.y,
root = this._pathRoot,
pane = this._panes.overlayPane;
// Hack to make flicker on drag end on mobile webkit less irritating
if (L.Browser.mobileWebkit) {
pane.removeChild(root);
}
L.DomUtil.setPosition(root, min);
root.setAttribute('width', width);
root.setAttribute('height', height);
root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
if (L.Browser.mobileWebkit) {
pane.appendChild(root);
}
}
});
/*
* Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
*/
L.Path.include({
bindPopup: function (content, options) {
if (content instanceof L.Popup) {
this._popup = content;
} else {
if (!this._popup || options) {
this._popup = new L.Popup(options, this);
}
this._popup.setContent(content);
}
if (!this._popupHandlersAdded) {
this
.on('click', this._openPopup, this)
.on('remove', this.closePopup, this);
this._popupHandlersAdded = true;
}
return this;
},
unbindPopup: function () {
if (this._popup) {
this._popup = null;
this
.off('click', this._openPopup)
.off('remove', this.closePopup);
this._popupHandlersAdded = false;
}
return this;
},
openPopup: function (latlng) {
if (this._popup) {
// open the popup from one of the path's points if not specified
latlng = latlng || this._latlng ||
this._latlngs[Math.floor(this._latlngs.length / 2)];
this._openPopup({latlng: latlng});
}
return this;
},
closePopup: function () {
if (this._popup) {
this._popup._close();
}
return this;
},
_openPopup: function (e) {
this._popup.setLatLng(e.latlng);
this._map.openPopup(this._popup);
}
});
/*
* Vector rendering for IE6-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 = '<v:shape adj="1"/>';
var shape = div.firstChild;
shape.style.behavior = 'url(#default#VML)';
return shape && (typeof shape.adj === 'object');
} catch (e) {
return false;
}
}());
L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
statics: {
VML: true,
CLIP_PADDING: 0.02
},
_createElement: (function () {
try {
document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
return function (name) {
return document.createElement('<lvml:' + name + ' class="lvml">');
};
} catch (e) {
return function (name) {
return document.createElement(
'<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
};
}
}()),
_initPath: function () {
var container = this._container = this._createElement('shape');
L.DomUtil.addClass(container, 'leaflet-vml-shape');
if (this.options.clickable) {
L.DomUtil.addClass(container, 'leaflet-clickable');
}
container.coordsize = '1 1';
this._path = this._createElement('path');
container.appendChild(this._path);
this._map._pathRoot.appendChild(container);
},
_initStyle: function () {
this._updateStyle();
},
_updateStyle: function () {
var stroke = this._stroke,
fill = this._fill,
options = this.options,
container = this._container;
container.stroked = options.stroke;
container.filled = options.fill;
if (options.stroke) {
if (!stroke) {
stroke = this._stroke = this._createElement('stroke');
stroke.endcap = 'round';
container.appendChild(stroke);
}
stroke.weight = options.weight + 'px';
stroke.color = options.color;
stroke.opacity = options.opacity;
if (options.dashArray) {
stroke.dashStyle = options.dashArray instanceof Array ?
options.dashArray.join(' ') :
options.dashArray.replace(/( *, *)/g, ' ');
} else {
stroke.dashStyle = '';
}
} else if (stroke) {
container.removeChild(stroke);
this._stroke = null;
}
if (options.fill) {
if (!fill) {
fill = this._fill = this._createElement('fill');
container.appendChild(fill);
}
fill.color = options.fillColor || options.color;
fill.opacity = options.fillOpacity;
} else if (fill) {
container.removeChild(fill);
this._fill = null;
}
},
_updatePath: function () {
var style = this._container.style;
style.display = 'none';
this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
style.display = '';
}
});
L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
_initPathRoot: function () {
if (this._pathRoot) { return; }
var root = this._pathRoot = document.createElement('div');
root.className = 'leaflet-vml-container';
this._panes.overlayPane.appendChild(root);
this.on('moveend', this._updatePathViewport);
this._updatePathViewport();
}
});
/*
* Vector rendering for all browsers that support canvas.
*/
L.Browser.canvas = (function () {
return !!document.createElement('canvas').getContext;
}());
L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
statics: {
//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
CANVAS: true,
SVG: false
},
redraw: function () {
if (this._map) {
this.projectLatlngs();
this._requestUpdate();
}
return this;
},
setStyle: function (style) {
L.setOptions(this, style);
if (this._map) {
this._updateStyle();
this._requestUpdate();
}
return this;
},
onRemove: function (map) {
map
.off('viewreset', this.projectLatlngs, this)
.off('moveend', this._updatePath, this);
if (this.options.clickable) {
this._map.off('click', this._onClick, this);
this._map.off('mousemove', this._onMouseMove, this);
}
this._requestUpdate();
this._map = null;
},
_requestUpdate: function () {
if (this._map && !L.Path._updateRequest) {
L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
}
},
_fireMapMoveEnd: function () {
L.Path._updateRequest = null;
this.fire('moveend');
},
_initElements: function () {
this._map._initPathRoot();
this._ctx = this._map._canvasCtx;
},
_updateStyle: function () {
var options = this.options;
if (options.stroke) {
this._ctx.lineWidth = options.weight;
this._ctx.strokeStyle = options.color;
}
if (options.fill) {
this._ctx.fillStyle = options.fillColor || options.color;
}
},
_drawPath: function () {
var i, j, len, len2, point, drawMethod;
this._ctx.beginPath();
for (i = 0, len = this._parts.length; i < len; i++) {
for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
point = this._parts[i][j];
drawMethod = (j === 0 ? 'move' : 'line') + 'To';
this._ctx[drawMethod](point.x, point.y);
}
// TODO refactor ugly hack
if (this instanceof L.Polygon) {
this._ctx.closePath();
}
}
},
_checkIfEmpty: function () {
return !this._parts.length;
},
_updatePath: function () {
if (this._checkIfEmpty()) { return; }
var ctx = this._ctx,
options = this.options;
this._drawPath();
ctx.save();
this._updateStyle();
if (options.fill) {
ctx.globalAlpha = options.fillOpacity;
ctx.fill();
}
if (options.stroke) {
ctx.globalAlpha = options.opacity;
ctx.stroke();
}
ctx.restore();
// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
},
_initEvents: function () {
if (this.options.clickable) {
// TODO dblclick
this._map.on('mousemove', this._onMouseMove, this);
this._map.on('click', this._onClick, this);
}
},
_onClick: function (e) {
if (this._containsPoint(e.layerPoint)) {
this.fire('click', e);
}
},
_onMouseMove: function (e) {
if (!this._map || this._map._animatingZoom) { return; }
// TODO don't do on each move
if (this._containsPoint(e.layerPoint)) {
this._ctx.canvas.style.cursor = 'pointer';
this._mouseInside = true;
this.fire('mouseover', e);
} else if (this._mouseInside) {
this._ctx.canvas.style.cursor = '';
this._mouseInside = false;
this.fire('mouseout', e);
}
}
});
L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
_initPathRoot: function () {
var root = this._pathRoot,
ctx;
if (!root) {
root = this._pathRoot = document.createElement('canvas');
root.style.position = 'absolute';
ctx = this._canvasCtx = root.getContext('2d');
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
this._panes.overlayPane.appendChild(root);
if (this.options.zoomAnimation) {
this._pathRoot.className = 'leaflet-zoom-animated';
this.on('zoomanim', this._animatePathZoom);
this.on('zoomend', this._endPathZoom);
}
this.on('moveend', this._updateCanvasViewport);
this._updateCanvasViewport();
}
},
_updateCanvasViewport: function () {
// don't redraw while zooming. See _updateSvgViewport for more details
if (this._pathZooming) { return; }
this._updatePathViewport();
var vp = this._pathViewport,
min = vp.min,
size = vp.max.subtract(min),
root = this._pathRoot;
//TODO check if this works properly on mobile webkit
L.DomUtil.setPosition(root, min);
root.width = size.x;
root.height = size.y;
root.getContext('2d').translate(-min.x, -min.y);
}
});
/*
* L.LineUtil contains different utility functions for line segments
* and polylines (clipping, simplification, distances, etc.)
*/
/*jshint bitwise:false */ // allow bitwise oprations 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;
if (code & 8) { // top
return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
} else if (code & 4) { // bottom
return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
} else if (code & 2) { // right
return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
} else if (code & 1) { // left
return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
}
},
_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 is used to display polylines on a map.
*/
L.Polyline = L.Path.extend({
initialize: function (latlngs, options) {
L.Path.prototype.initialize.call(this, options);
this._latlngs = this._convertLatLngs(latlngs);
},
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
},
projectLatlngs: function () {
this._originalPoints = [];
for (var i = 0, len = this._latlngs.length; i < len; i++) {
this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
}
},
getPathString: function () {
for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
str += this._getPathPartStr(this._parts[i]);
}
return str;
},
getLatLngs: function () {
return this._latlngs;
},
setLatLngs: function (latlngs) {
this._latlngs = this._convertLatLngs(latlngs);
return this.redraw();
},
addLatLng: function (latlng) {
this._latlngs.push(L.latLng(latlng));
return this.redraw();
},
spliceLatLngs: function () { // (Number index, Number howMany)
var removed = [].splice.apply(this._latlngs, arguments);
this._convertLatLngs(this._latlngs, true);
this.redraw();
return removed;
},
closestLayerPoint: function (p) {
var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
for (var j = 0, jLen = parts.length; j < jLen; j++) {
var points = parts[j];
for (var i = 1, len = points.length; i < len; i++) {
p1 = points[i - 1];
p2 = points[i];
var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
if (sqDist < minDistance) {
minDistance = sqDist;
minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
}
}
}
if (minPoint) {
minPoint.distance = Math.sqrt(minDistance);
}
return minPoint;
},
getBounds: function () {
return new L.LatLngBounds(this.getLatLngs());
},
_convertLatLngs: function (latlngs, overwrite) {
var i, len, target = overwrite ? latlngs : [];
for (i = 0, len = latlngs.length; i < len; i++) {
if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
return;
}
target[i] = L.latLng(latlngs[i]);
}
return target;
},
_initEvents: function () {
L.Path.prototype._initEvents.call(this);
},
_getPathPartStr: function (points) {
var round = L.Path.VML;
for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
p = points[j];
if (round) {
p._round();
}
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
return str;
},
_clipPoints: function () {
var points = this._originalPoints,
len = points.length,
i, k, segment;
if (this.options.noClip) {
this._parts = [points];
return;
}
this._parts = [];
var parts = this._parts,
vp = this._map._pathViewport,
lu = L.LineUtil;
for (i = 0, k = 0; i < len - 1; i++) {
segment = lu.clipSegment(points[i], points[i + 1], vp, i);
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[i + 1]) || (i === len - 2)) {
parts[k].push(segment[1]);
k++;
}
}
},
// simplify each clipped part of the polyline
_simplifyPoints: function () {
var parts = this._parts,
lu = L.LineUtil;
for (var i = 0, len = parts.length; i < len; i++) {
parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
}
},
_updatePath: function () {
if (!this._map) { return; }
this._clipPoints();
this._simplifyPoints();
L.Path.prototype._updatePath.call(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 is used to display polygons on a map.
*/
L.Polygon = L.Polyline.extend({
options: {
fill: true
},
initialize: function (latlngs, options) {
var i, len, hole;
L.Polyline.prototype.initialize.call(this, latlngs, options);
if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
this._latlngs = this._convertLatLngs(latlngs[0]);
this._holes = latlngs.slice(1);
for (i = 0, len = this._holes.length; i < len; i++) {
hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
if (hole[0].equals(hole[hole.length - 1])) {
hole.pop();
}
}
}
// filter out last point if its equal to the first one
latlngs = this._latlngs;
if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
latlngs.pop();
}
},
projectLatlngs: function () {
L.Polyline.prototype.projectLatlngs.call(this);
// project polygon holes points
// TODO move this logic to Polyline to get rid of duplication
this._holePoints = [];
if (!this._holes) { return; }
var i, j, len, len2;
for (i = 0, len = this._holes.length; i < len; i++) {
this._holePoints[i] = [];
for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
}
}
},
_clipPoints: function () {
var points = this._originalPoints,
newParts = [];
this._parts = [points].concat(this._holePoints);
if (this.options.noClip) { return; }
for (var i = 0, len = this._parts.length; i < len; i++) {
var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
if (clipped.length) {
newParts.push(clipped);
}
}
this._parts = newParts;
},
_getPathPartStr: function (points) {
var str = L.Polyline.prototype._getPathPartStr.call(this, points);
return str + (L.Browser.svg ? 'z' : 'x');
}
});
L.polygon = function (latlngs, options) {
return new L.Polygon(latlngs, options);
};
/*
* Contains L.MultiPolyline and L.MultiPolygon layers.
*/
(function () {
function createMulti(Klass) {
return L.FeatureGroup.extend({
initialize: function (latlngs, options) {
this._layers = {};
this._options = options;
this.setLatLngs(latlngs);
},
setLatLngs: function (latlngs) {
var i = 0,
len = latlngs.length;
this.eachLayer(function (layer) {
if (i < len) {
layer.setLatLngs(latlngs[i++]);
} else {
this.removeLayer(layer);
}
}, this);
while (i < len) {
this.addLayer(new Klass(latlngs[i++], this._options));
}
return this;
}
});
}
L.MultiPolyline = createMulti(L.Polyline);
L.MultiPolygon = createMulti(L.Polygon);
L.multiPolyline = function (latlngs, options) {
return new L.MultiPolyline(latlngs, options);
};
L.multiPolygon = function (latlngs, options) {
return new L.MultiPolygon(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.Circle is a circle overlay (with a certain radius in meters).
*/
L.Circle = L.Path.extend({
initialize: function (latlng, radius, options) {
L.Path.prototype.initialize.call(this, options);
this._latlng = L.latLng(latlng);
this._mRadius = radius;
},
options: {
fill: true
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
return this.redraw();
},
setRadius: function (radius) {
this._mRadius = radius;
return this.redraw();
},
projectLatlngs: function () {
var lngRadius = this._getLngRadius(),
latlng = this._latlng,
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
this._point = this._map.latLngToLayerPoint(latlng);
this._radius = Math.max(this._point.x - pointLeft.x, 1);
},
getBounds: function () {
var lngRadius = this._getLngRadius(),
latRadius = (this._mRadius / 40075017) * 360,
latlng = this._latlng;
return new L.LatLngBounds(
[latlng.lat - latRadius, latlng.lng - lngRadius],
[latlng.lat + latRadius, latlng.lng + lngRadius]);
},
getLatLng: function () {
return this._latlng;
},
getPathString: function () {
var p = this._point,
r = this._radius;
if (this._checkIfEmpty()) {
return '';
}
if (L.Browser.svg) {
return 'M' + p.x + ',' + (p.y - r) +
'A' + r + ',' + r + ',0,1,1,' +
(p.x - 0.1) + ',' + (p.y - r) + ' z';
} else {
p._round();
r = Math.round(r);
return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
}
},
getRadius: function () {
return this._mRadius;
},
// TODO Earth hardcoded, move into projection code!
_getLatRadius: function () {
return (this._mRadius / 40075017) * 360;
},
_getLngRadius: function () {
return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
},
_checkIfEmpty: function () {
if (!this._map) {
return false;
}
var vp = this._map._pathViewport,
r = this._radius,
p = this._point;
return p.x - r > vp.max.x || p.y - r > vp.max.y ||
p.x + r < vp.min.x || p.y + r < vp.min.y;
}
});
L.circle = function (latlng, radius, options) {
return new L.Circle(latlng, radius, options);
};
/*
* L.CircleMarker is a circle overlay with a permanent pixel radius.
*/
L.CircleMarker = L.Circle.extend({
options: {
radius: 10,
weight: 2
},
initialize: function (latlng, options) {
L.Circle.prototype.initialize.call(this, latlng, null, options);
this._radius = this.options.radius;
},
projectLatlngs: function () {
this._point = this._map.latLngToLayerPoint(this._latlng);
},
_updateStyle : function () {
L.Circle.prototype._updateStyle.call(this);
this.setRadius(this.options.radius);
},
setRadius: function (radius) {
this.options.radius = this._radius = radius;
return this.redraw();
}
});
L.circleMarker = function (latlng, options) {
return new L.CircleMarker(latlng, options);
};
/*
* Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
*/
L.Polyline.include(!L.Path.CANVAS ? {} : {
_containsPoint: function (p, closed) {
var i, j, k, len, len2, dist, part,
w = this.options.weight / 2;
if (L.Browser.touch) {
w += 10; // polyline click tolerance on touch devices
}
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;
}
dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
if (dist <= w) {
return true;
}
}
}
return false;
}
});
/*
* Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
*/
L.Polygon.include(!L.Path.CANVAS ? {} : {
_containsPoint: function (p) {
var inside = false,
part, p1, p2,
i, j, k,
len, len2;
// TODO optimization: check if within bounds first
if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
// click on polygon border
return true;
}
// 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;
}
}
}
return inside;
}
});
/*
* Extends L.Circle with Canvas-specific code.
*/
L.Circle.include(!L.Path.CANVAS ? {} : {
_drawPath: function () {
var p = this._point;
this._ctx.beginPath();
this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
},
_containsPoint: function (p) {
var center = this._point,
w2 = this.options.stroke ? this.options.weight / 2 : 0;
return (p.distanceTo(center) <= this._radius + w2);
}
});
/*
* CircleMarker canvas specific drawing parts.
*/
L.CircleMarker.include(!L.Path.CANVAS ? {} : {
_updateStyle: function () {
L.Path.prototype._updateStyle.call(this);
}
});
/*
* 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;
if (features) {
for (i = 0, len = features.length; i < len; i++) {
// Only add this if geometry or geometries are set and not null
if (features[i].geometries || features[i].geometry || features[i].features) {
this.addData(features[i]);
}
}
return this;
}
var options = this.options;
if (options.filter && !options.filter(geojson)) { return; }
var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
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) {
var style = this.options.style;
if (style) {
// reset any custom styles
L.Util.extend(layer.options, layer.defaultOptions);
this._setLayerStyle(layer, 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, pointToLayer, coordsToLatLng) {
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
coords = geometry.coordinates,
layers = [],
latlng, latlngs, i, len, layer;
coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
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]);
layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
layers.push(layer);
}
return new L.FeatureGroup(layers);
case 'LineString':
latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
return new L.Polyline(latlngs);
case 'Polygon':
latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
return new L.Polygon(latlngs);
case 'MultiLineString':
latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
return new L.MultiPolyline(latlngs);
case 'MultiPolygon':
latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
return new L.MultiPolygon(latlngs);
case 'GeometryCollection':
for (i = 0, len = geometry.geometries.length; i < len; i++) {
layer = this.geometryToLayer({
geometry: geometry.geometries[i],
type: 'Feature',
properties: geojson.properties
}, pointToLayer, coordsToLatLng);
layers.push(layer);
}
return new L.FeatureGroup(layers);
default:
throw new Error('Invalid GeoJSON object.');
}
},
coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
return new L.LatLng(coords[1], coords[0]);
},
coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
var latlng, i, len,
latlngs = [];
for (i = 0, len = coords.length; 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.lng, latLng.lat];
},
latLngsToCoords: function (latLngs) {
var coords = [];
for (var i = 0, len = latLngs.length; i < len; i++) {
coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));
}
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.include({
toGeoJSON: function () {
return L.GeoJSON.getFeature(this, {
type: 'LineString',
coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())
});
}
});
L.Polygon.include({
toGeoJSON: function () {
var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],
i, len, hole;
coords[0].push(coords[0][0]);
if (this._holes) {
for (i = 0, len = this._holes.length; i < len; i++) {
hole = L.GeoJSON.latLngsToCoords(this._holes[i]);
hole.push(hole[0]);
coords.push(hole);
}
}
return L.GeoJSON.getFeature(this, {
type: 'Polygon',
coordinates: coords
});
}
});
(function () {
function includeMulti(Klass, type) {
Klass.include({
toGeoJSON: function () {
var coords = [];
this.eachLayer(function (layer) {
coords.push(layer.toGeoJSON().geometry.coordinates);
});
return L.GeoJSON.getFeature(this, {
type: type,
coordinates: coords
});
}
});
}
includeMulti(L.MultiPolyline, 'MultiLineString');
includeMulti(L.MultiPolygon, 'MultiPolygon');
}());
L.LayerGroup.include({
toGeoJSON: function () {
var features = [];
this.eachLayer(function (layer) {
if (layer.toGeoJSON) {
features.push(L.GeoJSON.asFeature(layer.toGeoJSON()));
}
});
return {
type: 'FeatureCollection',
features: features
};
}
});
L.geoJson = function (geojson, options) {
return new L.GeoJSON(geojson, options);
};
/*
* L.DomEvent contains functions for working with DOM events.
*/
L.DomEvent = {
/* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
var id = L.stamp(fn),
key = '_leaflet_' + type + id,
handler, originalHandler, newType;
if (obj[key]) { return this; }
handler = function (e) {
return fn.call(context || obj, e || L.DomEvent._getEvent());
};
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
return this.addMsTouchListener(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')) {
originalHandler = handler;
newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
handler = function (e) {
if (!L.DomEvent._checkMouse(obj, e)) { return; }
return originalHandler(e);
};
obj.addEventListener(newType, handler, false);
} else if (type === 'click' && L.Browser.android) {
originalHandler = handler;
handler = function (e) {
return L.DomEvent._filterClick(e, originalHandler);
};
obj.addEventListener(type, handler, false);
} else {
obj.addEventListener(type, handler, false);
}
} else if ('attachEvent' in obj) {
obj.attachEvent('on' + type, handler);
}
obj[key] = handler;
return this;
},
removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
var id = L.stamp(fn),
key = '_leaflet_' + type + id,
handler = obj[key];
if (!handler) { return this; }
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
this.removeMsTouchListener(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 if ((type === 'mouseenter') || (type === 'mouseleave')) {
obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
} else {
obj.removeEventListener(type, handler, false);
}
} else if ('detachEvent' in obj) {
obj.detachEvent('on' + type, handler);
}
obj[key] = null;
return this;
},
stopPropagation: function (e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
return this;
},
disableClickPropagation: function (el) {
var stop = L.DomEvent.stopPropagation;
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.addListener(el, L.Draggable.START[i], stop);
}
return L.DomEvent
.addListener(el, 'click', L.DomEvent._fakeStop)
.addListener(el, '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) {
var body = document.body,
docEl = document.documentElement,
x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
pos = new L.Point(x, y);
return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
},
getWheelDelta: function (e) {
var delta = 0;
if (e.wheelDelta) {
delta = e.wheelDelta / 120;
}
if (e.detail) {
delta = -e.detail / 3;
}
return delta;
},
_fakeStop: function stop(e) {
// fakes stopPropagation by setting a special event flag checked in Map mouse events handler
// jshint camelcase: false
e._leaflet_stop = true;
},
// 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);
},
_getEvent: function () { // evil magic for IE
/*jshint noarg:false */
var e = window.event;
if (!e) {
var caller = arguments.callee.caller;
while (caller) {
e = caller['arguments'][0];
if (e && window.Event === e.constructor) {
break;
}
caller = caller.caller;
}
}
return e;
},
// 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 1000ms 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 < 1000) || (e.target._simulatedClick && !e._simulated)) {
L.DomEvent.stop(e);
return;
}
L.DomEvent._lastClick = timeStamp;
return handler(e);
}
};
L.DomEvent.on = L.DomEvent.addListener;
L.DomEvent.off = L.DomEvent.removeListener;
/*
* L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
*/
L.Draggable = L.Class.extend({
includes: L.Mixin.Events,
statics: {
START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
END: {
mousedown: 'mouseup',
touchstart: 'touchend',
MSPointerDown: 'touchend'
},
MOVE: {
mousedown: 'mousemove',
touchstart: 'touchmove',
MSPointerDown: 'touchmove'
}
},
initialize: function (element, dragStartTarget) {
this._element = element;
this._dragStartTarget = dragStartTarget || element;
},
enable: function () {
if (this._enabled) { return; }
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
}
this._enabled = true;
},
disable: function () {
if (!this._enabled) { return; }
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
}
this._enabled = false;
this._moved = false;
},
_onDown: function (e) {
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();
var first = e.touches ? e.touches[0] : e,
el = first.target;
// if touching a link, highlight it
if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
L.DomUtil.addClass(el, 'leaflet-active');
}
this._moved = false;
if (this._moving) { return; }
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) { 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; }
L.DomEvent.preventDefault(e);
if (!this._moved) {
this.fire('dragstart');
this._moved = true;
this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
if (!L.Browser.touch) {
L.DomUtil.addClass(document.body, 'leaflet-dragging');
}
}
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 () {
if (!L.Browser.touch) {
L.DomUtil.removeClass(document.body, 'leaflet-dragging');
}
for (var i in L.Draggable.MOVE) {
L.DomEvent
.off(document, L.Draggable.MOVE[i], this._onMove)
.off(document, L.Draggable.END[i], this._onUp);
}
L.DomUtil.enableImageDrag();
L.DomUtil.enableTextSelection();
if (this._moved) {
// ensure drag is not fired after dragend
L.Util.cancelAnimFrame(this._animRequest);
this.fire('dragend');
}
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({
'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);
}
}
this._draggable.enable();
},
removeHooks: function () {
this._draggable.disable();
},
moved: function () {
return this._draggable && this._draggable._moved;
},
_onDragStart: function () {
var map = this._map;
if (map._panAnim) {
map._panAnim.stop();
}
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 () {
// TODO fix hardcoded Earth values
var pxCenter = this._map.getSize()._divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
this._worldWidth = this._map.project([0, 180]).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 () {
var map = this._map,
options = map.options,
delay = +new Date() - this._lastTime,
noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
map.fire('dragend');
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 {
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);
},
removeHooks: function () {
this._map.off('dblclick', this._onDoubleClick);
},
_onDoubleClick: function (e) {
this.setZoomAround(e.containerPoint, this._zoom + 1);
}
});
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, this);
L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
this._delta = 0;
},
removeHooks: function () {
L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
},
_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.preventDefault(e);
L.DomEvent.stopPropagation(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; }
map.setZoomAround(this._lastMousePos, zoom + delta);
}
});
L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
/*
* Extends the event handling code with double tap support for mobile browsers.
*/
L.extend(L.DomEvent, {
_touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
_touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
// inspired by Zepto touch code by Thomas Fuchs
addDoubleTapListener: function (obj, handler, id) {
var last,
doubleTap = false,
delay = 250,
touch,
pre = '_leaflet_',
touchstart = this._touchstart,
touchend = this._touchend,
trackedTouches = [];
function onTouchStart(e) {
var count;
if (L.Browser.msTouch) {
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.msTouch) {
var idx = trackedTouches.indexOf(e.pointerId);
if (idx === -1) {
return;
}
trackedTouches.splice(idx, 1);
}
if (doubleTap) {
if (L.Browser.msTouch) {
// work around .type being readonly with MSPointer* events
var newTouch = { },
prop;
// jshint forin:false
for (var i in touch) {
prop = touch[i];
if (typeof prop === 'function') {
newTouch[i] = prop.bind(touch);
} else {
newTouch[i] = prop;
}
}
touch = newTouch;
}
touch.type = 'dblclick';
handler(touch);
last = null;
}
}
obj[pre + touchstart + id] = onTouchStart;
obj[pre + touchend + id] = onTouchEnd;
// on msTouch 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.msTouch ? document.documentElement : obj;
obj.addEventListener(touchstart, onTouchStart, false);
endElement.addEventListener(touchend, onTouchEnd, false);
if (L.Browser.msTouch) {
endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
}
return this;
},
removeDoubleTapListener: function (obj, id) {
var pre = '_leaflet_';
obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
(L.Browser.msTouch ? document.documentElement : obj).removeEventListener(
this._touchend, obj[pre + this._touchend + id], false);
if (L.Browser.msTouch) {
document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
}
return this;
}
});
/*
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
*/
L.extend(L.DomEvent, {
_msTouches: [],
_msDocumentListener: false,
// Provides a touch events wrapper for msPointer events.
// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
addMsTouchListener: function (obj, type, handler, id) {
switch (type) {
case 'touchstart':
return this.addMsTouchListenerStart(obj, type, handler, id);
case 'touchend':
return this.addMsTouchListenerEnd(obj, type, handler, id);
case 'touchmove':
return this.addMsTouchListenerMove(obj, type, handler, id);
default:
throw 'Unknown touch event type';
}
},
addMsTouchListenerStart: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._msTouches;
var cb = function (e) {
var alreadyInArray = false;
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
alreadyInArray = true;
break;
}
}
if (!alreadyInArray) {
touches.push(e);
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchstart' + id] = cb;
obj.addEventListener('MSPointerDown', cb, false);
// need to also listen for end events to keep the _msTouches list accurate
// this needs to be on the body and never go away
if (!this._msDocumentListener) {
var internalCb = function (e) {
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches.splice(i, 1);
break;
}
}
};
//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener('MSPointerUp', internalCb, false);
document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
this._msDocumentListener = true;
}
return this;
},
addMsTouchListenerMove: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._msTouches;
function cb(e) {
// don't fire touch moves when mouse isn't down
if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches[i] = e;
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
}
obj[pre + 'touchmove' + id] = cb;
obj.addEventListener('MSPointerMove', cb, false);
return this;
},
addMsTouchListenerEnd: function (obj, type, handler, id) {
var pre = '_leaflet_',
touches = this._msTouches;
var cb = function (e) {
for (var i = 0; i < touches.length; i++) {
if (touches[i].pointerId === e.pointerId) {
touches.splice(i, 1);
break;
}
}
e.touches = touches.slice();
e.changedTouches = [e];
handler(e);
};
obj[pre + 'touchend' + id] = cb;
obj.addEventListener('MSPointerUp', cb, false);
obj.addEventListener('MSPointerCancel', cb, false);
return this;
},
removeMsTouchListener: function (obj, type, id) {
var pre = '_leaflet_',
cb = obj[pre + type + id];
switch (type) {
case 'touchstart':
obj.removeEventListener('MSPointerDown', cb, false);
break;
case 'touchmove':
obj.removeEventListener('MSPointerMove', cb, false);
break;
case 'touchend':
obj.removeEventListener('MSPointerUp', cb, false);
obj.removeEventListener('MSPointerCancel', cb, false);
break;
}
return this;
}
});
/*
* 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
});
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) {
var map = this._map;
if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
var 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 (this._scale === 1) { return; }
if (!this._moved) {
L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
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,
origin = this._getScaleOrigin(),
center = map.layerPointToLatLng(origin),
zoom = map.getScaleZoom(this._scale);
map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta);
},
_onTouchEnd: function () {
if (!this._moved || !this._zooming) {
this._zooming = false;
return;
}
var map = this._map;
this._zooming = false;
L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
L.Util.cancelAnimFrame(this._animRequest);
L.DomEvent
.off(document, 'touchmove', this._onTouchMove)
.off(document, 'touchend', this._onTouchEnd);
var origin = this._getScaleOrigin(),
center = map.layerPointToLatLng(origin),
oldZoom = map.getZoom(),
floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
roundZoomDelta = (floatZoomDelta > 0 ?
Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
zoom = map._limitZoom(oldZoom + roundZoomDelta),
scale = map.getZoomScale(zoom) / this._scale;
map._animateZoom(center, zoom, origin, scale);
},
_getScaleOrigin: 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.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);
L.DomEvent
.on(document, 'touchmove', this._onMove, this)
.on(document, 'touchend', this._onUp, this);
},
_onUp: function (e) {
clearTimeout(this._holdTimeout);
L.DomEvent
.off(document, 'touchmove', this._onMove, this)
.off(document, 'touchend', this._onUp, this);
if (this._fireClick && e && e.changedTouches) {
var first = e.changedTouches[0],
el = first.target;
if (el.tagName.toLowerCase() === 'a') {
L.DomUtil.removeClass(el, 'leaflet-active');
}
// 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.msTouch) {
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);
},
_onMouseDown: function (e) {
if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
L.DomUtil.disableTextSelection();
L.DomUtil.disableImageDrag();
this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
L.DomUtil.setPosition(this._box, this._startLayerPoint);
//TODO refactor: move cursor to styles
this._container.style.cursor = 'crosshair';
L.DomEvent
.on(document, 'mousemove', this._onMouseMove, this)
.on(document, 'mouseup', this._onMouseUp, this)
.on(document, 'keydown', this._onKeyDown, this);
this._map.fire('boxzoomstart');
},
_onMouseMove: function (e) {
var startPoint = this._startLayerPoint,
box = this._box,
layerPoint = this._map.mouseEventToLayerPoint(e),
offset = layerPoint.subtract(startPoint),
newPos = new L.Point(
Math.min(layerPoint.x, startPoint.x),
Math.min(layerPoint.y, startPoint.y));
L.DomUtil.setPosition(box, newPos);
// TODO refactor: remove hardcoded 4 pixels
box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
},
_finish: function () {
this._pane.removeChild(this._box);
this._container.style.cursor = '';
L.DomUtil.enableTextSelection();
L.DomUtil.enableImageDrag();
L.DomEvent
.off(document, 'mousemove', this._onMouseMove)
.off(document, 'mouseup', this._onMouseUp)
.off(document, 'keydown', this._onKeyDown);
},
_onMouseUp: function (e) {
this._finish();
var map = this._map,
layerPoint = map.mouseEventToLayerPoint(e);
if (this._startLayerPoint.equals(layerPoint)) { return; }
var bounds = new L.LatLngBounds(
map.layerPointToLatLng(this._startLayerPoint),
map.layerPointToLatLng(layerPoint));
map.fitBounds(bounds);
map.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],
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, this)
.on(container, 'blur', this._onBlur, this)
.on(container, 'mousedown', this._onMouseDown, this);
this._map
.on('focus', this._addHooks, this)
.on('blur', this._removeHooks, this);
},
removeHooks: function () {
this._removeHooks();
var container = this._map._container;
L.DomEvent
.off(container, 'focus', this._onFocus, this)
.off(container, 'blur', this._onBlur, this)
.off(container, 'mousedown', this._onMouseDown, this);
this._map
.off('focus', this._addHooks, this)
.off('blur', this._removeHooks, this);
},
_onMouseDown: function () {
if (this._focused) { return; }
var body = document.body,
docEl = document.documentElement,
top = body.scrollTop || docEl.scrollTop,
left = body.scrollTop || 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) {
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, this)
.on('drag', this._onDrag, this)
.on('dragend', this._onDragEnd, this);
this._draggable.enable();
},
removeHooks: function () {
this._draggable
.off('dragstart', this._onDragStart, this)
.off('drag', this._onDrag, this)
.off('dragend', this._onDragEnd, this);
this._draggable.disable();
},
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 () {
this._marker
.fire('moveend')
.fire('dragend');
}
});
/*
* 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;
},
removeFrom: function (map) {
var pos = this.getPosition(),
corner = map._controlCorners[pos];
corner.removeChild(this._container);
this._map = null;
if (this.onRemove) {
this.onRemove(map);
}
return this;
}
});
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.removeFrom(this);
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 () {
this._container.removeChild(this._controlContainer);
}
});
/*
* L.Control.Zoom is used for the default zoom buttons on the map.
*/
L.Control.Zoom = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
var zoomName = 'leaflet-control-zoom',
container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
this._map = map;
this._zoomInButton = this._createButton(
'+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this);
this._zoomOutButton = this._createButton(
'-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this);
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, context) {
var link = L.DomUtil.create('a', className, container);
link.innerHTML = html;
link.href = '#';
link.title = title;
var stop = L.DomEvent.stopPropagation;
L.DomEvent
.on(link, 'click', stop)
.on(link, 'mousedown', stop)
.on(link, 'dblclick', stop)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', fn, context);
return link;
},
_updateDisabled: function () {
var map = this._map,
className = 'leaflet-disabled';
L.DomUtil.removeClass(this._zoomInButton, className);
L.DomUtil.removeClass(this._zoomOutButton, className);
if (map._zoom === map.getMinZoom()) {
L.DomUtil.addClass(this._zoomOutButton, className);
}
if (map._zoom === map.getMaxZoom()) {
L.DomUtil.addClass(this._zoomInButton, className);
}
}
});
L.Map.mergeOptions({
zoomControl: true
});
L.Map.addInitHook(function () {
if (this.options.zoomControl) {
this.zoomControl = new L.Control.Zoom();
this.addControl(this.zoomControl);
}
});
L.control.zoom = function (options) {
return new L.Control.Zoom(options);
};
/*
* L.Control.Attribution is used for displaying attribution on the map (added by default).
*/
L.Control.Attribution = L.Control.extend({
options: {
position: 'bottomright',
prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
},
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);
map
.on('layeradd', this._onLayerAdd, this)
.on('layerremove', this._onLayerRemove, this);
this._update();
return this._container;
},
onRemove: function (map) {
map
.off('layeradd', this._onLayerAdd)
.off('layerremove', this._onLayerRemove);
},
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(' | ');
},
_onLayerAdd: function (e) {
if (e.layer.getAttribution) {
this.addAttribution(e.layer.getAttribution());
}
},
_onLayerRemove: function (e) {
if (e.layer.getAttribution) {
this.removeAttribution(e.layer.getAttribution());
}
}
});
L.Map.mergeOptions({
attributionControl: true
});
L.Map.addInitHook(function () {
if (this.options.attributionControl) {
this.attributionControl = (new L.Control.Attribution()).addTo(this);
}
});
L.control.attribution = function (options) {
return new L.Control.Attribution(options);
};
/*
* L.Control.Scale is used for displaying metric/imperial scale on the map.
*/
L.Control.Scale = L.Control.extend({
options: {
position: 'bottomleft',
maxWidth: 100,
metric: true,
imperial: true,
updateWhenIdle: false
},
onAdd: function (map) {
this._map = map;
var className = 'leaflet-control-scale',
container = L.DomUtil.create('div', className),
options = this.options;
this._addScales(options, className, 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 + '-line', container);
}
if (options.imperial) {
this._iScale = L.DomUtil.create('div', className + '-line', container);
}
},
_update: function () {
var bounds = this._map.getBounds(),
centerLat = bounds.getCenter().lat,
halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
size = this._map.getSize(),
options = this.options,
maxMeters = 0;
if (size.x > 0) {
maxMeters = dist * (options.maxWidth / size.x);
}
this._updateScales(options, maxMeters);
},
_updateScales: function (options, maxMeters) {
if (options.metric && maxMeters) {
this._updateMetric(maxMeters);
}
if (options.imperial && maxMeters) {
this._updateImperial(maxMeters);
}
},
_updateMetric: function (maxMeters) {
var meters = this._getRoundNum(maxMeters);
this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
},
_updateImperial: function (maxMeters) {
var maxFeet = maxMeters * 3.2808399,
scale = this._iScale,
maxMiles, miles, feet;
if (maxFeet > 5280) {
maxMiles = maxFeet / 5280;
miles = this._getRoundNum(maxMiles);
scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
scale.innerHTML = miles + ' mi';
} else {
feet = this._getRoundNum(maxFeet);
scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
scale.innerHTML = feet + ' ft';
}
},
_getScaleWidth: function (ratio) {
return Math.round(this.options.maxWidth * ratio) - 10;
},
_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 (map) {
this._initLayout();
this._update();
map
.on('layeradd', this._onLayerChange, this)
.on('layerremove', this._onLayerChange, this);
return this._container;
},
onRemove: function (map) {
map
.off('layeradd', this._onLayerChange)
.off('layerremove', this._onLayerChange);
},
addBaseLayer: function (layer, name) {
this._addLayer(layer, name);
this._update();
return this;
},
addOverlay: function (layer, name) {
this._addLayer(layer, name, true);
this._update();
return this;
},
removeLayer: function (layer) {
var id = L.stamp(layer);
delete this._layers[id];
this._update();
return this;
},
_initLayout: function () {
var className = 'leaflet-control-layers',
container = this._container = L.DomUtil.create('div', className);
//Makes this work on IE10 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);
L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
} 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, 'mouseover', this._expand, this)
.on(container, 'mouseout', 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);
}
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) {
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 = false,
overlaysPresent = false,
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';
},
_onLayerChange: function (e) {
var obj = this._layers[L.stamp(e.layer)];
if (!obj) { return; }
if (!this._handlingClick) {
this._update();
}
var type = obj.overlay ?
(e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
(e.type === 'layeradd' ? 'baselayerchange' : null);
if (type) {
this._map.fire(type, obj);
}
},
// 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 = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
if (checked) {
radioHtml += ' checked="checked"';
}
radioHtml += '/>';
var radioFragment = document.createElement('div');
radioFragment.innerHTML = radioHtml;
return radioFragment.firstChild;
},
_addItem: function (obj) {
var label = document.createElement('label'),
input,
checked = this._map.hasLayer(obj.layer);
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 i, input, obj,
inputs = this._form.getElementsByTagName('input'),
inputsLen = inputs.length;
this._handlingClick = true;
for (i = 0; i < inputsLen; i++) {
input = inputs[i];
obj = this._layers[input.layerId];
if (input.checked && !this._map.hasLayer(obj.layer)) {
this._map.addLayer(obj.layer);
} else if (!input.checked && this._map.hasLayer(obj.layer)) {
this._map.removeLayer(obj.layer);
}
}
this._handlingClick = false;
},
_expand: function () {
L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
},
_collapse: function () {
this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
}
});
L.control.layers = function (baseLayers, overlays, options) {
return new L.Control.Layers(baseLayers, overlays, options);
};
/*
* L.PosAnimation is used by Leaflet internally for pan animations.
*/
L.PosAnimation = L.Class.extend({
includes: L.Mixin.Events,
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
L.DomUtil.setPosition(this._el, this._getPos());
this._onTransitionEnd();
L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
},
_onStep: function () {
// jshint camelcase: false
// make L.DomUtil.getPosition return intermediate position value during animation
this._el._leaflet_pos = this._getPos();
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);
left = matches ? parseFloat(matches[1]) : 0;
top = matches ? parseFloat(matches[2]) : 0;
} 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 = this._limitZoom(zoom);
center = L.latLng(center);
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;
}
});
/*
* 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);
}
});
/*
* Extends L.Map to handle zoom animations.
*/
L.Map.mergeOptions({
zoomAnimation: true,
zoomAnimationThreshold: 4
});
if (L.DomUtil.TRANSITION) {
L.Map.addInitHook(function () {
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
// 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(!L.DomUtil.TRANSITION ? {} : {
_catchTransitionEnd: function () {
if (this._animatingZoom) {
this._onZoomTransitionEnd();
}
},
_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 ||
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),
origin = this._getCenterLayerPoint()._add(offset);
// 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; }
this
.fire('movestart')
.fire('zoomstart');
this._animateZoom(center, zoom, origin, scale, null, true);
return true;
},
_animateZoom: function (center, zoom, origin, scale, delta, backwards) {
this._animatingZoom = true;
// put transform transition on all layers with leaflet-zoom-animated class
L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
// 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;
}
this.fire('zoomanim', {
center: center,
zoom: zoom,
origin: origin,
scale: scale,
delta: delta,
backwards: backwards
});
},
_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;
}
}
});
/*
Zoom animation logic for L.TileLayer.
*/
L.TileLayer.include({
_animateZoom: function (e) {
if (!this._animating) {
this._animating = true;
this._prepareBgBuffer();
}
var bg = this._bgBuffer,
transform = L.DomUtil.TRANSFORM,
initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
bg.style[transform] = e.backwards ?
scaleStr + ' ' + initialTransform :
initialTransform + ' ' + scaleStr;
},
_endZoomAnim: function () {
var front = this._tileContainer,
bg = this._bgBuffer;
front.style.visibility = '';
front.parentNode.appendChild(front); // Bring to fore
// force reflow
L.Util.falseFn(bg.offsetWidth);
this._animating = false;
},
_clearBgBuffer: function () {
var map = this._map;
if (map && !map._animatingZoom && !map.touchZoom._zooming) {
this._bgBuffer.innerHTML = '';
this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
}
},
_prepareBgBuffer: function () {
var front = this._tileContainer,
bg = this._bgBuffer;
// if foreground layer doesn't have many tiles but bg layer does,
// keep the existing bg layer and just zoom it some more
var bgLoaded = this._getLoadedTilesPercentage(bg),
frontLoaded = this._getLoadedTilesPercentage(front);
if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
front.style.visibility = 'hidden';
this._stopLoadingImages(front);
return;
}
// prepare the buffer to become the front tile pane
bg.style.visibility = 'hidden';
bg.style[L.DomUtil.TRANSFORM] = '';
// switch out the current layer to be the new bg layer (and vice-versa)
this._tileContainer = bg;
bg = this._bgBuffer = front;
this._stopLoadingImages(bg);
//prevent bg buffer from clearing right after zoom
clearTimeout(this._clearBgBufferTimer);
},
_getLoadedTilesPercentage: function (container) {
var tiles = container.getElementsByTagName('img'),
i, len, count = 0;
for (i = 0, len = tiles.length; i < len; i++) {
if (tiles[i].complete) {
count++;
}
}
return count / len;
},
// stops loading all tiles in the background layer
_stopLoadingImages: function (container) {
var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
i, len, tile;
for (i = 0, len = tiles.length; i < len; i++) {
tile = tiles[i];
if (!tile.complete) {
tile.onload = L.Util.falseFn;
tile.onerror = L.Util.falseFn;
tile.src = L.Util.emptyImageUrl;
tile.parentNode.removeChild(tile);
}
}
}
});
/*
* Provides L.Map with convenient shortcuts for using browser geolocation features.
*/
L.Map.include({
_defaultLocateOptions: {
watch: false,
setView: false,
maxZoom: Infinity,
timeout: 10000,
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(L.LatLng.DEG_TO_RAD * lat),
bounds = L.latLngBounds(
[lat - latAccuracy, lng - lngAccuracy],
[lat + latAccuracy, lng + lngAccuracy]),
options = this._locateOptions;
if (options.setView) {
var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
this.setView(latlng, zoom);
}
var data = {
latlng: latlng,
bounds: bounds,
};
for (var i in pos.coords) {
if (typeof pos.coords[i] === 'number') {
data[i] = pos.coords[i];
}
}
this.fire('locationfound', data);
}
});
}(window, document));
},{}],37:[function(require,module,exports){
/*!
* mustache.js - Logic-less {{mustache}} templates with JavaScript
* http://github.com/janl/mustache.js
*/
/*global define: false*/
(function (root, factory) {
if (typeof exports === "object" && exports) {
factory(exports); // CommonJS
} else {
var mustache = {};
factory(mustache);
if (typeof define === "function" && define.amd) {
define(mustache); // AMD
} else {
root.Mustache = mustache; // <script>
}
}
}(this, function (mustache) {
var whiteRe = /\s*/;
var spaceRe = /\s+/;
var nonSpaceRe = /\S/;
var eqRe = /\s*=/;
var curlyRe = /\s*\}/;
var tagRe = /#|\^|\/|>|\{|&|=|!/;
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189
var RegExp_test = RegExp.prototype.test;
function testRegExp(re, string) {
return RegExp_test.call(re, string);
}
function isWhitespace(string) {
return !testRegExp(nonSpaceRe, string);
}
var Object_toString = Object.prototype.toString;
var isArray = Array.isArray || function (object) {
return Object_toString.call(object) === '[object Array]';
};
function isFunction(object) {
return typeof object === 'function';
}
function escapeRegExp(string) {
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
function Scanner(string) {
this.string = string;
this.tail = string;
this.pos = 0;
}
/**
* Returns `true` if the tail is empty (end of string).
*/
Scanner.prototype.eos = function () {
return this.tail === "";
};
/**
* Tries to match the given regular expression at the current position.
* Returns the matched text if it can match, the empty string otherwise.
*/
Scanner.prototype.scan = function (re) {
var match = this.tail.match(re);
if (match && match.index === 0) {
var string = match[0];
this.tail = this.tail.substring(string.length);
this.pos += string.length;
return string;
}
return "";
};
/**
* Skips all text until the given regular expression can be matched. Returns
* the skipped string, which is the entire tail if no match can be made.
*/
Scanner.prototype.scanUntil = function (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = "";
break;
case 0:
match = "";
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
function Context(view, parent) {
this.view = view == null ? {} : view;
this.parent = parent;
this._cache = { '.': this.view };
}
Context.make = function (view) {
return (view instanceof Context) ? view : new Context(view);
};
Context.prototype.push = function (view) {
return new Context(view, this);
};
Context.prototype.lookup = function (name) {
var value;
if (name in this._cache) {
value = this._cache[name];
} else {
var context = this;
while (context) {
if (name.indexOf('.') > 0) {
value = context.view;
var names = name.split('.'), i = 0;
while (value != null && i < names.length) {
value = value[names[i++]];
}
} else {
value = context.view[name];
}
if (value != null) break;
context = context.parent;
}
this._cache[name] = value;
}
if (isFunction(value)) {
value = value.call(this.view);
}
return value;
};
function Writer() {
this.clearCache();
}
Writer.prototype.clearCache = function () {
this._cache = {};
this._partialCache = {};
};
Writer.prototype.compile = function (template, tags) {
var fn = this._cache[template];
if (!fn) {
var tokens = mustache.parse(template, tags);
fn = this._cache[template] = this.compileTokens(tokens, template);
}
return fn;
};
Writer.prototype.compilePartial = function (name, template, tags) {
var fn = this.compile(template, tags);
this._partialCache[name] = fn;
return fn;
};
Writer.prototype.getPartial = function (name) {
if (!(name in this._partialCache) && this._loadPartial) {
this.compilePartial(name, this._loadPartial(name));
}
return this._partialCache[name];
};
Writer.prototype.compileTokens = function (tokens, template) {
var self = this;
return function (view, partials) {
if (partials) {
if (isFunction(partials)) {
self._loadPartial = partials;
} else {
for (var name in partials) {
self.compilePartial(name, partials[name]);
}
}
}
return renderTokens(tokens, self, Context.make(view), template);
};
};
Writer.prototype.render = function (template, view, partials) {
return this.compile(template)(view, partials);
};
/**
* Low-level function that renders the given `tokens` using the given `writer`
* and `context`. The `template` string is only needed for templates that use
* higher-order sections to extract the portion of the original template that
* was contained in that section.
*/
function renderTokens(tokens, writer, context, template) {
var buffer = '';
// This function is used to render an artbitrary template
// in the current context by higher-order functions.
function subRender(template) {
return writer.render(template, context);
}
var token, tokenValue, value;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
tokenValue = token[1];
switch (token[0]) {
case '#':
value = context.lookup(tokenValue);
if (typeof value === 'object' || typeof value === 'string') {
if (isArray(value)) {
for (var j = 0, jlen = value.length; j < jlen; ++j) {
buffer += renderTokens(token[4], writer, context.push(value[j]), template);
}
} else if (value) {
buffer += renderTokens(token[4], writer, context.push(value), template);
}
} else if (isFunction(value)) {
var text = template == null ? null : template.slice(token[3], token[5]);
value = value.call(context.view, text, subRender);
if (value != null) buffer += value;
} else if (value) {
buffer += renderTokens(token[4], writer, context, template);
}
break;
case '^':
value = context.lookup(tokenValue);
// Use JavaScript's definition of falsy. Include empty arrays.
// See https://github.com/janl/mustache.js/issues/186
if (!value || (isArray(value) && value.length === 0)) {
buffer += renderTokens(token[4], writer, context, template);
}
break;
case '>':
value = writer.getPartial(tokenValue);
if (isFunction(value)) buffer += value(context);
break;
case '&':
value = context.lookup(tokenValue);
if (value != null) buffer += value;
break;
case 'name':
value = context.lookup(tokenValue);
if (value != null) buffer += mustache.escape(value);
break;
case 'text':
buffer += tokenValue;
break;
}
}
return buffer;
}
/**
* Forms the given array of `tokens` into a nested tree structure where
* tokens that represent a section have two additional items: 1) an array of
* all tokens that appear in that section and 2) the index in the original
* template that represents the end of that section.
*/
function nestTokens(tokens) {
var tree = [];
var collector = tree;
var sections = [];
var token;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
switch (token[0]) {
case '#':
case '^':
sections.push(token);
collector.push(token);
collector = token[4] = [];
break;
case '/':
var section = sections.pop();
section[5] = token[2];
collector = sections.length > 0 ? sections[sections.length - 1][4] : tree;
break;
default:
collector.push(token);
}
}
return tree;
}
/**
* Combines the values of consecutive text tokens in the given `tokens` array
* to a single token.
*/
function squashTokens(tokens) {
var squashedTokens = [];
var token, lastToken;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
if (token) {
if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
lastToken[1] += token[1];
lastToken[3] = token[3];
} else {
lastToken = token;
squashedTokens.push(token);
}
}
}
return squashedTokens;
}
function escapeTags(tags) {
return [
new RegExp(escapeRegExp(tags[0]) + "\\s*"),
new RegExp("\\s*" + escapeRegExp(tags[1]))
];
}
/**
* Breaks up the given `template` string into a tree of token objects. If
* `tags` is given here it must be an array with two string values: the
* opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
* course, the default is to use mustaches (i.e. Mustache.tags).
*/
function parseTemplate(template, tags) {
template = template || '';
tags = tags || mustache.tags;
if (typeof tags === 'string') tags = tags.split(spaceRe);
if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', '));
var tagRes = escapeTags(tags);
var scanner = new Scanner(template);
var sections = []; // Stack to hold section tokens
var tokens = []; // Buffer to hold the tokens
var spaces = []; // Indices of whitespace tokens on the current line
var hasTag = false; // Is there a {{tag}} on the current line?
var nonSpace = false; // Is there a non-space char on the current line?
// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
function stripSpace() {
if (hasTag && !nonSpace) {
while (spaces.length) {
delete tokens[spaces.pop()];
}
} else {
spaces = [];
}
hasTag = false;
nonSpace = false;
}
var start, type, value, chr, token, openSection;
while (!scanner.eos()) {
start = scanner.pos;
// Match any text between tags.
value = scanner.scanUntil(tagRes[0]);
if (value) {
for (var i = 0, len = value.length; i < len; ++i) {
chr = value.charAt(i);
if (isWhitespace(chr)) {
spaces.push(tokens.length);
} else {
nonSpace = true;
}
tokens.push(['text', chr, start, start + 1]);
start += 1;
// Check for whitespace on the current line.
if (chr == '\n') stripSpace();
}
}
// Match the opening tag.
if (!scanner.scan(tagRes[0])) break;
hasTag = true;
// Get the tag type.
type = scanner.scan(tagRe) || 'name';
scanner.scan(whiteRe);
// Get the tag value.
if (type === '=') {
value = scanner.scanUntil(eqRe);
scanner.scan(eqRe);
scanner.scanUntil(tagRes[1]);
} else if (type === '{') {
value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
scanner.scan(curlyRe);
scanner.scanUntil(tagRes[1]);
type = '&';
} else {
value = scanner.scanUntil(tagRes[1]);
}
// Match the closing tag.
if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos);
token = [type, value, start, scanner.pos];
tokens.push(token);
if (type === '#' || type === '^') {
sections.push(token);
} else if (type === '/') {
// Check section nesting.
openSection = sections.pop();
if (!openSection) {
throw new Error('Unopened section "' + value + '" at ' + start);
}
if (openSection[1] !== value) {
throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
}
} else if (type === 'name' || type === '{' || type === '&') {
nonSpace = true;
} else if (type === '=') {
// Set the tags for the next time around.
tags = value.split(spaceRe);
if (tags.length !== 2) {
throw new Error('Invalid tags at ' + start + ': ' + tags.join(', '));
}
tagRes = escapeTags(tags);
}
}
// Make sure there are no open sections when we're done.
openSection = sections.pop();
if (openSection) {
throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
}
return nestTokens(squashTokens(tokens));
}
mustache.name = "mustache.js";
mustache.version = "0.7.3";
mustache.tags = ["{{", "}}"];
mustache.Scanner = Scanner;
mustache.Context = Context;
mustache.Writer = Writer;
mustache.parse = parseTemplate;
// Export the escaping function so that the user may override it.
// See https://github.com/janl/mustache.js/issues/244
mustache.escape = escapeHtml;
// All Mustache.* functions use this writer.
var defaultWriter = new Writer();
/**
* Clears all cached templates and partials in the default writer.
*/
mustache.clearCache = function () {
return defaultWriter.clearCache();
};
/**
* Compiles the given `template` to a reusable function using the default
* writer.
*/
mustache.compile = function (template, tags) {
return defaultWriter.compile(template, tags);
};
/**
* Compiles the partial with the given `name` and `template` to a reusable
* function using the default writer.
*/
mustache.compilePartial = function (name, template, tags) {
return defaultWriter.compilePartial(name, template, tags);
};
/**
* Compiles the given array of tokens (the output of a parse) to a reusable
* function using the default writer.
*/
mustache.compileTokens = function (tokens, template) {
return defaultWriter.compileTokens(tokens, template);
};
/**
* Renders the `template` with the given `view` and `partials` using the
* default writer.
*/
mustache.render = function (template, view, partials) {
return defaultWriter.render(template, view, partials);
};
// This is here for backwards compatibility with 0.4.x.
mustache.to_html = function (template, view, partials, send) {
var result = mustache.render(template, view, partials);
if (isFunction(send)) {
send(result);
} else {
return result;
}
};
}));
},{}],38:[function(require,module,exports){
module.exports={
"author": {
"name": "MapBox"
},
"name": "mapbox.js",
"description": "mapbox javascript api",
"version": "1.3.1",
"homepage": "http://mapbox.com/",
"repository": {
"type": "git",
"url": "git://github.com/mapbox/mapbox.js.git"
},
"main": "index.js",
"dependencies": {
"leaflet": "0.6.2",
"mustache": "~0.7.2",
"corslite": "0.0.3",
"json3": "~3.2.5"
},
"scripts": {
"test": "mocha-phantomjs test/index.html"
},
"devDependencies": {
"leaflet-hash": "git://github.com/mlevans/leaflet-hash.git#b039a3aa4e2492a5c7448075172ac26769e601d6",
"leaflet-fullscreen": "0.0.0",
"uglify-js": "~2.2.5",
"mocha": "~1.9",
"expect.js": "~0.2.0",
"sinon": "~1.7.3",
"mocha-phantomjs": "~1.1.1",
"happen": "~0.1.2",
"browserify": "~2.22.0"
},
"optionalDependencies": {},
"engines": {
"node": "*"
},
"bugs": {
"url": "https://github.com/mapbox/mapbox.js/issues"
},
"_id": "mapbox.js@1.3.1",
"dist": {
"shasum": "a6d144286157eecf7273b202782b31a695450f6a",
"tarball": "http://registry.npmjs.org/mapbox.js/-/mapbox.js-1.3.1.tgz"
},
"_from": "mapbox.js@>=1.3.1 <1.4.0",
"_npmVersion": "1.2.32",
"_npmUser": {
"name": "tmcw",
"email": "tom@macwright.org"
},
"maintainers": [
{
"name": "tmcw",
"email": "macwright@gmail.com"
},
{
"name": "tristen",
"email": "tristen.brown@gmail.com"
},
{
"name": "ansis",
"email": "ansis.brammanis@gmail.com"
},
{
"name": "yhahn",
"email": "young@developmentseed.org"
},
{
"name": "willwhite",
"email": "will@mapbox.com"
},
{
"name": "jfirebaugh",
"email": "john.firebaugh@gmail.com"
},
{
"name": "heyitsgarrett",
"email": "heyitsgarrett@gmail.com"
}
],
"directories": {},
"_shasum": "a6d144286157eecf7273b202782b31a695450f6a",
"_resolved": "https://registry.npmjs.org/mapbox.js/-/mapbox.js-1.3.1.tgz"
}
},{}],39:[function(require,module,exports){
'use strict';
module.exports = {
HTTP_URLS: [
'http://a.tiles.mapbox.com/v3/',
'http://b.tiles.mapbox.com/v3/',
'http://c.tiles.mapbox.com/v3/',
'http://d.tiles.mapbox.com/v3/'],
FORCE_HTTPS: false,
HTTPS_URLS: [
'https://a.tiles.mapbox.com/v3/',
'https://b.tiles.mapbox.com/v3/',
'https://c.tiles.mapbox.com/v3/',
'https://d.tiles.mapbox.com/v3/']
};
},{}],40:[function(require,module,exports){
'use strict';
var util = require('./util'),
urlhelper = require('./url'),
request = require('./request');
// Low-level geocoding interface - wraps specific API calls and their
// return values.
module.exports = function(_) {
var geocoder = {}, url;
geocoder.getURL = function(_) {
return url;
};
geocoder.setURL = function(_) {
url = urlhelper.jsonify(_);
return geocoder;
};
geocoder.setID = function(_) {
util.strict(_, 'string');
geocoder.setURL(urlhelper.base() + _ + '/geocode/{query}.json');
return geocoder;
};
geocoder.setTileJSON = function(_) {
util.strict(_, 'object');
geocoder.setURL(_.geocoder);
return geocoder;
};
geocoder.queryURL = function(_) {
util.strict(_, 'string');
if (!geocoder.getURL()) throw new Error('Geocoding map ID not set');
return L.Util.template(geocoder.getURL(), { query: encodeURIComponent(_) });
};
geocoder.query = function(_, callback) {
util.strict(_, 'string');
util.strict(callback, 'function');
request(geocoder.queryURL(_), function(err, json) {
if (json && json.results && json.results.length) {
var res = {
results: json.results,
latlng: [json.results[0][0].lat, json.results[0][0].lon]
};
if (json.results[0][0].bounds !== undefined) {
res.bounds = json.results[0][0].bounds;
res.lbounds = util.lbounds(res.bounds);
}
callback(null, res);
} else callback(err || true);
});
return geocoder;
};
// a reverse geocode:
//
// geocoder.reverseQuery([80, 20])
geocoder.reverseQuery = function(_, callback) {
var q = '';
function norm(x) {
if (x.lat !== undefined && x.lng !== undefined) return x.lng + ',' + x.lat;
else if (x.lat !== undefined && x.lon !== undefined) return x.lon + ',' + x.lat;
else return x[0] + ',' + x[1];
}
if (_.length && _[0].length) {
for (var i = 0, pts = []; i < _.length; i++) pts.push(norm(_[i]));
q = pts.join(';');
} else q = norm(_);
request(geocoder.queryURL(q), function(err, json) {
callback(err, json);
});
return geocoder;
};
if (typeof _ === 'string') {
if (_.indexOf('/') == -1) geocoder.setID(_);
else geocoder.setURL(_);
}
else if (typeof _ === 'object') geocoder.setTileJSON(_);
return geocoder;
};
},{"./request":50,"./url":54,"./util":55}],41:[function(require,module,exports){
'use strict';
var geocoder = require('./geocoder');
var GeocoderControl = L.Control.extend({
includes: L.Mixin.Events,
options: {
position: 'topleft'
},
initialize: function(_) {
this.geocoder = geocoder(_);
},
setURL: function(_) {
this.geocoder.setURL(_);
return this;
},
getURL: function() {
return this.geocoder.getURL();
},
setID: function(_) {
this.geocoder.setID(_);
return this;
},
setTileJSON: function(_) {
this.geocoder.setTileJSON(_);
return this;
},
_toggle: function(e) {
if (e) L.DomEvent.stop(e);
if (L.DomUtil.hasClass(this._container, 'active')) {
L.DomUtil.removeClass(this._container, 'active');
this._results.innerHTML = '';
this._input.blur();
} else {
L.DomUtil.addClass(this._container, 'active');
this._input.focus();
this._input.select();
}
},
_closeIfOpen: function(e) {
if (L.DomUtil.hasClass(this._container, 'active')) {
L.DomUtil.removeClass(this._container, 'active');
this._results.innerHTML = '';
this._input.blur();
}
},
onAdd: function(map) {
var container = L.DomUtil.create('div', 'leaflet-control-mapbox-geocoder leaflet-bar leaflet-control'),
link = L.DomUtil.create('a', 'leaflet-control-mapbox-geocoder-toggle mapbox-icon mapbox-icon-geocoder', container),
results = L.DomUtil.create('div', 'leaflet-control-mapbox-geocoder-results', container),
wrap = L.DomUtil.create('div', 'leaflet-control-mapbox-geocoder-wrap', container),
form = L.DomUtil.create('form', 'leaflet-control-mapbox-geocoder-form', wrap),
input = L.DomUtil.create('input', '', form);
link.href = '#';
link.innerHTML = '&nbsp;';
input.type = 'text';
input.setAttribute('placeholder', 'Search');
L.DomEvent.addListener(link, 'click', this._toggle, this);
L.DomEvent.addListener(form, 'submit', this._geocode, this);
L.DomEvent.disableClickPropagation(container);
this._map = map;
this._results = results;
this._input = input;
this._form = form;
this._map.on('click', this._closeIfOpen, this);
return container;
},
_geocode: function(e) {
L.DomEvent.preventDefault(e);
L.DomUtil.addClass(this._container, 'searching');
var map = this._map;
this.geocoder.query(this._input.value, L.bind(function(err, resp) {
L.DomUtil.removeClass(this._container, 'searching');
if (err || !resp || !resp.results || !resp.results.length) {
this.fire('error', {error: err});
} else {
this._results.innerHTML = '';
if (resp.results.length === 1 && resp.lbounds) {
this._map.fitBounds(resp.lbounds);
this._closeIfOpen();
} else {
for (var i = 0, l = Math.min(resp.results.length, 5); i < l; i++) {
var name = [];
for (var j = 0; j < resp.results[i].length; j++) {
resp.results[i][j].name && name.push(resp.results[i][j].name);
}
if (!name.length) continue;
var r = L.DomUtil.create('a', '', this._results);
r.innerHTML = name.join(', ');
r.href = '#';
(function(result) {
L.DomEvent.addListener(r, 'click', function(e) {
var _ = result[0].bounds;
map.fitBounds(L.latLngBounds([[_[1], _[0]], [_[3], _[2]]]));
L.DomEvent.stop(e);
});
})(resp.results[i]);
}
if (resp.results.length > 5) {
var outof = L.DomUtil.create('span', '', this._results);
outof.innerHTML = 'Top 5 of ' + resp.results.length + ' results';
}
}
this.fire('found', resp);
}
}, this));
}
});
module.exports = function(options) {
return new GeocoderControl(options);
};
},{"./geocoder":40}],42:[function(require,module,exports){
'use strict';
function utfDecode(c) {
if (c >= 93) c--;
if (c >= 35) c--;
return c - 32;
}
module.exports = function(data) {
return function(x, y) {
if (!data) return;
var idx = utfDecode(data.grid[y].charCodeAt(x)),
key = data.keys[idx];
return data.data[key];
};
};
},{}],43:[function(require,module,exports){
'use strict';
var util = require('./util'),
Mustache = require('mustache');
var GridControl = L.Control.extend({
options: {
pinnable: true,
follow: false,
sanitizer: require('./sanitize'),
touchTeaser: true,
location: true
},
_currentContent: '',
// pinned means that this control is on a feature and the user has likely
// clicked. pinned will not become false unless the user clicks off
// of the feature onto another or clicks x
_pinned: false,
initialize: function(_, options) {
L.Util.setOptions(this, options);
util.strict_instance(_, L.Class, 'L.mapbox.gridLayer');
this._layer = _;
},
setTemplate: function(template) {
this.options.template = template;
},
_template: function(format, data) {
if (!data) return;
var template = this.options.template || this._layer.getTileJSON().template;
if (template) {
var d = {};
d['__' + format + '__'] = true;
return this.options.sanitizer(
Mustache.to_html(template, L.extend(d, data)));
}
},
// change the content of the tooltip HTML if it has changed, otherwise
// noop
_show: function(content, o) {
if (content === this._currentContent) return;
this._currentContent = content;
if (this.options.follow) {
this._popup.setContent(content)
.setLatLng(o.latLng);
if (this._map._popup !== this._popup) this._popup.openOn(this._map);
} else {
this._container.style.display = 'block';
this._contentWrapper.innerHTML = content;
}
},
_hide: function() {
this._pinned = false;
this._currentContent = '';
this._map.closePopup();
this._container.style.display = 'none';
this._contentWrapper.innerHTML = '';
L.DomUtil.removeClass(this._container, 'closable');
},
_mouseover: function(o) {
if (o.data) {
L.DomUtil.addClass(this._map._container, 'map-clickable');
} else {
L.DomUtil.removeClass(this._map._container, 'map-clickable');
}
if (this._pinned) return;
var content = this._template('teaser', o.data);
if (content) {
this._show(content, o);
} else {
this._hide();
}
},
_mousemove: function(o) {
if (this._pinned) return;
if (!this.options.follow) return;
this._popup.setLatLng(o.latLng);
},
_navigateTo: function(url) {
window.top.location.href = url;
},
_click: function(o) {
var location_formatted = this._template('location', o.data);
if (this.options.location && location_formatted &&
location_formatted.search(/^https?:/) === 0) {
return this._navigateTo(this._template('location', o.data));
}
if (!this.options.pinnable) return;
var content = this._template('full', o.data);
if (!content && this.options.touchTeaser && L.Browser.touch) {
content = this._template('teaser', o.data);
}
if (content) {
L.DomUtil.addClass(this._container, 'closable');
this._pinned = true;
this._show(content, o);
} else if (this._pinned) {
L.DomUtil.removeClass(this._container, 'closable');
this._pinned = false;
this._hide();
}
},
_onPopupClose: function() {
this._currentContent = null;
this._pinned = false;
},
_createClosebutton: function(container, fn) {
var link = L.DomUtil.create('a', 'close', container);
link.innerHTML = 'close';
link.href = '#';
link.title = 'close';
L.DomEvent
.on(link, 'click', L.DomEvent.stopPropagation)
.on(link, 'mousedown', L.DomEvent.stopPropagation)
.on(link, 'dblclick', L.DomEvent.stopPropagation)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', fn, this);
return link;
},
onAdd: function(map) {
this._map = map;
var className = 'leaflet-control-grid map-tooltip',
container = L.DomUtil.create('div', className),
contentWrapper = L.DomUtil.create('div', 'map-tooltip-content');
// hide the container element initially
container.style.display = 'none';
this._createClosebutton(container, this._hide);
container.appendChild(contentWrapper);
this._contentWrapper = contentWrapper;
this._popup = new L.Popup({ autoPan: false, closeOnClick: false });
map.on('popupclose', this._onPopupClose, this);
L.DomEvent
.disableClickPropagation(container)
// allow people to scroll tooltips with mousewheel
.addListener(container, 'mousewheel', L.DomEvent.stopPropagation);
this._layer
.on('mouseover', this._mouseover, this)
.on('mousemove', this._mousemove, this)
.on('click', this._click, this);
return container;
},
onRemove: function (map) {
map.off('popupclose', this._onPopupClose, this);
this._layer
.off('mouseover', this._mouseover, this)
.off('mousemove', this._mousemove, this)
.off('click', this._click, this);
}
});
module.exports = function(_, options) {
return new GridControl(_, options);
};
},{"./sanitize":51,"./util":55,"mustache":37}],44:[function(require,module,exports){
'use strict';
var util = require('./util'),
url = require('./url'),
request = require('./request'),
grid = require('./grid');
// forked from danzel/L.UTFGrid
var GridLayer = L.Class.extend({
includes: [L.Mixin.Events, require('./load_tilejson')],
options: {
template: function() { return ''; }
},
_mouseOn: null,
_tilejson: {},
_cache: {},
initialize: function(_, options) {
L.Util.setOptions(this, options);
this._loadTileJSON(_);
},
_setTileJSON: function(json) {
util.strict(json, 'object');
L.extend(this.options, {
grids: json.grids,
minZoom: json.minzoom,
maxZoom: json.maxzoom,
bounds: json.bounds && util.lbounds(json.bounds)
});
this._tilejson = json;
this._cache = {};
this._update();
return this;
},
getTileJSON: function() {
return this._tilejson;
},
active: function() {
return !!(this._map && this.options.grids && this.options.grids.length);
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onAdd: function(map) {
this._map = map;
this._update();
this._map
.on('click', this._click, this)
.on('mousemove', this._move, this)
.on('moveend', this._update, this);
},
onRemove: function() {
this._map
.off('click', this._click, this)
.off('mousemove', this._move, this)
.off('moveend', this._update, this);
},
getData: function(latlng, callback) {
if (!this.active()) return;
var map = this._map,
point = map.project(latlng),
tileSize = 256,
resolution = 4,
x = Math.floor(point.x / tileSize),
y = Math.floor(point.y / tileSize),
max = map.options.crs.scale(map.getZoom()) / tileSize;
x = (x + max) % max;
y = (y + max) % max;
this._getTile(map.getZoom(), x, y, function(grid) {
var gridX = Math.floor((point.x - (x * tileSize)) / resolution),
gridY = Math.floor((point.y - (y * tileSize)) / resolution);
callback(grid(gridX, gridY));
});
return this;
},
_click: function(e) {
this.getData(e.latlng, L.bind(function(data) {
this.fire('click', {
latLng: e.latlng,
data: data
});
}, this));
},
_move: function(e) {
this.getData(e.latlng, L.bind(function(data) {
if (data !== this._mouseOn) {
if (this._mouseOn) {
this.fire('mouseout', {
latLng: e.latlng,
data: this._mouseOn
});
}
this.fire('mouseover', {
latLng: e.latlng,
data: data
});
this._mouseOn = data;
} else {
this.fire('mousemove', {
latLng: e.latlng,
data: data
});
}
}, this));
},
_getTileURL: function(tilePoint) {
var urls = this.options.grids,
index = (tilePoint.x + tilePoint.y) % urls.length,
url = urls[index];
return L.Util.template(url, tilePoint);
},
// Load up all required json grid files
_update: function() {
if (!this.active()) return;
var bounds = this._map.getPixelBounds(),
z = this._map.getZoom(),
tileSize = 256;
if (z > this.options.maxZoom || z < this.options.minZoom) return;
var nwTilePoint = new L.Point(
Math.floor(bounds.min.x / tileSize),
Math.floor(bounds.min.y / tileSize)),
seTilePoint = new L.Point(
Math.floor(bounds.max.x / tileSize),
Math.floor(bounds.max.y / tileSize)),
max = this._map.options.crs.scale(z) / tileSize;
for (var x = nwTilePoint.x; x <= seTilePoint.x; x++) {
for (var y = nwTilePoint.y; y <= seTilePoint.y; y++) {
// x wrapped
var xw = (x + max) % max, yw = (y + max) % max;
this._getTile(z, xw, yw);
}
}
},
_getTile: function(z, x, y, callback) {
var key = z + '_' + x + '_' + y,
tilePoint = L.point(x, y);
tilePoint.z = z;
if (!this._tileShouldBeLoaded(tilePoint)) {
return;
}
if (key in this._cache) {
if (!callback) return;
if (typeof this._cache[key] === 'function') {
callback(this._cache[key]); // Already loaded
} else {
this._cache[key].push(callback); // Pending
}
return;
}
this._cache[key] = [];
if (callback) {
this._cache[key].push(callback);
}
request(this._getTileURL(tilePoint), L.bind(function(err, json) {
var callbacks = this._cache[key];
this._cache[key] = grid(json);
for (var i = 0; i < callbacks.length; ++i) {
callbacks[i](this._cache[key]);
}
}, this));
},
_tileShouldBeLoaded: function(tilePoint) {
if (tilePoint.z > this.options.maxZoom || tilePoint.z < this.options.minZoom) {
return false;
}
if (this.options.bounds) {
var tileSize = 256,
nwPoint = tilePoint.multiplyBy(tileSize),
sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
nw = this._map.unproject(nwPoint),
se = this._map.unproject(sePoint),
bounds = new L.LatLngBounds([nw, se]);
if (!this.options.bounds.intersects(bounds)) {
return false;
}
}
return true;
}
});
module.exports = function(_, options) {
return new GridLayer(_, options);
};
},{"./grid":42,"./load_tilejson":46,"./request":50,"./url":54,"./util":55}],45:[function(require,module,exports){
'use strict';
var LegendControl = L.Control.extend({
options: {
position: 'bottomright',
sanitizer: require('./sanitize')
},
initialize: function(options) {
L.setOptions(this, options);
this._legends = {};
},
onAdd: function(map) {
this._container = L.DomUtil.create('div', 'map-legends wax-legends');
L.DomEvent.disableClickPropagation(this._container);
this._update();
return this._container;
},
addLegend: function(text) {
if (!text) { return this; }
if (!this._legends[text]) {
this._legends[text] = 0;
}
this._legends[text]++;
return this._update();
},
removeLegend: function(text) {
if (!text) { return this; }
if (this._legends[text]) this._legends[text]--;
return this._update();
},
_update: function() {
if (!this._map) { return this; }
this._container.innerHTML = '';
var hide = 'none';
for (var i in this._legends) {
if (this._legends.hasOwnProperty(i) && this._legends[i]) {
var div = this._container.appendChild(document.createElement('div'));
div.className = 'map-legend wax-legend';
div.innerHTML = this.options.sanitizer(i);
hide = 'block';
}
}
// hide the control entirely unless there is at least one legend;
// otherwise there will be a small grey blemish on the map.
this._container.style.display = hide;
return this;
}
});
module.exports = function(options) {
return new LegendControl(options);
};
},{"./sanitize":51}],46:[function(require,module,exports){
'use strict';
var request = require('./request'),
url = require('./url'),
util = require('./util');
module.exports = {
_loadTileJSON: function(_) {
if (typeof _ === 'string') {
if (_.indexOf('/') == -1) {
_ = url.base() + _ + '.json';
}
request(url.secureFlag(_), L.bind(function(err, json) {
if (err) {
util.log('could not load TileJSON at ' + _);
this.fire('error', {error: err});
} else if (json) {
this._setTileJSON(json);
this.fire('ready');
}
}, this));
} else if (_ && typeof _ === 'object') {
this._setTileJSON(_);
}
}
};
},{"./request":50,"./url":54,"./util":55}],47:[function(require,module,exports){
'use strict';
var util = require('./util'),
tileLayer = require('./tile_layer'),
markerLayer = require('./marker_layer'),
gridLayer = require('./grid_layer'),
gridControl = require('./grid_control'),
legendControl = require('./legend_control');
var Map = L.Map.extend({
includes: [require('./load_tilejson')],
options: {
tileLayer: {},
markerLayer: {},
gridLayer: {},
legendControl: {},
gridControl: {}
},
_tilejson: {},
initialize: function(element, _, options) {
L.Map.prototype.initialize.call(this, element, options);
// disable the default 'Powered by Leaflet' text
if (this.attributionControl) this.attributionControl.setPrefix('');
if (this.options.tileLayer) {
this.tileLayer = tileLayer(undefined, this.options.tileLayer);
this.addLayer(this.tileLayer);
}
if (this.options.markerLayer) {
this.markerLayer = markerLayer(undefined, this.options.markerLayer);
this.addLayer(this.markerLayer);
}
if (this.options.gridLayer) {
this.gridLayer = gridLayer(undefined, this.options.gridLayer);
this.addLayer(this.gridLayer);
}
if (this.options.gridLayer && this.options.gridControl) {
this.gridControl = gridControl(this.gridLayer, this.options.gridControl);
this.addControl(this.gridControl);
}
if (this.options.legendControl) {
this.legendControl = legendControl(this.options.legendControl);
this.addControl(this.legendControl);
}
this._loadTileJSON(_);
},
// Update certain properties on 'ready' event
addLayer: function(layer) {
if ('on' in layer) { layer.on('ready', L.bind(function() { this._updateLayer(layer); }, this)); }
return L.Map.prototype.addLayer.call(this, layer);
},
// use a javascript object of tilejson data to configure this layer
_setTileJSON: function(_) {
this._tilejson = _;
this._initialize(_);
return this;
},
getTileJSON: function() {
return this._tilejson;
},
_initialize: function(json) {
if (this.tileLayer) {
this.tileLayer._setTileJSON(json);
this._updateLayer(this.tileLayer);
}
if (this.markerLayer && !this.markerLayer.getGeoJSON() && json.data && json.data[0]) {
this.markerLayer.loadURL(json.data[0]);
}
if (this.gridLayer) {
this.gridLayer._setTileJSON(json);
this._updateLayer(this.gridLayer);
}
if (this.legendControl && json.legend) {
this.legendControl.addLegend(json.legend);
}
if (!this._loaded) {
var zoom = json.center[2],
center = L.latLng(json.center[1], json.center[0]);
this.setView(center, zoom);
}
},
_updateLayer: function(layer) {
if (!layer.options) return;
if (this.attributionControl && this._loaded) {
this.attributionControl.addAttribution(layer.options.attribution);
}
if (!(L.stamp(layer) in this._zoomBoundLayers) &&
(layer.options.maxZoom || layer.options.minZoom)) {
this._zoomBoundLayers[L.stamp(layer)] = layer;
}
this._updateZoomLevels();
}
});
module.exports = function(element, _, options) {
return new Map(element, _, options);
};
},{"./grid_control":43,"./grid_layer":44,"./legend_control":45,"./load_tilejson":46,"./marker_layer":49,"./tile_layer":53,"./util":55}],48:[function(require,module,exports){
'use strict';
var url = require('./url'),
sanitize = require('./sanitize');
// mapbox-related markers functionality
// provide an icon from mapbox's simple-style spec and hosted markers
// service
function icon(fp) {
fp = fp || {};
var sizes = {
small: [20, 50],
medium: [30, 70],
large: [35, 90]
},
size = fp['marker-size'] || 'medium',
symbol = (fp['marker-symbol']) ? '-' + fp['marker-symbol'] : '',
color = (fp['marker-color'] || '7e7e7e').replace('#', '');
return L.icon({
iconUrl: url.base() + 'marker/' +
'pin-' + size.charAt(0) + symbol + '+' + color +
// detect and use retina markers, which are x2 resolution
((L.Browser.retina) ? '@2x' : '') + '.png',
iconSize: sizes[size],
iconAnchor: [sizes[size][0] / 2, sizes[size][1] / 2],
popupAnchor: [0, -sizes[size][1] / 2]
});
}
// a factory that provides markers for Leaflet from MapBox's
// [simple-style specification](https://github.com/mapbox/simplestyle-spec)
// and [Markers API](http://mapbox.com/developers/api/#markers).
function style(f, latlon) {
return L.marker(latlon, {
icon: icon(f.properties),
title: f.properties.title
});
}
function createPopup(f, sanitizer) {
if (!f || !f.properties) return '';
var popup = '';
if (f.properties.title) {
popup += '<div class="marker-title">' + f.properties.title + '</div>';
}
if (f.properties.description) {
popup += '<div class="marker-description">' + f.properties.description + '</div>';
}
return (sanitizer || sanitize)(popup);
}
module.exports = {
icon: icon,
style: style,
createPopup: createPopup
};
},{"./sanitize":51,"./url":54}],49:[function(require,module,exports){
'use strict';
var util = require('./util');
var urlhelper = require('./url');
var request = require('./request');
var marker = require('./marker');
// # markerLayer
2013-05-27 20:45:59 +00:00
//
2015-03-08 03:02:36 +00:00
// A layer of markers, loaded from MapBox or else. Adds the ability
// to reset markers, filter them, and load them from a GeoJSON URL.
var MarkerLayer = L.FeatureGroup.extend({
options: {
filter: function() { return true; },
sanitizer: require('./sanitize')
},
initialize: function(_, options) {
L.setOptions(this, options);
this._layers = {};
if (typeof _ === 'string') {
util.idUrl(_, this);
// javascript object of TileJSON data
} else if (_ && typeof _ === 'object') {
this.setGeoJSON(_);
}
},
setGeoJSON: function(_) {
this._geojson = _;
this.clearLayers();
this._initialize(_);
},
getGeoJSON: function() {
return this._geojson;
},
loadURL: function(url) {
url = urlhelper.jsonify(url);
request(url, L.bind(function(err, json) {
if (err) {
util.log('could not load markers at ' + url);
this.fire('error', {error: err});
} else if (json) {
this.setGeoJSON(json);
this.fire('ready');
}
}, this));
return this;
},
loadID: function(id) {
return this.loadURL(urlhelper.base() + id + '/markers.geojson');
},
setFilter: function(_) {
this.options.filter = _;
if (this._geojson) {
this.clearLayers();
this._initialize(this._geojson);
}
return this;
},
getFilter: function() {
return this.options.filter;
},
_initialize: function(json) {
var features = L.Util.isArray(json) ? json : json.features,
i, len;
if (features) {
for (i = 0, len = features.length; i < len; i++) {
// Only add this if geometry or geometries are set and not null
if (features[i].geometries || features[i].geometry || features[i].features) {
this._initialize(features[i]);
}
}
} else if (this.options.filter(json)) {
var layer = L.GeoJSON.geometryToLayer(json, marker.style),
popupHtml = marker.createPopup(json, this.options.sanitizer);
layer.feature = json;
if (popupHtml) {
layer.bindPopup(popupHtml, {
closeButton: false
});
}
this.addLayer(layer);
}
}
});
module.exports = function(_, options) {
return new MarkerLayer(_, options);
};
},{"./marker":48,"./request":50,"./sanitize":51,"./url":54,"./util":55}],50:[function(require,module,exports){
'use strict';
var corslite = require('corslite'),
JSON3 = require('json3'),
strict = require('./util').strict;
module.exports = function(url, callback) {
strict(url, 'string');
strict(callback, 'function');
corslite(url, function(err, resp) {
if (!err && resp) {
// hardcoded grid response
if (resp.responseText[0] == 'g') {
resp = JSON3.parse(resp.responseText
.substring(5, resp.responseText.length - 2));
} else {
resp = JSON3.parse(resp.responseText);
}
}
callback(err, resp);
});
};
},{"./util":55,"corslite":34,"json3":35}],51:[function(require,module,exports){
'use strict';
var html_sanitize = require('../ext/sanitizer/html-sanitizer-bundle.js');
// https://bugzilla.mozilla.org/show_bug.cgi?id=255107
function cleanUrl(url) {
if (/^https?/.test(url.getScheme())) return url.toString();
if ('data' == url.getScheme() && /^image/.test(url.getPath())) {
return url.toString();
}
}
function cleanId(id) {
return id;
}
module.exports = function(_) {
if (!_) return '';
return html_sanitize(_, cleanUrl, cleanId);
};
},{"../ext/sanitizer/html-sanitizer-bundle.js":30}],52:[function(require,module,exports){
'use strict';
var ShareControl = L.Control.extend({
includes: [require('./load_tilejson')],
options: {
position: 'topleft',
url: ''
},
initialize: function(_, options) {
L.setOptions(this, options);
this._loadTileJSON(_);
},
_setTileJSON: function(json) {
this._tilejson = json;
},
onAdd: function(map) {
this._map = map;
var container = L.DomUtil.create('div', 'leaflet-control-mapbox-share leaflet-bar');
var link = L.DomUtil.create('a', 'mapbox-share mapbox-icon mapbox-icon-share', container);
link.href = '#';
L.DomEvent.addListener(link, 'click', this._share, this);
L.DomEvent.disableClickPropagation(container);
// Close any open popups
this._map.on('mousedown', this._clickOut, this);
return container;
},
_clickOut: function(e) {
if (this._popup) {
this._map.removeLayer(this._popup);
this._popup = null;
return;
}
},
_share: function(e) {
L.DomEvent.stop(e);
var tilejson = this._tilejson || this._map._tilejson || {},
twitter = 'http://twitter.com/intent/tweet?status=' +
encodeURIComponent(tilejson.name + '\n' + (tilejson.webpage || window.location)),
facebook = 'https://www.facebook.com/sharer.php?u=' +
encodeURIComponent(this.options.url || tilejson.webpage || window.location) +
'&t=' + encodeURIComponent(tilejson.name),
share =
"<a class='leaflet-popup-close-button' href='#close'>×</a>" +
("<h3>Share this map</h3>" +
"<div class='mapbox-share-buttons'><a class='mapbox-share-facebook mapbox-icon mapbox-icon-facebook' target='_blank' href='{{facebook}}'>Facebook</a>" +
"<a class='mapbox-share-twitter mapbox-icon mapbox-icon-twitter' target='_blank' href='{{twitter}}'>Twitter</a></div>")
.replace('{{twitter}}', twitter)
.replace('{{facebook}}', facebook) +
("<h3>Get the embed code</h3>" +
"<small>Copy and paste this HTML into your website or blog.</small>") +
"<textarea rows=4>{{value}}</textarea>"
.replace('{{value}}', ("&lt;iframe width='500' height='300' frameBorder='0' src='{{embed}}'&gt;&lt;/iframe&gt;"
.replace('{{embed}}', tilejson.embed || window.location)));
this._popup = L.marker(this._map.getCenter(), {
zIndexOffset: 10000,
icon: L.divIcon({
className: 'mapbox-share-popup',
iconSize: L.point(360, 240),
iconAnchor: L.point(180, 120),
html: share
})
})
.on('mousedown', function(e) {
L.DomEvent.stopPropagation(e.originalEvent);
})
.on('click', clickPopup, this).addTo(this._map);
function clickPopup(e) {
if (e.originalEvent && e.originalEvent.target.nodeName === 'TEXTAREA') {
var target = e.originalEvent.target;
target.focus();
target.select();
} else if (e.originalEvent && e.originalEvent.target.getAttribute('href') === '#close') {
this._clickOut(e);
}
L.DomEvent.stop(e.originalEvent);
}
}
});
module.exports = function(_, options) {
return new ShareControl(_, options);
};
},{"./load_tilejson":46}],53:[function(require,module,exports){
'use strict';
var util = require('./util'),
url = require('./url');
var TileLayer = L.TileLayer.extend({
includes: [require('./load_tilejson')],
options: {
format: 'png'
},
// http://mapbox.com/developers/api/#image_quality
formats: [
'png',
// PNG
'png32', 'png64', 'png128', 'png256',
// JPG
'jpg70', 'jpg80', 'jpg90'],
initialize: function(_, options) {
L.TileLayer.prototype.initialize.call(this, undefined, options);
this._tilejson = {};
if (options && options.detectRetina &&
L.Browser.retina && options.retinaVersion) {
_ = options.retinaVersion;
}
if (options && options.format) {
util.strict_oneof(options.format, this.formats);
}
this._loadTileJSON(_);
},
setFormat: function(_) {
util.strict(_, 'string');
this.options.format = _;
this.redraw();
return this;
},
// disable the setUrl function, which is not available on mapbox tilelayers
setUrl: null,
_setTileJSON: function(json) {
util.strict(json, 'object');
L.extend(this.options, {
tiles: json.tiles,
attribution: json.attribution,
minZoom: json.minzoom,
maxZoom: json.maxzoom,
tms: json.scheme === 'tms',
bounds: json.bounds && util.lbounds(json.bounds)
});
this._tilejson = json;
this.redraw();
return this;
},
getTileJSON: function() {
return this._tilejson;
},
// this is an exception to mapbox.js naming rules because it's called
// by `L.map`
getTileUrl: function(tilePoint) {
var tiles = this.options.tiles,
index = Math.abs(tilePoint.x + tilePoint.y) % tiles.length,
url = tiles[index];
var templated = L.Util.template(url, tilePoint);
if (!templated) return templated;
else return templated.replace('.png', '.' + this.options.format);
},
// TileJSON.TileLayers are added to the map immediately, so that they get
// the desired z-index, but do not update until the TileJSON has been loaded.
_update: function() {
if (this.options.tiles) {
L.TileLayer.prototype._update.call(this);
}
}
});
module.exports = function(_, options) {
return new TileLayer(_, options);
};
},{"./load_tilejson":46,"./url":54,"./util":55}],54:[function(require,module,exports){
'use strict';
var config = require('./config');
// Return the base url of a specific version of MapBox's API.
2013-05-27 20:45:59 +00:00
//
2015-03-08 03:02:36 +00:00
// `hash`, if provided must be a number and is used to distribute requests
// against multiple `CNAME`s in order to avoid connection limits in browsers
module.exports = {
isSSL: function() {
return 'https:' === document.location.protocol || config.FORCE_HTTPS;
},
base: function(hash) {
// By default, use public HTTP urls
// Support HTTPS if the user has specified HTTPS urls to use, and this
// page is under HTTPS
var urls = this.isSSL() ? config.HTTPS_URLS : config.HTTP_URLS;
if (hash === undefined || typeof hash !== 'number') {
return urls[0];
} else {
return urls[hash % urls.length];
}
},
// Requests that contain URLs need a secure flag appended
// to their URLs so that the server knows to send SSL-ified
// resource references.
secureFlag: function(url) {
if (!this.isSSL()) return url;
else if (url.match(/(\?|&)secure/)) return url;
else if (url.indexOf('?') !== -1) return url + '&secure';
else return url + '?secure';
},
// Convert a JSONP url to a JSON URL. (MapBox TileJSON sometimes hardcodes JSONP.)
jsonify: function(url) {
return url.replace(/\.(geo)?jsonp(?=$|\?)/, '.$1json');
}
};
},{"./config":39}],55:[function(require,module,exports){
'use strict';
module.exports = {
idUrl: function(_, t) {
if (_.indexOf('/') == -1) t.loadID(_);
else t.loadURL(_);
},
log: function(_) {
if (console && typeof console.error === 'function') {
console.error(_);
}
},
strict: function(_, type) {
if (typeof _ !== type) {
throw new Error('Invalid argument: ' + type + ' expected');
}
},
strict_instance: function(_, klass, name) {
if (!(_ instanceof klass)) {
throw new Error('Invalid argument: ' + name + ' expected');
}
},
strict_oneof: function(_, values) {
if (values.indexOf(_) == -1) {
throw new Error('Invalid argument: ' + _ + ' given, valid values are ' +
values.join(', '));
}
},
lbounds: function(_) {
// leaflet-compatible bounds, since leaflet does not do geojson
return new L.LatLngBounds([[_[1], _[0]], [_[3], _[2]]]);
}
};
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
},{}],56:[function(require,module,exports){
var ich = require('icanhaz')
module.exports.initiateTableFilter = function(opts) {
2013-08-04 05:24:50 +00:00
$('.clear').on("click", function() {
2013-05-27 20:45:59 +00:00
$(this.id + ".noMatches").css("visibility", "hidden")
2015-03-08 03:02:36 +00:00
$(this.id + opts.filterDiv).val("")
makeTable(opts)
2013-05-27 20:45:59 +00:00
})
2015-03-08 03:02:36 +00:00
$(opts.filterDiv).keyup(function(e) {
2013-05-27 20:45:59 +00:00
var text = $(e.target).val()
2015-03-08 03:02:36 +00:00
searchTable(opts, text)
2013-05-27 20:45:59 +00:00
})
}
2015-03-08 03:02:36 +00:00
module.exports.searchTable = searchTable
function searchTable(opts, searchTerm) {
2013-05-27 20:45:59 +00:00
var filteredList = []
2015-03-08 03:02:36 +00:00
opts.data.forEach(function(object) {
2013-05-27 20:45:59 +00:00
var stringObject = JSON.stringify(object).toLowerCase()
2013-08-04 05:24:50 +00:00
if (stringObject.match(searchTerm.toLowerCase())) filteredList.push(object)
2013-05-27 20:45:59 +00:00
})
if (filteredList.length === 0) {
$(".noMatches").css("visibility", "inherit")
2015-03-08 03:02:36 +00:00
makeTable(opts, filteredList)
}
else {
$(".noMatches").css("visibility", "hidden")
makeTable(opts, filteredList)
2013-05-27 20:45:59 +00:00
}
}
2015-03-08 03:02:36 +00:00
module.exports.sortThings = sortThings
function sortThings(opts, sorter, sorted, tableDiv) {
if (opts.tableDiv != tableDiv) return
opts.data.sort(function(a,b){
2013-05-27 20:45:59 +00:00
if (a[sorter]<b[sorter]) return -1
if (a[sorter]>b[sorter]) return 1
return 0
})
2015-03-08 03:02:36 +00:00
if (sorted === "descending") opts.data.reverse()
makeTable(opts)
2013-08-04 05:24:50 +00:00
var header
2013-05-27 20:45:59 +00:00
$(tableDiv + " .tHeader").each(function(i, el){
var contents = resolveDataTitle($(el).text())
if (contents === sorter) header = el
})
$(header).attr("data-sorted", sorted)
}
2015-03-08 03:02:36 +00:00
module.exports.resolveDataTitle = resolveDataTitle
2013-05-27 20:45:59 +00:00
function resolveDataTitle(string) {
var adjusted = string.toLowerCase().replace(/\s/g, '').replace(/\W/g, '')
return adjusted
}
2015-03-08 03:02:36 +00:00
module.exports.initiateTableSorter = initiateTableSorter
function initiateTableSorter(options) {
$(document).on("click", ".tHeader", sendToSort)
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
function sendToSort(event) {
var tableDiv = "#" + $(event.target).closest("div").attr("id")
var sorted = $(event.target).attr("data-sorted")
if (sorted) {
if (sorted === "descending") sorted = "ascending"
else sorted = "descending"
2013-08-04 05:24:50 +00:00
}
2015-03-08 03:02:36 +00:00
else { sorted = "ascending" }
var sorter = resolveDataTitle(event.target.innerHTML)
var sortInfo = {"sorter": sorter, "sorted": sorted, "tableDiv": tableDiv}
sortThings(options, sorter, sorted, tableDiv)
}
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
module.exports.makeTable = makeTable
function makeTable(opts, filteredList) {
initiateTableSorter(opts)
if (filteredList) var data = filteredList
else var data = opts.data
var tableId = opts.tableDiv.slice(1)
if (!opts.pagination) {
table(data, opts)
} else {
var allRows = data.length
var totalPages = Math.ceil(allRows / opts.pagination)
var currentPage = 1
var currentStart = (currentPage * opts.pagination) - opts.pagination
var currentEnd = currentPage * opts.pagination
var currentRows = data.slice(currentStart, currentEnd)
table(currentRows, opts)
if (opts.data.length > opts.pagination) writePreNext(opts.tableDiv, currentPage, currentPage, totalPages, data, opts.pagination)
}
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
module.exports.setPagClicks = setPagClicks
function setPagClicks(data, tableId, currentPage, pagination, totalPages) {
$(".pagination-pre-" + tableId).addClass("no-pag")
$(document).on("click", (".pagination-next-" + tableId), function() {
if ($(this).hasClass("no-pag")) return
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
currentPage = currentPage + 1
var nextPage = currentPage + 1
currentStart = (currentPage * pagination) - pagination
currentEnd = currentPage * pagination
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
if (currentPage >= totalPages) {
currentRows = data.slice(currentStart, currentEnd)
table(currentRows, "#" + tableId)
setPreNext("#" + tableId, currentPage, currentPage, totalPages)
$(".pagination-next-" + tableId).addClass("no-pag")
$(".pagination-next-" + tableId)
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
else {
currentRows = data.slice(currentStart, currentEnd)
table(currentRows, "#" + tableId)
setPreNext("#" + tableId, currentPage, currentPage, totalPages)
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
})
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
$(document).on("click", (".pagination-pre-" + tableId), function() {
if (currentPage > 1) $(this).removeClass("no-pag")
if ($(this).hasClass("no-pag")) return
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
// if ((currentPage) === 2) {
// $(".pagination-pre-" + tableId).addClass("no-pag"); console.log("on page one!", currentPage)
// }
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
currentPage = currentPage - 1
var nextPage = currentPage + 1
currentStart = (currentPage * pagination) - pagination
currentEnd = currentPage * pagination
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
// currentRows = data.slice(currentStart, currentEnd)
// table(currentRows, "#" + tableId)
// setPreNext("#" + tableId, currentPage, currentPage, totalPages)
2013-05-27 20:45:59 +00:00
2015-03-08 03:02:36 +00:00
if (currentPage === 1) {
currentRows = data.slice(currentStart, currentEnd)
table(currentRows, "#" + tableId)
setPreNext("#" + tableId, currentPage, currentPage, totalPages)
$(".pagination-pre-" + tableId).addClass("no-pag")
}
else {
currentRows = data.slice(currentStart, currentEnd)
table(currentRows, "#" + tableId)
setPreNext("#" + tableId, currentPage, currentPage, totalPages)
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
2013-05-27 20:45:59 +00:00
})
}
2015-03-08 03:02:36 +00:00
module.exports.setPreNext = setPreNext
function setPreNext(targetDiv, currentPage, currentPage, totalPages, data, pagination) {
var tableId = targetDiv.slice(1)
$(targetDiv).append("<div id='Pagination' pageno='" + currentPage + "'" + "class='table-pagination'>Showing page "
+ currentPage + " of " + totalPages + " <a class='pagination-pre-" + tableId + "'>Previous</a>" +
" <a class='pagination-next-" + tableId + "'>Next</a></p></div>" )
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
module.exports.writePreNext = writePreNext
function writePreNext(targetDiv, currentPage, currentPage, totalPages, data, pagination) {
var tableId = targetDiv.slice(1)
$(targetDiv).append("<div id='Pagination' pageno='" + currentPage + "'" + "class='table-pagination'>Showing page "
+ currentPage + " of " + totalPages + " <a class='pagination-pre-" + tableId + "'>Previous</a>" +
" <a class='pagination-next-" + tableId + "'>Next</a></p></div>" )
setPagClicks(data, tableId, currentPage, pagination, totalPages)
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
module.exports.clearPreNext = clearPreNext
function clearPreNext() {
$(".table-pagination").attr("display", "none")
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
module.exports.table = table
function table(data, opts) {
if (opts.templateID) {
var templateID = opts.templateID
} else var templateID = opts.tableDiv.replace("#", "")
var tableContents = ich[templateID](
{
rows: data
2013-05-27 20:45:59 +00:00
})
2015-03-08 03:02:36 +00:00
$(opts.tableDiv).html(tableContents)
2013-05-27 20:45:59 +00:00
}
2015-03-08 03:02:36 +00:00
},{"icanhaz":2}]},{},[1])
;