Le repo des sources pour le site web des JM2L
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

9180 lignes
218 KiB

  1. /*
  2. Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
  3. (c) 2010-2013, Vladimir Agafonkin
  4. (c) 2010-2011, CloudMade
  5. */
  6. (function (window, document, undefined) {
  7. var oldL = window.L,
  8. L = {};
  9. L.version = '0.7.3';
  10. // define Leaflet for Node module pattern loaders, including Browserify
  11. if (typeof module === 'object' && typeof module.exports === 'object') {
  12. module.exports = L;
  13. // define Leaflet as an AMD module
  14. } else if (typeof define === 'function' && define.amd) {
  15. define(L);
  16. }
  17. // define Leaflet as a global L variable, saving the original L to restore later if needed
  18. L.noConflict = function () {
  19. window.L = oldL;
  20. return this;
  21. };
  22. window.L = L;
  23. /*
  24. * L.Util contains various utility functions used throughout Leaflet code.
  25. */
  26. L.Util = {
  27. extend: function (dest) { // (Object[, Object, ...]) ->
  28. var sources = Array.prototype.slice.call(arguments, 1),
  29. i, j, len, src;
  30. for (j = 0, len = sources.length; j < len; j++) {
  31. src = sources[j] || {};
  32. for (i in src) {
  33. if (src.hasOwnProperty(i)) {
  34. dest[i] = src[i];
  35. }
  36. }
  37. }
  38. return dest;
  39. },
  40. bind: function (fn, obj) { // (Function, Object) -> Function
  41. var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
  42. return function () {
  43. return fn.apply(obj, args || arguments);
  44. };
  45. },
  46. stamp: (function () {
  47. var lastId = 0,
  48. key = '_leaflet_id';
  49. return function (obj) {
  50. obj[key] = obj[key] || ++lastId;
  51. return obj[key];
  52. };
  53. }()),
  54. invokeEach: function (obj, method, context) {
  55. var i, args;
  56. if (typeof obj === 'object') {
  57. args = Array.prototype.slice.call(arguments, 3);
  58. for (i in obj) {
  59. method.apply(context, [i, obj[i]].concat(args));
  60. }
  61. return true;
  62. }
  63. return false;
  64. },
  65. limitExecByInterval: function (fn, time, context) {
  66. var lock, execOnUnlock;
  67. return function wrapperFn() {
  68. var args = arguments;
  69. if (lock) {
  70. execOnUnlock = true;
  71. return;
  72. }
  73. lock = true;
  74. setTimeout(function () {
  75. lock = false;
  76. if (execOnUnlock) {
  77. wrapperFn.apply(context, args);
  78. execOnUnlock = false;
  79. }
  80. }, time);
  81. fn.apply(context, args);
  82. };
  83. },
  84. falseFn: function () {
  85. return false;
  86. },
  87. formatNum: function (num, digits) {
  88. var pow = Math.pow(10, digits || 5);
  89. return Math.round(num * pow) / pow;
  90. },
  91. trim: function (str) {
  92. return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
  93. },
  94. splitWords: function (str) {
  95. return L.Util.trim(str).split(/\s+/);
  96. },
  97. setOptions: function (obj, options) {
  98. obj.options = L.extend({}, obj.options, options);
  99. return obj.options;
  100. },
  101. getParamString: function (obj, existingUrl, uppercase) {
  102. var params = [];
  103. for (var i in obj) {
  104. params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
  105. }
  106. return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
  107. },
  108. template: function (str, data) {
  109. return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
  110. var value = data[key];
  111. if (value === undefined) {
  112. throw new Error('No value provided for variable ' + str);
  113. } else if (typeof value === 'function') {
  114. value = value(data);
  115. }
  116. return value;
  117. });
  118. },
  119. isArray: Array.isArray || function (obj) {
  120. return (Object.prototype.toString.call(obj) === '[object Array]');
  121. },
  122. emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
  123. };
  124. (function () {
  125. // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  126. function getPrefixed(name) {
  127. var i, fn,
  128. prefixes = ['webkit', 'moz', 'o', 'ms'];
  129. for (i = 0; i < prefixes.length && !fn; i++) {
  130. fn = window[prefixes[i] + name];
  131. }
  132. return fn;
  133. }
  134. var lastTime = 0;
  135. function timeoutDefer(fn) {
  136. var time = +new Date(),
  137. timeToCall = Math.max(0, 16 - (time - lastTime));
  138. lastTime = time + timeToCall;
  139. return window.setTimeout(fn, timeToCall);
  140. }
  141. var requestFn = window.requestAnimationFrame ||
  142. getPrefixed('RequestAnimationFrame') || timeoutDefer;
  143. var cancelFn = window.cancelAnimationFrame ||
  144. getPrefixed('CancelAnimationFrame') ||
  145. getPrefixed('CancelRequestAnimationFrame') ||
  146. function (id) { window.clearTimeout(id); };
  147. L.Util.requestAnimFrame = function (fn, context, immediate, element) {
  148. fn = L.bind(fn, context);
  149. if (immediate && requestFn === timeoutDefer) {
  150. fn();
  151. } else {
  152. return requestFn.call(window, fn, element);
  153. }
  154. };
  155. L.Util.cancelAnimFrame = function (id) {
  156. if (id) {
  157. cancelFn.call(window, id);
  158. }
  159. };
  160. }());
  161. // shortcuts for most used utility functions
  162. L.extend = L.Util.extend;
  163. L.bind = L.Util.bind;
  164. L.stamp = L.Util.stamp;
  165. L.setOptions = L.Util.setOptions;
  166. /*
  167. * L.Class powers the OOP facilities of the library.
  168. * Thanks to John Resig and Dean Edwards for inspiration!
  169. */
  170. L.Class = function () {};
  171. L.Class.extend = function (props) {
  172. // extended class with the new prototype
  173. var NewClass = function () {
  174. // call the constructor
  175. if (this.initialize) {
  176. this.initialize.apply(this, arguments);
  177. }
  178. // call all constructor hooks
  179. if (this._initHooks) {
  180. this.callInitHooks();
  181. }
  182. };
  183. // instantiate class without calling constructor
  184. var F = function () {};
  185. F.prototype = this.prototype;
  186. var proto = new F();
  187. proto.constructor = NewClass;
  188. NewClass.prototype = proto;
  189. //inherit parent's statics
  190. for (var i in this) {
  191. if (this.hasOwnProperty(i) && i !== 'prototype') {
  192. NewClass[i] = this[i];
  193. }
  194. }
  195. // mix static properties into the class
  196. if (props.statics) {
  197. L.extend(NewClass, props.statics);
  198. delete props.statics;
  199. }
  200. // mix includes into the prototype
  201. if (props.includes) {
  202. L.Util.extend.apply(null, [proto].concat(props.includes));
  203. delete props.includes;
  204. }
  205. // merge options
  206. if (props.options && proto.options) {
  207. props.options = L.extend({}, proto.options, props.options);
  208. }
  209. // mix given properties into the prototype
  210. L.extend(proto, props);
  211. proto._initHooks = [];
  212. var parent = this;
  213. // jshint camelcase: false
  214. NewClass.__super__ = parent.prototype;
  215. // add method for calling all hooks
  216. proto.callInitHooks = function () {
  217. if (this._initHooksCalled) { return; }
  218. if (parent.prototype.callInitHooks) {
  219. parent.prototype.callInitHooks.call(this);
  220. }
  221. this._initHooksCalled = true;
  222. for (var i = 0, len = proto._initHooks.length; i < len; i++) {
  223. proto._initHooks[i].call(this);
  224. }
  225. };
  226. return NewClass;
  227. };
  228. // method for adding properties to prototype
  229. L.Class.include = function (props) {
  230. L.extend(this.prototype, props);
  231. };
  232. // merge new default options to the Class
  233. L.Class.mergeOptions = function (options) {
  234. L.extend(this.prototype.options, options);
  235. };
  236. // add a constructor hook
  237. L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
  238. var args = Array.prototype.slice.call(arguments, 1);
  239. var init = typeof fn === 'function' ? fn : function () {
  240. this[fn].apply(this, args);
  241. };
  242. this.prototype._initHooks = this.prototype._initHooks || [];
  243. this.prototype._initHooks.push(init);
  244. };
  245. /*
  246. * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
  247. */
  248. var eventsKey = '_leaflet_events';
  249. L.Mixin = {};
  250. L.Mixin.Events = {
  251. addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
  252. // types can be a map of types/handlers
  253. if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
  254. var events = this[eventsKey] = this[eventsKey] || {},
  255. contextId = context && context !== this && L.stamp(context),
  256. i, len, event, type, indexKey, indexLenKey, typeIndex;
  257. // types can be a string of space-separated words
  258. types = L.Util.splitWords(types);
  259. for (i = 0, len = types.length; i < len; i++) {
  260. event = {
  261. action: fn,
  262. context: context || this
  263. };
  264. type = types[i];
  265. if (contextId) {
  266. // store listeners of a particular context in a separate hash (if it has an id)
  267. // gives a major performance boost when removing thousands of map layers
  268. indexKey = type + '_idx';
  269. indexLenKey = indexKey + '_len';
  270. typeIndex = events[indexKey] = events[indexKey] || {};
  271. if (!typeIndex[contextId]) {
  272. typeIndex[contextId] = [];
  273. // keep track of the number of keys in the index to quickly check if it's empty
  274. events[indexLenKey] = (events[indexLenKey] || 0) + 1;
  275. }
  276. typeIndex[contextId].push(event);
  277. } else {
  278. events[type] = events[type] || [];
  279. events[type].push(event);
  280. }
  281. }
  282. return this;
  283. },
  284. hasEventListeners: function (type) { // (String) -> Boolean
  285. var events = this[eventsKey];
  286. return !!events && ((type in events && events[type].length > 0) ||
  287. (type + '_idx' in events && events[type + '_idx_len'] > 0));
  288. },
  289. removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
  290. if (!this[eventsKey]) {
  291. return this;
  292. }
  293. if (!types) {
  294. return this.clearAllEventListeners();
  295. }
  296. if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
  297. var events = this[eventsKey],
  298. contextId = context && context !== this && L.stamp(context),
  299. i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
  300. types = L.Util.splitWords(types);
  301. for (i = 0, len = types.length; i < len; i++) {
  302. type = types[i];
  303. indexKey = type + '_idx';
  304. indexLenKey = indexKey + '_len';
  305. typeIndex = events[indexKey];
  306. if (!fn) {
  307. // clear all listeners for a type if function isn't specified
  308. delete events[type];
  309. delete events[indexKey];
  310. delete events[indexLenKey];
  311. } else {
  312. listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];
  313. if (listeners) {
  314. for (j = listeners.length - 1; j >= 0; j--) {
  315. if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
  316. removed = listeners.splice(j, 1);
  317. // set the old action to a no-op, because it is possible
  318. // that the listener is being iterated over as part of a dispatch
  319. removed[0].action = L.Util.falseFn;
  320. }
  321. }
  322. if (context && typeIndex && (listeners.length === 0)) {
  323. delete typeIndex[contextId];
  324. events[indexLenKey]--;
  325. }
  326. }
  327. }
  328. }
  329. return this;
  330. },
  331. clearAllEventListeners: function () {
  332. delete this[eventsKey];
  333. return this;
  334. },
  335. fireEvent: function (type, data) { // (String[, Object])
  336. if (!this.hasEventListeners(type)) {
  337. return this;
  338. }
  339. var event = L.Util.extend({}, data, { type: type, target: this });
  340. var events = this[eventsKey],
  341. listeners, i, len, typeIndex, contextId;
  342. if (events[type]) {
  343. // make sure adding/removing listeners inside other listeners won't cause infinite loop
  344. listeners = events[type].slice();
  345. for (i = 0, len = listeners.length; i < len; i++) {
  346. listeners[i].action.call(listeners[i].context, event);
  347. }
  348. }
  349. // fire event for the context-indexed listeners as well
  350. typeIndex = events[type + '_idx'];
  351. for (contextId in typeIndex) {
  352. listeners = typeIndex[contextId].slice();
  353. if (listeners) {
  354. for (i = 0, len = listeners.length; i < len; i++) {
  355. listeners[i].action.call(listeners[i].context, event);
  356. }
  357. }
  358. }
  359. return this;
  360. },
  361. addOneTimeEventListener: function (types, fn, context) {
  362. if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
  363. var handler = L.bind(function () {
  364. this
  365. .removeEventListener(types, fn, context)
  366. .removeEventListener(types, handler, context);
  367. }, this);
  368. return this
  369. .addEventListener(types, fn, context)
  370. .addEventListener(types, handler, context);
  371. }
  372. };
  373. L.Mixin.Events.on = L.Mixin.Events.addEventListener;
  374. L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
  375. L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
  376. L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
  377. /*
  378. * L.Browser handles different browser and feature detections for internal Leaflet use.
  379. */
  380. (function () {
  381. var ie = 'ActiveXObject' in window,
  382. ielt9 = ie && !document.addEventListener,
  383. // terrible browser detection to work around Safari / iOS / Android browser bugs
  384. ua = navigator.userAgent.toLowerCase(),
  385. webkit = ua.indexOf('webkit') !== -1,
  386. chrome = ua.indexOf('chrome') !== -1,
  387. phantomjs = ua.indexOf('phantom') !== -1,
  388. android = ua.indexOf('android') !== -1,
  389. android23 = ua.search('android [23]') !== -1,
  390. gecko = ua.indexOf('gecko') !== -1,
  391. mobile = typeof orientation !== undefined + '',
  392. msPointer = window.navigator && window.navigator.msPointerEnabled &&
  393. window.navigator.msMaxTouchPoints && !window.PointerEvent,
  394. pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) ||
  395. msPointer,
  396. retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
  397. ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
  398. window.matchMedia('(min-resolution:144dpi)').matches),
  399. doc = document.documentElement,
  400. ie3d = ie && ('transition' in doc.style),
  401. webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
  402. gecko3d = 'MozPerspective' in doc.style,
  403. opera3d = 'OTransition' in doc.style,
  404. any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
  405. // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch.
  406. // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151
  407. var touch = !window.L_NO_TOUCH && !phantomjs && (function () {
  408. var startName = 'ontouchstart';
  409. // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc.
  410. if (pointer || (startName in doc)) {
  411. return true;
  412. }
  413. // Firefox/Gecko
  414. var div = document.createElement('div'),
  415. supported = false;
  416. if (!div.setAttribute) {
  417. return false;
  418. }
  419. div.setAttribute(startName, 'return;');
  420. if (typeof div[startName] === 'function') {
  421. supported = true;
  422. }
  423. div.removeAttribute(startName);
  424. div = null;
  425. return supported;
  426. }());
  427. L.Browser = {
  428. ie: ie,
  429. ielt9: ielt9,
  430. webkit: webkit,
  431. gecko: gecko && !webkit && !window.opera && !ie,
  432. android: android,
  433. android23: android23,
  434. chrome: chrome,
  435. ie3d: ie3d,
  436. webkit3d: webkit3d,
  437. gecko3d: gecko3d,
  438. opera3d: opera3d,
  439. any3d: any3d,
  440. mobile: mobile,
  441. mobileWebkit: mobile && webkit,
  442. mobileWebkit3d: mobile && webkit3d,
  443. mobileOpera: mobile && window.opera,
  444. touch: touch,
  445. msPointer: msPointer,
  446. pointer: pointer,
  447. retina: retina
  448. };
  449. }());
  450. /*
  451. * L.Point represents a point with x and y coordinates.
  452. */
  453. L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
  454. this.x = (round ? Math.round(x) : x);
  455. this.y = (round ? Math.round(y) : y);
  456. };
  457. L.Point.prototype = {
  458. clone: function () {
  459. return new L.Point(this.x, this.y);
  460. },
  461. // non-destructive, returns a new point
  462. add: function (point) {
  463. return this.clone()._add(L.point(point));
  464. },
  465. // destructive, used directly for performance in situations where it's safe to modify existing point
  466. _add: function (point) {
  467. this.x += point.x;
  468. this.y += point.y;
  469. return this;
  470. },
  471. subtract: function (point) {
  472. return this.clone()._subtract(L.point(point));
  473. },
  474. _subtract: function (point) {
  475. this.x -= point.x;
  476. this.y -= point.y;
  477. return this;
  478. },
  479. divideBy: function (num) {
  480. return this.clone()._divideBy(num);
  481. },
  482. _divideBy: function (num) {
  483. this.x /= num;
  484. this.y /= num;
  485. return this;
  486. },
  487. multiplyBy: function (num) {
  488. return this.clone()._multiplyBy(num);
  489. },
  490. _multiplyBy: function (num) {
  491. this.x *= num;
  492. this.y *= num;
  493. return this;
  494. },
  495. round: function () {
  496. return this.clone()._round();
  497. },
  498. _round: function () {
  499. this.x = Math.round(this.x);
  500. this.y = Math.round(this.y);
  501. return this;
  502. },
  503. floor: function () {
  504. return this.clone()._floor();
  505. },
  506. _floor: function () {
  507. this.x = Math.floor(this.x);
  508. this.y = Math.floor(this.y);
  509. return this;
  510. },
  511. distanceTo: function (point) {
  512. point = L.point(point);
  513. var x = point.x - this.x,
  514. y = point.y - this.y;
  515. return Math.sqrt(x * x + y * y);
  516. },
  517. equals: function (point) {
  518. point = L.point(point);
  519. return point.x === this.x &&
  520. point.y === this.y;
  521. },
  522. contains: function (point) {
  523. point = L.point(point);
  524. return Math.abs(point.x) <= Math.abs(this.x) &&
  525. Math.abs(point.y) <= Math.abs(this.y);
  526. },
  527. toString: function () {
  528. return 'Point(' +
  529. L.Util.formatNum(this.x) + ', ' +
  530. L.Util.formatNum(this.y) + ')';
  531. }
  532. };
  533. L.point = function (x, y, round) {
  534. if (x instanceof L.Point) {
  535. return x;
  536. }
  537. if (L.Util.isArray(x)) {
  538. return new L.Point(x[0], x[1]);
  539. }
  540. if (x === undefined || x === null) {
  541. return x;
  542. }
  543. return new L.Point(x, y, round);
  544. };
  545. /*
  546. * L.Bounds represents a rectangular area on the screen in pixel coordinates.
  547. */
  548. L.Bounds = function (a, b) { //(Point, Point) or Point[]
  549. if (!a) { return; }
  550. var points = b ? [a, b] : a;
  551. for (var i = 0, len = points.length; i < len; i++) {
  552. this.extend(points[i]);
  553. }
  554. };
  555. L.Bounds.prototype = {
  556. // extend the bounds to contain the given point
  557. extend: function (point) { // (Point)
  558. point = L.point(point);
  559. if (!this.min && !this.max) {
  560. this.min = point.clone();
  561. this.max = point.clone();
  562. } else {
  563. this.min.x = Math.min(point.x, this.min.x);
  564. this.max.x = Math.max(point.x, this.max.x);
  565. this.min.y = Math.min(point.y, this.min.y);
  566. this.max.y = Math.max(point.y, this.max.y);
  567. }
  568. return this;
  569. },
  570. getCenter: function (round) { // (Boolean) -> Point
  571. return new L.Point(
  572. (this.min.x + this.max.x) / 2,
  573. (this.min.y + this.max.y) / 2, round);
  574. },
  575. getBottomLeft: function () { // -> Point
  576. return new L.Point(this.min.x, this.max.y);
  577. },
  578. getTopRight: function () { // -> Point
  579. return new L.Point(this.max.x, this.min.y);
  580. },
  581. getSize: function () {
  582. return this.max.subtract(this.min);
  583. },
  584. contains: function (obj) { // (Bounds) or (Point) -> Boolean
  585. var min, max;
  586. if (typeof obj[0] === 'number' || obj instanceof L.Point) {
  587. obj = L.point(obj);
  588. } else {
  589. obj = L.bounds(obj);
  590. }
  591. if (obj instanceof L.Bounds) {
  592. min = obj.min;
  593. max = obj.max;
  594. } else {
  595. min = max = obj;
  596. }
  597. return (min.x >= this.min.x) &&
  598. (max.x <= this.max.x) &&
  599. (min.y >= this.min.y) &&
  600. (max.y <= this.max.y);
  601. },
  602. intersects: function (bounds) { // (Bounds) -> Boolean
  603. bounds = L.bounds(bounds);
  604. var min = this.min,
  605. max = this.max,
  606. min2 = bounds.min,
  607. max2 = bounds.max,
  608. xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
  609. yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
  610. return xIntersects && yIntersects;
  611. },
  612. isValid: function () {
  613. return !!(this.min && this.max);
  614. }
  615. };
  616. L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
  617. if (!a || a instanceof L.Bounds) {
  618. return a;
  619. }
  620. return new L.Bounds(a, b);
  621. };
  622. /*
  623. * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
  624. */
  625. L.Transformation = function (a, b, c, d) {
  626. this._a = a;
  627. this._b = b;
  628. this._c = c;
  629. this._d = d;
  630. };
  631. L.Transformation.prototype = {
  632. transform: function (point, scale) { // (Point, Number) -> Point
  633. return this._transform(point.clone(), scale);
  634. },
  635. // destructive transform (faster)
  636. _transform: function (point, scale) {
  637. scale = scale || 1;
  638. point.x = scale * (this._a * point.x + this._b);
  639. point.y = scale * (this._c * point.y + this._d);
  640. return point;
  641. },
  642. untransform: function (point, scale) {
  643. scale = scale || 1;
  644. return new L.Point(
  645. (point.x / scale - this._b) / this._a,
  646. (point.y / scale - this._d) / this._c);
  647. }
  648. };
  649. /*
  650. * L.DomUtil contains various utility functions for working with DOM.
  651. */
  652. L.DomUtil = {
  653. get: function (id) {
  654. return (typeof id === 'string' ? document.getElementById(id) : id);
  655. },
  656. getStyle: function (el, style) {
  657. var value = el.style[style];
  658. if (!value && el.currentStyle) {
  659. value = el.currentStyle[style];
  660. }
  661. if ((!value || value === 'auto') && document.defaultView) {
  662. var css = document.defaultView.getComputedStyle(el, null);
  663. value = css ? css[style] : null;
  664. }
  665. return value === 'auto' ? null : value;
  666. },
  667. getViewportOffset: function (element) {
  668. var top = 0,
  669. left = 0,
  670. el = element,
  671. docBody = document.body,
  672. docEl = document.documentElement,
  673. pos;
  674. do {
  675. top += el.offsetTop || 0;
  676. left += el.offsetLeft || 0;
  677. //add borders
  678. top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
  679. left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
  680. pos = L.DomUtil.getStyle(el, 'position');
  681. if (el.offsetParent === docBody && pos === 'absolute') { break; }
  682. if (pos === 'fixed') {
  683. top += docBody.scrollTop || docEl.scrollTop || 0;
  684. left += docBody.scrollLeft || docEl.scrollLeft || 0;
  685. break;
  686. }
  687. if (pos === 'relative' && !el.offsetLeft) {
  688. var width = L.DomUtil.getStyle(el, 'width'),
  689. maxWidth = L.DomUtil.getStyle(el, 'max-width'),
  690. r = el.getBoundingClientRect();
  691. if (width !== 'none' || maxWidth !== 'none') {
  692. left += r.left + el.clientLeft;
  693. }
  694. //calculate full y offset since we're breaking out of the loop
  695. top += r.top + (docBody.scrollTop || docEl.scrollTop || 0);
  696. break;
  697. }
  698. el = el.offsetParent;
  699. } while (el);
  700. el = element;
  701. do {
  702. if (el === docBody) { break; }
  703. top -= el.scrollTop || 0;
  704. left -= el.scrollLeft || 0;
  705. el = el.parentNode;
  706. } while (el);
  707. return new L.Point(left, top);
  708. },
  709. documentIsLtr: function () {
  710. if (!L.DomUtil._docIsLtrCached) {
  711. L.DomUtil._docIsLtrCached = true;
  712. L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
  713. }
  714. return L.DomUtil._docIsLtr;
  715. },
  716. create: function (tagName, className, container) {
  717. var el = document.createElement(tagName);
  718. el.className = className;
  719. if (container) {
  720. container.appendChild(el);
  721. }
  722. return el;
  723. },
  724. hasClass: function (el, name) {
  725. if (el.classList !== undefined) {
  726. return el.classList.contains(name);
  727. }
  728. var className = L.DomUtil._getClass(el);
  729. return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
  730. },
  731. addClass: function (el, name) {
  732. if (el.classList !== undefined) {
  733. var classes = L.Util.splitWords(name);
  734. for (var i = 0, len = classes.length; i < len; i++) {
  735. el.classList.add(classes[i]);
  736. }
  737. } else if (!L.DomUtil.hasClass(el, name)) {
  738. var className = L.DomUtil._getClass(el);
  739. L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);
  740. }
  741. },
  742. removeClass: function (el, name) {
  743. if (el.classList !== undefined) {
  744. el.classList.remove(name);
  745. } else {
  746. L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
  747. }
  748. },
  749. _setClass: function (el, name) {
  750. if (el.className.baseVal === undefined) {
  751. el.className = name;
  752. } else {
  753. // in case of SVG element
  754. el.className.baseVal = name;
  755. }
  756. },
  757. _getClass: function (el) {
  758. return el.className.baseVal === undefined ? el.className : el.className.baseVal;
  759. },
  760. setOpacity: function (el, value) {
  761. if ('opacity' in el.style) {
  762. el.style.opacity = value;
  763. } else if ('filter' in el.style) {
  764. var filter = false,
  765. filterName = 'DXImageTransform.Microsoft.Alpha';
  766. // filters collection throws an error if we try to retrieve a filter that doesn't exist
  767. try {
  768. filter = el.filters.item(filterName);
  769. } catch (e) {
  770. // don't set opacity to 1 if we haven't already set an opacity,
  771. // it isn't needed and breaks transparent pngs.
  772. if (value === 1) { return; }
  773. }
  774. value = Math.round(value * 100);
  775. if (filter) {
  776. filter.Enabled = (value !== 100);
  777. filter.Opacity = value;
  778. } else {
  779. el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
  780. }
  781. }
  782. },
  783. testProp: function (props) {
  784. var style = document.documentElement.style;
  785. for (var i = 0; i < props.length; i++) {
  786. if (props[i] in style) {
  787. return props[i];
  788. }
  789. }
  790. return false;
  791. },
  792. getTranslateString: function (point) {
  793. // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
  794. // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
  795. // (same speed either way), Opera 12 doesn't support translate3d
  796. var is3d = L.Browser.webkit3d,
  797. open = 'translate' + (is3d ? '3d' : '') + '(',
  798. close = (is3d ? ',0' : '') + ')';
  799. return open + point.x + 'px,' + point.y + 'px' + close;
  800. },
  801. getScaleString: function (scale, origin) {
  802. var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
  803. scaleStr = ' scale(' + scale + ') ';
  804. return preTranslateStr + scaleStr;
  805. },
  806. setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
  807. // jshint camelcase: false
  808. el._leaflet_pos = point;
  809. if (!disable3D && L.Browser.any3d) {
  810. el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
  811. } else {
  812. el.style.left = point.x + 'px';
  813. el.style.top = point.y + 'px';
  814. }
  815. },
  816. getPosition: function (el) {
  817. // this method is only used for elements previously positioned using setPosition,
  818. // so it's safe to cache the position for performance
  819. // jshint camelcase: false
  820. return el._leaflet_pos;
  821. }
  822. };
  823. // prefix style property names
  824. L.DomUtil.TRANSFORM = L.DomUtil.testProp(
  825. ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
  826. // webkitTransition comes first because some browser versions that drop vendor prefix don't do
  827. // the same for the transitionend event, in particular the Android 4.1 stock browser
  828. L.DomUtil.TRANSITION = L.DomUtil.testProp(
  829. ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
  830. L.DomUtil.TRANSITION_END =
  831. L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
  832. L.DomUtil.TRANSITION + 'End' : 'transitionend';
  833. (function () {
  834. if ('onselectstart' in document) {
  835. L.extend(L.DomUtil, {
  836. disableTextSelection: function () {
  837. L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
  838. },
  839. enableTextSelection: function () {
  840. L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
  841. }
  842. });
  843. } else {
  844. var userSelectProperty = L.DomUtil.testProp(
  845. ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
  846. L.extend(L.DomUtil, {
  847. disableTextSelection: function () {
  848. if (userSelectProperty) {
  849. var style = document.documentElement.style;
  850. this._userSelect = style[userSelectProperty];
  851. style[userSelectProperty] = 'none';
  852. }
  853. },
  854. enableTextSelection: function () {
  855. if (userSelectProperty) {
  856. document.documentElement.style[userSelectProperty] = this._userSelect;
  857. delete this._userSelect;
  858. }
  859. }
  860. });
  861. }
  862. L.extend(L.DomUtil, {
  863. disableImageDrag: function () {
  864. L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
  865. },
  866. enableImageDrag: function () {
  867. L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
  868. }
  869. });
  870. })();
  871. /*
  872. * L.LatLng represents a geographical point with latitude and longitude coordinates.
  873. */
  874. L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)
  875. lat = parseFloat(lat);
  876. lng = parseFloat(lng);
  877. if (isNaN(lat) || isNaN(lng)) {
  878. throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
  879. }
  880. this.lat = lat;
  881. this.lng = lng;
  882. if (alt !== undefined) {
  883. this.alt = parseFloat(alt);
  884. }
  885. };
  886. L.extend(L.LatLng, {
  887. DEG_TO_RAD: Math.PI / 180,
  888. RAD_TO_DEG: 180 / Math.PI,
  889. MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
  890. });
  891. L.LatLng.prototype = {
  892. equals: function (obj) { // (LatLng) -> Boolean
  893. if (!obj) { return false; }
  894. obj = L.latLng(obj);
  895. var margin = Math.max(
  896. Math.abs(this.lat - obj.lat),
  897. Math.abs(this.lng - obj.lng));
  898. return margin <= L.LatLng.MAX_MARGIN;
  899. },
  900. toString: function (precision) { // (Number) -> String
  901. return 'LatLng(' +
  902. L.Util.formatNum(this.lat, precision) + ', ' +
  903. L.Util.formatNum(this.lng, precision) + ')';
  904. },
  905. // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
  906. // TODO move to projection code, LatLng shouldn't know about Earth
  907. distanceTo: function (other) { // (LatLng) -> Number
  908. other = L.latLng(other);
  909. var R = 6378137, // earth radius in meters
  910. d2r = L.LatLng.DEG_TO_RAD,
  911. dLat = (other.lat - this.lat) * d2r,
  912. dLon = (other.lng - this.lng) * d2r,
  913. lat1 = this.lat * d2r,
  914. lat2 = other.lat * d2r,
  915. sin1 = Math.sin(dLat / 2),
  916. sin2 = Math.sin(dLon / 2);
  917. var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
  918. return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  919. },
  920. wrap: function (a, b) { // (Number, Number) -> LatLng
  921. var lng = this.lng;
  922. a = a || -180;
  923. b = b || 180;
  924. lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
  925. return new L.LatLng(this.lat, lng);
  926. }
  927. };
  928. L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
  929. if (a instanceof L.LatLng) {
  930. return a;
  931. }
  932. if (L.Util.isArray(a)) {
  933. if (typeof a[0] === 'number' || typeof a[0] === 'string') {
  934. return new L.LatLng(a[0], a[1], a[2]);
  935. } else {
  936. return null;
  937. }
  938. }
  939. if (a === undefined || a === null) {
  940. return a;
  941. }
  942. if (typeof a === 'object' && 'lat' in a) {
  943. return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
  944. }
  945. if (b === undefined) {
  946. return null;
  947. }
  948. return new L.LatLng(a, b);
  949. };
  950. /*
  951. * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
  952. */
  953. L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
  954. if (!southWest) { return; }
  955. var latlngs = northEast ? [southWest, northEast] : southWest;
  956. for (var i = 0, len = latlngs.length; i < len; i++) {
  957. this.extend(latlngs[i]);
  958. }
  959. };
  960. L.LatLngBounds.prototype = {
  961. // extend the bounds to contain the given point or bounds
  962. extend: function (obj) { // (LatLng) or (LatLngBounds)
  963. if (!obj) { return this; }
  964. var latLng = L.latLng(obj);
  965. if (latLng !== null) {
  966. obj = latLng;
  967. } else {
  968. obj = L.latLngBounds(obj);
  969. }
  970. if (obj instanceof L.LatLng) {
  971. if (!this._southWest && !this._northEast) {
  972. this._southWest = new L.LatLng(obj.lat, obj.lng);
  973. this._northEast = new L.LatLng(obj.lat, obj.lng);
  974. } else {
  975. this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
  976. this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
  977. this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
  978. this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
  979. }
  980. } else if (obj instanceof L.LatLngBounds) {
  981. this.extend(obj._southWest);
  982. this.extend(obj._northEast);
  983. }
  984. return this;
  985. },
  986. // extend the bounds by a percentage
  987. pad: function (bufferRatio) { // (Number) -> LatLngBounds
  988. var sw = this._southWest,
  989. ne = this._northEast,
  990. heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
  991. widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
  992. return new L.LatLngBounds(
  993. new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
  994. new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
  995. },
  996. getCenter: function () { // -> LatLng
  997. return new L.LatLng(
  998. (this._southWest.lat + this._northEast.lat) / 2,
  999. (this._southWest.lng + this._northEast.lng) / 2);
  1000. },
  1001. getSouthWest: function () {
  1002. return this._southWest;
  1003. },
  1004. getNorthEast: function () {
  1005. return this._northEast;
  1006. },
  1007. getNorthWest: function () {
  1008. return new L.LatLng(this.getNorth(), this.getWest());
  1009. },
  1010. getSouthEast: function () {
  1011. return new L.LatLng(this.getSouth(), this.getEast());
  1012. },
  1013. getWest: function () {
  1014. return this._southWest.lng;
  1015. },
  1016. getSouth: function () {
  1017. return this._southWest.lat;
  1018. },
  1019. getEast: function () {
  1020. return this._northEast.lng;
  1021. },
  1022. getNorth: function () {
  1023. return this._northEast.lat;
  1024. },
  1025. contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
  1026. if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
  1027. obj = L.latLng(obj);
  1028. } else {
  1029. obj = L.latLngBounds(obj);
  1030. }
  1031. var sw = this._southWest,
  1032. ne = this._northEast,
  1033. sw2, ne2;
  1034. if (obj instanceof L.LatLngBounds) {
  1035. sw2 = obj.getSouthWest();
  1036. ne2 = obj.getNorthEast();
  1037. } else {
  1038. sw2 = ne2 = obj;
  1039. }
  1040. return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
  1041. (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
  1042. },
  1043. intersects: function (bounds) { // (LatLngBounds)
  1044. bounds = L.latLngBounds(bounds);
  1045. var sw = this._southWest,
  1046. ne = this._northEast,
  1047. sw2 = bounds.getSouthWest(),
  1048. ne2 = bounds.getNorthEast(),
  1049. latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
  1050. lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
  1051. return latIntersects && lngIntersects;
  1052. },
  1053. toBBoxString: function () {
  1054. return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
  1055. },
  1056. equals: function (bounds) { // (LatLngBounds)
  1057. if (!bounds) { return false; }
  1058. bounds = L.latLngBounds(bounds);
  1059. return this._southWest.equals(bounds.getSouthWest()) &&
  1060. this._northEast.equals(bounds.getNorthEast());
  1061. },
  1062. isValid: function () {
  1063. return !!(this._southWest && this._northEast);
  1064. }
  1065. };
  1066. //TODO International date line?
  1067. L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
  1068. if (!a || a instanceof L.LatLngBounds) {
  1069. return a;
  1070. }
  1071. return new L.LatLngBounds(a, b);
  1072. };
  1073. /*
  1074. * L.Projection contains various geographical projections used by CRS classes.
  1075. */
  1076. L.Projection = {};
  1077. /*
  1078. * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
  1079. */
  1080. L.Projection.SphericalMercator = {
  1081. MAX_LATITUDE: 85.0511287798,
  1082. project: function (latlng) { // (LatLng) -> Point
  1083. var d = L.LatLng.DEG_TO_RAD,
  1084. max = this.MAX_LATITUDE,
  1085. lat = Math.max(Math.min(max, latlng.lat), -max),
  1086. x = latlng.lng * d,
  1087. y = lat * d;
  1088. y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
  1089. return new L.Point(x, y);
  1090. },
  1091. unproject: function (point) { // (Point, Boolean) -> LatLng
  1092. var d = L.LatLng.RAD_TO_DEG,
  1093. lng = point.x * d,
  1094. lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
  1095. return new L.LatLng(lat, lng);
  1096. }
  1097. };
  1098. /*
  1099. * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
  1100. */
  1101. L.Projection.LonLat = {
  1102. project: function (latlng) {
  1103. return new L.Point(latlng.lng, latlng.lat);
  1104. },
  1105. unproject: function (point) {
  1106. return new L.LatLng(point.y, point.x);
  1107. }
  1108. };
  1109. /*
  1110. * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
  1111. */
  1112. L.CRS = {
  1113. latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
  1114. var projectedPoint = this.projection.project(latlng),
  1115. scale = this.scale(zoom);
  1116. return this.transformation._transform(projectedPoint, scale);
  1117. },
  1118. pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
  1119. var scale = this.scale(zoom),
  1120. untransformedPoint = this.transformation.untransform(point, scale);
  1121. return this.projection.unproject(untransformedPoint);
  1122. },
  1123. project: function (latlng) {
  1124. return this.projection.project(latlng);
  1125. },
  1126. scale: function (zoom) {
  1127. return 256 * Math.pow(2, zoom);
  1128. },
  1129. getSize: function (zoom) {
  1130. var s = this.scale(zoom);
  1131. return L.point(s, s);
  1132. }
  1133. };
  1134. /*
  1135. * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
  1136. */
  1137. L.CRS.Simple = L.extend({}, L.CRS, {
  1138. projection: L.Projection.LonLat,
  1139. transformation: new L.Transformation(1, 0, -1, 0),
  1140. scale: function (zoom) {
  1141. return Math.pow(2, zoom);
  1142. }
  1143. });
  1144. /*
  1145. * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
  1146. * and is used by Leaflet by default.
  1147. */
  1148. L.CRS.EPSG3857 = L.extend({}, L.CRS, {
  1149. code: 'EPSG:3857',
  1150. projection: L.Projection.SphericalMercator,
  1151. transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
  1152. project: function (latlng) { // (LatLng) -> Point
  1153. var projectedPoint = this.projection.project(latlng),
  1154. earthRadius = 6378137;
  1155. return projectedPoint.multiplyBy(earthRadius);
  1156. }
  1157. });
  1158. L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
  1159. code: 'EPSG:900913'
  1160. });
  1161. /*
  1162. * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
  1163. */
  1164. L.CRS.EPSG4326 = L.extend({}, L.CRS, {
  1165. code: 'EPSG:4326',
  1166. projection: L.Projection.LonLat,
  1167. transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
  1168. });
  1169. /*
  1170. * L.Map is the central class of the API - it is used to create a map.
  1171. */
  1172. L.Map = L.Class.extend({
  1173. includes: L.Mixin.Events,
  1174. options: {
  1175. crs: L.CRS.EPSG3857,
  1176. /*
  1177. center: LatLng,
  1178. zoom: Number,
  1179. layers: Array,
  1180. */
  1181. fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
  1182. trackResize: true,
  1183. markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
  1184. },
  1185. initialize: function (id, options) { // (HTMLElement or String, Object)
  1186. options = L.setOptions(this, options);
  1187. this._initContainer(id);
  1188. this._initLayout();
  1189. // hack for https://github.com/Leaflet/Leaflet/issues/1980
  1190. this._onResize = L.bind(this._onResize, this);
  1191. this._initEvents();
  1192. if (options.maxBounds) {
  1193. this.setMaxBounds(options.maxBounds);
  1194. }
  1195. if (options.center && options.zoom !== undefined) {
  1196. this.setView(L.latLng(options.center), options.zoom, {reset: true});
  1197. }
  1198. this._handlers = [];
  1199. this._layers = {};
  1200. this._zoomBoundLayers = {};
  1201. this._tileLayersNum = 0;
  1202. this.callInitHooks();
  1203. this._addLayers(options.layers);
  1204. },
  1205. // public methods that modify map state
  1206. // replaced by animation-powered implementation in Map.PanAnimation.js
  1207. setView: function (center, zoom) {
  1208. zoom = zoom === undefined ? this.getZoom() : zoom;
  1209. this._resetView(L.latLng(center), this._limitZoom(zoom));
  1210. return this;
  1211. },
  1212. setZoom: function (zoom, options) {
  1213. if (!this._loaded) {
  1214. this._zoom = this._limitZoom(zoom);
  1215. return this;
  1216. }
  1217. return this.setView(this.getCenter(), zoom, {zoom: options});
  1218. },
  1219. zoomIn: function (delta, options) {
  1220. return this.setZoom(this._zoom + (delta || 1), options);
  1221. },
  1222. zoomOut: function (delta, options) {
  1223. return this.setZoom(this._zoom - (delta || 1), options);
  1224. },
  1225. setZoomAround: function (latlng, zoom, options) {
  1226. var scale = this.getZoomScale(zoom),
  1227. viewHalf = this.getSize().divideBy(2),
  1228. containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
  1229. centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
  1230. newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
  1231. return this.setView(newCenter, zoom, {zoom: options});
  1232. },
  1233. fitBounds: function (bounds, options) {
  1234. options = options || {};
  1235. bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
  1236. var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
  1237. paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
  1238. zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
  1239. paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
  1240. swPoint = this.project(bounds.getSouthWest(), zoom),
  1241. nePoint = this.project(bounds.getNorthEast(), zoom),
  1242. center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
  1243. zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom;
  1244. return this.setView(center, zoom, options);
  1245. },
  1246. fitWorld: function (options) {
  1247. return this.fitBounds([[-90, -180], [90, 180]], options);
  1248. },
  1249. panTo: function (center, options) { // (LatLng)
  1250. return this.setView(center, this._zoom, {pan: options});
  1251. },
  1252. panBy: function (offset) { // (Point)
  1253. // replaced with animated panBy in Map.PanAnimation.js
  1254. this.fire('movestart');
  1255. this._rawPanBy(L.point(offset));
  1256. this.fire('move');
  1257. return this.fire('moveend');
  1258. },
  1259. setMaxBounds: function (bounds) {
  1260. bounds = L.latLngBounds(bounds);
  1261. this.options.maxBounds = bounds;
  1262. if (!bounds) {
  1263. return this.off('moveend', this._panInsideMaxBounds, this);
  1264. }
  1265. if (this._loaded) {
  1266. this._panInsideMaxBounds();
  1267. }
  1268. return this.on('moveend', this._panInsideMaxBounds, this);
  1269. },
  1270. panInsideBounds: function (bounds, options) {
  1271. var center = this.getCenter(),
  1272. newCenter = this._limitCenter(center, this._zoom, bounds);
  1273. if (center.equals(newCenter)) { return this; }
  1274. return this.panTo(newCenter, options);
  1275. },
  1276. addLayer: function (layer) {
  1277. // TODO method is too big, refactor
  1278. var id = L.stamp(layer);
  1279. if (this._layers[id]) { return this; }
  1280. this._layers[id] = layer;
  1281. // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
  1282. if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
  1283. this._zoomBoundLayers[id] = layer;
  1284. this._updateZoomLevels();
  1285. }
  1286. // TODO looks ugly, refactor!!!
  1287. if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
  1288. this._tileLayersNum++;
  1289. this._tileLayersToLoad++;
  1290. layer.on('load', this._onTileLayerLoad, this);
  1291. }
  1292. if (this._loaded) {
  1293. this._layerAdd(layer);
  1294. }
  1295. return this;
  1296. },
  1297. removeLayer: function (layer) {
  1298. var id = L.stamp(layer);
  1299. if (!this._layers[id]) { return this; }
  1300. if (this._loaded) {
  1301. layer.onRemove(this);
  1302. }
  1303. delete this._layers[id];
  1304. if (this._loaded) {
  1305. this.fire('layerremove', {layer: layer});
  1306. }
  1307. if (this._zoomBoundLayers[id]) {
  1308. delete this._zoomBoundLayers[id];
  1309. this._updateZoomLevels();
  1310. }
  1311. // TODO looks ugly, refactor
  1312. if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
  1313. this._tileLayersNum--;
  1314. this._tileLayersToLoad--;
  1315. layer.off('load', this._onTileLayerLoad, this);
  1316. }
  1317. return this;
  1318. },
  1319. hasLayer: function (layer) {
  1320. if (!layer) { return false; }
  1321. return (L.stamp(layer) in this._layers);
  1322. },
  1323. eachLayer: function (method, context) {
  1324. for (var i in this._layers) {
  1325. method.call(context, this._layers[i]);
  1326. }
  1327. return this;
  1328. },
  1329. invalidateSize: function (options) {
  1330. if (!this._loaded) { return this; }
  1331. options = L.extend({
  1332. animate: false,
  1333. pan: true
  1334. }, options === true ? {animate: true} : options);
  1335. var oldSize = this.getSize();
  1336. this._sizeChanged = true;
  1337. this._initialCenter = null;
  1338. var newSize = this.getSize(),
  1339. oldCenter = oldSize.divideBy(2).round(),
  1340. newCenter = newSize.divideBy(2).round(),
  1341. offset = oldCenter.subtract(newCenter);
  1342. if (!offset.x && !offset.y) { return this; }
  1343. if (options.animate && options.pan) {
  1344. this.panBy(offset);
  1345. } else {
  1346. if (options.pan) {
  1347. this._rawPanBy(offset);
  1348. }
  1349. this.fire('move');
  1350. if (options.debounceMoveend) {
  1351. clearTimeout(this._sizeTimer);
  1352. this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
  1353. } else {
  1354. this.fire('moveend');
  1355. }
  1356. }
  1357. return this.fire('resize', {
  1358. oldSize: oldSize,
  1359. newSize: newSize
  1360. });
  1361. },
  1362. // TODO handler.addTo
  1363. addHandler: function (name, HandlerClass) {
  1364. if (!HandlerClass) { return this; }
  1365. var handler = this[name] = new HandlerClass(this);
  1366. this._handlers.push(handler);
  1367. if (this.options[name]) {
  1368. handler.enable();
  1369. }
  1370. return this;
  1371. },
  1372. remove: function () {
  1373. if (this._loaded) {
  1374. this.fire('unload');
  1375. }
  1376. this._initEvents('off');
  1377. try {
  1378. // throws error in IE6-8
  1379. delete this._container._leaflet;
  1380. } catch (e) {
  1381. this._container._leaflet = undefined;
  1382. }
  1383. this._clearPanes();
  1384. if (this._clearControlPos) {
  1385. this._clearControlPos();
  1386. }
  1387. this._clearHandlers();
  1388. return this;
  1389. },
  1390. // public methods for getting map state
  1391. getCenter: function () { // (Boolean) -> LatLng
  1392. this._checkIfLoaded();
  1393. if (this._initialCenter && !this._moved()) {
  1394. return this._initialCenter;
  1395. }
  1396. return this.layerPointToLatLng(this._getCenterLayerPoint());
  1397. },
  1398. getZoom: function () {
  1399. return this._zoom;
  1400. },
  1401. getBounds: function () {
  1402. var bounds = this.getPixelBounds(),
  1403. sw = this.unproject(bounds.getBottomLeft()),
  1404. ne = this.unproject(bounds.getTopRight());
  1405. return new L.LatLngBounds(sw, ne);
  1406. },
  1407. getMinZoom: function () {
  1408. return this.options.minZoom === undefined ?
  1409. (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
  1410. this.options.minZoom;
  1411. },
  1412. getMaxZoom: function () {
  1413. return this.options.maxZoom === undefined ?
  1414. (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
  1415. this.options.maxZoom;
  1416. },
  1417. getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
  1418. bounds = L.latLngBounds(bounds);
  1419. var zoom = this.getMinZoom() - (inside ? 1 : 0),
  1420. maxZoom = this.getMaxZoom(),
  1421. size = this.getSize(),
  1422. nw = bounds.getNorthWest(),
  1423. se = bounds.getSouthEast(),
  1424. zoomNotFound = true,
  1425. boundsSize;
  1426. padding = L.point(padding || [0, 0]);
  1427. do {
  1428. zoom++;
  1429. boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
  1430. zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
  1431. } while (zoomNotFound && zoom <= maxZoom);
  1432. if (zoomNotFound && inside) {
  1433. return null;
  1434. }
  1435. return inside ? zoom : zoom - 1;
  1436. },
  1437. getSize: function () {
  1438. if (!this._size || this._sizeChanged) {
  1439. this._size = new L.Point(
  1440. this._container.clientWidth,
  1441. this._container.clientHeight);
  1442. this._sizeChanged = false;
  1443. }
  1444. return this._size.clone();
  1445. },
  1446. getPixelBounds: function () {
  1447. var topLeftPoint = this._getTopLeftPoint();
  1448. return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
  1449. },
  1450. getPixelOrigin: function () {
  1451. this._checkIfLoaded();
  1452. return this._initialTopLeftPoint;
  1453. },
  1454. getPanes: function () {
  1455. return this._panes;
  1456. },
  1457. getContainer: function () {
  1458. return this._container;
  1459. },
  1460. // TODO replace with universal implementation after refactoring projections
  1461. getZoomScale: function (toZoom) {
  1462. var crs = this.options.crs;
  1463. return crs.scale(toZoom) / crs.scale(this._zoom);
  1464. },
  1465. getScaleZoom: function (scale) {
  1466. return this._zoom + (Math.log(scale) / Math.LN2);
  1467. },
  1468. // conversion methods
  1469. project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
  1470. zoom = zoom === undefined ? this._zoom : zoom;
  1471. return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
  1472. },
  1473. unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
  1474. zoom = zoom === undefined ? this._zoom : zoom;
  1475. return this.options.crs.pointToLatLng(L.point(point), zoom);
  1476. },
  1477. layerPointToLatLng: function (point) { // (Point)
  1478. var projectedPoint = L.point(point).add(this.getPixelOrigin());
  1479. return this.unproject(projectedPoint);
  1480. },
  1481. latLngToLayerPoint: function (latlng) { // (LatLng)
  1482. var projectedPoint = this.project(L.latLng(latlng))._round();
  1483. return projectedPoint._subtract(this.getPixelOrigin());
  1484. },
  1485. containerPointToLayerPoint: function (point) { // (Point)
  1486. return L.point(point).subtract(this._getMapPanePos());
  1487. },
  1488. layerPointToContainerPoint: function (point) { // (Point)
  1489. return L.point(point).add(this._getMapPanePos());
  1490. },
  1491. containerPointToLatLng: function (point) {
  1492. var layerPoint = this.containerPointToLayerPoint(L.point(point));
  1493. return this.layerPointToLatLng(layerPoint);
  1494. },
  1495. latLngToContainerPoint: function (latlng) {
  1496. return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
  1497. },
  1498. mouseEventToContainerPoint: function (e) { // (MouseEvent)
  1499. return L.DomEvent.getMousePosition(e, this._container);
  1500. },
  1501. mouseEventToLayerPoint: function (e) { // (MouseEvent)
  1502. return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
  1503. },
  1504. mouseEventToLatLng: function (e) { // (MouseEvent)
  1505. return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
  1506. },
  1507. // map initialization methods
  1508. _initContainer: function (id) {
  1509. var container = this._container = L.DomUtil.get(id);
  1510. if (!container) {
  1511. throw new Error('Map container not found.');
  1512. } else if (container._leaflet) {
  1513. throw new Error('Map container is already initialized.');
  1514. }
  1515. container._leaflet = true;
  1516. },
  1517. _initLayout: function () {
  1518. var container = this._container;
  1519. L.DomUtil.addClass(container, 'leaflet-container' +
  1520. (L.Browser.touch ? ' leaflet-touch' : '') +
  1521. (L.Browser.retina ? ' leaflet-retina' : '') +
  1522. (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
  1523. (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
  1524. var position = L.DomUtil.getStyle(container, 'position');
  1525. if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
  1526. container.style.position = 'relative';
  1527. }
  1528. this._initPanes();
  1529. if (this._initControlPos) {
  1530. this._initControlPos();
  1531. }
  1532. },
  1533. _initPanes: function () {
  1534. var panes = this._panes = {};
  1535. this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
  1536. this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
  1537. panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
  1538. panes.shadowPane = this._createPane('leaflet-shadow-pane');
  1539. panes.overlayPane = this._createPane('leaflet-overlay-pane');
  1540. panes.markerPane = this._createPane('leaflet-marker-pane');
  1541. panes.popupPane = this._createPane('leaflet-popup-pane');
  1542. var zoomHide = ' leaflet-zoom-hide';
  1543. if (!this.options.markerZoomAnimation) {
  1544. L.DomUtil.addClass(panes.markerPane, zoomHide);
  1545. L.DomUtil.addClass(panes.shadowPane, zoomHide);
  1546. L.DomUtil.addClass(panes.popupPane, zoomHide);
  1547. }
  1548. },
  1549. _createPane: function (className, container) {
  1550. return L.DomUtil.create('div', className, container || this._panes.objectsPane);
  1551. },
  1552. _clearPanes: function () {
  1553. this._container.removeChild(this._mapPane);
  1554. },
  1555. _addLayers: function (layers) {
  1556. layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
  1557. for (var i = 0, len = layers.length; i < len; i++) {
  1558. this.addLayer(layers[i]);
  1559. }
  1560. },
  1561. // private methods that modify map state
  1562. _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
  1563. var zoomChanged = (this._zoom !== zoom);
  1564. if (!afterZoomAnim) {
  1565. this.fire('movestart');
  1566. if (zoomChanged) {
  1567. this.fire('zoomstart');
  1568. }
  1569. }
  1570. this._zoom = zoom;
  1571. this._initialCenter = center;
  1572. this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
  1573. if (!preserveMapOffset) {
  1574. L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
  1575. } else {
  1576. this._initialTopLeftPoint._add(this._getMapPanePos());
  1577. }
  1578. this._tileLayersToLoad = this._tileLayersNum;
  1579. var loading = !this._loaded;
  1580. this._loaded = true;
  1581. this.fire('viewreset', {hard: !preserveMapOffset});
  1582. if (loading) {
  1583. this.fire('load');
  1584. this.eachLayer(this._layerAdd, this);
  1585. }
  1586. this.fire('move');
  1587. if (zoomChanged || afterZoomAnim) {
  1588. this.fire('zoomend');
  1589. }
  1590. this.fire('moveend', {hard: !preserveMapOffset});
  1591. },
  1592. _rawPanBy: function (offset) {
  1593. L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
  1594. },
  1595. _getZoomSpan: function () {
  1596. return this.getMaxZoom() - this.getMinZoom();
  1597. },
  1598. _updateZoomLevels: function () {
  1599. var i,
  1600. minZoom = Infinity,
  1601. maxZoom = -Infinity,
  1602. oldZoomSpan = this._getZoomSpan();
  1603. for (i in this._zoomBoundLayers) {
  1604. var layer = this._zoomBoundLayers[i];
  1605. if (!isNaN(layer.options.minZoom)) {
  1606. minZoom = Math.min(minZoom, layer.options.minZoom);
  1607. }
  1608. if (!isNaN(layer.options.maxZoom)) {
  1609. maxZoom = Math.max(maxZoom, layer.options.maxZoom);
  1610. }
  1611. }
  1612. if (i === undefined) { // we have no tilelayers
  1613. this._layersMaxZoom = this._layersMinZoom = undefined;
  1614. } else {
  1615. this._layersMaxZoom = maxZoom;
  1616. this._layersMinZoom = minZoom;
  1617. }
  1618. if (oldZoomSpan !== this._getZoomSpan()) {
  1619. this.fire('zoomlevelschange');
  1620. }
  1621. },
  1622. _panInsideMaxBounds: function () {
  1623. this.panInsideBounds(this.options.maxBounds);
  1624. },
  1625. _checkIfLoaded: function () {
  1626. if (!this._loaded) {
  1627. throw new Error('Set map center and zoom first.');
  1628. }
  1629. },
  1630. // map events
  1631. _initEvents: function (onOff) {
  1632. if (!L.DomEvent) { return; }
  1633. onOff = onOff || 'on';
  1634. L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
  1635. var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
  1636. 'mouseleave', 'mousemove', 'contextmenu'],
  1637. i, len;
  1638. for (i = 0, len = events.length; i < len; i++) {
  1639. L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
  1640. }
  1641. if (this.options.trackResize) {
  1642. L.DomEvent[onOff](window, 'resize', this._onResize, this);
  1643. }
  1644. },
  1645. _onResize: function () {
  1646. L.Util.cancelAnimFrame(this._resizeRequest);
  1647. this._resizeRequest = L.Util.requestAnimFrame(
  1648. function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);
  1649. },
  1650. _onMouseClick: function (e) {
  1651. if (!this._loaded || (!e._simulated &&
  1652. ((this.dragging && this.dragging.moved()) ||
  1653. (this.boxZoom && this.boxZoom.moved()))) ||
  1654. L.DomEvent._skipped(e)) { return; }
  1655. this.fire('preclick');
  1656. this._fireMouseEvent(e);
  1657. },
  1658. _fireMouseEvent: function (e) {
  1659. if (!this._loaded || L.DomEvent._skipped(e)) { return; }
  1660. var type = e.type;
  1661. type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
  1662. if (!this.hasEventListeners(type)) { return; }
  1663. if (type === 'contextmenu') {
  1664. L.DomEvent.preventDefault(e);
  1665. }
  1666. var containerPoint = this.mouseEventToContainerPoint(e),
  1667. layerPoint = this.containerPointToLayerPoint(containerPoint),
  1668. latlng = this.layerPointToLatLng(layerPoint);
  1669. this.fire(type, {
  1670. latlng: latlng,
  1671. layerPoint: layerPoint,
  1672. containerPoint: containerPoint,
  1673. originalEvent: e
  1674. });
  1675. },
  1676. _onTileLayerLoad: function () {
  1677. this._tileLayersToLoad--;
  1678. if (this._tileLayersNum && !this._tileLayersToLoad) {
  1679. this.fire('tilelayersload');
  1680. }
  1681. },
  1682. _clearHandlers: function () {
  1683. for (var i = 0, len = this._handlers.length; i < len; i++) {
  1684. this._handlers[i].disable();
  1685. }
  1686. },
  1687. whenReady: function (callback, context) {
  1688. if (this._loaded) {
  1689. callback.call(context || this, this);
  1690. } else {
  1691. this.on('load', callback, context);
  1692. }
  1693. return this;
  1694. },
  1695. _layerAdd: function (layer) {
  1696. layer.onAdd(this);
  1697. this.fire('layeradd', {layer: layer});
  1698. },
  1699. // private methods for getting map state
  1700. _getMapPanePos: function () {
  1701. return L.DomUtil.getPosition(this._mapPane);
  1702. },
  1703. _moved: function () {
  1704. var pos = this._getMapPanePos();
  1705. return pos && !pos.equals([0, 0]);
  1706. },
  1707. _getTopLeftPoint: function () {
  1708. return this.getPixelOrigin().subtract(this._getMapPanePos());
  1709. },
  1710. _getNewTopLeftPoint: function (center, zoom) {
  1711. var viewHalf = this.getSize()._divideBy(2);
  1712. // TODO round on display, not calculation to increase precision?
  1713. return this.project(center, zoom)._subtract(viewHalf)._round();
  1714. },
  1715. _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
  1716. var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
  1717. return this.project(latlng, newZoom)._subtract(topLeft);
  1718. },
  1719. // layer point of the current center
  1720. _getCenterLayerPoint: function () {
  1721. return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
  1722. },
  1723. // offset of the specified place to the current center in pixels
  1724. _getCenterOffset: function (latlng) {
  1725. return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
  1726. },
  1727. // adjust center for view to get inside bounds
  1728. _limitCenter: function (center, zoom, bounds) {
  1729. if (!bounds) { return center; }
  1730. var centerPoint = this.project(center, zoom),
  1731. viewHalf = this.getSize().divideBy(2),
  1732. viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
  1733. offset = this._getBoundsOffset(viewBounds, bounds, zoom);
  1734. return this.unproject(centerPoint.add(offset), zoom);
  1735. },
  1736. // adjust offset for view to get inside bounds
  1737. _limitOffset: function (offset, bounds) {
  1738. if (!bounds) { return offset; }
  1739. var viewBounds = this.getPixelBounds(),
  1740. newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
  1741. return offset.add(this._getBoundsOffset(newBounds, bounds));
  1742. },
  1743. // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
  1744. _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
  1745. var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),
  1746. seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),
  1747. dx = this._rebound(nwOffset.x, -seOffset.x),
  1748. dy = this._rebound(nwOffset.y, -seOffset.y);
  1749. return new L.Point(dx, dy);
  1750. },
  1751. _rebound: function (left, right) {
  1752. return left + right > 0 ?
  1753. Math.round(left - right) / 2 :
  1754. Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
  1755. },
  1756. _limitZoom: function (zoom) {
  1757. var min = this.getMinZoom(),
  1758. max = this.getMaxZoom();
  1759. return Math.max(min, Math.min(max, zoom));
  1760. }
  1761. });
  1762. L.map = function (id, options) {
  1763. return new L.Map(id, options);
  1764. };
  1765. /*
  1766. * Mercator projection that takes into account that the Earth is not a perfect sphere.
  1767. * Less popular than spherical mercator; used by projections like EPSG:3395.
  1768. */
  1769. L.Projection.Mercator = {
  1770. MAX_LATITUDE: 85.0840591556,
  1771. R_MINOR: 6356752.314245179,
  1772. R_MAJOR: 6378137,
  1773. project: function (latlng) { // (LatLng) -> Point
  1774. var d = L.LatLng.DEG_TO_RAD,
  1775. max = this.MAX_LATITUDE,
  1776. lat = Math.max(Math.min(max, latlng.lat), -max),
  1777. r = this.R_MAJOR,
  1778. r2 = this.R_MINOR,
  1779. x = latlng.lng * d * r,
  1780. y = lat * d,
  1781. tmp = r2 / r,
  1782. eccent = Math.sqrt(1.0 - tmp * tmp),
  1783. con = eccent * Math.sin(y);
  1784. con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
  1785. var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
  1786. y = -r * Math.log(ts);
  1787. return new L.Point(x, y);
  1788. },
  1789. unproject: function (point) { // (Point, Boolean) -> LatLng
  1790. var d = L.LatLng.RAD_TO_DEG,
  1791. r = this.R_MAJOR,
  1792. r2 = this.R_MINOR,
  1793. lng = point.x * d / r,
  1794. tmp = r2 / r,
  1795. eccent = Math.sqrt(1 - (tmp * tmp)),
  1796. ts = Math.exp(- point.y / r),
  1797. phi = (Math.PI / 2) - 2 * Math.atan(ts),
  1798. numIter = 15,
  1799. tol = 1e-7,
  1800. i = numIter,
  1801. dphi = 0.1,
  1802. con;
  1803. while ((Math.abs(dphi) > tol) && (--i > 0)) {
  1804. con = eccent * Math.sin(phi);
  1805. dphi = (Math.PI / 2) - 2 * Math.atan(ts *
  1806. Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
  1807. phi += dphi;
  1808. }
  1809. return new L.LatLng(phi * d, lng);
  1810. }
  1811. };
  1812. L.CRS.EPSG3395 = L.extend({}, L.CRS, {
  1813. code: 'EPSG:3395',
  1814. projection: L.Projection.Mercator,
  1815. transformation: (function () {
  1816. var m = L.Projection.Mercator,
  1817. r = m.R_MAJOR,
  1818. scale = 0.5 / (Math.PI * r);
  1819. return new L.Transformation(scale, 0.5, -scale, 0.5);
  1820. }())
  1821. });
  1822. /*
  1823. * L.TileLayer is used for standard xyz-numbered tile layers.
  1824. */
  1825. L.TileLayer = L.Class.extend({
  1826. includes: L.Mixin.Events,
  1827. options: {
  1828. minZoom: 0,
  1829. maxZoom: 18,
  1830. tileSize: 256,
  1831. subdomains: 'abc',
  1832. errorTileUrl: '',
  1833. attribution: '',
  1834. zoomOffset: 0,
  1835. opacity: 1,
  1836. /*
  1837. maxNativeZoom: null,
  1838. zIndex: null,
  1839. tms: false,
  1840. continuousWorld: false,
  1841. noWrap: false,
  1842. zoomReverse: false,
  1843. detectRetina: false,
  1844. reuseTiles: false,
  1845. bounds: false,
  1846. */
  1847. unloadInvisibleTiles: L.Browser.mobile,
  1848. updateWhenIdle: L.Browser.mobile
  1849. },
  1850. initialize: function (url, options) {
  1851. options = L.setOptions(this, options);
  1852. // detecting retina displays, adjusting tileSize and zoom levels
  1853. if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
  1854. options.tileSize = Math.floor(options.tileSize / 2);
  1855. options.zoomOffset++;
  1856. if (options.minZoom > 0) {
  1857. options.minZoom--;
  1858. }
  1859. this.options.maxZoom--;
  1860. }
  1861. if (options.bounds) {
  1862. options.bounds = L.latLngBounds(options.bounds);
  1863. }
  1864. this._url = url;
  1865. var subdomains = this.options.subdomains;
  1866. if (typeof subdomains === 'string') {
  1867. this.options.subdomains = subdomains.split('');
  1868. }
  1869. },
  1870. onAdd: function (map) {
  1871. this._map = map;
  1872. this._animated = map._zoomAnimated;
  1873. // create a container div for tiles
  1874. this._initContainer();
  1875. // set up events
  1876. map.on({
  1877. 'viewreset': this._reset,
  1878. 'moveend': this._update
  1879. }, this);
  1880. if (this._animated) {
  1881. map.on({
  1882. 'zoomanim': this._animateZoom,
  1883. 'zoomend': this._endZoomAnim
  1884. }, this);
  1885. }
  1886. if (!this.options.updateWhenIdle) {
  1887. this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
  1888. map.on('move', this._limitedUpdate, this);
  1889. }
  1890. this._reset();
  1891. this._update();
  1892. },
  1893. addTo: function (map) {
  1894. map.addLayer(this);
  1895. return this;
  1896. },
  1897. onRemove: function (map) {
  1898. this._container.parentNode.removeChild(this._container);
  1899. map.off({
  1900. 'viewreset': this._reset,
  1901. 'moveend': this._update
  1902. }, this);
  1903. if (this._animated) {
  1904. map.off({
  1905. 'zoomanim': this._animateZoom,
  1906. 'zoomend': this._endZoomAnim
  1907. }, this);
  1908. }
  1909. if (!this.options.updateWhenIdle) {
  1910. map.off('move', this._limitedUpdate, this);
  1911. }
  1912. this._container = null;
  1913. this._map = null;
  1914. },
  1915. bringToFront: function () {
  1916. var pane = this._map._panes.tilePane;
  1917. if (this._container) {
  1918. pane.appendChild(this._container);
  1919. this._setAutoZIndex(pane, Math.max);
  1920. }
  1921. return this;
  1922. },
  1923. bringToBack: function () {
  1924. var pane = this._map._panes.tilePane;
  1925. if (this._container) {
  1926. pane.insertBefore(this._container, pane.firstChild);
  1927. this._setAutoZIndex(pane, Math.min);
  1928. }
  1929. return this;
  1930. },
  1931. getAttribution: function () {
  1932. return this.options.attribution;
  1933. },
  1934. getContainer: function () {
  1935. return this._container;
  1936. },
  1937. setOpacity: function (opacity) {
  1938. this.options.opacity = opacity;
  1939. if (this._map) {
  1940. this._updateOpacity();
  1941. }
  1942. return this;
  1943. },
  1944. setZIndex: function (zIndex) {
  1945. this.options.zIndex = zIndex;
  1946. this._updateZIndex();
  1947. return this;
  1948. },
  1949. setUrl: function (url, noRedraw) {
  1950. this._url = url;
  1951. if (!noRedraw) {
  1952. this.redraw();
  1953. }
  1954. return this;
  1955. },
  1956. redraw: function () {
  1957. if (this._map) {
  1958. this._reset({hard: true});
  1959. this._update();
  1960. }
  1961. return this;
  1962. },
  1963. _updateZIndex: function () {
  1964. if (this._container && this.options.zIndex !== undefined) {
  1965. this._container.style.zIndex = this.options.zIndex;
  1966. }
  1967. },
  1968. _setAutoZIndex: function (pane, compare) {
  1969. var layers = pane.children,
  1970. edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
  1971. zIndex, i, len;
  1972. for (i = 0, len = layers.length; i < len; i++) {
  1973. if (layers[i] !== this._container) {
  1974. zIndex = parseInt(layers[i].style.zIndex, 10);
  1975. if (!isNaN(zIndex)) {
  1976. edgeZIndex = compare(edgeZIndex, zIndex);
  1977. }
  1978. }
  1979. }
  1980. this.options.zIndex = this._container.style.zIndex =
  1981. (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
  1982. },
  1983. _updateOpacity: function () {
  1984. var i,
  1985. tiles = this._tiles;
  1986. if (L.Browser.ielt9) {
  1987. for (i in tiles) {
  1988. L.DomUtil.setOpacity(tiles[i], this.options.opacity);
  1989. }
  1990. } else {
  1991. L.DomUtil.setOpacity(this._container, this.options.opacity);
  1992. }
  1993. },
  1994. _initContainer: function () {
  1995. var tilePane = this._map._panes.tilePane;
  1996. if (!this._container) {
  1997. this._container = L.DomUtil.create('div', 'leaflet-layer');
  1998. this._updateZIndex();
  1999. if (this._animated) {
  2000. var className = 'leaflet-tile-container';
  2001. this._bgBuffer = L.DomUtil.create('div', className, this._container);
  2002. this._tileContainer = L.DomUtil.create('div', className, this._container);
  2003. } else {
  2004. this._tileContainer = this._container;
  2005. }
  2006. tilePane.appendChild(this._container);
  2007. if (this.options.opacity < 1) {
  2008. this._updateOpacity();
  2009. }
  2010. }
  2011. },
  2012. _reset: function (e) {
  2013. for (var key in this._tiles) {
  2014. this.fire('tileunload', {tile: this._tiles[key]});
  2015. }
  2016. this._tiles = {};
  2017. this._tilesToLoad = 0;
  2018. if (this.options.reuseTiles) {
  2019. this._unusedTiles = [];
  2020. }
  2021. this._tileContainer.innerHTML = '';
  2022. if (this._animated && e && e.hard) {
  2023. this._clearBgBuffer();
  2024. }
  2025. this._initContainer();
  2026. },
  2027. _getTileSize: function () {
  2028. var map = this._map,
  2029. zoom = map.getZoom() + this.options.zoomOffset,
  2030. zoomN = this.options.maxNativeZoom,
  2031. tileSize = this.options.tileSize;
  2032. if (zoomN && zoom > zoomN) {
  2033. tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);
  2034. }
  2035. return tileSize;
  2036. },
  2037. _update: function () {
  2038. if (!this._map) { return; }
  2039. var map = this._map,
  2040. bounds = map.getPixelBounds(),
  2041. zoom = map.getZoom(),
  2042. tileSize = this._getTileSize();
  2043. if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
  2044. return;
  2045. }
  2046. var tileBounds = L.bounds(
  2047. bounds.min.divideBy(tileSize)._floor(),
  2048. bounds.max.divideBy(tileSize)._floor());
  2049. this._addTilesFromCenterOut(tileBounds);
  2050. if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
  2051. this._removeOtherTiles(tileBounds);
  2052. }
  2053. },
  2054. _addTilesFromCenterOut: function (bounds) {
  2055. var queue = [],
  2056. center = bounds.getCenter();
  2057. var j, i, point;
  2058. for (j = bounds.min.y; j <= bounds.max.y; j++) {
  2059. for (i = bounds.min.x; i <= bounds.max.x; i++) {
  2060. point = new L.Point(i, j);
  2061. if (this._tileShouldBeLoaded(point)) {
  2062. queue.push(point);
  2063. }
  2064. }
  2065. }
  2066. var tilesToLoad = queue.length;
  2067. if (tilesToLoad === 0) { return; }
  2068. // load tiles in order of their distance to center
  2069. queue.sort(function (a, b) {
  2070. return a.distanceTo(center) - b.distanceTo(center);
  2071. });
  2072. var fragment = document.createDocumentFragment();
  2073. // if its the first batch of tiles to load
  2074. if (!this._tilesToLoad) {
  2075. this.fire('loading');
  2076. }
  2077. this._tilesToLoad += tilesToLoad;
  2078. for (i = 0; i < tilesToLoad; i++) {
  2079. this._addTile(queue[i], fragment);
  2080. }
  2081. this._tileContainer.appendChild(fragment);
  2082. },
  2083. _tileShouldBeLoaded: function (tilePoint) {
  2084. if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
  2085. return false; // already loaded
  2086. }
  2087. var options = this.options;
  2088. if (!options.continuousWorld) {
  2089. var limit = this._getWrapTileNum();
  2090. // don't load if exceeds world bounds
  2091. if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||
  2092. tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }
  2093. }
  2094. if (options.bounds) {
  2095. var tileSize = options.tileSize,
  2096. nwPoint = tilePoint.multiplyBy(tileSize),
  2097. sePoint = nwPoint.add([tileSize, tileSize]),
  2098. nw = this._map.unproject(nwPoint),
  2099. se = this._map.unproject(sePoint);
  2100. // TODO temporary hack, will be removed after refactoring projections
  2101. // https://github.com/Leaflet/Leaflet/issues/1618
  2102. if (!options.continuousWorld && !options.noWrap) {
  2103. nw = nw.wrap();
  2104. se = se.wrap();
  2105. }
  2106. if (!options.bounds.intersects([nw, se])) { return false; }
  2107. }
  2108. return true;
  2109. },
  2110. _removeOtherTiles: function (bounds) {
  2111. var kArr, x, y, key;
  2112. for (key in this._tiles) {
  2113. kArr = key.split(':');
  2114. x = parseInt(kArr[0], 10);
  2115. y = parseInt(kArr[1], 10);
  2116. // remove tile if it's out of bounds
  2117. if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
  2118. this._removeTile(key);
  2119. }
  2120. }
  2121. },
  2122. _removeTile: function (key) {
  2123. var tile = this._tiles[key];
  2124. this.fire('tileunload', {tile: tile, url: tile.src});
  2125. if (this.options.reuseTiles) {
  2126. L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
  2127. this._unusedTiles.push(tile);
  2128. } else if (tile.parentNode === this._tileContainer) {
  2129. this._tileContainer.removeChild(tile);
  2130. }
  2131. // for https://github.com/CloudMade/Leaflet/issues/137
  2132. if (!L.Browser.android) {
  2133. tile.onload = null;
  2134. tile.src = L.Util.emptyImageUrl;
  2135. }
  2136. delete this._tiles[key];
  2137. },
  2138. _addTile: function (tilePoint, container) {
  2139. var tilePos = this._getTilePos(tilePoint);
  2140. // get unused tile - or create a new tile
  2141. var tile = this._getTile();
  2142. /*
  2143. Chrome 20 layouts much faster with top/left (verify with timeline, frames)
  2144. Android 4 browser has display issues with top/left and requires transform instead
  2145. (other browsers don't currently care) - see debug/hacks/jitter.html for an example
  2146. */
  2147. L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
  2148. this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
  2149. this._loadTile(tile, tilePoint);
  2150. if (tile.parentNode !== this._tileContainer) {
  2151. container.appendChild(tile);
  2152. }
  2153. },
  2154. _getZoomForUrl: function () {
  2155. var options = this.options,
  2156. zoom = this._map.getZoom();
  2157. if (options.zoomReverse) {
  2158. zoom = options.maxZoom - zoom;
  2159. }
  2160. zoom += options.zoomOffset;
  2161. return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;
  2162. },
  2163. _getTilePos: function (tilePoint) {
  2164. var origin = this._map.getPixelOrigin(),
  2165. tileSize = this._getTileSize();
  2166. return tilePoint.multiplyBy(tileSize).subtract(origin);
  2167. },
  2168. // image-specific code (override to implement e.g. Canvas or SVG tile layer)
  2169. getTileUrl: function (tilePoint) {
  2170. return L.Util.template(this._url, L.extend({
  2171. s: this._getSubdomain(tilePoint),
  2172. z: tilePoint.z,
  2173. x: tilePoint.x,
  2174. y: tilePoint.y
  2175. }, this.options));
  2176. },
  2177. _getWrapTileNum: function () {
  2178. var crs = this._map.options.crs,
  2179. size = crs.getSize(this._map.getZoom());
  2180. return size.divideBy(this._getTileSize())._floor();
  2181. },
  2182. _adjustTilePoint: function (tilePoint) {
  2183. var limit = this._getWrapTileNum();
  2184. // wrap tile coordinates
  2185. if (!this.options.continuousWorld && !this.options.noWrap) {
  2186. tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;
  2187. }
  2188. if (this.options.tms) {
  2189. tilePoint.y = limit.y - tilePoint.y - 1;
  2190. }
  2191. tilePoint.z = this._getZoomForUrl();
  2192. },
  2193. _getSubdomain: function (tilePoint) {
  2194. var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
  2195. return this.options.subdomains[index];
  2196. },
  2197. _getTile: function () {
  2198. if (this.options.reuseTiles && this._unusedTiles.length > 0) {
  2199. var tile = this._unusedTiles.pop();
  2200. this._resetTile(tile);
  2201. return tile;
  2202. }
  2203. return this._createTile();
  2204. },
  2205. // Override if data stored on a tile needs to be cleaned up before reuse
  2206. _resetTile: function (/*tile*/) {},
  2207. _createTile: function () {
  2208. var tile = L.DomUtil.create('img', 'leaflet-tile');
  2209. tile.style.width = tile.style.height = this._getTileSize() + 'px';
  2210. tile.galleryimg = 'no';
  2211. tile.onselectstart = tile.onmousemove = L.Util.falseFn;
  2212. if (L.Browser.ielt9 && this.options.opacity !== undefined) {
  2213. L.DomUtil.setOpacity(tile, this.options.opacity);
  2214. }
  2215. // without this hack, tiles disappear after zoom on Chrome for Android
  2216. // https://github.com/Leaflet/Leaflet/issues/2078
  2217. if (L.Browser.mobileWebkit3d) {
  2218. tile.style.WebkitBackfaceVisibility = 'hidden';
  2219. }
  2220. return tile;
  2221. },
  2222. _loadTile: function (tile, tilePoint) {
  2223. tile._layer = this;
  2224. tile.onload = this._tileOnLoad;
  2225. tile.onerror = this._tileOnError;
  2226. this._adjustTilePoint(tilePoint);
  2227. tile.src = this.getTileUrl(tilePoint);
  2228. this.fire('tileloadstart', {
  2229. tile: tile,
  2230. url: tile.src
  2231. });
  2232. },
  2233. _tileLoaded: function () {
  2234. this._tilesToLoad--;
  2235. if (this._animated) {
  2236. L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');
  2237. }
  2238. if (!this._tilesToLoad) {
  2239. this.fire('load');
  2240. if (this._animated) {
  2241. // clear scaled tiles after all new tiles are loaded (for performance)
  2242. clearTimeout(this._clearBgBufferTimer);
  2243. this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
  2244. }
  2245. }
  2246. },
  2247. _tileOnLoad: function () {
  2248. var layer = this._layer;
  2249. //Only if we are loading an actual image
  2250. if (this.src !== L.Util.emptyImageUrl) {
  2251. L.DomUtil.addClass(this, 'leaflet-tile-loaded');
  2252. layer.fire('tileload', {
  2253. tile: this,
  2254. url: this.src
  2255. });
  2256. }
  2257. layer._tileLoaded();
  2258. },
  2259. _tileOnError: function () {
  2260. var layer = this._layer;
  2261. layer.fire('tileerror', {
  2262. tile: this,
  2263. url: this.src
  2264. });
  2265. var newUrl = layer.options.errorTileUrl;
  2266. if (newUrl) {
  2267. this.src = newUrl;
  2268. }
  2269. layer._tileLoaded();
  2270. }
  2271. });
  2272. L.tileLayer = function (url, options) {
  2273. return new L.TileLayer(url, options);
  2274. };
  2275. /*
  2276. * L.TileLayer.WMS is used for putting WMS tile layers on the map.
  2277. */
  2278. L.TileLayer.WMS = L.TileLayer.extend({
  2279. defaultWmsParams: {
  2280. service: 'WMS',
  2281. request: 'GetMap',
  2282. version: '1.1.1',
  2283. layers: '',
  2284. styles: '',
  2285. format: 'image/jpeg',
  2286. transparent: false
  2287. },
  2288. initialize: function (url, options) { // (String, Object)
  2289. this._url = url;
  2290. var wmsParams = L.extend({}, this.defaultWmsParams),
  2291. tileSize = options.tileSize || this.options.tileSize;
  2292. if (options.detectRetina && L.Browser.retina) {
  2293. wmsParams.width = wmsParams.height = tileSize * 2;
  2294. } else {
  2295. wmsParams.width = wmsParams.height = tileSize;
  2296. }
  2297. for (var i in options) {
  2298. // all keys that are not TileLayer options go to WMS params
  2299. if (!this.options.hasOwnProperty(i) && i !== 'crs') {
  2300. wmsParams[i] = options[i];
  2301. }
  2302. }
  2303. this.wmsParams = wmsParams;
  2304. L.setOptions(this, options);
  2305. },
  2306. onAdd: function (map) {
  2307. this._crs = this.options.crs || map.options.crs;
  2308. this._wmsVersion = parseFloat(this.wmsParams.version);
  2309. var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
  2310. this.wmsParams[projectionKey] = this._crs.code;
  2311. L.TileLayer.prototype.onAdd.call(this, map);
  2312. },
  2313. getTileUrl: function (tilePoint) { // (Point, Number) -> String
  2314. var map = this._map,
  2315. tileSize = this.options.tileSize,
  2316. nwPoint = tilePoint.multiplyBy(tileSize),
  2317. sePoint = nwPoint.add([tileSize, tileSize]),
  2318. nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)),
  2319. se = this._crs.project(map.unproject(sePoint, tilePoint.z)),
  2320. bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
  2321. [se.y, nw.x, nw.y, se.x].join(',') :
  2322. [nw.x, se.y, se.x, nw.y].join(','),
  2323. url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
  2324. return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;
  2325. },
  2326. setParams: function (params, noRedraw) {
  2327. L.extend(this.wmsParams, params);
  2328. if (!noRedraw) {
  2329. this.redraw();
  2330. }
  2331. return this;
  2332. }
  2333. });
  2334. L.tileLayer.wms = function (url, options) {
  2335. return new L.TileLayer.WMS(url, options);
  2336. };
  2337. /*
  2338. * L.TileLayer.Canvas is a class that you can use as a base for creating
  2339. * dynamically drawn Canvas-based tile layers.
  2340. */
  2341. L.TileLayer.Canvas = L.TileLayer.extend({
  2342. options: {
  2343. async: false
  2344. },
  2345. initialize: function (options) {
  2346. L.setOptions(this, options);
  2347. },
  2348. redraw: function () {
  2349. if (this._map) {
  2350. this._reset({hard: true});
  2351. this._update();
  2352. }
  2353. for (var i in this._tiles) {
  2354. this._redrawTile(this._tiles[i]);
  2355. }
  2356. return this;
  2357. },
  2358. _redrawTile: function (tile) {
  2359. this.drawTile(tile, tile._tilePoint, this._map._zoom);
  2360. },
  2361. _createTile: function () {
  2362. var tile = L.DomUtil.create('canvas', 'leaflet-tile');
  2363. tile.width = tile.height = this.options.tileSize;
  2364. tile.onselectstart = tile.onmousemove = L.Util.falseFn;
  2365. return tile;
  2366. },
  2367. _loadTile: function (tile, tilePoint) {
  2368. tile._layer = this;
  2369. tile._tilePoint = tilePoint;
  2370. this._redrawTile(tile);
  2371. if (!this.options.async) {
  2372. this.tileDrawn(tile);
  2373. }
  2374. },
  2375. drawTile: function (/*tile, tilePoint*/) {
  2376. // override with rendering code
  2377. },
  2378. tileDrawn: function (tile) {
  2379. this._tileOnLoad.call(tile);
  2380. }
  2381. });
  2382. L.tileLayer.canvas = function (options) {
  2383. return new L.TileLayer.Canvas(options);
  2384. };
  2385. /*
  2386. * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
  2387. */
  2388. L.ImageOverlay = L.Class.extend({
  2389. includes: L.Mixin.Events,
  2390. options: {
  2391. opacity: 1
  2392. },
  2393. initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
  2394. this._url = url;
  2395. this._bounds = L.latLngBounds(bounds);
  2396. L.setOptions(this, options);
  2397. },
  2398. onAdd: function (map) {
  2399. this._map = map;
  2400. if (!this._image) {
  2401. this._initImage();
  2402. }
  2403. map._panes.overlayPane.appendChild(this._image);
  2404. map.on('viewreset', this._reset, this);
  2405. if (map.options.zoomAnimation && L.Browser.any3d) {
  2406. map.on('zoomanim', this._animateZoom, this);
  2407. }
  2408. this._reset();
  2409. },
  2410. onRemove: function (map) {
  2411. map.getPanes().overlayPane.removeChild(this._image);
  2412. map.off('viewreset', this._reset, this);
  2413. if (map.options.zoomAnimation) {
  2414. map.off('zoomanim', this._animateZoom, this);
  2415. }
  2416. },
  2417. addTo: function (map) {
  2418. map.addLayer(this);
  2419. return this;
  2420. },
  2421. setOpacity: function (opacity) {
  2422. this.options.opacity = opacity;
  2423. this._updateOpacity();
  2424. return this;
  2425. },
  2426. // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
  2427. bringToFront: function () {
  2428. if (this._image) {
  2429. this._map._panes.overlayPane.appendChild(this._image);
  2430. }
  2431. return this;
  2432. },
  2433. bringToBack: function () {
  2434. var pane = this._map._panes.overlayPane;
  2435. if (this._image) {
  2436. pane.insertBefore(this._image, pane.firstChild);
  2437. }
  2438. return this;
  2439. },
  2440. setUrl: function (url) {
  2441. this._url = url;
  2442. this._image.src = this._url;
  2443. },
  2444. getAttribution: function () {
  2445. return this.options.attribution;
  2446. },
  2447. _initImage: function () {
  2448. this._image = L.DomUtil.create('img', 'leaflet-image-layer');
  2449. if (this._map.options.zoomAnimation && L.Browser.any3d) {
  2450. L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
  2451. } else {
  2452. L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
  2453. }
  2454. this._updateOpacity();
  2455. //TODO createImage util method to remove duplication
  2456. L.extend(this._image, {
  2457. galleryimg: 'no',
  2458. onselectstart: L.Util.falseFn,
  2459. onmousemove: L.Util.falseFn,
  2460. onload: L.bind(this._onImageLoad, this),
  2461. src: this._url
  2462. });
  2463. },
  2464. _animateZoom: function (e) {
  2465. var map = this._map,
  2466. image = this._image,
  2467. scale = map.getZoomScale(e.zoom),
  2468. nw = this._bounds.getNorthWest(),
  2469. se = this._bounds.getSouthEast(),
  2470. topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
  2471. size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
  2472. origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
  2473. image.style[L.DomUtil.TRANSFORM] =
  2474. L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
  2475. },
  2476. _reset: function () {
  2477. var image = this._image,
  2478. topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
  2479. size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
  2480. L.DomUtil.setPosition(image, topLeft);
  2481. image.style.width = size.x + 'px';
  2482. image.style.height = size.y + 'px';
  2483. },
  2484. _onImageLoad: function () {
  2485. this.fire('load');
  2486. },
  2487. _updateOpacity: function () {
  2488. L.DomUtil.setOpacity(this._image, this.options.opacity);
  2489. }
  2490. });
  2491. L.imageOverlay = function (url, bounds, options) {
  2492. return new L.ImageOverlay(url, bounds, options);
  2493. };
  2494. /*
  2495. * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
  2496. */
  2497. L.Icon = L.Class.extend({
  2498. options: {
  2499. /*
  2500. iconUrl: (String) (required)
  2501. iconRetinaUrl: (String) (optional, used for retina devices if detected)
  2502. iconSize: (Point) (can be set through CSS)
  2503. iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
  2504. popupAnchor: (Point) (if not specified, popup opens in the anchor point)
  2505. shadowUrl: (String) (no shadow by default)
  2506. shadowRetinaUrl: (String) (optional, used for retina devices if detected)
  2507. shadowSize: (Point)
  2508. shadowAnchor: (Point)
  2509. */
  2510. className: ''
  2511. },
  2512. initialize: function (options) {
  2513. L.setOptions(this, options);
  2514. },
  2515. createIcon: function (oldIcon) {
  2516. return this._createIcon('icon', oldIcon);
  2517. },
  2518. createShadow: function (oldIcon) {
  2519. return this._createIcon('shadow', oldIcon);
  2520. },
  2521. _createIcon: function (name, oldIcon) {
  2522. var src = this._getIconUrl(name);
  2523. if (!src) {
  2524. if (name === 'icon') {
  2525. throw new Error('iconUrl not set in Icon options (see the docs).');
  2526. }
  2527. return null;
  2528. }
  2529. var img;
  2530. if (!oldIcon || oldIcon.tagName !== 'IMG') {
  2531. img = this._createImg(src);
  2532. } else {
  2533. img = this._createImg(src, oldIcon);
  2534. }
  2535. this._setIconStyles(img, name);
  2536. return img;
  2537. },
  2538. _setIconStyles: function (img, name) {
  2539. var options = this.options,
  2540. size = L.point(options[name + 'Size']),
  2541. anchor;
  2542. if (name === 'shadow') {
  2543. anchor = L.point(options.shadowAnchor || options.iconAnchor);
  2544. } else {
  2545. anchor = L.point(options.iconAnchor);
  2546. }
  2547. if (!anchor && size) {
  2548. anchor = size.divideBy(2, true);
  2549. }
  2550. img.className = 'leaflet-marker-' + name + ' ' + options.className;
  2551. if (anchor) {
  2552. img.style.marginLeft = (-anchor.x) + 'px';
  2553. img.style.marginTop = (-anchor.y) + 'px';
  2554. }
  2555. if (size) {
  2556. img.style.width = size.x + 'px';
  2557. img.style.height = size.y + 'px';
  2558. }
  2559. },
  2560. _createImg: function (src, el) {
  2561. el = el || document.createElement('img');
  2562. el.src = src;
  2563. return el;
  2564. },
  2565. _getIconUrl: function (name) {
  2566. if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
  2567. return this.options[name + 'RetinaUrl'];
  2568. }
  2569. return this.options[name + 'Url'];
  2570. }
  2571. });
  2572. L.icon = function (options) {
  2573. return new L.Icon(options);
  2574. };
  2575. /*
  2576. * L.Icon.Default is the blue marker icon used by default in Leaflet.
  2577. */
  2578. L.Icon.Default = L.Icon.extend({
  2579. options: {
  2580. iconSize: [25, 41],
  2581. iconAnchor: [12, 41],
  2582. popupAnchor: [1, -34],
  2583. shadowSize: [41, 41]
  2584. },
  2585. _getIconUrl: function (name) {
  2586. var key = name + 'Url';
  2587. if (this.options[key]) {
  2588. return this.options[key];
  2589. }
  2590. if (L.Browser.retina && name === 'icon') {
  2591. name += '-2x';
  2592. }
  2593. var path = L.Icon.Default.imagePath;
  2594. if (!path) {
  2595. throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
  2596. }
  2597. return path + '/marker-' + name + '.png';
  2598. }
  2599. });
  2600. L.Icon.Default.imagePath = (function () {
  2601. var scripts = document.getElementsByTagName('script'),
  2602. leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
  2603. var i, len, src, matches, path;
  2604. for (i = 0, len = scripts.length; i < len; i++) {
  2605. src = scripts[i].src;
  2606. matches = src.match(leafletRe);
  2607. if (matches) {
  2608. path = src.split(leafletRe)[0];
  2609. return (path ? path + '/' : '') + 'images';
  2610. }
  2611. }
  2612. }());
  2613. /*
  2614. * L.Marker is used to display clickable/draggable icons on the map.
  2615. */
  2616. L.Marker = L.Class.extend({
  2617. includes: L.Mixin.Events,
  2618. options: {
  2619. icon: new L.Icon.Default(),
  2620. title: '',
  2621. alt: '',
  2622. clickable: true,
  2623. draggable: false,
  2624. keyboard: true,
  2625. zIndexOffset: 0,
  2626. opacity: 1,
  2627. riseOnHover: false,
  2628. riseOffset: 250
  2629. },
  2630. initialize: function (latlng, options) {
  2631. L.setOptions(this, options);
  2632. this._latlng = L.latLng(latlng);
  2633. },
  2634. onAdd: function (map) {
  2635. this._map = map;
  2636. map.on('viewreset', this.update, this);
  2637. this._initIcon();
  2638. this.update();
  2639. this.fire('add');
  2640. if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
  2641. map.on('zoomanim', this._animateZoom, this);
  2642. }
  2643. },
  2644. addTo: function (map) {
  2645. map.addLayer(this);
  2646. return this;
  2647. },
  2648. onRemove: function (map) {
  2649. if (this.dragging) {
  2650. this.dragging.disable();
  2651. }
  2652. this._removeIcon();
  2653. this._removeShadow();
  2654. this.fire('remove');
  2655. map.off({
  2656. 'viewreset': this.update,
  2657. 'zoomanim': this._animateZoom
  2658. }, this);
  2659. this._map = null;
  2660. },
  2661. getLatLng: function () {
  2662. return this._latlng;
  2663. },
  2664. setLatLng: function (latlng) {
  2665. this._latlng = L.latLng(latlng);
  2666. this.update();
  2667. return this.fire('move', { latlng: this._latlng });
  2668. },
  2669. setZIndexOffset: function (offset) {
  2670. this.options.zIndexOffset = offset;
  2671. this.update();
  2672. return this;
  2673. },
  2674. setIcon: function (icon) {
  2675. this.options.icon = icon;
  2676. if (this._map) {
  2677. this._initIcon();
  2678. this.update();
  2679. }
  2680. if (this._popup) {
  2681. this.bindPopup(this._popup);
  2682. }
  2683. return this;
  2684. },
  2685. update: function () {
  2686. if (this._icon) {
  2687. var pos = this._map.latLngToLayerPoint(this._latlng).round();
  2688. this._setPos(pos);
  2689. }
  2690. return this;
  2691. },
  2692. _initIcon: function () {
  2693. var options = this.options,
  2694. map = this._map,
  2695. animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
  2696. classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
  2697. var icon = options.icon.createIcon(this._icon),
  2698. addIcon = false;
  2699. // if we're not reusing the icon, remove the old one and init new one
  2700. if (icon !== this._icon) {
  2701. if (this._icon) {
  2702. this._removeIcon();
  2703. }
  2704. addIcon = true;
  2705. if (options.title) {
  2706. icon.title = options.title;
  2707. }
  2708. if (options.alt) {
  2709. icon.alt = options.alt;
  2710. }
  2711. }
  2712. L.DomUtil.addClass(icon, classToAdd);
  2713. if (options.keyboard) {
  2714. icon.tabIndex = '0';
  2715. }
  2716. this._icon = icon;
  2717. this._initInteraction();
  2718. if (options.riseOnHover) {
  2719. L.DomEvent
  2720. .on(icon, 'mouseover', this._bringToFront, this)
  2721. .on(icon, 'mouseout', this._resetZIndex, this);
  2722. }
  2723. var newShadow = options.icon.createShadow(this._shadow),
  2724. addShadow = false;
  2725. if (newShadow !== this._shadow) {
  2726. this._removeShadow();
  2727. addShadow = true;
  2728. }
  2729. if (newShadow) {
  2730. L.DomUtil.addClass(newShadow, classToAdd);
  2731. }
  2732. this._shadow = newShadow;
  2733. if (options.opacity < 1) {
  2734. this._updateOpacity();
  2735. }
  2736. var panes = this._map._panes;
  2737. if (addIcon) {
  2738. panes.markerPane.appendChild(this._icon);
  2739. }
  2740. if (newShadow && addShadow) {
  2741. panes.shadowPane.appendChild(this._shadow);
  2742. }
  2743. },
  2744. _removeIcon: function () {
  2745. if (this.options.riseOnHover) {
  2746. L.DomEvent
  2747. .off(this._icon, 'mouseover', this._bringToFront)
  2748. .off(this._icon, 'mouseout', this._resetZIndex);
  2749. }
  2750. this._map._panes.markerPane.removeChild(this._icon);
  2751. this._icon = null;
  2752. },
  2753. _removeShadow: function () {
  2754. if (this._shadow) {
  2755. this._map._panes.shadowPane.removeChild(this._shadow);
  2756. }
  2757. this._shadow = null;
  2758. },
  2759. _setPos: function (pos) {
  2760. L.DomUtil.setPosition(this._icon, pos);
  2761. if (this._shadow) {
  2762. L.DomUtil.setPosition(this._shadow, pos);
  2763. }
  2764. this._zIndex = pos.y + this.options.zIndexOffset;
  2765. this._resetZIndex();
  2766. },
  2767. _updateZIndex: function (offset) {
  2768. this._icon.style.zIndex = this._zIndex + offset;
  2769. },
  2770. _animateZoom: function (opt) {
  2771. var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
  2772. this._setPos(pos);
  2773. },
  2774. _initInteraction: function () {
  2775. if (!this.options.clickable) { return; }
  2776. // TODO refactor into something shared with Map/Path/etc. to DRY it up
  2777. var icon = this._icon,
  2778. events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
  2779. L.DomUtil.addClass(icon, 'leaflet-clickable');
  2780. L.DomEvent.on(icon, 'click', this._onMouseClick, this);
  2781. L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
  2782. for (var i = 0; i < events.length; i++) {
  2783. L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
  2784. }
  2785. if (L.Handler.MarkerDrag) {
  2786. this.dragging = new L.Handler.MarkerDrag(this);
  2787. if (this.options.draggable) {
  2788. this.dragging.enable();
  2789. }
  2790. }
  2791. },
  2792. _onMouseClick: function (e) {
  2793. var wasDragged = this.dragging && this.dragging.moved();
  2794. if (this.hasEventListeners(e.type) || wasDragged) {
  2795. L.DomEvent.stopPropagation(e);
  2796. }
  2797. if (wasDragged) { return; }
  2798. if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
  2799. this.fire(e.type, {
  2800. originalEvent: e,
  2801. latlng: this._latlng
  2802. });
  2803. },
  2804. _onKeyPress: function (e) {
  2805. if (e.keyCode === 13) {
  2806. this.fire('click', {
  2807. originalEvent: e,
  2808. latlng: this._latlng
  2809. });
  2810. }
  2811. },
  2812. _fireMouseEvent: function (e) {
  2813. this.fire(e.type, {
  2814. originalEvent: e,
  2815. latlng: this._latlng
  2816. });
  2817. // TODO proper custom event propagation
  2818. // this line will always be called if marker is in a FeatureGroup
  2819. if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
  2820. L.DomEvent.preventDefault(e);
  2821. }
  2822. if (e.type !== 'mousedown') {
  2823. L.DomEvent.stopPropagation(e);
  2824. } else {
  2825. L.DomEvent.preventDefault(e);
  2826. }
  2827. },
  2828. setOpacity: function (opacity) {
  2829. this.options.opacity = opacity;
  2830. if (this._map) {
  2831. this._updateOpacity();
  2832. }
  2833. return this;
  2834. },
  2835. _updateOpacity: function () {
  2836. L.DomUtil.setOpacity(this._icon, this.options.opacity);
  2837. if (this._shadow) {
  2838. L.DomUtil.setOpacity(this._shadow, this.options.opacity);
  2839. }
  2840. },
  2841. _bringToFront: function () {
  2842. this._updateZIndex(this.options.riseOffset);
  2843. },
  2844. _resetZIndex: function () {
  2845. this._updateZIndex(0);
  2846. }
  2847. });
  2848. L.marker = function (latlng, options) {
  2849. return new L.Marker(latlng, options);
  2850. };
  2851. /*
  2852. * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
  2853. * to use with L.Marker.
  2854. */
  2855. L.DivIcon = L.Icon.extend({
  2856. options: {
  2857. iconSize: [12, 12], // also can be set through CSS
  2858. /*
  2859. iconAnchor: (Point)
  2860. popupAnchor: (Point)
  2861. html: (String)
  2862. bgPos: (Point)
  2863. */
  2864. className: 'leaflet-div-icon',
  2865. html: false
  2866. },
  2867. createIcon: function (oldIcon) {
  2868. var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
  2869. options = this.options;
  2870. if (options.html !== false) {
  2871. div.innerHTML = options.html;
  2872. } else {
  2873. div.innerHTML = '';
  2874. }
  2875. if (options.bgPos) {
  2876. div.style.backgroundPosition =
  2877. (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
  2878. }
  2879. this._setIconStyles(div, 'icon');
  2880. return div;
  2881. },
  2882. createShadow: function () {
  2883. return null;
  2884. }
  2885. });
  2886. L.divIcon = function (options) {
  2887. return new L.DivIcon(options);
  2888. };
  2889. /*
  2890. * L.Popup is used for displaying popups on the map.
  2891. */
  2892. L.Map.mergeOptions({
  2893. closePopupOnClick: true
  2894. });
  2895. L.Popup = L.Class.extend({
  2896. includes: L.Mixin.Events,
  2897. options: {
  2898. minWidth: 50,
  2899. maxWidth: 300,
  2900. // maxHeight: null,
  2901. autoPan: true,
  2902. closeButton: true,
  2903. offset: [0, 7],
  2904. autoPanPadding: [5, 5],
  2905. // autoPanPaddingTopLeft: null,
  2906. // autoPanPaddingBottomRight: null,
  2907. keepInView: false,
  2908. className: '',
  2909. zoomAnimation: true
  2910. },
  2911. initialize: function (options, source) {
  2912. L.setOptions(this, options);
  2913. this._source = source;
  2914. this._animated = L.Browser.any3d && this.options.zoomAnimation;
  2915. this._isOpen = false;
  2916. },
  2917. onAdd: function (map) {
  2918. this._map = map;
  2919. if (!this._container) {
  2920. this._initLayout();
  2921. }
  2922. var animFade = map.options.fadeAnimation;
  2923. if (animFade) {
  2924. L.DomUtil.setOpacity(this._container, 0);
  2925. }
  2926. map._panes.popupPane.appendChild(this._container);
  2927. map.on(this._getEvents(), this);
  2928. this.update();
  2929. if (animFade) {
  2930. L.DomUtil.setOpacity(this._container, 1);
  2931. }
  2932. this.fire('open');
  2933. map.fire('popupopen', {popup: this});
  2934. if (this._source) {
  2935. this._source.fire('popupopen', {popup: this});
  2936. }
  2937. },
  2938. addTo: function (map) {
  2939. map.addLayer(this);
  2940. return this;
  2941. },
  2942. openOn: function (map) {
  2943. map.openPopup(this);
  2944. return this;
  2945. },
  2946. onRemove: function (map) {
  2947. map._panes.popupPane.removeChild(this._container);
  2948. L.Util.falseFn(this._container.offsetWidth); // force reflow
  2949. map.off(this._getEvents(), this);
  2950. if (map.options.fadeAnimation) {
  2951. L.DomUtil.setOpacity(this._container, 0);
  2952. }
  2953. this._map = null;
  2954. this.fire('close');
  2955. map.fire('popupclose', {popup: this});
  2956. if (this._source) {
  2957. this._source.fire('popupclose', {popup: this});
  2958. }
  2959. },
  2960. getLatLng: function () {
  2961. return this._latlng;
  2962. },
  2963. setLatLng: function (latlng) {
  2964. this._latlng = L.latLng(latlng);
  2965. if (this._map) {
  2966. this._updatePosition();
  2967. this._adjustPan();
  2968. }
  2969. return this;
  2970. },
  2971. getContent: function () {
  2972. return this._content;
  2973. },
  2974. setContent: function (content) {
  2975. this._content = content;
  2976. this.update();
  2977. return this;
  2978. },
  2979. update: function () {
  2980. if (!this._map) { return; }
  2981. this._container.style.visibility = 'hidden';
  2982. this._updateContent();
  2983. this._updateLayout();
  2984. this._updatePosition();
  2985. this._container.style.visibility = '';
  2986. this._adjustPan();
  2987. },
  2988. _getEvents: function () {
  2989. var events = {
  2990. viewreset: this._updatePosition
  2991. };
  2992. if (this._animated) {
  2993. events.zoomanim = this._zoomAnimation;
  2994. }
  2995. if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
  2996. events.preclick = this._close;
  2997. }
  2998. if (this.options.keepInView) {
  2999. events.moveend = this._adjustPan;
  3000. }
  3001. return events;
  3002. },
  3003. _close: function () {
  3004. if (this._map) {
  3005. this._map.closePopup(this);
  3006. }
  3007. },
  3008. _initLayout: function () {
  3009. var prefix = 'leaflet-popup',
  3010. containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
  3011. (this._animated ? 'animated' : 'hide'),
  3012. container = this._container = L.DomUtil.create('div', containerClass),
  3013. closeButton;
  3014. if (this.options.closeButton) {
  3015. closeButton = this._closeButton =
  3016. L.DomUtil.create('a', prefix + '-close-button', container);
  3017. closeButton.href = '#close';
  3018. closeButton.innerHTML = '&#215;';
  3019. L.DomEvent.disableClickPropagation(closeButton);
  3020. L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
  3021. }
  3022. var wrapper = this._wrapper =
  3023. L.DomUtil.create('div', prefix + '-content-wrapper', container);
  3024. L.DomEvent.disableClickPropagation(wrapper);
  3025. this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
  3026. L.DomEvent.disableScrollPropagation(this._contentNode);
  3027. L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
  3028. this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
  3029. this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
  3030. },
  3031. _updateContent: function () {
  3032. if (!this._content) { return; }
  3033. if (typeof this._content === 'string') {
  3034. this._contentNode.innerHTML = this._content;
  3035. } else {
  3036. while (this._contentNode.hasChildNodes()) {
  3037. this._contentNode.removeChild(this._contentNode.firstChild);
  3038. }
  3039. this._contentNode.appendChild(this._content);
  3040. }
  3041. this.fire('contentupdate');
  3042. },
  3043. _updateLayout: function () {
  3044. var container = this._contentNode,
  3045. style = container.style;
  3046. style.width = '';
  3047. style.whiteSpace = 'nowrap';
  3048. var width = container.offsetWidth;
  3049. width = Math.min(width, this.options.maxWidth);
  3050. width = Math.max(width, this.options.minWidth);
  3051. style.width = (width + 1) + 'px';
  3052. style.whiteSpace = '';
  3053. style.height = '';
  3054. var height = container.offsetHeight,
  3055. maxHeight = this.options.maxHeight,
  3056. scrolledClass = 'leaflet-popup-scrolled';
  3057. if (maxHeight && height > maxHeight) {
  3058. style.height = maxHeight + 'px';
  3059. L.DomUtil.addClass(container, scrolledClass);
  3060. } else {
  3061. L.DomUtil.removeClass(container, scrolledClass);
  3062. }
  3063. this._containerWidth = this._container.offsetWidth;
  3064. },
  3065. _updatePosition: function () {
  3066. if (!this._map) { return; }
  3067. var pos = this._map.latLngToLayerPoint(this._latlng),
  3068. animated = this._animated,
  3069. offset = L.point(this.options.offset);
  3070. if (animated) {
  3071. L.DomUtil.setPosition(this._container, pos);
  3072. }
  3073. this._containerBottom = -offset.y - (animated ? 0 : pos.y);
  3074. this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
  3075. // bottom position the popup in case the height of the popup changes (images loading etc)
  3076. this._container.style.bottom = this._containerBottom + 'px';
  3077. this._container.style.left = this._containerLeft + 'px';
  3078. },
  3079. _zoomAnimation: function (opt) {
  3080. var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
  3081. L.DomUtil.setPosition(this._container, pos);
  3082. },
  3083. _adjustPan: function () {
  3084. if (!this.options.autoPan) { return; }
  3085. var map = this._map,
  3086. containerHeight = this._container.offsetHeight,
  3087. containerWidth = this._containerWidth,
  3088. layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
  3089. if (this._animated) {
  3090. layerPos._add(L.DomUtil.getPosition(this._container));
  3091. }
  3092. var containerPos = map.layerPointToContainerPoint(layerPos),
  3093. padding = L.point(this.options.autoPanPadding),
  3094. paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
  3095. paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
  3096. size = map.getSize(),
  3097. dx = 0,
  3098. dy = 0;
  3099. if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
  3100. dx = containerPos.x + containerWidth - size.x + paddingBR.x;
  3101. }
  3102. if (containerPos.x - dx - paddingTL.x < 0) { // left
  3103. dx = containerPos.x - paddingTL.x;
  3104. }
  3105. if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
  3106. dy = containerPos.y + containerHeight - size.y + paddingBR.y;
  3107. }
  3108. if (containerPos.y - dy - paddingTL.y < 0) { // top
  3109. dy = containerPos.y - paddingTL.y;
  3110. }
  3111. if (dx || dy) {
  3112. map
  3113. .fire('autopanstart')
  3114. .panBy([dx, dy]);
  3115. }
  3116. },
  3117. _onCloseButtonClick: function (e) {
  3118. this._close();
  3119. L.DomEvent.stop(e);
  3120. }
  3121. });
  3122. L.popup = function (options, source) {
  3123. return new L.Popup(options, source);
  3124. };
  3125. L.Map.include({
  3126. openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])
  3127. this.closePopup();
  3128. if (!(popup instanceof L.Popup)) {
  3129. var content = popup;
  3130. popup = new L.Popup(options)
  3131. .setLatLng(latlng)
  3132. .setContent(content);
  3133. }
  3134. popup._isOpen = true;
  3135. this._popup = popup;
  3136. return this.addLayer(popup);
  3137. },
  3138. closePopup: function (popup) {
  3139. if (!popup || popup === this._popup) {
  3140. popup = this._popup;
  3141. this._popup = null;
  3142. }
  3143. if (popup) {
  3144. this.removeLayer(popup);
  3145. popup._isOpen = false;
  3146. }
  3147. return this;
  3148. }
  3149. });
  3150. /*
  3151. * Popup extension to L.Marker, adding popup-related methods.
  3152. */
  3153. L.Marker.include({
  3154. openPopup: function () {
  3155. if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
  3156. this._popup.setLatLng(this._latlng);
  3157. this._map.openPopup(this._popup);
  3158. }
  3159. return this;
  3160. },
  3161. closePopup: function () {
  3162. if (this._popup) {
  3163. this._popup._close();
  3164. }
  3165. return this;
  3166. },
  3167. togglePopup: function () {
  3168. if (this._popup) {
  3169. if (this._popup._isOpen) {
  3170. this.closePopup();
  3171. } else {
  3172. this.openPopup();
  3173. }
  3174. }
  3175. return this;
  3176. },
  3177. bindPopup: function (content, options) {
  3178. var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
  3179. anchor = anchor.add(L.Popup.prototype.options.offset);
  3180. if (options && options.offset) {
  3181. anchor = anchor.add(options.offset);
  3182. }
  3183. options = L.extend({offset: anchor}, options);
  3184. if (!this._popupHandlersAdded) {
  3185. this
  3186. .on('click', this.togglePopup, this)
  3187. .on('remove', this.closePopup, this)
  3188. .on('move', this._movePopup, this);
  3189. this._popupHandlersAdded = true;
  3190. }
  3191. if (content instanceof L.Popup) {
  3192. L.setOptions(content, options);
  3193. this._popup = content;
  3194. } else {
  3195. this._popup = new L.Popup(options, this)
  3196. .setContent(content);
  3197. }
  3198. return this;
  3199. },
  3200. setPopupContent: function (content) {
  3201. if (this._popup) {
  3202. this._popup.setContent(content);
  3203. }
  3204. return this;
  3205. },
  3206. unbindPopup: function () {
  3207. if (this._popup) {
  3208. this._popup = null;
  3209. this
  3210. .off('click', this.togglePopup, this)
  3211. .off('remove', this.closePopup, this)
  3212. .off('move', this._movePopup, this);
  3213. this._popupHandlersAdded = false;
  3214. }
  3215. return this;
  3216. },
  3217. getPopup: function () {
  3218. return this._popup;
  3219. },
  3220. _movePopup: function (e) {
  3221. this._popup.setLatLng(e.latlng);
  3222. }
  3223. });
  3224. /*
  3225. * L.LayerGroup is a class to combine several layers into one so that
  3226. * you can manipulate the group (e.g. add/remove it) as one layer.
  3227. */
  3228. L.LayerGroup = L.Class.extend({
  3229. initialize: function (layers) {
  3230. this._layers = {};
  3231. var i, len;
  3232. if (layers) {
  3233. for (i = 0, len = layers.length; i < len; i++) {
  3234. this.addLayer(layers[i]);
  3235. }
  3236. }
  3237. },
  3238. addLayer: function (layer) {
  3239. var id = this.getLayerId(layer);
  3240. this._layers[id] = layer;
  3241. if (this._map) {
  3242. this._map.addLayer(layer);
  3243. }
  3244. return this;
  3245. },
  3246. removeLayer: function (layer) {
  3247. var id = layer in this._layers ? layer : this.getLayerId(layer);
  3248. if (this._map && this._layers[id]) {
  3249. this._map.removeLayer(this._layers[id]);
  3250. }
  3251. delete this._layers[id];
  3252. return this;
  3253. },
  3254. hasLayer: function (layer) {
  3255. if (!layer) { return false; }
  3256. return (layer in this._layers || this.getLayerId(layer) in this._layers);
  3257. },
  3258. clearLayers: function () {
  3259. this.eachLayer(this.removeLayer, this);
  3260. return this;
  3261. },
  3262. invoke: function (methodName) {
  3263. var args = Array.prototype.slice.call(arguments, 1),
  3264. i, layer;
  3265. for (i in this._layers) {
  3266. layer = this._layers[i];
  3267. if (layer[methodName]) {
  3268. layer[methodName].apply(layer, args);
  3269. }
  3270. }
  3271. return this;
  3272. },
  3273. onAdd: function (map) {
  3274. this._map = map;
  3275. this.eachLayer(map.addLayer, map);
  3276. },
  3277. onRemove: function (map) {
  3278. this.eachLayer(map.removeLayer, map);
  3279. this._map = null;
  3280. },
  3281. addTo: function (map) {
  3282. map.addLayer(this);
  3283. return this;
  3284. },
  3285. eachLayer: function (method, context) {
  3286. for (var i in this._layers) {
  3287. method.call(context, this._layers[i]);
  3288. }
  3289. return this;
  3290. },
  3291. getLayer: function (id) {
  3292. return this._layers[id];
  3293. },
  3294. getLayers: function () {
  3295. var layers = [];
  3296. for (var i in this._layers) {
  3297. layers.push(this._layers[i]);
  3298. }
  3299. return layers;
  3300. },
  3301. setZIndex: function (zIndex) {
  3302. return this.invoke('setZIndex', zIndex);
  3303. },
  3304. getLayerId: function (layer) {
  3305. return L.stamp(layer);
  3306. }
  3307. });
  3308. L.layerGroup = function (layers) {
  3309. return new L.LayerGroup(layers);
  3310. };
  3311. /*
  3312. * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
  3313. * shared between a group of interactive layers (like vectors or markers).
  3314. */
  3315. L.FeatureGroup = L.LayerGroup.extend({
  3316. includes: L.Mixin.Events,
  3317. statics: {
  3318. EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
  3319. },
  3320. addLayer: function (layer) {
  3321. if (this.hasLayer(layer)) {
  3322. return this;
  3323. }
  3324. if ('on' in layer) {
  3325. layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
  3326. }
  3327. L.LayerGroup.prototype.addLayer.call(this, layer);
  3328. if (this._popupContent && layer.bindPopup) {
  3329. layer.bindPopup(this._popupContent, this._popupOptions);
  3330. }
  3331. return this.fire('layeradd', {layer: layer});
  3332. },
  3333. removeLayer: function (layer) {
  3334. if (!this.hasLayer(layer)) {
  3335. return this;
  3336. }
  3337. if (layer in this._layers) {
  3338. layer = this._layers[layer];
  3339. }
  3340. layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
  3341. L.LayerGroup.prototype.removeLayer.call(this, layer);
  3342. if (this._popupContent) {
  3343. this.invoke('unbindPopup');
  3344. }
  3345. return this.fire('layerremove', {layer: layer});
  3346. },
  3347. bindPopup: function (content, options) {
  3348. this._popupContent = content;
  3349. this._popupOptions = options;
  3350. return this.invoke('bindPopup', content, options);
  3351. },
  3352. openPopup: function (latlng) {
  3353. // open popup on the first layer
  3354. for (var id in this._layers) {
  3355. this._layers[id].openPopup(latlng);
  3356. break;
  3357. }
  3358. return this;
  3359. },
  3360. setStyle: function (style) {
  3361. return this.invoke('setStyle', style);
  3362. },
  3363. bringToFront: function () {
  3364. return this.invoke('bringToFront');
  3365. },
  3366. bringToBack: function () {
  3367. return this.invoke('bringToBack');
  3368. },
  3369. getBounds: function () {
  3370. var bounds = new L.LatLngBounds();
  3371. this.eachLayer(function (layer) {
  3372. bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
  3373. });
  3374. return bounds;
  3375. },
  3376. _propagateEvent: function (e) {
  3377. e = L.extend({
  3378. layer: e.target,
  3379. target: this
  3380. }, e);
  3381. this.fire(e.type, e);
  3382. }
  3383. });
  3384. L.featureGroup = function (layers) {
  3385. return new L.FeatureGroup(layers);
  3386. };
  3387. /*
  3388. * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
  3389. */
  3390. L.Path = L.Class.extend({
  3391. includes: [L.Mixin.Events],
  3392. statics: {
  3393. // how much to extend the clip area around the map view
  3394. // (relative to its size, e.g. 0.5 is half the screen in each direction)
  3395. // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
  3396. CLIP_PADDING: (function () {
  3397. var max = L.Browser.mobile ? 1280 : 2000,
  3398. target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;
  3399. return Math.max(0, Math.min(0.5, target));
  3400. })()
  3401. },
  3402. options: {
  3403. stroke: true,
  3404. color: '#0033ff',
  3405. dashArray: null,
  3406. lineCap: null,
  3407. lineJoin: null,
  3408. weight: 5,
  3409. opacity: 0.5,
  3410. fill: false,
  3411. fillColor: null, //same as color by default
  3412. fillOpacity: 0.2,
  3413. clickable: true
  3414. },
  3415. initialize: function (options) {
  3416. L.setOptions(this, options);
  3417. },
  3418. onAdd: function (map) {
  3419. this._map = map;
  3420. if (!this._container) {
  3421. this._initElements();
  3422. this._initEvents();
  3423. }
  3424. this.projectLatlngs();
  3425. this._updatePath();
  3426. if (this._container) {
  3427. this._map._pathRoot.appendChild(this._container);
  3428. }
  3429. this.fire('add');
  3430. map.on({
  3431. 'viewreset': this.projectLatlngs,
  3432. 'moveend': this._updatePath
  3433. }, this);
  3434. },
  3435. addTo: function (map) {
  3436. map.addLayer(this);
  3437. return this;
  3438. },
  3439. onRemove: function (map) {
  3440. map._pathRoot.removeChild(this._container);
  3441. // Need to fire remove event before we set _map to null as the event hooks might need the object
  3442. this.fire('remove');
  3443. this._map = null;
  3444. if (L.Browser.vml) {
  3445. this._container = null;
  3446. this._stroke = null;
  3447. this._fill = null;
  3448. }
  3449. map.off({
  3450. 'viewreset': this.projectLatlngs,
  3451. 'moveend': this._updatePath
  3452. }, this);
  3453. },
  3454. projectLatlngs: function () {
  3455. // do all projection stuff here
  3456. },
  3457. setStyle: function (style) {
  3458. L.setOptions(this, style);
  3459. if (this._container) {
  3460. this._updateStyle();
  3461. }
  3462. return this;
  3463. },
  3464. redraw: function () {
  3465. if (this._map) {
  3466. this.projectLatlngs();
  3467. this._updatePath();
  3468. }
  3469. return this;
  3470. }
  3471. });
  3472. L.Map.include({
  3473. _updatePathViewport: function () {
  3474. var p = L.Path.CLIP_PADDING,
  3475. size = this.getSize(),
  3476. panePos = L.DomUtil.getPosition(this._mapPane),
  3477. min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
  3478. max = min.add(size.multiplyBy(1 + p * 2)._round());
  3479. this._pathViewport = new L.Bounds(min, max);
  3480. }
  3481. });
  3482. /*
  3483. * Extends L.Path with SVG-specific rendering code.
  3484. */
  3485. L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
  3486. L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
  3487. L.Path = L.Path.extend({
  3488. statics: {
  3489. SVG: L.Browser.svg
  3490. },
  3491. bringToFront: function () {
  3492. var root = this._map._pathRoot,
  3493. path = this._container;
  3494. if (path && root.lastChild !== path) {
  3495. root.appendChild(path);
  3496. }
  3497. return this;
  3498. },
  3499. bringToBack: function () {
  3500. var root = this._map._pathRoot,
  3501. path = this._container,
  3502. first = root.firstChild;
  3503. if (path && first !== path) {
  3504. root.insertBefore(path, first);
  3505. }
  3506. return this;
  3507. },
  3508. getPathString: function () {
  3509. // form path string here
  3510. },
  3511. _createElement: function (name) {
  3512. return document.createElementNS(L.Path.SVG_NS, name);
  3513. },
  3514. _initElements: function () {
  3515. this._map._initPathRoot();
  3516. this._initPath();
  3517. this._initStyle();
  3518. },
  3519. _initPath: function () {
  3520. this._container = this._createElement('g');
  3521. this._path = this._createElement('path');
  3522. if (this.options.className) {
  3523. L.DomUtil.addClass(this._path, this.options.className);
  3524. }
  3525. this._container.appendChild(this._path);
  3526. },
  3527. _initStyle: function () {
  3528. if (this.options.stroke) {
  3529. this._path.setAttribute('stroke-linejoin', 'round');
  3530. this._path.setAttribute('stroke-linecap', 'round');
  3531. }
  3532. if (this.options.fill) {
  3533. this._path.setAttribute('fill-rule', 'evenodd');
  3534. }
  3535. if (this.options.pointerEvents) {
  3536. this._path.setAttribute('pointer-events', this.options.pointerEvents);
  3537. }
  3538. if (!this.options.clickable && !this.options.pointerEvents) {
  3539. this._path.setAttribute('pointer-events', 'none');
  3540. }
  3541. this._updateStyle();
  3542. },
  3543. _updateStyle: function () {
  3544. if (this.options.stroke) {
  3545. this._path.setAttribute('stroke', this.options.color);
  3546. this._path.setAttribute('stroke-opacity', this.options.opacity);
  3547. this._path.setAttribute('stroke-width', this.options.weight);
  3548. if (this.options.dashArray) {
  3549. this._path.setAttribute('stroke-dasharray', this.options.dashArray);
  3550. } else {
  3551. this._path.removeAttribute('stroke-dasharray');
  3552. }
  3553. if (this.options.lineCap) {
  3554. this._path.setAttribute('stroke-linecap', this.options.lineCap);
  3555. }
  3556. if (this.options.lineJoin) {
  3557. this._path.setAttribute('stroke-linejoin', this.options.lineJoin);
  3558. }
  3559. } else {
  3560. this._path.setAttribute('stroke', 'none');
  3561. }
  3562. if (this.options.fill) {
  3563. this._path.setAttribute('fill', this.options.fillColor || this.options.color);
  3564. this._path.setAttribute('fill-opacity', this.options.fillOpacity);
  3565. } else {
  3566. this._path.setAttribute('fill', 'none');
  3567. }
  3568. },
  3569. _updatePath: function () {
  3570. var str = this.getPathString();
  3571. if (!str) {
  3572. // fix webkit empty string parsing bug
  3573. str = 'M0 0';
  3574. }
  3575. this._path.setAttribute('d', str);
  3576. },
  3577. // TODO remove duplication with L.Map
  3578. _initEvents: function () {
  3579. if (this.options.clickable) {
  3580. if (L.Browser.svg || !L.Browser.vml) {
  3581. L.DomUtil.addClass(this._path, 'leaflet-clickable');
  3582. }
  3583. L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
  3584. var events = ['dblclick', 'mousedown', 'mouseover',
  3585. 'mouseout', 'mousemove', 'contextmenu'];
  3586. for (var i = 0; i < events.length; i++) {
  3587. L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
  3588. }
  3589. }
  3590. },
  3591. _onMouseClick: function (e) {
  3592. if (this._map.dragging && this._map.dragging.moved()) { return; }
  3593. this._fireMouseEvent(e);
  3594. },
  3595. _fireMouseEvent: function (e) {
  3596. if (!this.hasEventListeners(e.type)) { return; }
  3597. var map = this._map,
  3598. containerPoint = map.mouseEventToContainerPoint(e),
  3599. layerPoint = map.containerPointToLayerPoint(containerPoint),
  3600. latlng = map.layerPointToLatLng(layerPoint);
  3601. this.fire(e.type, {
  3602. latlng: latlng,
  3603. layerPoint: layerPoint,
  3604. containerPoint: containerPoint,
  3605. originalEvent: e
  3606. });
  3607. if (e.type === 'contextmenu') {
  3608. L.DomEvent.preventDefault(e);
  3609. }
  3610. if (e.type !== 'mousemove') {
  3611. L.DomEvent.stopPropagation(e);
  3612. }
  3613. }
  3614. });
  3615. L.Map.include({
  3616. _initPathRoot: function () {
  3617. if (!this._pathRoot) {
  3618. this._pathRoot = L.Path.prototype._createElement('svg');
  3619. this._panes.overlayPane.appendChild(this._pathRoot);
  3620. if (this.options.zoomAnimation && L.Browser.any3d) {
  3621. L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');
  3622. this.on({
  3623. 'zoomanim': this._animatePathZoom,
  3624. 'zoomend': this._endPathZoom
  3625. });
  3626. } else {
  3627. L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');
  3628. }
  3629. this.on('moveend', this._updateSvgViewport);
  3630. this._updateSvgViewport();
  3631. }
  3632. },
  3633. _animatePathZoom: function (e) {
  3634. var scale = this.getZoomScale(e.zoom),
  3635. offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
  3636. this._pathRoot.style[L.DomUtil.TRANSFORM] =
  3637. L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
  3638. this._pathZooming = true;
  3639. },
  3640. _endPathZoom: function () {
  3641. this._pathZooming = false;
  3642. },
  3643. _updateSvgViewport: function () {
  3644. if (this._pathZooming) {
  3645. // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
  3646. // When the zoom animation ends we will be updated again anyway
  3647. // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
  3648. return;
  3649. }
  3650. this._updatePathViewport();
  3651. var vp = this._pathViewport,
  3652. min = vp.min,
  3653. max = vp.max,
  3654. width = max.x - min.x,
  3655. height = max.y - min.y,
  3656. root = this._pathRoot,
  3657. pane = this._panes.overlayPane;
  3658. // Hack to make flicker on drag end on mobile webkit less irritating
  3659. if (L.Browser.mobileWebkit) {
  3660. pane.removeChild(root);
  3661. }
  3662. L.DomUtil.setPosition(root, min);
  3663. root.setAttribute('width', width);
  3664. root.setAttribute('height', height);
  3665. root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
  3666. if (L.Browser.mobileWebkit) {
  3667. pane.appendChild(root);
  3668. }
  3669. }
  3670. });
  3671. /*
  3672. * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
  3673. */
  3674. L.Path.include({
  3675. bindPopup: function (content, options) {
  3676. if (content instanceof L.Popup) {
  3677. this._popup = content;
  3678. } else {
  3679. if (!this._popup || options) {
  3680. this._popup = new L.Popup(options, this);
  3681. }
  3682. this._popup.setContent(content);
  3683. }
  3684. if (!this._popupHandlersAdded) {
  3685. this
  3686. .on('click', this._openPopup, this)
  3687. .on('remove', this.closePopup, this);
  3688. this._popupHandlersAdded = true;
  3689. }
  3690. return this;
  3691. },
  3692. unbindPopup: function () {
  3693. if (this._popup) {
  3694. this._popup = null;
  3695. this
  3696. .off('click', this._openPopup)
  3697. .off('remove', this.closePopup);
  3698. this._popupHandlersAdded = false;
  3699. }
  3700. return this;
  3701. },
  3702. openPopup: function (latlng) {
  3703. if (this._popup) {
  3704. // open the popup from one of the path's points if not specified
  3705. latlng = latlng || this._latlng ||
  3706. this._latlngs[Math.floor(this._latlngs.length / 2)];
  3707. this._openPopup({latlng: latlng});
  3708. }
  3709. return this;
  3710. },
  3711. closePopup: function () {
  3712. if (this._popup) {
  3713. this._popup._close();
  3714. }
  3715. return this;
  3716. },
  3717. _openPopup: function (e) {
  3718. this._popup.setLatLng(e.latlng);
  3719. this._map.openPopup(this._popup);
  3720. }
  3721. });
  3722. /*
  3723. * Vector rendering for IE6-8 through VML.
  3724. * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
  3725. */
  3726. L.Browser.vml = !L.Browser.svg && (function () {
  3727. try {
  3728. var div = document.createElement('div');
  3729. div.innerHTML = '<v:shape adj="1"/>';
  3730. var shape = div.firstChild;
  3731. shape.style.behavior = 'url(#default#VML)';
  3732. return shape && (typeof shape.adj === 'object');
  3733. } catch (e) {
  3734. return false;
  3735. }
  3736. }());
  3737. L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
  3738. statics: {
  3739. VML: true,
  3740. CLIP_PADDING: 0.02
  3741. },
  3742. _createElement: (function () {
  3743. try {
  3744. document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
  3745. return function (name) {
  3746. return document.createElement('<lvml:' + name + ' class="lvml">');
  3747. };
  3748. } catch (e) {
  3749. return function (name) {
  3750. return document.createElement(
  3751. '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
  3752. };
  3753. }
  3754. }()),
  3755. _initPath: function () {
  3756. var container = this._container = this._createElement('shape');
  3757. L.DomUtil.addClass(container, 'leaflet-vml-shape' +
  3758. (this.options.className ? ' ' + this.options.className : ''));
  3759. if (this.options.clickable) {
  3760. L.DomUtil.addClass(container, 'leaflet-clickable');
  3761. }
  3762. container.coordsize = '1 1';
  3763. this._path = this._createElement('path');
  3764. container.appendChild(this._path);
  3765. this._map._pathRoot.appendChild(container);
  3766. },
  3767. _initStyle: function () {
  3768. this._updateStyle();
  3769. },
  3770. _updateStyle: function () {
  3771. var stroke = this._stroke,
  3772. fill = this._fill,
  3773. options = this.options,
  3774. container = this._container;
  3775. container.stroked = options.stroke;
  3776. container.filled = options.fill;
  3777. if (options.stroke) {
  3778. if (!stroke) {
  3779. stroke = this._stroke = this._createElement('stroke');
  3780. stroke.endcap = 'round';
  3781. container.appendChild(stroke);
  3782. }
  3783. stroke.weight = options.weight + 'px';
  3784. stroke.color = options.color;
  3785. stroke.opacity = options.opacity;
  3786. if (options.dashArray) {
  3787. stroke.dashStyle = L.Util.isArray(options.dashArray) ?
  3788. options.dashArray.join(' ') :
  3789. options.dashArray.replace(/( *, *)/g, ' ');
  3790. } else {
  3791. stroke.dashStyle = '';
  3792. }
  3793. if (options.lineCap) {
  3794. stroke.endcap = options.lineCap.replace('butt', 'flat');
  3795. }
  3796. if (options.lineJoin) {
  3797. stroke.joinstyle = options.lineJoin;
  3798. }
  3799. } else if (stroke) {
  3800. container.removeChild(stroke);
  3801. this._stroke = null;
  3802. }
  3803. if (options.fill) {
  3804. if (!fill) {
  3805. fill = this._fill = this._createElement('fill');
  3806. container.appendChild(fill);
  3807. }
  3808. fill.color = options.fillColor || options.color;
  3809. fill.opacity = options.fillOpacity;
  3810. } else if (fill) {
  3811. container.removeChild(fill);
  3812. this._fill = null;
  3813. }
  3814. },
  3815. _updatePath: function () {
  3816. var style = this._container.style;
  3817. style.display = 'none';
  3818. this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
  3819. style.display = '';
  3820. }
  3821. });
  3822. L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
  3823. _initPathRoot: function () {
  3824. if (this._pathRoot) { return; }
  3825. var root = this._pathRoot = document.createElement('div');
  3826. root.className = 'leaflet-vml-container';
  3827. this._panes.overlayPane.appendChild(root);
  3828. this.on('moveend', this._updatePathViewport);
  3829. this._updatePathViewport();
  3830. }
  3831. });
  3832. /*
  3833. * Vector rendering for all browsers that support canvas.
  3834. */
  3835. L.Browser.canvas = (function () {
  3836. return !!document.createElement('canvas').getContext;
  3837. }());
  3838. L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
  3839. statics: {
  3840. //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
  3841. CANVAS: true,
  3842. SVG: false
  3843. },
  3844. redraw: function () {
  3845. if (this._map) {
  3846. this.projectLatlngs();
  3847. this._requestUpdate();
  3848. }
  3849. return this;
  3850. },
  3851. setStyle: function (style) {
  3852. L.setOptions(this, style);
  3853. if (this._map) {
  3854. this._updateStyle();
  3855. this._requestUpdate();
  3856. }
  3857. return this;
  3858. },
  3859. onRemove: function (map) {
  3860. map
  3861. .off('viewreset', this.projectLatlngs, this)
  3862. .off('moveend', this._updatePath, this);
  3863. if (this.options.clickable) {
  3864. this._map.off('click', this._onClick, this);
  3865. this._map.off('mousemove', this._onMouseMove, this);
  3866. }
  3867. this._requestUpdate();
  3868. this.fire('remove');
  3869. this._map = null;
  3870. },
  3871. _requestUpdate: function () {
  3872. if (this._map && !L.Path._updateRequest) {
  3873. L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
  3874. }
  3875. },
  3876. _fireMapMoveEnd: function () {
  3877. L.Path._updateRequest = null;
  3878. this.fire('moveend');
  3879. },
  3880. _initElements: function () {
  3881. this._map._initPathRoot();
  3882. this._ctx = this._map._canvasCtx;
  3883. },
  3884. _updateStyle: function () {
  3885. var options = this.options;
  3886. if (options.stroke) {
  3887. this._ctx.lineWidth = options.weight;
  3888. this._ctx.strokeStyle = options.color;
  3889. }
  3890. if (options.fill) {
  3891. this._ctx.fillStyle = options.fillColor || options.color;
  3892. }
  3893. },
  3894. _drawPath: function () {
  3895. var i, j, len, len2, point, drawMethod;
  3896. this._ctx.beginPath();
  3897. for (i = 0, len = this._parts.length; i < len; i++) {
  3898. for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
  3899. point = this._parts[i][j];
  3900. drawMethod = (j === 0 ? 'move' : 'line') + 'To';
  3901. this._ctx[drawMethod](point.x, point.y);
  3902. }
  3903. // TODO refactor ugly hack
  3904. if (this instanceof L.Polygon) {
  3905. this._ctx.closePath();
  3906. }
  3907. }
  3908. },
  3909. _checkIfEmpty: function () {
  3910. return !this._parts.length;
  3911. },
  3912. _updatePath: function () {
  3913. if (this._checkIfEmpty()) { return; }
  3914. var ctx = this._ctx,
  3915. options = this.options;
  3916. this._drawPath();
  3917. ctx.save();
  3918. this._updateStyle();
  3919. if (options.fill) {
  3920. ctx.globalAlpha = options.fillOpacity;
  3921. ctx.fill();
  3922. }
  3923. if (options.stroke) {
  3924. ctx.globalAlpha = options.opacity;
  3925. ctx.stroke();
  3926. }
  3927. ctx.restore();
  3928. // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
  3929. },
  3930. _initEvents: function () {
  3931. if (this.options.clickable) {
  3932. // TODO dblclick
  3933. this._map.on('mousemove', this._onMouseMove, this);
  3934. this._map.on('click', this._onClick, this);
  3935. }
  3936. },
  3937. _onClick: function (e) {
  3938. if (this._containsPoint(e.layerPoint)) {
  3939. this.fire('click', e);
  3940. }
  3941. },
  3942. _onMouseMove: function (e) {
  3943. if (!this._map || this._map._animatingZoom) { return; }
  3944. // TODO don't do on each move
  3945. if (this._containsPoint(e.layerPoint)) {
  3946. this._ctx.canvas.style.cursor = 'pointer';
  3947. this._mouseInside = true;
  3948. this.fire('mouseover', e);
  3949. } else if (this._mouseInside) {
  3950. this._ctx.canvas.style.cursor = '';
  3951. this._mouseInside = false;
  3952. this.fire('mouseout', e);
  3953. }
  3954. }
  3955. });
  3956. L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
  3957. _initPathRoot: function () {
  3958. var root = this._pathRoot,
  3959. ctx;
  3960. if (!root) {
  3961. root = this._pathRoot = document.createElement('canvas');
  3962. root.style.position = 'absolute';
  3963. ctx = this._canvasCtx = root.getContext('2d');
  3964. ctx.lineCap = 'round';
  3965. ctx.lineJoin = 'round';
  3966. this._panes.overlayPane.appendChild(root);
  3967. if (this.options.zoomAnimation) {
  3968. this._pathRoot.className = 'leaflet-zoom-animated';
  3969. this.on('zoomanim', this._animatePathZoom);
  3970. this.on('zoomend', this._endPathZoom);
  3971. }
  3972. this.on('moveend', this._updateCanvasViewport);
  3973. this._updateCanvasViewport();
  3974. }
  3975. },
  3976. _updateCanvasViewport: function () {
  3977. // don't redraw while zooming. See _updateSvgViewport for more details
  3978. if (this._pathZooming) { return; }
  3979. this._updatePathViewport();
  3980. var vp = this._pathViewport,
  3981. min = vp.min,
  3982. size = vp.max.subtract(min),
  3983. root = this._pathRoot;
  3984. //TODO check if this works properly on mobile webkit
  3985. L.DomUtil.setPosition(root, min);
  3986. root.width = size.x;
  3987. root.height = size.y;
  3988. root.getContext('2d').translate(-min.x, -min.y);
  3989. }
  3990. });
  3991. /*
  3992. * L.LineUtil contains different utility functions for line segments
  3993. * and polylines (clipping, simplification, distances, etc.)
  3994. */
  3995. /*jshint bitwise:false */ // allow bitwise operations for this file
  3996. L.LineUtil = {
  3997. // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
  3998. // Improves rendering performance dramatically by lessening the number of points to draw.
  3999. simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
  4000. if (!tolerance || !points.length) {
  4001. return points.slice();
  4002. }
  4003. var sqTolerance = tolerance * tolerance;
  4004. // stage 1: vertex reduction
  4005. points = this._reducePoints(points, sqTolerance);
  4006. // stage 2: Douglas-Peucker simplification
  4007. points = this._simplifyDP(points, sqTolerance);
  4008. return points;
  4009. },
  4010. // distance from a point to a segment between two points
  4011. pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
  4012. return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
  4013. },
  4014. closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
  4015. return this._sqClosestPointOnSegment(p, p1, p2);
  4016. },
  4017. // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
  4018. _simplifyDP: function (points, sqTolerance) {
  4019. var len = points.length,
  4020. ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
  4021. markers = new ArrayConstructor(len);
  4022. markers[0] = markers[len - 1] = 1;
  4023. this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
  4024. var i,
  4025. newPoints = [];
  4026. for (i = 0; i < len; i++) {
  4027. if (markers[i]) {
  4028. newPoints.push(points[i]);
  4029. }
  4030. }
  4031. return newPoints;
  4032. },
  4033. _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
  4034. var maxSqDist = 0,
  4035. index, i, sqDist;
  4036. for (i = first + 1; i <= last - 1; i++) {
  4037. sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
  4038. if (sqDist > maxSqDist) {
  4039. index = i;
  4040. maxSqDist = sqDist;
  4041. }
  4042. }
  4043. if (maxSqDist > sqTolerance) {
  4044. markers[index] = 1;
  4045. this._simplifyDPStep(points, markers, sqTolerance, first, index);
  4046. this._simplifyDPStep(points, markers, sqTolerance, index, last);
  4047. }
  4048. },
  4049. // reduce points that are too close to each other to a single point
  4050. _reducePoints: function (points, sqTolerance) {
  4051. var reducedPoints = [points[0]];
  4052. for (var i = 1, prev = 0, len = points.length; i < len; i++) {
  4053. if (this._sqDist(points[i], points[prev]) > sqTolerance) {
  4054. reducedPoints.push(points[i]);
  4055. prev = i;
  4056. }
  4057. }
  4058. if (prev < len - 1) {
  4059. reducedPoints.push(points[len - 1]);
  4060. }
  4061. return reducedPoints;
  4062. },
  4063. // Cohen-Sutherland line clipping algorithm.
  4064. // Used to avoid rendering parts of a polyline that are not currently visible.
  4065. clipSegment: function (a, b, bounds, useLastCode) {
  4066. var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
  4067. codeB = this._getBitCode(b, bounds),
  4068. codeOut, p, newCode;
  4069. // save 2nd code to avoid calculating it on the next segment
  4070. this._lastCode = codeB;
  4071. while (true) {
  4072. // if a,b is inside the clip window (trivial accept)
  4073. if (!(codeA | codeB)) {
  4074. return [a, b];
  4075. // if a,b is outside the clip window (trivial reject)
  4076. } else if (codeA & codeB) {
  4077. return false;
  4078. // other cases
  4079. } else {
  4080. codeOut = codeA || codeB;
  4081. p = this._getEdgeIntersection(a, b, codeOut, bounds);
  4082. newCode = this._getBitCode(p, bounds);
  4083. if (codeOut === codeA) {
  4084. a = p;
  4085. codeA = newCode;
  4086. } else {
  4087. b = p;
  4088. codeB = newCode;
  4089. }
  4090. }
  4091. }
  4092. },
  4093. _getEdgeIntersection: function (a, b, code, bounds) {
  4094. var dx = b.x - a.x,
  4095. dy = b.y - a.y,
  4096. min = bounds.min,
  4097. max = bounds.max;
  4098. if (code & 8) { // top
  4099. return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
  4100. } else if (code & 4) { // bottom
  4101. return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
  4102. } else if (code & 2) { // right
  4103. return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
  4104. } else if (code & 1) { // left
  4105. return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
  4106. }
  4107. },
  4108. _getBitCode: function (/*Point*/ p, bounds) {
  4109. var code = 0;
  4110. if (p.x < bounds.min.x) { // left
  4111. code |= 1;
  4112. } else if (p.x > bounds.max.x) { // right
  4113. code |= 2;
  4114. }
  4115. if (p.y < bounds.min.y) { // bottom
  4116. code |= 4;
  4117. } else if (p.y > bounds.max.y) { // top
  4118. code |= 8;
  4119. }
  4120. return code;
  4121. },
  4122. // square distance (to avoid unnecessary Math.sqrt calls)
  4123. _sqDist: function (p1, p2) {
  4124. var dx = p2.x - p1.x,
  4125. dy = p2.y - p1.y;
  4126. return dx * dx + dy * dy;
  4127. },
  4128. // return closest point on segment or distance to that point
  4129. _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
  4130. var x = p1.x,
  4131. y = p1.y,
  4132. dx = p2.x - x,
  4133. dy = p2.y - y,
  4134. dot = dx * dx + dy * dy,
  4135. t;
  4136. if (dot > 0) {
  4137. t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
  4138. if (t > 1) {
  4139. x = p2.x;
  4140. y = p2.y;
  4141. } else if (t > 0) {
  4142. x += dx * t;
  4143. y += dy * t;
  4144. }
  4145. }
  4146. dx = p.x - x;
  4147. dy = p.y - y;
  4148. return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
  4149. }
  4150. };
  4151. /*
  4152. * L.Polyline is used to display polylines on a map.
  4153. */
  4154. L.Polyline = L.Path.extend({
  4155. initialize: function (latlngs, options) {
  4156. L.Path.prototype.initialize.call(this, options);
  4157. this._latlngs = this._convertLatLngs(latlngs);
  4158. },
  4159. options: {
  4160. // how much to simplify the polyline on each zoom level
  4161. // more = better performance and smoother look, less = more accurate
  4162. smoothFactor: 1.0,
  4163. noClip: false
  4164. },
  4165. projectLatlngs: function () {
  4166. this._originalPoints = [];
  4167. for (var i = 0, len = this._latlngs.length; i < len; i++) {
  4168. this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
  4169. }
  4170. },
  4171. getPathString: function () {
  4172. for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
  4173. str += this._getPathPartStr(this._parts[i]);
  4174. }
  4175. return str;
  4176. },
  4177. getLatLngs: function () {
  4178. return this._latlngs;
  4179. },
  4180. setLatLngs: function (latlngs) {
  4181. this._latlngs = this._convertLatLngs(latlngs);
  4182. return this.redraw();
  4183. },
  4184. addLatLng: function (latlng) {
  4185. this._latlngs.push(L.latLng(latlng));
  4186. return this.redraw();
  4187. },
  4188. spliceLatLngs: function () { // (Number index, Number howMany)
  4189. var removed = [].splice.apply(this._latlngs, arguments);
  4190. this._convertLatLngs(this._latlngs, true);
  4191. this.redraw();
  4192. return removed;
  4193. },
  4194. closestLayerPoint: function (p) {
  4195. var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
  4196. for (var j = 0, jLen = parts.length; j < jLen; j++) {
  4197. var points = parts[j];
  4198. for (var i = 1, len = points.length; i < len; i++) {
  4199. p1 = points[i - 1];
  4200. p2 = points[i];
  4201. var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
  4202. if (sqDist < minDistance) {
  4203. minDistance = sqDist;
  4204. minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
  4205. }
  4206. }
  4207. }
  4208. if (minPoint) {
  4209. minPoint.distance = Math.sqrt(minDistance);
  4210. }
  4211. return minPoint;
  4212. },
  4213. getBounds: function () {
  4214. return new L.LatLngBounds(this.getLatLngs());
  4215. },
  4216. _convertLatLngs: function (latlngs, overwrite) {
  4217. var i, len, target = overwrite ? latlngs : [];
  4218. for (i = 0, len = latlngs.length; i < len; i++) {
  4219. if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
  4220. return;
  4221. }
  4222. target[i] = L.latLng(latlngs[i]);
  4223. }
  4224. return target;
  4225. },
  4226. _initEvents: function () {
  4227. L.Path.prototype._initEvents.call(this);
  4228. },
  4229. _getPathPartStr: function (points) {
  4230. var round = L.Path.VML;
  4231. for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
  4232. p = points[j];
  4233. if (round) {
  4234. p._round();
  4235. }
  4236. str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
  4237. }
  4238. return str;
  4239. },
  4240. _clipPoints: function () {
  4241. var points = this._originalPoints,
  4242. len = points.length,
  4243. i, k, segment;
  4244. if (this.options.noClip) {
  4245. this._parts = [points];
  4246. return;
  4247. }
  4248. this._parts = [];
  4249. var parts = this._parts,
  4250. vp = this._map._pathViewport,
  4251. lu = L.LineUtil;
  4252. for (i = 0, k = 0; i < len - 1; i++) {
  4253. segment = lu.clipSegment(points[i], points[i + 1], vp, i);
  4254. if (!segment) {
  4255. continue;
  4256. }
  4257. parts[k] = parts[k] || [];
  4258. parts[k].push(segment[0]);
  4259. // if segment goes out of screen, or it's the last one, it's the end of the line part
  4260. if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
  4261. parts[k].push(segment[1]);
  4262. k++;
  4263. }
  4264. }
  4265. },
  4266. // simplify each clipped part of the polyline
  4267. _simplifyPoints: function () {
  4268. var parts = this._parts,
  4269. lu = L.LineUtil;
  4270. for (var i = 0, len = parts.length; i < len; i++) {
  4271. parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
  4272. }
  4273. },
  4274. _updatePath: function () {
  4275. if (!this._map) { return; }
  4276. this._clipPoints();
  4277. this._simplifyPoints();
  4278. L.Path.prototype._updatePath.call(this);
  4279. }
  4280. });
  4281. L.polyline = function (latlngs, options) {
  4282. return new L.Polyline(latlngs, options);
  4283. };
  4284. /*
  4285. * L.PolyUtil contains utility functions for polygons (clipping, etc.).
  4286. */
  4287. /*jshint bitwise:false */ // allow bitwise operations here
  4288. L.PolyUtil = {};
  4289. /*
  4290. * Sutherland-Hodgeman polygon clipping algorithm.
  4291. * Used to avoid rendering parts of a polygon that are not currently visible.
  4292. */
  4293. L.PolyUtil.clipPolygon = function (points, bounds) {
  4294. var clippedPoints,
  4295. edges = [1, 4, 2, 8],
  4296. i, j, k,
  4297. a, b,
  4298. len, edge, p,
  4299. lu = L.LineUtil;
  4300. for (i = 0, len = points.length; i < len; i++) {
  4301. points[i]._code = lu._getBitCode(points[i], bounds);
  4302. }
  4303. // for each edge (left, bottom, right, top)
  4304. for (k = 0; k < 4; k++) {
  4305. edge = edges[k];
  4306. clippedPoints = [];
  4307. for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
  4308. a = points[i];
  4309. b = points[j];
  4310. // if a is inside the clip window
  4311. if (!(a._code & edge)) {
  4312. // if b is outside the clip window (a->b goes out of screen)
  4313. if (b._code & edge) {
  4314. p = lu._getEdgeIntersection(b, a, edge, bounds);
  4315. p._code = lu._getBitCode(p, bounds);
  4316. clippedPoints.push(p);
  4317. }
  4318. clippedPoints.push(a);
  4319. // else if b is inside the clip window (a->b enters the screen)
  4320. } else if (!(b._code & edge)) {
  4321. p = lu._getEdgeIntersection(b, a, edge, bounds);
  4322. p._code = lu._getBitCode(p, bounds);
  4323. clippedPoints.push(p);
  4324. }
  4325. }
  4326. points = clippedPoints;
  4327. }
  4328. return points;
  4329. };
  4330. /*
  4331. * L.Polygon is used to display polygons on a map.
  4332. */
  4333. L.Polygon = L.Polyline.extend({
  4334. options: {
  4335. fill: true
  4336. },
  4337. initialize: function (latlngs, options) {
  4338. L.Polyline.prototype.initialize.call(this, latlngs, options);
  4339. this._initWithHoles(latlngs);
  4340. },
  4341. _initWithHoles: function (latlngs) {
  4342. var i, len, hole;
  4343. if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
  4344. this._latlngs = this._convertLatLngs(latlngs[0]);
  4345. this._holes = latlngs.slice(1);
  4346. for (i = 0, len = this._holes.length; i < len; i++) {
  4347. hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
  4348. if (hole[0].equals(hole[hole.length - 1])) {
  4349. hole.pop();
  4350. }
  4351. }
  4352. }
  4353. // filter out last point if its equal to the first one
  4354. latlngs = this._latlngs;
  4355. if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
  4356. latlngs.pop();
  4357. }
  4358. },
  4359. projectLatlngs: function () {
  4360. L.Polyline.prototype.projectLatlngs.call(this);
  4361. // project polygon holes points
  4362. // TODO move this logic to Polyline to get rid of duplication
  4363. this._holePoints = [];
  4364. if (!this._holes) { return; }
  4365. var i, j, len, len2;
  4366. for (i = 0, len = this._holes.length; i < len; i++) {
  4367. this._holePoints[i] = [];
  4368. for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
  4369. this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
  4370. }
  4371. }
  4372. },
  4373. setLatLngs: function (latlngs) {
  4374. if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
  4375. this._initWithHoles(latlngs);
  4376. return this.redraw();
  4377. } else {
  4378. return L.Polyline.prototype.setLatLngs.call(this, latlngs);
  4379. }
  4380. },
  4381. _clipPoints: function () {
  4382. var points = this._originalPoints,
  4383. newParts = [];
  4384. this._parts = [points].concat(this._holePoints);
  4385. if (this.options.noClip) { return; }
  4386. for (var i = 0, len = this._parts.length; i < len; i++) {
  4387. var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
  4388. if (clipped.length) {
  4389. newParts.push(clipped);
  4390. }
  4391. }
  4392. this._parts = newParts;
  4393. },
  4394. _getPathPartStr: function (points) {
  4395. var str = L.Polyline.prototype._getPathPartStr.call(this, points);
  4396. return str + (L.Browser.svg ? 'z' : 'x');
  4397. }
  4398. });
  4399. L.polygon = function (latlngs, options) {
  4400. return new L.Polygon(latlngs, options);
  4401. };
  4402. /*
  4403. * Contains L.MultiPolyline and L.MultiPolygon layers.
  4404. */
  4405. (function () {
  4406. function createMulti(Klass) {
  4407. return L.FeatureGroup.extend({
  4408. initialize: function (latlngs, options) {
  4409. this._layers = {};
  4410. this._options = options;
  4411. this.setLatLngs(latlngs);
  4412. },
  4413. setLatLngs: function (latlngs) {
  4414. var i = 0,
  4415. len = latlngs.length;
  4416. this.eachLayer(function (layer) {
  4417. if (i < len) {
  4418. layer.setLatLngs(latlngs[i++]);
  4419. } else {
  4420. this.removeLayer(layer);
  4421. }
  4422. }, this);
  4423. while (i < len) {
  4424. this.addLayer(new Klass(latlngs[i++], this._options));
  4425. }
  4426. return this;
  4427. },
  4428. getLatLngs: function () {
  4429. var latlngs = [];
  4430. this.eachLayer(function (layer) {
  4431. latlngs.push(layer.getLatLngs());
  4432. });
  4433. return latlngs;
  4434. }
  4435. });
  4436. }
  4437. L.MultiPolyline = createMulti(L.Polyline);
  4438. L.MultiPolygon = createMulti(L.Polygon);
  4439. L.multiPolyline = function (latlngs, options) {
  4440. return new L.MultiPolyline(latlngs, options);
  4441. };
  4442. L.multiPolygon = function (latlngs, options) {
  4443. return new L.MultiPolygon(latlngs, options);
  4444. };
  4445. }());
  4446. /*
  4447. * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
  4448. */
  4449. L.Rectangle = L.Polygon.extend({
  4450. initialize: function (latLngBounds, options) {
  4451. L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
  4452. },
  4453. setBounds: function (latLngBounds) {
  4454. this.setLatLngs(this._boundsToLatLngs(latLngBounds));
  4455. },
  4456. _boundsToLatLngs: function (latLngBounds) {
  4457. latLngBounds = L.latLngBounds(latLngBounds);
  4458. return [
  4459. latLngBounds.getSouthWest(),
  4460. latLngBounds.getNorthWest(),
  4461. latLngBounds.getNorthEast(),
  4462. latLngBounds.getSouthEast()
  4463. ];
  4464. }
  4465. });
  4466. L.rectangle = function (latLngBounds, options) {
  4467. return new L.Rectangle(latLngBounds, options);
  4468. };
  4469. /*
  4470. * L.Circle is a circle overlay (with a certain radius in meters).
  4471. */
  4472. L.Circle = L.Path.extend({
  4473. initialize: function (latlng, radius, options) {
  4474. L.Path.prototype.initialize.call(this, options);
  4475. this._latlng = L.latLng(latlng);
  4476. this._mRadius = radius;
  4477. },
  4478. options: {
  4479. fill: true
  4480. },
  4481. setLatLng: function (latlng) {
  4482. this._latlng = L.latLng(latlng);
  4483. return this.redraw();
  4484. },
  4485. setRadius: function (radius) {
  4486. this._mRadius = radius;
  4487. return this.redraw();
  4488. },
  4489. projectLatlngs: function () {
  4490. var lngRadius = this._getLngRadius(),
  4491. latlng = this._latlng,
  4492. pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
  4493. this._point = this._map.latLngToLayerPoint(latlng);
  4494. this._radius = Math.max(this._point.x - pointLeft.x, 1);
  4495. },
  4496. getBounds: function () {
  4497. var lngRadius = this._getLngRadius(),
  4498. latRadius = (this._mRadius / 40075017) * 360,
  4499. latlng = this._latlng;
  4500. return new L.LatLngBounds(
  4501. [latlng.lat - latRadius, latlng.lng - lngRadius],
  4502. [latlng.lat + latRadius, latlng.lng + lngRadius]);
  4503. },
  4504. getLatLng: function () {
  4505. return this._latlng;
  4506. },
  4507. getPathString: function () {
  4508. var p = this._point,
  4509. r = this._radius;
  4510. if (this._checkIfEmpty()) {
  4511. return '';
  4512. }
  4513. if (L.Browser.svg) {
  4514. return 'M' + p.x + ',' + (p.y - r) +
  4515. 'A' + r + ',' + r + ',0,1,1,' +
  4516. (p.x - 0.1) + ',' + (p.y - r) + ' z';
  4517. } else {
  4518. p._round();
  4519. r = Math.round(r);
  4520. return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
  4521. }
  4522. },
  4523. getRadius: function () {
  4524. return this._mRadius;
  4525. },
  4526. // TODO Earth hardcoded, move into projection code!
  4527. _getLatRadius: function () {
  4528. return (this._mRadius / 40075017) * 360;
  4529. },
  4530. _getLngRadius: function () {
  4531. return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
  4532. },
  4533. _checkIfEmpty: function () {
  4534. if (!this._map) {
  4535. return false;
  4536. }
  4537. var vp = this._map._pathViewport,
  4538. r = this._radius,
  4539. p = this._point;
  4540. return p.x - r > vp.max.x || p.y - r > vp.max.y ||
  4541. p.x + r < vp.min.x || p.y + r < vp.min.y;
  4542. }
  4543. });
  4544. L.circle = function (latlng, radius, options) {
  4545. return new L.Circle(latlng, radius, options);
  4546. };
  4547. /*
  4548. * L.CircleMarker is a circle overlay with a permanent pixel radius.
  4549. */
  4550. L.CircleMarker = L.Circle.extend({
  4551. options: {
  4552. radius: 10,
  4553. weight: 2
  4554. },
  4555. initialize: function (latlng, options) {
  4556. L.Circle.prototype.initialize.call(this, latlng, null, options);
  4557. this._radius = this.options.radius;
  4558. },
  4559. projectLatlngs: function () {
  4560. this._point = this._map.latLngToLayerPoint(this._latlng);
  4561. },
  4562. _updateStyle : function () {
  4563. L.Circle.prototype._updateStyle.call(this);
  4564. this.setRadius(this.options.radius);
  4565. },
  4566. setLatLng: function (latlng) {
  4567. L.Circle.prototype.setLatLng.call(this, latlng);
  4568. if (this._popup && this._popup._isOpen) {
  4569. this._popup.setLatLng(latlng);
  4570. }
  4571. return this;
  4572. },
  4573. setRadius: function (radius) {
  4574. this.options.radius = this._radius = radius;
  4575. return this.redraw();
  4576. },
  4577. getRadius: function () {
  4578. return this._radius;
  4579. }
  4580. });
  4581. L.circleMarker = function (latlng, options) {
  4582. return new L.CircleMarker(latlng, options);
  4583. };
  4584. /*
  4585. * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
  4586. */
  4587. L.Polyline.include(!L.Path.CANVAS ? {} : {
  4588. _containsPoint: function (p, closed) {
  4589. var i, j, k, len, len2, dist, part,
  4590. w = this.options.weight / 2;
  4591. if (L.Browser.touch) {
  4592. w += 10; // polyline click tolerance on touch devices
  4593. }
  4594. for (i = 0, len = this._parts.length; i < len; i++) {
  4595. part = this._parts[i];
  4596. for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
  4597. if (!closed && (j === 0)) {
  4598. continue;
  4599. }
  4600. dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
  4601. if (dist <= w) {
  4602. return true;
  4603. }
  4604. }
  4605. }
  4606. return false;
  4607. }
  4608. });
  4609. /*
  4610. * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
  4611. */
  4612. L.Polygon.include(!L.Path.CANVAS ? {} : {
  4613. _containsPoint: function (p) {
  4614. var inside = false,
  4615. part, p1, p2,
  4616. i, j, k,
  4617. len, len2;
  4618. // TODO optimization: check if within bounds first
  4619. if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
  4620. // click on polygon border
  4621. return true;
  4622. }
  4623. // ray casting algorithm for detecting if point is in polygon
  4624. for (i = 0, len = this._parts.length; i < len; i++) {
  4625. part = this._parts[i];
  4626. for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
  4627. p1 = part[j];
  4628. p2 = part[k];
  4629. if (((p1.y > p.y) !== (p2.y > p.y)) &&
  4630. (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
  4631. inside = !inside;
  4632. }
  4633. }
  4634. }
  4635. return inside;
  4636. }
  4637. });
  4638. /*
  4639. * Extends L.Circle with Canvas-specific code.
  4640. */
  4641. L.Circle.include(!L.Path.CANVAS ? {} : {
  4642. _drawPath: function () {
  4643. var p = this._point;
  4644. this._ctx.beginPath();
  4645. this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
  4646. },
  4647. _containsPoint: function (p) {
  4648. var center = this._point,
  4649. w2 = this.options.stroke ? this.options.weight / 2 : 0;
  4650. return (p.distanceTo(center) <= this._radius + w2);
  4651. }
  4652. });
  4653. /*
  4654. * CircleMarker canvas specific drawing parts.
  4655. */
  4656. L.CircleMarker.include(!L.Path.CANVAS ? {} : {
  4657. _updateStyle: function () {
  4658. L.Path.prototype._updateStyle.call(this);
  4659. }
  4660. });
  4661. /*
  4662. * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
  4663. */
  4664. L.GeoJSON = L.FeatureGroup.extend({
  4665. initialize: function (geojson, options) {
  4666. L.setOptions(this, options);
  4667. this._layers = {};
  4668. if (geojson) {
  4669. this.addData(geojson);
  4670. }
  4671. },
  4672. addData: function (geojson) {
  4673. var features = L.Util.isArray(geojson) ? geojson : geojson.features,
  4674. i, len, feature;
  4675. if (features) {
  4676. for (i = 0, len = features.length; i < len; i++) {
  4677. // Only add this if geometry or geometries are set and not null
  4678. feature = features[i];
  4679. if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
  4680. this.addData(features[i]);
  4681. }
  4682. }
  4683. return this;
  4684. }
  4685. var options = this.options;
  4686. if (options.filter && !options.filter(geojson)) { return; }
  4687. var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options);
  4688. layer.feature = L.GeoJSON.asFeature(geojson);
  4689. layer.defaultOptions = layer.options;
  4690. this.resetStyle(layer);
  4691. if (options.onEachFeature) {
  4692. options.onEachFeature(geojson, layer);
  4693. }
  4694. return this.addLayer(layer);
  4695. },
  4696. resetStyle: function (layer) {
  4697. var style = this.options.style;
  4698. if (style) {
  4699. // reset any custom styles
  4700. L.Util.extend(layer.options, layer.defaultOptions);
  4701. this._setLayerStyle(layer, style);
  4702. }
  4703. },
  4704. setStyle: function (style) {
  4705. this.eachLayer(function (layer) {
  4706. this._setLayerStyle(layer, style);
  4707. }, this);
  4708. },
  4709. _setLayerStyle: function (layer, style) {
  4710. if (typeof style === 'function') {
  4711. style = style(layer.feature);
  4712. }
  4713. if (layer.setStyle) {
  4714. layer.setStyle(style);
  4715. }
  4716. }
  4717. });
  4718. L.extend(L.GeoJSON, {
  4719. geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) {
  4720. var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
  4721. coords = geometry.coordinates,
  4722. layers = [],
  4723. latlng, latlngs, i, len;
  4724. coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
  4725. switch (geometry.type) {
  4726. case 'Point':
  4727. latlng = coordsToLatLng(coords);
  4728. return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
  4729. case 'MultiPoint':
  4730. for (i = 0, len = coords.length; i < len; i++) {
  4731. latlng = coordsToLatLng(coords[i]);
  4732. layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
  4733. }
  4734. return new L.FeatureGroup(layers);
  4735. case 'LineString':
  4736. latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
  4737. return new L.Polyline(latlngs, vectorOptions);
  4738. case 'Polygon':
  4739. if (coords.length === 2 && !coords[1].length) {
  4740. throw new Error('Invalid GeoJSON object.');
  4741. }
  4742. latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
  4743. return new L.Polygon(latlngs, vectorOptions);
  4744. case 'MultiLineString':
  4745. latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
  4746. return new L.MultiPolyline(latlngs, vectorOptions);
  4747. case 'MultiPolygon':
  4748. latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
  4749. return new L.MultiPolygon(latlngs, vectorOptions);
  4750. case 'GeometryCollection':
  4751. for (i = 0, len = geometry.geometries.length; i < len; i++) {
  4752. layers.push(this.geometryToLayer({
  4753. geometry: geometry.geometries[i],
  4754. type: 'Feature',
  4755. properties: geojson.properties
  4756. }, pointToLayer, coordsToLatLng, vectorOptions));
  4757. }
  4758. return new L.FeatureGroup(layers);
  4759. default:
  4760. throw new Error('Invalid GeoJSON object.');
  4761. }
  4762. },
  4763. coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
  4764. return new L.LatLng(coords[1], coords[0], coords[2]);
  4765. },
  4766. coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
  4767. var latlng, i, len,
  4768. latlngs = [];
  4769. for (i = 0, len = coords.length; i < len; i++) {
  4770. latlng = levelsDeep ?
  4771. this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
  4772. (coordsToLatLng || this.coordsToLatLng)(coords[i]);
  4773. latlngs.push(latlng);
  4774. }
  4775. return latlngs;
  4776. },
  4777. latLngToCoords: function (latlng) {
  4778. var coords = [latlng.lng, latlng.lat];
  4779. if (latlng.alt !== undefined) {
  4780. coords.push(latlng.alt);
  4781. }
  4782. return coords;
  4783. },
  4784. latLngsToCoords: function (latLngs) {
  4785. var coords = [];
  4786. for (var i = 0, len = latLngs.length; i < len; i++) {
  4787. coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));
  4788. }
  4789. return coords;
  4790. },
  4791. getFeature: function (layer, newGeometry) {
  4792. return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);
  4793. },
  4794. asFeature: function (geoJSON) {
  4795. if (geoJSON.type === 'Feature') {
  4796. return geoJSON;
  4797. }
  4798. return {
  4799. type: 'Feature',
  4800. properties: {},
  4801. geometry: geoJSON
  4802. };
  4803. }
  4804. });
  4805. var PointToGeoJSON = {
  4806. toGeoJSON: function () {
  4807. return L.GeoJSON.getFeature(this, {
  4808. type: 'Point',
  4809. coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
  4810. });
  4811. }
  4812. };
  4813. L.Marker.include(PointToGeoJSON);
  4814. L.Circle.include(PointToGeoJSON);
  4815. L.CircleMarker.include(PointToGeoJSON);
  4816. L.Polyline.include({
  4817. toGeoJSON: function () {
  4818. return L.GeoJSON.getFeature(this, {
  4819. type: 'LineString',
  4820. coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())
  4821. });
  4822. }
  4823. });
  4824. L.Polygon.include({
  4825. toGeoJSON: function () {
  4826. var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],
  4827. i, len, hole;
  4828. coords[0].push(coords[0][0]);
  4829. if (this._holes) {
  4830. for (i = 0, len = this._holes.length; i < len; i++) {
  4831. hole = L.GeoJSON.latLngsToCoords(this._holes[i]);
  4832. hole.push(hole[0]);
  4833. coords.push(hole);
  4834. }
  4835. }
  4836. return L.GeoJSON.getFeature(this, {
  4837. type: 'Polygon',
  4838. coordinates: coords
  4839. });
  4840. }
  4841. });
  4842. (function () {
  4843. function multiToGeoJSON(type) {
  4844. return function () {
  4845. var coords = [];
  4846. this.eachLayer(function (layer) {
  4847. coords.push(layer.toGeoJSON().geometry.coordinates);
  4848. });
  4849. return L.GeoJSON.getFeature(this, {
  4850. type: type,
  4851. coordinates: coords
  4852. });
  4853. };
  4854. }
  4855. L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')});
  4856. L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')});
  4857. L.LayerGroup.include({
  4858. toGeoJSON: function () {
  4859. var geometry = this.feature && this.feature.geometry,
  4860. jsons = [],
  4861. json;
  4862. if (geometry && geometry.type === 'MultiPoint') {
  4863. return multiToGeoJSON('MultiPoint').call(this);
  4864. }
  4865. var isGeometryCollection = geometry && geometry.type === 'GeometryCollection';
  4866. this.eachLayer(function (layer) {
  4867. if (layer.toGeoJSON) {
  4868. json = layer.toGeoJSON();
  4869. jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
  4870. }
  4871. });
  4872. if (isGeometryCollection) {
  4873. return L.GeoJSON.getFeature(this, {
  4874. geometries: jsons,
  4875. type: 'GeometryCollection'
  4876. });
  4877. }
  4878. return {
  4879. type: 'FeatureCollection',
  4880. features: jsons
  4881. };
  4882. }
  4883. });
  4884. }());
  4885. L.geoJson = function (geojson, options) {
  4886. return new L.GeoJSON(geojson, options);
  4887. };
  4888. /*
  4889. * L.DomEvent contains functions for working with DOM events.
  4890. */
  4891. L.DomEvent = {
  4892. /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
  4893. addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
  4894. var id = L.stamp(fn),
  4895. key = '_leaflet_' + type + id,
  4896. handler, originalHandler, newType;
  4897. if (obj[key]) { return this; }
  4898. handler = function (e) {
  4899. return fn.call(context || obj, e || L.DomEvent._getEvent());
  4900. };
  4901. if (L.Browser.pointer && type.indexOf('touch') === 0) {
  4902. return this.addPointerListener(obj, type, handler, id);
  4903. }
  4904. if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
  4905. this.addDoubleTapListener(obj, handler, id);
  4906. }
  4907. if ('addEventListener' in obj) {
  4908. if (type === 'mousewheel') {
  4909. obj.addEventListener('DOMMouseScroll', handler, false);
  4910. obj.addEventListener(type, handler, false);
  4911. } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
  4912. originalHandler = handler;
  4913. newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
  4914. handler = function (e) {
  4915. if (!L.DomEvent._checkMouse(obj, e)) { return; }
  4916. return originalHandler(e);
  4917. };
  4918. obj.addEventListener(newType, handler, false);
  4919. } else if (type === 'click' && L.Browser.android) {
  4920. originalHandler = handler;
  4921. handler = function (e) {
  4922. return L.DomEvent._filterClick(e, originalHandler);
  4923. };
  4924. obj.addEventListener(type, handler, false);
  4925. } else {
  4926. obj.addEventListener(type, handler, false);
  4927. }
  4928. } else if ('attachEvent' in obj) {
  4929. obj.attachEvent('on' + type, handler);
  4930. }
  4931. obj[key] = handler;
  4932. return this;
  4933. },
  4934. removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
  4935. var id = L.stamp(fn),
  4936. key = '_leaflet_' + type + id,
  4937. handler = obj[key];
  4938. if (!handler) { return this; }
  4939. if (L.Browser.pointer && type.indexOf('touch') === 0) {
  4940. this.removePointerListener(obj, type, id);
  4941. } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
  4942. this.removeDoubleTapListener(obj, id);
  4943. } else if ('removeEventListener' in obj) {
  4944. if (type === 'mousewheel') {
  4945. obj.removeEventListener('DOMMouseScroll', handler, false);
  4946. obj.removeEventListener(type, handler, false);
  4947. } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
  4948. obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
  4949. } else {
  4950. obj.removeEventListener(type, handler, false);
  4951. }
  4952. } else if ('detachEvent' in obj) {
  4953. obj.detachEvent('on' + type, handler);
  4954. }
  4955. obj[key] = null;
  4956. return this;
  4957. },
  4958. stopPropagation: function (e) {
  4959. if (e.stopPropagation) {
  4960. e.stopPropagation();
  4961. } else {
  4962. e.cancelBubble = true;
  4963. }
  4964. L.DomEvent._skipped(e);
  4965. return this;
  4966. },
  4967. disableScrollPropagation: function (el) {
  4968. var stop = L.DomEvent.stopPropagation;
  4969. return L.DomEvent
  4970. .on(el, 'mousewheel', stop)
  4971. .on(el, 'MozMousePixelScroll', stop);
  4972. },
  4973. disableClickPropagation: function (el) {
  4974. var stop = L.DomEvent.stopPropagation;
  4975. for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
  4976. L.DomEvent.on(el, L.Draggable.START[i], stop);
  4977. }
  4978. return L.DomEvent
  4979. .on(el, 'click', L.DomEvent._fakeStop)
  4980. .on(el, 'dblclick', stop);
  4981. },
  4982. preventDefault: function (e) {
  4983. if (e.preventDefault) {
  4984. e.preventDefault();
  4985. } else {
  4986. e.returnValue = false;
  4987. }
  4988. return this;
  4989. },
  4990. stop: function (e) {
  4991. return L.DomEvent
  4992. .preventDefault(e)
  4993. .stopPropagation(e);
  4994. },
  4995. getMousePosition: function (e, container) {
  4996. if (!container) {
  4997. return new L.Point(e.clientX, e.clientY);
  4998. }
  4999. var rect = container.getBoundingClientRect();
  5000. return new L.Point(
  5001. e.clientX - rect.left - container.clientLeft,
  5002. e.clientY - rect.top - container.clientTop);
  5003. },
  5004. getWheelDelta: function (e) {
  5005. var delta = 0;
  5006. if (e.wheelDelta) {
  5007. delta = e.wheelDelta / 120;
  5008. }
  5009. if (e.detail) {
  5010. delta = -e.detail / 3;
  5011. }
  5012. return delta;
  5013. },
  5014. _skipEvents: {},
  5015. _fakeStop: function (e) {
  5016. // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
  5017. L.DomEvent._skipEvents[e.type] = true;
  5018. },
  5019. _skipped: function (e) {
  5020. var skipped = this._skipEvents[e.type];
  5021. // reset when checking, as it's only used in map container and propagates outside of the map
  5022. this._skipEvents[e.type] = false;
  5023. return skipped;
  5024. },
  5025. // check if element really left/entered the event target (for mouseenter/mouseleave)
  5026. _checkMouse: function (el, e) {
  5027. var related = e.relatedTarget;
  5028. if (!related) { return true; }
  5029. try {
  5030. while (related && (related !== el)) {
  5031. related = related.parentNode;
  5032. }
  5033. } catch (err) {
  5034. return false;
  5035. }
  5036. return (related !== el);
  5037. },
  5038. _getEvent: function () { // evil magic for IE
  5039. /*jshint noarg:false */
  5040. var e = window.event;
  5041. if (!e) {
  5042. var caller = arguments.callee.caller;
  5043. while (caller) {
  5044. e = caller['arguments'][0];
  5045. if (e && window.Event === e.constructor) {
  5046. break;
  5047. }
  5048. caller = caller.caller;
  5049. }
  5050. }
  5051. return e;
  5052. },
  5053. // this is a horrible workaround for a bug in Android where a single touch triggers two click events
  5054. _filterClick: function (e, handler) {
  5055. var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
  5056. elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
  5057. // are they closer together than 500ms yet more than 100ms?
  5058. // Android typically triggers them ~300ms apart while multiple listeners
  5059. // on the same event should be triggered far faster;
  5060. // or check if click is simulated on the element, and if it is, reject any non-simulated events
  5061. if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
  5062. L.DomEvent.stop(e);
  5063. return;
  5064. }
  5065. L.DomEvent._lastClick = timeStamp;
  5066. return handler(e);
  5067. }
  5068. };
  5069. L.DomEvent.on = L.DomEvent.addListener;
  5070. L.DomEvent.off = L.DomEvent.removeListener;
  5071. /*
  5072. * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
  5073. */
  5074. L.Draggable = L.Class.extend({
  5075. includes: L.Mixin.Events,
  5076. statics: {
  5077. START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
  5078. END: {
  5079. mousedown: 'mouseup',
  5080. touchstart: 'touchend',
  5081. pointerdown: 'touchend',
  5082. MSPointerDown: 'touchend'
  5083. },
  5084. MOVE: {
  5085. mousedown: 'mousemove',
  5086. touchstart: 'touchmove',
  5087. pointerdown: 'touchmove',
  5088. MSPointerDown: 'touchmove'
  5089. }
  5090. },
  5091. initialize: function (element, dragStartTarget) {
  5092. this._element = element;
  5093. this._dragStartTarget = dragStartTarget || element;
  5094. },
  5095. enable: function () {
  5096. if (this._enabled) { return; }
  5097. for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
  5098. L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
  5099. }
  5100. this._enabled = true;
  5101. },
  5102. disable: function () {
  5103. if (!this._enabled) { return; }
  5104. for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
  5105. L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
  5106. }
  5107. this._enabled = false;
  5108. this._moved = false;
  5109. },
  5110. _onDown: function (e) {
  5111. this._moved = false;
  5112. if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
  5113. L.DomEvent.stopPropagation(e);
  5114. if (L.Draggable._disabled) { return; }
  5115. L.DomUtil.disableImageDrag();
  5116. L.DomUtil.disableTextSelection();
  5117. if (this._moving) { return; }
  5118. var first = e.touches ? e.touches[0] : e;
  5119. this._startPoint = new L.Point(first.clientX, first.clientY);
  5120. this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
  5121. L.DomEvent
  5122. .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
  5123. .on(document, L.Draggable.END[e.type], this._onUp, this);
  5124. },
  5125. _onMove: function (e) {
  5126. if (e.touches && e.touches.length > 1) {
  5127. this._moved = true;
  5128. return;
  5129. }
  5130. var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
  5131. newPoint = new L.Point(first.clientX, first.clientY),
  5132. offset = newPoint.subtract(this._startPoint);
  5133. if (!offset.x && !offset.y) { return; }
  5134. if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; }
  5135. L.DomEvent.preventDefault(e);
  5136. if (!this._moved) {
  5137. this.fire('dragstart');
  5138. this._moved = true;
  5139. this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
  5140. L.DomUtil.addClass(document.body, 'leaflet-dragging');
  5141. this._lastTarget = e.target || e.srcElement;
  5142. L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
  5143. }
  5144. this._newPos = this._startPos.add(offset);
  5145. this._moving = true;
  5146. L.Util.cancelAnimFrame(this._animRequest);
  5147. this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
  5148. },
  5149. _updatePosition: function () {
  5150. this.fire('predrag');
  5151. L.DomUtil.setPosition(this._element, this._newPos);
  5152. this.fire('drag');
  5153. },
  5154. _onUp: function () {
  5155. L.DomUtil.removeClass(document.body, 'leaflet-dragging');
  5156. if (this._lastTarget) {
  5157. L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
  5158. this._lastTarget = null;
  5159. }
  5160. for (var i in L.Draggable.MOVE) {
  5161. L.DomEvent
  5162. .off(document, L.Draggable.MOVE[i], this._onMove)
  5163. .off(document, L.Draggable.END[i], this._onUp);
  5164. }
  5165. L.DomUtil.enableImageDrag();
  5166. L.DomUtil.enableTextSelection();
  5167. if (this._moved && this._moving) {
  5168. // ensure drag is not fired after dragend
  5169. L.Util.cancelAnimFrame(this._animRequest);
  5170. this.fire('dragend', {
  5171. distance: this._newPos.distanceTo(this._startPos)
  5172. });
  5173. }
  5174. this._moving = false;
  5175. }
  5176. });
  5177. /*
  5178. L.Handler is a base class for handler classes that are used internally to inject
  5179. interaction features like dragging to classes like Map and Marker.
  5180. */
  5181. L.Handler = L.Class.extend({
  5182. initialize: function (map) {
  5183. this._map = map;
  5184. },
  5185. enable: function () {
  5186. if (this._enabled) { return; }
  5187. this._enabled = true;
  5188. this.addHooks();
  5189. },
  5190. disable: function () {
  5191. if (!this._enabled) { return; }
  5192. this._enabled = false;
  5193. this.removeHooks();
  5194. },
  5195. enabled: function () {
  5196. return !!this._enabled;
  5197. }
  5198. });
  5199. /*
  5200. * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
  5201. */
  5202. L.Map.mergeOptions({
  5203. dragging: true,
  5204. inertia: !L.Browser.android23,
  5205. inertiaDeceleration: 3400, // px/s^2
  5206. inertiaMaxSpeed: Infinity, // px/s
  5207. inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
  5208. easeLinearity: 0.25,
  5209. // TODO refactor, move to CRS
  5210. worldCopyJump: false
  5211. });
  5212. L.Map.Drag = L.Handler.extend({
  5213. addHooks: function () {
  5214. if (!this._draggable) {
  5215. var map = this._map;
  5216. this._draggable = new L.Draggable(map._mapPane, map._container);
  5217. this._draggable.on({
  5218. 'dragstart': this._onDragStart,
  5219. 'drag': this._onDrag,
  5220. 'dragend': this._onDragEnd
  5221. }, this);
  5222. if (map.options.worldCopyJump) {
  5223. this._draggable.on('predrag', this._onPreDrag, this);
  5224. map.on('viewreset', this._onViewReset, this);
  5225. map.whenReady(this._onViewReset, this);
  5226. }
  5227. }
  5228. this._draggable.enable();
  5229. },
  5230. removeHooks: function () {
  5231. this._draggable.disable();
  5232. },
  5233. moved: function () {
  5234. return this._draggable && this._draggable._moved;
  5235. },
  5236. _onDragStart: function () {
  5237. var map = this._map;
  5238. if (map._panAnim) {
  5239. map._panAnim.stop();
  5240. }
  5241. map
  5242. .fire('movestart')
  5243. .fire('dragstart');
  5244. if (map.options.inertia) {
  5245. this._positions = [];
  5246. this._times = [];
  5247. }
  5248. },
  5249. _onDrag: function () {
  5250. if (this._map.options.inertia) {
  5251. var time = this._lastTime = +new Date(),
  5252. pos = this._lastPos = this._draggable._newPos;
  5253. this._positions.push(pos);
  5254. this._times.push(time);
  5255. if (time - this._times[0] > 200) {
  5256. this._positions.shift();
  5257. this._times.shift();
  5258. }
  5259. }
  5260. this._map
  5261. .fire('move')
  5262. .fire('drag');
  5263. },
  5264. _onViewReset: function () {
  5265. // TODO fix hardcoded Earth values
  5266. var pxCenter = this._map.getSize()._divideBy(2),
  5267. pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
  5268. this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
  5269. this._worldWidth = this._map.project([0, 180]).x;
  5270. },
  5271. _onPreDrag: function () {
  5272. // TODO refactor to be able to adjust map pane position after zoom
  5273. var worldWidth = this._worldWidth,
  5274. halfWidth = Math.round(worldWidth / 2),
  5275. dx = this._initialWorldOffset,
  5276. x = this._draggable._newPos.x,
  5277. newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
  5278. newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
  5279. newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
  5280. this._draggable._newPos.x = newX;
  5281. },
  5282. _onDragEnd: function (e) {
  5283. var map = this._map,
  5284. options = map.options,
  5285. delay = +new Date() - this._lastTime,
  5286. noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
  5287. map.fire('dragend', e);
  5288. if (noInertia) {
  5289. map.fire('moveend');
  5290. } else {
  5291. var direction = this._lastPos.subtract(this._positions[0]),
  5292. duration = (this._lastTime + delay - this._times[0]) / 1000,
  5293. ease = options.easeLinearity,
  5294. speedVector = direction.multiplyBy(ease / duration),
  5295. speed = speedVector.distanceTo([0, 0]),
  5296. limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
  5297. limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
  5298. decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
  5299. offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
  5300. if (!offset.x || !offset.y) {
  5301. map.fire('moveend');
  5302. } else {
  5303. offset = map._limitOffset(offset, map.options.maxBounds);
  5304. L.Util.requestAnimFrame(function () {
  5305. map.panBy(offset, {
  5306. duration: decelerationDuration,
  5307. easeLinearity: ease,
  5308. noMoveStart: true
  5309. });
  5310. });
  5311. }
  5312. }
  5313. }
  5314. });
  5315. L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
  5316. /*
  5317. * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
  5318. */
  5319. L.Map.mergeOptions({
  5320. doubleClickZoom: true
  5321. });
  5322. L.Map.DoubleClickZoom = L.Handler.extend({
  5323. addHooks: function () {
  5324. this._map.on('dblclick', this._onDoubleClick, this);
  5325. },
  5326. removeHooks: function () {
  5327. this._map.off('dblclick', this._onDoubleClick, this);
  5328. },
  5329. _onDoubleClick: function (e) {
  5330. var map = this._map,
  5331. zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1);
  5332. if (map.options.doubleClickZoom === 'center') {
  5333. map.setZoom(zoom);
  5334. } else {
  5335. map.setZoomAround(e.containerPoint, zoom);
  5336. }
  5337. }
  5338. });
  5339. L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
  5340. /*
  5341. * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
  5342. */
  5343. L.Map.mergeOptions({
  5344. scrollWheelZoom: true
  5345. });
  5346. L.Map.ScrollWheelZoom = L.Handler.extend({
  5347. addHooks: function () {
  5348. L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
  5349. L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
  5350. this._delta = 0;
  5351. },
  5352. removeHooks: function () {
  5353. L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
  5354. L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
  5355. },
  5356. _onWheelScroll: function (e) {
  5357. var delta = L.DomEvent.getWheelDelta(e);
  5358. this._delta += delta;
  5359. this._lastMousePos = this._map.mouseEventToContainerPoint(e);
  5360. if (!this._startTime) {
  5361. this._startTime = +new Date();
  5362. }
  5363. var left = Math.max(40 - (+new Date() - this._startTime), 0);
  5364. clearTimeout(this._timer);
  5365. this._timer = setTimeout(L.bind(this._performZoom, this), left);
  5366. L.DomEvent.preventDefault(e);
  5367. L.DomEvent.stopPropagation(e);
  5368. },
  5369. _performZoom: function () {
  5370. var map = this._map,
  5371. delta = this._delta,
  5372. zoom = map.getZoom();
  5373. delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
  5374. delta = Math.max(Math.min(delta, 4), -4);
  5375. delta = map._limitZoom(zoom + delta) - zoom;
  5376. this._delta = 0;
  5377. this._startTime = null;
  5378. if (!delta) { return; }
  5379. if (map.options.scrollWheelZoom === 'center') {
  5380. map.setZoom(zoom + delta);
  5381. } else {
  5382. map.setZoomAround(this._lastMousePos, zoom + delta);
  5383. }
  5384. }
  5385. });
  5386. L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
  5387. /*
  5388. * Extends the event handling code with double tap support for mobile browsers.
  5389. */
  5390. L.extend(L.DomEvent, {
  5391. _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
  5392. _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
  5393. // inspired by Zepto touch code by Thomas Fuchs
  5394. addDoubleTapListener: function (obj, handler, id) {
  5395. var last,
  5396. doubleTap = false,
  5397. delay = 250,
  5398. touch,
  5399. pre = '_leaflet_',
  5400. touchstart = this._touchstart,
  5401. touchend = this._touchend,
  5402. trackedTouches = [];
  5403. function onTouchStart(e) {
  5404. var count;
  5405. if (L.Browser.pointer) {
  5406. trackedTouches.push(e.pointerId);
  5407. count = trackedTouches.length;
  5408. } else {
  5409. count = e.touches.length;
  5410. }
  5411. if (count > 1) {
  5412. return;
  5413. }
  5414. var now = Date.now(),
  5415. delta = now - (last || now);
  5416. touch = e.touches ? e.touches[0] : e;
  5417. doubleTap = (delta > 0 && delta <= delay);
  5418. last = now;
  5419. }
  5420. function onTouchEnd(e) {
  5421. if (L.Browser.pointer) {
  5422. var idx = trackedTouches.indexOf(e.pointerId);
  5423. if (idx === -1) {
  5424. return;
  5425. }
  5426. trackedTouches.splice(idx, 1);
  5427. }
  5428. if (doubleTap) {
  5429. if (L.Browser.pointer) {
  5430. // work around .type being readonly with MSPointer* events
  5431. var newTouch = { },
  5432. prop;
  5433. // jshint forin:false
  5434. for (var i in touch) {
  5435. prop = touch[i];
  5436. if (typeof prop === 'function') {
  5437. newTouch[i] = prop.bind(touch);
  5438. } else {
  5439. newTouch[i] = prop;
  5440. }
  5441. }
  5442. touch = newTouch;
  5443. }
  5444. touch.type = 'dblclick';
  5445. handler(touch);
  5446. last = null;
  5447. }
  5448. }
  5449. obj[pre + touchstart + id] = onTouchStart;
  5450. obj[pre + touchend + id] = onTouchEnd;
  5451. // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen
  5452. // will not come through to us, so we will lose track of how many touches are ongoing
  5453. var endElement = L.Browser.pointer ? document.documentElement : obj;
  5454. obj.addEventListener(touchstart, onTouchStart, false);
  5455. endElement.addEventListener(touchend, onTouchEnd, false);
  5456. if (L.Browser.pointer) {
  5457. endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);
  5458. }
  5459. return this;
  5460. },
  5461. removeDoubleTapListener: function (obj, id) {
  5462. var pre = '_leaflet_';
  5463. obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
  5464. (L.Browser.pointer ? document.documentElement : obj).removeEventListener(
  5465. this._touchend, obj[pre + this._touchend + id], false);
  5466. if (L.Browser.pointer) {
  5467. document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],
  5468. false);
  5469. }
  5470. return this;
  5471. }
  5472. });
  5473. /*
  5474. * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
  5475. */
  5476. L.extend(L.DomEvent, {
  5477. //static
  5478. POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
  5479. POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
  5480. POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
  5481. POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
  5482. _pointers: [],
  5483. _pointerDocumentListener: false,
  5484. // Provides a touch events wrapper for (ms)pointer events.
  5485. // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
  5486. //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
  5487. addPointerListener: function (obj, type, handler, id) {
  5488. switch (type) {
  5489. case 'touchstart':
  5490. return this.addPointerListenerStart(obj, type, handler, id);
  5491. case 'touchend':
  5492. return this.addPointerListenerEnd(obj, type, handler, id);
  5493. case 'touchmove':
  5494. return this.addPointerListenerMove(obj, type, handler, id);
  5495. default:
  5496. throw 'Unknown touch event type';
  5497. }
  5498. },
  5499. addPointerListenerStart: function (obj, type, handler, id) {
  5500. var pre = '_leaflet_',
  5501. pointers = this._pointers;
  5502. var cb = function (e) {
  5503. L.DomEvent.preventDefault(e);
  5504. var alreadyInArray = false;
  5505. for (var i = 0; i < pointers.length; i++) {
  5506. if (pointers[i].pointerId === e.pointerId) {
  5507. alreadyInArray = true;
  5508. break;
  5509. }
  5510. }
  5511. if (!alreadyInArray) {
  5512. pointers.push(e);
  5513. }
  5514. e.touches = pointers.slice();
  5515. e.changedTouches = [e];
  5516. handler(e);
  5517. };
  5518. obj[pre + 'touchstart' + id] = cb;
  5519. obj.addEventListener(this.POINTER_DOWN, cb, false);
  5520. // need to also listen for end events to keep the _pointers list accurate
  5521. // this needs to be on the body and never go away
  5522. if (!this._pointerDocumentListener) {
  5523. var internalCb = function (e) {
  5524. for (var i = 0; i < pointers.length; i++) {
  5525. if (pointers[i].pointerId === e.pointerId) {
  5526. pointers.splice(i, 1);
  5527. break;
  5528. }
  5529. }
  5530. };
  5531. //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
  5532. document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
  5533. document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
  5534. this._pointerDocumentListener = true;
  5535. }
  5536. return this;
  5537. },
  5538. addPointerListenerMove: function (obj, type, handler, id) {
  5539. var pre = '_leaflet_',
  5540. touches = this._pointers;
  5541. function cb(e) {
  5542. // don't fire touch moves when mouse isn't down
  5543. if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
  5544. for (var i = 0; i < touches.length; i++) {
  5545. if (touches[i].pointerId === e.pointerId) {
  5546. touches[i] = e;
  5547. break;
  5548. }
  5549. }
  5550. e.touches = touches.slice();
  5551. e.changedTouches = [e];
  5552. handler(e);
  5553. }
  5554. obj[pre + 'touchmove' + id] = cb;
  5555. obj.addEventListener(this.POINTER_MOVE, cb, false);
  5556. return this;
  5557. },
  5558. addPointerListenerEnd: function (obj, type, handler, id) {
  5559. var pre = '_leaflet_',
  5560. touches = this._pointers;
  5561. var cb = function (e) {
  5562. for (var i = 0; i < touches.length; i++) {
  5563. if (touches[i].pointerId === e.pointerId) {
  5564. touches.splice(i, 1);
  5565. break;
  5566. }
  5567. }
  5568. e.touches = touches.slice();
  5569. e.changedTouches = [e];
  5570. handler(e);
  5571. };
  5572. obj[pre + 'touchend' + id] = cb;
  5573. obj.addEventListener(this.POINTER_UP, cb, false);
  5574. obj.addEventListener(this.POINTER_CANCEL, cb, false);
  5575. return this;
  5576. },
  5577. removePointerListener: function (obj, type, id) {
  5578. var pre = '_leaflet_',
  5579. cb = obj[pre + type + id];
  5580. switch (type) {
  5581. case 'touchstart':
  5582. obj.removeEventListener(this.POINTER_DOWN, cb, false);
  5583. break;
  5584. case 'touchmove':
  5585. obj.removeEventListener(this.POINTER_MOVE, cb, false);
  5586. break;
  5587. case 'touchend':
  5588. obj.removeEventListener(this.POINTER_UP, cb, false);
  5589. obj.removeEventListener(this.POINTER_CANCEL, cb, false);
  5590. break;
  5591. }
  5592. return this;
  5593. }
  5594. });
  5595. /*
  5596. * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
  5597. */
  5598. L.Map.mergeOptions({
  5599. touchZoom: L.Browser.touch && !L.Browser.android23,
  5600. bounceAtZoomLimits: true
  5601. });
  5602. L.Map.TouchZoom = L.Handler.extend({
  5603. addHooks: function () {
  5604. L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
  5605. },
  5606. removeHooks: function () {
  5607. L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
  5608. },
  5609. _onTouchStart: function (e) {
  5610. var map = this._map;
  5611. if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
  5612. var p1 = map.mouseEventToLayerPoint(e.touches[0]),
  5613. p2 = map.mouseEventToLayerPoint(e.touches[1]),
  5614. viewCenter = map._getCenterLayerPoint();
  5615. this._startCenter = p1.add(p2)._divideBy(2);
  5616. this._startDist = p1.distanceTo(p2);
  5617. this._moved = false;
  5618. this._zooming = true;
  5619. this._centerOffset = viewCenter.subtract(this._startCenter);
  5620. if (map._panAnim) {
  5621. map._panAnim.stop();
  5622. }
  5623. L.DomEvent
  5624. .on(document, 'touchmove', this._onTouchMove, this)
  5625. .on(document, 'touchend', this._onTouchEnd, this);
  5626. L.DomEvent.preventDefault(e);
  5627. },
  5628. _onTouchMove: function (e) {
  5629. var map = this._map;
  5630. if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
  5631. var p1 = map.mouseEventToLayerPoint(e.touches[0]),
  5632. p2 = map.mouseEventToLayerPoint(e.touches[1]);
  5633. this._scale = p1.distanceTo(p2) / this._startDist;
  5634. this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
  5635. if (this._scale === 1) { return; }
  5636. if (!map.options.bounceAtZoomLimits) {
  5637. if ((map.getZoom() === map.getMinZoom() && this._scale < 1) ||
  5638. (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; }
  5639. }
  5640. if (!this._moved) {
  5641. L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
  5642. map
  5643. .fire('movestart')
  5644. .fire('zoomstart');
  5645. this._moved = true;
  5646. }
  5647. L.Util.cancelAnimFrame(this._animRequest);
  5648. this._animRequest = L.Util.requestAnimFrame(
  5649. this._updateOnMove, this, true, this._map._container);
  5650. L.DomEvent.preventDefault(e);
  5651. },
  5652. _updateOnMove: function () {
  5653. var map = this._map,
  5654. origin = this._getScaleOrigin(),
  5655. center = map.layerPointToLatLng(origin),
  5656. zoom = map.getScaleZoom(this._scale);
  5657. map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, false, true);
  5658. },
  5659. _onTouchEnd: function () {
  5660. if (!this._moved || !this._zooming) {
  5661. this._zooming = false;
  5662. return;
  5663. }
  5664. var map = this._map;
  5665. this._zooming = false;
  5666. L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
  5667. L.Util.cancelAnimFrame(this._animRequest);
  5668. L.DomEvent
  5669. .off(document, 'touchmove', this._onTouchMove)
  5670. .off(document, 'touchend', this._onTouchEnd);
  5671. var origin = this._getScaleOrigin(),
  5672. center = map.layerPointToLatLng(origin),
  5673. oldZoom = map.getZoom(),
  5674. floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
  5675. roundZoomDelta = (floatZoomDelta > 0 ?
  5676. Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
  5677. zoom = map._limitZoom(oldZoom + roundZoomDelta),
  5678. scale = map.getZoomScale(zoom) / this._scale;
  5679. map._animateZoom(center, zoom, origin, scale);
  5680. },
  5681. _getScaleOrigin: function () {
  5682. var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
  5683. return this._startCenter.add(centerOffset);
  5684. }
  5685. });
  5686. L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
  5687. /*
  5688. * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
  5689. */
  5690. L.Map.mergeOptions({
  5691. tap: true,
  5692. tapTolerance: 15
  5693. });
  5694. L.Map.Tap = L.Handler.extend({
  5695. addHooks: function () {
  5696. L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
  5697. },
  5698. removeHooks: function () {
  5699. L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
  5700. },
  5701. _onDown: function (e) {
  5702. if (!e.touches) { return; }
  5703. L.DomEvent.preventDefault(e);
  5704. this._fireClick = true;
  5705. // don't simulate click or track longpress if more than 1 touch
  5706. if (e.touches.length > 1) {
  5707. this._fireClick = false;
  5708. clearTimeout(this._holdTimeout);
  5709. return;
  5710. }
  5711. var first = e.touches[0],
  5712. el = first.target;
  5713. this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
  5714. // if touching a link, highlight it
  5715. if (el.tagName && el.tagName.toLowerCase() === 'a') {
  5716. L.DomUtil.addClass(el, 'leaflet-active');
  5717. }
  5718. // simulate long hold but setting a timeout
  5719. this._holdTimeout = setTimeout(L.bind(function () {
  5720. if (this._isTapValid()) {
  5721. this._fireClick = false;
  5722. this._onUp();
  5723. this._simulateEvent('contextmenu', first);
  5724. }
  5725. }, this), 1000);
  5726. L.DomEvent
  5727. .on(document, 'touchmove', this._onMove, this)
  5728. .on(document, 'touchend', this._onUp, this);
  5729. },
  5730. _onUp: function (e) {
  5731. clearTimeout(this._holdTimeout);
  5732. L.DomEvent
  5733. .off(document, 'touchmove', this._onMove, this)
  5734. .off(document, 'touchend', this._onUp, this);
  5735. if (this._fireClick && e && e.changedTouches) {
  5736. var first = e.changedTouches[0],
  5737. el = first.target;
  5738. if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
  5739. L.DomUtil.removeClass(el, 'leaflet-active');
  5740. }
  5741. // simulate click if the touch didn't move too much
  5742. if (this._isTapValid()) {
  5743. this._simulateEvent('click', first);
  5744. }
  5745. }
  5746. },
  5747. _isTapValid: function () {
  5748. return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
  5749. },
  5750. _onMove: function (e) {
  5751. var first = e.touches[0];
  5752. this._newPos = new L.Point(first.clientX, first.clientY);
  5753. },
  5754. _simulateEvent: function (type, e) {
  5755. var simulatedEvent = document.createEvent('MouseEvents');
  5756. simulatedEvent._simulated = true;
  5757. e.target._simulatedClick = true;
  5758. simulatedEvent.initMouseEvent(
  5759. type, true, true, window, 1,
  5760. e.screenX, e.screenY,
  5761. e.clientX, e.clientY,
  5762. false, false, false, false, 0, null);
  5763. e.target.dispatchEvent(simulatedEvent);
  5764. }
  5765. });
  5766. if (L.Browser.touch && !L.Browser.pointer) {
  5767. L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
  5768. }
  5769. /*
  5770. * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
  5771. * (zoom to a selected bounding box), enabled by default.
  5772. */
  5773. L.Map.mergeOptions({
  5774. boxZoom: true
  5775. });
  5776. L.Map.BoxZoom = L.Handler.extend({
  5777. initialize: function (map) {
  5778. this._map = map;
  5779. this._container = map._container;
  5780. this._pane = map._panes.overlayPane;
  5781. this._moved = false;
  5782. },
  5783. addHooks: function () {
  5784. L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
  5785. },
  5786. removeHooks: function () {
  5787. L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
  5788. this._moved = false;
  5789. },
  5790. moved: function () {
  5791. return this._moved;
  5792. },
  5793. _onMouseDown: function (e) {
  5794. this._moved = false;
  5795. if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
  5796. L.DomUtil.disableTextSelection();
  5797. L.DomUtil.disableImageDrag();
  5798. this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
  5799. L.DomEvent
  5800. .on(document, 'mousemove', this._onMouseMove, this)
  5801. .on(document, 'mouseup', this._onMouseUp, this)
  5802. .on(document, 'keydown', this._onKeyDown, this);
  5803. },
  5804. _onMouseMove: function (e) {
  5805. if (!this._moved) {
  5806. this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
  5807. L.DomUtil.setPosition(this._box, this._startLayerPoint);
  5808. //TODO refactor: move cursor to styles
  5809. this._container.style.cursor = 'crosshair';
  5810. this._map.fire('boxzoomstart');
  5811. }
  5812. var startPoint = this._startLayerPoint,
  5813. box = this._box,
  5814. layerPoint = this._map.mouseEventToLayerPoint(e),
  5815. offset = layerPoint.subtract(startPoint),
  5816. newPos = new L.Point(
  5817. Math.min(layerPoint.x, startPoint.x),
  5818. Math.min(layerPoint.y, startPoint.y));
  5819. L.DomUtil.setPosition(box, newPos);
  5820. this._moved = true;
  5821. // TODO refactor: remove hardcoded 4 pixels
  5822. box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
  5823. box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
  5824. },
  5825. _finish: function () {
  5826. if (this._moved) {
  5827. this._pane.removeChild(this._box);
  5828. this._container.style.cursor = '';
  5829. }
  5830. L.DomUtil.enableTextSelection();
  5831. L.DomUtil.enableImageDrag();
  5832. L.DomEvent
  5833. .off(document, 'mousemove', this._onMouseMove)
  5834. .off(document, 'mouseup', this._onMouseUp)
  5835. .off(document, 'keydown', this._onKeyDown);
  5836. },
  5837. _onMouseUp: function (e) {
  5838. this._finish();
  5839. var map = this._map,
  5840. layerPoint = map.mouseEventToLayerPoint(e);
  5841. if (this._startLayerPoint.equals(layerPoint)) { return; }
  5842. var bounds = new L.LatLngBounds(
  5843. map.layerPointToLatLng(this._startLayerPoint),
  5844. map.layerPointToLatLng(layerPoint));
  5845. map.fitBounds(bounds);
  5846. map.fire('boxzoomend', {
  5847. boxZoomBounds: bounds
  5848. });
  5849. },
  5850. _onKeyDown: function (e) {
  5851. if (e.keyCode === 27) {
  5852. this._finish();
  5853. }
  5854. }
  5855. });
  5856. L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
  5857. /*
  5858. * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
  5859. */
  5860. L.Map.mergeOptions({
  5861. keyboard: true,
  5862. keyboardPanOffset: 80,
  5863. keyboardZoomOffset: 1
  5864. });
  5865. L.Map.Keyboard = L.Handler.extend({
  5866. keyCodes: {
  5867. left: [37],
  5868. right: [39],
  5869. down: [40],
  5870. up: [38],
  5871. zoomIn: [187, 107, 61, 171],
  5872. zoomOut: [189, 109, 173]
  5873. },
  5874. initialize: function (map) {
  5875. this._map = map;
  5876. this._setPanOffset(map.options.keyboardPanOffset);
  5877. this._setZoomOffset(map.options.keyboardZoomOffset);
  5878. },
  5879. addHooks: function () {
  5880. var container = this._map._container;
  5881. // make the container focusable by tabbing
  5882. if (container.tabIndex === -1) {
  5883. container.tabIndex = '0';
  5884. }
  5885. L.DomEvent
  5886. .on(container, 'focus', this._onFocus, this)
  5887. .on(container, 'blur', this._onBlur, this)
  5888. .on(container, 'mousedown', this._onMouseDown, this);
  5889. this._map
  5890. .on('focus', this._addHooks, this)
  5891. .on('blur', this._removeHooks, this);
  5892. },
  5893. removeHooks: function () {
  5894. this._removeHooks();
  5895. var container = this._map._container;
  5896. L.DomEvent
  5897. .off(container, 'focus', this._onFocus, this)
  5898. .off(container, 'blur', this._onBlur, this)
  5899. .off(container, 'mousedown', this._onMouseDown, this);
  5900. this._map
  5901. .off('focus', this._addHooks, this)
  5902. .off('blur', this._removeHooks, this);
  5903. },
  5904. _onMouseDown: function () {
  5905. if (this._focused) { return; }
  5906. var body = document.body,
  5907. docEl = document.documentElement,
  5908. top = body.scrollTop || docEl.scrollTop,
  5909. left = body.scrollLeft || docEl.scrollLeft;
  5910. this._map._container.focus();
  5911. window.scrollTo(left, top);
  5912. },
  5913. _onFocus: function () {
  5914. this._focused = true;
  5915. this._map.fire('focus');
  5916. },
  5917. _onBlur: function () {
  5918. this._focused = false;
  5919. this._map.fire('blur');
  5920. },
  5921. _setPanOffset: function (pan) {
  5922. var keys = this._panKeys = {},
  5923. codes = this.keyCodes,
  5924. i, len;
  5925. for (i = 0, len = codes.left.length; i < len; i++) {
  5926. keys[codes.left[i]] = [-1 * pan, 0];
  5927. }
  5928. for (i = 0, len = codes.right.length; i < len; i++) {
  5929. keys[codes.right[i]] = [pan, 0];
  5930. }
  5931. for (i = 0, len = codes.down.length; i < len; i++) {
  5932. keys[codes.down[i]] = [0, pan];
  5933. }
  5934. for (i = 0, len = codes.up.length; i < len; i++) {
  5935. keys[codes.up[i]] = [0, -1 * pan];
  5936. }
  5937. },
  5938. _setZoomOffset: function (zoom) {
  5939. var keys = this._zoomKeys = {},
  5940. codes = this.keyCodes,
  5941. i, len;
  5942. for (i = 0, len = codes.zoomIn.length; i < len; i++) {
  5943. keys[codes.zoomIn[i]] = zoom;
  5944. }
  5945. for (i = 0, len = codes.zoomOut.length; i < len; i++) {
  5946. keys[codes.zoomOut[i]] = -zoom;
  5947. }
  5948. },
  5949. _addHooks: function () {
  5950. L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
  5951. },
  5952. _removeHooks: function () {
  5953. L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
  5954. },
  5955. _onKeyDown: function (e) {
  5956. var key = e.keyCode,
  5957. map = this._map;
  5958. if (key in this._panKeys) {
  5959. if (map._panAnim && map._panAnim._inProgress) { return; }
  5960. map.panBy(this._panKeys[key]);
  5961. if (map.options.maxBounds) {
  5962. map.panInsideBounds(map.options.maxBounds);
  5963. }
  5964. } else if (key in this._zoomKeys) {
  5965. map.setZoom(map.getZoom() + this._zoomKeys[key]);
  5966. } else {
  5967. return;
  5968. }
  5969. L.DomEvent.stop(e);
  5970. }
  5971. });
  5972. L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
  5973. /*
  5974. * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
  5975. */
  5976. L.Handler.MarkerDrag = L.Handler.extend({
  5977. initialize: function (marker) {
  5978. this._marker = marker;
  5979. },
  5980. addHooks: function () {
  5981. var icon = this._marker._icon;
  5982. if (!this._draggable) {
  5983. this._draggable = new L.Draggable(icon, icon);
  5984. }
  5985. this._draggable
  5986. .on('dragstart', this._onDragStart, this)
  5987. .on('drag', this._onDrag, this)
  5988. .on('dragend', this._onDragEnd, this);
  5989. this._draggable.enable();
  5990. L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable');
  5991. },
  5992. removeHooks: function () {
  5993. this._draggable
  5994. .off('dragstart', this._onDragStart, this)
  5995. .off('drag', this._onDrag, this)
  5996. .off('dragend', this._onDragEnd, this);
  5997. this._draggable.disable();
  5998. L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
  5999. },
  6000. moved: function () {
  6001. return this._draggable && this._draggable._moved;
  6002. },
  6003. _onDragStart: function () {
  6004. this._marker
  6005. .closePopup()
  6006. .fire('movestart')
  6007. .fire('dragstart');
  6008. },
  6009. _onDrag: function () {
  6010. var marker = this._marker,
  6011. shadow = marker._shadow,
  6012. iconPos = L.DomUtil.getPosition(marker._icon),
  6013. latlng = marker._map.layerPointToLatLng(iconPos);
  6014. // update shadow position
  6015. if (shadow) {
  6016. L.DomUtil.setPosition(shadow, iconPos);
  6017. }
  6018. marker._latlng = latlng;
  6019. marker
  6020. .fire('move', {latlng: latlng})
  6021. .fire('drag');
  6022. },
  6023. _onDragEnd: function (e) {
  6024. this._marker
  6025. .fire('moveend')
  6026. .fire('dragend', e);
  6027. }
  6028. });
  6029. /*
  6030. * L.Control is a base class for implementing map controls. Handles positioning.
  6031. * All other controls extend from this class.
  6032. */
  6033. L.Control = L.Class.extend({
  6034. options: {
  6035. position: 'topright'
  6036. },
  6037. initialize: function (options) {
  6038. L.setOptions(this, options);
  6039. },
  6040. getPosition: function () {
  6041. return this.options.position;
  6042. },
  6043. setPosition: function (position) {
  6044. var map = this._map;
  6045. if (map) {
  6046. map.removeControl(this);
  6047. }
  6048. this.options.position = position;
  6049. if (map) {
  6050. map.addControl(this);
  6051. }
  6052. return this;
  6053. },
  6054. getContainer: function () {
  6055. return this._container;
  6056. },
  6057. addTo: function (map) {
  6058. this._map = map;
  6059. var container = this._container = this.onAdd(map),
  6060. pos = this.getPosition(),
  6061. corner = map._controlCorners[pos];
  6062. L.DomUtil.addClass(container, 'leaflet-control');
  6063. if (pos.indexOf('bottom') !== -1) {
  6064. corner.insertBefore(container, corner.firstChild);
  6065. } else {
  6066. corner.appendChild(container);
  6067. }
  6068. return this;
  6069. },
  6070. removeFrom: function (map) {
  6071. var pos = this.getPosition(),
  6072. corner = map._controlCorners[pos];
  6073. corner.removeChild(this._container);
  6074. this._map = null;
  6075. if (this.onRemove) {
  6076. this.onRemove(map);
  6077. }
  6078. return this;
  6079. },
  6080. _refocusOnMap: function () {
  6081. if (this._map) {
  6082. this._map.getContainer().focus();
  6083. }
  6084. }
  6085. });
  6086. L.control = function (options) {
  6087. return new L.Control(options);
  6088. };
  6089. // adds control-related methods to L.Map
  6090. L.Map.include({
  6091. addControl: function (control) {
  6092. control.addTo(this);
  6093. return this;
  6094. },
  6095. removeControl: function (control) {
  6096. control.removeFrom(this);
  6097. return this;
  6098. },
  6099. _initControlPos: function () {
  6100. var corners = this._controlCorners = {},
  6101. l = 'leaflet-',
  6102. container = this._controlContainer =
  6103. L.DomUtil.create('div', l + 'control-container', this._container);
  6104. function createCorner(vSide, hSide) {
  6105. var className = l + vSide + ' ' + l + hSide;
  6106. corners[vSide + hSide] = L.DomUtil.create('div', className, container);
  6107. }
  6108. createCorner('top', 'left');
  6109. createCorner('top', 'right');
  6110. createCorner('bottom', 'left');
  6111. createCorner('bottom', 'right');
  6112. },
  6113. _clearControlPos: function () {
  6114. this._container.removeChild(this._controlContainer);
  6115. }
  6116. });
  6117. /*
  6118. * L.Control.Zoom is used for the default zoom buttons on the map.
  6119. */
  6120. L.Control.Zoom = L.Control.extend({
  6121. options: {
  6122. position: 'topleft',
  6123. zoomInText: '+',
  6124. zoomInTitle: 'Zoom in',
  6125. zoomOutText: '-',
  6126. zoomOutTitle: 'Zoom out'
  6127. },
  6128. onAdd: function (map) {
  6129. var zoomName = 'leaflet-control-zoom',
  6130. container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
  6131. this._map = map;
  6132. this._zoomInButton = this._createButton(
  6133. this.options.zoomInText, this.options.zoomInTitle,
  6134. zoomName + '-in', container, this._zoomIn, this);
  6135. this._zoomOutButton = this._createButton(
  6136. this.options.zoomOutText, this.options.zoomOutTitle,
  6137. zoomName + '-out', container, this._zoomOut, this);
  6138. this._updateDisabled();
  6139. map.on('zoomend zoomlevelschange', this._updateDisabled, this);
  6140. return container;
  6141. },
  6142. onRemove: function (map) {
  6143. map.off('zoomend zoomlevelschange', this._updateDisabled, this);
  6144. },
  6145. _zoomIn: function (e) {
  6146. this._map.zoomIn(e.shiftKey ? 3 : 1);
  6147. },
  6148. _zoomOut: function (e) {
  6149. this._map.zoomOut(e.shiftKey ? 3 : 1);
  6150. },
  6151. _createButton: function (html, title, className, container, fn, context) {
  6152. var link = L.DomUtil.create('a', className, container);
  6153. link.innerHTML = html;
  6154. link.href = '#';
  6155. link.title = title;
  6156. var stop = L.DomEvent.stopPropagation;
  6157. L.DomEvent
  6158. .on(link, 'click', stop)
  6159. .on(link, 'mousedown', stop)
  6160. .on(link, 'dblclick', stop)
  6161. .on(link, 'click', L.DomEvent.preventDefault)
  6162. .on(link, 'click', fn, context)
  6163. .on(link, 'click', this._refocusOnMap, context);
  6164. return link;
  6165. },
  6166. _updateDisabled: function () {
  6167. var map = this._map,
  6168. className = 'leaflet-disabled';
  6169. L.DomUtil.removeClass(this._zoomInButton, className);
  6170. L.DomUtil.removeClass(this._zoomOutButton, className);
  6171. if (map._zoom === map.getMinZoom()) {
  6172. L.DomUtil.addClass(this._zoomOutButton, className);
  6173. }
  6174. if (map._zoom === map.getMaxZoom()) {
  6175. L.DomUtil.addClass(this._zoomInButton, className);
  6176. }
  6177. }
  6178. });
  6179. L.Map.mergeOptions({
  6180. zoomControl: true
  6181. });
  6182. L.Map.addInitHook(function () {
  6183. if (this.options.zoomControl) {
  6184. this.zoomControl = new L.Control.Zoom();
  6185. this.addControl(this.zoomControl);
  6186. }
  6187. });
  6188. L.control.zoom = function (options) {
  6189. return new L.Control.Zoom(options);
  6190. };
  6191. /*
  6192. * L.Control.Attribution is used for displaying attribution on the map (added by default).
  6193. */
  6194. L.Control.Attribution = L.Control.extend({
  6195. options: {
  6196. position: 'bottomright',
  6197. prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
  6198. },
  6199. initialize: function (options) {
  6200. L.setOptions(this, options);
  6201. this._attributions = {};
  6202. },
  6203. onAdd: function (map) {
  6204. this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
  6205. L.DomEvent.disableClickPropagation(this._container);
  6206. for (var i in map._layers) {
  6207. if (map._layers[i].getAttribution) {
  6208. this.addAttribution(map._layers[i].getAttribution());
  6209. }
  6210. }
  6211. map
  6212. .on('layeradd', this._onLayerAdd, this)
  6213. .on('layerremove', this._onLayerRemove, this);
  6214. this._update();
  6215. return this._container;
  6216. },
  6217. onRemove: function (map) {
  6218. map
  6219. .off('layeradd', this._onLayerAdd)
  6220. .off('layerremove', this._onLayerRemove);
  6221. },
  6222. setPrefix: function (prefix) {
  6223. this.options.prefix = prefix;
  6224. this._update();
  6225. return this;
  6226. },
  6227. addAttribution: function (text) {
  6228. if (!text) { return; }
  6229. if (!this._attributions[text]) {
  6230. this._attributions[text] = 0;
  6231. }
  6232. this._attributions[text]++;
  6233. this._update();
  6234. return this;
  6235. },
  6236. removeAttribution: function (text) {
  6237. if (!text) { return; }
  6238. if (this._attributions[text]) {
  6239. this._attributions[text]--;
  6240. this._update();
  6241. }
  6242. return this;
  6243. },
  6244. _update: function () {
  6245. if (!this._map) { return; }
  6246. var attribs = [];
  6247. for (var i in this._attributions) {
  6248. if (this._attributions[i]) {
  6249. attribs.push(i);
  6250. }
  6251. }
  6252. var prefixAndAttribs = [];
  6253. if (this.options.prefix) {
  6254. prefixAndAttribs.push(this.options.prefix);
  6255. }
  6256. if (attribs.length) {
  6257. prefixAndAttribs.push(attribs.join(', '));
  6258. }
  6259. this._container.innerHTML = prefixAndAttribs.join(' | ');
  6260. },
  6261. _onLayerAdd: function (e) {
  6262. if (e.layer.getAttribution) {
  6263. this.addAttribution(e.layer.getAttribution());
  6264. }
  6265. },
  6266. _onLayerRemove: function (e) {
  6267. if (e.layer.getAttribution) {
  6268. this.removeAttribution(e.layer.getAttribution());
  6269. }
  6270. }
  6271. });
  6272. L.Map.mergeOptions({
  6273. attributionControl: true
  6274. });
  6275. L.Map.addInitHook(function () {
  6276. if (this.options.attributionControl) {
  6277. this.attributionControl = (new L.Control.Attribution()).addTo(this);
  6278. }
  6279. });
  6280. L.control.attribution = function (options) {
  6281. return new L.Control.Attribution(options);
  6282. };
  6283. /*
  6284. * L.Control.Scale is used for displaying metric/imperial scale on the map.
  6285. */
  6286. L.Control.Scale = L.Control.extend({
  6287. options: {
  6288. position: 'bottomleft',
  6289. maxWidth: 100,
  6290. metric: true,
  6291. imperial: true,
  6292. updateWhenIdle: false
  6293. },
  6294. onAdd: function (map) {
  6295. this._map = map;
  6296. var className = 'leaflet-control-scale',
  6297. container = L.DomUtil.create('div', className),
  6298. options = this.options;
  6299. this._addScales(options, className, container);
  6300. map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
  6301. map.whenReady(this._update, this);
  6302. return container;
  6303. },
  6304. onRemove: function (map) {
  6305. map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
  6306. },
  6307. _addScales: function (options, className, container) {
  6308. if (options.metric) {
  6309. this._mScale = L.DomUtil.create('div', className + '-line', container);
  6310. }
  6311. if (options.imperial) {
  6312. this._iScale = L.DomUtil.create('div', className + '-line', container);
  6313. }
  6314. },
  6315. _update: function () {
  6316. var bounds = this._map.getBounds(),
  6317. centerLat = bounds.getCenter().lat,
  6318. halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
  6319. dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
  6320. size = this._map.getSize(),
  6321. options = this.options,
  6322. maxMeters = 0;
  6323. if (size.x > 0) {
  6324. maxMeters = dist * (options.maxWidth / size.x);
  6325. }
  6326. this._updateScales(options, maxMeters);
  6327. },
  6328. _updateScales: function (options, maxMeters) {
  6329. if (options.metric && maxMeters) {
  6330. this._updateMetric(maxMeters);
  6331. }
  6332. if (options.imperial && maxMeters) {
  6333. this._updateImperial(maxMeters);
  6334. }
  6335. },
  6336. _updateMetric: function (maxMeters) {
  6337. var meters = this._getRoundNum(maxMeters);
  6338. this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
  6339. this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
  6340. },
  6341. _updateImperial: function (maxMeters) {
  6342. var maxFeet = maxMeters * 3.2808399,
  6343. scale = this._iScale,
  6344. maxMiles, miles, feet;
  6345. if (maxFeet > 5280) {
  6346. maxMiles = maxFeet / 5280;
  6347. miles = this._getRoundNum(maxMiles);
  6348. scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
  6349. scale.innerHTML = miles + ' mi';
  6350. } else {
  6351. feet = this._getRoundNum(maxFeet);
  6352. scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
  6353. scale.innerHTML = feet + ' ft';
  6354. }
  6355. },
  6356. _getScaleWidth: function (ratio) {
  6357. return Math.round(this.options.maxWidth * ratio) - 10;
  6358. },
  6359. _getRoundNum: function (num) {
  6360. var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
  6361. d = num / pow10;
  6362. d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
  6363. return pow10 * d;
  6364. }
  6365. });
  6366. L.control.scale = function (options) {
  6367. return new L.Control.Scale(options);
  6368. };
  6369. /*
  6370. * L.Control.Layers is a control to allow users to switch between different layers on the map.
  6371. */
  6372. L.Control.Layers = L.Control.extend({
  6373. options: {
  6374. collapsed: true,
  6375. position: 'topright',
  6376. autoZIndex: true
  6377. },
  6378. initialize: function (baseLayers, overlays, options) {
  6379. L.setOptions(this, options);
  6380. this._layers = {};
  6381. this._lastZIndex = 0;
  6382. this._handlingClick = false;
  6383. for (var i in baseLayers) {
  6384. this._addLayer(baseLayers[i], i);
  6385. }
  6386. for (i in overlays) {
  6387. this._addLayer(overlays[i], i, true);
  6388. }
  6389. },
  6390. onAdd: function (map) {
  6391. this._initLayout();
  6392. this._update();
  6393. map
  6394. .on('layeradd', this._onLayerChange, this)
  6395. .on('layerremove', this._onLayerChange, this);
  6396. return this._container;
  6397. },
  6398. onRemove: function (map) {
  6399. map
  6400. .off('layeradd', this._onLayerChange, this)
  6401. .off('layerremove', this._onLayerChange, this);
  6402. },
  6403. addBaseLayer: function (layer, name) {
  6404. this._addLayer(layer, name);
  6405. this._update();
  6406. return this;
  6407. },
  6408. addOverlay: function (layer, name) {
  6409. this._addLayer(layer, name, true);
  6410. this._update();
  6411. return this;
  6412. },
  6413. removeLayer: function (layer) {
  6414. var id = L.stamp(layer);
  6415. delete this._layers[id];
  6416. this._update();
  6417. return this;
  6418. },
  6419. _initLayout: function () {
  6420. var className = 'leaflet-control-layers',
  6421. container = this._container = L.DomUtil.create('div', className);
  6422. //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
  6423. container.setAttribute('aria-haspopup', true);
  6424. if (!L.Browser.touch) {
  6425. L.DomEvent
  6426. .disableClickPropagation(container)
  6427. .disableScrollPropagation(container);
  6428. } else {
  6429. L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
  6430. }
  6431. var form = this._form = L.DomUtil.create('form', className + '-list');
  6432. if (this.options.collapsed) {
  6433. if (!L.Browser.android) {
  6434. L.DomEvent
  6435. .on(container, 'mouseover', this._expand, this)
  6436. .on(container, 'mouseout', this._collapse, this);
  6437. }
  6438. var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
  6439. link.href = '#';
  6440. link.title = 'Layers';
  6441. if (L.Browser.touch) {
  6442. L.DomEvent
  6443. .on(link, 'click', L.DomEvent.stop)
  6444. .on(link, 'click', this._expand, this);
  6445. }
  6446. else {
  6447. L.DomEvent.on(link, 'focus', this._expand, this);
  6448. }
  6449. //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033
  6450. L.DomEvent.on(form, 'click', function () {
  6451. setTimeout(L.bind(this._onInputClick, this), 0);
  6452. }, this);
  6453. this._map.on('click', this._collapse, this);
  6454. // TODO keyboard accessibility
  6455. } else {
  6456. this._expand();
  6457. }
  6458. this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
  6459. this._separator = L.DomUtil.create('div', className + '-separator', form);
  6460. this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
  6461. container.appendChild(form);
  6462. },
  6463. _addLayer: function (layer, name, overlay) {
  6464. var id = L.stamp(layer);
  6465. this._layers[id] = {
  6466. layer: layer,
  6467. name: name,
  6468. overlay: overlay
  6469. };
  6470. if (this.options.autoZIndex && layer.setZIndex) {
  6471. this._lastZIndex++;
  6472. layer.setZIndex(this._lastZIndex);
  6473. }
  6474. },
  6475. _update: function () {
  6476. if (!this._container) {
  6477. return;
  6478. }
  6479. this._baseLayersList.innerHTML = '';
  6480. this._overlaysList.innerHTML = '';
  6481. var baseLayersPresent = false,
  6482. overlaysPresent = false,
  6483. i, obj;
  6484. for (i in this._layers) {
  6485. obj = this._layers[i];
  6486. this._addItem(obj);
  6487. overlaysPresent = overlaysPresent || obj.overlay;
  6488. baseLayersPresent = baseLayersPresent || !obj.overlay;
  6489. }
  6490. this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
  6491. },
  6492. _onLayerChange: function (e) {
  6493. var obj = this._layers[L.stamp(e.layer)];
  6494. if (!obj) { return; }
  6495. if (!this._handlingClick) {
  6496. this._update();
  6497. }
  6498. var type = obj.overlay ?
  6499. (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
  6500. (e.type === 'layeradd' ? 'baselayerchange' : null);
  6501. if (type) {
  6502. this._map.fire(type, obj);
  6503. }
  6504. },
  6505. // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
  6506. _createRadioElement: function (name, checked) {
  6507. var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
  6508. if (checked) {
  6509. radioHtml += ' checked="checked"';
  6510. }
  6511. radioHtml += '/>';
  6512. var radioFragment = document.createElement('div');
  6513. radioFragment.innerHTML = radioHtml;
  6514. return radioFragment.firstChild;
  6515. },
  6516. _addItem: function (obj) {
  6517. var label = document.createElement('label'),
  6518. input,
  6519. checked = this._map.hasLayer(obj.layer);
  6520. if (obj.overlay) {
  6521. input = document.createElement('input');
  6522. input.type = 'checkbox';
  6523. input.className = 'leaflet-control-layers-selector';
  6524. input.defaultChecked = checked;
  6525. } else {
  6526. input = this._createRadioElement('leaflet-base-layers', checked);
  6527. }
  6528. input.layerId = L.stamp(obj.layer);
  6529. L.DomEvent.on(input, 'click', this._onInputClick, this);
  6530. var name = document.createElement('span');
  6531. name.innerHTML = ' ' + obj.name;
  6532. label.appendChild(input);
  6533. label.appendChild(name);
  6534. var container = obj.overlay ? this._overlaysList : this._baseLayersList;
  6535. container.appendChild(label);
  6536. return label;
  6537. },
  6538. _onInputClick: function () {
  6539. var i, input, obj,
  6540. inputs = this._form.getElementsByTagName('input'),
  6541. inputsLen = inputs.length;
  6542. this._handlingClick = true;
  6543. for (i = 0; i < inputsLen; i++) {
  6544. input = inputs[i];
  6545. obj = this._layers[input.layerId];
  6546. if (input.checked && !this._map.hasLayer(obj.layer)) {
  6547. this._map.addLayer(obj.layer);
  6548. } else if (!input.checked && this._map.hasLayer(obj.layer)) {
  6549. this._map.removeLayer(obj.layer);
  6550. }
  6551. }
  6552. this._handlingClick = false;
  6553. this._refocusOnMap();
  6554. },
  6555. _expand: function () {
  6556. L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
  6557. },
  6558. _collapse: function () {
  6559. this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
  6560. }
  6561. });
  6562. L.control.layers = function (baseLayers, overlays, options) {
  6563. return new L.Control.Layers(baseLayers, overlays, options);
  6564. };
  6565. /*
  6566. * L.PosAnimation is used by Leaflet internally for pan animations.
  6567. */
  6568. L.PosAnimation = L.Class.extend({
  6569. includes: L.Mixin.Events,
  6570. run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
  6571. this.stop();
  6572. this._el = el;
  6573. this._inProgress = true;
  6574. this._newPos = newPos;
  6575. this.fire('start');
  6576. el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
  6577. 's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
  6578. L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
  6579. L.DomUtil.setPosition(el, newPos);
  6580. // toggle reflow, Chrome flickers for some reason if you don't do this
  6581. L.Util.falseFn(el.offsetWidth);
  6582. // there's no native way to track value updates of transitioned properties, so we imitate this
  6583. this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
  6584. },
  6585. stop: function () {
  6586. if (!this._inProgress) { return; }
  6587. // if we just removed the transition property, the element would jump to its final position,
  6588. // so we need to make it stay at the current position
  6589. L.DomUtil.setPosition(this._el, this._getPos());
  6590. this._onTransitionEnd();
  6591. L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
  6592. },
  6593. _onStep: function () {
  6594. var stepPos = this._getPos();
  6595. if (!stepPos) {
  6596. this._onTransitionEnd();
  6597. return;
  6598. }
  6599. // jshint camelcase: false
  6600. // make L.DomUtil.getPosition return intermediate position value during animation
  6601. this._el._leaflet_pos = stepPos;
  6602. this.fire('step');
  6603. },
  6604. // you can't easily get intermediate values of properties animated with CSS3 Transitions,
  6605. // we need to parse computed style (in case of transform it returns matrix string)
  6606. _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
  6607. _getPos: function () {
  6608. var left, top, matches,
  6609. el = this._el,
  6610. style = window.getComputedStyle(el);
  6611. if (L.Browser.any3d) {
  6612. matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
  6613. if (!matches) { return; }
  6614. left = parseFloat(matches[1]);
  6615. top = parseFloat(matches[2]);
  6616. } else {
  6617. left = parseFloat(style.left);
  6618. top = parseFloat(style.top);
  6619. }
  6620. return new L.Point(left, top, true);
  6621. },
  6622. _onTransitionEnd: function () {
  6623. L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
  6624. if (!this._inProgress) { return; }
  6625. this._inProgress = false;
  6626. this._el.style[L.DomUtil.TRANSITION] = '';
  6627. // jshint camelcase: false
  6628. // make sure L.DomUtil.getPosition returns the final position value after animation
  6629. this._el._leaflet_pos = this._newPos;
  6630. clearInterval(this._stepTimer);
  6631. this.fire('step').fire('end');
  6632. }
  6633. });
  6634. /*
  6635. * Extends L.Map to handle panning animations.
  6636. */
  6637. L.Map.include({
  6638. setView: function (center, zoom, options) {
  6639. zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
  6640. center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
  6641. options = options || {};
  6642. if (this._panAnim) {
  6643. this._panAnim.stop();
  6644. }
  6645. if (this._loaded && !options.reset && options !== true) {
  6646. if (options.animate !== undefined) {
  6647. options.zoom = L.extend({animate: options.animate}, options.zoom);
  6648. options.pan = L.extend({animate: options.animate}, options.pan);
  6649. }
  6650. // try animating pan or zoom
  6651. var animated = (this._zoom !== zoom) ?
  6652. this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
  6653. this._tryAnimatedPan(center, options.pan);
  6654. if (animated) {
  6655. // prevent resize handler call, the view will refresh after animation anyway
  6656. clearTimeout(this._sizeTimer);
  6657. return this;
  6658. }
  6659. }
  6660. // animation didn't start, just reset the map view
  6661. this._resetView(center, zoom);
  6662. return this;
  6663. },
  6664. panBy: function (offset, options) {
  6665. offset = L.point(offset).round();
  6666. options = options || {};
  6667. if (!offset.x && !offset.y) {
  6668. return this;
  6669. }
  6670. if (!this._panAnim) {
  6671. this._panAnim = new L.PosAnimation();
  6672. this._panAnim.on({
  6673. 'step': this._onPanTransitionStep,
  6674. 'end': this._onPanTransitionEnd
  6675. }, this);
  6676. }
  6677. // don't fire movestart if animating inertia
  6678. if (!options.noMoveStart) {
  6679. this.fire('movestart');
  6680. }
  6681. // animate pan unless animate: false specified
  6682. if (options.animate !== false) {
  6683. L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
  6684. var newPos = this._getMapPanePos().subtract(offset);
  6685. this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
  6686. } else {
  6687. this._rawPanBy(offset);
  6688. this.fire('move').fire('moveend');
  6689. }
  6690. return this;
  6691. },
  6692. _onPanTransitionStep: function () {
  6693. this.fire('move');
  6694. },
  6695. _onPanTransitionEnd: function () {
  6696. L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
  6697. this.fire('moveend');
  6698. },
  6699. _tryAnimatedPan: function (center, options) {
  6700. // difference between the new and current centers in pixels
  6701. var offset = this._getCenterOffset(center)._floor();
  6702. // don't animate too far unless animate: true specified in options
  6703. if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
  6704. this.panBy(offset, options);
  6705. return true;
  6706. }
  6707. });
  6708. /*
  6709. * L.PosAnimation fallback implementation that powers Leaflet pan animations
  6710. * in browsers that don't support CSS3 Transitions.
  6711. */
  6712. L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
  6713. run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
  6714. this.stop();
  6715. this._el = el;
  6716. this._inProgress = true;
  6717. this._duration = duration || 0.25;
  6718. this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
  6719. this._startPos = L.DomUtil.getPosition(el);
  6720. this._offset = newPos.subtract(this._startPos);
  6721. this._startTime = +new Date();
  6722. this.fire('start');
  6723. this._animate();
  6724. },
  6725. stop: function () {
  6726. if (!this._inProgress) { return; }
  6727. this._step();
  6728. this._complete();
  6729. },
  6730. _animate: function () {
  6731. // animation loop
  6732. this._animId = L.Util.requestAnimFrame(this._animate, this);
  6733. this._step();
  6734. },
  6735. _step: function () {
  6736. var elapsed = (+new Date()) - this._startTime,
  6737. duration = this._duration * 1000;
  6738. if (elapsed < duration) {
  6739. this._runFrame(this._easeOut(elapsed / duration));
  6740. } else {
  6741. this._runFrame(1);
  6742. this._complete();
  6743. }
  6744. },
  6745. _runFrame: function (progress) {
  6746. var pos = this._startPos.add(this._offset.multiplyBy(progress));
  6747. L.DomUtil.setPosition(this._el, pos);
  6748. this.fire('step');
  6749. },
  6750. _complete: function () {
  6751. L.Util.cancelAnimFrame(this._animId);
  6752. this._inProgress = false;
  6753. this.fire('end');
  6754. },
  6755. _easeOut: function (t) {
  6756. return 1 - Math.pow(1 - t, this._easeOutPower);
  6757. }
  6758. });
  6759. /*
  6760. * Extends L.Map to handle zoom animations.
  6761. */
  6762. L.Map.mergeOptions({
  6763. zoomAnimation: true,
  6764. zoomAnimationThreshold: 4
  6765. });
  6766. if (L.DomUtil.TRANSITION) {
  6767. L.Map.addInitHook(function () {
  6768. // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
  6769. this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
  6770. L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
  6771. // zoom transitions run with the same duration for all layers, so if one of transitionend events
  6772. // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
  6773. if (this._zoomAnimated) {
  6774. L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
  6775. }
  6776. });
  6777. }
  6778. L.Map.include(!L.DomUtil.TRANSITION ? {} : {
  6779. _catchTransitionEnd: function (e) {
  6780. if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
  6781. this._onZoomTransitionEnd();
  6782. }
  6783. },
  6784. _nothingToAnimate: function () {
  6785. return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
  6786. },
  6787. _tryAnimatedZoom: function (center, zoom, options) {
  6788. if (this._animatingZoom) { return true; }
  6789. options = options || {};
  6790. // don't animate if disabled, not supported or zoom difference is too large
  6791. if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
  6792. Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
  6793. // offset is the pixel coords of the zoom origin relative to the current center
  6794. var scale = this.getZoomScale(zoom),
  6795. offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
  6796. origin = this._getCenterLayerPoint()._add(offset);
  6797. // don't animate if the zoom origin isn't within one screen from the current center, unless forced
  6798. if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
  6799. this
  6800. .fire('movestart')
  6801. .fire('zoomstart');
  6802. this._animateZoom(center, zoom, origin, scale, null, true);
  6803. return true;
  6804. },
  6805. _animateZoom: function (center, zoom, origin, scale, delta, backwards, forTouchZoom) {
  6806. if (!forTouchZoom) {
  6807. this._animatingZoom = true;
  6808. }
  6809. // put transform transition on all layers with leaflet-zoom-animated class
  6810. L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
  6811. // remember what center/zoom to set after animation
  6812. this._animateToCenter = center;
  6813. this._animateToZoom = zoom;
  6814. // disable any dragging during animation
  6815. if (L.Draggable) {
  6816. L.Draggable._disabled = true;
  6817. }
  6818. L.Util.requestAnimFrame(function () {
  6819. this.fire('zoomanim', {
  6820. center: center,
  6821. zoom: zoom,
  6822. origin: origin,
  6823. scale: scale,
  6824. delta: delta,
  6825. backwards: backwards
  6826. });
  6827. }, this);
  6828. },
  6829. _onZoomTransitionEnd: function () {
  6830. this._animatingZoom = false;
  6831. L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
  6832. this._resetView(this._animateToCenter, this._animateToZoom, true, true);
  6833. if (L.Draggable) {
  6834. L.Draggable._disabled = false;
  6835. }
  6836. }
  6837. });
  6838. /*
  6839. Zoom animation logic for L.TileLayer.
  6840. */
  6841. L.TileLayer.include({
  6842. _animateZoom: function (e) {
  6843. if (!this._animating) {
  6844. this._animating = true;
  6845. this._prepareBgBuffer();
  6846. }
  6847. var bg = this._bgBuffer,
  6848. transform = L.DomUtil.TRANSFORM,
  6849. initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
  6850. scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
  6851. bg.style[transform] = e.backwards ?
  6852. scaleStr + ' ' + initialTransform :
  6853. initialTransform + ' ' + scaleStr;
  6854. },
  6855. _endZoomAnim: function () {
  6856. var front = this._tileContainer,
  6857. bg = this._bgBuffer;
  6858. front.style.visibility = '';
  6859. front.parentNode.appendChild(front); // Bring to fore
  6860. // force reflow
  6861. L.Util.falseFn(bg.offsetWidth);
  6862. this._animating = false;
  6863. },
  6864. _clearBgBuffer: function () {
  6865. var map = this._map;
  6866. if (map && !map._animatingZoom && !map.touchZoom._zooming) {
  6867. this._bgBuffer.innerHTML = '';
  6868. this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
  6869. }
  6870. },
  6871. _prepareBgBuffer: function () {
  6872. var front = this._tileContainer,
  6873. bg = this._bgBuffer;
  6874. // if foreground layer doesn't have many tiles but bg layer does,
  6875. // keep the existing bg layer and just zoom it some more
  6876. var bgLoaded = this._getLoadedTilesPercentage(bg),
  6877. frontLoaded = this._getLoadedTilesPercentage(front);
  6878. if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
  6879. front.style.visibility = 'hidden';
  6880. this._stopLoadingImages(front);
  6881. return;
  6882. }
  6883. // prepare the buffer to become the front tile pane
  6884. bg.style.visibility = 'hidden';
  6885. bg.style[L.DomUtil.TRANSFORM] = '';
  6886. // switch out the current layer to be the new bg layer (and vice-versa)
  6887. this._tileContainer = bg;
  6888. bg = this._bgBuffer = front;
  6889. this._stopLoadingImages(bg);
  6890. //prevent bg buffer from clearing right after zoom
  6891. clearTimeout(this._clearBgBufferTimer);
  6892. },
  6893. _getLoadedTilesPercentage: function (container) {
  6894. var tiles = container.getElementsByTagName('img'),
  6895. i, len, count = 0;
  6896. for (i = 0, len = tiles.length; i < len; i++) {
  6897. if (tiles[i].complete) {
  6898. count++;
  6899. }
  6900. }
  6901. return count / len;
  6902. },
  6903. // stops loading all tiles in the background layer
  6904. _stopLoadingImages: function (container) {
  6905. var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
  6906. i, len, tile;
  6907. for (i = 0, len = tiles.length; i < len; i++) {
  6908. tile = tiles[i];
  6909. if (!tile.complete) {
  6910. tile.onload = L.Util.falseFn;
  6911. tile.onerror = L.Util.falseFn;
  6912. tile.src = L.Util.emptyImageUrl;
  6913. tile.parentNode.removeChild(tile);
  6914. }
  6915. }
  6916. }
  6917. });
  6918. /*
  6919. * Provides L.Map with convenient shortcuts for using browser geolocation features.
  6920. */
  6921. L.Map.include({
  6922. _defaultLocateOptions: {
  6923. watch: false,
  6924. setView: false,
  6925. maxZoom: Infinity,
  6926. timeout: 10000,
  6927. maximumAge: 0,
  6928. enableHighAccuracy: false
  6929. },
  6930. locate: function (/*Object*/ options) {
  6931. options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
  6932. if (!navigator.geolocation) {
  6933. this._handleGeolocationError({
  6934. code: 0,
  6935. message: 'Geolocation not supported.'
  6936. });
  6937. return this;
  6938. }
  6939. var onResponse = L.bind(this._handleGeolocationResponse, this),
  6940. onError = L.bind(this._handleGeolocationError, this);
  6941. if (options.watch) {
  6942. this._locationWatchId =
  6943. navigator.geolocation.watchPosition(onResponse, onError, options);
  6944. } else {
  6945. navigator.geolocation.getCurrentPosition(onResponse, onError, options);
  6946. }
  6947. return this;
  6948. },
  6949. stopLocate: function () {
  6950. if (navigator.geolocation) {
  6951. navigator.geolocation.clearWatch(this._locationWatchId);
  6952. }
  6953. if (this._locateOptions) {
  6954. this._locateOptions.setView = false;
  6955. }
  6956. return this;
  6957. },
  6958. _handleGeolocationError: function (error) {
  6959. var c = error.code,
  6960. message = error.message ||
  6961. (c === 1 ? 'permission denied' :
  6962. (c === 2 ? 'position unavailable' : 'timeout'));
  6963. if (this._locateOptions.setView && !this._loaded) {
  6964. this.fitWorld();
  6965. }
  6966. this.fire('locationerror', {
  6967. code: c,
  6968. message: 'Geolocation error: ' + message + '.'
  6969. });
  6970. },
  6971. _handleGeolocationResponse: function (pos) {
  6972. var lat = pos.coords.latitude,
  6973. lng = pos.coords.longitude,
  6974. latlng = new L.LatLng(lat, lng),
  6975. latAccuracy = 180 * pos.coords.accuracy / 40075017,
  6976. lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),
  6977. bounds = L.latLngBounds(
  6978. [lat - latAccuracy, lng - lngAccuracy],
  6979. [lat + latAccuracy, lng + lngAccuracy]),
  6980. options = this._locateOptions;
  6981. if (options.setView) {
  6982. var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
  6983. this.setView(latlng, zoom);
  6984. }
  6985. var data = {
  6986. latlng: latlng,
  6987. bounds: bounds,
  6988. timestamp: pos.timestamp
  6989. };
  6990. for (var i in pos.coords) {
  6991. if (typeof pos.coords[i] === 'number') {
  6992. data[i] = pos.coords[i];
  6993. }
  6994. }
  6995. this.fire('locationfound', data);
  6996. }
  6997. });
  6998. }(window, document));