text: ;(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
  
    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
  //
  // 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.
  //
  // `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]]]);
      }
  };
  
  },{}],56:[function(require,module,exports){
  var ich = require('icanhaz')
  
  module.exports.initiateTableFilter = function(opts) {
    $('.clear').on("click", function() {
      $(this.id + ".noMatches").css("visibility", "hidden")
      $(this.id + opts.filterDiv).val("")
      makeTable(opts)
    })
    $(opts.filterDiv).keyup(function(e) {
      var text = $(e.target).val()
      searchTable(opts, text)
    })
  }
  
  module.exports.searchTable = searchTable
  function searchTable(opts, searchTerm) {
    var filteredList = []
    opts.data.forEach(function(object) {
      var stringObject = JSON.stringify(object).toLowerCase()
      if (stringObject.match(searchTerm.toLowerCase())) filteredList.push(object)
    })
    if (filteredList.length === 0) {
      $(".noMatches").css("visibility", "inherit")
      makeTable(opts, filteredList)
    }
    else {
      $(".noMatches").css("visibility", "hidden")
      makeTable(opts, filteredList)
    }
  }
  module.exports.sortThings = sortThings
  function sortThings(opts, sorter, sorted, tableDiv) {
    if (opts.tableDiv != tableDiv) return
    opts.data.sort(function(a,b){
      if (a[sorter]<b[sorter]) return -1
      if (a[sorter]>b[sorter]) return 1
      return 0
    })
    if (sorted === "descending") opts.data.reverse()
    makeTable(opts)
    var header
    $(tableDiv + " .tHeader").each(function(i, el){
      var contents = resolveDataTitle($(el).text())
      if (contents === sorter) header = el
    })
    $(header).attr("data-sorted", sorted)
  }
  
  module.exports.resolveDataTitle = resolveDataTitle
  function resolveDataTitle(string) {
    var adjusted = string.toLowerCase().replace(/\s/g, '').replace(/\W/g, '')
    return adjusted
  }
  
  module.exports.initiateTableSorter = initiateTableSorter
  function initiateTableSorter(options) {
    $(document).on("click", ".tHeader", sendToSort)
  
    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"
      }
      else { sorted = "ascending" }
      var sorter = resolveDataTitle(event.target.innerHTML)
      var sortInfo = {"sorter": sorter, "sorted": sorted, "tableDiv": tableDiv}
      sortThings(options, sorter, sorted, tableDiv)
    }
  }
  
  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)
    }    
  }
  
  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
  
      currentPage = currentPage + 1
      var nextPage = currentPage + 1
      currentStart = (currentPage * pagination) - pagination
      currentEnd = currentPage * pagination
  
      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)
      }
      else {
        currentRows = data.slice(currentStart, currentEnd)
        table(currentRows, "#" + tableId)
        setPreNext("#" + tableId, currentPage, currentPage, totalPages)
      }
  })
  
    $(document).on("click", (".pagination-pre-" + tableId), function() {
      if (currentPage > 1) $(this).removeClass("no-pag")
      if ($(this).hasClass("no-pag")) return
  
      // if ((currentPage) === 2) {
      //   $(".pagination-pre-" + tableId).addClass("no-pag"); console.log("on page one!", currentPage)
      // }
  
      currentPage = currentPage - 1
      var nextPage = currentPage + 1
      currentStart = (currentPage * pagination) - pagination
      currentEnd = currentPage * pagination
  
      // currentRows = data.slice(currentStart, currentEnd)
      // table(currentRows, "#" + tableId)
      // setPreNext("#" + tableId, currentPage, currentPage, totalPages)
  
      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)
      }
      
    })
  }
  
  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>" )
  }
  
  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)
  }
  
  module.exports.clearPreNext = clearPreNext
  function clearPreNext() {
    $(".table-pagination").attr("display", "none")
  }
  
  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
    })
    $(opts.tableDiv).html(tableContents)
  }
  },{"icanhaz":2}]},{},[1])
  ;