Codebase list leaflet / upstream/0.6
Imported Upstream version 0.6 Jonas Smedegaard 10 years ago
154 changed file(s) with 18264 addition(s) and 12923 deletion(s). Raw diff Collapse all Expand all
0 node_modules
1 .DS_Store
2 tmp/**/*
3 .idea
4 .idea/**/*
5 *.iml
6 *.sublime-*
7 _site
8 coverage/
0 language: node_js
1 node_js:
2 - 0.10
22
33 (all changes without author notice are by [@mourner](https://github.com/mourner))
44
5 ## master
5 ## 0.7 (master)
66
77 An in-progress version being developed on the master branch.
8 No changes since the latest stable yet.
9
10 ## 0.6 (June 25, 2013)
11
12 ### Breaking changes
13
14 * Moved polyline editing code into [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw) plugin (where it fits much better along with all other editing and drawing handlers). The API remains the same.
15 * Dropped support for environments that augment `Object.prototype` (luckily it's a thing of the past nowadays).
16
17 ### Improvements
18
19 #### Usability and performance improvements
20
21 * **Improved zoom control design** once more - cleaner, simpler, more accessible (mostly by [@jacobtoye](https://github.com/jacobtoye)). [#1313](https://github.com/Leaflet/Leaflet/issues/1313)
22 * Updated `Control.Layers` icon (designed by Volker K), added retina version and SVG source. [#1739](https://github.com/Leaflet/Leaflet/issues/1739)
23 * Added keyboard accessibility to markers (you can now tab to them and press enter for click behavior). [#1355](https://github.com/Leaflet/Leaflet/issues/1355)
24 * Improved `TileLayer` zoom animation to eliminate flickering in case one tile layer on top of another or when zooming several times quickly (by [@mourner](https://github.com/mourner) with lots of fixes from [@danzel](https://github.com/danzel)). [#1140](https://github.com/Leaflet/Leaflet/issues/1140) [#1437](https://github.com/Leaflet/Leaflet/issues/1437) [#52](https://github.com/Leaflet/Leaflet/issues/52)
25 * Subtly improved default popup styles
26 * Significantly improved mass layer removal performance (by [@jfgirard](https://github.com/jfgirard) with fixes from [@danzel](https://github.com/danzel)). [#1141](https://github.com/Leaflet/Leaflet/pull/1141) [#1514](https://github.com/Leaflet/Leaflet/pull/1514)
27 * Improved zoom behavior so that there's no drift of coordinates when you change zoom back and forth without panning. [#426](https://github.com/Leaflet/Leaflet/issues/426)
28 * Improved double click behavior to zoom while keeping the clicked point fixed (by [@ansis](https://github.com/ansis)). [#1582](https://github.com/Leaflet/Leaflet/issues/1582)
29 * Improved attribution control to be much less obtrusive (no "powered by", just a Leaflet link). You can still remove the prefix with `map.attributionControl.setPrefix('')` if you need.
30 * Improved dragging behavior to not get stuck if mouse moved outside of an iframe containing the map (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1277](https://github.com/Leaflet/Leaflet/issues/1277) [#1782](https://github.com/Leaflet/Leaflet/issues/1782) [#1786](https://github.com/Leaflet/Leaflet/issues/1786)
31 * Improved box zoom to be cancelable by pressing Escape (by [@yohanboniface](https://github.com/yohanboniface)). [#1438](https://github.com/Leaflet/Leaflet/issues/1438)
32 * Improved `Marker` popups to close on marker click if opened (by [@popox](https://github.com/popox)). [#1761](https://github.com/Leaflet/Leaflet/issues/1761)
33
34 #### API improvements
35
36 ##### General API improvements
37
38 * Made Leaflet classes compatible with **CoffeeScript class inheritance** syntax (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1345](https://github.com/Leaflet/Leaflet/pull/1345) [#1314](https://github.com/Leaflet/Leaflet/issues/1314)
39 * Added `cleanAllEventListeners` method (aliased to `off` without arguments) to all events-enabled objects (by [@iirvine](https://github.com/iirvine)). [#1599](https://github.com/Leaflet/Leaflet/issues/1599)
40 * Added `addOneTimeEventListener` method (aliased to `once`) to all events-enabled objects (by [@iirvine](https://github.com/iirvine)). [#473](https://github.com/Leaflet/Leaflet/issues/473) [#1596](https://github.com/Leaflet/Leaflet/issues/1596)
41 * Added ability to pass coordinates as simple objects (`{lat: 50, lon: 30}` or `{lat: 50, lng: 30}`). [#1412](https://github.com/Leaflet/Leaflet/issues/1412)
42 * Added `LatLngBounds` `getNorth`, `getEast`, `getSouth`, `getWest` methods (by [@yohanboniface](https://github.com/yohanboniface)). [#1318](https://github.com/Leaflet/Leaflet/issues/1318)
43 * Added `AMD` support (Leaflet now registers itself as a `leaflet` AMD module) (with fixes from [@sheppard](https://github.com/sheppard)). [#1364](https://github.com/Leaflet/Leaflet/issues/1364) [#1778](https://github.com/Leaflet/Leaflet/issues/1778)
44 * Added `L.Util.trim` function (by [@kristerkari](https://github.com/kristerkari)). [#1607](https://github.com/Leaflet/Leaflet/pull/1607)
45
46 ##### Layers API improvements
47
48 * Added `toGeoJSON` method to various layer types, allowing you to **save your Leaflet layers as GeoJSON**. (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1462](https://github.com/Leaflet/Leaflet/issues/1462) [#712](https://github.com/Leaflet/Leaflet/issues/712) [#1779](https://github.com/Leaflet/Leaflet/issues/1779)
49 * Improved `Marker` to reuse icon DOM elements when changing icons on the fly (e.g. fixes problems when changing icon on mouse hover) (by [@robpvn](https://github.com/robpvn) & [@danzel](https://github.com/danzel)). [#1726](https://github.com/Leaflet/Leaflet/issues/1726) [#561](https://github.com/Leaflet/Leaflet/issues/561) [#1753](https://github.com/Leaflet/Leaflet/issues/1753) [#1754](https://github.com/Leaflet/Leaflet/pull/1754)
50 * Added `LayerGroup` `hasLayer` method (by [@rvlasveld](https://github.com/rvlasveld)). [#1282](https://github.com/Leaflet/Leaflet/issues/1282) [#1300](https://github.com/Leaflet/Leaflet/pull/1300)
51 * Added `LayerGroup` `getLayers` method (by [@tmcw](https://github.com/tmcw)). [#1469](https://github.com/Leaflet/Leaflet/pull/1469)
52 * Added `LayerGroup` `getLayer` method (by [@gumballhead](https://github.com/gumballhead)). [#1650](https://github.com/Leaflet/Leaflet/pull/1650)
53 * Improved `LayerGroup` `removeLayer` method to also accept layer `id` (by [@gumballhead](https://github.com/gumballhead)). [#1642](https://github.com/Leaflet/Leaflet/pull/1642)
54 * Improved `Marker` and `Path` `bindPopup` method to also accept `Popup` objects (by [@snkashis](https://github.com/snkashis)). [#1385](https://github.com/Leaflet/Leaflet/pull/1385) [#1208](https://github.com/Leaflet/Leaflet/issues/1208) [#1402](https://github.com/Leaflet/Leaflet/pull/1402)
55 * Added `Marker` `setPopupContent` method (by [@snkashis](https://github.com/snkashis)). [#1373](https://github.com/Leaflet/Leaflet/pull/1373)
56 * Added `Path` `pointerEvents` option for setting pointer-events on SVG-powered vector layers (by [@inpursuit](https://github.com/inpursuit)). [#1053](https://github.com/Leaflet/Leaflet/pull/1053)
57 * Improved `Polygon` to filter out last point if it's equal to the first one (to fix GeoJSON polygon issues) (by [@snkashis](https://github.com/snkashis)). [#1153](https://github.com/Leaflet/Leaflet/pull/1153) [#1135](https://github.com/Leaflet/Leaflet/issues/1135)
58 * Improved `L.Util.template` (and correspondingly url-related `TileLayer` options) to support functions for data values (by [@olegsmith](https://github.com/olegsmith)). [#1554](https://github.com/Leaflet/Leaflet/pull/1554)
59 * Added `TileLayer` `getContainer` method (by [@tmcw](https://github.com/tmcw)). [#1433](https://github.com/Leaflet/Leaflet/pull/1433)
60 * Fixed `TileLayer.Canvas` `redraw` method chaining (by [@jieter](https://github.com/jieter)). [#1287](https://github.com/Leaflet/Leaflet/pull/1287)
61 * Added `latlng` property to `Marker` mouse event data. [#1613](https://github.com/Leaflet/Leaflet/issues/1613)
62 * Added `popupopen` and `popupclose` events to various layers (by [@Koc](https://github.com/Koc)). [#738](https://github.com/Leaflet/Leaflet/pull/738)
63 * Added `GeoJSON` `coordsToLatLng` option for dealing with GeoJSON that has non-WGS84 coords (thanks to [@moonlite](https://github.com/moonlite)). [#888](https://github.com/Leaflet/Leaflet/issues/888) [#886](https://github.com/Leaflet/Leaflet/issues/886)
64 * Added `Popup` `keepInView` option (thanks to [@lapo-luchini](https://github.com/lapo-luchini)) that prevents the popup from going off-screen while it's opened. [#1308](https://github.com/Leaflet/Leaflet/pull/1308) [#1052](https://github.com/Leaflet/Leaflet/issues/1052)
65 * Added `Marker` `togglePopup` method (by [@popox](https://github.com/popox)). [#1761](https://github.com/Leaflet/Leaflet/issues/1761)
66 * Added `Popup` `closeOnClick` option that overrides the same `Map` option for specific popups (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1669](https://github.com/Leaflet/Leaflet/issues/1669)
67 * Added `TileLayer.WMS` `crs` option to be able to use WMS of CRS other than the map CRS (by [@kengu](https://github.com/kengu)). [#942](https://github.com/Leaflet/Leaflet/issues/942) [#945](https://github.com/Leaflet/Leaflet/issues/945)
68 * Improved paths with `clickable: false` to allow mouse events to pass through to objects underneath (by [@snkashis](https://github.com/snkashis)). [#1384](https://github.com/Leaflet/Leaflet/pull/1384) [#1281](https://github.com/Leaflet/Leaflet/issues/1281)
69
70 ##### Map API improvements
71
72 * Improved all view changing methods of `Map` (`setView`, `panTo`, `setZoom`, `fitBounds`, etc.) to accept an options object, including the ability to precisely control zoom/pan animations they cause (force disable/enable, etc.). [#1617](https://github.com/Leaflet/Leaflet/pull/1617) [#1616](https://github.com/Leaflet/Leaflet/issues/1616) [#340](https://github.com/Leaflet/Leaflet/issues/340) [#483](https://github.com/Leaflet/Leaflet/issues/483) [#1164](https://github.com/Leaflet/Leaflet/issues/1164) [#1420](https://github.com/Leaflet/Leaflet/issues/1420)
73 * Improved `Map` `invalidateSize` to accept options object (`animate` and `pan`, the latter controls if it pans the map on size change). (by [@jacobtoye](https://github.com/jacobtoye) and [@mourner](https://github.com/mourner)). [#1766](https://github.com/Leaflet/Leaflet/issues/1766) [#1767](https://github.com/Leaflet/Leaflet/issues/1767)
74 * Improved `Map` `fitBounds` method to accept `padding` (or `paddingTopLeft` and `paddingBottomRight`) options, allowing you to zoom to an area with a certain padding in pixels (usually left for controls). [#859](https://github.com/Leaflet/Leaflet/issues/859)
75 * Added `Map` `remove` method to properly destroy the map and clean up all events (by [@jfirebaugh](https://github.com/jfirebaugh) and [@mourner](https://github.com/mourner)). [#1434](https://github.com/Leaflet/Leaflet/issues/1434) [#1101](https://github.com/Leaflet/Leaflet/issues/1101) [#1621](https://github.com/Leaflet/Leaflet/issues/1621)
76 * Added `Map` `tap` handler that now contains all mobile hacks for enabling quick taps and long holds and `tapTolerance` option specifying the number of pixels you can shift your finger for click to still fire.
77 * Added `Map` `zoomlevelschange` event that triggers when the current zoom range (min/max) changes (by [@moonlite](https://github.com/moonlite)). [#1376](https://github.com/Leaflet/Leaflet/pull/1376)
78 * Added `Map` `setZoomAround` method for zooming while keeping a certain point fixed (used by scroll and double-click zooming). [#1157](https://github.com/Leaflet/Leaflet/issues/1157)
79 * Improved `Map` to throw exception if the specified container id is not found (by [@tmcw](htts://github.com/tmcw)). [#1574](https://github.com/Leaflet/Leaflet/pull/1574)
80 * Improved `Map` `locationfound` event to pass all location data (heading, speed, etc.). [#984](https://github.com/Leaflet/Leaflet/issues/984) [#584](https://github.com/Leaflet/Leaflet/issues/584) [#987](https://github.com/Leaflet/Leaflet/issues/987) [#1028](https://github.com/Leaflet/Leaflet/issues/1028)
81 * Improved `Map` `closePopup` method to optionally accept a popup object to close. [#1641](https://github.com/Leaflet/Leaflet/issues/1641)
82 * Improved `Map` `stopLocate` method to abort resetting map view if calling `locate` with `setView` option. [#747](https://github.com/Leaflet/Leaflet/issues/747)
83 * Improved `Map` `openPopup` method to also accept `(content, latlng)` signature as a shortcut.
84 * Added `Map` `resize` event. [#1564](https://github.com/Leaflet/Leaflet/issues/1564)
85
86 ##### Controls API improvements
87
88 * Added **generic toolbar classes** for reuse by plugin developers (used by zoom control).
89 * Added `Map` `baselayerchange`, `overlayadd` and `overlayremove` events fired by `Control.Layers` (by [@calvinmetcalf](https://github.com/calvinmetcalf) and [@Xelio](https://github.com/Xelio)). [#1286](https://github.com/Leaflet/Leaflet/issues/1286) [#1634](https://github.com/Leaflet/Leaflet/issues/1634)
90 * Added `Control` `getContainer` method. [#1409](https://github.com/Leaflet/Leaflet/issues/1409)
91
92 #### Development workflow improvements
93
94 * Switched from Jasmine to [Mocha](http://visionmedia.github.io/mocha/) with Expect.js (matchers) and Sinon (spies) for tests (by [@tmcw](https://github.com/tmcw) & [@jfirebaugh](https://github.com/jfirebaugh)). [#1479](https://github.com/Leaflet/Leaflet/issues/1479)
95 * Added [Karma](http://karma-runner.github.io) integration for running tests in a headless PhantomJS instance and code coverage reporting (by [@edjafarov](https://github.com/edjafarov)). [#1326](https://github.com/Leaflet/Leaflet/issues/1326) [#1340](https://github.com/Leaflet/Leaflet/pull/1340)
96 * Added [Travis CI integration](https://travis-ci.org/Leaflet/Leaflet) for linting and running tests for each commit and pull request automatically (by [@edjafarov](https://github.com/edjafarov)). [#1336](https://github.com/Leaflet/Leaflet/issues/1336)
97 * Added compatibility with lazy evaluation scripts (by [@kristerkari](https://github.com/kristerkari)). [#1288](https://github.com/Leaflet/Leaflet/issues/1288) [#1607](https://github.com/Leaflet/Leaflet/issues/1607) [#1288](https://github.com/Leaflet/Leaflet/issues/1288)
98
99 ### Bugfixes
100
101 #### General bugfixes
102
103 * Fixed lots of issues with extent restriction by `Map` `maxBounds`. [#1491](https://github.com/Leaflet/Leaflet/issues/1491) [#1475](https://github.com/Leaflet/Leaflet/issues/1475) [#1194](https://github.com/Leaflet/Leaflet/issues/1194) [#900](https://github.com/Leaflet/Leaflet/issues/900) [#1333](https://github.com/Leaflet/Leaflet/issues/1333)
104 * Fixed occasional crashes by disabling zoom animation if changing zoom level by more than 4 levels.. [#1377](https://github.com/Leaflet/Leaflet/issues/1377)
105 * Fixed a bug with that caused stuttery keyboard panning in some cases (by [@tmcw](https://github.com/tmcw)). [#1710](https://github.com/Leaflet/Leaflet/issues/1710)
106 * Fixed a bug that caused unwanted scrolling of the page to the top of the map on focus. [#1228](https://github.com/Leaflet/Leaflet/issues/1228) [#1540](https://github.com/Leaflet/Leaflet/issues/1540)
107 * Fixed a bug where clicking on a marker with an open popup caused the popup to faded in again (by [@snkashis](https://github.com/snkashis)). [#506](https://github.com/Leaflet/Leaflet/issues/560) [#1386](https://github.com/Leaflet/Leaflet/pull/1386)
108 * Fixed a bug where zoom buttons disabled state didn't update on min/max zoom change (by [@snkashis](https://github.com/snkashis)). [#1372](https://github.com/Leaflet/Leaflet/pull/1372) [#1328](https://github.com/Leaflet/Leaflet/issues/1328)
109 * Fixed a bug where scrolling slightly wouldn't always zoom out the map (by [@cschwarz](https://github.com/cschwarz)). [#1575](https://github.com/Leaflet/Leaflet/pull/1575)
110 * Fixed popup close button to not leave an outline after clicking on it and reopening the popup (by [@dotCypress](https://github.com/dotCypress)). [#1537](https://github.com/Leaflet/Leaflet/pull/1537)
111 * Fixed a bug that prevented tiles from loading during pan animation.
112 * Fixed a bug with `contextmenu` events on popups falling through to map (by [@snkashis](https://github.com/snkashis)). [#1730](https://github.com/Leaflet/Leaflet/issues/1730) [#1732](https://github.com/Leaflet/Leaflet/issues/1732)
113 * Fixed `404` tile loading errors when browsing the map off the world bounds.
114 * Fixed shifted mouse events in some cases with map inside a relatively positioned parent (by [@scooterw](https://github.com/scooterw) and [@jec006](https://github.com/jec006)). [#1670](https://github.com/Leaflet/Leaflet/issues/1670) [#1684](https://github.com/Leaflet/Leaflet/issues/1684) [#1745](https://github.com/Leaflet/Leaflet/issues/1745) [#1744](https://github.com/Leaflet/Leaflet/issues/1744)
115
116 #### Browser bugfixes
117
118 * Fixed a bug with undesirable page scrolling when you zoom the map by scrolling in Firefox 17+ (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1789](https://github.com/Leaflet/Leaflet/issues/1789) [#1788](https://github.com/Leaflet/Leaflet/issues/1788)
119 * Fixed a bug where `TileLayer` opacity didn't work in IE 7-8 (by [@javisantana](https://github.com/javisantana) & [@danzel](https://gi
120 .com/danzel)). [#1084](https://github.com/Leaflet/Leaflet/issues/1084) [#1396](https://github.com/Leaflet/Leaflet/pull/1396) [#1371](https://github.com/Leaflet/Leaflet/issues/1371)
121 * Fixed a bug in Android where click was triggered twice on one tap (by [@jerel](https://github.com/jerel) & [@mourner](https://github.com/mourner)). [#1227](https://github.com/Leaflet/Leaflet/pull/1227) [#1263](https://github.com/Leaflet/Leaflet/issues/1263) [#1785](https://github.com/Leaflet/Leaflet/issues/1785) [#1694](https://github.com/Leaflet/Leaflet/issues/1694)
122 * Fixed a bug in Android where click on a collapsed layers control would immediately select one of the layers (by [@danzel](https://github.com/danzel)). [#1784](https://github.com/Leaflet/Leaflet/issues/1784) [#1694](https://github.com/Leaflet/Leaflet/issues/1694)
123 * Fixed a bug where mouse coordinates where shifted in Firefox if the map was inside a positioned block on a scrolled page (by [@joschka](https://github.com/joschka)). [#1365](https://github.com/Leaflet/Leaflet/pull/1365) [#1322](https://github.com/Leaflet/Leaflet/issues/1322)
124 * Fixed a bug where box zoom didn't work in some cases in Firefox 18+ (by [@fabriceds](https://github.com/fabriceds)). [#1405](https://github.com/Leaflet/Leaflet/pull/1405)
125 * Fixed a bug where tile layer z-index order sometimes broke after view reset. [#1422](https://github.com/Leaflet/Leaflet/issues/1422)
126 * Fixed Leaflet not working correctly in PhantomJS (by [@rassie](https://github.com/rassie)). [#1501](https://github.com/Leaflet/Leaflet/pull/1501)
127 * Fixed a bug that prevented the layers control from working correctly on WinPhone8/IE10 Touch (by [@danzel](https://github.com/danzel)). [#1635](https://github.com/Leaflet/Leaflet/pull/1635) [#1539](https://github.com/Leaflet/Leaflet/issues/1539)
128
129 #### API bugfixes
130
131 ##### General API bugfixes
132
133 * Fixed compatibility with Browserify (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1572](https://github.com/Leaflet/Leaflet/pull/1572)
134 * Fixed click mouse event inside popups not propagating outside the map (fixes issues with jQuery.live and some mobile frameworks that rely on document click listeners). [#301](https://github.com/Leaflet/Leaflet/issues/301)
135 * Fixed a bug where event listener still fired even if it was removed on the same event in other listener (by [@spamdaemon](https://github.com/spamdaemon)). [#1661](https://github.com/Leaflet/Leaflet/issues/1661) [#1654](https://github.com/Leaflet/Leaflet/issues/1654)
136 * Fixed a bug where `L.point` and `L.latLng` factories weren't passing `null` and `undefined` values through.
137 * Fixed `DomEvent` `removeListener` function chaining (by [@pagameba](https://github.com/pagameba)).
138 * Fixed a bug where `removeEventListener` would throw an error if no events are registered on the object (by [@tjoekbezoer](https://github.com/tjoekbezoer)). [#1632](https://github.com/Leaflet/Leaflet/pull/1632) [#1631](https://github.com/Leaflet/Leaflet/issues/1631)
139 * Fixed a bug where `Point` `equals` and `contains` methods didn't accept points in array form.
140 * Fixed a bug where `LatLngBounds` `extend` of an undefined object would cause an error (by [@korzhyk](https://github.com/korzhyk)). [#1688](https://github.com/Leaflet/Leaflet/issues/1688)
141 * Fixed a bug where `Control.Attribution` `removeAttribution` of inexistant attribution corrupted the attribution text. [#1410](https://github.com/Leaflet/Leaflet/issues/1410)
142 * Fixed a bug where `setView` on an invisible map caused an error (by [@jfire](https://github.com/jfire)). [#1707](https://github.com/Leaflet/Leaflet/issues/1707)
143
144 ##### Layers API bugfixes
145
146 * Fixed a bug where default marker icon path wasn't properly detected in some cases in IE6-7 (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1294](https://github.com/Leaflet/Leaflet/pull/1294)
147 * Fixed a bug where `TileLayer.WMS` param values weren't escaped in URLs (by [@yohanboniface](https://github.com/yohanboniface)). [#1317](https://github.com/Leaflet/Leaflet/issues/1317)
148 * Fixed a bug where layers that belong to multiple feature groups didn't propagate events correctly (by [@danzel](https://github.com/danzel)). [#1359](https://github.com/Leaflet/Leaflet/pull/1359)
149 * Fixed a bug where `TileLayer.WMS` `tileSize` option was ignored (by [@brianhatchl](https://github.com/brianhatchl)). [#1080](https://github.com/brianhatchl)
150 * Fixed a bug where `Polyline` constructor could overwrite the source array (by [@snkashis](https://github.com/snkashis) and [@danzel](https://github.com/danzel)). [#1439](https://github.com/Leaflet/Leaflet/pull/1439) [#1092](https://github.com/Leaflet/Leaflet/issues/1092) [#1246](https://github.com/Leaflet/Leaflet/issues/1246) [#1426](https://github.com/Leaflet/Leaflet/issues/1426)
151 * Fixed a bug where marker dragging disabling/enabling wouldn't always work correctly (by [@snkashis](https://github.com/snkashis) and [@escaped](https://github.com/escaped)). [#1471](https://github.com/Leaflet/Leaflet/pull/1471) [#1551](https://github.com/Leaflet/Leaflet/pull/1551)
152 * Fixed `TileLayer` to prevent incorrect subdomain in case of negative tile coordinates (by [@malexeev](https://github.com/malexeev)). [#1532](https://github.com/Leaflet/Leaflet/pull/1532)
153 * Fixed polygons to normalize holes (remove last point if it's equal to the first one) (by [@jfirebaugh](https://github.com/jfirebaugh)). [#](https://github.com/Leaflet/Leaflet/pull/1467) [#1459](https://github.com/Leaflet/Leaflet/issues/1459)
154 * Fixed `DivIcon` `html` option to accept `0` as a value (by [@stuporglue](https://github.com/stuporglue)). [#1633](https://github.com/Leaflet/Leaflet/pull/1633)
155 * Fixed a bug with Canvas-based paths throwing an error on `mousemove` in certain conditions. [#1615](https://github.com/Leaflet/Leaflet/issues/1615)
156 * Fixed a bug where copies of the world wouldn't load if you set `TileLayer` `bounds` (by [@ansis](https://github.com/ansis)). [#1618](https://github.com/Leaflet/Leaflet/issues/1618)
157 * Fixed a bug where `TileLayer` `load` event wouldn't always fire correctly. [#1565](https://github.com/Leaflet/Leaflet/issues/1565)
158 * Fixed `TileLayer.WMS` compatibility with some old servers that only accepted request parameters in uppercase. [#1751](https://github.com/Leaflet/Leaflet/issues/1751)
159 * Fixed a bug with incorrect `L.Icon.Default.imagePath` detection in some cases. [#1657](https://github.com/Leaflet/Leaflet/issues/1657)
160 * Fixed a bug where layer `onRemove` was still called even if it was never added (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1729](https://github.com/Leaflet/Leaflet/issues/1729)
161 * Fixed a bug where calling `setRadius` on a Canvas-powered `CircleMarker` would cause an infinite loop (by [@snkashis](https://github.com/snkashis)). [#1712](https://github.com/Leaflet/Leaflet/issues/1712) [#1713](https://github.com/Leaflet/Leaflet/issues/1713) [#1728](https://github.com/Leaflet/Leaflet/issues/1728)
162 * Renamed `marker-icon@2x.png` to `marker-icon-2x.png` to fix compatibility with Google AppEngine. [#1552](https://github.com/Leaflet/Leaflet/issues/1552) [#1553](https://github.com/Leaflet/Leaflet/issues/1553)
163 * Fixed a bug where `popupclose` and `popupopen` events weren't fired for multipolygons and multipolylines (by [@tmcw](https://github.com/tmcw)). [#1681](https://github.com/Leaflet/Leaflet/issues/1681)
164
165 ##### Map API bugfixes
166
167 * Fixed a bug where `Map` `fitBounds` wouldn't work correctly with large bounds (thanks to [@MaZderMind](https://github.com/MaZderMind)). [#1069](https://github.com/Leaflet/Leaflet/issues/1069)
168 * Fixed a bug where `Map` `hasLayer` wasn't handling `null` objects (by [@rvlasveld](https://github.com/rvlasveld)). [#1282](https://github.com/Leaflet/Leaflet/issues/1282) [#1302](https://github.com/Leaflet/Leaflet/pull/1302)
169 * Fixed a bug where `Map` `moveend` fired before `dragend` on drag (by [@oslek](https://github.com/oslek)). [#1374](https://github.com/Leaflet/Leaflet/pull/1374)
170 * Fixed a bug where panning with inertia produced an excessive `Map` `movestart` event on inertia start (by [@oslek](https://github.com/oslek)). [#1374](https://github.com/Leaflet/Leaflet/pull/1374)
171 * Fixed a bug where `Map` `moveend` fired repeatedly on window resize even if the actual map size didn't change (by [@oslek](https://github.com/oslek)). [#1374](https://github.com/Leaflet/Leaflet/pull/1374)
172 * Fixed a bug where `Map` `moveend` sometimes wasn't fired after drag (particularly often when dragging with a trackpad).
173 * Fixed a bug that would cause an error when trying to get the state of the map in a `Map` `load` event listener. [#962](https://github.com/Leaflet/Leaflet/issues/962)
174 * Added `Map` `autopanstart` event back (it was removed occasionally in previous version). [#1375](https://github.com/Leaflet/Leaflet/issues/1375)
175 * Fixed a bug with removing previously set `Map` `maxBounds` (by [@jec006](https://github.com/jec006)). [#1749](https://github.com/Leaflet/Leaflet/issues/1749) [#1750](https://github.com/Leaflet/Leaflet/issues/1750)
176
177
178 ## 0.5.1 (February 6, 2013)
179
180 * Fixed a regression with `GeoJSON` not accepting arrays of `FeatureCollection` (by [@snkashis](https://github.com/snkashis)). [#1299](https://github.com/Leaflet/Leaflet/pull/1299) [#1298](https://github.com/Leaflet/Leaflet/issues/1298)
181 * Fixed a regression with `CirleMarker` `setRadius` not working if called before adding the layer to the map (by [@danzel](https://github.com/danzel)). [#1342](https://github.com/Leaflet/Leaflet/issues/1342) [#1297](https://github.com/Leaflet/Leaflet/issues/1297)
182
183 ## 0.5 (January 17, 2013)
8184
9185 ### Breaking changes
10186
66242 * Added `L.extend`, `L.bind`, `L.stamp`, `L.setOptions` shortcuts for corresponding `L.Util` methods.
67243 * Disabled clearing of map container contents on map initialization (as a result of fixing [#278](https://github.com/Leaflet/Leaflet/issues/278)).
68244 * Added `L.Util.isArray` function (by [@oslek](https://github.com/oslek)). [#1279](https://github.com/Leaflet/Leaflet/pull/1279)
245 * Added `mouseover` and `mouseout` events to canvas-based vector layers (by [@snkashis](https://github.com/snkashis)). [#1403](https://github.com/Leaflet/Leaflet/pull/1403)
246 * Added `Map` `eachLayer` to iterate over all layers added to the map (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1457](https://github.com/Leaflet/Leaflet/pull/1457)
247 * Added `TileLayer` `bounds` option to limit tile loading to a specific region (by [@adimitrov](https://github.com/adimitrov)). [#991](https://github.com/Leaflet/Leaflet/pull/991)
69248
70249 ### Bugfixes
71250
107286 * Fixed a bug where removing a polyline in editing state wouldn't clean up the editing handles (by [@mehmeta](https://github.com/mehmeta)). [#1233](https://github.com/Leaflet/Leaflet/pull/1233)
108287 * Fixed a bug where removing a vector layer with a bound popup wouldn't clean up its click event properly (by [@yohanboniface](https://github.com/yohanboniface)). [#1229](https://github.com/Leaflet/Leaflet/pull/1229)
109288 * Fixed a bug where `GeoJSON` features with `GeometryCollection` didn't pass properties to `pointToLayer` function (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1097](https://github.com/Leaflet/Leaflet/pull/1097)
289 * Fixed `FeatureGroup` `eachLayer` chaining. [#1452](https://github.com/Leaflet/Leaflet/issues/1452)
110290
111291 #### Browser bugfixes
112292
3636 * *Bonus tip:* if the bug only appears in the master version but the stable version is fine,
3737 use `git bisect` to find the exact commit that introduced the bug.
3838
39 If you just want some help with your project,
40 try asking [on the Leaflet forum](https://groups.google.com/forum/#!forum/leaflet-js) instead.
41
3942 ## Contributing Code
4043
4144 ### Considerations for Accepting Patches
5962 ### Setting up the Build System
6063
6164 To set up the Leaflet build system, install [Node](http://nodejs.org/),
62 then run the following commands in the project root (with superuser permissions):
65 then run the following commands in the project root:
6366
6467 ```
6568 npm install -g jake
66 npm install jshint
67 npm install uglify-js
69 npm install
6870 ```
6971
7072 You can build minified Leaflet by running `jake` (it will be built from source in the `dist` folder).
73 For a custom build with selected components, open `build/build.html` in the browser and follow the instructions from there.
7174
7275 ### Making Changes to Leaflet Source
7376
7982 Please do not commit to the `master` branch, or your unrelated changes will go into the same pull request.
8083
8184 You should also follow the code style and whitespace conventions of the original codebase.
85 In particular, use tabs for indentation and spaces for alignment.
8286
8387 Before commiting your changes, run `jake lint` to catch any JS errors in the code and fix them.
8488 If you add any new files to the Leaflet source, make sure to also add them to `build/deps.js`
8589 so that the build system knows about them.
8690
87 But please **do not commit the built files** (`leaflet.js` and `leaflet-src.js`) along with your changes,
88 otherwise there may be problems merging the pull request.
89 These files are only commited in the `master` branch of the main Leaflet repository.
91 Also, please make sure that you have [line endings configured properly](https://help.github.com/articles/dealing-with-line-endings) in Git! Otherwise the diff will show that all lines of a file were changed even if you touched only one.
9092
9193 Happy coding!
94
95 ## Running the Tests
96
97 To run the tests from the command line,
98 install [PhantomJS](http://phantomjs.org/) (and make sure it's in your `PATH`),
99 then run:
100
101 ```
102 jake test
103 ```
104
105 To run all the tests in actual browsers at the same time, you can do:
106
107 ```
108 jake test --ff --chrome --safari --ie
109 ```
110
111 To run the tests in a browser manually, open `spec/index.html`.
112
113 ## Code Coverage
114
115 To generate a detailed report about test coverage (which helps tremendously when working on test improvements), run:
116
117 ```
118 jake test --cov
119 ```
120
121 After that, open `spec/coverage/<environment>/index.html` in a browser to see the report.
122 From there you can click through folders/files to get details on their individual coverage.
92123
93124 ## Improving Documentation
94125
106137
107138 1. [Install Ruby](http://www.ruby-lang.org/en/) if don't have it yet.
108139 2. Run `gem install jekyll`.
109 3. Run `jekyll --auto` inside the `Leaflet` folder.
110 4. Open the website from the `_site` folder.
140 3. Run `jekyll serve --watch` in the root `Leaflet` folder.
141 4. Open `localhost:4000` in your browser.
111142
112 Now any file changes will be reflected on the generated pages automatically.
143 Now any file changes will be updated when you reload pages automatically.
113144 After commiting the changes, just send a pull request.
114145
115146 If you need to update documentation according to a new feature that only appeared in the master version (not stable one),
00 /*
1 Leaflet building and linting scripts.
1 Leaflet building, testing and linting scripts.
22
33 To use, install Node, then run the following commands in the project root:
44
55 npm install -g jake
6 npm install uglify-js
7 npm install jshint
6 npm install
87
9 To check the code and build Leaflet from source, run "jake"
8 To check the code for errors and build Leaflet from source, run "jake".
9 To run the tests, run "jake test".
1010
1111 For a custom build, open build/build.html in the browser and follow the instructions.
1212 */
1919 desc('Combine and compress Leaflet source files');
2020 task('build', ['lint'], build.build);
2121
22 desc('Run PhantomJS tests');
23 task('test', ['lint'], build.test);
24
2225 task('default', ['build']);
1616 If you want to **get involved** with Leaflet development, check out the [contribution guide][contribute].
1717 Let's make the best open-source library for maps that can possibly exist!
1818
19 [![Build Status](https://travis-ci.org/Leaflet/Leaflet.png?branch=master)](https://travis-ci.org/Leaflet/Leaflet)
20
1921 [Vladimir Agafonkin]: http://agafonkin.com/en
2022 [contributors]: https://github.com/Leaflet/Leaflet/graphs/contributors
2123 [features]: http://leafletjs.com/features.html
33
44 deps = require('./deps.js').deps,
55 hintrc = require('./hintrc.js').config;
6
76
87 function lintFiles(files) {
98
1211
1312 for (i = 0, len = files.length; i < len; i++) {
1413
15 jshint.JSHINT(fs.readFileSync(files[i], 'utf8'), hintrc);
14 jshint.JSHINT(fs.readFileSync(files[i], 'utf8'), hintrc, i ? {L: true} : null);
1615 errors = jshint.JSHINT.errors;
1716
1817 for (j = 0, len2 = errors.length; j < len2; j++) {
3231
3332 if (compsBase32) {
3433 comps = parseInt(compsBase32, 32).toString(2).split('');
35 console.log('Managing dependencies...')
34 console.log('Managing dependencies...');
3635 }
3736
3837 function addFiles(srcs) {
6261
6362 return files;
6463 }
64
65 exports.getFiles = getFiles;
6566
6667 exports.lint = function () {
6768
115116
116117 var copy = fs.readFileSync('src/copyright.js', 'utf8'),
117118 intro = '(function (window, document, undefined) {',
118 outro = '}(this, document));',
119 outro = '}(window, document));',
119120 newSrc = copy + intro + combineFiles(files) + outro,
120121
121122 pathPart = 'dist/leaflet' + (buildName ? '-' + buildName : ''),
152153 console.log('\tSaved to ' + path);
153154 }
154155 };
156
157 exports.test = function() {
158 var karma = require('karma'),
159 testConfig = {configFile : __dirname + '/../spec/karma.conf.js'};
160
161 testConfig.browsers = ['PhantomJS'];
162
163 if (isArgv('--chrome')) {
164 testConfig.browsers.push('Chrome');
165 }
166 if (isArgv('--safari')) {
167 testConfig.browsers.push('Safari');
168 }
169 if (isArgv('--ff')) {
170 testConfig.browsers.push('Firefox');
171 }
172 if (isArgv('--ie')) {
173 testConfig.browsers.push('IE');
174 }
175
176 if (isArgv('--cov')) {
177 testConfig.preprocessors = {
178 '../src/**/*.js': 'coverage'
179 };
180 testConfig.coverageReporter = {
181 type : 'html',
182 dir : 'coverage/'
183 };
184 testConfig.reporters = ['coverage'];
185 }
186
187 karma.server.start(testConfig);
188
189 function isArgv(optName) {
190 return process.argv.indexOf(optName) !== -1;
191 }
192 };
2121 desc: 'The core of the library, including OOP, events, DOM facilities, basic units, projections (EPSG:3857 and EPSG:4326) and the base Map class.'
2222 },
2323
24
2524 EPSG3395: {
2625 src: ['geo/projection/Projection.Mercator.js',
2726 'geo/crs/CRS.EPSG3395.js'],
6766
6867 Popup: {
6968 src: ['layer/Popup.js',
70 'layer/marker/Marker.Popup.js',
71 'map/ext/Map.Popup.js'],
69 'layer/marker/Marker.Popup.js'],
7270 deps: ['Marker'],
7371 desc: 'Used to display the map popup (used mostly for binding HTML data to markers and paths on click).'
7472 },
8381 deps: ['LayerGroup', 'Popup'],
8482 desc: 'Extends LayerGroup with mouse events and bindPopup method shared between layers.'
8583 },
86
8784
8885 Path: {
8986 src: ['layer/vector/Path.js',
145142 VectorsCanvas: {
146143 src: ['layer/vector/canvas/Polyline.Canvas.js',
147144 'layer/vector/canvas/Polygon.Canvas.js',
148 'layer/vector/canvas/Circle.Canvas.js'],
149 deps: ['PathCanvas', 'Polyline', 'Polygon', 'Circle'],
150 desc: 'Canvas fallback for vector layers (polygons, polylines, circles)'
145 'layer/vector/canvas/Circle.Canvas.js',
146 'layer/vector/canvas/CircleMarker.Canvas.js'],
147 deps: ['PathCanvas', 'Polyline', 'Polygon', 'Circle', 'CircleMarker'],
148 desc: 'Canvas fallback for vector layers (polygons, polylines, circles, circlemarkers)'
151149 },
152150
153151 GeoJSON: {
179177 'dom/DomEvent.DoubleTap.js',
180178 'dom/DomEvent.MsTouch.js',
181179 'core/Handler.js',
182 'map/handler/Map.TouchZoom.js'],
183 deps: ['MapAnimationZoom'],
184 desc: 'Enables smooth touch zooming on iOS and IE10 and double tap on iOS/IE10/Android.'
180 'map/handler/Map.TouchZoom.js',
181 'map/handler/Map.Tap.js'],
182 deps: ['AnimationZoom'],
183 desc: 'Enables smooth touch zoom / tap / longhold / doubletap on iOS, IE10, Android.'
185184 },
186185
187186 BoxZoom: {
200199 desc: 'Makes markers draggable (by mouse or touch).'
201200 },
202201
203 PolyEdit: {
204 src: ['layer/vector/Polyline.Edit.js'],
205 deps: ['Polyline', 'DivIcon'],
206 desc: 'Polyline and polygon editing.'
207 },
208
209
210202 ControlZoom: {
211203 src: ['control/Control.js',
212 'map/ext/Map.Control.js',
213204 'control/Control.Zoom.js'],
214205 heading: 'Controls',
215206 desc: 'Basic zoom control with two buttons (zoom in / zoom out).'
217208
218209 ControlAttrib: {
219210 src: ['control/Control.js',
220 'map/ext/Map.Control.js',
221211 'control/Control.Attribution.js'],
222212 desc: 'Attribution control.'
223213 },
224214
225215 ControlScale: {
226216 src: ['control/Control.js',
227 'map/ext/Map.Control.js',
228217 'control/Control.Scale.js'],
229218 desc: 'Scale control.'
230219 },
231220
232221 ControlLayers: {
233222 src: ['control/Control.js',
234 'map/ext/Map.Control.js',
235223 'control/Control.Layers.js'],
236224 desc: 'Layer Switcher control.'
237225 },
238
239226
240227 AnimationPan: {
241228 src: [
243230 'dom/PosAnimation.js',
244231 'map/anim/Map.PanAnimation.js'
245232 ],
246 deps: ['AnimationPan'],
247233 desc: 'Core panning animation support.'
248234 },
249235
254240 },
255241
256242 AnimationZoom: {
257 src: ['map/anim/Map.ZoomAnimation.js'],
243 src: ['map/anim/Map.ZoomAnimation.js', 'layer/tile/TileLayer.Anim.js'],
258244 deps: ['AnimationPan'],
259245 desc: 'Smooth zooming animation. Works only on browsers that support CSS3 Transitions.'
260246 },
00 exports.config = {
1
2 // environment
13 "browser": true,
24 "node": true,
3 "predef": ["L"],
5 "predef": ['define'],
6 "strict": false,
47
5 "debug": false,
6 "devel": false,
7
8 "es5": false,
9 "strict": false,
10 "globalstrict": false,
11
12 "asi": false,
13 "laxbreak": false,
8 // code style
149 "bitwise": true,
15 "boss": false,
10 "camelcase": true,
1611 "curly": true,
17 "eqnull": false,
18 "evil": false,
19 "expr": false,
20 "forin": true,
12 "eqeqeq": true,
13 "forin": false,
2114 "immed": true,
2215 "latedef": true,
23 "loopfunc": false,
16 "newcap": true,
2417 "noarg": true,
25 "regexp": true,
26 "regexdash": false,
27 "scripturl": false,
28 "shadow": false,
29 "supernew": false,
30 "undef": true,
31 "funcscope": false,
32
33 "newcap": true,
3418 "noempty": true,
3519 "nonew": true,
36 "nomen": false,
37 "onevar": false,
38 "plusplus": false,
39 "sub": false,
20 "undef": true,
21 "unused": true,
22 "quotmark": "single",
23
24 // whitespace
4025 "indent": 4,
41
42 "eqeqeq": true,
4326 "trailing": true,
4427 "white": true,
45 "smarttabs": true
28 "smarttabs": true,
29 "maxlen": 120
30
31 // code simplicity - not enforced but nice to check from time to time
32 // "maxstatements": 20,
33 // "maxcomplexity": 5
34 // "maxparams": 4,
35 // "maxdepth": 4
4636 };
88 <meta name="viewport" content="width=device-width,initial-scale=1 maximum-scale=1.0 user-scalable=0">
99 <link rel="stylesheet" href="../css/screen.css" />
1010
11 <script type="text/javascript" src="../../build/deps.js"></script>
1112 <script src="../leaflet-include.js"></script>
1213 </head>
1314 <body>
1415
1516 <div id="map"></div>
1617 <div >
17 <form method="get">Click in field then scroll map (in up/left direction) to see shift of map tiles.
18 <form method="get">Click in field then scroll map (in up/left direction) to see shift of map tiles.
1819 <fieldset><label for="textField">Name</label>:
1920 <input id="textField" name="textField" type="text" value="">
2021 </fieldset>
2728 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/997/256/{z}/{x}/{y}.png', {
2829 maxZoom: 18,
2930 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
30 key: 'BC9A493B41014CAABB98F0471D759707'
31 key: 'd4fc77ea4a63471cab2423e66626cbb6'
3132 });
3233
3334 //Disable the hack fix
00 (function() {
1 //TODO replace script list with the one from ../buid/deps.js
2 var scripts = [
3 'Leaflet.js',
1 function getFiles() {
2 var memo = {},
3 files = [],
4 i, src;
45
5 'core/Util.js',
6 'core/Class.js',
7 'core/Events.js',
8 'core/Browser.js',
6 function addFiles(srcs) {
7 for (var j = 0, len = srcs.length; j < len; j++) {
8 memo[srcs[j]] = true;
9 }
10 }
911
10 'geometry/Point.js',
11 'geometry/Bounds.js',
12 'geometry/Transformation.js',
13 'geometry/LineUtil.js',
14 'geometry/PolyUtil.js',
12 for (i in deps) {
13 addFiles(deps[i].src);
14 }
1515
16 'dom/DomEvent.js',
17 'dom/DomEvent.DoubleTap.js',
18 'dom/DomEvent.MsTouch.js',
19 'dom/DomUtil.js',
20 'dom/Draggable.js',
16 for (src in memo) {
17 files.push(src);
18 }
2119
22 'dom/PosAnimation.js',
23 'dom/PosAnimation.Timer.js',
24 // 'dom/transition/Transition.js',
25 // 'dom/transition/Transition.Native.js',
26 // 'dom/transition/Transition.Timer.js',
27
28 'geo/LatLng.js',
29 'geo/LatLngBounds.js',
30
31 'geo/projection/Projection.js',
32 'geo/projection/Projection.SphericalMercator.js',
33 'geo/projection/Projection.LonLat.js',
34 'geo/projection/Projection.Mercator.js',
35
36 'geo/crs/CRS.js',
37 'geo/crs/CRS.EPSG3857.js',
38 'geo/crs/CRS.EPSG4326.js',
39 'geo/crs/CRS.EPSG3395.js',
40 'geo/crs/CRS.Simple.js',
41
42 'map/Map.js',
43
44 'map/ext/Map.Geolocation.js',
45 'map/ext/Map.Popup.js',
46 'map/ext/Map.Control.js',
47
48 'map/anim/Map.PanAnimation.js',
49 'map/anim/Map.ZoomAnimation.js',
50
51 'core/Handler.js',
52 'map/handler/Map.Drag.js',
53 'map/handler/Map.TouchZoom.js',
54 'map/handler/Map.DoubleClickZoom.js',
55 'map/handler/Map.ScrollWheelZoom.js',
56 'map/handler/Map.BoxZoom.js',
57 'map/handler/Map.Keyboard.js',
58
59 'layer/LayerGroup.js',
60 'layer/FeatureGroup.js',
61
62 'layer/tile/TileLayer.js',
63 'layer/tile/TileLayer.WMS.js',
64 'layer/tile/TileLayer.Canvas.js',
65 'layer/ImageOverlay.js',
66 'layer/Popup.js',
67
68 'layer/marker/Icon.js',
69 'layer/marker/Icon.Default.js',
70 'layer/marker/DivIcon.js',
71 'layer/marker/Marker.js',
72 'layer/marker/Marker.Popup.js',
73 'layer/marker/Marker.Drag.js',
74
75 'layer/vector/Path.js',
76 'layer/vector/Path.Popup.js',
77 'layer/vector/Path.SVG.js',
78 'layer/vector/Path.VML.js',
79 'layer/vector/canvas/Path.Canvas.js',
80 'layer/vector/Polyline.js',
81 'layer/vector/Polyline.Edit.js',
82 'layer/vector/canvas/Polyline.Canvas.js',
83 'layer/vector/Polygon.js',
84 'layer/vector/Rectangle.js',
85 'layer/vector/canvas/Polygon.Canvas.js',
86 'layer/vector/MultiPoly.js',
87 'layer/vector/Circle.js',
88 'layer/vector/canvas/Circle.Canvas.js',
89 'layer/vector/CircleMarker.js',
90
91 'layer/GeoJSON.js',
92
93 'control/Control.js',
94 'control/Control.Zoom.js',
95 'control/Control.Attribution.js',
96 'control/Control.Layers.js',
97 'control/Control.Scale.js'
98 ];
20 return files;
21 }
22 var scripts = getFiles();
9923
10024 function getSrcUrl() {
10125 var scripts = document.getElementsByTagName('script');
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1617 <script type="text/javascript">
1718
1819 function getCloudMadeUrl(styleId) {
19 return 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/' + styleId + '/256/{z}/{x}/{y}.png';
20 return 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/' + styleId + '/256/{z}/{x}/{y}.png';
2021 }
2122
2223 var cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2324 cloudmade = new L.TileLayer(getCloudMadeUrl(997), {attribution: cloudmadeAttribution}),
2425 cloudmade2 = new L.TileLayer(getCloudMadeUrl(998), {attribution: 'Hello world'});
2526
26 var map = new L.Map('map').addLayer(cloudmade).setView(new L.LatLng(50.5, 30.51), 15);
27 var map = new L.Map('map').addLayer(cloudmade).setView(new L.LatLng(50.5, 30.512), 15);
2728
28 var marker = new L.CircleMarker(new L.LatLng(50.5, 30.505), {color: 'red'});
29 var marker = new L.Marker(new L.LatLng(50.5, 30.505), {color: 'red'});
2930 map.addLayer(marker);
30 marker.bindPopup("Hello World").openPopup();
31 marker.bindPopup("Leaflet is designed with simplicity, performance and usability in mind. It works efficiently across all major desktop and mobile platforms out of the box, taking advantage of HTML5 and CSS3 on modern browsers while still being accessible on older ones.").openPopup();
3132
3233 var marker2 = new L.Marker(new L.LatLng(50.502, 30.515));
3334 map.addLayer(marker2);
1717
1818 <script type="text/javascript">
1919
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2121 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2222 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
2323
0 <style>
1 iframe {
2 position: absolute;
3 top: 50px;
4 left: 50px;
5 width: 500px;
6 height: 500px;
7 }
8 </style>
9 <iframe src="map.html">
10 </iframe>
99
1010 <link rel="stylesheet" href="../css/screen.css" />
1111
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
1819
1920 <script type="text/javascript">
2021
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
22 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2223 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2324 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
2425 latlng = new L.LatLng(50.5, 30.51);
99
1010 <link rel="stylesheet" href="../css/mobile.css" />
1111
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
1718
1819 <script type="text/javascript">
1920
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2122 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2223 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
2324 latlng = new L.LatLng(50.5, 30.51);
99
1010 <link rel="stylesheet" href="../css/screen.css" />
1111
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
2122 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/997/256/{z}/{x}/{y}.png', {
2223 maxZoom: 18,
2324 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
24 key: 'BC9A493B41014CAABB98F0471D759707'
25 key: 'd4fc77ea4a63471cab2423e66626cbb6'
2526 });
2627
2728 var map = L.map('map')
66
77 <link rel="stylesheet" href="../../dist/leaflet.css" />
88 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
9
9
1010 <link rel="stylesheet" href="../css/mobile.css" />
11
11
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
1718
1819 <script type="text/javascript">
1920
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2122 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2223 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
2324 bounds = new L.LatLngBounds(new L.LatLng(49.5, -11.3), new L.LatLng(61.2, 2.5));
24
25
2526 var map = new L.Map('map', {
2627 center: bounds.getCenter(),
2728 zoom: 7,
2829 layers: [cloudmade],
2930 maxBounds: bounds
3031 });
31
32
3233
3334 </script>
3435 </body>
35 </html>
36 </html>
44
55 <link rel="stylesheet" href="../../dist/leaflet.css" />
66 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
7
88 <link rel="stylesheet" href="../css/screen.css" />
9
9
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
2122
2223 <script type="text/javascript">
2324
24 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
25 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2526 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2627 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
2728 latlng = new L.LatLng(50.5, 30.51);
28
29
2930 var map = new L.Map('map', {center: latlng, zoom: 15, layers: [cloudmade]});
30
31
31
32
3233 </script>
3334 </body>
34 </html>
35 </html>
99
1010 <link rel="stylesheet" href="../css/screen.css" />
1111
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1617 <script type="text/javascript">
1718 var map = new L.Map('map');
1819
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/{styleId}/256/{z}/{x}/{y}.png',
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/{styleId}/256/{z}/{x}/{y}.png',
2021 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2122 cloudmade = new L.TileLayer(cloudmadeUrl, {styleId: 997, attribution: cloudmadeAttribution}),
2223 cloudmade2 = new L.TileLayer(cloudmadeUrl, {styleId: 998, attribution: cloudmadeAttribution});
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1516
1617 <script type="text/javascript">
1718 // Test that changing between layers with differing zoomlevels also updates
18 // the zoomlevels in the map + also
19 // the zoomlevels in the map + also
1920
2021 function getCloudMadeUrl(styleId) {
21 return 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/' + styleId + '/256/{z}/{x}/{y}.png';
22 return 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/' + styleId + '/256/{z}/{x}/{y}.png';
2223 }
2324
2425 var map = L.map('map').setView(L.latLng(50.5, 30.51), 0);
2627 var cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2728 cloudmade = L.tileLayer(getCloudMadeUrl(997), {attribution: cloudmadeAttribution, minZoom: 0, maxZoom: 10}).addTo(map),
2829 cloudmade2 = L.tileLayer(getCloudMadeUrl(998), {attribution: 'Hello world', minZoom: 5, maxZoom: 18});
29
30
3031 L.control.layers({
3132 'CloudMade Pale Dawn (5-18)': cloudmade2,
3233 'CloudMade Fresh (0-10)': cloudmade
88 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
99
1010 <link rel="stylesheet" href="../css/screen.css" />
11 <script type="text/javascript" src="../../build/deps.js"></script>
1112 <script src="../leaflet-include.js"></script>
1213 <script type='text/javascript' src='http://code.jquery.com/jquery-1.8.0.js'></script>
1314 </head>
0 <html>
1 <head>
2 <link rel="stylesheet" href="../../dist/leaflet.css" />
3 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
4 <script>
5 L_PREFER_CANVAS = true;
6 </script>
7 <link rel="stylesheet" href="../css/screen.css" />
8
9 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
10
11 </head>
12 <body>
13
14 <div id="map"></div>
15
16
17 <script>
18
19 $(document).ready(function() {
20 //Init a map, and attempt a locate.
21 var map = L.map('map', {
22 center: [39.84, -96.591],
23 zoom: 4
24 }).locate();
25
26 L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
27 attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
28 }).addTo(map);
29
30 var vanillaLayer = new L.LayerGroup();
31 map.addLayer(vanillaLayer);
32
33 map.on('moveend',function(e) {
34 console.log('moveend fired.')
35 });
36
37 //For experiments using setRadius
38 window.marker = L.circleMarker(map.getCenter(),{radius:30}).addTo(vanillaLayer);
39 });
40
41 </script>
42 <script type="text/javascript" src="../../build/deps.js"></script>
43
44 <script src="../leaflet-include.js"></script>
45
46 </body>
47 </html>
1313 <script>
1414 L_PREFER_CANVAS = true;
1515 $(document).ready(function() {
16 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
17 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
18 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
19
1620 var map = L.map('map', {
1721 minZoom: 1,
1822 maxZoom: 19,
1923 center: [51.505, -0.09],
20 zoom: 9
24 zoom: 9,
25 layers: [cloudmade]
2126 });
2227
2328 var polygons = new L.FeatureGroup();
2631 polygons.addLayer(
2732 new L.Polyline(
2833 points, {
29 weight: 2,
34 weight: 10,
3035 opacity: 1,
3136 smoothFactor: 1,
32 color: 'red'
37 color: 'red',
38 clickable:true
3339 }));
3440
3541 polygons.on('click', function(m) {
4248 });
4349 </script>
4450
51
52 <script type="text/javascript" src="../../build/deps.js"></script>
4553 <script src="../leaflet-include.js"></script>
4654 </head>
4755 <body>
1414 }
1515
1616 </style>
17 <script type="text/javascript" src="../../build/deps.js"></script>
1718 <script src="../leaflet-include.js"></script>
1819 </head>
1920 <body>
2223
2324 <script type="text/javascript">
2425
25 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
26 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2627 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2728 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
2829 latlng = new L.LatLng(50.5, 30.51);
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet debug page</title>
4
5 <link rel="stylesheet" href="../../dist/leaflet.css" />
6 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
8 <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
10 <link rel="stylesheet" href="../css/screen.css" />
11
12 <script type="text/javascript" src="../../build/deps.js"></script>
13 <script src="../leaflet-include.js"></script>
14 </head>
15 <body>
16
17 <div id="map"></div>
18 <button id="populate">Populate with 10 markers</button>
19
20 <script type="text/javascript">
21
22 var map = L.map('map').setView([36.9, -95.4], 5);
23 map.on('contextmenu', function (e) {
24 alert('The map has been right-clicked');
25 });
26 L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
27 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
28 key: 'BC9A493B41014CAABB98F0471D759707'
29 }).addTo(map);
30
31
32
33 var exampleGeoJSON = {
34 type: 'Polygon',
35 coordinates: [
36 [
37 [-90.0, 35.0],
38 [-90.0, 45.0],
39 [-100.0, 45.0],
40 [-100.0, 35.0]
41 ]
42 ]
43 };
44
45 var geoJsonLayer = L.geoJson(exampleGeoJSON, {
46 onEachFeature: function (feature, layer) {
47 layer.on('contextmenu', function (e) {
48 alert('The GeoJSON layer has been clicked');
49 });
50 }
51 }).addTo(map);
52
53 var marker = L.marker([36, -95]).addTo(map);
54 marker.bindPopup('Right-click me <br> to test contextmenu <br> event capture').openPopup();
55
56
57 </script>
58 </body>
59 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet debug page</title>
4
5 <link rel="stylesheet" href="../../dist/leaflet.css" />
6 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
8 <link rel="stylesheet" href="../css/screen.css" />
9
10 <script type="text/javascript" src="../../build/deps.js"></script>
11 <script src="../leaflet-include.js"></script>
12 </head>
13 <body>
14
15 <div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
16
17 <script type="text/javascript">
18
19 var map = L.map('map').setView( [50, 50], 10);
20 var marker = L.marker([50, 50], {draggable: true}).addTo(map);
21
22 setTimeout(function() {
23 map.removeLayer(marker);
24 }, 3000);
25
26 </script>
27 </body>
28 </html>
88 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
99
1010 <link rel="stylesheet" href="../css/screen.css" />
11 <script type="text/javascript" src="../../build/deps.js"></script>
1112 <script src="../leaflet-include.js"></script>
1213 <script type='text/javascript' src='http://code.jquery.com/jquery-1.8.0.js'></script>
1314 </head>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet debug page</title>
4
5 <link rel="stylesheet" href="../../dist/leaflet.css" />
6 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
8 <link rel="stylesheet" href="../css/screen.css" />
9 <script type="text/javascript" src="../../build/deps.js"></script>
10 <script src="../leaflet-include.js"></script>
11 </head>
12 <body>
13 <div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
14
15 <script>
16 var map = L.map('map');
17
18 var marker = L.marker([51.5, -0.09])
19 .bindPopup("<b>Hello world!</b><br />I am a popup.")
20 .addTo(map);
21 //.openPopup();
22
23 var marker2 = L.marker([51.525, -0.09])
24 .addTo(map);
25
26 map.setView([51.505, -0.09], 13);
27
28 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
29 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
30 maxZoom: 18,
31 key: 'd4fc77ea4a63471cab2423e66626cbb6',
32 styleId: 997
33 }).addTo(map);
34
35 var a_popup = L.popup().setContent('Previously created')
36
37 marker2.bindPopup(a_popup);
38 </script>
39 </body>
40 </html>
0 <html>
1 <head>
2 <title>Test for preservation of Icon DOM element</title>
3 <link rel="stylesheet" href="../../dist/leaflet.css" />
4 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
5
6 <link rel="stylesheet" href="../css/screen.css" />
7
8 <script type="text/javascript" src="../../build/deps.js"></script>
9 <script src="../leaflet-include.js"></script>
10 </head>
11 <body>
12 <div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
13
14 <script type="text/javascript">
15
16 var blueIcon = new L.Icon({iconUrl: 'http://www.webatlas.no/webatlasapi/v/071009/media/interface/default/markers/flag_blue.gif'});
17 var redIcon = new L.Icon({iconUrl: 'http://www.webatlas.no/webatlasapi/v/071009/media/interface/default/markers/flag_red.gif'});
18
19 var map = L.map('map').setView( [50, 50], 10);
20
21 var marker = L.marker([50, 50], {icon: blueIcon, draggable: true});
22
23 marker.on('dragstart', function () {
24 console.log('dragstart');
25 marker.setIcon(redIcon);
26 //This is the previous workaround:
27 //var iconElem = L.DomUtil.get(marker._icon);
28 //iconElem.src = 'http://www.webatlas.no/webatlasapi/v/071009/media/interface/default/markers/flag_red.gif';
29 });
30
31 marker.on('dragend', function () {
32 console.log('dragend');
33 marker.setIcon(blueIcon);
34 //This is the previous workaround:
35 //var iconElem = L.DomUtil.get(marker._icon);
36 //iconElem.src = 'http://www.webatlas.no/webatlasapi/v/071009/media/interface/default/markers/flag_blue.gif';
37
38 });
39
40 marker.addTo(map);
41 </script>
42 </body>
43 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet debug page</title>
4
5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
7 <link rel="stylesheet" href="../../dist/leaflet.css" />
8 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
9
10 <link rel="stylesheet" href="../css/screen.css" />
11 <script type="text/javascript" src="../../build/deps.js"></script>
12 <script src="../leaflet-include.js"></script>
13 <script type='text/javascript' src='http://code.jquery.com/jquery-1.8.0.js'></script>
14 </head>
15 <body>
16
17 <div id="map"></div>
18
19 <script type="text/javascript">
20
21 var map;
22 var myLayerGroup = new L.LayerGroup();
23
24 initmap();
25
26 function initmap() {
27 // set up the map
28 map = new L.Map('map');
29
30 // create the tile layer with correct attribution
31 var osmUrl = 'http://a.tile.openstreetmap.org/{z}/{x}/{y}.png';
32 var osm = new L.TileLayer(osmUrl, { minZoom: 1, maxZoom: 17 });
33 map.addLayer(osm);
34 map.fitBounds(new L.LatLngBounds([51,7],[51,7]));
35
36 var route = L.polyline([
37 [51, 7.000],
38 [51.002, 7.004],
39 [51.004, 7.006]
40 ]).addTo(map).on('click',function(e){console.log('bottom')})
41
42 var route2 = L.polyline([
43 [51, 7.000],
44 [51.002, 7.004]
45 ],
46 { clickable:false,color:'#f00' }
47 ).addTo(map);
48
49 // when the mouse hovers over the red route2, you cannot click through the blue route1 beneath
50 };
51
52 </script>
53 </body>
54 </html>
44
55 <link rel="stylesheet" href="../../dist/leaflet.css" />
66 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
7
88 <link rel="stylesheet" href="../css/screen.css" />
9
9
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1516 <button onclick="boundsExtendLatLng()">Extend the bounds of the center rectangle with the lower left marker</button>
1617 <script src="route.js"></script>
1718 <script>
18 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
1920 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
2021
2122 var latLng = new L.LatLng(54.18815548107151, -7.657470703124999);
3738 var marker = new L.Marker(latLng);
3839
3940 map.addLayer(rectangle1).addLayer(rectangle2).addLayer(marker);
40
41
42
43
41
42
43
44
4445
4546 function boundsExtendBounds() {
4647 if (rectangle3) {
5859 opacity: 1,
5960 fillOpacity: 0
6061 });
61
62
6263 map.addLayer(rectangle3);
6364 }
6465
7879 opacity: 1,
7980 fillOpacity: 0
8081 });
81
82
8283 map.addLayer(rectangle3);
8384 }
84
85
8586 </script>
8687 </body>
87 </html>
88 </html>
+0
-52
debug/vector/editable.html less more
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet debug page</title>
4
5 <link rel="stylesheet" href="../../dist/leaflet.css" />
6 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
8 <link rel="stylesheet" href="../css/screen.css" />
9
10 <script src="../leaflet-include.js"></script>
11 </head>
12 <body>
13 <div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
14
15 <script src="route.js"></script>
16 <script>
17 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
18 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18}),
19 map = new L.Map('map', {layers: [cloudmade], center: new L.LatLng(51.505, -0.04), zoom: 13});
20
21
22 var polygon = new L.Polygon([
23 [51.51, -0.1],
24 [51.5, -0.06],
25 [51.52, -0.03]
26 ]);
27
28 polygon.editing.enable();
29
30 map.addLayer(polygon);
31
32 var polyline = new L.Polyline([
33 [51.50, -0.04],
34 [51.49, -0.02],
35 [51.51, 0],
36 [51.52, -0.02]
37 ]);
38
39 polyline.editing.enable();
40
41 map.addLayer(polyline);
42
43 polygon.on('edit', function() {
44 console.log('Polygon was edited!');
45 });
46 polyline.on('edit', function() {
47 console.log('Polyline was edited!');
48 });
49 </script>
50 </body>
51 </html>
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1819 <script type="text/javascript" src="geojson-sample.js"></script>
1920 <script type="text/javascript">
2021
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
22 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2223 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2324 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
2425 rectangle,
3132 });
3233
3334 var geojson = L.geoJson(geojsonSample, {
34
35
3536 style: function (feature) {
3637 return {color: feature.properties.color};
3738 },
4040 }
4141 </style>
4242
43 <script type="text/javascript" src="../../build/deps.js"></script>
4344 <script src="../leaflet-include.js"></script>
4445 </head>
4546 <body>
5354
5455 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
5556 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
56 key: 'BC9A493B41014CAABB98F0471D759707',
57 key: 'd4fc77ea4a63471cab2423e66626cbb6',
5758 styleId: 998
5859 }).addTo(map);
5960
66
77 <link rel="stylesheet" href="../../dist/leaflet.css" />
88 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
9
9
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1617
1718 <script type="text/javascript">
1819
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2021 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
2122 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});;
22
23
2324 var bounds = new L.LatLngBounds(new L.LatLng(54.559322, -5.767822), new L.LatLng(56.1210604, -3.021240));
2425 var bounds2 = new L.LatLngBounds(new L.LatLng(56.2124322195806, -3.427734375), new L.LatLng(56.307776937156945, -3.2560729980468746));
2526
3435 rectangle.on("click", function () {
3536 alert("you clicked a rectangle.")
3637 });
37
38
3839 var map = new L.Map('map', {
3940 center: bounds.getCenter(),
4041 zoom: 7,
4950
5051 </script>
5152 </body>
52 </html>
53 </html>
77
88 <link rel="stylesheet" href="../css/screen.css" />
99
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1314 <div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
1415 <div style="background-color:chartreuse; width: 100px; height:100px; position: absolute; left: 850px; top: 10px" onclick="Hack1()">Hack1Touch</div>
1516 <div style="background-color:coral; width: 100px; height:100px; position: absolute; left: 850px; top: 120px" onclick="Hack2()">Hack2Touch</div>
17 <div style="background-color:lightblue; width: 100px; height:100px; position: absolute; left: 850px; top: 230px" onclick="Hack3()">Hack3Touch</div>
18 <div style="background-color:lightpink; width: 100px; height:100px; position: absolute; left: 850px; top: 340px" onclick="Hack4()">Hack4Touch</div>
19 <div style="background-color:yellow; width: 100px; height:100px; position: absolute; left: 850px; top: 450px" onclick="alert(buffer)">Print Buffer</div>
1620 <script src="route.js"></script>
1721 <script>
18 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
22 //var buffer = "";
23 //console.log = function (text) {
24 // buffer += text + "\r\n";
25 //}
26
27 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
1928 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18}),
2029 map = new L.Map('map', {layers: [cloudmade], center: new L.LatLng(51.505, -0.04), zoom: 13});
2130
2635 new L.LatLng(51.52, -0.03)
2736 ]);
2837
29 polygon.editing.enable();
30
3138 map.addLayer(polygon);
3239
3340 var polyline = new L.Polyline([
3542 new L.LatLng(51.51, 0),
3643 new L.LatLng(51.52, -0.02)
3744 ]);
38
39 polyline.editing.enable();
4045
4146 map.addLayer(polyline);
4247
5156 function Hack1() {
5257 _timerAt = 0;
5358 clearInterval(_timer);
54 _timer = setInterval(Hack1Timer, 1000);
59 _timer = setInterval(Hack1Timer, 200);
5560 }
5661 function Hack1Timer() {
62 console.log(_timerAt);
5763 switch (_timerAt) {
5864 case 0:
5965 map.touchZoom._onTouchStart({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
6066 break;
6167 case 1:
62 map.touchZoom._onTouchMove({ touches: [{ pageX: 412, pageY: 312 }, { pageX: 236, pageY: 322 }] });
68 map.touchZoom._onTouchMove({ touches: [{ pageX: 452, pageY: 312 }, { pageX: 236, pageY: 322 }] });
6369 break;
6470 case 2:
65 map.touchZoom._onTouchMove({ touches: [{ pageX: 423, pageY: 313 }, { pageX: 243, pageY: 321 }] });
71 map.touchZoom._onTouchEnd();
6672 break;
6773 case 3:
68 map.touchZoom._onTouchMove({ touches: [{ pageX: 476, pageY: 326 }, { pageX: 299, pageY: 321 }] });
74 map.touchZoom._onTouchStart({ touches: [{ pageX: 423, pageY: 313 }, { pageX: 243, pageY: 321 }] });
6975 break;
7076 case 4:
77 map.touchZoom._onTouchMove({ touches: [{ pageX: 423, pageY: 313 }, { pageX: 243, pageY: 321 }] });
78 //map.touchZoom._onTouchMove({ touches: [{ pageX: 476, pageY: 326 }, { pageX: 299, pageY: 321 }] });
79 break;
80 case 5:
7181 map.touchZoom._onTouchEnd();
7282 break;
73 case 5:
83 case 6:
7484 clearInterval(_timer);
7585 break;
7686 }
7787 _timerAt++;
7888 }
7989 function Hack2() {
80 map.touchZoom._onTouchStart({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
81 map.touchZoom._onTouchMove({ touches: [{ pageX: 476, pageY: 326 }, { pageX: 299, pageY: 321 }] });
82 //_timerAt = 0;
83 //clearInterval(_timer);
84 //_timer = setInterval(Hack2Timer, 100);
90 _timerAt = 0;
91 clearInterval(_timer);
92 _timer = setInterval(Hack2Timer, 100);
8593 }
8694 function Hack2Timer() {
95 console.log(_timerAt);
8796 switch (_timerAt) {
8897 case 0:
8998 map.touchZoom._onTouchStart({ touches: [{ pageX: 100, pageY: 100 }, { pageX: 50, pageY: 100}] });
115124 }
116125 _timerAt++;
117126 }
127 function Hack3() {
128 _timerAt = 0;
129 clearInterval(_timer);
130 _timer = setInterval(Hack3Timer, 100);
131 }
132 function Hack3Timer() {
133 console.log(_timerAt);
134 switch (_timerAt) {
135 case 0:
136 map.touchZoom._onTouchStart({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
137 break;
138 case 1:
139 map.touchZoom._onTouchMove({ touches: [{ pageX: 412, pageY: 312 }, { pageX: 236, pageY: 322 }] });
140 break;
141 case 2:
142 map.touchZoom._onTouchEnd();
143 break;
144 case 3:
145 map.touchZoom._onTouchStart({ touches: [{ pageX: 423, pageY: 313 }, { pageX: 243, pageY: 321 }] });
146 break;
147 case 4:
148 map.touchZoom._onTouchMove({ touches: [{ pageX: 476, pageY: 326 }, { pageX: 299, pageY: 321 }] });
149 break;
150 case 5:
151 map.touchZoom._onTouchEnd();
152 break;
153 case 6:
154 clearInterval(_timer);
155 break;
156 }
157 _timerAt++;
158 }
159
160 function Hack4() {
161 _timerAt = 0;
162 clearInterval(_timer);
163 _timer = setInterval(Hack4Timer, 40);
164 }
165 function Hack4Timer() {
166 console.log(_timerAt);
167 switch (_timerAt) {
168 case 0:
169 map.touchZoom._onTouchStart({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
170 break;
171 case 1:
172 map.touchZoom._onTouchMove({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 236, pageY: 322 }] });
173 break;
174 case 2:
175 map.touchZoom._onTouchEnd();
176 break;
177 case 5:
178 map.touchZoom._onTouchStart({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
179 break;
180 case 6:
181 map.touchZoom._onTouchMove({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
182 break;
183 case 7:
184 map.touchZoom._onTouchEnd();
185 break;
186 case 8:
187 clearInterval(_timer);
188 break;
189 }
190 _timerAt++;
191 }
118192 </script>
119193 </body>
120194 </html>
44
55 <link rel="stylesheet" href="../../dist/leaflet.css" />
66 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
7
88 <link rel="stylesheet" href="../css/screen.css" />
9
9
10 <script type="text/javascript" src="../../build/deps.js"></script>
1011 <script src="../leaflet-include.js"></script>
1112 </head>
1213 <body>
1516 <button onclick="map.fitBounds(poly.getBounds())">Zoom to polygon</button>
1617 <script src="route.js"></script>
1718 <script>
18 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
1920 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
20
21
2122 var poly_points = [
2223 [39.70348880963439, -104.98603820800781],
2324 [39.69926245589766, -104.95582580566406],
2627 [39.66279941218785, -104.98672485351562],
2728 [39.70348880963439, -104.98603820800781]
2829 ];
29
30
3031 var path_points = [
3132 [39.72567292003209, -104.98672485351562],
3233 [39.717222671644635, -104.96612548828124],
3738 [39.687638648548635, -104.90432739257812],
3839 [39.67759833072648, -104.89471435546875]
3940 ];
40
41
4142 for (var i = 0, latlngs = [], len = path_points.length; i < len; i++) {
4243 latlngs.push(new L.LatLng(path_points[i][0], path_points[i][1]));
4344 }
4445 var path = new L.Polyline(latlngs);
45
46
4647 for (var i = 0, latlngs2 = [], len = poly_points.length; i < len; i++) {
4748 latlngs2.push(new L.LatLng(poly_points[i][0], poly_points[i][1]));
4849 }
5556 });
5657
5758 //map.fitBounds(new L.LatLngBounds(latlngs));
58
59
5960 //map.addLayer(new L.Marker(latlngs[0]));
6061 //map.addLayer(new L.Marker(latlngs[len - 1]));
61
62
6263 map.addLayer(path);
6364 map.addLayer(poly);
64
65
6566 path.bindPopup("Hello world");
6667 </script>
6768 </body>
68 </html>
69 </html>
1010 <script>
1111 L_PREFER_CANVAS = true; // experimental
1212 </script>
13 <script type="text/javascript" src="../../build/deps.js"></script>
1314 <script src="../leaflet-include.js"></script>
1415 </head>
1516 <body>
2122
2223 <script src="route.js"></script>
2324 <script>
24 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
25 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2526 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
2627
2728 for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
66
77 <link rel="stylesheet" href="../../dist/leaflet.css" />
88 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
9
9
1010 <link rel="stylesheet" href="../css/mobile.css" />
11
11
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
1617
1718 <script src="route.js"></script>
1819 <script>
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
2021 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
21
22
2223 for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
2324 latlngs.push(new L.LatLng(route[i][0], route[i][1]));
2425 }
2728 var map = new L.Map('map', {layers: [cloudmade]});
2829
2930 map.fitBounds(new L.LatLngBounds(latlngs));
30
31
3132 map.addLayer(new L.Marker(latlngs[0]));
3233 map.addLayer(new L.Marker(latlngs[latlngs.length - 1]));
33
34
3435 map.addLayer(path);
3536 </script>
3637 </body>
37 </html>
38 </html>
99
1010 <link rel="stylesheet" href="../css/mobile.css" />
1111
12 <script type="text/javascript" src="../../build/deps.js"></script>
1213 <script src="../leaflet-include.js"></script>
1314 </head>
1415 <body>
3839 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
3940 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
4041 maxZoom: 18,
41 key: 'BC9A493B41014CAABB98F0471D759707',
42 key: 'd4fc77ea4a63471cab2423e66626cbb6',
4243 styleId: 997
4344 }).addTo(map);
4445
44
55 <link rel="stylesheet" href="../../dist/leaflet.css" />
66 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
7
7
88 <link rel="stylesheet" href="../css/screen.css" />
9
9 <script type="text/javascript" src="../../build/deps.js"></script>
1010 <script src="../leaflet-include.js"></script>
1111 </head>
1212 <body>
1414
1515 <script src="route.js"></script>
1616 <script>
17 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
17 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/d4fc77ea4a63471cab2423e66626cbb6/997/256/{z}/{x}/{y}.png',
1818 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
19
19
2020 for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
2121 latlngs.push(new L.LatLng(route[i][0], route[i][1]));
2222 }
2525 var map = new L.Map('map', {layers: [cloudmade]});
2626
2727 map.fitBounds(new L.LatLngBounds(latlngs));
28
28
2929 map.addLayer(new L.Marker(latlngs[0]));
3030 map.addLayer(new L.Marker(latlngs[len - 1]));
31
31
3232 map.addLayer(path);
33
33
3434 path.bindPopup("Hello world");
3535 </script>
3636 </body>
37 </html>
37 </html>
Binary diff not shown
Binary diff not shown
dist/images/marker-icon@2x.png less more
Binary diff not shown
00 /*
11 Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
2 (c) 2010-2013, Vladimir Agafonkin, CloudMade
2 (c) 2010-2013, Vladimir Agafonkin
3 (c) 2010-2011, CloudMade
34 */
4 (function (window, document, undefined) {/*
5 * The L namespace contains all Leaflet classes and functions.
6 * This code allows you to handle any possible namespace conflicts.
7 */
8
9 var L, originalL;
10
11 if (typeof exports !== undefined + '') {
12 L = exports;
13 } else {
14 originalL = window.L;
15 L = {};
16
17 L.noConflict = function () {
18 window.L = originalL;
19 return this;
20 };
21
22 window.L = L;
23 }
24
25 L.version = '0.5.1';
26
27
28 /*
29 * L.Util contains various utility functions used throughout Leaflet code.
30 */
31
32 L.Util = {
33 extend: function (dest) { // (Object[, Object, ...]) ->
34 var sources = Array.prototype.slice.call(arguments, 1),
35 i, j, len, src;
36
37 for (j = 0, len = sources.length; j < len; j++) {
38 src = sources[j] || {};
39 for (i in src) {
40 if (src.hasOwnProperty(i)) {
41 dest[i] = src[i];
42 }
43 }
44 }
45 return dest;
46 },
47
48 bind: function (fn, obj) { // (Function, Object) -> Function
49 var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
50 return function () {
51 return fn.apply(obj, args || arguments);
52 };
53 },
54
55 stamp: (function () {
56 var lastId = 0, key = '_leaflet_id';
57 return function (/*Object*/ obj) {
58 obj[key] = obj[key] || ++lastId;
59 return obj[key];
60 };
61 }()),
62
63 limitExecByInterval: function (fn, time, context) {
64 var lock, execOnUnlock;
65
66 return function wrapperFn() {
67 var args = arguments;
68
69 if (lock) {
70 execOnUnlock = true;
71 return;
72 }
73
74 lock = true;
75
76 setTimeout(function () {
77 lock = false;
78
79 if (execOnUnlock) {
80 wrapperFn.apply(context, args);
81 execOnUnlock = false;
82 }
83 }, time);
84
85 fn.apply(context, args);
86 };
87 },
88
89 falseFn: function () {
90 return false;
91 },
92
93 formatNum: function (num, digits) {
94 var pow = Math.pow(10, digits || 5);
95 return Math.round(num * pow) / pow;
96 },
97
98 splitWords: function (str) {
99 return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
100 },
101
102 setOptions: function (obj, options) {
103 obj.options = L.extend({}, obj.options, options);
104 return obj.options;
105 },
106
107 getParamString: function (obj, existingUrl) {
108 var params = [];
109 for (var i in obj) {
110 if (obj.hasOwnProperty(i)) {
111 params.push(i + '=' + obj[i]);
112 }
113 }
114 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
115 },
116
117 template: function (str, data) {
118 return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
119 var value = data[key];
120 if (!data.hasOwnProperty(key)) {
121 throw new Error('No value provided for variable ' + str);
122 }
123 return value;
124 });
125 },
126
127 isArray: function (obj) {
128 return (Object.prototype.toString.call(obj) === '[object Array]');
129 },
130
131 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
132 };
133
134 (function () {
135
136 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
137
138 function getPrefixed(name) {
139 var i, fn,
140 prefixes = ['webkit', 'moz', 'o', 'ms'];
141
142 for (i = 0; i < prefixes.length && !fn; i++) {
143 fn = window[prefixes[i] + name];
144 }
145
146 return fn;
147 }
148
149 var lastTime = 0;
150
151 function timeoutDefer(fn) {
152 var time = +new Date(),
153 timeToCall = Math.max(0, 16 - (time - lastTime));
154
155 lastTime = time + timeToCall;
156 return window.setTimeout(fn, timeToCall);
157 }
158
159 var requestFn = window.requestAnimationFrame ||
160 getPrefixed('RequestAnimationFrame') || timeoutDefer;
161
162 var cancelFn = window.cancelAnimationFrame ||
163 getPrefixed('CancelAnimationFrame') ||
164 getPrefixed('CancelRequestAnimationFrame') ||
165 function (id) { window.clearTimeout(id); };
166
167
168 L.Util.requestAnimFrame = function (fn, context, immediate, element) {
169 fn = L.bind(fn, context);
170
171 if (immediate && requestFn === timeoutDefer) {
172 fn();
173 } else {
174 return requestFn.call(window, fn, element);
175 }
176 };
177
178 L.Util.cancelAnimFrame = function (id) {
179 if (id) {
180 cancelFn.call(window, id);
181 }
182 };
183
184 }());
185
186 // shortcuts for most used utility functions
187 L.extend = L.Util.extend;
188 L.bind = L.Util.bind;
189 L.stamp = L.Util.stamp;
190 L.setOptions = L.Util.setOptions;
191
192
193 /*
194 * L.Class powers the OOP facilities of the library.
195 * Thanks to John Resig and Dean Edwards for inspiration!
196 */
197
198 L.Class = function () {};
199
200 L.Class.extend = function (props) {
201
202 // extended class with the new prototype
203 var NewClass = function () {
204
205 // call the constructor
206 if (this.initialize) {
207 this.initialize.apply(this, arguments);
208 }
209
210 // call all constructor hooks
211 if (this._initHooks) {
212 this.callInitHooks();
213 }
214 };
215
216 // instantiate class without calling constructor
217 var F = function () {};
218 F.prototype = this.prototype;
219
220 var proto = new F();
221 proto.constructor = NewClass;
222
223 NewClass.prototype = proto;
224
225 //inherit parent's statics
226 for (var i in this) {
227 if (this.hasOwnProperty(i) && i !== 'prototype') {
228 NewClass[i] = this[i];
229 }
230 }
231
232 // mix static properties into the class
233 if (props.statics) {
234 L.extend(NewClass, props.statics);
235 delete props.statics;
236 }
237
238 // mix includes into the prototype
239 if (props.includes) {
240 L.Util.extend.apply(null, [proto].concat(props.includes));
241 delete props.includes;
242 }
243
244 // merge options
245 if (props.options && proto.options) {
246 props.options = L.extend({}, proto.options, props.options);
247 }
248
249 // mix given properties into the prototype
250 L.extend(proto, props);
251
252 proto._initHooks = [];
253
254 var parent = this;
255 // add method for calling all hooks
256 proto.callInitHooks = function () {
257
258 if (this._initHooksCalled) { return; }
259
260 if (parent.prototype.callInitHooks) {
261 parent.prototype.callInitHooks.call(this);
262 }
263
264 this._initHooksCalled = true;
265
266 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
267 proto._initHooks[i].call(this);
268 }
269 };
270
271 return NewClass;
272 };
273
274
275 // method for adding properties to prototype
276 L.Class.include = function (props) {
277 L.extend(this.prototype, props);
278 };
279
280 // merge new default options to the Class
281 L.Class.mergeOptions = function (options) {
282 L.extend(this.prototype.options, options);
283 };
284
285 // add a constructor hook
286 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
287 var args = Array.prototype.slice.call(arguments, 1);
288
289 var init = typeof fn === 'function' ? fn : function () {
290 this[fn].apply(this, args);
291 };
292
293 this.prototype._initHooks = this.prototype._initHooks || [];
294 this.prototype._initHooks.push(init);
295 };
296
297
298 /*
299 * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
300 */
301
302 var key = '_leaflet_events';
303
304 L.Mixin = {};
305
306 L.Mixin.Events = {
307
308 addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
309 var events = this[key] = this[key] || {},
310 type, i, len;
311
312 // Types can be a map of types/handlers
313 if (typeof types === 'object') {
314 for (type in types) {
315 if (types.hasOwnProperty(type)) {
316 this.addEventListener(type, types[type], fn);
317 }
318 }
319
320 return this;
321 }
322
323 types = L.Util.splitWords(types);
324
325 for (i = 0, len = types.length; i < len; i++) {
326 events[types[i]] = events[types[i]] || [];
327 events[types[i]].push({
328 action: fn,
329 context: context || this
330 });
331 }
332
333 return this;
334 },
335
336 hasEventListeners: function (type) { // (String) -> Boolean
337 return (key in this) && (type in this[key]) && (this[key][type].length > 0);
338 },
339
340 removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
341 var events = this[key],
342 type, i, len, listeners, j;
343
344 if (typeof types === 'object') {
345 for (type in types) {
346 if (types.hasOwnProperty(type)) {
347 this.removeEventListener(type, types[type], fn);
348 }
349 }
350
351 return this;
352 }
353
354 types = L.Util.splitWords(types);
355
356 for (i = 0, len = types.length; i < len; i++) {
357
358 if (this.hasEventListeners(types[i])) {
359 listeners = events[types[i]];
360
361 for (j = listeners.length - 1; j >= 0; j--) {
362 if (
363 (!fn || listeners[j].action === fn) &&
364 (!context || (listeners[j].context === context))
365 ) {
366 listeners.splice(j, 1);
367 }
368 }
369 }
370 }
371
372 return this;
373 },
374
375 fireEvent: function (type, data) { // (String[, Object])
376 if (!this.hasEventListeners(type)) {
377 return this;
378 }
379
380 var event = L.extend({
381 type: type,
382 target: this
383 }, data);
384
385 var listeners = this[key][type].slice();
386
387 for (var i = 0, len = listeners.length; i < len; i++) {
388 listeners[i].action.call(listeners[i].context || this, event);
389 }
390
391 return this;
392 }
393 };
394
395 L.Mixin.Events.on = L.Mixin.Events.addEventListener;
396 L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
397 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
398
399
400 /*
401 * L.Browser handles different browser and feature detections for internal Leaflet use.
402 */
403
404 (function () {
405
406 var ie = !!window.ActiveXObject,
407 ie6 = ie && !window.XMLHttpRequest,
408 ie7 = ie && !document.querySelector,
409
410 // terrible browser detection to work around Safari / iOS / Android browser bugs
411 ua = navigator.userAgent.toLowerCase(),
412 webkit = ua.indexOf('webkit') !== -1,
413 chrome = ua.indexOf('chrome') !== -1,
414 android = ua.indexOf('android') !== -1,
415 android23 = ua.search('android [23]') !== -1,
416
417 mobile = typeof orientation !== undefined + '',
418 msTouch = window.navigator && window.navigator.msPointerEnabled &&
419 window.navigator.msMaxTouchPoints,
420 retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
421 ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
422 window.matchMedia('(min-resolution:144dpi)').matches),
423
424 doc = document.documentElement,
425 ie3d = ie && ('transition' in doc.style),
426 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
427 gecko3d = 'MozPerspective' in doc.style,
428 opera3d = 'OTransition' in doc.style,
429 any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d);
430
431
432 var touch = !window.L_NO_TOUCH && (function () {
433
434 var startName = 'ontouchstart';
435
436 // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
437 if (msTouch || (startName in doc)) {
438 return true;
439 }
440
441 // Firefox/Gecko
442 var div = document.createElement('div'),
443 supported = false;
444
445 if (!div.setAttribute) {
446 return false;
447 }
448 div.setAttribute(startName, 'return;');
449
450 if (typeof div[startName] === 'function') {
451 supported = true;
452 }
453
454 div.removeAttribute(startName);
455 div = null;
456
457 return supported;
458 }());
459
460
461 L.Browser = {
462 ie: ie,
463 ie6: ie6,
464 ie7: ie7,
465 webkit: webkit,
466
467 android: android,
468 android23: android23,
469
470 chrome: chrome,
471
472 ie3d: ie3d,
473 webkit3d: webkit3d,
474 gecko3d: gecko3d,
475 opera3d: opera3d,
476 any3d: any3d,
477
478 mobile: mobile,
479 mobileWebkit: mobile && webkit,
480 mobileWebkit3d: mobile && webkit3d,
481 mobileOpera: mobile && window.opera,
482
483 touch: touch,
484 msTouch: msTouch,
485
486 retina: retina
487 };
488
489 }());
490
491
492 /*
493 * L.Point represents a point with x and y coordinates.
494 */
495
496 L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
497 this.x = (round ? Math.round(x) : x);
498 this.y = (round ? Math.round(y) : y);
499 };
500
501 L.Point.prototype = {
502
503 clone: function () {
504 return new L.Point(this.x, this.y);
505 },
506
507 // non-destructive, returns a new point
508 add: function (point) {
509 return this.clone()._add(L.point(point));
510 },
511
512 // destructive, used directly for performance in situations where it's safe to modify existing point
513 _add: function (point) {
514 this.x += point.x;
515 this.y += point.y;
516 return this;
517 },
518
519 subtract: function (point) {
520 return this.clone()._subtract(L.point(point));
521 },
522
523 _subtract: function (point) {
524 this.x -= point.x;
525 this.y -= point.y;
526 return this;
527 },
528
529 divideBy: function (num) {
530 return this.clone()._divideBy(num);
531 },
532
533 _divideBy: function (num) {
534 this.x /= num;
535 this.y /= num;
536 return this;
537 },
538
539 multiplyBy: function (num) {
540 return this.clone()._multiplyBy(num);
541 },
542
543 _multiplyBy: function (num) {
544 this.x *= num;
545 this.y *= num;
546 return this;
547 },
548
549 round: function () {
550 return this.clone()._round();
551 },
552
553 _round: function () {
554 this.x = Math.round(this.x);
555 this.y = Math.round(this.y);
556 return this;
557 },
558
559 floor: function () {
560 return this.clone()._floor();
561 },
562
563 _floor: function () {
564 this.x = Math.floor(this.x);
565 this.y = Math.floor(this.y);
566 return this;
567 },
568
569 distanceTo: function (point) {
570 point = L.point(point);
571
572 var x = point.x - this.x,
573 y = point.y - this.y;
574
575 return Math.sqrt(x * x + y * y);
576 },
577
578 equals: function (point) {
579 return point.x === this.x &&
580 point.y === this.y;
581 },
582
583 toString: function () {
584 return 'Point(' +
585 L.Util.formatNum(this.x) + ', ' +
586 L.Util.formatNum(this.y) + ')';
587 }
588 };
589
590 L.point = function (x, y, round) {
591 if (x instanceof L.Point) {
592 return x;
593 }
594 if (L.Util.isArray(x)) {
595 return new L.Point(x[0], x[1]);
596 }
597 if (isNaN(x)) {
598 return x;
599 }
600 return new L.Point(x, y, round);
601 };
602
603
604 /*
605 * L.Bounds represents a rectangular area on the screen in pixel coordinates.
606 */
607
608 L.Bounds = function (a, b) { //(Point, Point) or Point[]
609 if (!a) { return; }
610
611 var points = b ? [a, b] : a;
612
613 for (var i = 0, len = points.length; i < len; i++) {
614 this.extend(points[i]);
615 }
616 };
617
618 L.Bounds.prototype = {
619 // extend the bounds to contain the given point
620 extend: function (point) { // (Point)
621 point = L.point(point);
622
623 if (!this.min && !this.max) {
624 this.min = point.clone();
625 this.max = point.clone();
626 } else {
627 this.min.x = Math.min(point.x, this.min.x);
628 this.max.x = Math.max(point.x, this.max.x);
629 this.min.y = Math.min(point.y, this.min.y);
630 this.max.y = Math.max(point.y, this.max.y);
631 }
632 return this;
633 },
634
635 getCenter: function (round) { // (Boolean) -> Point
636 return new L.Point(
637 (this.min.x + this.max.x) / 2,
638 (this.min.y + this.max.y) / 2, round);
639 },
640
641 getBottomLeft: function () { // -> Point
642 return new L.Point(this.min.x, this.max.y);
643 },
644
645 getTopRight: function () { // -> Point
646 return new L.Point(this.max.x, this.min.y);
647 },
648
649 getSize: function () {
650 return this.max.subtract(this.min);
651 },
652
653 contains: function (obj) { // (Bounds) or (Point) -> Boolean
654 var min, max;
655
656 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
657 obj = L.point(obj);
658 } else {
659 obj = L.bounds(obj);
660 }
661
662 if (obj instanceof L.Bounds) {
663 min = obj.min;
664 max = obj.max;
665 } else {
666 min = max = obj;
667 }
668
669 return (min.x >= this.min.x) &&
670 (max.x <= this.max.x) &&
671 (min.y >= this.min.y) &&
672 (max.y <= this.max.y);
673 },
674
675 intersects: function (bounds) { // (Bounds) -> Boolean
676 bounds = L.bounds(bounds);
677
678 var min = this.min,
679 max = this.max,
680 min2 = bounds.min,
681 max2 = bounds.max,
682 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
683 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
684
685 return xIntersects && yIntersects;
686 },
687
688 isValid: function () {
689 return !!(this.min && this.max);
690 }
691 };
692
693 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
694 if (!a || a instanceof L.Bounds) {
695 return a;
696 }
697 return new L.Bounds(a, b);
698 };
699
700
701 /*
702 * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
703 */
704
705 L.Transformation = function (a, b, c, d) {
706 this._a = a;
707 this._b = b;
708 this._c = c;
709 this._d = d;
710 };
711
712 L.Transformation.prototype = {
713 transform: function (point, scale) { // (Point, Number) -> Point
714 return this._transform(point.clone(), scale);
715 },
716
717 // destructive transform (faster)
718 _transform: function (point, scale) {
719 scale = scale || 1;
720 point.x = scale * (this._a * point.x + this._b);
721 point.y = scale * (this._c * point.y + this._d);
722 return point;
723 },
724
725 untransform: function (point, scale) {
726 scale = scale || 1;
727 return new L.Point(
728 (point.x / scale - this._b) / this._a,
729 (point.y / scale - this._d) / this._c);
730 }
731 };
732
733
734 /*
735 * L.DomUtil contains various utility functions for working with DOM.
736 */
737
738 L.DomUtil = {
739 get: function (id) {
740 return (typeof id === 'string' ? document.getElementById(id) : id);
741 },
742
743 getStyle: function (el, style) {
744
745 var value = el.style[style];
746
747 if (!value && el.currentStyle) {
748 value = el.currentStyle[style];
749 }
750
751 if ((!value || value === 'auto') && document.defaultView) {
752 var css = document.defaultView.getComputedStyle(el, null);
753 value = css ? css[style] : null;
754 }
755
756 return value === 'auto' ? null : value;
757 },
758
759 getViewportOffset: function (element) {
760
761 var top = 0,
762 left = 0,
763 el = element,
764 docBody = document.body,
765 pos,
766 ie7 = L.Browser.ie7;
767
768 do {
769 top += el.offsetTop || 0;
770 left += el.offsetLeft || 0;
771
772 //add borders
773 top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0;
774 left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0;
775
776 pos = L.DomUtil.getStyle(el, 'position');
777
778 if (el.offsetParent === docBody && pos === 'absolute') { break; }
779
780 if (pos === 'fixed') {
781 top += docBody.scrollTop || 0;
782 left += docBody.scrollLeft || 0;
783 break;
784 }
785 el = el.offsetParent;
786
787 } while (el);
788
789 el = element;
790
791 do {
792 if (el === docBody) { break; }
793
794 top -= el.scrollTop || 0;
795 left -= el.scrollLeft || 0;
796
797 // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
798 // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
799 if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
800 left += el.scrollWidth - el.clientWidth;
801
802 // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
803 // need to add it back in if it is visible; scrollbar is on the left as we are RTL
804 if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
805 L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
806 left += 17;
807 }
808 }
809
810 el = el.parentNode;
811 } while (el);
812
813 return new L.Point(left, top);
814 },
815
816 documentIsLtr: function () {
817 if (!L.DomUtil._docIsLtrCached) {
818 L.DomUtil._docIsLtrCached = true;
819 L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr";
820 }
821 return L.DomUtil._docIsLtr;
822 },
823
824 create: function (tagName, className, container) {
825
826 var el = document.createElement(tagName);
827 el.className = className;
828
829 if (container) {
830 container.appendChild(el);
831 }
832
833 return el;
834 },
835
836 disableTextSelection: function () {
837 if (document.selection && document.selection.empty) {
838 document.selection.empty();
839 }
840 if (!this._onselectstart) {
841 this._onselectstart = document.onselectstart || null;
842 document.onselectstart = L.Util.falseFn;
843 }
844 },
845
846 enableTextSelection: function () {
847 if (document.onselectstart === L.Util.falseFn) {
848 document.onselectstart = this._onselectstart;
849 this._onselectstart = null;
850 }
851 },
852
853 hasClass: function (el, name) {
854 return (el.className.length > 0) &&
855 new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
856 },
857
858 addClass: function (el, name) {
859 if (!L.DomUtil.hasClass(el, name)) {
860 el.className += (el.className ? ' ' : '') + name;
861 }
862 },
863
864 removeClass: function (el, name) {
865
866 function replaceFn(w, match) {
867 if (match === name) { return ''; }
868 return w;
869 }
870
871 el.className = el.className
872 .replace(/(\S+)\s*/g, replaceFn)
873 .replace(/(^\s+|\s+$)/, '');
874 },
875
876 setOpacity: function (el, value) {
877
878 if ('opacity' in el.style) {
879 el.style.opacity = value;
880
881 } else if ('filter' in el.style) {
882
883 var filter = false,
884 filterName = 'DXImageTransform.Microsoft.Alpha';
885
886 // filters collection throws an error if we try to retrieve a filter that doesn't exist
887 try { filter = el.filters.item(filterName); } catch (e) {}
888
889 value = Math.round(value * 100);
890
891 if (filter) {
892 filter.Enabled = (value !== 100);
893 filter.Opacity = value;
894 } else {
895 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
896 }
897 }
898 },
899
900 testProp: function (props) {
901
902 var style = document.documentElement.style;
903
904 for (var i = 0; i < props.length; i++) {
905 if (props[i] in style) {
906 return props[i];
907 }
908 }
909 return false;
910 },
911
912 getTranslateString: function (point) {
913 // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
914 // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
915 // (same speed either way), Opera 12 doesn't support translate3d
916
917 var is3d = L.Browser.webkit3d,
918 open = 'translate' + (is3d ? '3d' : '') + '(',
919 close = (is3d ? ',0' : '') + ')';
920
921 return open + point.x + 'px,' + point.y + 'px' + close;
922 },
923
924 getScaleString: function (scale, origin) {
925
926 var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
927 scaleStr = ' scale(' + scale + ') ';
928
929 return preTranslateStr + scaleStr;
930 },
931
932 setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
933
934 el._leaflet_pos = point;
935
936 if (!disable3D && L.Browser.any3d) {
937 el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
938
939 // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
940 if (L.Browser.mobileWebkit3d) {
941 el.style.WebkitBackfaceVisibility = 'hidden';
942 }
943 } else {
944 el.style.left = point.x + 'px';
945 el.style.top = point.y + 'px';
946 }
947 },
948
949 getPosition: function (el) {
950 // this method is only used for elements previously positioned using setPosition,
951 // so it's safe to cache the position for performance
952 return el._leaflet_pos;
953 }
954 };
955
956
957 // prefix style property names
958
959 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
960 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
961
962 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
963 // the same for the transitionend event, in particular the Android 4.1 stock browser
964
965 L.DomUtil.TRANSITION = L.DomUtil.testProp(
966 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
967
968 L.DomUtil.TRANSITION_END =
969 L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
970 L.DomUtil.TRANSITION + 'End' : 'transitionend';
971
972
973 /*
974 * L.LatLng represents a geographical point with latitude and longitude coordinates.
975 */
976
977 L.LatLng = function (rawLat, rawLng) { // (Number, Number)
978 var lat = parseFloat(rawLat),
979 lng = parseFloat(rawLng);
980
981 if (isNaN(lat) || isNaN(lng)) {
982 throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
983 }
984
985 this.lat = lat;
986 this.lng = lng;
987 };
988
989 L.extend(L.LatLng, {
990 DEG_TO_RAD: Math.PI / 180,
991 RAD_TO_DEG: 180 / Math.PI,
992 MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
993 });
994
995 L.LatLng.prototype = {
996 equals: function (obj) { // (LatLng) -> Boolean
997 if (!obj) { return false; }
998
999 obj = L.latLng(obj);
1000
1001 var margin = Math.max(
1002 Math.abs(this.lat - obj.lat),
1003 Math.abs(this.lng - obj.lng));
1004
1005 return margin <= L.LatLng.MAX_MARGIN;
1006 },
1007
1008 toString: function (precision) { // (Number) -> String
1009 return 'LatLng(' +
1010 L.Util.formatNum(this.lat, precision) + ', ' +
1011 L.Util.formatNum(this.lng, precision) + ')';
1012 },
1013
1014 // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
1015 // TODO move to projection code, LatLng shouldn't know about Earth
1016 distanceTo: function (other) { // (LatLng) -> Number
1017 other = L.latLng(other);
1018
1019 var R = 6378137, // earth radius in meters
1020 d2r = L.LatLng.DEG_TO_RAD,
1021 dLat = (other.lat - this.lat) * d2r,
1022 dLon = (other.lng - this.lng) * d2r,
1023 lat1 = this.lat * d2r,
1024 lat2 = other.lat * d2r,
1025 sin1 = Math.sin(dLat / 2),
1026 sin2 = Math.sin(dLon / 2);
1027
1028 var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
1029
1030 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1031 },
1032
1033 wrap: function (a, b) { // (Number, Number) -> LatLng
1034 var lng = this.lng;
1035
1036 a = a || -180;
1037 b = b || 180;
1038
1039 lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
1040
1041 return new L.LatLng(this.lat, lng);
1042 }
1043 };
1044
1045 L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
1046 if (a instanceof L.LatLng) {
1047 return a;
1048 }
1049 if (L.Util.isArray(a)) {
1050 return new L.LatLng(a[0], a[1]);
1051 }
1052 if (isNaN(a)) {
1053 return a;
1054 }
1055 return new L.LatLng(a, b);
1056 };
1057
1058
1059
1060 /*
1061 * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
1062 */
1063
1064 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1065 if (!southWest) { return; }
1066
1067 var latlngs = northEast ? [southWest, northEast] : southWest;
1068
1069 for (var i = 0, len = latlngs.length; i < len; i++) {
1070 this.extend(latlngs[i]);
1071 }
1072 };
1073
1074 L.LatLngBounds.prototype = {
1075 // extend the bounds to contain the given point or bounds
1076 extend: function (obj) { // (LatLng) or (LatLngBounds)
1077 if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
1078 obj = L.latLng(obj);
1079 } else {
1080 obj = L.latLngBounds(obj);
1081 }
1082
1083 if (obj instanceof L.LatLng) {
1084 if (!this._southWest && !this._northEast) {
1085 this._southWest = new L.LatLng(obj.lat, obj.lng);
1086 this._northEast = new L.LatLng(obj.lat, obj.lng);
1087 } else {
1088 this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
1089 this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
1090
1091 this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
1092 this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
1093 }
1094 } else if (obj instanceof L.LatLngBounds) {
1095 this.extend(obj._southWest);
1096 this.extend(obj._northEast);
1097 }
1098 return this;
1099 },
1100
1101 // extend the bounds by a percentage
1102 pad: function (bufferRatio) { // (Number) -> LatLngBounds
1103 var sw = this._southWest,
1104 ne = this._northEast,
1105 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1106 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1107
1108 return new L.LatLngBounds(
1109 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1110 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1111 },
1112
1113 getCenter: function () { // -> LatLng
1114 return new L.LatLng(
1115 (this._southWest.lat + this._northEast.lat) / 2,
1116 (this._southWest.lng + this._northEast.lng) / 2);
1117 },
1118
1119 getSouthWest: function () {
1120 return this._southWest;
1121 },
1122
1123 getNorthEast: function () {
1124 return this._northEast;
1125 },
1126
1127 getNorthWest: function () {
1128 return new L.LatLng(this._northEast.lat, this._southWest.lng);
1129 },
1130
1131 getSouthEast: function () {
1132 return new L.LatLng(this._southWest.lat, this._northEast.lng);
1133 },
1134
1135 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1136 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1137 obj = L.latLng(obj);
1138 } else {
1139 obj = L.latLngBounds(obj);
1140 }
1141
1142 var sw = this._southWest,
1143 ne = this._northEast,
1144 sw2, ne2;
1145
1146 if (obj instanceof L.LatLngBounds) {
1147 sw2 = obj.getSouthWest();
1148 ne2 = obj.getNorthEast();
1149 } else {
1150 sw2 = ne2 = obj;
1151 }
1152
1153 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1154 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1155 },
1156
1157 intersects: function (bounds) { // (LatLngBounds)
1158 bounds = L.latLngBounds(bounds);
1159
1160 var sw = this._southWest,
1161 ne = this._northEast,
1162 sw2 = bounds.getSouthWest(),
1163 ne2 = bounds.getNorthEast(),
1164
1165 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1166 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1167
1168 return latIntersects && lngIntersects;
1169 },
1170
1171 toBBoxString: function () {
1172 var sw = this._southWest,
1173 ne = this._northEast;
1174
1175 return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
1176 },
1177
1178 equals: function (bounds) { // (LatLngBounds)
1179 if (!bounds) { return false; }
1180
1181 bounds = L.latLngBounds(bounds);
1182
1183 return this._southWest.equals(bounds.getSouthWest()) &&
1184 this._northEast.equals(bounds.getNorthEast());
1185 },
1186
1187 isValid: function () {
1188 return !!(this._southWest && this._northEast);
1189 }
1190 };
1191
1192 //TODO International date line?
1193
1194 L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
1195 if (!a || a instanceof L.LatLngBounds) {
1196 return a;
1197 }
1198 return new L.LatLngBounds(a, b);
1199 };
1200
1201
1202 /*
1203 * L.Projection contains various geographical projections used by CRS classes.
1204 */
1205
1206 L.Projection = {};
1207
1208
1209 /*
1210 * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
1211 */
1212
1213 L.Projection.SphericalMercator = {
1214 MAX_LATITUDE: 85.0511287798,
1215
1216 project: function (latlng) { // (LatLng) -> Point
1217 var d = L.LatLng.DEG_TO_RAD,
1218 max = this.MAX_LATITUDE,
1219 lat = Math.max(Math.min(max, latlng.lat), -max),
1220 x = latlng.lng * d,
1221 y = lat * d;
1222
1223 y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
1224
1225 return new L.Point(x, y);
1226 },
1227
1228 unproject: function (point) { // (Point, Boolean) -> LatLng
1229 var d = L.LatLng.RAD_TO_DEG,
1230 lng = point.x * d,
1231 lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
1232
1233 return new L.LatLng(lat, lng);
1234 }
1235 };
1236
1237
1238 /*
1239 * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
1240 */
1241
1242 L.Projection.LonLat = {
1243 project: function (latlng) {
1244 return new L.Point(latlng.lng, latlng.lat);
1245 },
1246
1247 unproject: function (point) {
1248 return new L.LatLng(point.y, point.x);
1249 }
1250 };
1251
1252
1253 /*
1254 * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
1255 */
1256
1257 L.CRS = {
1258 latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
1259 var projectedPoint = this.projection.project(latlng),
1260 scale = this.scale(zoom);
1261
1262 return this.transformation._transform(projectedPoint, scale);
1263 },
1264
1265 pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
1266 var scale = this.scale(zoom),
1267 untransformedPoint = this.transformation.untransform(point, scale);
1268
1269 return this.projection.unproject(untransformedPoint);
1270 },
1271
1272 project: function (latlng) {
1273 return this.projection.project(latlng);
1274 },
1275
1276 scale: function (zoom) {
1277 return 256 * Math.pow(2, zoom);
1278 }
1279 };
5 (function (window, document, undefined) {
6 var oldL = window.L,
7 L = {};
8
9 L.version = '0.6';
10
11 // define Leaflet for Node module pattern loaders, including Browserify
12 if (typeof module === 'object' && typeof module.exports === 'object') {
13 module.exports = L;
14
15 // define Leaflet as an AMD module
16 } else if (typeof define === 'function' && define.amd) {
17 define(L);
18 }
19
20 // define Leaflet as a global L variable, saving the original L to restore later if needed
21
22 L.noConflict = function () {
23 window.L = oldL;
24 return this;
25 };
26
27 window.L = L;
28
29
30 /*
31 * L.Util contains various utility functions used throughout Leaflet code.
32 */
33
34 L.Util = {
35 extend: function (dest) { // (Object[, Object, ...]) ->
36 var sources = Array.prototype.slice.call(arguments, 1),
37 i, j, len, src;
38
39 for (j = 0, len = sources.length; j < len; j++) {
40 src = sources[j] || {};
41 for (i in src) {
42 if (src.hasOwnProperty(i)) {
43 dest[i] = src[i];
44 }
45 }
46 }
47 return dest;
48 },
49
50 bind: function (fn, obj) { // (Function, Object) -> Function
51 var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
52 return function () {
53 return fn.apply(obj, args || arguments);
54 };
55 },
56
57 stamp: (function () {
58 var lastId = 0,
59 key = '_leaflet_id';
60 return function (obj) {
61 obj[key] = obj[key] || ++lastId;
62 return obj[key];
63 };
64 }()),
65
66 invokeEach: function (obj, method, context) {
67 var i, args;
68
69 if (typeof obj === 'object') {
70 args = Array.prototype.slice.call(arguments, 3);
71
72 for (i in obj) {
73 method.apply(context, [i, obj[i]].concat(args));
74 }
75 return true;
76 }
77
78 return false;
79 },
80
81 limitExecByInterval: function (fn, time, context) {
82 var lock, execOnUnlock;
83
84 return function wrapperFn() {
85 var args = arguments;
86
87 if (lock) {
88 execOnUnlock = true;
89 return;
90 }
91
92 lock = true;
93
94 setTimeout(function () {
95 lock = false;
96
97 if (execOnUnlock) {
98 wrapperFn.apply(context, args);
99 execOnUnlock = false;
100 }
101 }, time);
102
103 fn.apply(context, args);
104 };
105 },
106
107 falseFn: function () {
108 return false;
109 },
110
111 formatNum: function (num, digits) {
112 var pow = Math.pow(10, digits || 5);
113 return Math.round(num * pow) / pow;
114 },
115
116 trim: function (str) {
117 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
118 },
119
120 splitWords: function (str) {
121 return L.Util.trim(str).split(/\s+/);
122 },
123
124 setOptions: function (obj, options) {
125 obj.options = L.extend({}, obj.options, options);
126 return obj.options;
127 },
128
129 getParamString: function (obj, existingUrl, uppercase) {
130 var params = [];
131 for (var i in obj) {
132 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
133 }
134 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
135 },
136
137 template: function (str, data) {
138 return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
139 var value = data[key];
140 if (value === undefined) {
141 throw new Error('No value provided for variable ' + str);
142 } else if (typeof value === 'function') {
143 value = value(data);
144 }
145 return value;
146 });
147 },
148
149 isArray: function (obj) {
150 return (Object.prototype.toString.call(obj) === '[object Array]');
151 },
152
153 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
154 };
155
156 (function () {
157
158 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
159
160 function getPrefixed(name) {
161 var i, fn,
162 prefixes = ['webkit', 'moz', 'o', 'ms'];
163
164 for (i = 0; i < prefixes.length && !fn; i++) {
165 fn = window[prefixes[i] + name];
166 }
167
168 return fn;
169 }
170
171 var lastTime = 0;
172
173 function timeoutDefer(fn) {
174 var time = +new Date(),
175 timeToCall = Math.max(0, 16 - (time - lastTime));
176
177 lastTime = time + timeToCall;
178 return window.setTimeout(fn, timeToCall);
179 }
180
181 var requestFn = window.requestAnimationFrame ||
182 getPrefixed('RequestAnimationFrame') || timeoutDefer;
183
184 var cancelFn = window.cancelAnimationFrame ||
185 getPrefixed('CancelAnimationFrame') ||
186 getPrefixed('CancelRequestAnimationFrame') ||
187 function (id) { window.clearTimeout(id); };
188
189
190 L.Util.requestAnimFrame = function (fn, context, immediate, element) {
191 fn = L.bind(fn, context);
192
193 if (immediate && requestFn === timeoutDefer) {
194 fn();
195 } else {
196 return requestFn.call(window, fn, element);
197 }
198 };
199
200 L.Util.cancelAnimFrame = function (id) {
201 if (id) {
202 cancelFn.call(window, id);
203 }
204 };
205
206 }());
207
208 // shortcuts for most used utility functions
209 L.extend = L.Util.extend;
210 L.bind = L.Util.bind;
211 L.stamp = L.Util.stamp;
212 L.setOptions = L.Util.setOptions;
213
214
215 /*
216 * L.Class powers the OOP facilities of the library.
217 * Thanks to John Resig and Dean Edwards for inspiration!
218 */
219
220 L.Class = function () {};
221
222 L.Class.extend = function (props) {
223
224 // extended class with the new prototype
225 var NewClass = function () {
226
227 // call the constructor
228 if (this.initialize) {
229 this.initialize.apply(this, arguments);
230 }
231
232 // call all constructor hooks
233 if (this._initHooks) {
234 this.callInitHooks();
235 }
236 };
237
238 // instantiate class without calling constructor
239 var F = function () {};
240 F.prototype = this.prototype;
241
242 var proto = new F();
243 proto.constructor = NewClass;
244
245 NewClass.prototype = proto;
246
247 //inherit parent's statics
248 for (var i in this) {
249 if (this.hasOwnProperty(i) && i !== 'prototype') {
250 NewClass[i] = this[i];
251 }
252 }
253
254 // mix static properties into the class
255 if (props.statics) {
256 L.extend(NewClass, props.statics);
257 delete props.statics;
258 }
259
260 // mix includes into the prototype
261 if (props.includes) {
262 L.Util.extend.apply(null, [proto].concat(props.includes));
263 delete props.includes;
264 }
265
266 // merge options
267 if (props.options && proto.options) {
268 props.options = L.extend({}, proto.options, props.options);
269 }
270
271 // mix given properties into the prototype
272 L.extend(proto, props);
273
274 proto._initHooks = [];
275
276 var parent = this;
277 // jshint camelcase: false
278 NewClass.__super__ = parent.prototype;
279
280 // add method for calling all hooks
281 proto.callInitHooks = function () {
282
283 if (this._initHooksCalled) { return; }
284
285 if (parent.prototype.callInitHooks) {
286 parent.prototype.callInitHooks.call(this);
287 }
288
289 this._initHooksCalled = true;
290
291 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
292 proto._initHooks[i].call(this);
293 }
294 };
295
296 return NewClass;
297 };
298
299
300 // method for adding properties to prototype
301 L.Class.include = function (props) {
302 L.extend(this.prototype, props);
303 };
304
305 // merge new default options to the Class
306 L.Class.mergeOptions = function (options) {
307 L.extend(this.prototype.options, options);
308 };
309
310 // add a constructor hook
311 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
312 var args = Array.prototype.slice.call(arguments, 1);
313
314 var init = typeof fn === 'function' ? fn : function () {
315 this[fn].apply(this, args);
316 };
317
318 this.prototype._initHooks = this.prototype._initHooks || [];
319 this.prototype._initHooks.push(init);
320 };
321
322
323 /*
324 * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
325 */
326
327 var eventsKey = '_leaflet_events';
328
329 L.Mixin = {};
330
331 L.Mixin.Events = {
332
333 addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
334
335 // types can be a map of types/handlers
336 if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
337
338 var events = this[eventsKey] = this[eventsKey] || {},
339 contextId = context && L.stamp(context),
340 i, len, event, type, indexKey, indexLenKey, typeIndex;
341
342 // types can be a string of space-separated words
343 types = L.Util.splitWords(types);
344
345 for (i = 0, len = types.length; i < len; i++) {
346 event = {
347 action: fn,
348 context: context || this
349 };
350 type = types[i];
351
352 if (context) {
353 // store listeners of a particular context in a separate hash (if it has an id)
354 // gives a major performance boost when removing thousands of map layers
355
356 indexKey = type + '_idx';
357 indexLenKey = indexKey + '_len';
358
359 typeIndex = events[indexKey] = events[indexKey] || {};
360
361 if (!typeIndex[contextId]) {
362 typeIndex[contextId] = [];
363
364 // keep track of the number of keys in the index to quickly check if it's empty
365 events[indexLenKey] = (events[indexLenKey] || 0) + 1;
366 }
367
368 typeIndex[contextId].push(event);
369
370
371 } else {
372 events[type] = events[type] || [];
373 events[type].push(event);
374 }
375 }
376
377 return this;
378 },
379
380 hasEventListeners: function (type) { // (String) -> Boolean
381 var events = this[eventsKey];
382 return !!events && ((type in events && events[type].length > 0) ||
383 (type + '_idx' in events && events[type + '_idx_len'] > 0));
384 },
385
386 removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
387
388 if (!this[eventsKey]) {
389 return this;
390 }
391
392 if (!types) {
393 return this.clearAllEventListeners();
394 }
395
396 if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
397
398 var events = this[eventsKey],
399 contextId = context && L.stamp(context),
400 i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
401
402 types = L.Util.splitWords(types);
403
404 for (i = 0, len = types.length; i < len; i++) {
405 type = types[i];
406 indexKey = type + '_idx';
407 indexLenKey = indexKey + '_len';
408
409 typeIndex = events[indexKey];
410
411 if (!fn) {
412 // clear all listeners for a type if function isn't specified
413 delete events[type];
414 delete events[indexKey];
415
416 } else {
417 listeners = context && typeIndex ? typeIndex[contextId] : events[type];
418
419 if (listeners) {
420 for (j = listeners.length - 1; j >= 0; j--) {
421 if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
422 removed = listeners.splice(j, 1);
423 // set the old action to a no-op, because it is possible
424 // that the listener is being iterated over as part of a dispatch
425 removed[0].action = L.Util.falseFn;
426 }
427 }
428
429 if (context && typeIndex && (listeners.length === 0)) {
430 delete typeIndex[contextId];
431 events[indexLenKey]--;
432 }
433 }
434 }
435 }
436
437 return this;
438 },
439
440 clearAllEventListeners: function () {
441 delete this[eventsKey];
442 return this;
443 },
444
445 fireEvent: function (type, data) { // (String[, Object])
446 if (!this.hasEventListeners(type)) {
447 return this;
448 }
449
450 var event = L.Util.extend({}, data, { type: type, target: this });
451
452 var events = this[eventsKey],
453 listeners, i, len, typeIndex, contextId;
454
455 if (events[type]) {
456 // make sure adding/removing listeners inside other listeners won't cause infinite loop
457 listeners = events[type].slice();
458
459 for (i = 0, len = listeners.length; i < len; i++) {
460 listeners[i].action.call(listeners[i].context || this, event);
461 }
462 }
463
464 // fire event for the context-indexed listeners as well
465 typeIndex = events[type + '_idx'];
466
467 for (contextId in typeIndex) {
468 listeners = typeIndex[contextId].slice();
469
470 if (listeners) {
471 for (i = 0, len = listeners.length; i < len; i++) {
472 listeners[i].action.call(listeners[i].context || this, event);
473 }
474 }
475 }
476
477 return this;
478 },
479
480 addOneTimeEventListener: function (types, fn, context) {
481
482 if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
483
484 var handler = L.bind(function () {
485 this
486 .removeEventListener(types, fn, context)
487 .removeEventListener(types, handler, context);
488 }, this);
489
490 return this
491 .addEventListener(types, fn, context)
492 .addEventListener(types, handler, context);
493 }
494 };
495
496 L.Mixin.Events.on = L.Mixin.Events.addEventListener;
497 L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
498 L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
499 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
500
501
502 /*
503 * L.Browser handles different browser and feature detections for internal Leaflet use.
504 */
505
506 (function () {
507
508 var ie = !!window.ActiveXObject,
509 ie6 = ie && !window.XMLHttpRequest,
510 ie7 = ie && !document.querySelector,
511 ielt9 = ie && !document.addEventListener,
512
513 // terrible browser detection to work around Safari / iOS / Android browser bugs
514 ua = navigator.userAgent.toLowerCase(),
515 webkit = ua.indexOf('webkit') !== -1,
516 chrome = ua.indexOf('chrome') !== -1,
517 phantomjs = ua.indexOf('phantom') !== -1,
518 android = ua.indexOf('android') !== -1,
519 android23 = ua.search('android [23]') !== -1,
520
521 mobile = typeof orientation !== undefined + '',
522 msTouch = window.navigator && window.navigator.msPointerEnabled &&
523 window.navigator.msMaxTouchPoints,
524 retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
525 ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
526 window.matchMedia('(min-resolution:144dpi)').matches),
527
528 doc = document.documentElement,
529 ie3d = ie && ('transition' in doc.style),
530 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
531 gecko3d = 'MozPerspective' in doc.style,
532 opera3d = 'OTransition' in doc.style,
533 any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
534
535
536 // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch.
537 // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151
538
539 var touch = !window.L_NO_TOUCH && !phantomjs && (function () {
540
541 var startName = 'ontouchstart';
542
543 // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc.
544 if (msTouch || (startName in doc)) {
545 return true;
546 }
547
548 // Firefox/Gecko
549 var div = document.createElement('div'),
550 supported = false;
551
552 if (!div.setAttribute) {
553 return false;
554 }
555 div.setAttribute(startName, 'return;');
556
557 if (typeof div[startName] === 'function') {
558 supported = true;
559 }
560
561 div.removeAttribute(startName);
562 div = null;
563
564 return supported;
565 }());
566
567
568 L.Browser = {
569 ie: ie,
570 ie6: ie6,
571 ie7: ie7,
572 ielt9: ielt9,
573 webkit: webkit,
574
575 android: android,
576 android23: android23,
577
578 chrome: chrome,
579
580 ie3d: ie3d,
581 webkit3d: webkit3d,
582 gecko3d: gecko3d,
583 opera3d: opera3d,
584 any3d: any3d,
585
586 mobile: mobile,
587 mobileWebkit: mobile && webkit,
588 mobileWebkit3d: mobile && webkit3d,
589 mobileOpera: mobile && window.opera,
590
591 touch: touch,
592 msTouch: msTouch,
593
594 retina: retina
595 };
596
597 }());
598
599
600 /*
601 * L.Point represents a point with x and y coordinates.
602 */
603
604 L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
605 this.x = (round ? Math.round(x) : x);
606 this.y = (round ? Math.round(y) : y);
607 };
608
609 L.Point.prototype = {
610
611 clone: function () {
612 return new L.Point(this.x, this.y);
613 },
614
615 // non-destructive, returns a new point
616 add: function (point) {
617 return this.clone()._add(L.point(point));
618 },
619
620 // destructive, used directly for performance in situations where it's safe to modify existing point
621 _add: function (point) {
622 this.x += point.x;
623 this.y += point.y;
624 return this;
625 },
626
627 subtract: function (point) {
628 return this.clone()._subtract(L.point(point));
629 },
630
631 _subtract: function (point) {
632 this.x -= point.x;
633 this.y -= point.y;
634 return this;
635 },
636
637 divideBy: function (num) {
638 return this.clone()._divideBy(num);
639 },
640
641 _divideBy: function (num) {
642 this.x /= num;
643 this.y /= num;
644 return this;
645 },
646
647 multiplyBy: function (num) {
648 return this.clone()._multiplyBy(num);
649 },
650
651 _multiplyBy: function (num) {
652 this.x *= num;
653 this.y *= num;
654 return this;
655 },
656
657 round: function () {
658 return this.clone()._round();
659 },
660
661 _round: function () {
662 this.x = Math.round(this.x);
663 this.y = Math.round(this.y);
664 return this;
665 },
666
667 floor: function () {
668 return this.clone()._floor();
669 },
670
671 _floor: function () {
672 this.x = Math.floor(this.x);
673 this.y = Math.floor(this.y);
674 return this;
675 },
676
677 distanceTo: function (point) {
678 point = L.point(point);
679
680 var x = point.x - this.x,
681 y = point.y - this.y;
682
683 return Math.sqrt(x * x + y * y);
684 },
685
686 equals: function (point) {
687 point = L.point(point);
688
689 return point.x === this.x &&
690 point.y === this.y;
691 },
692
693 contains: function (point) {
694 point = L.point(point);
695
696 return Math.abs(point.x) <= Math.abs(this.x) &&
697 Math.abs(point.y) <= Math.abs(this.y);
698 },
699
700 toString: function () {
701 return 'Point(' +
702 L.Util.formatNum(this.x) + ', ' +
703 L.Util.formatNum(this.y) + ')';
704 }
705 };
706
707 L.point = function (x, y, round) {
708 if (x instanceof L.Point) {
709 return x;
710 }
711 if (L.Util.isArray(x)) {
712 return new L.Point(x[0], x[1]);
713 }
714 if (x === undefined || x === null) {
715 return x;
716 }
717 return new L.Point(x, y, round);
718 };
719
720
721 /*
722 * L.Bounds represents a rectangular area on the screen in pixel coordinates.
723 */
724
725 L.Bounds = function (a, b) { //(Point, Point) or Point[]
726 if (!a) { return; }
727
728 var points = b ? [a, b] : a;
729
730 for (var i = 0, len = points.length; i < len; i++) {
731 this.extend(points[i]);
732 }
733 };
734
735 L.Bounds.prototype = {
736 // extend the bounds to contain the given point
737 extend: function (point) { // (Point)
738 point = L.point(point);
739
740 if (!this.min && !this.max) {
741 this.min = point.clone();
742 this.max = point.clone();
743 } else {
744 this.min.x = Math.min(point.x, this.min.x);
745 this.max.x = Math.max(point.x, this.max.x);
746 this.min.y = Math.min(point.y, this.min.y);
747 this.max.y = Math.max(point.y, this.max.y);
748 }
749 return this;
750 },
751
752 getCenter: function (round) { // (Boolean) -> Point
753 return new L.Point(
754 (this.min.x + this.max.x) / 2,
755 (this.min.y + this.max.y) / 2, round);
756 },
757
758 getBottomLeft: function () { // -> Point
759 return new L.Point(this.min.x, this.max.y);
760 },
761
762 getTopRight: function () { // -> Point
763 return new L.Point(this.max.x, this.min.y);
764 },
765
766 getSize: function () {
767 return this.max.subtract(this.min);
768 },
769
770 contains: function (obj) { // (Bounds) or (Point) -> Boolean
771 var min, max;
772
773 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
774 obj = L.point(obj);
775 } else {
776 obj = L.bounds(obj);
777 }
778
779 if (obj instanceof L.Bounds) {
780 min = obj.min;
781 max = obj.max;
782 } else {
783 min = max = obj;
784 }
785
786 return (min.x >= this.min.x) &&
787 (max.x <= this.max.x) &&
788 (min.y >= this.min.y) &&
789 (max.y <= this.max.y);
790 },
791
792 intersects: function (bounds) { // (Bounds) -> Boolean
793 bounds = L.bounds(bounds);
794
795 var min = this.min,
796 max = this.max,
797 min2 = bounds.min,
798 max2 = bounds.max,
799 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
800 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
801
802 return xIntersects && yIntersects;
803 },
804
805 isValid: function () {
806 return !!(this.min && this.max);
807 }
808 };
809
810 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
811 if (!a || a instanceof L.Bounds) {
812 return a;
813 }
814 return new L.Bounds(a, b);
815 };
816
817
818 /*
819 * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
820 */
821
822 L.Transformation = function (a, b, c, d) {
823 this._a = a;
824 this._b = b;
825 this._c = c;
826 this._d = d;
827 };
828
829 L.Transformation.prototype = {
830 transform: function (point, scale) { // (Point, Number) -> Point
831 return this._transform(point.clone(), scale);
832 },
833
834 // destructive transform (faster)
835 _transform: function (point, scale) {
836 scale = scale || 1;
837 point.x = scale * (this._a * point.x + this._b);
838 point.y = scale * (this._c * point.y + this._d);
839 return point;
840 },
841
842 untransform: function (point, scale) {
843 scale = scale || 1;
844 return new L.Point(
845 (point.x / scale - this._b) / this._a,
846 (point.y / scale - this._d) / this._c);
847 }
848 };
849
850
851 /*
852 * L.DomUtil contains various utility functions for working with DOM.
853 */
854
855 L.DomUtil = {
856 get: function (id) {
857 return (typeof id === 'string' ? document.getElementById(id) : id);
858 },
859
860 getStyle: function (el, style) {
861
862 var value = el.style[style];
863
864 if (!value && el.currentStyle) {
865 value = el.currentStyle[style];
866 }
867
868 if ((!value || value === 'auto') && document.defaultView) {
869 var css = document.defaultView.getComputedStyle(el, null);
870 value = css ? css[style] : null;
871 }
872
873 return value === 'auto' ? null : value;
874 },
875
876 getViewportOffset: function (element) {
877
878 var top = 0,
879 left = 0,
880 el = element,
881 docBody = document.body,
882 docEl = document.documentElement,
883 pos,
884 ie7 = L.Browser.ie7;
885
886 do {
887 top += el.offsetTop || 0;
888 left += el.offsetLeft || 0;
889
890 //add borders
891 top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
892 left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
893
894 pos = L.DomUtil.getStyle(el, 'position');
895
896 if (el.offsetParent === docBody && pos === 'absolute') { break; }
897
898 if (pos === 'fixed') {
899 top += docBody.scrollTop || docEl.scrollTop || 0;
900 left += docBody.scrollLeft || docEl.scrollLeft || 0;
901 break;
902 }
903
904 if (pos === 'relative' && !el.offsetLeft) {
905 var width = L.DomUtil.getStyle(el, 'width'),
906 maxWidth = L.DomUtil.getStyle(el, 'max-width'),
907 r = el.getBoundingClientRect();
908
909 if (width !== 'none' || maxWidth !== 'none') {
910 left += r.left + el.clientLeft;
911 }
912
913 //calculate full y offset since we're breaking out of the loop
914 top += r.top + (docBody.scrollTop || docEl.scrollTop || 0);
915
916 break;
917 }
918
919 el = el.offsetParent;
920
921 } while (el);
922
923 el = element;
924
925 do {
926 if (el === docBody) { break; }
927
928 top -= el.scrollTop || 0;
929 left -= el.scrollLeft || 0;
930
931 // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
932 // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
933 if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
934 left += el.scrollWidth - el.clientWidth;
935
936 // ie7 shows the scrollbar by default and provides clientWidth counting it, so we
937 // need to add it back in if it is visible; scrollbar is on the left as we are RTL
938 if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' &&
939 L.DomUtil.getStyle(el, 'overflow') !== 'hidden') {
940 left += 17;
941 }
942 }
943
944 el = el.parentNode;
945 } while (el);
946
947 return new L.Point(left, top);
948 },
949
950 documentIsLtr: function () {
951 if (!L.DomUtil._docIsLtrCached) {
952 L.DomUtil._docIsLtrCached = true;
953 L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
954 }
955 return L.DomUtil._docIsLtr;
956 },
957
958 create: function (tagName, className, container) {
959
960 var el = document.createElement(tagName);
961 el.className = className;
962
963 if (container) {
964 container.appendChild(el);
965 }
966
967 return el;
968 },
969
970 hasClass: function (el, name) {
971 return (el.className.length > 0) &&
972 new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className);
973 },
974
975 addClass: function (el, name) {
976 if (!L.DomUtil.hasClass(el, name)) {
977 el.className += (el.className ? ' ' : '') + name;
978 }
979 },
980
981 removeClass: function (el, name) {
982 el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' '));
983 },
984
985 setOpacity: function (el, value) {
986
987 if ('opacity' in el.style) {
988 el.style.opacity = value;
989
990 } else if ('filter' in el.style) {
991
992 var filter = false,
993 filterName = 'DXImageTransform.Microsoft.Alpha';
994
995 // filters collection throws an error if we try to retrieve a filter that doesn't exist
996 try {
997 filter = el.filters.item(filterName);
998 } catch (e) {
999 // don't set opacity to 1 if we haven't already set an opacity,
1000 // it isn't needed and breaks transparent pngs.
1001 if (value === 1) { return; }
1002 }
1003
1004 value = Math.round(value * 100);
1005
1006 if (filter) {
1007 filter.Enabled = (value !== 100);
1008 filter.Opacity = value;
1009 } else {
1010 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1011 }
1012 }
1013 },
1014
1015 testProp: function (props) {
1016
1017 var style = document.documentElement.style;
1018
1019 for (var i = 0; i < props.length; i++) {
1020 if (props[i] in style) {
1021 return props[i];
1022 }
1023 }
1024 return false;
1025 },
1026
1027 getTranslateString: function (point) {
1028 // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
1029 // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
1030 // (same speed either way), Opera 12 doesn't support translate3d
1031
1032 var is3d = L.Browser.webkit3d,
1033 open = 'translate' + (is3d ? '3d' : '') + '(',
1034 close = (is3d ? ',0' : '') + ')';
1035
1036 return open + point.x + 'px,' + point.y + 'px' + close;
1037 },
1038
1039 getScaleString: function (scale, origin) {
1040
1041 var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
1042 scaleStr = ' scale(' + scale + ') ';
1043
1044 return preTranslateStr + scaleStr;
1045 },
1046
1047 setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
1048
1049 // jshint camelcase: false
1050 el._leaflet_pos = point;
1051
1052 if (!disable3D && L.Browser.any3d) {
1053 el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
1054
1055 // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
1056 if (L.Browser.mobileWebkit3d) {
1057 el.style.WebkitBackfaceVisibility = 'hidden';
1058 }
1059 } else {
1060 el.style.left = point.x + 'px';
1061 el.style.top = point.y + 'px';
1062 }
1063 },
1064
1065 getPosition: function (el) {
1066 // this method is only used for elements previously positioned using setPosition,
1067 // so it's safe to cache the position for performance
1068
1069 // jshint camelcase: false
1070 return el._leaflet_pos;
1071 }
1072 };
1073
1074
1075 // prefix style property names
1076
1077 L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1078 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1079
1080 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1081 // the same for the transitionend event, in particular the Android 4.1 stock browser
1082
1083 L.DomUtil.TRANSITION = L.DomUtil.testProp(
1084 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1085
1086 L.DomUtil.TRANSITION_END =
1087 L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
1088 L.DomUtil.TRANSITION + 'End' : 'transitionend';
1089
1090 (function () {
1091 var userSelectProperty = L.DomUtil.testProp(
1092 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1093
1094 var userDragProperty = L.DomUtil.testProp(
1095 ['userDrag', 'WebkitUserDrag', 'OUserDrag', 'MozUserDrag', 'msUserDrag']);
1096
1097 L.extend(L.DomUtil, {
1098 disableTextSelection: function () {
1099 if (userSelectProperty) {
1100 var style = document.documentElement.style;
1101 this._userSelect = style[userSelectProperty];
1102 style[userSelectProperty] = 'none';
1103 } else {
1104 L.DomEvent.on(window, 'selectstart', L.DomEvent.stop);
1105 }
1106 },
1107
1108 enableTextSelection: function () {
1109 if (userSelectProperty) {
1110 document.documentElement.style[userSelectProperty] = this._userSelect;
1111 delete this._userSelect;
1112 } else {
1113 L.DomEvent.off(window, 'selectstart', L.DomEvent.stop);
1114 }
1115 },
1116
1117 disableImageDrag: function () {
1118 if (userDragProperty) {
1119 var style = document.documentElement.style;
1120 this._userDrag = style[userDragProperty];
1121 style[userDragProperty] = 'none';
1122 } else {
1123 L.DomEvent.on(window, 'dragstart', L.DomEvent.stop);
1124 }
1125 },
1126
1127 enableImageDrag: function () {
1128 if (userDragProperty) {
1129 document.documentElement.style[userDragProperty] = this._userDrag;
1130 delete this._userDrag;
1131 } else {
1132 L.DomEvent.off(window, 'dragstart', L.DomEvent.stop);
1133 }
1134 }
1135 });
1136 })();
1137
1138
1139 /*
1140 * L.LatLng represents a geographical point with latitude and longitude coordinates.
1141 */
1142
1143 L.LatLng = function (rawLat, rawLng) { // (Number, Number)
1144 var lat = parseFloat(rawLat),
1145 lng = parseFloat(rawLng);
1146
1147 if (isNaN(lat) || isNaN(lng)) {
1148 throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
1149 }
1150
1151 this.lat = lat;
1152 this.lng = lng;
1153 };
1154
1155 L.extend(L.LatLng, {
1156 DEG_TO_RAD: Math.PI / 180,
1157 RAD_TO_DEG: 180 / Math.PI,
1158 MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
1159 });
1160
1161 L.LatLng.prototype = {
1162 equals: function (obj) { // (LatLng) -> Boolean
1163 if (!obj) { return false; }
1164
1165 obj = L.latLng(obj);
1166
1167 var margin = Math.max(
1168 Math.abs(this.lat - obj.lat),
1169 Math.abs(this.lng - obj.lng));
1170
1171 return margin <= L.LatLng.MAX_MARGIN;
1172 },
1173
1174 toString: function (precision) { // (Number) -> String
1175 return 'LatLng(' +
1176 L.Util.formatNum(this.lat, precision) + ', ' +
1177 L.Util.formatNum(this.lng, precision) + ')';
1178 },
1179
1180 // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
1181 // TODO move to projection code, LatLng shouldn't know about Earth
1182 distanceTo: function (other) { // (LatLng) -> Number
1183 other = L.latLng(other);
1184
1185 var R = 6378137, // earth radius in meters
1186 d2r = L.LatLng.DEG_TO_RAD,
1187 dLat = (other.lat - this.lat) * d2r,
1188 dLon = (other.lng - this.lng) * d2r,
1189 lat1 = this.lat * d2r,
1190 lat2 = other.lat * d2r,
1191 sin1 = Math.sin(dLat / 2),
1192 sin2 = Math.sin(dLon / 2);
1193
1194 var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
1195
1196 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1197 },
1198
1199 wrap: function (a, b) { // (Number, Number) -> LatLng
1200 var lng = this.lng;
1201
1202 a = a || -180;
1203 b = b || 180;
1204
1205 lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
1206
1207 return new L.LatLng(this.lat, lng);
1208 }
1209 };
1210
1211 L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
1212 if (a instanceof L.LatLng) {
1213 return a;
1214 }
1215 if (L.Util.isArray(a)) {
1216 return new L.LatLng(a[0], a[1]);
1217 }
1218 if (a === undefined || a === null) {
1219 return a;
1220 }
1221 if (typeof a === 'object' && 'lat' in a) {
1222 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
1223 }
1224 return new L.LatLng(a, b);
1225 };
1226
1227
1228
1229 /*
1230 * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
1231 */
1232
1233 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1234 if (!southWest) { return; }
1235
1236 var latlngs = northEast ? [southWest, northEast] : southWest;
1237
1238 for (var i = 0, len = latlngs.length; i < len; i++) {
1239 this.extend(latlngs[i]);
1240 }
1241 };
1242
1243 L.LatLngBounds.prototype = {
1244 // extend the bounds to contain the given point or bounds
1245 extend: function (obj) { // (LatLng) or (LatLngBounds)
1246 if (!obj) { return this; }
1247
1248 if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
1249 obj = L.latLng(obj);
1250 } else {
1251 obj = L.latLngBounds(obj);
1252 }
1253
1254 if (obj instanceof L.LatLng) {
1255 if (!this._southWest && !this._northEast) {
1256 this._southWest = new L.LatLng(obj.lat, obj.lng);
1257 this._northEast = new L.LatLng(obj.lat, obj.lng);
1258 } else {
1259 this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
1260 this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
1261
1262 this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
1263 this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
1264 }
1265 } else if (obj instanceof L.LatLngBounds) {
1266 this.extend(obj._southWest);
1267 this.extend(obj._northEast);
1268 }
1269 return this;
1270 },
1271
1272 // extend the bounds by a percentage
1273 pad: function (bufferRatio) { // (Number) -> LatLngBounds
1274 var sw = this._southWest,
1275 ne = this._northEast,
1276 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1277 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1278
1279 return new L.LatLngBounds(
1280 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1281 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1282 },
1283
1284 getCenter: function () { // -> LatLng
1285 return new L.LatLng(
1286 (this._southWest.lat + this._northEast.lat) / 2,
1287 (this._southWest.lng + this._northEast.lng) / 2);
1288 },
1289
1290 getSouthWest: function () {
1291 return this._southWest;
1292 },
1293
1294 getNorthEast: function () {
1295 return this._northEast;
1296 },
1297
1298 getNorthWest: function () {
1299 return new L.LatLng(this.getNorth(), this.getWest());
1300 },
1301
1302 getSouthEast: function () {
1303 return new L.LatLng(this.getSouth(), this.getEast());
1304 },
1305
1306 getWest: function () {
1307 return this._southWest.lng;
1308 },
1309
1310 getSouth: function () {
1311 return this._southWest.lat;
1312 },
1313
1314 getEast: function () {
1315 return this._northEast.lng;
1316 },
1317
1318 getNorth: function () {
1319 return this._northEast.lat;
1320 },
1321
1322 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1323 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1324 obj = L.latLng(obj);
1325 } else {
1326 obj = L.latLngBounds(obj);
1327 }
1328
1329 var sw = this._southWest,
1330 ne = this._northEast,
1331 sw2, ne2;
1332
1333 if (obj instanceof L.LatLngBounds) {
1334 sw2 = obj.getSouthWest();
1335 ne2 = obj.getNorthEast();
1336 } else {
1337 sw2 = ne2 = obj;
1338 }
1339
1340 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1341 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1342 },
1343
1344 intersects: function (bounds) { // (LatLngBounds)
1345 bounds = L.latLngBounds(bounds);
1346
1347 var sw = this._southWest,
1348 ne = this._northEast,
1349 sw2 = bounds.getSouthWest(),
1350 ne2 = bounds.getNorthEast(),
1351
1352 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1353 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1354
1355 return latIntersects && lngIntersects;
1356 },
1357
1358 toBBoxString: function () {
1359 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1360 },
1361
1362 equals: function (bounds) { // (LatLngBounds)
1363 if (!bounds) { return false; }
1364
1365 bounds = L.latLngBounds(bounds);
1366
1367 return this._southWest.equals(bounds.getSouthWest()) &&
1368 this._northEast.equals(bounds.getNorthEast());
1369 },
1370
1371 isValid: function () {
1372 return !!(this._southWest && this._northEast);
1373 }
1374 };
1375
1376 //TODO International date line?
1377
1378 L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
1379 if (!a || a instanceof L.LatLngBounds) {
1380 return a;
1381 }
1382 return new L.LatLngBounds(a, b);
1383 };
1384
1385
1386 /*
1387 * L.Projection contains various geographical projections used by CRS classes.
1388 */
1389
1390 L.Projection = {};
1391
1392
1393 /*
1394 * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
1395 */
1396
1397 L.Projection.SphericalMercator = {
1398 MAX_LATITUDE: 85.0511287798,
1399
1400 project: function (latlng) { // (LatLng) -> Point
1401 var d = L.LatLng.DEG_TO_RAD,
1402 max = this.MAX_LATITUDE,
1403 lat = Math.max(Math.min(max, latlng.lat), -max),
1404 x = latlng.lng * d,
1405 y = lat * d;
1406
1407 y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
1408
1409 return new L.Point(x, y);
1410 },
1411
1412 unproject: function (point) { // (Point, Boolean) -> LatLng
1413 var d = L.LatLng.RAD_TO_DEG,
1414 lng = point.x * d,
1415 lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
1416
1417 return new L.LatLng(lat, lng);
1418 }
1419 };
1420
1421
1422 /*
1423 * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
1424 */
1425
1426 L.Projection.LonLat = {
1427 project: function (latlng) {
1428 return new L.Point(latlng.lng, latlng.lat);
1429 },
1430
1431 unproject: function (point) {
1432 return new L.LatLng(point.y, point.x);
1433 }
1434 };
1435
1436
1437 /*
1438 * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
1439 */
1440
1441 L.CRS = {
1442 latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
1443 var projectedPoint = this.projection.project(latlng),
1444 scale = this.scale(zoom);
1445
1446 return this.transformation._transform(projectedPoint, scale);
1447 },
1448
1449 pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
1450 var scale = this.scale(zoom),
1451 untransformedPoint = this.transformation.untransform(point, scale);
1452
1453 return this.projection.unproject(untransformedPoint);
1454 },
1455
1456 project: function (latlng) {
1457 return this.projection.project(latlng);
1458 },
1459
1460 scale: function (zoom) {
1461 return 256 * Math.pow(2, zoom);
1462 }
1463 };
12801464
12811465
12821466 /*
12931477 });
12941478
12951479
1296 /*
1297 * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
1298 * and is used by Leaflet by default.
1299 */
1300
1301 L.CRS.EPSG3857 = L.extend({}, L.CRS, {
1302 code: 'EPSG:3857',
1303
1304 projection: L.Projection.SphericalMercator,
1305 transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
1306
1307 project: function (latlng) { // (LatLng) -> Point
1308 var projectedPoint = this.projection.project(latlng),
1309 earthRadius = 6378137;
1310 return projectedPoint.multiplyBy(earthRadius);
1311 }
1312 });
1313
1314 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
1315 code: 'EPSG:900913'
1316 });
1317
1318
1319 /*
1320 * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
1321 */
1322
1323 L.CRS.EPSG4326 = L.extend({}, L.CRS, {
1324 code: 'EPSG:4326',
1325
1326 projection: L.Projection.LonLat,
1327 transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
1328 });
1329
1330
1331 /*
1332 * L.Map is the central class of the API - it is used to create a map.
1333 */
1334
1335 L.Map = L.Class.extend({
1336
1337 includes: L.Mixin.Events,
1338
1339 options: {
1340 crs: L.CRS.EPSG3857,
1341
1342 /*
1343 center: LatLng,
1344 zoom: Number,
1345 layers: Array,
1346 */
1347
1348 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
1349 trackResize: true,
1350 markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
1351 },
1352
1353 initialize: function (id, options) { // (HTMLElement or String, Object)
1354 options = L.setOptions(this, options);
1355
1356 this._initContainer(id);
1357 this._initLayout();
1358 this.callInitHooks();
1359 this._initEvents();
1360
1361 if (options.maxBounds) {
1362 this.setMaxBounds(options.maxBounds);
1363 }
1364
1365 if (options.center && options.zoom !== undefined) {
1366 this.setView(L.latLng(options.center), options.zoom, true);
1367 }
1368
1369 this._initLayers(options.layers);
1370 },
1371
1372
1373 // public methods that modify map state
1374
1375 // replaced by animation-powered implementation in Map.PanAnimation.js
1376 setView: function (center, zoom) {
1377 this._resetView(L.latLng(center), this._limitZoom(zoom));
1378 return this;
1379 },
1380
1381 setZoom: function (zoom) { // (Number)
1382 return this.setView(this.getCenter(), zoom);
1383 },
1384
1385 zoomIn: function (delta) {
1386 return this.setZoom(this._zoom + (delta || 1));
1387 },
1388
1389 zoomOut: function (delta) {
1390 return this.setZoom(this._zoom - (delta || 1));
1391 },
1392
1393 fitBounds: function (bounds) { // (LatLngBounds)
1394 var zoom = this.getBoundsZoom(bounds);
1395 return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
1396 },
1397
1398 fitWorld: function () {
1399 var sw = new L.LatLng(-60, -170),
1400 ne = new L.LatLng(85, 179);
1401
1402 return this.fitBounds(new L.LatLngBounds(sw, ne));
1403 },
1404
1405 panTo: function (center) { // (LatLng)
1406 return this.setView(center, this._zoom);
1407 },
1408
1409 panBy: function (offset) { // (Point)
1410 // replaced with animated panBy in Map.Animation.js
1411 this.fire('movestart');
1412
1413 this._rawPanBy(L.point(offset));
1414
1415 this.fire('move');
1416 return this.fire('moveend');
1417 },
1418
1419 setMaxBounds: function (bounds) {
1420 bounds = L.latLngBounds(bounds);
1421
1422 this.options.maxBounds = bounds;
1423
1424 if (!bounds) {
1425 this._boundsMinZoom = null;
1426 return this;
1427 }
1428
1429 var minZoom = this.getBoundsZoom(bounds, true);
1430
1431 this._boundsMinZoom = minZoom;
1432
1433 if (this._loaded) {
1434 if (this._zoom < minZoom) {
1435 this.setView(bounds.getCenter(), minZoom);
1436 } else {
1437 this.panInsideBounds(bounds);
1438 }
1439 }
1440
1441 return this;
1442 },
1443
1444 panInsideBounds: function (bounds) {
1445 bounds = L.latLngBounds(bounds);
1446
1447 var viewBounds = this.getBounds(),
1448 viewSw = this.project(viewBounds.getSouthWest()),
1449 viewNe = this.project(viewBounds.getNorthEast()),
1450 sw = this.project(bounds.getSouthWest()),
1451 ne = this.project(bounds.getNorthEast()),
1452 dx = 0,
1453 dy = 0;
1454
1455 if (viewNe.y < ne.y) { // north
1456 dy = ne.y - viewNe.y;
1457 }
1458 if (viewNe.x > ne.x) { // east
1459 dx = ne.x - viewNe.x;
1460 }
1461 if (viewSw.y > sw.y) { // south
1462 dy = sw.y - viewSw.y;
1463 }
1464 if (viewSw.x < sw.x) { // west
1465 dx = sw.x - viewSw.x;
1466 }
1467
1468 return this.panBy(new L.Point(dx, dy, true));
1469 },
1470
1471 addLayer: function (layer) {
1472 // TODO method is too big, refactor
1473
1474 var id = L.stamp(layer);
1475
1476 if (this._layers[id]) { return this; }
1477
1478 this._layers[id] = layer;
1479
1480 // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
1481 if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
1482 this._zoomBoundLayers[id] = layer;
1483 this._updateZoomLevels();
1484 }
1485
1486 // TODO looks ugly, refactor!!!
1487 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1488 this._tileLayersNum++;
1489 this._tileLayersToLoad++;
1490 layer.on('load', this._onTileLayerLoad, this);
1491 }
1492
1493 this.whenReady(function () {
1494 layer.onAdd(this);
1495 this.fire('layeradd', {layer: layer});
1496 }, this);
1497
1498 return this;
1499 },
1500
1501 removeLayer: function (layer) {
1502 var id = L.stamp(layer);
1503
1504 if (!this._layers[id]) { return; }
1505
1506 layer.onRemove(this);
1507
1508 delete this._layers[id];
1509 if (this._zoomBoundLayers[id]) {
1510 delete this._zoomBoundLayers[id];
1511 this._updateZoomLevels();
1512 }
1513
1514 // TODO looks ugly, refactor
1515 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1516 this._tileLayersNum--;
1517 this._tileLayersToLoad--;
1518 layer.off('load', this._onTileLayerLoad, this);
1519 }
1520
1521 return this.fire('layerremove', {layer: layer});
1522 },
1523
1524 hasLayer: function (layer) {
1525 var id = L.stamp(layer);
1526 return this._layers.hasOwnProperty(id);
1527 },
1528
1529 invalidateSize: function (animate) {
1530 var oldSize = this.getSize();
1531
1532 this._sizeChanged = true;
1533
1534 if (this.options.maxBounds) {
1535 this.setMaxBounds(this.options.maxBounds);
1536 }
1537
1538 if (!this._loaded) { return this; }
1539
1540 var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();
1541
1542 if (animate === true) {
1543 this.panBy(offset);
1544 } else {
1545 this._rawPanBy(offset);
1546
1547 this.fire('move');
1548
1549 clearTimeout(this._sizeTimer);
1550 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
1551 }
1552 return this;
1553 },
1554
1555 // TODO handler.addTo
1556 addHandler: function (name, HandlerClass) {
1557 if (!HandlerClass) { return; }
1558
1559 this[name] = new HandlerClass(this);
1560
1561 if (this.options[name]) {
1562 this[name].enable();
1563 }
1564
1565 return this;
1566 },
1567
1568
1569 // public methods for getting map state
1570
1571 getCenter: function () { // (Boolean) -> LatLng
1572 return this.layerPointToLatLng(this._getCenterLayerPoint());
1573 },
1574
1575 getZoom: function () {
1576 return this._zoom;
1577 },
1578
1579 getBounds: function () {
1580 var bounds = this.getPixelBounds(),
1581 sw = this.unproject(bounds.getBottomLeft()),
1582 ne = this.unproject(bounds.getTopRight());
1583
1584 return new L.LatLngBounds(sw, ne);
1585 },
1586
1587 getMinZoom: function () {
1588 var z1 = this.options.minZoom || 0,
1589 z2 = this._layersMinZoom || 0,
1590 z3 = this._boundsMinZoom || 0;
1591
1592 return Math.max(z1, z2, z3);
1593 },
1594
1595 getMaxZoom: function () {
1596 var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
1597 z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom;
1598
1599 return Math.min(z1, z2);
1600 },
1601
1602 getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
1603 bounds = L.latLngBounds(bounds);
1604
1605 var size = this.getSize(),
1606 zoom = this.options.minZoom || 0,
1607 maxZoom = this.getMaxZoom(),
1608 ne = bounds.getNorthEast(),
1609 sw = bounds.getSouthWest(),
1610 boundsSize,
1611 nePoint,
1612 swPoint,
1613 zoomNotFound = true;
1614
1615 if (inside) {
1616 zoom--;
1617 }
1618
1619 do {
1620 zoom++;
1621 nePoint = this.project(ne, zoom);
1622 swPoint = this.project(sw, zoom);
1623
1624 boundsSize = new L.Point(
1625 Math.abs(nePoint.x - swPoint.x),
1626 Math.abs(swPoint.y - nePoint.y));
1627
1628 if (!inside) {
1629 zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
1630 } else {
1631 zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
1632 }
1633 } while (zoomNotFound && zoom <= maxZoom);
1634
1635 if (zoomNotFound && inside) {
1636 return null;
1637 }
1638
1639 return inside ? zoom : zoom - 1;
1640 },
1641
1642 getSize: function () {
1643 if (!this._size || this._sizeChanged) {
1644 this._size = new L.Point(
1645 this._container.clientWidth,
1646 this._container.clientHeight);
1647
1648 this._sizeChanged = false;
1649 }
1650 return this._size.clone();
1651 },
1652
1653 getPixelBounds: function () {
1654 var topLeftPoint = this._getTopLeftPoint();
1655 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
1656 },
1657
1658 getPixelOrigin: function () {
1659 return this._initialTopLeftPoint;
1660 },
1661
1662 getPanes: function () {
1663 return this._panes;
1664 },
1665
1666 getContainer: function () {
1667 return this._container;
1668 },
1669
1670
1671 // TODO replace with universal implementation after refactoring projections
1672
1673 getZoomScale: function (toZoom) {
1674 var crs = this.options.crs;
1675 return crs.scale(toZoom) / crs.scale(this._zoom);
1676 },
1677
1678 getScaleZoom: function (scale) {
1679 return this._zoom + (Math.log(scale) / Math.LN2);
1680 },
1681
1682
1683 // conversion methods
1684
1685 project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
1686 zoom = zoom === undefined ? this._zoom : zoom;
1687 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
1688 },
1689
1690 unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
1691 zoom = zoom === undefined ? this._zoom : zoom;
1692 return this.options.crs.pointToLatLng(L.point(point), zoom);
1693 },
1694
1695 layerPointToLatLng: function (point) { // (Point)
1696 var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
1697 return this.unproject(projectedPoint);
1698 },
1699
1700 latLngToLayerPoint: function (latlng) { // (LatLng)
1701 var projectedPoint = this.project(L.latLng(latlng))._round();
1702 return projectedPoint._subtract(this._initialTopLeftPoint);
1703 },
1704
1705 containerPointToLayerPoint: function (point) { // (Point)
1706 return L.point(point).subtract(this._getMapPanePos());
1707 },
1708
1709 layerPointToContainerPoint: function (point) { // (Point)
1710 return L.point(point).add(this._getMapPanePos());
1711 },
1712
1713 containerPointToLatLng: function (point) {
1714 var layerPoint = this.containerPointToLayerPoint(L.point(point));
1715 return this.layerPointToLatLng(layerPoint);
1716 },
1717
1718 latLngToContainerPoint: function (latlng) {
1719 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
1720 },
1721
1722 mouseEventToContainerPoint: function (e) { // (MouseEvent)
1723 return L.DomEvent.getMousePosition(e, this._container);
1724 },
1725
1726 mouseEventToLayerPoint: function (e) { // (MouseEvent)
1727 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1728 },
1729
1730 mouseEventToLatLng: function (e) { // (MouseEvent)
1731 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1732 },
1733
1734
1735 // map initialization methods
1736
1737 _initContainer: function (id) {
1738 var container = this._container = L.DomUtil.get(id);
1739
1740 if (container._leaflet) {
1741 throw new Error("Map container is already initialized.");
1742 }
1743
1744 container._leaflet = true;
1745 },
1746
1747 _initLayout: function () {
1748 var container = this._container;
1749
1750 L.DomUtil.addClass(container, 'leaflet-container');
1751
1752 if (L.Browser.touch) {
1753 L.DomUtil.addClass(container, 'leaflet-touch');
1754 }
1755
1756 if (this.options.fadeAnimation) {
1757 L.DomUtil.addClass(container, 'leaflet-fade-anim');
1758 }
1759
1760 var position = L.DomUtil.getStyle(container, 'position');
1761
1762 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
1763 container.style.position = 'relative';
1764 }
1765
1766 this._initPanes();
1767
1768 if (this._initControlPos) {
1769 this._initControlPos();
1770 }
1771 },
1772
1773 _initPanes: function () {
1774 var panes = this._panes = {};
1775
1776 this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
1777
1778 this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
1779 panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
1780 panes.shadowPane = this._createPane('leaflet-shadow-pane');
1781 panes.overlayPane = this._createPane('leaflet-overlay-pane');
1782 panes.markerPane = this._createPane('leaflet-marker-pane');
1783 panes.popupPane = this._createPane('leaflet-popup-pane');
1784
1785 var zoomHide = ' leaflet-zoom-hide';
1786
1787 if (!this.options.markerZoomAnimation) {
1788 L.DomUtil.addClass(panes.markerPane, zoomHide);
1789 L.DomUtil.addClass(panes.shadowPane, zoomHide);
1790 L.DomUtil.addClass(panes.popupPane, zoomHide);
1791 }
1792 },
1793
1794 _createPane: function (className, container) {
1795 return L.DomUtil.create('div', className, container || this._panes.objectsPane);
1796 },
1797
1798 _initLayers: function (layers) {
1799 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
1800
1801 this._layers = {};
1802 this._zoomBoundLayers = {};
1803 this._tileLayersNum = 0;
1804
1805 var i, len;
1806
1807 for (i = 0, len = layers.length; i < len; i++) {
1808 this.addLayer(layers[i]);
1809 }
1810 },
1811
1812
1813 // private methods that modify map state
1814
1815 _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
1816
1817 var zoomChanged = (this._zoom !== zoom);
1818
1819 if (!afterZoomAnim) {
1820 this.fire('movestart');
1821
1822 if (zoomChanged) {
1823 this.fire('zoomstart');
1824 }
1825 }
1826
1827 this._zoom = zoom;
1828
1829 this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
1830
1831 if (!preserveMapOffset) {
1832 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
1833 } else {
1834 this._initialTopLeftPoint._add(this._getMapPanePos());
1835 }
1836
1837 this._tileLayersToLoad = this._tileLayersNum;
1838
1839 var loading = !this._loaded;
1840 this._loaded = true;
1841
1842 this.fire('viewreset', {hard: !preserveMapOffset});
1843
1844 this.fire('move');
1845
1846 if (zoomChanged || afterZoomAnim) {
1847 this.fire('zoomend');
1848 }
1849
1850 this.fire('moveend', {hard: !preserveMapOffset});
1851
1852 if (loading) {
1853 this.fire('load');
1854 }
1855 },
1856
1857 _rawPanBy: function (offset) {
1858 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
1859 },
1860
1861 _updateZoomLevels: function () {
1862 var i,
1863 minZoom = Infinity,
1864 maxZoom = -Infinity;
1865
1866 for (i in this._zoomBoundLayers) {
1867 if (this._zoomBoundLayers.hasOwnProperty(i)) {
1868 var layer = this._zoomBoundLayers[i];
1869 if (!isNaN(layer.options.minZoom)) {
1870 minZoom = Math.min(minZoom, layer.options.minZoom);
1871 }
1872 if (!isNaN(layer.options.maxZoom)) {
1873 maxZoom = Math.max(maxZoom, layer.options.maxZoom);
1874 }
1875 }
1876 }
1877
1878 if (i === undefined) { // we have no tilelayers
1879 this._layersMaxZoom = this._layersMinZoom = undefined;
1880 } else {
1881 this._layersMaxZoom = maxZoom;
1882 this._layersMinZoom = minZoom;
1883 }
1884 },
1885
1886 // map events
1887
1888 _initEvents: function () {
1889 if (!L.DomEvent) { return; }
1890
1891 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
1892
1893 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
1894 'mouseleave', 'mousemove', 'contextmenu'],
1895 i, len;
1896
1897 for (i = 0, len = events.length; i < len; i++) {
1898 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
1899 }
1900
1901 if (this.options.trackResize) {
1902 L.DomEvent.on(window, 'resize', this._onResize, this);
1903 }
1904 },
1905
1906 _onResize: function () {
1907 L.Util.cancelAnimFrame(this._resizeRequest);
1908 this._resizeRequest = L.Util.requestAnimFrame(
1909 this.invalidateSize, this, false, this._container);
1910 },
1911
1912 _onMouseClick: function (e) {
1913 if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
1914
1915 this.fire('preclick');
1916 this._fireMouseEvent(e);
1917 },
1918
1919 _fireMouseEvent: function (e) {
1920 if (!this._loaded) { return; }
1921
1922 var type = e.type;
1923
1924 type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
1925
1926 if (!this.hasEventListeners(type)) { return; }
1927
1928 if (type === 'contextmenu') {
1929 L.DomEvent.preventDefault(e);
1930 }
1931
1932 var containerPoint = this.mouseEventToContainerPoint(e),
1933 layerPoint = this.containerPointToLayerPoint(containerPoint),
1934 latlng = this.layerPointToLatLng(layerPoint);
1935
1936 this.fire(type, {
1937 latlng: latlng,
1938 layerPoint: layerPoint,
1939 containerPoint: containerPoint,
1940 originalEvent: e
1941 });
1942 },
1943
1944 _onTileLayerLoad: function () {
1945 // TODO super-ugly, refactor!!!
1946 // clear scaled tiles after all new tiles are loaded (for performance)
1947 this._tileLayersToLoad--;
1948 if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
1949 clearTimeout(this._clearTileBgTimer);
1950 this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
1951 }
1952 },
1953
1954 whenReady: function (callback, context) {
1955 if (this._loaded) {
1956 callback.call(context || this, this);
1957 } else {
1958 this.on('load', callback, context);
1959 }
1960 return this;
1961 },
1962
1963
1964 // private methods for getting map state
1965
1966 _getMapPanePos: function () {
1967 return L.DomUtil.getPosition(this._mapPane);
1968 },
1969
1970 _getTopLeftPoint: function () {
1971 if (!this._loaded) {
1972 throw new Error('Set map center and zoom first.');
1973 }
1974
1975 return this._initialTopLeftPoint.subtract(this._getMapPanePos());
1976 },
1977
1978 _getNewTopLeftPoint: function (center, zoom) {
1979 var viewHalf = this.getSize()._divideBy(2);
1980 // TODO round on display, not calculation to increase precision?
1981 return this.project(center, zoom)._subtract(viewHalf)._round();
1982 },
1983
1984 _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
1985 var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
1986 return this.project(latlng, newZoom)._subtract(topLeft);
1987 },
1988
1989 _getCenterLayerPoint: function () {
1990 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
1991 },
1992
1993 _getCenterOffset: function (center) {
1994 return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
1995 },
1996
1997 _limitZoom: function (zoom) {
1998 var min = this.getMinZoom(),
1999 max = this.getMaxZoom();
2000
2001 return Math.max(min, Math.min(max, zoom));
2002 }
2003 });
2004
2005 L.map = function (id, options) {
2006 return new L.Map(id, options);
2007 };
2008
2009
2010 /*
2011 * Mercator projection that takes into account that the Earth is not a perfect sphere.
2012 * Less popular than spherical mercator; used by projections like EPSG:3395.
2013 */
2014
2015 L.Projection.Mercator = {
2016 MAX_LATITUDE: 85.0840591556,
2017
2018 R_MINOR: 6356752.3142,
2019 R_MAJOR: 6378137,
2020
2021 project: function (latlng) { // (LatLng) -> Point
2022 var d = L.LatLng.DEG_TO_RAD,
2023 max = this.MAX_LATITUDE,
2024 lat = Math.max(Math.min(max, latlng.lat), -max),
2025 r = this.R_MAJOR,
2026 r2 = this.R_MINOR,
2027 x = latlng.lng * d * r,
2028 y = lat * d,
2029 tmp = r2 / r,
2030 eccent = Math.sqrt(1.0 - tmp * tmp),
2031 con = eccent * Math.sin(y);
2032
2033 con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
2034
2035 var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
2036 y = -r2 * Math.log(ts);
2037
2038 return new L.Point(x, y);
2039 },
2040
2041 unproject: function (point) { // (Point, Boolean) -> LatLng
2042 var d = L.LatLng.RAD_TO_DEG,
2043 r = this.R_MAJOR,
2044 r2 = this.R_MINOR,
2045 lng = point.x * d / r,
2046 tmp = r2 / r,
2047 eccent = Math.sqrt(1 - (tmp * tmp)),
2048 ts = Math.exp(- point.y / r2),
2049 phi = (Math.PI / 2) - 2 * Math.atan(ts),
2050 numIter = 15,
2051 tol = 1e-7,
2052 i = numIter,
2053 dphi = 0.1,
2054 con;
2055
2056 while ((Math.abs(dphi) > tol) && (--i > 0)) {
2057 con = eccent * Math.sin(phi);
2058 dphi = (Math.PI / 2) - 2 * Math.atan(ts *
2059 Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
2060 phi += dphi;
2061 }
2062
2063 return new L.LatLng(phi * d, lng);
2064 }
2065 };
2066
2067
2068
2069 L.CRS.EPSG3395 = L.extend({}, L.CRS, {
2070 code: 'EPSG:3395',
2071
2072 projection: L.Projection.Mercator,
2073
2074 transformation: (function () {
2075 var m = L.Projection.Mercator,
2076 r = m.R_MAJOR,
2077 r2 = m.R_MINOR;
2078
2079 return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
2080 }())
2081 });
2082
2083
2084 /*
2085 * L.TileLayer is used for standard xyz-numbered tile layers.
2086 */
2087
2088 L.TileLayer = L.Class.extend({
2089 includes: L.Mixin.Events,
2090
2091 options: {
2092 minZoom: 0,
2093 maxZoom: 18,
2094 tileSize: 256,
2095 subdomains: 'abc',
2096 errorTileUrl: '',
2097 attribution: '',
2098 zoomOffset: 0,
2099 opacity: 1,
2100 /* (undefined works too)
2101 zIndex: null,
2102 tms: false,
2103 continuousWorld: false,
2104 noWrap: false,
2105 zoomReverse: false,
2106 detectRetina: false,
2107 reuseTiles: false,
2108 */
2109 unloadInvisibleTiles: L.Browser.mobile,
2110 updateWhenIdle: L.Browser.mobile
2111 },
2112
2113 initialize: function (url, options) {
2114 options = L.setOptions(this, options);
2115
2116 // detecting retina displays, adjusting tileSize and zoom levels
2117 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
2118
2119 options.tileSize = Math.floor(options.tileSize / 2);
2120 options.zoomOffset++;
2121
2122 if (options.minZoom > 0) {
2123 options.minZoom--;
2124 }
2125 this.options.maxZoom--;
2126 }
2127
2128 this._url = url;
2129
2130 var subdomains = this.options.subdomains;
2131
2132 if (typeof subdomains === 'string') {
2133 this.options.subdomains = subdomains.split('');
2134 }
2135 },
2136
2137 onAdd: function (map) {
2138 this._map = map;
2139
2140 // create a container div for tiles
2141 this._initContainer();
2142
2143 // create an image to clone for tiles
2144 this._createTileProto();
2145
2146 // set up events
2147 map.on({
2148 'viewreset': this._resetCallback,
2149 'moveend': this._update
2150 }, this);
2151
2152 if (!this.options.updateWhenIdle) {
2153 this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
2154 map.on('move', this._limitedUpdate, this);
2155 }
2156
2157 this._reset();
2158 this._update();
2159 },
2160
2161 addTo: function (map) {
2162 map.addLayer(this);
2163 return this;
2164 },
2165
2166 onRemove: function (map) {
2167 this._container.parentNode.removeChild(this._container);
2168
2169 map.off({
2170 'viewreset': this._resetCallback,
2171 'moveend': this._update
2172 }, this);
2173
2174 if (!this.options.updateWhenIdle) {
2175 map.off('move', this._limitedUpdate, this);
2176 }
2177
2178 this._container = null;
2179 this._map = null;
2180 },
2181
2182 bringToFront: function () {
2183 var pane = this._map._panes.tilePane;
2184
2185 if (this._container) {
2186 pane.appendChild(this._container);
2187 this._setAutoZIndex(pane, Math.max);
2188 }
2189
2190 return this;
2191 },
2192
2193 bringToBack: function () {
2194 var pane = this._map._panes.tilePane;
2195
2196 if (this._container) {
2197 pane.insertBefore(this._container, pane.firstChild);
2198 this._setAutoZIndex(pane, Math.min);
2199 }
2200
2201 return this;
2202 },
2203
2204 getAttribution: function () {
2205 return this.options.attribution;
2206 },
2207
2208 setOpacity: function (opacity) {
2209 this.options.opacity = opacity;
2210
2211 if (this._map) {
2212 this._updateOpacity();
2213 }
2214
2215 return this;
2216 },
2217
2218 setZIndex: function (zIndex) {
2219 this.options.zIndex = zIndex;
2220 this._updateZIndex();
2221
2222 return this;
2223 },
2224
2225 setUrl: function (url, noRedraw) {
2226 this._url = url;
2227
2228 if (!noRedraw) {
2229 this.redraw();
2230 }
2231
2232 return this;
2233 },
2234
2235 redraw: function () {
2236 if (this._map) {
2237 this._map._panes.tilePane.empty = false;
2238 this._reset(true);
2239 this._update();
2240 }
2241 return this;
2242 },
2243
2244 _updateZIndex: function () {
2245 if (this._container && this.options.zIndex !== undefined) {
2246 this._container.style.zIndex = this.options.zIndex;
2247 }
2248 },
2249
2250 _setAutoZIndex: function (pane, compare) {
2251
2252 var layers = pane.children,
2253 edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
2254 zIndex, i, len;
2255
2256 for (i = 0, len = layers.length; i < len; i++) {
2257
2258 if (layers[i] !== this._container) {
2259 zIndex = parseInt(layers[i].style.zIndex, 10);
2260
2261 if (!isNaN(zIndex)) {
2262 edgeZIndex = compare(edgeZIndex, zIndex);
2263 }
2264 }
2265 }
2266
2267 this.options.zIndex = this._container.style.zIndex =
2268 (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
2269 },
2270
2271 _updateOpacity: function () {
2272 L.DomUtil.setOpacity(this._container, this.options.opacity);
2273
2274 // stupid webkit hack to force redrawing of tiles
2275 var i,
2276 tiles = this._tiles;
2277
2278 if (L.Browser.webkit) {
2279 for (i in tiles) {
2280 if (tiles.hasOwnProperty(i)) {
2281 tiles[i].style.webkitTransform += ' translate(0,0)';
2282 }
2283 }
2284 }
2285 },
2286
2287 _initContainer: function () {
2288 var tilePane = this._map._panes.tilePane;
2289
2290 if (!this._container || tilePane.empty) {
2291 this._container = L.DomUtil.create('div', 'leaflet-layer');
2292
2293 this._updateZIndex();
2294
2295 tilePane.appendChild(this._container);
2296
2297 if (this.options.opacity < 1) {
2298 this._updateOpacity();
2299 }
2300 }
2301 },
2302
2303 _resetCallback: function (e) {
2304 this._reset(e.hard);
2305 },
2306
2307 _reset: function (clearOldContainer) {
2308 var tiles = this._tiles;
2309
2310 for (var key in tiles) {
2311 if (tiles.hasOwnProperty(key)) {
2312 this.fire('tileunload', {tile: tiles[key]});
2313 }
2314 }
2315
2316 this._tiles = {};
2317 this._tilesToLoad = 0;
2318
2319 if (this.options.reuseTiles) {
2320 this._unusedTiles = [];
2321 }
2322
2323 if (clearOldContainer && this._container) {
2324 this._container.innerHTML = "";
2325 }
2326
2327 this._initContainer();
2328 },
2329
2330 _update: function () {
2331
2332 if (!this._map) { return; }
2333
2334 var bounds = this._map.getPixelBounds(),
2335 zoom = this._map.getZoom(),
2336 tileSize = this.options.tileSize;
2337
2338 if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
2339 return;
2340 }
2341
2342 var nwTilePoint = new L.Point(
2343 Math.floor(bounds.min.x / tileSize),
2344 Math.floor(bounds.min.y / tileSize)),
2345
2346 seTilePoint = new L.Point(
2347 Math.floor(bounds.max.x / tileSize),
2348 Math.floor(bounds.max.y / tileSize)),
2349
2350 tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
2351
2352 this._addTilesFromCenterOut(tileBounds);
2353
2354 if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
2355 this._removeOtherTiles(tileBounds);
2356 }
2357 },
2358
2359 _addTilesFromCenterOut: function (bounds) {
2360 var queue = [],
2361 center = bounds.getCenter();
2362
2363 var j, i, point;
2364
2365 for (j = bounds.min.y; j <= bounds.max.y; j++) {
2366 for (i = bounds.min.x; i <= bounds.max.x; i++) {
2367 point = new L.Point(i, j);
2368
2369 if (this._tileShouldBeLoaded(point)) {
2370 queue.push(point);
2371 }
2372 }
2373 }
2374
2375 var tilesToLoad = queue.length;
2376
2377 if (tilesToLoad === 0) { return; }
2378
2379 // load tiles in order of their distance to center
2380 queue.sort(function (a, b) {
2381 return a.distanceTo(center) - b.distanceTo(center);
2382 });
2383
2384 var fragment = document.createDocumentFragment();
2385
2386 // if its the first batch of tiles to load
2387 if (!this._tilesToLoad) {
2388 this.fire('loading');
2389 }
2390
2391 this._tilesToLoad += tilesToLoad;
2392
2393 for (i = 0; i < tilesToLoad; i++) {
2394 this._addTile(queue[i], fragment);
2395 }
2396
2397 this._container.appendChild(fragment);
2398 },
2399
2400 _tileShouldBeLoaded: function (tilePoint) {
2401 if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
2402 return false; // already loaded
2403 }
2404
2405 if (!this.options.continuousWorld) {
2406 var limit = this._getWrapTileNum();
2407
2408 if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
2409 tilePoint.y < 0 || tilePoint.y >= limit) {
2410 return false; // exceeds world bounds
2411 }
2412 }
2413
2414 return true;
2415 },
2416
2417 _removeOtherTiles: function (bounds) {
2418 var kArr, x, y, key;
2419
2420 for (key in this._tiles) {
2421 if (this._tiles.hasOwnProperty(key)) {
2422 kArr = key.split(':');
2423 x = parseInt(kArr[0], 10);
2424 y = parseInt(kArr[1], 10);
2425
2426 // remove tile if it's out of bounds
2427 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
2428 this._removeTile(key);
2429 }
2430 }
2431 }
2432 },
2433
2434 _removeTile: function (key) {
2435 var tile = this._tiles[key];
2436
2437 this.fire("tileunload", {tile: tile, url: tile.src});
2438
2439 if (this.options.reuseTiles) {
2440 L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
2441 this._unusedTiles.push(tile);
2442
2443 } else if (tile.parentNode === this._container) {
2444 this._container.removeChild(tile);
2445 }
2446
2447 // for https://github.com/CloudMade/Leaflet/issues/137
2448 if (!L.Browser.android) {
2449 tile.src = L.Util.emptyImageUrl;
2450 }
2451
2452 delete this._tiles[key];
2453 },
2454
2455 _addTile: function (tilePoint, container) {
2456 var tilePos = this._getTilePos(tilePoint);
2457
2458 // get unused tile - or create a new tile
2459 var tile = this._getTile();
2460
2461 /*
2462 Chrome 20 layouts much faster with top/left (verify with timeline, frames)
2463 Android 4 browser has display issues with top/left and requires transform instead
2464 Android 3 browser not tested
2465 Android 2 browser requires top/left or tiles disappear on load or first drag
2466 (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
2467 (other browsers don't currently care) - see debug/hacks/jitter.html for an example
2468 */
2469 L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
2470
2471 this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
2472
2473 this._loadTile(tile, tilePoint);
2474
2475 if (tile.parentNode !== this._container) {
2476 container.appendChild(tile);
2477 }
2478 },
2479
2480 _getZoomForUrl: function () {
2481
2482 var options = this.options,
2483 zoom = this._map.getZoom();
2484
2485 if (options.zoomReverse) {
2486 zoom = options.maxZoom - zoom;
2487 }
2488
2489 return zoom + options.zoomOffset;
2490 },
2491
2492 _getTilePos: function (tilePoint) {
2493 var origin = this._map.getPixelOrigin(),
2494 tileSize = this.options.tileSize;
2495
2496 return tilePoint.multiplyBy(tileSize).subtract(origin);
2497 },
2498
2499 // image-specific code (override to implement e.g. Canvas or SVG tile layer)
2500
2501 getTileUrl: function (tilePoint) {
2502 this._adjustTilePoint(tilePoint);
2503
2504 return L.Util.template(this._url, L.extend({
2505 s: this._getSubdomain(tilePoint),
2506 z: this._getZoomForUrl(),
2507 x: tilePoint.x,
2508 y: tilePoint.y
2509 }, this.options));
2510 },
2511
2512 _getWrapTileNum: function () {
2513 // TODO refactor, limit is not valid for non-standard projections
2514 return Math.pow(2, this._getZoomForUrl());
2515 },
2516
2517 _adjustTilePoint: function (tilePoint) {
2518
2519 var limit = this._getWrapTileNum();
2520
2521 // wrap tile coordinates
2522 if (!this.options.continuousWorld && !this.options.noWrap) {
2523 tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
2524 }
2525
2526 if (this.options.tms) {
2527 tilePoint.y = limit - tilePoint.y - 1;
2528 }
2529 },
2530
2531 _getSubdomain: function (tilePoint) {
2532 var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
2533 return this.options.subdomains[index];
2534 },
2535
2536 _createTileProto: function () {
2537 var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
2538 img.style.width = img.style.height = this.options.tileSize + 'px';
2539 img.galleryimg = 'no';
2540 },
2541
2542 _getTile: function () {
2543 if (this.options.reuseTiles && this._unusedTiles.length > 0) {
2544 var tile = this._unusedTiles.pop();
2545 this._resetTile(tile);
2546 return tile;
2547 }
2548 return this._createTile();
2549 },
2550
2551 // Override if data stored on a tile needs to be cleaned up before reuse
2552 _resetTile: function (/*tile*/) {},
2553
2554 _createTile: function () {
2555 var tile = this._tileImg.cloneNode(false);
2556 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2557 return tile;
2558 },
2559
2560 _loadTile: function (tile, tilePoint) {
2561 tile._layer = this;
2562 tile.onload = this._tileOnLoad;
2563 tile.onerror = this._tileOnError;
2564
2565 tile.src = this.getTileUrl(tilePoint);
2566 },
2567
2568 _tileLoaded: function () {
2569 this._tilesToLoad--;
2570 if (!this._tilesToLoad) {
2571 this.fire('load');
2572 }
2573 },
2574
2575 _tileOnLoad: function () {
2576 var layer = this._layer;
2577
2578 //Only if we are loading an actual image
2579 if (this.src !== L.Util.emptyImageUrl) {
2580 L.DomUtil.addClass(this, 'leaflet-tile-loaded');
2581
2582 layer.fire('tileload', {
2583 tile: this,
2584 url: this.src
2585 });
2586 }
2587
2588 layer._tileLoaded();
2589 },
2590
2591 _tileOnError: function () {
2592 var layer = this._layer;
2593
2594 layer.fire('tileerror', {
2595 tile: this,
2596 url: this.src
2597 });
2598
2599 var newUrl = layer.options.errorTileUrl;
2600 if (newUrl) {
2601 this.src = newUrl;
2602 }
2603
2604 layer._tileLoaded();
2605 }
2606 });
2607
2608 L.tileLayer = function (url, options) {
2609 return new L.TileLayer(url, options);
2610 };
2611
2612
2613 /*
2614 * L.TileLayer.WMS is used for putting WMS tile layers on the map.
2615 */
2616
2617 L.TileLayer.WMS = L.TileLayer.extend({
2618
2619 defaultWmsParams: {
2620 service: 'WMS',
2621 request: 'GetMap',
2622 version: '1.1.1',
2623 layers: '',
2624 styles: '',
2625 format: 'image/jpeg',
2626 transparent: false
2627 },
2628
2629 initialize: function (url, options) { // (String, Object)
2630
2631 this._url = url;
2632
2633 var wmsParams = L.extend({}, this.defaultWmsParams);
2634
2635 if (options.detectRetina && L.Browser.retina) {
2636 wmsParams.width = wmsParams.height = this.options.tileSize * 2;
2637 } else {
2638 wmsParams.width = wmsParams.height = this.options.tileSize;
2639 }
2640
2641 for (var i in options) {
2642 // all keys that are not TileLayer options go to WMS params
2643 if (!this.options.hasOwnProperty(i)) {
2644 wmsParams[i] = options[i];
2645 }
2646 }
2647
2648 this.wmsParams = wmsParams;
2649
2650 L.setOptions(this, options);
2651 },
2652
2653 onAdd: function (map) {
2654
2655 var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
2656 this.wmsParams[projectionKey] = map.options.crs.code;
2657
2658 L.TileLayer.prototype.onAdd.call(this, map);
2659 },
2660
2661 getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
2662
2663 this._adjustTilePoint(tilePoint);
2664
2665 var map = this._map,
2666 crs = map.options.crs,
2667 tileSize = this.options.tileSize,
2668
2669 nwPoint = tilePoint.multiplyBy(tileSize),
2670 sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
2671
2672 nw = crs.project(map.unproject(nwPoint, zoom)),
2673 se = crs.project(map.unproject(sePoint, zoom)),
2674
2675 bbox = [nw.x, se.y, se.x, nw.y].join(','),
2676
2677 url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
2678
2679 return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox;
2680 },
2681
2682 setParams: function (params, noRedraw) {
2683
2684 L.extend(this.wmsParams, params);
2685
2686 if (!noRedraw) {
2687 this.redraw();
2688 }
2689
2690 return this;
2691 }
2692 });
2693
2694 L.tileLayer.wms = function (url, options) {
2695 return new L.TileLayer.WMS(url, options);
2696 };
2697
2698
2699 /*
2700 * L.TileLayer.Canvas is a class that you can use as a base for creating
2701 * dynamically drawn Canvas-based tile layers.
2702 */
2703
2704 L.TileLayer.Canvas = L.TileLayer.extend({
2705 options: {
2706 async: false
2707 },
2708
2709 initialize: function (options) {
2710 L.setOptions(this, options);
2711 },
2712
2713 redraw: function () {
2714 var tiles = this._tiles;
2715
2716 for (var i in tiles) {
2717 if (tiles.hasOwnProperty(i)) {
2718 this._redrawTile(tiles[i]);
2719 }
2720 }
2721 },
2722
2723 _redrawTile: function (tile) {
2724 this.drawTile(tile, tile._tilePoint, this._map._zoom);
2725 },
2726
2727 _createTileProto: function () {
2728 var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
2729 proto.width = proto.height = this.options.tileSize;
2730 },
2731
2732 _createTile: function () {
2733 var tile = this._canvasProto.cloneNode(false);
2734 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2735 return tile;
2736 },
2737
2738 _loadTile: function (tile, tilePoint) {
2739 tile._layer = this;
2740 tile._tilePoint = tilePoint;
2741
2742 this._redrawTile(tile);
2743
2744 if (!this.options.async) {
2745 this.tileDrawn(tile);
2746 }
2747 },
2748
2749 drawTile: function (/*tile, tilePoint*/) {
2750 // override with rendering code
2751 },
2752
2753 tileDrawn: function (tile) {
2754 this._tileOnLoad.call(tile);
2755 }
2756 });
2757
2758
2759 L.tileLayer.canvas = function (options) {
2760 return new L.TileLayer.Canvas(options);
2761 };
2762
2763
2764 /*
2765 * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
2766 */
2767
2768 L.ImageOverlay = L.Class.extend({
2769 includes: L.Mixin.Events,
2770
2771 options: {
2772 opacity: 1
2773 },
2774
2775 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
2776 this._url = url;
2777 this._bounds = L.latLngBounds(bounds);
2778
2779 L.setOptions(this, options);
2780 },
2781
2782 onAdd: function (map) {
2783 this._map = map;
2784
2785 if (!this._image) {
2786 this._initImage();
2787 }
2788
2789 map._panes.overlayPane.appendChild(this._image);
2790
2791 map.on('viewreset', this._reset, this);
2792
2793 if (map.options.zoomAnimation && L.Browser.any3d) {
2794 map.on('zoomanim', this._animateZoom, this);
2795 }
2796
2797 this._reset();
2798 },
2799
2800 onRemove: function (map) {
2801 map.getPanes().overlayPane.removeChild(this._image);
2802
2803 map.off('viewreset', this._reset, this);
2804
2805 if (map.options.zoomAnimation) {
2806 map.off('zoomanim', this._animateZoom, this);
2807 }
2808 },
2809
2810 addTo: function (map) {
2811 map.addLayer(this);
2812 return this;
2813 },
2814
2815 setOpacity: function (opacity) {
2816 this.options.opacity = opacity;
2817 this._updateOpacity();
2818 return this;
2819 },
2820
2821 // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
2822 bringToFront: function () {
2823 if (this._image) {
2824 this._map._panes.overlayPane.appendChild(this._image);
2825 }
2826 return this;
2827 },
2828
2829 bringToBack: function () {
2830 var pane = this._map._panes.overlayPane;
2831 if (this._image) {
2832 pane.insertBefore(this._image, pane.firstChild);
2833 }
2834 return this;
2835 },
2836
2837 _initImage: function () {
2838 this._image = L.DomUtil.create('img', 'leaflet-image-layer');
2839
2840 if (this._map.options.zoomAnimation && L.Browser.any3d) {
2841 L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
2842 } else {
2843 L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
2844 }
2845
2846 this._updateOpacity();
2847
2848 //TODO createImage util method to remove duplication
2849 L.extend(this._image, {
2850 galleryimg: 'no',
2851 onselectstart: L.Util.falseFn,
2852 onmousemove: L.Util.falseFn,
2853 onload: L.bind(this._onImageLoad, this),
2854 src: this._url
2855 });
2856 },
2857
2858 _animateZoom: function (e) {
2859 var map = this._map,
2860 image = this._image,
2861 scale = map.getZoomScale(e.zoom),
2862 nw = this._bounds.getNorthWest(),
2863 se = this._bounds.getSouthEast(),
2864
2865 topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
2866 size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
2867 origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
2868
2869 image.style[L.DomUtil.TRANSFORM] =
2870 L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
2871 },
2872
2873 _reset: function () {
2874 var image = this._image,
2875 topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
2876 size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
2877
2878 L.DomUtil.setPosition(image, topLeft);
2879
2880 image.style.width = size.x + 'px';
2881 image.style.height = size.y + 'px';
2882 },
2883
2884 _onImageLoad: function () {
2885 this.fire('load');
2886 },
2887
2888 _updateOpacity: function () {
2889 L.DomUtil.setOpacity(this._image, this.options.opacity);
2890 }
2891 });
2892
2893 L.imageOverlay = function (url, bounds, options) {
2894 return new L.ImageOverlay(url, bounds, options);
2895 };
2896
2897
2898 /*
2899 * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
2900 */
2901
2902 L.Icon = L.Class.extend({
2903 options: {
2904 /*
2905 iconUrl: (String) (required)
2906 iconRetinaUrl: (String) (optional, used for retina devices if detected)
2907 iconSize: (Point) (can be set through CSS)
2908 iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
2909 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
2910 shadowUrl: (Point) (no shadow by default)
2911 shadowRetinaUrl: (String) (optional, used for retina devices if detected)
2912 shadowSize: (Point)
2913 shadowAnchor: (Point)
2914 */
2915 className: ''
2916 },
2917
2918 initialize: function (options) {
2919 L.setOptions(this, options);
2920 },
2921
2922 createIcon: function () {
2923 return this._createIcon('icon');
2924 },
2925
2926 createShadow: function () {
2927 return this._createIcon('shadow');
2928 },
2929
2930 _createIcon: function (name) {
2931 var src = this._getIconUrl(name);
2932
2933 if (!src) {
2934 if (name === 'icon') {
2935 throw new Error("iconUrl not set in Icon options (see the docs).");
2936 }
2937 return null;
2938 }
2939
2940 var img = this._createImg(src);
2941 this._setIconStyles(img, name);
2942
2943 return img;
2944 },
2945
2946 _setIconStyles: function (img, name) {
2947 var options = this.options,
2948 size = L.point(options[name + 'Size']),
2949 anchor;
2950
2951 if (name === 'shadow') {
2952 anchor = L.point(options.shadowAnchor || options.iconAnchor);
2953 } else {
2954 anchor = L.point(options.iconAnchor);
2955 }
2956
2957 if (!anchor && size) {
2958 anchor = size.divideBy(2, true);
2959 }
2960
2961 img.className = 'leaflet-marker-' + name + ' ' + options.className;
2962
2963 if (anchor) {
2964 img.style.marginLeft = (-anchor.x) + 'px';
2965 img.style.marginTop = (-anchor.y) + 'px';
2966 }
2967
2968 if (size) {
2969 img.style.width = size.x + 'px';
2970 img.style.height = size.y + 'px';
2971 }
2972 },
2973
2974 _createImg: function (src) {
2975 var el;
2976
2977 if (!L.Browser.ie6) {
2978 el = document.createElement('img');
2979 el.src = src;
2980 } else {
2981 el = document.createElement('div');
2982 el.style.filter =
2983 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
2984 }
2985 return el;
2986 },
2987
2988 _getIconUrl: function (name) {
2989 if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
2990 return this.options[name + 'RetinaUrl'];
2991 }
2992 return this.options[name + 'Url'];
2993 }
2994 });
2995
2996 L.icon = function (options) {
2997 return new L.Icon(options);
2998 };
1480 /*
1481 * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
1482 * and is used by Leaflet by default.
1483 */
1484
1485 L.CRS.EPSG3857 = L.extend({}, L.CRS, {
1486 code: 'EPSG:3857',
1487
1488 projection: L.Projection.SphericalMercator,
1489 transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
1490
1491 project: function (latlng) { // (LatLng) -> Point
1492 var projectedPoint = this.projection.project(latlng),
1493 earthRadius = 6378137;
1494 return projectedPoint.multiplyBy(earthRadius);
1495 }
1496 });
1497
1498 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
1499 code: 'EPSG:900913'
1500 });
1501
1502
1503 /*
1504 * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
1505 */
1506
1507 L.CRS.EPSG4326 = L.extend({}, L.CRS, {
1508 code: 'EPSG:4326',
1509
1510 projection: L.Projection.LonLat,
1511 transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
1512 });
1513
1514
1515 /*
1516 * L.Map is the central class of the API - it is used to create a map.
1517 */
1518
1519 L.Map = L.Class.extend({
1520
1521 includes: L.Mixin.Events,
1522
1523 options: {
1524 crs: L.CRS.EPSG3857,
1525
1526 /*
1527 center: LatLng,
1528 zoom: Number,
1529 layers: Array,
1530 */
1531
1532 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
1533 trackResize: true,
1534 markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
1535 },
1536
1537 initialize: function (id, options) { // (HTMLElement or String, Object)
1538 options = L.setOptions(this, options);
1539
1540 this._initContainer(id);
1541 this._initLayout();
1542 this._initEvents();
1543
1544 if (options.maxBounds) {
1545 this.setMaxBounds(options.maxBounds);
1546 }
1547
1548 if (options.center && options.zoom !== undefined) {
1549 this.setView(L.latLng(options.center), options.zoom, {reset: true});
1550 }
1551
1552 this._handlers = [];
1553
1554 this._layers = {};
1555 this._zoomBoundLayers = {};
1556 this._tileLayersNum = 0;
1557
1558 this.callInitHooks();
1559
1560 this._addLayers(options.layers);
1561 },
1562
1563
1564 // public methods that modify map state
1565
1566 // replaced by animation-powered implementation in Map.PanAnimation.js
1567 setView: function (center, zoom) {
1568 this._resetView(L.latLng(center), this._limitZoom(zoom));
1569 return this;
1570 },
1571
1572 setZoom: function (zoom, options) {
1573 return this.setView(this.getCenter(), zoom, {zoom: options});
1574 },
1575
1576 zoomIn: function (delta, options) {
1577 return this.setZoom(this._zoom + (delta || 1), options);
1578 },
1579
1580 zoomOut: function (delta, options) {
1581 return this.setZoom(this._zoom - (delta || 1), options);
1582 },
1583
1584 setZoomAround: function (latlng, zoom, options) {
1585 var scale = this.getZoomScale(zoom),
1586 viewHalf = this.getSize().divideBy(2),
1587 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
1588
1589 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
1590 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
1591
1592 return this.setView(newCenter, zoom, {zoom: options});
1593 },
1594
1595 fitBounds: function (bounds, options) {
1596
1597 options = options || {};
1598 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
1599
1600 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
1601 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
1602
1603 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
1604 paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
1605
1606 swPoint = this.project(bounds.getSouthWest(), zoom),
1607 nePoint = this.project(bounds.getNorthEast(), zoom),
1608 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
1609
1610 return this.setView(center, zoom, options);
1611 },
1612
1613 fitWorld: function (options) {
1614 return this.fitBounds([[-90, -180], [90, 180]], options);
1615 },
1616
1617 panTo: function (center, options) { // (LatLng)
1618 return this.setView(center, this._zoom, {pan: options});
1619 },
1620
1621 panBy: function (offset) { // (Point)
1622 // replaced with animated panBy in Map.Animation.js
1623 this.fire('movestart');
1624
1625 this._rawPanBy(L.point(offset));
1626
1627 this.fire('move');
1628 return this.fire('moveend');
1629 },
1630
1631 setMaxBounds: function (bounds) {
1632 bounds = L.latLngBounds(bounds);
1633
1634 this.options.maxBounds = bounds;
1635
1636 if (!bounds) {
1637 this._boundsMinZoom = null;
1638 this.off('moveend', this._panInsideMaxBounds, this);
1639 return this;
1640 }
1641
1642 var minZoom = this.getBoundsZoom(bounds, true);
1643
1644 this._boundsMinZoom = minZoom;
1645
1646 if (this._loaded) {
1647 if (this._zoom < minZoom) {
1648 this.setView(bounds.getCenter(), minZoom);
1649 } else {
1650 this.panInsideBounds(bounds);
1651 }
1652 }
1653
1654 this.on('moveend', this._panInsideMaxBounds, this);
1655
1656 return this;
1657 },
1658
1659 panInsideBounds: function (bounds) {
1660 bounds = L.latLngBounds(bounds);
1661
1662 var viewBounds = this.getPixelBounds(),
1663 viewSw = viewBounds.getBottomLeft(),
1664 viewNe = viewBounds.getTopRight(),
1665 sw = this.project(bounds.getSouthWest()),
1666 ne = this.project(bounds.getNorthEast()),
1667 dx = 0,
1668 dy = 0;
1669
1670 if (viewNe.y < ne.y) { // north
1671 dy = Math.ceil(ne.y - viewNe.y);
1672 }
1673 if (viewNe.x > ne.x) { // east
1674 dx = Math.floor(ne.x - viewNe.x);
1675 }
1676 if (viewSw.y > sw.y) { // south
1677 dy = Math.floor(sw.y - viewSw.y);
1678 }
1679 if (viewSw.x < sw.x) { // west
1680 dx = Math.ceil(sw.x - viewSw.x);
1681 }
1682
1683 if (dx || dy) {
1684 return this.panBy([dx, dy]);
1685 }
1686
1687 return this;
1688 },
1689
1690 addLayer: function (layer) {
1691 // TODO method is too big, refactor
1692
1693 var id = L.stamp(layer);
1694
1695 if (this._layers[id]) { return this; }
1696
1697 this._layers[id] = layer;
1698
1699 // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
1700 if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
1701 this._zoomBoundLayers[id] = layer;
1702 this._updateZoomLevels();
1703 }
1704
1705 // TODO looks ugly, refactor!!!
1706 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1707 this._tileLayersNum++;
1708 this._tileLayersToLoad++;
1709 layer.on('load', this._onTileLayerLoad, this);
1710 }
1711
1712 if (this._loaded) {
1713 this._layerAdd(layer);
1714 }
1715
1716 return this;
1717 },
1718
1719 removeLayer: function (layer) {
1720 var id = L.stamp(layer);
1721
1722 if (!this._layers[id]) { return; }
1723
1724 if (this._loaded) {
1725 layer.onRemove(this);
1726 this.fire('layerremove', {layer: layer});
1727 }
1728
1729 delete this._layers[id];
1730 if (this._zoomBoundLayers[id]) {
1731 delete this._zoomBoundLayers[id];
1732 this._updateZoomLevels();
1733 }
1734
1735 // TODO looks ugly, refactor
1736 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1737 this._tileLayersNum--;
1738 this._tileLayersToLoad--;
1739 layer.off('load', this._onTileLayerLoad, this);
1740 }
1741
1742 return this;
1743 },
1744
1745 hasLayer: function (layer) {
1746 if (!layer) { return false; }
1747
1748 return (L.stamp(layer) in this._layers);
1749 },
1750
1751 eachLayer: function (method, context) {
1752 for (var i in this._layers) {
1753 method.call(context, this._layers[i]);
1754 }
1755 return this;
1756 },
1757
1758 invalidateSize: function (options) {
1759 options = L.extend({
1760 animate: false,
1761 pan: true
1762 }, options === true ? {animate: true} : options);
1763
1764 var oldSize = this.getSize();
1765 this._sizeChanged = true;
1766
1767 if (this.options.maxBounds) {
1768 this.setMaxBounds(this.options.maxBounds);
1769 }
1770
1771 if (!this._loaded) { return this; }
1772
1773 var newSize = this.getSize(),
1774 offset = oldSize.subtract(newSize).divideBy(2).round();
1775
1776 if (!offset.x && !offset.y) { return this; }
1777
1778 if (options.animate && options.pan) {
1779 this.panBy(offset);
1780
1781 } else {
1782 if (options.pan) {
1783 this._rawPanBy(offset);
1784 }
1785
1786 this.fire('move');
1787
1788 // make sure moveend is not fired too often on resize
1789 clearTimeout(this._sizeTimer);
1790 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
1791 }
1792
1793 return this.fire('resize', {
1794 oldSize: oldSize,
1795 newSize: newSize
1796 });
1797 },
1798
1799 // TODO handler.addTo
1800 addHandler: function (name, HandlerClass) {
1801 if (!HandlerClass) { return; }
1802
1803 var handler = this[name] = new HandlerClass(this);
1804
1805 this._handlers.push(handler);
1806
1807 if (this.options[name]) {
1808 handler.enable();
1809 }
1810
1811 return this;
1812 },
1813
1814 remove: function () {
1815 if (this._loaded) {
1816 this.fire('unload');
1817 }
1818
1819 this._initEvents('off');
1820
1821 delete this._container._leaflet;
1822
1823 this._clearPanes();
1824 if (this._clearControlPos) {
1825 this._clearControlPos();
1826 }
1827
1828 this._clearHandlers();
1829
1830 return this;
1831 },
1832
1833
1834 // public methods for getting map state
1835
1836 getCenter: function () { // (Boolean) -> LatLng
1837 this._checkIfLoaded();
1838
1839 if (!this._moved()) {
1840 return this._initialCenter;
1841 }
1842 return this.layerPointToLatLng(this._getCenterLayerPoint());
1843 },
1844
1845 getZoom: function () {
1846 return this._zoom;
1847 },
1848
1849 getBounds: function () {
1850 var bounds = this.getPixelBounds(),
1851 sw = this.unproject(bounds.getBottomLeft()),
1852 ne = this.unproject(bounds.getTopRight());
1853
1854 return new L.LatLngBounds(sw, ne);
1855 },
1856
1857 getMinZoom: function () {
1858 var z1 = this.options.minZoom || 0,
1859 z2 = this._layersMinZoom || 0,
1860 z3 = this._boundsMinZoom || 0;
1861
1862 return Math.max(z1, z2, z3);
1863 },
1864
1865 getMaxZoom: function () {
1866 var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
1867 z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom;
1868
1869 return Math.min(z1, z2);
1870 },
1871
1872 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
1873 bounds = L.latLngBounds(bounds);
1874
1875 var zoom = this.getMinZoom() - (inside ? 1 : 0),
1876 maxZoom = this.getMaxZoom(),
1877 size = this.getSize(),
1878
1879 nw = bounds.getNorthWest(),
1880 se = bounds.getSouthEast(),
1881
1882 zoomNotFound = true,
1883 boundsSize;
1884
1885 padding = L.point(padding || [0, 0]);
1886
1887 do {
1888 zoom++;
1889 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
1890 zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
1891
1892 } while (zoomNotFound && zoom <= maxZoom);
1893
1894 if (zoomNotFound && inside) {
1895 return null;
1896 }
1897
1898 return inside ? zoom : zoom - 1;
1899 },
1900
1901 getSize: function () {
1902 if (!this._size || this._sizeChanged) {
1903 this._size = new L.Point(
1904 this._container.clientWidth,
1905 this._container.clientHeight);
1906
1907 this._sizeChanged = false;
1908 }
1909 return this._size.clone();
1910 },
1911
1912 getPixelBounds: function () {
1913 var topLeftPoint = this._getTopLeftPoint();
1914 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
1915 },
1916
1917 getPixelOrigin: function () {
1918 this._checkIfLoaded();
1919 return this._initialTopLeftPoint;
1920 },
1921
1922 getPanes: function () {
1923 return this._panes;
1924 },
1925
1926 getContainer: function () {
1927 return this._container;
1928 },
1929
1930
1931 // TODO replace with universal implementation after refactoring projections
1932
1933 getZoomScale: function (toZoom) {
1934 var crs = this.options.crs;
1935 return crs.scale(toZoom) / crs.scale(this._zoom);
1936 },
1937
1938 getScaleZoom: function (scale) {
1939 return this._zoom + (Math.log(scale) / Math.LN2);
1940 },
1941
1942
1943 // conversion methods
1944
1945 project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
1946 zoom = zoom === undefined ? this._zoom : zoom;
1947 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
1948 },
1949
1950 unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
1951 zoom = zoom === undefined ? this._zoom : zoom;
1952 return this.options.crs.pointToLatLng(L.point(point), zoom);
1953 },
1954
1955 layerPointToLatLng: function (point) { // (Point)
1956 var projectedPoint = L.point(point).add(this.getPixelOrigin());
1957 return this.unproject(projectedPoint);
1958 },
1959
1960 latLngToLayerPoint: function (latlng) { // (LatLng)
1961 var projectedPoint = this.project(L.latLng(latlng))._round();
1962 return projectedPoint._subtract(this.getPixelOrigin());
1963 },
1964
1965 containerPointToLayerPoint: function (point) { // (Point)
1966 return L.point(point).subtract(this._getMapPanePos());
1967 },
1968
1969 layerPointToContainerPoint: function (point) { // (Point)
1970 return L.point(point).add(this._getMapPanePos());
1971 },
1972
1973 containerPointToLatLng: function (point) {
1974 var layerPoint = this.containerPointToLayerPoint(L.point(point));
1975 return this.layerPointToLatLng(layerPoint);
1976 },
1977
1978 latLngToContainerPoint: function (latlng) {
1979 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
1980 },
1981
1982 mouseEventToContainerPoint: function (e) { // (MouseEvent)
1983 return L.DomEvent.getMousePosition(e, this._container);
1984 },
1985
1986 mouseEventToLayerPoint: function (e) { // (MouseEvent)
1987 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1988 },
1989
1990 mouseEventToLatLng: function (e) { // (MouseEvent)
1991 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1992 },
1993
1994
1995 // map initialization methods
1996
1997 _initContainer: function (id) {
1998 var container = this._container = L.DomUtil.get(id);
1999
2000 if (!container) {
2001 throw new Error('Map container not found.');
2002 } else if (container._leaflet) {
2003 throw new Error('Map container is already initialized.');
2004 }
2005
2006 container._leaflet = true;
2007 },
2008
2009 _initLayout: function () {
2010 var container = this._container;
2011
2012 L.DomUtil.addClass(container, 'leaflet-container' +
2013 (L.Browser.touch ? ' leaflet-touch' : '') +
2014 (L.Browser.retina ? ' leaflet-retina' : '') +
2015 (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
2016
2017 var position = L.DomUtil.getStyle(container, 'position');
2018
2019 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2020 container.style.position = 'relative';
2021 }
2022
2023 this._initPanes();
2024
2025 if (this._initControlPos) {
2026 this._initControlPos();
2027 }
2028 },
2029
2030 _initPanes: function () {
2031 var panes = this._panes = {};
2032
2033 this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
2034
2035 this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
2036 panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
2037 panes.shadowPane = this._createPane('leaflet-shadow-pane');
2038 panes.overlayPane = this._createPane('leaflet-overlay-pane');
2039 panes.markerPane = this._createPane('leaflet-marker-pane');
2040 panes.popupPane = this._createPane('leaflet-popup-pane');
2041
2042 var zoomHide = ' leaflet-zoom-hide';
2043
2044 if (!this.options.markerZoomAnimation) {
2045 L.DomUtil.addClass(panes.markerPane, zoomHide);
2046 L.DomUtil.addClass(panes.shadowPane, zoomHide);
2047 L.DomUtil.addClass(panes.popupPane, zoomHide);
2048 }
2049 },
2050
2051 _createPane: function (className, container) {
2052 return L.DomUtil.create('div', className, container || this._panes.objectsPane);
2053 },
2054
2055 _clearPanes: function () {
2056 this._container.removeChild(this._mapPane);
2057 },
2058
2059 _addLayers: function (layers) {
2060 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
2061
2062 for (var i = 0, len = layers.length; i < len; i++) {
2063 this.addLayer(layers[i]);
2064 }
2065 },
2066
2067
2068 // private methods that modify map state
2069
2070 _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
2071
2072 var zoomChanged = (this._zoom !== zoom);
2073
2074 if (!afterZoomAnim) {
2075 this.fire('movestart');
2076
2077 if (zoomChanged) {
2078 this.fire('zoomstart');
2079 }
2080 }
2081
2082 this._zoom = zoom;
2083 this._initialCenter = center;
2084
2085 this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
2086
2087 if (!preserveMapOffset) {
2088 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
2089 } else {
2090 this._initialTopLeftPoint._add(this._getMapPanePos());
2091 }
2092
2093 this._tileLayersToLoad = this._tileLayersNum;
2094
2095 var loading = !this._loaded;
2096 this._loaded = true;
2097
2098 if (loading) {
2099 this.fire('load');
2100 this.eachLayer(this._layerAdd, this);
2101 }
2102
2103 this.fire('viewreset', {hard: !preserveMapOffset});
2104
2105 this.fire('move');
2106
2107 if (zoomChanged || afterZoomAnim) {
2108 this.fire('zoomend');
2109 }
2110
2111 this.fire('moveend', {hard: !preserveMapOffset});
2112 },
2113
2114 _rawPanBy: function (offset) {
2115 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
2116 },
2117
2118 _getZoomSpan: function () {
2119 return this.getMaxZoom() - this.getMinZoom();
2120 },
2121
2122 _updateZoomLevels: function () {
2123 var i,
2124 minZoom = Infinity,
2125 maxZoom = -Infinity,
2126 oldZoomSpan = this._getZoomSpan();
2127
2128 for (i in this._zoomBoundLayers) {
2129 var layer = this._zoomBoundLayers[i];
2130 if (!isNaN(layer.options.minZoom)) {
2131 minZoom = Math.min(minZoom, layer.options.minZoom);
2132 }
2133 if (!isNaN(layer.options.maxZoom)) {
2134 maxZoom = Math.max(maxZoom, layer.options.maxZoom);
2135 }
2136 }
2137
2138 if (i === undefined) { // we have no tilelayers
2139 this._layersMaxZoom = this._layersMinZoom = undefined;
2140 } else {
2141 this._layersMaxZoom = maxZoom;
2142 this._layersMinZoom = minZoom;
2143 }
2144
2145 if (oldZoomSpan !== this._getZoomSpan()) {
2146 this.fire('zoomlevelschange');
2147 }
2148 },
2149
2150 _panInsideMaxBounds: function () {
2151 this.panInsideBounds(this.options.maxBounds);
2152 },
2153
2154 _checkIfLoaded: function () {
2155 if (!this._loaded) {
2156 throw new Error('Set map center and zoom first.');
2157 }
2158 },
2159
2160 // map events
2161
2162 _initEvents: function (onOff) {
2163 if (!L.DomEvent) { return; }
2164
2165 onOff = onOff || 'on';
2166
2167 L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
2168
2169 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
2170 'mouseleave', 'mousemove', 'contextmenu'],
2171 i, len;
2172
2173 for (i = 0, len = events.length; i < len; i++) {
2174 L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
2175 }
2176
2177 if (this.options.trackResize) {
2178 L.DomEvent[onOff](window, 'resize', this._onResize, this);
2179 }
2180 },
2181
2182 _onResize: function () {
2183 L.Util.cancelAnimFrame(this._resizeRequest);
2184 this._resizeRequest = L.Util.requestAnimFrame(
2185 this.invalidateSize, this, false, this._container);
2186 },
2187
2188 _onMouseClick: function (e) {
2189 // jshint camelcase: false
2190 if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) || e._leaflet_stop) { return; }
2191
2192 this.fire('preclick');
2193 this._fireMouseEvent(e);
2194 },
2195
2196 _fireMouseEvent: function (e) {
2197 // jshint camelcase: false
2198 if (!this._loaded || e._leaflet_stop) { return; }
2199
2200 var type = e.type;
2201
2202 type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
2203
2204 if (!this.hasEventListeners(type)) { return; }
2205
2206 if (type === 'contextmenu') {
2207 L.DomEvent.preventDefault(e);
2208 }
2209
2210 var containerPoint = this.mouseEventToContainerPoint(e),
2211 layerPoint = this.containerPointToLayerPoint(containerPoint),
2212 latlng = this.layerPointToLatLng(layerPoint);
2213
2214 this.fire(type, {
2215 latlng: latlng,
2216 layerPoint: layerPoint,
2217 containerPoint: containerPoint,
2218 originalEvent: e
2219 });
2220 },
2221
2222 _onTileLayerLoad: function () {
2223 this._tileLayersToLoad--;
2224 if (this._tileLayersNum && !this._tileLayersToLoad) {
2225 this.fire('tilelayersload');
2226 }
2227 },
2228
2229 _clearHandlers: function () {
2230 for (var i = 0, len = this._handlers.length; i < len; i++) {
2231 this._handlers[i].disable();
2232 }
2233 },
2234
2235 whenReady: function (callback, context) {
2236 if (this._loaded) {
2237 callback.call(context || this, this);
2238 } else {
2239 this.on('load', callback, context);
2240 }
2241 return this;
2242 },
2243
2244 _layerAdd: function (layer) {
2245 layer.onAdd(this);
2246 this.fire('layeradd', {layer: layer});
2247 },
2248
2249
2250 // private methods for getting map state
2251
2252 _getMapPanePos: function () {
2253 return L.DomUtil.getPosition(this._mapPane);
2254 },
2255
2256 _moved: function () {
2257 var pos = this._getMapPanePos();
2258 return pos && !pos.equals([0, 0]);
2259 },
2260
2261 _getTopLeftPoint: function () {
2262 return this.getPixelOrigin().subtract(this._getMapPanePos());
2263 },
2264
2265 _getNewTopLeftPoint: function (center, zoom) {
2266 var viewHalf = this.getSize()._divideBy(2);
2267 // TODO round on display, not calculation to increase precision?
2268 return this.project(center, zoom)._subtract(viewHalf)._round();
2269 },
2270
2271 _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
2272 var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
2273 return this.project(latlng, newZoom)._subtract(topLeft);
2274 },
2275
2276 // layer point of the current center
2277 _getCenterLayerPoint: function () {
2278 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
2279 },
2280
2281 // offset of the specified place to the current center in pixels
2282 _getCenterOffset: function (latlng) {
2283 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
2284 },
2285
2286 _limitZoom: function (zoom) {
2287 var min = this.getMinZoom(),
2288 max = this.getMaxZoom();
2289
2290 return Math.max(min, Math.min(max, zoom));
2291 }
2292 });
2293
2294 L.map = function (id, options) {
2295 return new L.Map(id, options);
2296 };
2297
2298
2299 /*
2300 * Mercator projection that takes into account that the Earth is not a perfect sphere.
2301 * Less popular than spherical mercator; used by projections like EPSG:3395.
2302 */
2303
2304 L.Projection.Mercator = {
2305 MAX_LATITUDE: 85.0840591556,
2306
2307 R_MINOR: 6356752.314245179,
2308 R_MAJOR: 6378137,
2309
2310 project: function (latlng) { // (LatLng) -> Point
2311 var d = L.LatLng.DEG_TO_RAD,
2312 max = this.MAX_LATITUDE,
2313 lat = Math.max(Math.min(max, latlng.lat), -max),
2314 r = this.R_MAJOR,
2315 r2 = this.R_MINOR,
2316 x = latlng.lng * d * r,
2317 y = lat * d,
2318 tmp = r2 / r,
2319 eccent = Math.sqrt(1.0 - tmp * tmp),
2320 con = eccent * Math.sin(y);
2321
2322 con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
2323
2324 var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
2325 y = -r * Math.log(ts);
2326
2327 return new L.Point(x, y);
2328 },
2329
2330 unproject: function (point) { // (Point, Boolean) -> LatLng
2331 var d = L.LatLng.RAD_TO_DEG,
2332 r = this.R_MAJOR,
2333 r2 = this.R_MINOR,
2334 lng = point.x * d / r,
2335 tmp = r2 / r,
2336 eccent = Math.sqrt(1 - (tmp * tmp)),
2337 ts = Math.exp(- point.y / r2),
2338 phi = (Math.PI / 2) - 2 * Math.atan(ts),
2339 numIter = 15,
2340 tol = 1e-7,
2341 i = numIter,
2342 dphi = 0.1,
2343 con;
2344
2345 while ((Math.abs(dphi) > tol) && (--i > 0)) {
2346 con = eccent * Math.sin(phi);
2347 dphi = (Math.PI / 2) - 2 * Math.atan(ts *
2348 Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
2349 phi += dphi;
2350 }
2351
2352 return new L.LatLng(phi * d, lng);
2353 }
2354 };
2355
2356
2357
2358 L.CRS.EPSG3395 = L.extend({}, L.CRS, {
2359 code: 'EPSG:3395',
2360
2361 projection: L.Projection.Mercator,
2362
2363 transformation: (function () {
2364 var m = L.Projection.Mercator,
2365 r = m.R_MAJOR,
2366 r2 = m.R_MINOR;
2367
2368 return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
2369 }())
2370 });
2371
2372
2373 /*
2374 * L.TileLayer is used for standard xyz-numbered tile layers.
2375 */
2376
2377 L.TileLayer = L.Class.extend({
2378 includes: L.Mixin.Events,
2379
2380 options: {
2381 minZoom: 0,
2382 maxZoom: 18,
2383 tileSize: 256,
2384 subdomains: 'abc',
2385 errorTileUrl: '',
2386 attribution: '',
2387 zoomOffset: 0,
2388 opacity: 1,
2389 /* (undefined works too)
2390 zIndex: null,
2391 tms: false,
2392 continuousWorld: false,
2393 noWrap: false,
2394 zoomReverse: false,
2395 detectRetina: false,
2396 reuseTiles: false,
2397 bounds: false,
2398 */
2399 unloadInvisibleTiles: L.Browser.mobile,
2400 updateWhenIdle: L.Browser.mobile
2401 },
2402
2403 initialize: function (url, options) {
2404 options = L.setOptions(this, options);
2405
2406 // detecting retina displays, adjusting tileSize and zoom levels
2407 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
2408
2409 options.tileSize = Math.floor(options.tileSize / 2);
2410 options.zoomOffset++;
2411
2412 if (options.minZoom > 0) {
2413 options.minZoom--;
2414 }
2415 this.options.maxZoom--;
2416 }
2417
2418 if (options.bounds) {
2419 options.bounds = L.latLngBounds(options.bounds);
2420 }
2421
2422 this._url = url;
2423
2424 var subdomains = this.options.subdomains;
2425
2426 if (typeof subdomains === 'string') {
2427 this.options.subdomains = subdomains.split('');
2428 }
2429 },
2430
2431 onAdd: function (map) {
2432 this._map = map;
2433 this._animated = map._zoomAnimated;
2434
2435 // create a container div for tiles
2436 this._initContainer();
2437
2438 // create an image to clone for tiles
2439 this._createTileProto();
2440
2441 // set up events
2442 map.on({
2443 'viewreset': this._reset,
2444 'moveend': this._update
2445 }, this);
2446
2447 if (this._animated) {
2448 map.on({
2449 'zoomanim': this._animateZoom,
2450 'zoomend': this._endZoomAnim
2451 }, this);
2452 }
2453
2454 if (!this.options.updateWhenIdle) {
2455 this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
2456 map.on('move', this._limitedUpdate, this);
2457 }
2458
2459 this._reset();
2460 this._update();
2461 },
2462
2463 addTo: function (map) {
2464 map.addLayer(this);
2465 return this;
2466 },
2467
2468 onRemove: function (map) {
2469 this._container.parentNode.removeChild(this._container);
2470
2471 map.off({
2472 'viewreset': this._reset,
2473 'moveend': this._update
2474 }, this);
2475
2476 if (this._animated) {
2477 map.off({
2478 'zoomanim': this._animateZoom,
2479 'zoomend': this._endZoomAnim
2480 }, this);
2481 }
2482
2483 if (!this.options.updateWhenIdle) {
2484 map.off('move', this._limitedUpdate, this);
2485 }
2486
2487 this._container = null;
2488 this._map = null;
2489 },
2490
2491 bringToFront: function () {
2492 var pane = this._map._panes.tilePane;
2493
2494 if (this._container) {
2495 pane.appendChild(this._container);
2496 this._setAutoZIndex(pane, Math.max);
2497 }
2498
2499 return this;
2500 },
2501
2502 bringToBack: function () {
2503 var pane = this._map._panes.tilePane;
2504
2505 if (this._container) {
2506 pane.insertBefore(this._container, pane.firstChild);
2507 this._setAutoZIndex(pane, Math.min);
2508 }
2509
2510 return this;
2511 },
2512
2513 getAttribution: function () {
2514 return this.options.attribution;
2515 },
2516
2517 getContainer: function () {
2518 return this._container;
2519 },
2520
2521 setOpacity: function (opacity) {
2522 this.options.opacity = opacity;
2523
2524 if (this._map) {
2525 this._updateOpacity();
2526 }
2527
2528 return this;
2529 },
2530
2531 setZIndex: function (zIndex) {
2532 this.options.zIndex = zIndex;
2533 this._updateZIndex();
2534
2535 return this;
2536 },
2537
2538 setUrl: function (url, noRedraw) {
2539 this._url = url;
2540
2541 if (!noRedraw) {
2542 this.redraw();
2543 }
2544
2545 return this;
2546 },
2547
2548 redraw: function () {
2549 if (this._map) {
2550 this._reset({hard: true});
2551 this._update();
2552 }
2553 return this;
2554 },
2555
2556 _updateZIndex: function () {
2557 if (this._container && this.options.zIndex !== undefined) {
2558 this._container.style.zIndex = this.options.zIndex;
2559 }
2560 },
2561
2562 _setAutoZIndex: function (pane, compare) {
2563
2564 var layers = pane.children,
2565 edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
2566 zIndex, i, len;
2567
2568 for (i = 0, len = layers.length; i < len; i++) {
2569
2570 if (layers[i] !== this._container) {
2571 zIndex = parseInt(layers[i].style.zIndex, 10);
2572
2573 if (!isNaN(zIndex)) {
2574 edgeZIndex = compare(edgeZIndex, zIndex);
2575 }
2576 }
2577 }
2578
2579 this.options.zIndex = this._container.style.zIndex =
2580 (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
2581 },
2582
2583 _updateOpacity: function () {
2584 var i,
2585 tiles = this._tiles;
2586
2587 if (L.Browser.ielt9) {
2588 for (i in tiles) {
2589 L.DomUtil.setOpacity(tiles[i], this.options.opacity);
2590 }
2591 } else {
2592 L.DomUtil.setOpacity(this._container, this.options.opacity);
2593 }
2594 },
2595
2596 _initContainer: function () {
2597 var tilePane = this._map._panes.tilePane;
2598
2599 if (!this._container) {
2600 this._container = L.DomUtil.create('div', 'leaflet-layer');
2601
2602 this._updateZIndex();
2603
2604 if (this._animated) {
2605 var className = 'leaflet-tile-container leaflet-zoom-animated';
2606
2607 this._bgBuffer = L.DomUtil.create('div', className, this._container);
2608 this._bgBuffer.style.zIndex = 1;
2609
2610 this._tileContainer = L.DomUtil.create('div', className, this._container);
2611 this._tileContainer.style.zIndex = 2;
2612
2613 } else {
2614 this._tileContainer = this._container;
2615 }
2616
2617 tilePane.appendChild(this._container);
2618
2619 if (this.options.opacity < 1) {
2620 this._updateOpacity();
2621 }
2622 }
2623 },
2624
2625 _reset: function (e) {
2626 for (var key in this._tiles) {
2627 this.fire('tileunload', {tile: this._tiles[key]});
2628 }
2629
2630 this._tiles = {};
2631 this._tilesToLoad = 0;
2632
2633 if (this.options.reuseTiles) {
2634 this._unusedTiles = [];
2635 }
2636
2637 this._tileContainer.innerHTML = '';
2638
2639 if (this._animated && e && e.hard) {
2640 this._clearBgBuffer();
2641 }
2642
2643 this._initContainer();
2644 },
2645
2646 _update: function () {
2647
2648 if (!this._map) { return; }
2649
2650 var bounds = this._map.getPixelBounds(),
2651 zoom = this._map.getZoom(),
2652 tileSize = this.options.tileSize;
2653
2654 if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
2655 return;
2656 }
2657
2658 var tileBounds = L.bounds(
2659 bounds.min.divideBy(tileSize)._floor(),
2660 bounds.max.divideBy(tileSize)._floor());
2661
2662 this._addTilesFromCenterOut(tileBounds);
2663
2664 if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
2665 this._removeOtherTiles(tileBounds);
2666 }
2667 },
2668
2669 _addTilesFromCenterOut: function (bounds) {
2670 var queue = [],
2671 center = bounds.getCenter();
2672
2673 var j, i, point;
2674
2675 for (j = bounds.min.y; j <= bounds.max.y; j++) {
2676 for (i = bounds.min.x; i <= bounds.max.x; i++) {
2677 point = new L.Point(i, j);
2678
2679 if (this._tileShouldBeLoaded(point)) {
2680 queue.push(point);
2681 }
2682 }
2683 }
2684
2685 var tilesToLoad = queue.length;
2686
2687 if (tilesToLoad === 0) { return; }
2688
2689 // load tiles in order of their distance to center
2690 queue.sort(function (a, b) {
2691 return a.distanceTo(center) - b.distanceTo(center);
2692 });
2693
2694 var fragment = document.createDocumentFragment();
2695
2696 // if its the first batch of tiles to load
2697 if (!this._tilesToLoad) {
2698 this.fire('loading');
2699 }
2700
2701 this._tilesToLoad += tilesToLoad;
2702
2703 for (i = 0; i < tilesToLoad; i++) {
2704 this._addTile(queue[i], fragment);
2705 }
2706
2707 this._tileContainer.appendChild(fragment);
2708 },
2709
2710 _tileShouldBeLoaded: function (tilePoint) {
2711 if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
2712 return false; // already loaded
2713 }
2714
2715 var options = this.options;
2716
2717 if (!options.continuousWorld) {
2718 var limit = this._getWrapTileNum();
2719
2720 // don't load if exceeds world bounds
2721 if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) ||
2722 tilePoint.y < 0 || tilePoint.y >= limit) { return false; }
2723 }
2724
2725 if (options.bounds) {
2726 var tileSize = options.tileSize,
2727 nwPoint = tilePoint.multiplyBy(tileSize),
2728 sePoint = nwPoint.add([tileSize, tileSize]),
2729 nw = this._map.unproject(nwPoint),
2730 se = this._map.unproject(sePoint);
2731
2732 // TODO temporary hack, will be removed after refactoring projections
2733 // https://github.com/Leaflet/Leaflet/issues/1618
2734 if (!options.continuousWorld && !options.noWrap) {
2735 nw = nw.wrap();
2736 se = se.wrap();
2737 }
2738
2739 if (!options.bounds.intersects([nw, se])) { return false; }
2740 }
2741
2742 return true;
2743 },
2744
2745 _removeOtherTiles: function (bounds) {
2746 var kArr, x, y, key;
2747
2748 for (key in this._tiles) {
2749 kArr = key.split(':');
2750 x = parseInt(kArr[0], 10);
2751 y = parseInt(kArr[1], 10);
2752
2753 // remove tile if it's out of bounds
2754 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
2755 this._removeTile(key);
2756 }
2757 }
2758 },
2759
2760 _removeTile: function (key) {
2761 var tile = this._tiles[key];
2762
2763 this.fire('tileunload', {tile: tile, url: tile.src});
2764
2765 if (this.options.reuseTiles) {
2766 L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
2767 this._unusedTiles.push(tile);
2768
2769 } else if (tile.parentNode === this._tileContainer) {
2770 this._tileContainer.removeChild(tile);
2771 }
2772
2773 // for https://github.com/CloudMade/Leaflet/issues/137
2774 if (!L.Browser.android) {
2775 tile.onload = null;
2776 tile.src = L.Util.emptyImageUrl;
2777 }
2778
2779 delete this._tiles[key];
2780 },
2781
2782 _addTile: function (tilePoint, container) {
2783 var tilePos = this._getTilePos(tilePoint);
2784
2785 // get unused tile - or create a new tile
2786 var tile = this._getTile();
2787
2788 /*
2789 Chrome 20 layouts much faster with top/left (verify with timeline, frames)
2790 Android 4 browser has display issues with top/left and requires transform instead
2791 Android 2 browser requires top/left or tiles disappear on load or first drag
2792 (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
2793 (other browsers don't currently care) - see debug/hacks/jitter.html for an example
2794 */
2795 L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
2796
2797 this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
2798
2799 this._loadTile(tile, tilePoint);
2800
2801 if (tile.parentNode !== this._tileContainer) {
2802 container.appendChild(tile);
2803 }
2804 },
2805
2806 _getZoomForUrl: function () {
2807
2808 var options = this.options,
2809 zoom = this._map.getZoom();
2810
2811 if (options.zoomReverse) {
2812 zoom = options.maxZoom - zoom;
2813 }
2814
2815 return zoom + options.zoomOffset;
2816 },
2817
2818 _getTilePos: function (tilePoint) {
2819 var origin = this._map.getPixelOrigin(),
2820 tileSize = this.options.tileSize;
2821
2822 return tilePoint.multiplyBy(tileSize).subtract(origin);
2823 },
2824
2825 // image-specific code (override to implement e.g. Canvas or SVG tile layer)
2826
2827 getTileUrl: function (tilePoint) {
2828 return L.Util.template(this._url, L.extend({
2829 s: this._getSubdomain(tilePoint),
2830 z: tilePoint.z,
2831 x: tilePoint.x,
2832 y: tilePoint.y
2833 }, this.options));
2834 },
2835
2836 _getWrapTileNum: function () {
2837 // TODO refactor, limit is not valid for non-standard projections
2838 return Math.pow(2, this._getZoomForUrl());
2839 },
2840
2841 _adjustTilePoint: function (tilePoint) {
2842
2843 var limit = this._getWrapTileNum();
2844
2845 // wrap tile coordinates
2846 if (!this.options.continuousWorld && !this.options.noWrap) {
2847 tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
2848 }
2849
2850 if (this.options.tms) {
2851 tilePoint.y = limit - tilePoint.y - 1;
2852 }
2853
2854 tilePoint.z = this._getZoomForUrl();
2855 },
2856
2857 _getSubdomain: function (tilePoint) {
2858 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
2859 return this.options.subdomains[index];
2860 },
2861
2862 _createTileProto: function () {
2863 var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
2864 img.style.width = img.style.height = this.options.tileSize + 'px';
2865 img.galleryimg = 'no';
2866 },
2867
2868 _getTile: function () {
2869 if (this.options.reuseTiles && this._unusedTiles.length > 0) {
2870 var tile = this._unusedTiles.pop();
2871 this._resetTile(tile);
2872 return tile;
2873 }
2874 return this._createTile();
2875 },
2876
2877 // Override if data stored on a tile needs to be cleaned up before reuse
2878 _resetTile: function (/*tile*/) {},
2879
2880 _createTile: function () {
2881 var tile = this._tileImg.cloneNode(false);
2882 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2883
2884 if (L.Browser.ielt9 && this.options.opacity !== undefined) {
2885 L.DomUtil.setOpacity(tile, this.options.opacity);
2886 }
2887 return tile;
2888 },
2889
2890 _loadTile: function (tile, tilePoint) {
2891 tile._layer = this;
2892 tile.onload = this._tileOnLoad;
2893 tile.onerror = this._tileOnError;
2894
2895 this._adjustTilePoint(tilePoint);
2896 tile.src = this.getTileUrl(tilePoint);
2897 },
2898
2899 _tileLoaded: function () {
2900 this._tilesToLoad--;
2901 if (!this._tilesToLoad) {
2902 this.fire('load');
2903
2904 if (this._animated) {
2905 // clear scaled tiles after all new tiles are loaded (for performance)
2906 clearTimeout(this._clearBgBufferTimer);
2907 this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
2908 }
2909 }
2910 },
2911
2912 _tileOnLoad: function () {
2913 var layer = this._layer;
2914
2915 //Only if we are loading an actual image
2916 if (this.src !== L.Util.emptyImageUrl) {
2917 L.DomUtil.addClass(this, 'leaflet-tile-loaded');
2918
2919 layer.fire('tileload', {
2920 tile: this,
2921 url: this.src
2922 });
2923 }
2924
2925 layer._tileLoaded();
2926 },
2927
2928 _tileOnError: function () {
2929 var layer = this._layer;
2930
2931 layer.fire('tileerror', {
2932 tile: this,
2933 url: this.src
2934 });
2935
2936 var newUrl = layer.options.errorTileUrl;
2937 if (newUrl) {
2938 this.src = newUrl;
2939 }
2940
2941 layer._tileLoaded();
2942 }
2943 });
2944
2945 L.tileLayer = function (url, options) {
2946 return new L.TileLayer(url, options);
2947 };
2948
2949
2950 /*
2951 * L.TileLayer.WMS is used for putting WMS tile layers on the map.
2952 */
2953
2954 L.TileLayer.WMS = L.TileLayer.extend({
2955
2956 defaultWmsParams: {
2957 service: 'WMS',
2958 request: 'GetMap',
2959 version: '1.1.1',
2960 layers: '',
2961 styles: '',
2962 format: 'image/jpeg',
2963 transparent: false
2964 },
2965
2966 initialize: function (url, options) { // (String, Object)
2967
2968 this._url = url;
2969
2970 var wmsParams = L.extend({}, this.defaultWmsParams),
2971 tileSize = options.tileSize || this.options.tileSize;
2972
2973 if (options.detectRetina && L.Browser.retina) {
2974 wmsParams.width = wmsParams.height = tileSize * 2;
2975 } else {
2976 wmsParams.width = wmsParams.height = tileSize;
2977 }
2978
2979 for (var i in options) {
2980 // all keys that are not TileLayer options go to WMS params
2981 if (!this.options.hasOwnProperty(i) && i !== 'crs') {
2982 wmsParams[i] = options[i];
2983 }
2984 }
2985
2986 this.wmsParams = wmsParams;
2987
2988 L.setOptions(this, options);
2989 },
2990
2991 onAdd: function (map) {
2992
2993 this._crs = this.options.crs || map.options.crs;
2994
2995 var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
2996 this.wmsParams[projectionKey] = this._crs.code;
2997
2998 L.TileLayer.prototype.onAdd.call(this, map);
2999 },
3000
3001 getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
3002
3003 var map = this._map,
3004 tileSize = this.options.tileSize,
3005
3006 nwPoint = tilePoint.multiplyBy(tileSize),
3007 sePoint = nwPoint.add([tileSize, tileSize]),
3008
3009 nw = this._crs.project(map.unproject(nwPoint, zoom)),
3010 se = this._crs.project(map.unproject(sePoint, zoom)),
3011
3012 bbox = [nw.x, se.y, se.x, nw.y].join(','),
3013
3014 url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
3015
3016 return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;
3017 },
3018
3019 setParams: function (params, noRedraw) {
3020
3021 L.extend(this.wmsParams, params);
3022
3023 if (!noRedraw) {
3024 this.redraw();
3025 }
3026
3027 return this;
3028 }
3029 });
3030
3031 L.tileLayer.wms = function (url, options) {
3032 return new L.TileLayer.WMS(url, options);
3033 };
3034
3035
3036 /*
3037 * L.TileLayer.Canvas is a class that you can use as a base for creating
3038 * dynamically drawn Canvas-based tile layers.
3039 */
3040
3041 L.TileLayer.Canvas = L.TileLayer.extend({
3042 options: {
3043 async: false
3044 },
3045
3046 initialize: function (options) {
3047 L.setOptions(this, options);
3048 },
3049
3050 redraw: function () {
3051 for (var i in this._tiles) {
3052 this._redrawTile(this._tiles[i]);
3053 }
3054 return this;
3055 },
3056
3057 _redrawTile: function (tile) {
3058 this.drawTile(tile, tile._tilePoint, this._map._zoom);
3059 },
3060
3061 _createTileProto: function () {
3062 var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
3063 proto.width = proto.height = this.options.tileSize;
3064 },
3065
3066 _createTile: function () {
3067 var tile = this._canvasProto.cloneNode(false);
3068 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
3069 return tile;
3070 },
3071
3072 _loadTile: function (tile, tilePoint) {
3073 tile._layer = this;
3074 tile._tilePoint = tilePoint;
3075
3076 this._redrawTile(tile);
3077
3078 if (!this.options.async) {
3079 this.tileDrawn(tile);
3080 }
3081 },
3082
3083 drawTile: function (/*tile, tilePoint*/) {
3084 // override with rendering code
3085 },
3086
3087 tileDrawn: function (tile) {
3088 this._tileOnLoad.call(tile);
3089 }
3090 });
3091
3092
3093 L.tileLayer.canvas = function (options) {
3094 return new L.TileLayer.Canvas(options);
3095 };
3096
3097
3098 /*
3099 * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
3100 */
3101
3102 L.ImageOverlay = L.Class.extend({
3103 includes: L.Mixin.Events,
3104
3105 options: {
3106 opacity: 1
3107 },
3108
3109 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
3110 this._url = url;
3111 this._bounds = L.latLngBounds(bounds);
3112
3113 L.setOptions(this, options);
3114 },
3115
3116 onAdd: function (map) {
3117 this._map = map;
3118
3119 if (!this._image) {
3120 this._initImage();
3121 }
3122
3123 map._panes.overlayPane.appendChild(this._image);
3124
3125 map.on('viewreset', this._reset, this);
3126
3127 if (map.options.zoomAnimation && L.Browser.any3d) {
3128 map.on('zoomanim', this._animateZoom, this);
3129 }
3130
3131 this._reset();
3132 },
3133
3134 onRemove: function (map) {
3135 map.getPanes().overlayPane.removeChild(this._image);
3136
3137 map.off('viewreset', this._reset, this);
3138
3139 if (map.options.zoomAnimation) {
3140 map.off('zoomanim', this._animateZoom, this);
3141 }
3142 },
3143
3144 addTo: function (map) {
3145 map.addLayer(this);
3146 return this;
3147 },
3148
3149 setOpacity: function (opacity) {
3150 this.options.opacity = opacity;
3151 this._updateOpacity();
3152 return this;
3153 },
3154
3155 // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
3156 bringToFront: function () {
3157 if (this._image) {
3158 this._map._panes.overlayPane.appendChild(this._image);
3159 }
3160 return this;
3161 },
3162
3163 bringToBack: function () {
3164 var pane = this._map._panes.overlayPane;
3165 if (this._image) {
3166 pane.insertBefore(this._image, pane.firstChild);
3167 }
3168 return this;
3169 },
3170
3171 _initImage: function () {
3172 this._image = L.DomUtil.create('img', 'leaflet-image-layer');
3173
3174 if (this._map.options.zoomAnimation && L.Browser.any3d) {
3175 L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
3176 } else {
3177 L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
3178 }
3179
3180 this._updateOpacity();
3181
3182 //TODO createImage util method to remove duplication
3183 L.extend(this._image, {
3184 galleryimg: 'no',
3185 onselectstart: L.Util.falseFn,
3186 onmousemove: L.Util.falseFn,
3187 onload: L.bind(this._onImageLoad, this),
3188 src: this._url
3189 });
3190 },
3191
3192 _animateZoom: function (e) {
3193 var map = this._map,
3194 image = this._image,
3195 scale = map.getZoomScale(e.zoom),
3196 nw = this._bounds.getNorthWest(),
3197 se = this._bounds.getSouthEast(),
3198
3199 topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
3200 size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
3201 origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
3202
3203 image.style[L.DomUtil.TRANSFORM] =
3204 L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
3205 },
3206
3207 _reset: function () {
3208 var image = this._image,
3209 topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
3210 size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
3211
3212 L.DomUtil.setPosition(image, topLeft);
3213
3214 image.style.width = size.x + 'px';
3215 image.style.height = size.y + 'px';
3216 },
3217
3218 _onImageLoad: function () {
3219 this.fire('load');
3220 },
3221
3222 _updateOpacity: function () {
3223 L.DomUtil.setOpacity(this._image, this.options.opacity);
3224 }
3225 });
3226
3227 L.imageOverlay = function (url, bounds, options) {
3228 return new L.ImageOverlay(url, bounds, options);
3229 };
3230
3231
3232 /*
3233 * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
3234 */
3235
3236 L.Icon = L.Class.extend({
3237 options: {
3238 /*
3239 iconUrl: (String) (required)
3240 iconRetinaUrl: (String) (optional, used for retina devices if detected)
3241 iconSize: (Point) (can be set through CSS)
3242 iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
3243 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
3244 shadowUrl: (String) (no shadow by default)
3245 shadowRetinaUrl: (String) (optional, used for retina devices if detected)
3246 shadowSize: (Point)
3247 shadowAnchor: (Point)
3248 */
3249 className: ''
3250 },
3251
3252 initialize: function (options) {
3253 L.setOptions(this, options);
3254 },
3255
3256 createIcon: function (oldIcon) {
3257 return this._createIcon('icon', oldIcon);
3258 },
3259
3260 createShadow: function (oldIcon) {
3261 return this._createIcon('shadow', oldIcon);
3262 },
3263
3264 _createIcon: function (name, oldIcon) {
3265 var src = this._getIconUrl(name);
3266
3267 if (!src) {
3268 if (name === 'icon') {
3269 throw new Error('iconUrl not set in Icon options (see the docs).');
3270 }
3271 return null;
3272 }
3273
3274 var img;
3275 if (!oldIcon || oldIcon.tagName !== 'IMG') {
3276 img = this._createImg(src);
3277 } else {
3278 img = this._createImg(src, oldIcon);
3279 }
3280 this._setIconStyles(img, name);
3281
3282 return img;
3283 },
3284
3285 _setIconStyles: function (img, name) {
3286 var options = this.options,
3287 size = L.point(options[name + 'Size']),
3288 anchor;
3289
3290 if (name === 'shadow') {
3291 anchor = L.point(options.shadowAnchor || options.iconAnchor);
3292 } else {
3293 anchor = L.point(options.iconAnchor);
3294 }
3295
3296 if (!anchor && size) {
3297 anchor = size.divideBy(2, true);
3298 }
3299
3300 img.className = 'leaflet-marker-' + name + ' ' + options.className;
3301
3302 if (anchor) {
3303 img.style.marginLeft = (-anchor.x) + 'px';
3304 img.style.marginTop = (-anchor.y) + 'px';
3305 }
3306
3307 if (size) {
3308 img.style.width = size.x + 'px';
3309 img.style.height = size.y + 'px';
3310 }
3311 },
3312
3313 _createImg: function (src, el) {
3314
3315 if (!L.Browser.ie6) {
3316 if (!el) {
3317 el = document.createElement('img');
3318 }
3319 el.src = src;
3320 } else {
3321 if (!el) {
3322 el = document.createElement('div');
3323 }
3324 el.style.filter =
3325 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
3326 }
3327 return el;
3328 },
3329
3330 _getIconUrl: function (name) {
3331 if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
3332 return this.options[name + 'RetinaUrl'];
3333 }
3334 return this.options[name + 'Url'];
3335 }
3336 });
3337
3338 L.icon = function (options) {
3339 return new L.Icon(options);
3340 };
29993341
30003342
30013343 /*
30053347 L.Icon.Default = L.Icon.extend({
30063348
30073349 options: {
3008 iconSize: new L.Point(25, 41),
3009 iconAnchor: new L.Point(12, 41),
3010 popupAnchor: new L.Point(1, -34),
3011
3012 shadowSize: new L.Point(41, 41)
3350 iconSize: [25, 41],
3351 iconAnchor: [12, 41],
3352 popupAnchor: [1, -34],
3353
3354 shadowSize: [41, 41]
30133355 },
30143356
30153357 _getIconUrl: function (name) {
30203362 }
30213363
30223364 if (L.Browser.retina && name === 'icon') {
3023 name += '@2x';
3365 name += '-2x';
30243366 }
30253367
30263368 var path = L.Icon.Default.imagePath;
30273369
30283370 if (!path) {
3029 throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
3371 throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
30303372 }
30313373
30323374 return path + '/marker-' + name + '.png';
30353377
30363378 L.Icon.Default.imagePath = (function () {
30373379 var scripts = document.getElementsByTagName('script'),
3038 leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
3039
3040 var i, len, src, matches;
3380 leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
3381
3382 var i, len, src, matches, path;
30413383
30423384 for (i = 0, len = scripts.length; i < len; i++) {
30433385 src = scripts[i].src;
30443386 matches = src.match(leafletRe);
30453387
30463388 if (matches) {
3047 return src.split(leafletRe)[0] + '/images';
3389 path = src.split(leafletRe)[0];
3390 return (path ? path + '/' : '') + 'images';
30483391 }
30493392 }
30503393 }());
30513394
30523395
3053 /*
3054 * L.Marker is used to display clickable/draggable icons on the map.
3055 */
3056
3057 L.Marker = L.Class.extend({
3058
3059 includes: L.Mixin.Events,
3060
3061 options: {
3062 icon: new L.Icon.Default(),
3063 title: '',
3064 clickable: true,
3065 draggable: false,
3066 zIndexOffset: 0,
3067 opacity: 1,
3068 riseOnHover: false,
3069 riseOffset: 250
3070 },
3071
3072 initialize: function (latlng, options) {
3073 L.setOptions(this, options);
3074 this._latlng = L.latLng(latlng);
3075 },
3076
3077 onAdd: function (map) {
3078 this._map = map;
3079
3080 map.on('viewreset', this.update, this);
3081
3082 this._initIcon();
3083 this.update();
3084
3085 if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
3086 map.on('zoomanim', this._animateZoom, this);
3087 }
3088 },
3089
3090 addTo: function (map) {
3091 map.addLayer(this);
3092 return this;
3093 },
3094
3095 onRemove: function (map) {
3096 this._removeIcon();
3097
3098 this.fire('remove');
3099
3100 map.off({
3101 'viewreset': this.update,
3102 'zoomanim': this._animateZoom
3103 }, this);
3104
3105 this._map = null;
3106 },
3107
3108 getLatLng: function () {
3109 return this._latlng;
3110 },
3111
3112 setLatLng: function (latlng) {
3113 this._latlng = L.latLng(latlng);
3114
3115 this.update();
3116
3117 return this.fire('move', { latlng: this._latlng });
3118 },
3119
3120 setZIndexOffset: function (offset) {
3121 this.options.zIndexOffset = offset;
3122 this.update();
3123
3124 return this;
3125 },
3126
3127 setIcon: function (icon) {
3128 if (this._map) {
3129 this._removeIcon();
3130 }
3131
3132 this.options.icon = icon;
3133
3134 if (this._map) {
3135 this._initIcon();
3136 this.update();
3137 }
3138
3139 return this;
3140 },
3141
3142 update: function () {
3143 if (this._icon) {
3144 var pos = this._map.latLngToLayerPoint(this._latlng).round();
3145 this._setPos(pos);
3146 }
3147
3148 return this;
3149 },
3150
3151 _initIcon: function () {
3152 var options = this.options,
3153 map = this._map,
3154 animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
3155 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
3156 needOpacityUpdate = false;
3157
3158 if (!this._icon) {
3159 this._icon = options.icon.createIcon();
3160
3161 if (options.title) {
3162 this._icon.title = options.title;
3163 }
3164
3165 this._initInteraction();
3166 needOpacityUpdate = (this.options.opacity < 1);
3167
3168 L.DomUtil.addClass(this._icon, classToAdd);
3169
3170 if (options.riseOnHover) {
3171 L.DomEvent
3172 .on(this._icon, 'mouseover', this._bringToFront, this)
3173 .on(this._icon, 'mouseout', this._resetZIndex, this);
3174 }
3175 }
3176
3177 if (!this._shadow) {
3178 this._shadow = options.icon.createShadow();
3179
3180 if (this._shadow) {
3181 L.DomUtil.addClass(this._shadow, classToAdd);
3182 needOpacityUpdate = (this.options.opacity < 1);
3183 }
3184 }
3185
3186 if (needOpacityUpdate) {
3187 this._updateOpacity();
3188 }
3189
3190 var panes = this._map._panes;
3191
3192 panes.markerPane.appendChild(this._icon);
3193
3194 if (this._shadow) {
3195 panes.shadowPane.appendChild(this._shadow);
3196 }
3197 },
3198
3199 _removeIcon: function () {
3200 var panes = this._map._panes;
3201
3202 if (this.options.riseOnHover) {
3203 L.DomEvent
3204 .off(this._icon, 'mouseover', this._bringToFront)
3205 .off(this._icon, 'mouseout', this._resetZIndex);
3206 }
3207
3208 panes.markerPane.removeChild(this._icon);
3209
3210 if (this._shadow) {
3211 panes.shadowPane.removeChild(this._shadow);
3212 }
3213
3214 this._icon = this._shadow = null;
3215 },
3216
3217 _setPos: function (pos) {
3218 L.DomUtil.setPosition(this._icon, pos);
3219
3220 if (this._shadow) {
3221 L.DomUtil.setPosition(this._shadow, pos);
3222 }
3223
3224 this._zIndex = pos.y + this.options.zIndexOffset;
3225
3226 this._resetZIndex();
3227 },
3228
3229 _updateZIndex: function (offset) {
3230 this._icon.style.zIndex = this._zIndex + offset;
3231 },
3232
3233 _animateZoom: function (opt) {
3234 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3235
3236 this._setPos(pos);
3237 },
3238
3239 _initInteraction: function () {
3240
3241 if (!this.options.clickable) { return; }
3242
3243 // TODO refactor into something shared with Map/Path/etc. to DRY it up
3244
3245 var icon = this._icon,
3246 events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
3247
3248 L.DomUtil.addClass(icon, 'leaflet-clickable');
3249 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
3250
3251 for (var i = 0; i < events.length; i++) {
3252 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
3253 }
3254
3255 if (L.Handler.MarkerDrag) {
3256 this.dragging = new L.Handler.MarkerDrag(this);
3257
3258 if (this.options.draggable) {
3259 this.dragging.enable();
3260 }
3261 }
3262 },
3263
3264 _onMouseClick: function (e) {
3265 var wasDragged = this.dragging && this.dragging.moved();
3266
3267 if (this.hasEventListeners(e.type) || wasDragged) {
3268 L.DomEvent.stopPropagation(e);
3269 }
3270
3271 if (wasDragged) { return; }
3272
3273 if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
3274
3275 this.fire(e.type, {
3276 originalEvent: e
3277 });
3278 },
3279
3280 _fireMouseEvent: function (e) {
3281
3282 this.fire(e.type, {
3283 originalEvent: e
3284 });
3285
3286 // TODO proper custom event propagation
3287 // this line will always be called if marker is in a FeatureGroup
3288 if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
3289 L.DomEvent.preventDefault(e);
3290 }
3291 if (e.type !== 'mousedown') {
3292 L.DomEvent.stopPropagation(e);
3293 }
3294 },
3295
3296 setOpacity: function (opacity) {
3297 this.options.opacity = opacity;
3298 if (this._map) {
3299 this._updateOpacity();
3300 }
3301 },
3302
3303 _updateOpacity: function () {
3304 L.DomUtil.setOpacity(this._icon, this.options.opacity);
3305 if (this._shadow) {
3306 L.DomUtil.setOpacity(this._shadow, this.options.opacity);
3307 }
3308 },
3309
3310 _bringToFront: function () {
3311 this._updateZIndex(this.options.riseOffset);
3312 },
3313
3314 _resetZIndex: function () {
3315 this._updateZIndex(0);
3316 }
3317 });
3318
3319 L.marker = function (latlng, options) {
3320 return new L.Marker(latlng, options);
3321 };
3396 /*
3397 * L.Marker is used to display clickable/draggable icons on the map.
3398 */
3399
3400 L.Marker = L.Class.extend({
3401
3402 includes: L.Mixin.Events,
3403
3404 options: {
3405 icon: new L.Icon.Default(),
3406 title: '',
3407 clickable: true,
3408 draggable: false,
3409 keyboard: true,
3410 zIndexOffset: 0,
3411 opacity: 1,
3412 riseOnHover: false,
3413 riseOffset: 250
3414 },
3415
3416 initialize: function (latlng, options) {
3417 L.setOptions(this, options);
3418 this._latlng = L.latLng(latlng);
3419 },
3420
3421 onAdd: function (map) {
3422 this._map = map;
3423
3424 map.on('viewreset', this.update, this);
3425
3426 this._initIcon();
3427 this.update();
3428
3429 if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
3430 map.on('zoomanim', this._animateZoom, this);
3431 }
3432 },
3433
3434 addTo: function (map) {
3435 map.addLayer(this);
3436 return this;
3437 },
3438
3439 onRemove: function (map) {
3440 if (this.dragging) {
3441 this.dragging.disable();
3442 }
3443
3444 this._removeIcon();
3445 this._removeShadow();
3446
3447 this.fire('remove');
3448
3449 map.off({
3450 'viewreset': this.update,
3451 'zoomanim': this._animateZoom
3452 }, this);
3453
3454 this._map = null;
3455 },
3456
3457 getLatLng: function () {
3458 return this._latlng;
3459 },
3460
3461 setLatLng: function (latlng) {
3462 this._latlng = L.latLng(latlng);
3463
3464 this.update();
3465
3466 return this.fire('move', { latlng: this._latlng });
3467 },
3468
3469 setZIndexOffset: function (offset) {
3470 this.options.zIndexOffset = offset;
3471 this.update();
3472
3473 return this;
3474 },
3475
3476 setIcon: function (icon) {
3477
3478 this.options.icon = icon;
3479
3480 if (this._map) {
3481 this._initIcon();
3482 this.update();
3483 }
3484
3485 return this;
3486 },
3487
3488 update: function () {
3489 if (this._icon) {
3490 var pos = this._map.latLngToLayerPoint(this._latlng).round();
3491 this._setPos(pos);
3492 }
3493
3494 return this;
3495 },
3496
3497 _initIcon: function () {
3498 var options = this.options,
3499 map = this._map,
3500 animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
3501 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
3502
3503 var icon = options.icon.createIcon(this._icon),
3504 addIcon = false;
3505
3506 // if we're not reusing the icon, remove the old one and init new one
3507 if (icon !== this._icon) {
3508 if (this._icon) {
3509 this._removeIcon();
3510 }
3511 addIcon = true;
3512
3513 if (options.title) {
3514 icon.title = options.title;
3515 }
3516 }
3517
3518 L.DomUtil.addClass(icon, classToAdd);
3519
3520 if (options.keyboard) {
3521 icon.tabIndex = '0';
3522 }
3523
3524 this._icon = icon;
3525
3526 this._initInteraction();
3527
3528 if (options.riseOnHover) {
3529 L.DomEvent
3530 .on(icon, 'mouseover', this._bringToFront, this)
3531 .on(icon, 'mouseout', this._resetZIndex, this);
3532 }
3533
3534 var newShadow = options.icon.createShadow(this._shadow),
3535 addShadow = false;
3536
3537 if (newShadow !== this._shadow) {
3538 this._removeShadow();
3539 addShadow = true;
3540
3541 if (newShadow) {
3542 L.DomUtil.addClass(newShadow, classToAdd);
3543 }
3544 }
3545 this._shadow = newShadow;
3546
3547
3548 if (options.opacity < 1) {
3549 this._updateOpacity();
3550 }
3551
3552
3553 var panes = this._map._panes;
3554
3555 if (addIcon) {
3556 panes.markerPane.appendChild(this._icon);
3557 }
3558
3559 if (newShadow && addShadow) {
3560 panes.shadowPane.appendChild(this._shadow);
3561 }
3562 },
3563
3564 _removeIcon: function () {
3565 if (this.options.riseOnHover) {
3566 L.DomEvent
3567 .off(this._icon, 'mouseover', this._bringToFront)
3568 .off(this._icon, 'mouseout', this._resetZIndex);
3569 }
3570
3571 this._map._panes.markerPane.removeChild(this._icon);
3572
3573 this._icon = null;
3574 },
3575
3576 _removeShadow: function () {
3577 if (this._shadow) {
3578 this._map._panes.shadowPane.removeChild(this._shadow);
3579 }
3580 this._shadow = null;
3581 },
3582
3583 _setPos: function (pos) {
3584 L.DomUtil.setPosition(this._icon, pos);
3585
3586 if (this._shadow) {
3587 L.DomUtil.setPosition(this._shadow, pos);
3588 }
3589
3590 this._zIndex = pos.y + this.options.zIndexOffset;
3591
3592 this._resetZIndex();
3593 },
3594
3595 _updateZIndex: function (offset) {
3596 this._icon.style.zIndex = this._zIndex + offset;
3597 },
3598
3599 _animateZoom: function (opt) {
3600 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3601
3602 this._setPos(pos);
3603 },
3604
3605 _initInteraction: function () {
3606
3607 if (!this.options.clickable) { return; }
3608
3609 // TODO refactor into something shared with Map/Path/etc. to DRY it up
3610
3611 var icon = this._icon,
3612 events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
3613
3614 L.DomUtil.addClass(icon, 'leaflet-clickable');
3615 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
3616 L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
3617
3618 for (var i = 0; i < events.length; i++) {
3619 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
3620 }
3621
3622 if (L.Handler.MarkerDrag) {
3623 this.dragging = new L.Handler.MarkerDrag(this);
3624
3625 if (this.options.draggable) {
3626 this.dragging.enable();
3627 }
3628 }
3629 },
3630
3631 _onMouseClick: function (e) {
3632 var wasDragged = this.dragging && this.dragging.moved();
3633
3634 if (this.hasEventListeners(e.type) || wasDragged) {
3635 L.DomEvent.stopPropagation(e);
3636 }
3637
3638 if (wasDragged) { return; }
3639
3640 if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
3641
3642 this.fire(e.type, {
3643 originalEvent: e,
3644 latlng: this._latlng
3645 });
3646 },
3647
3648 _onKeyPress: function (e) {
3649 if (e.keyCode === 13) {
3650 this.fire('click', {
3651 originalEvent: e,
3652 latlng: this._latlng
3653 });
3654 }
3655 },
3656
3657 _fireMouseEvent: function (e) {
3658
3659 this.fire(e.type, {
3660 originalEvent: e,
3661 latlng: this._latlng
3662 });
3663
3664 // TODO proper custom event propagation
3665 // this line will always be called if marker is in a FeatureGroup
3666 if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
3667 L.DomEvent.preventDefault(e);
3668 }
3669 if (e.type !== 'mousedown') {
3670 L.DomEvent.stopPropagation(e);
3671 } else {
3672 L.DomEvent.preventDefault(e);
3673 }
3674 },
3675
3676 setOpacity: function (opacity) {
3677 this.options.opacity = opacity;
3678 if (this._map) {
3679 this._updateOpacity();
3680 }
3681 },
3682
3683 _updateOpacity: function () {
3684 L.DomUtil.setOpacity(this._icon, this.options.opacity);
3685 if (this._shadow) {
3686 L.DomUtil.setOpacity(this._shadow, this.options.opacity);
3687 }
3688 },
3689
3690 _bringToFront: function () {
3691 this._updateZIndex(this.options.riseOffset);
3692 },
3693
3694 _resetZIndex: function () {
3695 this._updateZIndex(0);
3696 }
3697 });
3698
3699 L.marker = function (latlng, options) {
3700 return new L.Marker(latlng, options);
3701 };
33223702
33233703
33243704 /*
33283708
33293709 L.DivIcon = L.Icon.extend({
33303710 options: {
3331 iconSize: new L.Point(12, 12), // also can be set through CSS
3711 iconSize: [12, 12], // also can be set through CSS
33323712 /*
33333713 iconAnchor: (Point)
33343714 popupAnchor: (Point)
33353715 html: (String)
33363716 bgPos: (Point)
33373717 */
3338 className: 'leaflet-div-icon'
3718 className: 'leaflet-div-icon',
3719 html: false
33393720 },
33403721
3341 createIcon: function () {
3342 var div = document.createElement('div'),
3722 createIcon: function (oldIcon) {
3723 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
33433724 options = this.options;
33443725
3345 if (options.html) {
3726 if (options.html !== false) {
33463727 div.innerHTML = options.html;
3728 } else {
3729 div.innerHTML = '';
33473730 }
33483731
33493732 if (options.bgPos) {
33653748 };
33663749
33673750
3751 /*
3752 * L.Popup is used for displaying popups on the map.
3753 */
3754
3755 L.Map.mergeOptions({
3756 closePopupOnClick: true
3757 });
3758
3759 L.Popup = L.Class.extend({
3760 includes: L.Mixin.Events,
3761
3762 options: {
3763 minWidth: 50,
3764 maxWidth: 300,
3765 maxHeight: null,
3766 autoPan: true,
3767 closeButton: true,
3768 offset: [0, 7],
3769 autoPanPadding: [5, 5],
3770 keepInView: false,
3771 className: '',
3772 zoomAnimation: true
3773 },
3774
3775 initialize: function (options, source) {
3776 L.setOptions(this, options);
3777
3778 this._source = source;
3779 this._animated = L.Browser.any3d && this.options.zoomAnimation;
3780 this._isOpen = false;
3781 },
3782
3783 onAdd: function (map) {
3784 this._map = map;
3785
3786 if (!this._container) {
3787 this._initLayout();
3788 }
3789 this._updateContent();
3790
3791 var animFade = map.options.fadeAnimation;
3792
3793 if (animFade) {
3794 L.DomUtil.setOpacity(this._container, 0);
3795 }
3796 map._panes.popupPane.appendChild(this._container);
3797
3798 map.on(this._getEvents(), this);
3799
3800 this._update();
3801
3802 if (animFade) {
3803 L.DomUtil.setOpacity(this._container, 1);
3804 }
3805
3806 this.fire('open');
3807
3808 map.fire('popupopen', {popup: this});
3809
3810 if (this._source) {
3811 this._source.fire('popupopen', {popup: this});
3812 }
3813 },
3814
3815 addTo: function (map) {
3816 map.addLayer(this);
3817 return this;
3818 },
3819
3820 openOn: function (map) {
3821 map.openPopup(this);
3822 return this;
3823 },
3824
3825 onRemove: function (map) {
3826 map._panes.popupPane.removeChild(this._container);
3827
3828 L.Util.falseFn(this._container.offsetWidth); // force reflow
3829
3830 map.off(this._getEvents(), this);
3831
3832 if (map.options.fadeAnimation) {
3833 L.DomUtil.setOpacity(this._container, 0);
3834 }
3835
3836 this._map = null;
3837
3838 this.fire('close');
3839
3840 map.fire('popupclose', {popup: this});
3841
3842 if (this._source) {
3843 this._source.fire('popupclose', {popup: this});
3844 }
3845 },
3846
3847 setLatLng: function (latlng) {
3848 this._latlng = L.latLng(latlng);
3849 this._update();
3850 return this;
3851 },
3852
3853 setContent: function (content) {
3854 this._content = content;
3855 this._update();
3856 return this;
3857 },
3858
3859 _getEvents: function () {
3860 var events = {
3861 viewreset: this._updatePosition
3862 };
3863
3864 if (this._animated) {
3865 events.zoomanim = this._zoomAnimation;
3866 }
3867 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
3868 events.preclick = this._close;
3869 }
3870 if (this.options.keepInView) {
3871 events.moveend = this._adjustPan;
3872 }
3873
3874 return events;
3875 },
3876
3877 _close: function () {
3878 if (this._map) {
3879 this._map.closePopup(this);
3880 }
3881 },
3882
3883 _initLayout: function () {
3884 var prefix = 'leaflet-popup',
3885 containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
3886 (this._animated ? 'animated' : 'hide'),
3887 container = this._container = L.DomUtil.create('div', containerClass),
3888 closeButton;
3889
3890 if (this.options.closeButton) {
3891 closeButton = this._closeButton =
3892 L.DomUtil.create('a', prefix + '-close-button', container);
3893 closeButton.href = '#close';
3894 closeButton.innerHTML = '&#215;';
3895 L.DomEvent.disableClickPropagation(closeButton);
3896
3897 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
3898 }
3899
3900 var wrapper = this._wrapper =
3901 L.DomUtil.create('div', prefix + '-content-wrapper', container);
3902 L.DomEvent.disableClickPropagation(wrapper);
3903
3904 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
3905 L.DomEvent.on(this._contentNode, 'wheel', L.DomEvent.stopPropagation);
3906 L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
3907 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
3908 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
3909 },
3910
3911 _update: function () {
3912 if (!this._map) { return; }
3913
3914 this._container.style.visibility = 'hidden';
3915
3916 this._updateContent();
3917 this._updateLayout();
3918 this._updatePosition();
3919
3920 this._container.style.visibility = '';
3921
3922 this._adjustPan();
3923 },
3924
3925 _updateContent: function () {
3926 if (!this._content) { return; }
3927
3928 if (typeof this._content === 'string') {
3929 this._contentNode.innerHTML = this._content;
3930 } else {
3931 while (this._contentNode.hasChildNodes()) {
3932 this._contentNode.removeChild(this._contentNode.firstChild);
3933 }
3934 this._contentNode.appendChild(this._content);
3935 }
3936 this.fire('contentupdate');
3937 },
3938
3939 _updateLayout: function () {
3940 var container = this._contentNode,
3941 style = container.style;
3942
3943 style.width = '';
3944 style.whiteSpace = 'nowrap';
3945
3946 var width = container.offsetWidth;
3947 width = Math.min(width, this.options.maxWidth);
3948 width = Math.max(width, this.options.minWidth);
3949
3950 style.width = (width + 1) + 'px';
3951 style.whiteSpace = '';
3952
3953 style.height = '';
3954
3955 var height = container.offsetHeight,
3956 maxHeight = this.options.maxHeight,
3957 scrolledClass = 'leaflet-popup-scrolled';
3958
3959 if (maxHeight && height > maxHeight) {
3960 style.height = maxHeight + 'px';
3961 L.DomUtil.addClass(container, scrolledClass);
3962 } else {
3963 L.DomUtil.removeClass(container, scrolledClass);
3964 }
3965
3966 this._containerWidth = this._container.offsetWidth;
3967 },
3968
3969 _updatePosition: function () {
3970 if (!this._map) { return; }
3971
3972 var pos = this._map.latLngToLayerPoint(this._latlng),
3973 animated = this._animated,
3974 offset = L.point(this.options.offset);
3975
3976 if (animated) {
3977 L.DomUtil.setPosition(this._container, pos);
3978 }
3979
3980 this._containerBottom = -offset.y - (animated ? 0 : pos.y);
3981 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
3982
3983 // bottom position the popup in case the height of the popup changes (images loading etc)
3984 this._container.style.bottom = this._containerBottom + 'px';
3985 this._container.style.left = this._containerLeft + 'px';
3986 },
3987
3988 _zoomAnimation: function (opt) {
3989 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3990
3991 L.DomUtil.setPosition(this._container, pos);
3992 },
3993
3994 _adjustPan: function () {
3995 if (!this.options.autoPan) { return; }
3996
3997 var map = this._map,
3998 containerHeight = this._container.offsetHeight,
3999 containerWidth = this._containerWidth,
4000
4001 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
4002
4003 if (this._animated) {
4004 layerPos._add(L.DomUtil.getPosition(this._container));
4005 }
4006
4007 var containerPos = map.layerPointToContainerPoint(layerPos),
4008 padding = L.point(this.options.autoPanPadding),
4009 size = map.getSize(),
4010 dx = 0,
4011 dy = 0;
4012
4013 if (containerPos.x + containerWidth > size.x) { // right
4014 dx = containerPos.x + containerWidth - size.x + padding.x;
4015 }
4016 if (containerPos.x - dx < 0) { // left
4017 dx = containerPos.x - padding.x;
4018 }
4019 if (containerPos.y + containerHeight > size.y) { // bottom
4020 dy = containerPos.y + containerHeight - size.y + padding.y;
4021 }
4022 if (containerPos.y - dy < 0) { // top
4023 dy = containerPos.y - padding.y;
4024 }
4025
4026 if (dx || dy) {
4027 map
4028 .fire('autopanstart')
4029 .panBy([dx, dy]);
4030 }
4031 },
4032
4033 _onCloseButtonClick: function (e) {
4034 this._close();
4035 L.DomEvent.stop(e);
4036 }
4037 });
4038
4039 L.popup = function (options, source) {
4040 return new L.Popup(options, source);
4041 };
4042
4043
4044 L.Map.include({
4045 openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])
4046 this.closePopup();
4047
4048 if (!(popup instanceof L.Popup)) {
4049 var content = popup;
4050
4051 popup = new L.Popup(options)
4052 .setLatLng(latlng)
4053 .setContent(content);
4054 }
4055 popup._isOpen = true;
4056
4057 this._popup = popup;
4058 return this.addLayer(popup);
4059 },
4060
4061 closePopup: function (popup) {
4062 if (!popup || popup === this._popup) {
4063 popup = this._popup;
4064 this._popup = null;
4065 }
4066 if (popup) {
4067 this.removeLayer(popup);
4068 popup._isOpen = false;
4069 }
4070 return this;
4071 }
4072 });
4073
4074
4075 /*
4076 * Popup extension to L.Marker, adding popup-related methods.
4077 */
4078
4079 L.Marker.include({
4080 openPopup: function () {
4081 if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
4082 this._popup.setLatLng(this._latlng);
4083 this._map.openPopup(this._popup);
4084 }
4085
4086 return this;
4087 },
4088
4089 closePopup: function () {
4090 if (this._popup) {
4091 this._popup._close();
4092 }
4093 return this;
4094 },
4095
4096 togglePopup: function () {
4097 if (this._popup) {
4098 if (this._popup._isOpen) {
4099 this.closePopup();
4100 } else {
4101 this.openPopup();
4102 }
4103 }
4104 return this;
4105 },
4106
4107 bindPopup: function (content, options) {
4108 var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
4109
4110 anchor = anchor.add(L.Popup.prototype.options.offset);
4111
4112 if (options && options.offset) {
4113 anchor = anchor.add(options.offset);
4114 }
4115
4116 options = L.extend({offset: anchor}, options);
4117
4118 if (!this._popup) {
4119 this
4120 .on('click', this.togglePopup, this)
4121 .on('remove', this.closePopup, this)
4122 .on('move', this._movePopup, this);
4123 }
4124
4125 if (content instanceof L.Popup) {
4126 L.setOptions(content, options);
4127 this._popup = content;
4128 } else {
4129 this._popup = new L.Popup(options, this)
4130 .setContent(content);
4131 }
4132
4133 return this;
4134 },
4135
4136 setPopupContent: function (content) {
4137 if (this._popup) {
4138 this._popup.setContent(content);
4139 }
4140 return this;
4141 },
4142
4143 unbindPopup: function () {
4144 if (this._popup) {
4145 this._popup = null;
4146 this
4147 .off('click', this.togglePopup)
4148 .off('remove', this.closePopup)
4149 .off('move', this._movePopup);
4150 }
4151 return this;
4152 },
4153
4154 _movePopup: function (e) {
4155 this._popup.setLatLng(e.latlng);
4156 }
4157 });
4158
4159
4160 /*
4161 * L.LayerGroup is a class to combine several layers into one so that
4162 * you can manipulate the group (e.g. add/remove it) as one layer.
4163 */
4164
4165 L.LayerGroup = L.Class.extend({
4166 initialize: function (layers) {
4167 this._layers = {};
4168
4169 var i, len;
4170
4171 if (layers) {
4172 for (i = 0, len = layers.length; i < len; i++) {
4173 this.addLayer(layers[i]);
4174 }
4175 }
4176 },
4177
4178 addLayer: function (layer) {
4179 var id = this.getLayerId(layer);
4180
4181 this._layers[id] = layer;
4182
4183 if (this._map) {
4184 this._map.addLayer(layer);
4185 }
4186
4187 return this;
4188 },
4189
4190 removeLayer: function (layer) {
4191 var id = layer in this._layers ? layer : this.getLayerId(layer);
4192
4193 if (this._map && this._layers[id]) {
4194 this._map.removeLayer(this._layers[id]);
4195 }
4196
4197 delete this._layers[id];
4198
4199 return this;
4200 },
4201
4202 hasLayer: function (layer) {
4203 if (!layer) { return false; }
4204
4205 return (layer in this._layers || this.getLayerId(layer) in this._layers);
4206 },
4207
4208 clearLayers: function () {
4209 this.eachLayer(this.removeLayer, this);
4210 return this;
4211 },
4212
4213 invoke: function (methodName) {
4214 var args = Array.prototype.slice.call(arguments, 1),
4215 i, layer;
4216
4217 for (i in this._layers) {
4218 layer = this._layers[i];
4219
4220 if (layer[methodName]) {
4221 layer[methodName].apply(layer, args);
4222 }
4223 }
4224
4225 return this;
4226 },
4227
4228 onAdd: function (map) {
4229 this._map = map;
4230 this.eachLayer(map.addLayer, map);
4231 },
4232
4233 onRemove: function (map) {
4234 this.eachLayer(map.removeLayer, map);
4235 this._map = null;
4236 },
4237
4238 addTo: function (map) {
4239 map.addLayer(this);
4240 return this;
4241 },
4242
4243 eachLayer: function (method, context) {
4244 for (var i in this._layers) {
4245 method.call(context, this._layers[i]);
4246 }
4247 return this;
4248 },
4249
4250 getLayer: function (id) {
4251 return this._layers[id];
4252 },
4253
4254 getLayers: function () {
4255 var layers = [];
4256
4257 for (var i in this._layers) {
4258 layers.push(this._layers[i]);
4259 }
4260 return layers;
4261 },
4262
4263 setZIndex: function (zIndex) {
4264 return this.invoke('setZIndex', zIndex);
4265 },
4266
4267 getLayerId: function (layer) {
4268 return L.stamp(layer);
4269 }
4270 });
4271
4272 L.layerGroup = function (layers) {
4273 return new L.LayerGroup(layers);
4274 };
4275
4276
4277 /*
4278 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
4279 * shared between a group of interactive layers (like vectors or markers).
4280 */
4281
4282 L.FeatureGroup = L.LayerGroup.extend({
4283 includes: L.Mixin.Events,
4284
4285 statics: {
4286 EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
4287 },
4288
4289 addLayer: function (layer) {
4290 if (this.hasLayer(layer)) {
4291 return this;
4292 }
4293
4294 layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
4295
4296 L.LayerGroup.prototype.addLayer.call(this, layer);
4297
4298 if (this._popupContent && layer.bindPopup) {
4299 layer.bindPopup(this._popupContent, this._popupOptions);
4300 }
4301
4302 return this.fire('layeradd', {layer: layer});
4303 },
4304
4305 removeLayer: function (layer) {
4306 if (layer in this._layers) {
4307 layer = this._layers[layer];
4308 }
4309
4310 layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
4311
4312 L.LayerGroup.prototype.removeLayer.call(this, layer);
4313
4314 if (this._popupContent) {
4315 this.invoke('unbindPopup');
4316 }
4317
4318 return this.fire('layerremove', {layer: layer});
4319 },
4320
4321 bindPopup: function (content, options) {
4322 this._popupContent = content;
4323 this._popupOptions = options;
4324 return this.invoke('bindPopup', content, options);
4325 },
4326
4327 setStyle: function (style) {
4328 return this.invoke('setStyle', style);
4329 },
4330
4331 bringToFront: function () {
4332 return this.invoke('bringToFront');
4333 },
4334
4335 bringToBack: function () {
4336 return this.invoke('bringToBack');
4337 },
4338
4339 getBounds: function () {
4340 var bounds = new L.LatLngBounds();
4341
4342 this.eachLayer(function (layer) {
4343 bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
4344 });
4345
4346 return bounds;
4347 },
4348
4349 _propagateEvent: function (e) {
4350 if (!e.layer) {
4351 e.layer = e.target;
4352 }
4353 e.target = this;
4354
4355 this.fire(e.type, e);
4356 }
4357 });
4358
4359 L.featureGroup = function (layers) {
4360 return new L.FeatureGroup(layers);
4361 };
4362
4363
4364 /*
4365 * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
4366 */
4367
4368 L.Path = L.Class.extend({
4369 includes: [L.Mixin.Events],
4370
4371 statics: {
4372 // how much to extend the clip area around the map view
4373 // (relative to its size, e.g. 0.5 is half the screen in each direction)
4374 // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
4375 CLIP_PADDING: L.Browser.mobile ?
4376 Math.max(0, Math.min(0.5,
4377 (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
4378 },
4379
4380 options: {
4381 stroke: true,
4382 color: '#0033ff',
4383 dashArray: null,
4384 weight: 5,
4385 opacity: 0.5,
4386
4387 fill: false,
4388 fillColor: null, //same as color by default
4389 fillOpacity: 0.2,
4390
4391 clickable: true
4392 },
4393
4394 initialize: function (options) {
4395 L.setOptions(this, options);
4396 },
4397
4398 onAdd: function (map) {
4399 this._map = map;
4400
4401 if (!this._container) {
4402 this._initElements();
4403 this._initEvents();
4404 }
4405
4406 this.projectLatlngs();
4407 this._updatePath();
4408
4409 if (this._container) {
4410 this._map._pathRoot.appendChild(this._container);
4411 }
4412
4413 this.fire('add');
4414
4415 map.on({
4416 'viewreset': this.projectLatlngs,
4417 'moveend': this._updatePath
4418 }, this);
4419 },
4420
4421 addTo: function (map) {
4422 map.addLayer(this);
4423 return this;
4424 },
4425
4426 onRemove: function (map) {
4427 map._pathRoot.removeChild(this._container);
4428
4429 // Need to fire remove event before we set _map to null as the event hooks might need the object
4430 this.fire('remove');
4431 this._map = null;
4432
4433 if (L.Browser.vml) {
4434 this._container = null;
4435 this._stroke = null;
4436 this._fill = null;
4437 }
4438
4439 map.off({
4440 'viewreset': this.projectLatlngs,
4441 'moveend': this._updatePath
4442 }, this);
4443 },
4444
4445 projectLatlngs: function () {
4446 // do all projection stuff here
4447 },
4448
4449 setStyle: function (style) {
4450 L.setOptions(this, style);
4451
4452 if (this._container) {
4453 this._updateStyle();
4454 }
4455
4456 return this;
4457 },
4458
4459 redraw: function () {
4460 if (this._map) {
4461 this.projectLatlngs();
4462 this._updatePath();
4463 }
4464 return this;
4465 }
4466 });
4467
4468 L.Map.include({
4469 _updatePathViewport: function () {
4470 var p = L.Path.CLIP_PADDING,
4471 size = this.getSize(),
4472 panePos = L.DomUtil.getPosition(this._mapPane),
4473 min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
4474 max = min.add(size.multiplyBy(1 + p * 2)._round());
4475
4476 this._pathViewport = new L.Bounds(min, max);
4477 }
4478 });
4479
4480
4481 /*
4482 * Extends L.Path with SVG-specific rendering code.
4483 */
4484
4485 L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
4486
4487 L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
4488
4489 L.Path = L.Path.extend({
4490 statics: {
4491 SVG: L.Browser.svg
4492 },
4493
4494 bringToFront: function () {
4495 var root = this._map._pathRoot,
4496 path = this._container;
4497
4498 if (path && root.lastChild !== path) {
4499 root.appendChild(path);
4500 }
4501 return this;
4502 },
4503
4504 bringToBack: function () {
4505 var root = this._map._pathRoot,
4506 path = this._container,
4507 first = root.firstChild;
4508
4509 if (path && first !== path) {
4510 root.insertBefore(path, first);
4511 }
4512 return this;
4513 },
4514
4515 getPathString: function () {
4516 // form path string here
4517 },
4518
4519 _createElement: function (name) {
4520 return document.createElementNS(L.Path.SVG_NS, name);
4521 },
4522
4523 _initElements: function () {
4524 this._map._initPathRoot();
4525 this._initPath();
4526 this._initStyle();
4527 },
4528
4529 _initPath: function () {
4530 this._container = this._createElement('g');
4531
4532 this._path = this._createElement('path');
4533 this._container.appendChild(this._path);
4534 },
4535
4536 _initStyle: function () {
4537 if (this.options.stroke) {
4538 this._path.setAttribute('stroke-linejoin', 'round');
4539 this._path.setAttribute('stroke-linecap', 'round');
4540 }
4541 if (this.options.fill) {
4542 this._path.setAttribute('fill-rule', 'evenodd');
4543 }
4544 if (this.options.pointerEvents) {
4545 this._path.setAttribute('pointer-events', this.options.pointerEvents);
4546 }
4547 if (!this.options.clickable && !this.options.pointerEvents) {
4548 this._path.setAttribute('pointer-events', 'none');
4549 }
4550 this._updateStyle();
4551 },
4552
4553 _updateStyle: function () {
4554 if (this.options.stroke) {
4555 this._path.setAttribute('stroke', this.options.color);
4556 this._path.setAttribute('stroke-opacity', this.options.opacity);
4557 this._path.setAttribute('stroke-width', this.options.weight);
4558 if (this.options.dashArray) {
4559 this._path.setAttribute('stroke-dasharray', this.options.dashArray);
4560 } else {
4561 this._path.removeAttribute('stroke-dasharray');
4562 }
4563 } else {
4564 this._path.setAttribute('stroke', 'none');
4565 }
4566 if (this.options.fill) {
4567 this._path.setAttribute('fill', this.options.fillColor || this.options.color);
4568 this._path.setAttribute('fill-opacity', this.options.fillOpacity);
4569 } else {
4570 this._path.setAttribute('fill', 'none');
4571 }
4572 },
4573
4574 _updatePath: function () {
4575 var str = this.getPathString();
4576 if (!str) {
4577 // fix webkit empty string parsing bug
4578 str = 'M0 0';
4579 }
4580 this._path.setAttribute('d', str);
4581 },
4582
4583 // TODO remove duplication with L.Map
4584 _initEvents: function () {
4585 if (this.options.clickable) {
4586 if (L.Browser.svg || !L.Browser.vml) {
4587 this._path.setAttribute('class', 'leaflet-clickable');
4588 }
4589
4590 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
4591
4592 var events = ['dblclick', 'mousedown', 'mouseover',
4593 'mouseout', 'mousemove', 'contextmenu'];
4594 for (var i = 0; i < events.length; i++) {
4595 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
4596 }
4597 }
4598 },
4599
4600 _onMouseClick: function (e) {
4601 if (this._map.dragging && this._map.dragging.moved()) { return; }
4602
4603 this._fireMouseEvent(e);
4604 },
4605
4606 _fireMouseEvent: function (e) {
4607 if (!this.hasEventListeners(e.type)) { return; }
4608
4609 var map = this._map,
4610 containerPoint = map.mouseEventToContainerPoint(e),
4611 layerPoint = map.containerPointToLayerPoint(containerPoint),
4612 latlng = map.layerPointToLatLng(layerPoint);
4613
4614 this.fire(e.type, {
4615 latlng: latlng,
4616 layerPoint: layerPoint,
4617 containerPoint: containerPoint,
4618 originalEvent: e
4619 });
4620
4621 if (e.type === 'contextmenu') {
4622 L.DomEvent.preventDefault(e);
4623 }
4624 if (e.type !== 'mousemove') {
4625 L.DomEvent.stopPropagation(e);
4626 }
4627 }
4628 });
4629
4630 L.Map.include({
4631 _initPathRoot: function () {
4632 if (!this._pathRoot) {
4633 this._pathRoot = L.Path.prototype._createElement('svg');
4634 this._panes.overlayPane.appendChild(this._pathRoot);
4635
4636 if (this.options.zoomAnimation && L.Browser.any3d) {
4637 this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
4638
4639 this.on({
4640 'zoomanim': this._animatePathZoom,
4641 'zoomend': this._endPathZoom
4642 });
4643 } else {
4644 this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
4645 }
4646
4647 this.on('moveend', this._updateSvgViewport);
4648 this._updateSvgViewport();
4649 }
4650 },
4651
4652 _animatePathZoom: function (e) {
4653 var scale = this.getZoomScale(e.zoom),
4654 offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
4655
4656 this._pathRoot.style[L.DomUtil.TRANSFORM] =
4657 L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
4658
4659 this._pathZooming = true;
4660 },
4661
4662 _endPathZoom: function () {
4663 this._pathZooming = false;
4664 },
4665
4666 _updateSvgViewport: function () {
4667
4668 if (this._pathZooming) {
4669 // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
4670 // When the zoom animation ends we will be updated again anyway
4671 // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
4672 return;
4673 }
4674
4675 this._updatePathViewport();
4676
4677 var vp = this._pathViewport,
4678 min = vp.min,
4679 max = vp.max,
4680 width = max.x - min.x,
4681 height = max.y - min.y,
4682 root = this._pathRoot,
4683 pane = this._panes.overlayPane;
4684
4685 // Hack to make flicker on drag end on mobile webkit less irritating
4686 if (L.Browser.mobileWebkit) {
4687 pane.removeChild(root);
4688 }
4689
4690 L.DomUtil.setPosition(root, min);
4691 root.setAttribute('width', width);
4692 root.setAttribute('height', height);
4693 root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
4694
4695 if (L.Browser.mobileWebkit) {
4696 pane.appendChild(root);
4697 }
4698 }
4699 });
4700
4701
4702 /*
4703 * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
4704 */
4705
4706 L.Path.include({
4707
4708 bindPopup: function (content, options) {
4709
4710 if (content instanceof L.Popup) {
4711 this._popup = content;
4712 } else {
4713 if (!this._popup || options) {
4714 this._popup = new L.Popup(options, this);
4715 }
4716 this._popup.setContent(content);
4717 }
4718
4719 if (!this._popupHandlersAdded) {
4720 this
4721 .on('click', this._openPopup, this)
4722 .on('remove', this.closePopup, this);
4723
4724 this._popupHandlersAdded = true;
4725 }
4726
4727 return this;
4728 },
4729
4730 unbindPopup: function () {
4731 if (this._popup) {
4732 this._popup = null;
4733 this
4734 .off('click', this._openPopup)
4735 .off('remove', this.closePopup);
4736
4737 this._popupHandlersAdded = false;
4738 }
4739 return this;
4740 },
4741
4742 openPopup: function (latlng) {
4743
4744 if (this._popup) {
4745 // open the popup from one of the path's points if not specified
4746 latlng = latlng || this._latlng ||
4747 this._latlngs[Math.floor(this._latlngs.length / 2)];
4748
4749 this._openPopup({latlng: latlng});
4750 }
4751
4752 return this;
4753 },
4754
4755 closePopup: function () {
4756 if (this._popup) {
4757 this._popup._close();
4758 }
4759 return this;
4760 },
4761
4762 _openPopup: function (e) {
4763 this._popup.setLatLng(e.latlng);
4764 this._map.openPopup(this._popup);
4765 }
4766 });
4767
4768
4769 /*
4770 * Vector rendering for IE6-8 through VML.
4771 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
4772 */
4773
4774 L.Browser.vml = !L.Browser.svg && (function () {
4775 try {
4776 var div = document.createElement('div');
4777 div.innerHTML = '<v:shape adj="1"/>';
4778
4779 var shape = div.firstChild;
4780 shape.style.behavior = 'url(#default#VML)';
4781
4782 return shape && (typeof shape.adj === 'object');
4783
4784 } catch (e) {
4785 return false;
4786 }
4787 }());
4788
4789 L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
4790 statics: {
4791 VML: true,
4792 CLIP_PADDING: 0.02
4793 },
4794
4795 _createElement: (function () {
4796 try {
4797 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
4798 return function (name) {
4799 return document.createElement('<lvml:' + name + ' class="lvml">');
4800 };
4801 } catch (e) {
4802 return function (name) {
4803 return document.createElement(
4804 '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
4805 };
4806 }
4807 }()),
4808
4809 _initPath: function () {
4810 var container = this._container = this._createElement('shape');
4811 L.DomUtil.addClass(container, 'leaflet-vml-shape');
4812 if (this.options.clickable) {
4813 L.DomUtil.addClass(container, 'leaflet-clickable');
4814 }
4815 container.coordsize = '1 1';
4816
4817 this._path = this._createElement('path');
4818 container.appendChild(this._path);
4819
4820 this._map._pathRoot.appendChild(container);
4821 },
4822
4823 _initStyle: function () {
4824 this._updateStyle();
4825 },
4826
4827 _updateStyle: function () {
4828 var stroke = this._stroke,
4829 fill = this._fill,
4830 options = this.options,
4831 container = this._container;
4832
4833 container.stroked = options.stroke;
4834 container.filled = options.fill;
4835
4836 if (options.stroke) {
4837 if (!stroke) {
4838 stroke = this._stroke = this._createElement('stroke');
4839 stroke.endcap = 'round';
4840 container.appendChild(stroke);
4841 }
4842 stroke.weight = options.weight + 'px';
4843 stroke.color = options.color;
4844 stroke.opacity = options.opacity;
4845
4846 if (options.dashArray) {
4847 stroke.dashStyle = options.dashArray instanceof Array ?
4848 options.dashArray.join(' ') :
4849 options.dashArray.replace(/( *, *)/g, ' ');
4850 } else {
4851 stroke.dashStyle = '';
4852 }
4853
4854 } else if (stroke) {
4855 container.removeChild(stroke);
4856 this._stroke = null;
4857 }
4858
4859 if (options.fill) {
4860 if (!fill) {
4861 fill = this._fill = this._createElement('fill');
4862 container.appendChild(fill);
4863 }
4864 fill.color = options.fillColor || options.color;
4865 fill.opacity = options.fillOpacity;
4866
4867 } else if (fill) {
4868 container.removeChild(fill);
4869 this._fill = null;
4870 }
4871 },
4872
4873 _updatePath: function () {
4874 var style = this._container.style;
4875
4876 style.display = 'none';
4877 this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
4878 style.display = '';
4879 }
4880 });
4881
4882 L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
4883 _initPathRoot: function () {
4884 if (this._pathRoot) { return; }
4885
4886 var root = this._pathRoot = document.createElement('div');
4887 root.className = 'leaflet-vml-container';
4888 this._panes.overlayPane.appendChild(root);
4889
4890 this.on('moveend', this._updatePathViewport);
4891 this._updatePathViewport();
4892 }
4893 });
4894
4895
4896 /*
4897 * Vector rendering for all browsers that support canvas.
4898 */
4899
4900 L.Browser.canvas = (function () {
4901 return !!document.createElement('canvas').getContext;
4902 }());
4903
4904 L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
4905 statics: {
4906 //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
4907 CANVAS: true,
4908 SVG: false
4909 },
4910
4911 redraw: function () {
4912 if (this._map) {
4913 this.projectLatlngs();
4914 this._requestUpdate();
4915 }
4916 return this;
4917 },
4918
4919 setStyle: function (style) {
4920 L.setOptions(this, style);
4921
4922 if (this._map) {
4923 this._updateStyle();
4924 this._requestUpdate();
4925 }
4926 return this;
4927 },
4928
4929 onRemove: function (map) {
4930 map
4931 .off('viewreset', this.projectLatlngs, this)
4932 .off('moveend', this._updatePath, this);
4933
4934 if (this.options.clickable) {
4935 this._map.off('click', this._onClick, this);
4936 this._map.off('mousemove', this._onMouseMove, this);
4937 }
4938
4939 this._requestUpdate();
4940
4941 this._map = null;
4942 },
4943
4944 _requestUpdate: function () {
4945 if (this._map && !L.Path._updateRequest) {
4946 L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
4947 }
4948 },
4949
4950 _fireMapMoveEnd: function () {
4951 L.Path._updateRequest = null;
4952 this.fire('moveend');
4953 },
4954
4955 _initElements: function () {
4956 this._map._initPathRoot();
4957 this._ctx = this._map._canvasCtx;
4958 },
4959
4960 _updateStyle: function () {
4961 var options = this.options;
4962
4963 if (options.stroke) {
4964 this._ctx.lineWidth = options.weight;
4965 this._ctx.strokeStyle = options.color;
4966 }
4967 if (options.fill) {
4968 this._ctx.fillStyle = options.fillColor || options.color;
4969 }
4970 },
4971
4972 _drawPath: function () {
4973 var i, j, len, len2, point, drawMethod;
4974
4975 this._ctx.beginPath();
4976
4977 for (i = 0, len = this._parts.length; i < len; i++) {
4978 for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
4979 point = this._parts[i][j];
4980 drawMethod = (j === 0 ? 'move' : 'line') + 'To';
4981
4982 this._ctx[drawMethod](point.x, point.y);
4983 }
4984 // TODO refactor ugly hack
4985 if (this instanceof L.Polygon) {
4986 this._ctx.closePath();
4987 }
4988 }
4989 },
4990
4991 _checkIfEmpty: function () {
4992 return !this._parts.length;
4993 },
4994
4995 _updatePath: function () {
4996 if (this._checkIfEmpty()) { return; }
4997
4998 var ctx = this._ctx,
4999 options = this.options;
5000
5001 this._drawPath();
5002 ctx.save();
5003 this._updateStyle();
5004
5005 if (options.fill) {
5006 ctx.globalAlpha = options.fillOpacity;
5007 ctx.fill();
5008 }
5009
5010 if (options.stroke) {
5011 ctx.globalAlpha = options.opacity;
5012 ctx.stroke();
5013 }
5014
5015 ctx.restore();
5016
5017 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
5018 },
5019
5020 _initEvents: function () {
5021 if (this.options.clickable) {
5022 // TODO dblclick
5023 this._map.on('mousemove', this._onMouseMove, this);
5024 this._map.on('click', this._onClick, this);
5025 }
5026 },
5027
5028 _onClick: function (e) {
5029 if (this._containsPoint(e.layerPoint)) {
5030 this.fire('click', e);
5031 }
5032 },
5033
5034 _onMouseMove: function (e) {
5035 if (!this._map || this._map._animatingZoom) { return; }
5036
5037 // TODO don't do on each move
5038 if (this._containsPoint(e.layerPoint)) {
5039 this._ctx.canvas.style.cursor = 'pointer';
5040 this._mouseInside = true;
5041 this.fire('mouseover', e);
5042
5043 } else if (this._mouseInside) {
5044 this._ctx.canvas.style.cursor = '';
5045 this._mouseInside = false;
5046 this.fire('mouseout', e);
5047 }
5048 }
5049 });
5050
5051 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
5052 _initPathRoot: function () {
5053 var root = this._pathRoot,
5054 ctx;
5055
5056 if (!root) {
5057 root = this._pathRoot = document.createElement('canvas');
5058 root.style.position = 'absolute';
5059 ctx = this._canvasCtx = root.getContext('2d');
5060
5061 ctx.lineCap = 'round';
5062 ctx.lineJoin = 'round';
5063
5064 this._panes.overlayPane.appendChild(root);
5065
5066 if (this.options.zoomAnimation) {
5067 this._pathRoot.className = 'leaflet-zoom-animated';
5068 this.on('zoomanim', this._animatePathZoom);
5069 this.on('zoomend', this._endPathZoom);
5070 }
5071 this.on('moveend', this._updateCanvasViewport);
5072 this._updateCanvasViewport();
5073 }
5074 },
5075
5076 _updateCanvasViewport: function () {
5077 // don't redraw while zooming. See _updateSvgViewport for more details
5078 if (this._pathZooming) { return; }
5079 this._updatePathViewport();
5080
5081 var vp = this._pathViewport,
5082 min = vp.min,
5083 size = vp.max.subtract(min),
5084 root = this._pathRoot;
5085
5086 //TODO check if this works properly on mobile webkit
5087 L.DomUtil.setPosition(root, min);
5088 root.width = size.x;
5089 root.height = size.y;
5090 root.getContext('2d').translate(-min.x, -min.y);
5091 }
5092 });
5093
5094
5095 /*
5096 * L.LineUtil contains different utility functions for line segments
5097 * and polylines (clipping, simplification, distances, etc.)
5098 */
5099
5100 /*jshint bitwise:false */ // allow bitwise oprations for this file
5101
5102 L.LineUtil = {
5103
5104 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5105 // Improves rendering performance dramatically by lessening the number of points to draw.
5106
5107 simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
5108 if (!tolerance || !points.length) {
5109 return points.slice();
5110 }
5111
5112 var sqTolerance = tolerance * tolerance;
5113
5114 // stage 1: vertex reduction
5115 points = this._reducePoints(points, sqTolerance);
5116
5117 // stage 2: Douglas-Peucker simplification
5118 points = this._simplifyDP(points, sqTolerance);
5119
5120 return points;
5121 },
5122
5123 // distance from a point to a segment between two points
5124 pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
5125 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
5126 },
5127
5128 closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
5129 return this._sqClosestPointOnSegment(p, p1, p2);
5130 },
5131
5132 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5133 _simplifyDP: function (points, sqTolerance) {
5134
5135 var len = points.length,
5136 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5137 markers = new ArrayConstructor(len);
5138
5139 markers[0] = markers[len - 1] = 1;
5140
5141 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5142
5143 var i,
5144 newPoints = [];
5145
5146 for (i = 0; i < len; i++) {
5147 if (markers[i]) {
5148 newPoints.push(points[i]);
5149 }
5150 }
5151
5152 return newPoints;
5153 },
5154
5155 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
5156
5157 var maxSqDist = 0,
5158 index, i, sqDist;
5159
5160 for (i = first + 1; i <= last - 1; i++) {
5161 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
5162
5163 if (sqDist > maxSqDist) {
5164 index = i;
5165 maxSqDist = sqDist;
5166 }
5167 }
5168
5169 if (maxSqDist > sqTolerance) {
5170 markers[index] = 1;
5171
5172 this._simplifyDPStep(points, markers, sqTolerance, first, index);
5173 this._simplifyDPStep(points, markers, sqTolerance, index, last);
5174 }
5175 },
5176
5177 // reduce points that are too close to each other to a single point
5178 _reducePoints: function (points, sqTolerance) {
5179 var reducedPoints = [points[0]];
5180
5181 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5182 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
5183 reducedPoints.push(points[i]);
5184 prev = i;
5185 }
5186 }
5187 if (prev < len - 1) {
5188 reducedPoints.push(points[len - 1]);
5189 }
5190 return reducedPoints;
5191 },
5192
5193 // Cohen-Sutherland line clipping algorithm.
5194 // Used to avoid rendering parts of a polyline that are not currently visible.
5195
5196 clipSegment: function (a, b, bounds, useLastCode) {
5197 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
5198 codeB = this._getBitCode(b, bounds),
5199
5200 codeOut, p, newCode;
5201
5202 // save 2nd code to avoid calculating it on the next segment
5203 this._lastCode = codeB;
5204
5205 while (true) {
5206 // if a,b is inside the clip window (trivial accept)
5207 if (!(codeA | codeB)) {
5208 return [a, b];
5209 // if a,b is outside the clip window (trivial reject)
5210 } else if (codeA & codeB) {
5211 return false;
5212 // other cases
5213 } else {
5214 codeOut = codeA || codeB;
5215 p = this._getEdgeIntersection(a, b, codeOut, bounds);
5216 newCode = this._getBitCode(p, bounds);
5217
5218 if (codeOut === codeA) {
5219 a = p;
5220 codeA = newCode;
5221 } else {
5222 b = p;
5223 codeB = newCode;
5224 }
5225 }
5226 }
5227 },
5228
5229 _getEdgeIntersection: function (a, b, code, bounds) {
5230 var dx = b.x - a.x,
5231 dy = b.y - a.y,
5232 min = bounds.min,
5233 max = bounds.max;
5234
5235 if (code & 8) { // top
5236 return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
5237 } else if (code & 4) { // bottom
5238 return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
5239 } else if (code & 2) { // right
5240 return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
5241 } else if (code & 1) { // left
5242 return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
5243 }
5244 },
5245
5246 _getBitCode: function (/*Point*/ p, bounds) {
5247 var code = 0;
5248
5249 if (p.x < bounds.min.x) { // left
5250 code |= 1;
5251 } else if (p.x > bounds.max.x) { // right
5252 code |= 2;
5253 }
5254 if (p.y < bounds.min.y) { // bottom
5255 code |= 4;
5256 } else if (p.y > bounds.max.y) { // top
5257 code |= 8;
5258 }
5259
5260 return code;
5261 },
5262
5263 // square distance (to avoid unnecessary Math.sqrt calls)
5264 _sqDist: function (p1, p2) {
5265 var dx = p2.x - p1.x,
5266 dy = p2.y - p1.y;
5267 return dx * dx + dy * dy;
5268 },
5269
5270 // return closest point on segment or distance to that point
5271 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
5272 var x = p1.x,
5273 y = p1.y,
5274 dx = p2.x - x,
5275 dy = p2.y - y,
5276 dot = dx * dx + dy * dy,
5277 t;
5278
5279 if (dot > 0) {
5280 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
5281
5282 if (t > 1) {
5283 x = p2.x;
5284 y = p2.y;
5285 } else if (t > 0) {
5286 x += dx * t;
5287 y += dy * t;
5288 }
5289 }
5290
5291 dx = p.x - x;
5292 dy = p.y - y;
5293
5294 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
5295 }
5296 };
5297
5298
5299 /*
5300 * L.Polyline is used to display polylines on a map.
5301 */
5302
5303 L.Polyline = L.Path.extend({
5304 initialize: function (latlngs, options) {
5305 L.Path.prototype.initialize.call(this, options);
5306
5307 this._latlngs = this._convertLatLngs(latlngs);
5308 },
5309
5310 options: {
5311 // how much to simplify the polyline on each zoom level
5312 // more = better performance and smoother look, less = more accurate
5313 smoothFactor: 1.0,
5314 noClip: false
5315 },
5316
5317 projectLatlngs: function () {
5318 this._originalPoints = [];
5319
5320 for (var i = 0, len = this._latlngs.length; i < len; i++) {
5321 this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
5322 }
5323 },
5324
5325 getPathString: function () {
5326 for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
5327 str += this._getPathPartStr(this._parts[i]);
5328 }
5329 return str;
5330 },
5331
5332 getLatLngs: function () {
5333 return this._latlngs;
5334 },
5335
5336 setLatLngs: function (latlngs) {
5337 this._latlngs = this._convertLatLngs(latlngs);
5338 return this.redraw();
5339 },
5340
5341 addLatLng: function (latlng) {
5342 this._latlngs.push(L.latLng(latlng));
5343 return this.redraw();
5344 },
5345
5346 spliceLatLngs: function () { // (Number index, Number howMany)
5347 var removed = [].splice.apply(this._latlngs, arguments);
5348 this._convertLatLngs(this._latlngs, true);
5349 this.redraw();
5350 return removed;
5351 },
5352
5353 closestLayerPoint: function (p) {
5354 var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
5355
5356 for (var j = 0, jLen = parts.length; j < jLen; j++) {
5357 var points = parts[j];
5358 for (var i = 1, len = points.length; i < len; i++) {
5359 p1 = points[i - 1];
5360 p2 = points[i];
5361 var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
5362 if (sqDist < minDistance) {
5363 minDistance = sqDist;
5364 minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
5365 }
5366 }
5367 }
5368 if (minPoint) {
5369 minPoint.distance = Math.sqrt(minDistance);
5370 }
5371 return minPoint;
5372 },
5373
5374 getBounds: function () {
5375 return new L.LatLngBounds(this.getLatLngs());
5376 },
5377
5378 _convertLatLngs: function (latlngs, overwrite) {
5379 var i, len, target = overwrite ? latlngs : [];
5380
5381 for (i = 0, len = latlngs.length; i < len; i++) {
5382 if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
5383 return;
5384 }
5385 target[i] = L.latLng(latlngs[i]);
5386 }
5387 return target;
5388 },
5389
5390 _initEvents: function () {
5391 L.Path.prototype._initEvents.call(this);
5392 },
5393
5394 _getPathPartStr: function (points) {
5395 var round = L.Path.VML;
5396
5397 for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
5398 p = points[j];
5399 if (round) {
5400 p._round();
5401 }
5402 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
5403 }
5404 return str;
5405 },
5406
5407 _clipPoints: function () {
5408 var points = this._originalPoints,
5409 len = points.length,
5410 i, k, segment;
5411
5412 if (this.options.noClip) {
5413 this._parts = [points];
5414 return;
5415 }
5416
5417 this._parts = [];
5418
5419 var parts = this._parts,
5420 vp = this._map._pathViewport,
5421 lu = L.LineUtil;
5422
5423 for (i = 0, k = 0; i < len - 1; i++) {
5424 segment = lu.clipSegment(points[i], points[i + 1], vp, i);
5425 if (!segment) {
5426 continue;
5427 }
5428
5429 parts[k] = parts[k] || [];
5430 parts[k].push(segment[0]);
5431
5432 // if segment goes out of screen, or it's the last one, it's the end of the line part
5433 if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
5434 parts[k].push(segment[1]);
5435 k++;
5436 }
5437 }
5438 },
5439
5440 // simplify each clipped part of the polyline
5441 _simplifyPoints: function () {
5442 var parts = this._parts,
5443 lu = L.LineUtil;
5444
5445 for (var i = 0, len = parts.length; i < len; i++) {
5446 parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
5447 }
5448 },
5449
5450 _updatePath: function () {
5451 if (!this._map) { return; }
5452
5453 this._clipPoints();
5454 this._simplifyPoints();
5455
5456 L.Path.prototype._updatePath.call(this);
5457 }
5458 });
5459
5460 L.polyline = function (latlngs, options) {
5461 return new L.Polyline(latlngs, options);
5462 };
5463
5464
5465 /*
5466 * L.PolyUtil contains utility functions for polygons (clipping, etc.).
5467 */
5468
5469 /*jshint bitwise:false */ // allow bitwise operations here
5470
5471 L.PolyUtil = {};
5472
5473 /*
5474 * Sutherland-Hodgeman polygon clipping algorithm.
5475 * Used to avoid rendering parts of a polygon that are not currently visible.
5476 */
5477 L.PolyUtil.clipPolygon = function (points, bounds) {
5478 var clippedPoints,
5479 edges = [1, 4, 2, 8],
5480 i, j, k,
5481 a, b,
5482 len, edge, p,
5483 lu = L.LineUtil;
5484
5485 for (i = 0, len = points.length; i < len; i++) {
5486 points[i]._code = lu._getBitCode(points[i], bounds);
5487 }
5488
5489 // for each edge (left, bottom, right, top)
5490 for (k = 0; k < 4; k++) {
5491 edge = edges[k];
5492 clippedPoints = [];
5493
5494 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
5495 a = points[i];
5496 b = points[j];
5497
5498 // if a is inside the clip window
5499 if (!(a._code & edge)) {
5500 // if b is outside the clip window (a->b goes out of screen)
5501 if (b._code & edge) {
5502 p = lu._getEdgeIntersection(b, a, edge, bounds);
5503 p._code = lu._getBitCode(p, bounds);
5504 clippedPoints.push(p);
5505 }
5506 clippedPoints.push(a);
5507
5508 // else if b is inside the clip window (a->b enters the screen)
5509 } else if (!(b._code & edge)) {
5510 p = lu._getEdgeIntersection(b, a, edge, bounds);
5511 p._code = lu._getBitCode(p, bounds);
5512 clippedPoints.push(p);
5513 }
5514 }
5515 points = clippedPoints;
5516 }
5517
5518 return points;
5519 };
5520
5521
5522 /*
5523 * L.Polygon is used to display polygons on a map.
5524 */
5525
5526 L.Polygon = L.Polyline.extend({
5527 options: {
5528 fill: true
5529 },
5530
5531 initialize: function (latlngs, options) {
5532 var i, len, hole;
5533
5534 L.Polyline.prototype.initialize.call(this, latlngs, options);
5535
5536 if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
5537 this._latlngs = this._convertLatLngs(latlngs[0]);
5538 this._holes = latlngs.slice(1);
5539
5540 for (i = 0, len = this._holes.length; i < len; i++) {
5541 hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
5542 if (hole[0].equals(hole[hole.length - 1])) {
5543 hole.pop();
5544 }
5545 }
5546 }
5547
5548 // filter out last point if its equal to the first one
5549 latlngs = this._latlngs;
5550
5551 if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
5552 latlngs.pop();
5553 }
5554 },
5555
5556 projectLatlngs: function () {
5557 L.Polyline.prototype.projectLatlngs.call(this);
5558
5559 // project polygon holes points
5560 // TODO move this logic to Polyline to get rid of duplication
5561 this._holePoints = [];
5562
5563 if (!this._holes) { return; }
5564
5565 var i, j, len, len2;
5566
5567 for (i = 0, len = this._holes.length; i < len; i++) {
5568 this._holePoints[i] = [];
5569
5570 for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
5571 this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
5572 }
5573 }
5574 },
5575
5576 _clipPoints: function () {
5577 var points = this._originalPoints,
5578 newParts = [];
5579
5580 this._parts = [points].concat(this._holePoints);
5581
5582 if (this.options.noClip) { return; }
5583
5584 for (var i = 0, len = this._parts.length; i < len; i++) {
5585 var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
5586 if (clipped.length) {
5587 newParts.push(clipped);
5588 }
5589 }
5590
5591 this._parts = newParts;
5592 },
5593
5594 _getPathPartStr: function (points) {
5595 var str = L.Polyline.prototype._getPathPartStr.call(this, points);
5596 return str + (L.Browser.svg ? 'z' : 'x');
5597 }
5598 });
5599
5600 L.polygon = function (latlngs, options) {
5601 return new L.Polygon(latlngs, options);
5602 };
5603
5604
5605 /*
5606 * Contains L.MultiPolyline and L.MultiPolygon layers.
5607 */
5608
5609 (function () {
5610 function createMulti(Klass) {
5611
5612 return L.FeatureGroup.extend({
5613
5614 initialize: function (latlngs, options) {
5615 this._layers = {};
5616 this._options = options;
5617 this.setLatLngs(latlngs);
5618 },
5619
5620 setLatLngs: function (latlngs) {
5621 var i = 0,
5622 len = latlngs.length;
5623
5624 this.eachLayer(function (layer) {
5625 if (i < len) {
5626 layer.setLatLngs(latlngs[i++]);
5627 } else {
5628 this.removeLayer(layer);
5629 }
5630 }, this);
5631
5632 while (i < len) {
5633 this.addLayer(new Klass(latlngs[i++], this._options));
5634 }
5635
5636 return this;
5637 }
5638 });
5639 }
5640
5641 L.MultiPolyline = createMulti(L.Polyline);
5642 L.MultiPolygon = createMulti(L.Polygon);
5643
5644 L.multiPolyline = function (latlngs, options) {
5645 return new L.MultiPolyline(latlngs, options);
5646 };
5647
5648 L.multiPolygon = function (latlngs, options) {
5649 return new L.MultiPolygon(latlngs, options);
5650 };
5651 }());
5652
5653
5654 /*
5655 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
5656 */
5657
5658 L.Rectangle = L.Polygon.extend({
5659 initialize: function (latLngBounds, options) {
5660 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
5661 },
5662
5663 setBounds: function (latLngBounds) {
5664 this.setLatLngs(this._boundsToLatLngs(latLngBounds));
5665 },
5666
5667 _boundsToLatLngs: function (latLngBounds) {
5668 latLngBounds = L.latLngBounds(latLngBounds);
5669 return [
5670 latLngBounds.getSouthWest(),
5671 latLngBounds.getNorthWest(),
5672 latLngBounds.getNorthEast(),
5673 latLngBounds.getSouthEast()
5674 ];
5675 }
5676 });
5677
5678 L.rectangle = function (latLngBounds, options) {
5679 return new L.Rectangle(latLngBounds, options);
5680 };
5681
5682
5683 /*
5684 * L.Circle is a circle overlay (with a certain radius in meters).
5685 */
5686
5687 L.Circle = L.Path.extend({
5688 initialize: function (latlng, radius, options) {
5689 L.Path.prototype.initialize.call(this, options);
5690
5691 this._latlng = L.latLng(latlng);
5692 this._mRadius = radius;
5693 },
5694
5695 options: {
5696 fill: true
5697 },
5698
5699 setLatLng: function (latlng) {
5700 this._latlng = L.latLng(latlng);
5701 return this.redraw();
5702 },
5703
5704 setRadius: function (radius) {
5705 this._mRadius = radius;
5706 return this.redraw();
5707 },
5708
5709 projectLatlngs: function () {
5710 var lngRadius = this._getLngRadius(),
5711 latlng = this._latlng,
5712 pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
5713
5714 this._point = this._map.latLngToLayerPoint(latlng);
5715 this._radius = Math.max(this._point.x - pointLeft.x, 1);
5716 },
5717
5718 getBounds: function () {
5719 var lngRadius = this._getLngRadius(),
5720 latRadius = (this._mRadius / 40075017) * 360,
5721 latlng = this._latlng;
5722
5723 return new L.LatLngBounds(
5724 [latlng.lat - latRadius, latlng.lng - lngRadius],
5725 [latlng.lat + latRadius, latlng.lng + lngRadius]);
5726 },
5727
5728 getLatLng: function () {
5729 return this._latlng;
5730 },
5731
5732 getPathString: function () {
5733 var p = this._point,
5734 r = this._radius;
5735
5736 if (this._checkIfEmpty()) {
5737 return '';
5738 }
5739
5740 if (L.Browser.svg) {
5741 return 'M' + p.x + ',' + (p.y - r) +
5742 'A' + r + ',' + r + ',0,1,1,' +
5743 (p.x - 0.1) + ',' + (p.y - r) + ' z';
5744 } else {
5745 p._round();
5746 r = Math.round(r);
5747 return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
5748 }
5749 },
5750
5751 getRadius: function () {
5752 return this._mRadius;
5753 },
5754
5755 // TODO Earth hardcoded, move into projection code!
5756
5757 _getLatRadius: function () {
5758 return (this._mRadius / 40075017) * 360;
5759 },
5760
5761 _getLngRadius: function () {
5762 return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
5763 },
5764
5765 _checkIfEmpty: function () {
5766 if (!this._map) {
5767 return false;
5768 }
5769 var vp = this._map._pathViewport,
5770 r = this._radius,
5771 p = this._point;
5772
5773 return p.x - r > vp.max.x || p.y - r > vp.max.y ||
5774 p.x + r < vp.min.x || p.y + r < vp.min.y;
5775 }
5776 });
5777
5778 L.circle = function (latlng, radius, options) {
5779 return new L.Circle(latlng, radius, options);
5780 };
5781
5782
5783 /*
5784 * L.CircleMarker is a circle overlay with a permanent pixel radius.
5785 */
5786
5787 L.CircleMarker = L.Circle.extend({
5788 options: {
5789 radius: 10,
5790 weight: 2
5791 },
5792
5793 initialize: function (latlng, options) {
5794 L.Circle.prototype.initialize.call(this, latlng, null, options);
5795 this._radius = this.options.radius;
5796 },
5797
5798 projectLatlngs: function () {
5799 this._point = this._map.latLngToLayerPoint(this._latlng);
5800 },
5801
5802 _updateStyle : function () {
5803 L.Circle.prototype._updateStyle.call(this);
5804 this.setRadius(this.options.radius);
5805 },
5806
5807 setRadius: function (radius) {
5808 this.options.radius = this._radius = radius;
5809 return this.redraw();
5810 }
5811 });
5812
5813 L.circleMarker = function (latlng, options) {
5814 return new L.CircleMarker(latlng, options);
5815 };
5816
5817
5818 /*
5819 * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
5820 */
5821
5822 L.Polyline.include(!L.Path.CANVAS ? {} : {
5823 _containsPoint: function (p, closed) {
5824 var i, j, k, len, len2, dist, part,
5825 w = this.options.weight / 2;
5826
5827 if (L.Browser.touch) {
5828 w += 10; // polyline click tolerance on touch devices
5829 }
5830
5831 for (i = 0, len = this._parts.length; i < len; i++) {
5832 part = this._parts[i];
5833 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
5834 if (!closed && (j === 0)) {
5835 continue;
5836 }
5837
5838 dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
5839
5840 if (dist <= w) {
5841 return true;
5842 }
5843 }
5844 }
5845 return false;
5846 }
5847 });
5848
5849
5850 /*
5851 * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
5852 */
5853
5854 L.Polygon.include(!L.Path.CANVAS ? {} : {
5855 _containsPoint: function (p) {
5856 var inside = false,
5857 part, p1, p2,
5858 i, j, k,
5859 len, len2;
5860
5861 // TODO optimization: check if within bounds first
5862
5863 if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
5864 // click on polygon border
5865 return true;
5866 }
5867
5868 // ray casting algorithm for detecting if point is in polygon
5869
5870 for (i = 0, len = this._parts.length; i < len; i++) {
5871 part = this._parts[i];
5872
5873 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
5874 p1 = part[j];
5875 p2 = part[k];
5876
5877 if (((p1.y > p.y) !== (p2.y > p.y)) &&
5878 (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
5879 inside = !inside;
5880 }
5881 }
5882 }
5883
5884 return inside;
5885 }
5886 });
5887
5888
5889 /*
5890 * Extends L.Circle with Canvas-specific code.
5891 */
5892
5893 L.Circle.include(!L.Path.CANVAS ? {} : {
5894 _drawPath: function () {
5895 var p = this._point;
5896 this._ctx.beginPath();
5897 this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
5898 },
5899
5900 _containsPoint: function (p) {
5901 var center = this._point,
5902 w2 = this.options.stroke ? this.options.weight / 2 : 0;
5903
5904 return (p.distanceTo(center) <= this._radius + w2);
5905 }
5906 });
5907
5908
33685909 /*
3369 * L.Popup is used for displaying popups on the map.
5910 * CircleMarker canvas specific drawing parts.
33705911 */
33715912
3372 L.Map.mergeOptions({
3373 closePopupOnClick: true
3374 });
3375
3376 L.Popup = L.Class.extend({
3377 includes: L.Mixin.Events,
3378
3379 options: {
3380 minWidth: 50,
3381 maxWidth: 300,
3382 maxHeight: null,
3383 autoPan: true,
3384 closeButton: true,
3385 offset: new L.Point(0, 6),
3386 autoPanPadding: new L.Point(5, 5),
3387 className: '',
3388 zoomAnimation: true
3389 },
3390
3391 initialize: function (options, source) {
3392 L.setOptions(this, options);
3393
3394 this._source = source;
3395 this._animated = L.Browser.any3d && this.options.zoomAnimation;
3396 },
3397
3398 onAdd: function (map) {
3399 this._map = map;
3400
3401 if (!this._container) {
3402 this._initLayout();
3403 }
3404 this._updateContent();
3405
3406 var animFade = map.options.fadeAnimation;
3407
3408 if (animFade) {
3409 L.DomUtil.setOpacity(this._container, 0);
3410 }
3411 map._panes.popupPane.appendChild(this._container);
3412
3413 map.on('viewreset', this._updatePosition, this);
3414
3415 if (this._animated) {
3416 map.on('zoomanim', this._zoomAnimation, this);
3417 }
3418
3419 if (map.options.closePopupOnClick) {
3420 map.on('preclick', this._close, this);
3421 }
3422
3423 this._update();
3424
3425 if (animFade) {
3426 L.DomUtil.setOpacity(this._container, 1);
3427 }
3428 },
3429
3430 addTo: function (map) {
3431 map.addLayer(this);
3432 return this;
3433 },
3434
3435 openOn: function (map) {
3436 map.openPopup(this);
3437 return this;
3438 },
3439
3440 onRemove: function (map) {
3441 map._panes.popupPane.removeChild(this._container);
3442
3443 L.Util.falseFn(this._container.offsetWidth); // force reflow
3444
3445 map.off({
3446 viewreset: this._updatePosition,
3447 preclick: this._close,
3448 zoomanim: this._zoomAnimation
3449 }, this);
3450
3451 if (map.options.fadeAnimation) {
3452 L.DomUtil.setOpacity(this._container, 0);
3453 }
3454
3455 this._map = null;
3456 },
3457
3458 setLatLng: function (latlng) {
3459 this._latlng = L.latLng(latlng);
3460 this._update();
3461 return this;
3462 },
3463
3464 setContent: function (content) {
3465 this._content = content;
3466 this._update();
3467 return this;
3468 },
3469
3470 _close: function () {
3471 var map = this._map;
3472
3473 if (map) {
3474 map._popup = null;
3475
3476 map
3477 .removeLayer(this)
3478 .fire('popupclose', {popup: this});
3479 }
3480 },
3481
3482 _initLayout: function () {
3483 var prefix = 'leaflet-popup',
3484 containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
3485 (this._animated ? 'animated' : 'hide'),
3486 container = this._container = L.DomUtil.create('div', containerClass),
3487 closeButton;
3488
3489 if (this.options.closeButton) {
3490 closeButton = this._closeButton =
3491 L.DomUtil.create('a', prefix + '-close-button', container);
3492 closeButton.href = '#close';
3493 closeButton.innerHTML = '&#215;';
3494
3495 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
3496 }
3497
3498 var wrapper = this._wrapper =
3499 L.DomUtil.create('div', prefix + '-content-wrapper', container);
3500 L.DomEvent.disableClickPropagation(wrapper);
3501
3502 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
3503 L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
3504
3505 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
3506 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
3507 },
3508
3509 _update: function () {
3510 if (!this._map) { return; }
3511
3512 this._container.style.visibility = 'hidden';
3513
3514 this._updateContent();
3515 this._updateLayout();
3516 this._updatePosition();
3517
3518 this._container.style.visibility = '';
3519
3520 this._adjustPan();
3521 },
3522
3523 _updateContent: function () {
3524 if (!this._content) { return; }
3525
3526 if (typeof this._content === 'string') {
3527 this._contentNode.innerHTML = this._content;
3528 } else {
3529 while (this._contentNode.hasChildNodes()) {
3530 this._contentNode.removeChild(this._contentNode.firstChild);
3531 }
3532 this._contentNode.appendChild(this._content);
3533 }
3534 this.fire('contentupdate');
3535 },
3536
3537 _updateLayout: function () {
3538 var container = this._contentNode,
3539 style = container.style;
3540
3541 style.width = '';
3542 style.whiteSpace = 'nowrap';
3543
3544 var width = container.offsetWidth;
3545 width = Math.min(width, this.options.maxWidth);
3546 width = Math.max(width, this.options.minWidth);
3547
3548 style.width = (width + 1) + 'px';
3549 style.whiteSpace = '';
3550
3551 style.height = '';
3552
3553 var height = container.offsetHeight,
3554 maxHeight = this.options.maxHeight,
3555 scrolledClass = 'leaflet-popup-scrolled';
3556
3557 if (maxHeight && height > maxHeight) {
3558 style.height = maxHeight + 'px';
3559 L.DomUtil.addClass(container, scrolledClass);
3560 } else {
3561 L.DomUtil.removeClass(container, scrolledClass);
3562 }
3563
3564 this._containerWidth = this._container.offsetWidth;
3565 },
3566
3567 _updatePosition: function () {
3568 if (!this._map) { return; }
3569
3570 var pos = this._map.latLngToLayerPoint(this._latlng),
3571 animated = this._animated,
3572 offset = this.options.offset;
3573
3574 if (animated) {
3575 L.DomUtil.setPosition(this._container, pos);
3576 }
3577
3578 this._containerBottom = -offset.y - (animated ? 0 : pos.y);
3579 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
3580
3581 //Bottom position the popup in case the height of the popup changes (images loading etc)
3582 this._container.style.bottom = this._containerBottom + 'px';
3583 this._container.style.left = this._containerLeft + 'px';
3584 },
3585
3586 _zoomAnimation: function (opt) {
3587 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3588
3589 L.DomUtil.setPosition(this._container, pos);
3590 },
3591
3592 _adjustPan: function () {
3593 if (!this.options.autoPan) { return; }
3594
3595 var map = this._map,
3596 containerHeight = this._container.offsetHeight,
3597 containerWidth = this._containerWidth,
3598
3599 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
3600
3601 if (this._animated) {
3602 layerPos._add(L.DomUtil.getPosition(this._container));
3603 }
3604
3605 var containerPos = map.layerPointToContainerPoint(layerPos),
3606 padding = this.options.autoPanPadding,
3607 size = map.getSize(),
3608 dx = 0,
3609 dy = 0;
3610
3611 if (containerPos.x < 0) {
3612 dx = containerPos.x - padding.x;
3613 }
3614 if (containerPos.x + containerWidth > size.x) {
3615 dx = containerPos.x + containerWidth - size.x + padding.x;
3616 }
3617 if (containerPos.y < 0) {
3618 dy = containerPos.y - padding.y;
3619 }
3620 if (containerPos.y + containerHeight > size.y) {
3621 dy = containerPos.y + containerHeight - size.y + padding.y;
3622 }
3623
3624 if (dx || dy) {
3625 map.panBy(new L.Point(dx, dy));
3626 }
3627 },
3628
3629 _onCloseButtonClick: function (e) {
3630 this._close();
3631 L.DomEvent.stop(e);
5913 L.CircleMarker.include(!L.Path.CANVAS ? {} : {
5914 _updateStyle: function () {
5915 L.Path.prototype._updateStyle.call(this);
36325916 }
36335917 });
36345918
3635 L.popup = function (options, source) {
3636 return new L.Popup(options, source);
3637 };
3638
3639
3640 /*
3641 * Popup extension to L.Marker, adding popup-related methods.
3642 */
3643
3644 L.Marker.include({
3645 openPopup: function () {
3646 if (this._popup && this._map) {
3647 this._popup.setLatLng(this._latlng);
3648 this._map.openPopup(this._popup);
3649 }
3650
3651 return this;
3652 },
3653
3654 closePopup: function () {
3655 if (this._popup) {
3656 this._popup._close();
3657 }
3658 return this;
3659 },
3660
3661 bindPopup: function (content, options) {
3662 var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
3663
3664 anchor = anchor.add(L.Popup.prototype.options.offset);
3665
3666 if (options && options.offset) {
3667 anchor = anchor.add(options.offset);
3668 }
3669
3670 options = L.extend({offset: anchor}, options);
3671
3672 if (!this._popup) {
3673 this
3674 .on('click', this.openPopup, this)
3675 .on('remove', this.closePopup, this)
3676 .on('move', this._movePopup, this);
3677 }
3678
3679 this._popup = new L.Popup(options, this)
3680 .setContent(content);
3681
3682 return this;
3683 },
3684
3685 unbindPopup: function () {
3686 if (this._popup) {
3687 this._popup = null;
3688 this
3689 .off('click', this.openPopup)
3690 .off('remove', this.closePopup)
3691 .off('move', this._movePopup);
3692 }
3693 return this;
3694 },
3695
3696 _movePopup: function (e) {
3697 this._popup.setLatLng(e.latlng);
3698 }
3699 });
3700
3701
3702 /*
3703 * Adds popup-related methods to L.Map.
3704 */
3705
3706 L.Map.include({
3707 openPopup: function (popup) {
3708 this.closePopup();
3709
3710 this._popup = popup;
3711
3712 return this
3713 .addLayer(popup)
3714 .fire('popupopen', {popup: this._popup});
3715 },
3716
3717 closePopup: function () {
3718 if (this._popup) {
3719 this._popup._close();
3720 }
3721 return this;
3722 }
3723 });
3724
3725
3726 /*
3727 * L.LayerGroup is a class to combine several layers into one so that
3728 * you can manipulate the group (e.g. add/remove it) as one layer.
3729 */
3730
3731 L.LayerGroup = L.Class.extend({
3732 initialize: function (layers) {
3733 this._layers = {};
3734
3735 var i, len;
3736
3737 if (layers) {
3738 for (i = 0, len = layers.length; i < len; i++) {
3739 this.addLayer(layers[i]);
3740 }
3741 }
3742 },
3743
3744 addLayer: function (layer) {
3745 var id = L.stamp(layer);
3746
3747 this._layers[id] = layer;
3748
3749 if (this._map) {
3750 this._map.addLayer(layer);
3751 }
3752
3753 return this;
3754 },
3755
3756 removeLayer: function (layer) {
3757 var id = L.stamp(layer);
3758
3759 delete this._layers[id];
3760
3761 if (this._map) {
3762 this._map.removeLayer(layer);
3763 }
3764
3765 return this;
3766 },
3767
3768 clearLayers: function () {
3769 this.eachLayer(this.removeLayer, this);
3770 return this;
3771 },
3772
3773 invoke: function (methodName) {
3774 var args = Array.prototype.slice.call(arguments, 1),
3775 i, layer;
3776
3777 for (i in this._layers) {
3778 if (this._layers.hasOwnProperty(i)) {
3779 layer = this._layers[i];
3780
3781 if (layer[methodName]) {
3782 layer[methodName].apply(layer, args);
3783 }
3784 }
3785 }
3786
3787 return this;
3788 },
3789
3790 onAdd: function (map) {
3791 this._map = map;
3792 this.eachLayer(map.addLayer, map);
3793 },
3794
3795 onRemove: function (map) {
3796 this.eachLayer(map.removeLayer, map);
3797 this._map = null;
3798 },
3799
3800 addTo: function (map) {
3801 map.addLayer(this);
3802 return this;
3803 },
3804
3805 eachLayer: function (method, context) {
3806 for (var i in this._layers) {
3807 if (this._layers.hasOwnProperty(i)) {
3808 method.call(context, this._layers[i]);
3809 }
3810 }
3811 },
3812
3813 setZIndex: function (zIndex) {
3814 return this.invoke('setZIndex', zIndex);
3815 }
3816 });
3817
3818 L.layerGroup = function (layers) {
3819 return new L.LayerGroup(layers);
3820 };
3821
3822
3823 /*
3824 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
3825 * shared between a group of interactive layers (like vectors or markers).
3826 */
3827
3828 L.FeatureGroup = L.LayerGroup.extend({
3829 includes: L.Mixin.Events,
3830
3831 statics: {
3832 EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu'
3833 },
3834
3835 addLayer: function (layer) {
3836 if (this._layers[L.stamp(layer)]) {
3837 return this;
3838 }
3839
3840 layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
3841
3842 L.LayerGroup.prototype.addLayer.call(this, layer);
3843
3844 if (this._popupContent && layer.bindPopup) {
3845 layer.bindPopup(this._popupContent, this._popupOptions);
3846 }
3847
3848 return this.fire('layeradd', {layer: layer});
3849 },
3850
3851 removeLayer: function (layer) {
3852 layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
3853
3854 L.LayerGroup.prototype.removeLayer.call(this, layer);
3855
3856
3857 if (this._popupContent) {
3858 this.invoke('unbindPopup');
3859 }
3860
3861 return this.fire('layerremove', {layer: layer});
3862 },
3863
3864 bindPopup: function (content, options) {
3865 this._popupContent = content;
3866 this._popupOptions = options;
3867 return this.invoke('bindPopup', content, options);
3868 },
3869
3870 setStyle: function (style) {
3871 return this.invoke('setStyle', style);
3872 },
3873
3874 bringToFront: function () {
3875 return this.invoke('bringToFront');
3876 },
3877
3878 bringToBack: function () {
3879 return this.invoke('bringToBack');
3880 },
3881
3882 getBounds: function () {
3883 var bounds = new L.LatLngBounds();
3884
3885 this.eachLayer(function (layer) {
3886 bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
3887 });
3888
3889 return bounds;
3890 },
3891
3892 _propagateEvent: function (e) {
3893 e.layer = e.target;
3894 e.target = this;
3895
3896 this.fire(e.type, e);
3897 }
3898 });
3899
3900 L.featureGroup = function (layers) {
3901 return new L.FeatureGroup(layers);
3902 };
3903
3904
3905 /*
3906 * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
3907 */
3908
3909 L.Path = L.Class.extend({
3910 includes: [L.Mixin.Events],
3911
3912 statics: {
3913 // how much to extend the clip area around the map view
3914 // (relative to its size, e.g. 0.5 is half the screen in each direction)
3915 // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
3916 CLIP_PADDING: L.Browser.mobile ?
3917 Math.max(0, Math.min(0.5,
3918 (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5
3919 },
3920
3921 options: {
3922 stroke: true,
3923 color: '#0033ff',
3924 dashArray: null,
3925 weight: 5,
3926 opacity: 0.5,
3927
3928 fill: false,
3929 fillColor: null, //same as color by default
3930 fillOpacity: 0.2,
3931
3932 clickable: true
3933 },
3934
3935 initialize: function (options) {
3936 L.setOptions(this, options);
3937 },
3938
3939 onAdd: function (map) {
3940 this._map = map;
3941
3942 if (!this._container) {
3943 this._initElements();
3944 this._initEvents();
3945 }
3946
3947 this.projectLatlngs();
3948 this._updatePath();
3949
3950 if (this._container) {
3951 this._map._pathRoot.appendChild(this._container);
3952 }
3953
3954 this.fire('add');
3955
3956 map.on({
3957 'viewreset': this.projectLatlngs,
3958 'moveend': this._updatePath
3959 }, this);
3960 },
3961
3962 addTo: function (map) {
3963 map.addLayer(this);
3964 return this;
3965 },
3966
3967 onRemove: function (map) {
3968 map._pathRoot.removeChild(this._container);
3969
3970 // Need to fire remove event before we set _map to null as the event hooks might need the object
3971 this.fire('remove');
3972 this._map = null;
3973
3974 if (L.Browser.vml) {
3975 this._container = null;
3976 this._stroke = null;
3977 this._fill = null;
3978 }
3979
3980 map.off({
3981 'viewreset': this.projectLatlngs,
3982 'moveend': this._updatePath
3983 }, this);
3984 },
3985
3986 projectLatlngs: function () {
3987 // do all projection stuff here
3988 },
3989
3990 setStyle: function (style) {
3991 L.setOptions(this, style);
3992
3993 if (this._container) {
3994 this._updateStyle();
3995 }
3996
3997 return this;
3998 },
3999
4000 redraw: function () {
4001 if (this._map) {
4002 this.projectLatlngs();
4003 this._updatePath();
4004 }
4005 return this;
4006 }
4007 });
4008
4009 L.Map.include({
4010 _updatePathViewport: function () {
4011 var p = L.Path.CLIP_PADDING,
4012 size = this.getSize(),
4013 panePos = L.DomUtil.getPosition(this._mapPane),
4014 min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
4015 max = min.add(size.multiplyBy(1 + p * 2)._round());
4016
4017 this._pathViewport = new L.Bounds(min, max);
4018 }
4019 });
4020
4021
4022 /*
4023 * Extends L.Path with SVG-specific rendering code.
4024 */
4025
4026 L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
4027
4028 L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
4029
4030 L.Path = L.Path.extend({
4031 statics: {
4032 SVG: L.Browser.svg
4033 },
4034
4035 bringToFront: function () {
4036 var root = this._map._pathRoot,
4037 path = this._container;
4038
4039 if (path && root.lastChild !== path) {
4040 root.appendChild(path);
4041 }
4042 return this;
4043 },
4044
4045 bringToBack: function () {
4046 var root = this._map._pathRoot,
4047 path = this._container,
4048 first = root.firstChild;
4049
4050 if (path && first !== path) {
4051 root.insertBefore(path, first);
4052 }
4053 return this;
4054 },
4055
4056 getPathString: function () {
4057 // form path string here
4058 },
4059
4060 _createElement: function (name) {
4061 return document.createElementNS(L.Path.SVG_NS, name);
4062 },
4063
4064 _initElements: function () {
4065 this._map._initPathRoot();
4066 this._initPath();
4067 this._initStyle();
4068 },
4069
4070 _initPath: function () {
4071 this._container = this._createElement('g');
4072
4073 this._path = this._createElement('path');
4074 this._container.appendChild(this._path);
4075 },
4076
4077 _initStyle: function () {
4078 if (this.options.stroke) {
4079 this._path.setAttribute('stroke-linejoin', 'round');
4080 this._path.setAttribute('stroke-linecap', 'round');
4081 }
4082 if (this.options.fill) {
4083 this._path.setAttribute('fill-rule', 'evenodd');
4084 }
4085 this._updateStyle();
4086 },
4087
4088 _updateStyle: function () {
4089 if (this.options.stroke) {
4090 this._path.setAttribute('stroke', this.options.color);
4091 this._path.setAttribute('stroke-opacity', this.options.opacity);
4092 this._path.setAttribute('stroke-width', this.options.weight);
4093 if (this.options.dashArray) {
4094 this._path.setAttribute('stroke-dasharray', this.options.dashArray);
4095 } else {
4096 this._path.removeAttribute('stroke-dasharray');
4097 }
4098 } else {
4099 this._path.setAttribute('stroke', 'none');
4100 }
4101 if (this.options.fill) {
4102 this._path.setAttribute('fill', this.options.fillColor || this.options.color);
4103 this._path.setAttribute('fill-opacity', this.options.fillOpacity);
4104 } else {
4105 this._path.setAttribute('fill', 'none');
4106 }
4107 },
4108
4109 _updatePath: function () {
4110 var str = this.getPathString();
4111 if (!str) {
4112 // fix webkit empty string parsing bug
4113 str = 'M0 0';
4114 }
4115 this._path.setAttribute('d', str);
4116 },
4117
4118 // TODO remove duplication with L.Map
4119 _initEvents: function () {
4120 if (this.options.clickable) {
4121 if (L.Browser.svg || !L.Browser.vml) {
4122 this._path.setAttribute('class', 'leaflet-clickable');
4123 }
4124
4125 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
4126
4127 var events = ['dblclick', 'mousedown', 'mouseover',
4128 'mouseout', 'mousemove', 'contextmenu'];
4129 for (var i = 0; i < events.length; i++) {
4130 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
4131 }
4132 }
4133 },
4134
4135 _onMouseClick: function (e) {
4136 if (this._map.dragging && this._map.dragging.moved()) { return; }
4137
4138 this._fireMouseEvent(e);
4139 },
4140
4141 _fireMouseEvent: function (e) {
4142 if (!this.hasEventListeners(e.type)) { return; }
4143
4144 var map = this._map,
4145 containerPoint = map.mouseEventToContainerPoint(e),
4146 layerPoint = map.containerPointToLayerPoint(containerPoint),
4147 latlng = map.layerPointToLatLng(layerPoint);
4148
4149 this.fire(e.type, {
4150 latlng: latlng,
4151 layerPoint: layerPoint,
4152 containerPoint: containerPoint,
4153 originalEvent: e
4154 });
4155
4156 if (e.type === 'contextmenu') {
4157 L.DomEvent.preventDefault(e);
4158 }
4159 if (e.type !== 'mousemove') {
4160 L.DomEvent.stopPropagation(e);
4161 }
4162 }
4163 });
4164
4165 L.Map.include({
4166 _initPathRoot: function () {
4167 if (!this._pathRoot) {
4168 this._pathRoot = L.Path.prototype._createElement('svg');
4169 this._panes.overlayPane.appendChild(this._pathRoot);
4170
4171 if (this.options.zoomAnimation && L.Browser.any3d) {
4172 this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
4173
4174 this.on({
4175 'zoomanim': this._animatePathZoom,
4176 'zoomend': this._endPathZoom
4177 });
4178 } else {
4179 this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
4180 }
4181
4182 this.on('moveend', this._updateSvgViewport);
4183 this._updateSvgViewport();
4184 }
4185 },
4186
4187 _animatePathZoom: function (e) {
4188 var scale = this.getZoomScale(e.zoom),
4189 offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
4190
4191 this._pathRoot.style[L.DomUtil.TRANSFORM] =
4192 L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
4193
4194 this._pathZooming = true;
4195 },
4196
4197 _endPathZoom: function () {
4198 this._pathZooming = false;
4199 },
4200
4201 _updateSvgViewport: function () {
4202
4203 if (this._pathZooming) {
4204 // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
4205 // When the zoom animation ends we will be updated again anyway
4206 // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
4207 return;
4208 }
4209
4210 this._updatePathViewport();
4211
4212 var vp = this._pathViewport,
4213 min = vp.min,
4214 max = vp.max,
4215 width = max.x - min.x,
4216 height = max.y - min.y,
4217 root = this._pathRoot,
4218 pane = this._panes.overlayPane;
4219
4220 // Hack to make flicker on drag end on mobile webkit less irritating
4221 if (L.Browser.mobileWebkit) {
4222 pane.removeChild(root);
4223 }
4224
4225 L.DomUtil.setPosition(root, min);
4226 root.setAttribute('width', width);
4227 root.setAttribute('height', height);
4228 root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
4229
4230 if (L.Browser.mobileWebkit) {
4231 pane.appendChild(root);
4232 }
4233 }
4234 });
4235
4236
4237 /*
4238 * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
4239 */
4240
4241 L.Path.include({
4242
4243 bindPopup: function (content, options) {
4244
4245 if (!this._popup || options) {
4246 this._popup = new L.Popup(options, this);
4247 }
4248
4249 this._popup.setContent(content);
4250
4251 if (!this._popupHandlersAdded) {
4252 this
4253 .on('click', this._openPopup, this)
4254 .on('remove', this.closePopup, this);
4255
4256 this._popupHandlersAdded = true;
4257 }
4258
4259 return this;
4260 },
4261
4262 unbindPopup: function () {
4263 if (this._popup) {
4264 this._popup = null;
4265 this
4266 .off('click', this._openPopup)
4267 .off('remove', this.closePopup);
4268
4269 this._popupHandlersAdded = false;
4270 }
4271 return this;
4272 },
4273
4274 openPopup: function (latlng) {
4275
4276 if (this._popup) {
4277 // open the popup from one of the path's points if not specified
4278 latlng = latlng || this._latlng ||
4279 this._latlngs[Math.floor(this._latlngs.length / 2)];
4280
4281 this._openPopup({latlng: latlng});
4282 }
4283
4284 return this;
4285 },
4286
4287 closePopup: function () {
4288 if (this._popup) {
4289 this._popup._close();
4290 }
4291 return this;
4292 },
4293
4294 _openPopup: function (e) {
4295 this._popup.setLatLng(e.latlng);
4296 this._map.openPopup(this._popup);
4297 }
4298 });
4299
4300
4301 /*
4302 * Vector rendering for IE6-8 through VML.
4303 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
4304 */
4305
4306 L.Browser.vml = !L.Browser.svg && (function () {
4307 try {
4308 var div = document.createElement('div');
4309 div.innerHTML = '<v:shape adj="1"/>';
4310
4311 var shape = div.firstChild;
4312 shape.style.behavior = 'url(#default#VML)';
4313
4314 return shape && (typeof shape.adj === 'object');
4315
4316 } catch (e) {
4317 return false;
4318 }
4319 }());
4320
4321 L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
4322 statics: {
4323 VML: true,
4324 CLIP_PADDING: 0.02
4325 },
4326
4327 _createElement: (function () {
4328 try {
4329 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
4330 return function (name) {
4331 return document.createElement('<lvml:' + name + ' class="lvml">');
4332 };
4333 } catch (e) {
4334 return function (name) {
4335 return document.createElement(
4336 '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
4337 };
4338 }
4339 }()),
4340
4341 _initPath: function () {
4342 var container = this._container = this._createElement('shape');
4343 L.DomUtil.addClass(container, 'leaflet-vml-shape');
4344 if (this.options.clickable) {
4345 L.DomUtil.addClass(container, 'leaflet-clickable');
4346 }
4347 container.coordsize = '1 1';
4348
4349 this._path = this._createElement('path');
4350 container.appendChild(this._path);
4351
4352 this._map._pathRoot.appendChild(container);
4353 },
4354
4355 _initStyle: function () {
4356 this._updateStyle();
4357 },
4358
4359 _updateStyle: function () {
4360 var stroke = this._stroke,
4361 fill = this._fill,
4362 options = this.options,
4363 container = this._container;
4364
4365 container.stroked = options.stroke;
4366 container.filled = options.fill;
4367
4368 if (options.stroke) {
4369 if (!stroke) {
4370 stroke = this._stroke = this._createElement('stroke');
4371 stroke.endcap = 'round';
4372 container.appendChild(stroke);
4373 }
4374 stroke.weight = options.weight + 'px';
4375 stroke.color = options.color;
4376 stroke.opacity = options.opacity;
4377
4378 if (options.dashArray) {
4379 stroke.dashStyle = options.dashArray instanceof Array ?
4380 options.dashArray.join(' ') :
4381 options.dashArray.replace(/ *, */g, ' ');
4382 } else {
4383 stroke.dashStyle = '';
4384 }
4385
4386 } else if (stroke) {
4387 container.removeChild(stroke);
4388 this._stroke = null;
4389 }
4390
4391 if (options.fill) {
4392 if (!fill) {
4393 fill = this._fill = this._createElement('fill');
4394 container.appendChild(fill);
4395 }
4396 fill.color = options.fillColor || options.color;
4397 fill.opacity = options.fillOpacity;
4398
4399 } else if (fill) {
4400 container.removeChild(fill);
4401 this._fill = null;
4402 }
4403 },
4404
4405 _updatePath: function () {
4406 var style = this._container.style;
4407
4408 style.display = 'none';
4409 this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
4410 style.display = '';
4411 }
4412 });
4413
4414 L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
4415 _initPathRoot: function () {
4416 if (this._pathRoot) { return; }
4417
4418 var root = this._pathRoot = document.createElement('div');
4419 root.className = 'leaflet-vml-container';
4420 this._panes.overlayPane.appendChild(root);
4421
4422 this.on('moveend', this._updatePathViewport);
4423 this._updatePathViewport();
4424 }
4425 });
4426
4427
4428 /*
4429 * Vector rendering for all browsers that support canvas.
4430 */
4431
4432 L.Browser.canvas = (function () {
4433 return !!document.createElement('canvas').getContext;
4434 }());
4435
4436 L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
4437 statics: {
4438 //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
4439 CANVAS: true,
4440 SVG: false
4441 },
4442
4443 redraw: function () {
4444 if (this._map) {
4445 this.projectLatlngs();
4446 this._requestUpdate();
4447 }
4448 return this;
4449 },
4450
4451 setStyle: function (style) {
4452 L.setOptions(this, style);
4453
4454 if (this._map) {
4455 this._updateStyle();
4456 this._requestUpdate();
4457 }
4458 return this;
4459 },
4460
4461 onRemove: function (map) {
4462 map
4463 .off('viewreset', this.projectLatlngs, this)
4464 .off('moveend', this._updatePath, this);
4465
4466 if (this.options.clickable) {
4467 this._map.off('click', this._onClick, this);
4468 }
4469
4470 this._requestUpdate();
4471
4472 this._map = null;
4473 },
4474
4475 _requestUpdate: function () {
4476 if (this._map && !L.Path._updateRequest) {
4477 L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
4478 }
4479 },
4480
4481 _fireMapMoveEnd: function () {
4482 L.Path._updateRequest = null;
4483 this.fire('moveend');
4484 },
4485
4486 _initElements: function () {
4487 this._map._initPathRoot();
4488 this._ctx = this._map._canvasCtx;
4489 },
4490
4491 _updateStyle: function () {
4492 var options = this.options;
4493
4494 if (options.stroke) {
4495 this._ctx.lineWidth = options.weight;
4496 this._ctx.strokeStyle = options.color;
4497 }
4498 if (options.fill) {
4499 this._ctx.fillStyle = options.fillColor || options.color;
4500 }
4501 },
4502
4503 _drawPath: function () {
4504 var i, j, len, len2, point, drawMethod;
4505
4506 this._ctx.beginPath();
4507
4508 for (i = 0, len = this._parts.length; i < len; i++) {
4509 for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
4510 point = this._parts[i][j];
4511 drawMethod = (j === 0 ? 'move' : 'line') + 'To';
4512
4513 this._ctx[drawMethod](point.x, point.y);
4514 }
4515 // TODO refactor ugly hack
4516 if (this instanceof L.Polygon) {
4517 this._ctx.closePath();
4518 }
4519 }
4520 },
4521
4522 _checkIfEmpty: function () {
4523 return !this._parts.length;
4524 },
4525
4526 _updatePath: function () {
4527 if (this._checkIfEmpty()) { return; }
4528
4529 var ctx = this._ctx,
4530 options = this.options;
4531
4532 this._drawPath();
4533 ctx.save();
4534 this._updateStyle();
4535
4536 if (options.fill) {
4537 ctx.globalAlpha = options.fillOpacity;
4538 ctx.fill();
4539 }
4540
4541 if (options.stroke) {
4542 ctx.globalAlpha = options.opacity;
4543 ctx.stroke();
4544 }
4545
4546 ctx.restore();
4547
4548 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
4549 },
4550
4551 _initEvents: function () {
4552 if (this.options.clickable) {
4553 // TODO hand cursor
4554 // TODO mouseover, mouseout, dblclick
4555 this._map.on('click', this._onClick, this);
4556 }
4557 },
4558
4559 _onClick: function (e) {
4560 if (this._containsPoint(e.layerPoint)) {
4561 this.fire('click', {
4562 latlng: e.latlng,
4563 layerPoint: e.layerPoint,
4564 containerPoint: e.containerPoint,
4565 originalEvent: e
4566 });
4567 }
4568 }
4569 });
4570
4571 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
4572 _initPathRoot: function () {
4573 var root = this._pathRoot,
4574 ctx;
4575
4576 if (!root) {
4577 root = this._pathRoot = document.createElement("canvas");
4578 root.style.position = 'absolute';
4579 ctx = this._canvasCtx = root.getContext('2d');
4580
4581 ctx.lineCap = "round";
4582 ctx.lineJoin = "round";
4583
4584 this._panes.overlayPane.appendChild(root);
4585
4586 if (this.options.zoomAnimation) {
4587 this._pathRoot.className = 'leaflet-zoom-animated';
4588 this.on('zoomanim', this._animatePathZoom);
4589 this.on('zoomend', this._endPathZoom);
4590 }
4591 this.on('moveend', this._updateCanvasViewport);
4592 this._updateCanvasViewport();
4593 }
4594 },
4595
4596 _updateCanvasViewport: function () {
4597 // don't redraw while zooming. See _updateSvgViewport for more details
4598 if (this._pathZooming) { return; }
4599 this._updatePathViewport();
4600
4601 var vp = this._pathViewport,
4602 min = vp.min,
4603 size = vp.max.subtract(min),
4604 root = this._pathRoot;
4605
4606 //TODO check if this works properly on mobile webkit
4607 L.DomUtil.setPosition(root, min);
4608 root.width = size.x;
4609 root.height = size.y;
4610 root.getContext('2d').translate(-min.x, -min.y);
4611 }
4612 });
4613
4614
4615 /*
4616 * L.LineUtil contains different utility functions for line segments
4617 * and polylines (clipping, simplification, distances, etc.)
4618 */
4619
4620 /*jshint bitwise:false */ // allow bitwise oprations for this file
4621
4622 L.LineUtil = {
4623
4624 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
4625 // Improves rendering performance dramatically by lessening the number of points to draw.
4626
4627 simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
4628 if (!tolerance || !points.length) {
4629 return points.slice();
4630 }
4631
4632 var sqTolerance = tolerance * tolerance;
4633
4634 // stage 1: vertex reduction
4635 points = this._reducePoints(points, sqTolerance);
4636
4637 // stage 2: Douglas-Peucker simplification
4638 points = this._simplifyDP(points, sqTolerance);
4639
4640 return points;
4641 },
4642
4643 // distance from a point to a segment between two points
4644 pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
4645 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
4646 },
4647
4648 closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
4649 return this._sqClosestPointOnSegment(p, p1, p2);
4650 },
4651
4652 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
4653 _simplifyDP: function (points, sqTolerance) {
4654
4655 var len = points.length,
4656 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
4657 markers = new ArrayConstructor(len);
4658
4659 markers[0] = markers[len - 1] = 1;
4660
4661 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
4662
4663 var i,
4664 newPoints = [];
4665
4666 for (i = 0; i < len; i++) {
4667 if (markers[i]) {
4668 newPoints.push(points[i]);
4669 }
4670 }
4671
4672 return newPoints;
4673 },
4674
4675 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
4676
4677 var maxSqDist = 0,
4678 index, i, sqDist;
4679
4680 for (i = first + 1; i <= last - 1; i++) {
4681 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
4682
4683 if (sqDist > maxSqDist) {
4684 index = i;
4685 maxSqDist = sqDist;
4686 }
4687 }
4688
4689 if (maxSqDist > sqTolerance) {
4690 markers[index] = 1;
4691
4692 this._simplifyDPStep(points, markers, sqTolerance, first, index);
4693 this._simplifyDPStep(points, markers, sqTolerance, index, last);
4694 }
4695 },
4696
4697 // reduce points that are too close to each other to a single point
4698 _reducePoints: function (points, sqTolerance) {
4699 var reducedPoints = [points[0]];
4700
4701 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
4702 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
4703 reducedPoints.push(points[i]);
4704 prev = i;
4705 }
4706 }
4707 if (prev < len - 1) {
4708 reducedPoints.push(points[len - 1]);
4709 }
4710 return reducedPoints;
4711 },
4712
4713 // Cohen-Sutherland line clipping algorithm.
4714 // Used to avoid rendering parts of a polyline that are not currently visible.
4715
4716 clipSegment: function (a, b, bounds, useLastCode) {
4717 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
4718 codeB = this._getBitCode(b, bounds),
4719
4720 codeOut, p, newCode;
4721
4722 // save 2nd code to avoid calculating it on the next segment
4723 this._lastCode = codeB;
4724
4725 while (true) {
4726 // if a,b is inside the clip window (trivial accept)
4727 if (!(codeA | codeB)) {
4728 return [a, b];
4729 // if a,b is outside the clip window (trivial reject)
4730 } else if (codeA & codeB) {
4731 return false;
4732 // other cases
4733 } else {
4734 codeOut = codeA || codeB,
4735 p = this._getEdgeIntersection(a, b, codeOut, bounds),
4736 newCode = this._getBitCode(p, bounds);
4737
4738 if (codeOut === codeA) {
4739 a = p;
4740 codeA = newCode;
4741 } else {
4742 b = p;
4743 codeB = newCode;
4744 }
4745 }
4746 }
4747 },
4748
4749 _getEdgeIntersection: function (a, b, code, bounds) {
4750 var dx = b.x - a.x,
4751 dy = b.y - a.y,
4752 min = bounds.min,
4753 max = bounds.max;
4754
4755 if (code & 8) { // top
4756 return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
4757 } else if (code & 4) { // bottom
4758 return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
4759 } else if (code & 2) { // right
4760 return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
4761 } else if (code & 1) { // left
4762 return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
4763 }
4764 },
4765
4766 _getBitCode: function (/*Point*/ p, bounds) {
4767 var code = 0;
4768
4769 if (p.x < bounds.min.x) { // left
4770 code |= 1;
4771 } else if (p.x > bounds.max.x) { // right
4772 code |= 2;
4773 }
4774 if (p.y < bounds.min.y) { // bottom
4775 code |= 4;
4776 } else if (p.y > bounds.max.y) { // top
4777 code |= 8;
4778 }
4779
4780 return code;
4781 },
4782
4783 // square distance (to avoid unnecessary Math.sqrt calls)
4784 _sqDist: function (p1, p2) {
4785 var dx = p2.x - p1.x,
4786 dy = p2.y - p1.y;
4787 return dx * dx + dy * dy;
4788 },
4789
4790 // return closest point on segment or distance to that point
4791 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
4792 var x = p1.x,
4793 y = p1.y,
4794 dx = p2.x - x,
4795 dy = p2.y - y,
4796 dot = dx * dx + dy * dy,
4797 t;
4798
4799 if (dot > 0) {
4800 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
4801
4802 if (t > 1) {
4803 x = p2.x;
4804 y = p2.y;
4805 } else if (t > 0) {
4806 x += dx * t;
4807 y += dy * t;
4808 }
4809 }
4810
4811 dx = p.x - x;
4812 dy = p.y - y;
4813
4814 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
4815 }
4816 };
4817
4818
4819 /*
4820 * L.Polygon is used to display polylines on a map.
4821 */
4822
4823 L.Polyline = L.Path.extend({
4824 initialize: function (latlngs, options) {
4825 L.Path.prototype.initialize.call(this, options);
4826
4827 this._latlngs = this._convertLatLngs(latlngs);
4828 },
4829
4830 options: {
4831 // how much to simplify the polyline on each zoom level
4832 // more = better performance and smoother look, less = more accurate
4833 smoothFactor: 1.0,
4834 noClip: false
4835 },
4836
4837 projectLatlngs: function () {
4838 this._originalPoints = [];
4839
4840 for (var i = 0, len = this._latlngs.length; i < len; i++) {
4841 this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
4842 }
4843 },
4844
4845 getPathString: function () {
4846 for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
4847 str += this._getPathPartStr(this._parts[i]);
4848 }
4849 return str;
4850 },
4851
4852 getLatLngs: function () {
4853 return this._latlngs;
4854 },
4855
4856 setLatLngs: function (latlngs) {
4857 this._latlngs = this._convertLatLngs(latlngs);
4858 return this.redraw();
4859 },
4860
4861 addLatLng: function (latlng) {
4862 this._latlngs.push(L.latLng(latlng));
4863 return this.redraw();
4864 },
4865
4866 spliceLatLngs: function () { // (Number index, Number howMany)
4867 var removed = [].splice.apply(this._latlngs, arguments);
4868 this._convertLatLngs(this._latlngs);
4869 this.redraw();
4870 return removed;
4871 },
4872
4873 closestLayerPoint: function (p) {
4874 var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
4875
4876 for (var j = 0, jLen = parts.length; j < jLen; j++) {
4877 var points = parts[j];
4878 for (var i = 1, len = points.length; i < len; i++) {
4879 p1 = points[i - 1];
4880 p2 = points[i];
4881 var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
4882 if (sqDist < minDistance) {
4883 minDistance = sqDist;
4884 minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
4885 }
4886 }
4887 }
4888 if (minPoint) {
4889 minPoint.distance = Math.sqrt(minDistance);
4890 }
4891 return minPoint;
4892 },
4893
4894 getBounds: function () {
4895 var bounds = new L.LatLngBounds(),
4896 latLngs = this.getLatLngs(),
4897 i, len;
4898
4899 for (i = 0, len = latLngs.length; i < len; i++) {
4900 bounds.extend(latLngs[i]);
4901 }
4902
4903 return bounds;
4904 },
4905
4906 _convertLatLngs: function (latlngs) {
4907 var i, len;
4908 for (i = 0, len = latlngs.length; i < len; i++) {
4909 if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
4910 return;
4911 }
4912 latlngs[i] = L.latLng(latlngs[i]);
4913 }
4914 return latlngs;
4915 },
4916
4917 _initEvents: function () {
4918 L.Path.prototype._initEvents.call(this);
4919 },
4920
4921 _getPathPartStr: function (points) {
4922 var round = L.Path.VML;
4923
4924 for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
4925 p = points[j];
4926 if (round) {
4927 p._round();
4928 }
4929 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
4930 }
4931 return str;
4932 },
4933
4934 _clipPoints: function () {
4935 var points = this._originalPoints,
4936 len = points.length,
4937 i, k, segment;
4938
4939 if (this.options.noClip) {
4940 this._parts = [points];
4941 return;
4942 }
4943
4944 this._parts = [];
4945
4946 var parts = this._parts,
4947 vp = this._map._pathViewport,
4948 lu = L.LineUtil;
4949
4950 for (i = 0, k = 0; i < len - 1; i++) {
4951 segment = lu.clipSegment(points[i], points[i + 1], vp, i);
4952 if (!segment) {
4953 continue;
4954 }
4955
4956 parts[k] = parts[k] || [];
4957 parts[k].push(segment[0]);
4958
4959 // if segment goes out of screen, or it's the last one, it's the end of the line part
4960 if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
4961 parts[k].push(segment[1]);
4962 k++;
4963 }
4964 }
4965 },
4966
4967 // simplify each clipped part of the polyline
4968 _simplifyPoints: function () {
4969 var parts = this._parts,
4970 lu = L.LineUtil;
4971
4972 for (var i = 0, len = parts.length; i < len; i++) {
4973 parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
4974 }
4975 },
4976
4977 _updatePath: function () {
4978 if (!this._map) { return; }
4979
4980 this._clipPoints();
4981 this._simplifyPoints();
4982
4983 L.Path.prototype._updatePath.call(this);
4984 }
4985 });
4986
4987 L.polyline = function (latlngs, options) {
4988 return new L.Polyline(latlngs, options);
4989 };
4990
4991
4992 /*
4993 * L.PolyUtil contains utility functions for polygons (clipping, etc.).
4994 */
4995
4996 /*jshint bitwise:false */ // allow bitwise operations here
4997
4998 L.PolyUtil = {};
4999
5000 /*
5001 * Sutherland-Hodgeman polygon clipping algorithm.
5002 * Used to avoid rendering parts of a polygon that are not currently visible.
5003 */
5004 L.PolyUtil.clipPolygon = function (points, bounds) {
5005 var clippedPoints,
5006 edges = [1, 4, 2, 8],
5007 i, j, k,
5008 a, b,
5009 len, edge, p,
5010 lu = L.LineUtil;
5011
5012 for (i = 0, len = points.length; i < len; i++) {
5013 points[i]._code = lu._getBitCode(points[i], bounds);
5014 }
5015
5016 // for each edge (left, bottom, right, top)
5017 for (k = 0; k < 4; k++) {
5018 edge = edges[k];
5019 clippedPoints = [];
5020
5021 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
5022 a = points[i];
5023 b = points[j];
5024
5025 // if a is inside the clip window
5026 if (!(a._code & edge)) {
5027 // if b is outside the clip window (a->b goes out of screen)
5028 if (b._code & edge) {
5029 p = lu._getEdgeIntersection(b, a, edge, bounds);
5030 p._code = lu._getBitCode(p, bounds);
5031 clippedPoints.push(p);
5032 }
5033 clippedPoints.push(a);
5034
5035 // else if b is inside the clip window (a->b enters the screen)
5036 } else if (!(b._code & edge)) {
5037 p = lu._getEdgeIntersection(b, a, edge, bounds);
5038 p._code = lu._getBitCode(p, bounds);
5039 clippedPoints.push(p);
5040 }
5041 }
5042 points = clippedPoints;
5043 }
5044
5045 return points;
5046 };
5047
5048
5049 /*
5050 * L.Polygon is used to display polygons on a map.
5051 */
5052
5053 L.Polygon = L.Polyline.extend({
5054 options: {
5055 fill: true
5056 },
5057
5058 initialize: function (latlngs, options) {
5059 L.Polyline.prototype.initialize.call(this, latlngs, options);
5060
5061 if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
5062 this._latlngs = this._convertLatLngs(latlngs[0]);
5063 this._holes = latlngs.slice(1);
5064 }
5065 },
5066
5067 projectLatlngs: function () {
5068 L.Polyline.prototype.projectLatlngs.call(this);
5069
5070 // project polygon holes points
5071 // TODO move this logic to Polyline to get rid of duplication
5072 this._holePoints = [];
5073
5074 if (!this._holes) { return; }
5075
5076 var i, j, len, len2;
5077
5078 for (i = 0, len = this._holes.length; i < len; i++) {
5079 this._holePoints[i] = [];
5080
5081 for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
5082 this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
5083 }
5084 }
5085 },
5086
5087 _clipPoints: function () {
5088 var points = this._originalPoints,
5089 newParts = [];
5090
5091 this._parts = [points].concat(this._holePoints);
5092
5093 if (this.options.noClip) { return; }
5094
5095 for (var i = 0, len = this._parts.length; i < len; i++) {
5096 var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
5097 if (clipped.length) {
5098 newParts.push(clipped);
5099 }
5100 }
5101
5102 this._parts = newParts;
5103 },
5104
5105 _getPathPartStr: function (points) {
5106 var str = L.Polyline.prototype._getPathPartStr.call(this, points);
5107 return str + (L.Browser.svg ? 'z' : 'x');
5108 }
5109 });
5110
5111 L.polygon = function (latlngs, options) {
5112 return new L.Polygon(latlngs, options);
5113 };
5114
5115
5116 /*
5117 * Contains L.MultiPolyline and L.MultiPolygon layers.
5118 */
5119
5120 (function () {
5121 function createMulti(Klass) {
5122
5123 return L.FeatureGroup.extend({
5124
5125 initialize: function (latlngs, options) {
5126 this._layers = {};
5127 this._options = options;
5128 this.setLatLngs(latlngs);
5129 },
5130
5131 setLatLngs: function (latlngs) {
5132 var i = 0,
5133 len = latlngs.length;
5134
5135 this.eachLayer(function (layer) {
5136 if (i < len) {
5137 layer.setLatLngs(latlngs[i++]);
5138 } else {
5139 this.removeLayer(layer);
5140 }
5141 }, this);
5142
5143 while (i < len) {
5144 this.addLayer(new Klass(latlngs[i++], this._options));
5145 }
5146
5147 return this;
5148 }
5149 });
5150 }
5151
5152 L.MultiPolyline = createMulti(L.Polyline);
5153 L.MultiPolygon = createMulti(L.Polygon);
5154
5155 L.multiPolyline = function (latlngs, options) {
5156 return new L.MultiPolyline(latlngs, options);
5157 };
5158
5159 L.multiPolygon = function (latlngs, options) {
5160 return new L.MultiPolygon(latlngs, options);
5161 };
5162 }());
5163
5164
5165 /*
5166 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
5167 */
5168
5169 L.Rectangle = L.Polygon.extend({
5170 initialize: function (latLngBounds, options) {
5171 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
5172 },
5173
5174 setBounds: function (latLngBounds) {
5175 this.setLatLngs(this._boundsToLatLngs(latLngBounds));
5176 },
5177
5178 _boundsToLatLngs: function (latLngBounds) {
5179 latLngBounds = L.latLngBounds(latLngBounds);
5180 return [
5181 latLngBounds.getSouthWest(),
5182 latLngBounds.getNorthWest(),
5183 latLngBounds.getNorthEast(),
5184 latLngBounds.getSouthEast()
5185 ];
5186 }
5187 });
5188
5189 L.rectangle = function (latLngBounds, options) {
5190 return new L.Rectangle(latLngBounds, options);
5191 };
5192
5193
5194 /*
5195 * L.Circle is a circle overlay (with a certain radius in meters).
5196 */
5197
5198 L.Circle = L.Path.extend({
5199 initialize: function (latlng, radius, options) {
5200 L.Path.prototype.initialize.call(this, options);
5201
5202 this._latlng = L.latLng(latlng);
5203 this._mRadius = radius;
5204 },
5205
5206 options: {
5207 fill: true
5208 },
5209
5210 setLatLng: function (latlng) {
5211 this._latlng = L.latLng(latlng);
5212 return this.redraw();
5213 },
5214
5215 setRadius: function (radius) {
5216 this._mRadius = radius;
5217 return this.redraw();
5218 },
5219
5220 projectLatlngs: function () {
5221 var lngRadius = this._getLngRadius(),
5222 latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius),
5223 point2 = this._map.latLngToLayerPoint(latlng2);
5224
5225 this._point = this._map.latLngToLayerPoint(this._latlng);
5226 this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
5227 },
5228
5229 getBounds: function () {
5230 var lngRadius = this._getLngRadius(),
5231 latRadius = (this._mRadius / 40075017) * 360,
5232 latlng = this._latlng,
5233 sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
5234 ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
5235
5236 return new L.LatLngBounds(sw, ne);
5237 },
5238
5239 getLatLng: function () {
5240 return this._latlng;
5241 },
5242
5243 getPathString: function () {
5244 var p = this._point,
5245 r = this._radius;
5246
5247 if (this._checkIfEmpty()) {
5248 return '';
5249 }
5250
5251 if (L.Browser.svg) {
5252 return "M" + p.x + "," + (p.y - r) +
5253 "A" + r + "," + r + ",0,1,1," +
5254 (p.x - 0.1) + "," + (p.y - r) + " z";
5255 } else {
5256 p._round();
5257 r = Math.round(r);
5258 return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
5259 }
5260 },
5261
5262 getRadius: function () {
5263 return this._mRadius;
5264 },
5265
5266 // TODO Earth hardcoded, move into projection code!
5267
5268 _getLatRadius: function () {
5269 return (this._mRadius / 40075017) * 360;
5270 },
5271
5272 _getLngRadius: function () {
5273 return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
5274 },
5275
5276 _checkIfEmpty: function () {
5277 if (!this._map) {
5278 return false;
5279 }
5280 var vp = this._map._pathViewport,
5281 r = this._radius,
5282 p = this._point;
5283
5284 return p.x - r > vp.max.x || p.y - r > vp.max.y ||
5285 p.x + r < vp.min.x || p.y + r < vp.min.y;
5286 }
5287 });
5288
5289 L.circle = function (latlng, radius, options) {
5290 return new L.Circle(latlng, radius, options);
5291 };
5292
5293
5294 /*
5295 * L.CircleMarker is a circle overlay with a permanent pixel radius.
5296 */
5297
5298 L.CircleMarker = L.Circle.extend({
5299 options: {
5300 radius: 10,
5301 weight: 2
5302 },
5303
5304 initialize: function (latlng, options) {
5305 L.Circle.prototype.initialize.call(this, latlng, null, options);
5306 this._radius = this.options.radius;
5307 },
5308
5309 projectLatlngs: function () {
5310 this._point = this._map.latLngToLayerPoint(this._latlng);
5311 },
5312
5313 _updateStyle : function () {
5314 L.Circle.prototype._updateStyle.call(this);
5315 this.setRadius(this.options.radius);
5316 },
5317
5318 setRadius: function (radius) {
5319 this.options.radius = this._radius = radius;
5320 return this.redraw();
5321 }
5322 });
5323
5324 L.circleMarker = function (latlng, options) {
5325 return new L.CircleMarker(latlng, options);
5326 };
5327
5328
5329 /*
5330 * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
5331 */
5332
5333 L.Polyline.include(!L.Path.CANVAS ? {} : {
5334 _containsPoint: function (p, closed) {
5335 var i, j, k, len, len2, dist, part,
5336 w = this.options.weight / 2;
5337
5338 if (L.Browser.touch) {
5339 w += 10; // polyline click tolerance on touch devices
5340 }
5341
5342 for (i = 0, len = this._parts.length; i < len; i++) {
5343 part = this._parts[i];
5344 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
5345 if (!closed && (j === 0)) {
5346 continue;
5347 }
5348
5349 dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
5350
5351 if (dist <= w) {
5352 return true;
5353 }
5354 }
5355 }
5356 return false;
5357 }
5358 });
5359
5360
5361 /*
5362 * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
5363 */
5364
5365 L.Polygon.include(!L.Path.CANVAS ? {} : {
5366 _containsPoint: function (p) {
5367 var inside = false,
5368 part, p1, p2,
5369 i, j, k,
5370 len, len2;
5371
5372 // TODO optimization: check if within bounds first
5373
5374 if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
5375 // click on polygon border
5376 return true;
5377 }
5378
5379 // ray casting algorithm for detecting if point is in polygon
5380
5381 for (i = 0, len = this._parts.length; i < len; i++) {
5382 part = this._parts[i];
5383
5384 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
5385 p1 = part[j];
5386 p2 = part[k];
5387
5388 if (((p1.y > p.y) !== (p2.y > p.y)) &&
5389 (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
5390 inside = !inside;
5391 }
5392 }
5393 }
5394
5395 return inside;
5396 }
5397 });
5398
5399
5400 /*
5401 * Extends L.Circle with Canvas-specific code.
5402 */
5403
5404 L.Circle.include(!L.Path.CANVAS ? {} : {
5405 _drawPath: function () {
5406 var p = this._point;
5407 this._ctx.beginPath();
5408 this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
5409 },
5410
5411 _containsPoint: function (p) {
5412 var center = this._point,
5413 w2 = this.options.stroke ? this.options.weight / 2 : 0;
5414
5415 return (p.distanceTo(center) <= this._radius + w2);
5416 }
5417 });
5418
5419
5420 /*
5421 * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
5422 */
5423
5424 L.GeoJSON = L.FeatureGroup.extend({
5425
5426 initialize: function (geojson, options) {
5427 L.setOptions(this, options);
5428
5429 this._layers = {};
5430
5431 if (geojson) {
5432 this.addData(geojson);
5433 }
5434 },
5435
5436 addData: function (geojson) {
5437 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
5438 i, len;
5439
5440 if (features) {
5441 for (i = 0, len = features.length; i < len; i++) {
5442 // Only add this if geometry or geometries are set and not null
5443 if (features[i].geometries || features[i].geometry || features[i].features) {
5444 this.addData(features[i]);
5445 }
5446 }
5447 return this;
5448 }
5449
5450 var options = this.options;
5451
5452 if (options.filter && !options.filter(geojson)) { return; }
5453
5454 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
5455 layer.feature = geojson;
5456
5457 layer.defaultOptions = layer.options;
5458 this.resetStyle(layer);
5459
5460 if (options.onEachFeature) {
5461 options.onEachFeature(geojson, layer);
5462 }
5463
5464 return this.addLayer(layer);
5465 },
5466
5467 resetStyle: function (layer) {
5468 var style = this.options.style;
5469 if (style) {
5470 // reset any custom styles
5471 L.Util.extend(layer.options, layer.defaultOptions);
5472
5473 this._setLayerStyle(layer, style);
5474 }
5475 },
5476
5477 setStyle: function (style) {
5478 this.eachLayer(function (layer) {
5479 this._setLayerStyle(layer, style);
5480 }, this);
5481 },
5482
5483 _setLayerStyle: function (layer, style) {
5484 if (typeof style === 'function') {
5485 style = style(layer.feature);
5486 }
5487 if (layer.setStyle) {
5488 layer.setStyle(style);
5489 }
5490 }
5491 });
5492
5493 L.extend(L.GeoJSON, {
5494 geometryToLayer: function (geojson, pointToLayer) {
5495 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
5496 coords = geometry.coordinates,
5497 layers = [],
5498 latlng, latlngs, i, len, layer;
5499
5500 switch (geometry.type) {
5501 case 'Point':
5502 latlng = this.coordsToLatLng(coords);
5503 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
5504
5505 case 'MultiPoint':
5506 for (i = 0, len = coords.length; i < len; i++) {
5507 latlng = this.coordsToLatLng(coords[i]);
5508 layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
5509 layers.push(layer);
5510 }
5511 return new L.FeatureGroup(layers);
5512
5513 case 'LineString':
5514 latlngs = this.coordsToLatLngs(coords);
5515 return new L.Polyline(latlngs);
5516
5517 case 'Polygon':
5518 latlngs = this.coordsToLatLngs(coords, 1);
5519 return new L.Polygon(latlngs);
5520
5521 case 'MultiLineString':
5522 latlngs = this.coordsToLatLngs(coords, 1);
5523 return new L.MultiPolyline(latlngs);
5524
5525 case 'MultiPolygon':
5526 latlngs = this.coordsToLatLngs(coords, 2);
5527 return new L.MultiPolygon(latlngs);
5528
5529 case 'GeometryCollection':
5530 for (i = 0, len = geometry.geometries.length; i < len; i++) {
5531 layer = this.geometryToLayer({
5532 geometry: geometry.geometries[i],
5533 type: 'Feature',
5534 properties: geojson.properties
5535 }, pointToLayer);
5536 layers.push(layer);
5537 }
5538 return new L.FeatureGroup(layers);
5539
5540 default:
5541 throw new Error('Invalid GeoJSON object.');
5542 }
5543 },
5544
5545 coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
5546 var lat = parseFloat(coords[reverse ? 0 : 1]),
5547 lng = parseFloat(coords[reverse ? 1 : 0]);
5548
5549 return new L.LatLng(lat, lng);
5550 },
5551
5552 coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
5553 var latlng,
5554 latlngs = [],
5555 i, len;
5556
5557 for (i = 0, len = coords.length; i < len; i++) {
5558 latlng = levelsDeep ?
5559 this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
5560 this.coordsToLatLng(coords[i], reverse);
5561
5562 latlngs.push(latlng);
5563 }
5564
5565 return latlngs;
5566 }
5567 });
5568
5569 L.geoJson = function (geojson, options) {
5570 return new L.GeoJSON(geojson, options);
5571 };
5572
5573
5574 /*
5575 * L.DomEvent contains functions for working with DOM events.
5576 */
5577
5578 L.DomEvent = {
5579 /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
5580 addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
5581
5582 var id = L.stamp(fn),
5583 key = '_leaflet_' + type + id,
5584 handler, originalHandler, newType;
5585
5586 if (obj[key]) { return this; }
5587
5588 handler = function (e) {
5589 return fn.call(context || obj, e || L.DomEvent._getEvent());
5590 };
5591
5592 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
5593 return this.addMsTouchListener(obj, type, handler, id);
5594 }
5595 if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
5596 this.addDoubleTapListener(obj, handler, id);
5597 }
5598
5599 if ('addEventListener' in obj) {
5600
5601 if (type === 'mousewheel') {
5602 obj.addEventListener('DOMMouseScroll', handler, false);
5603 obj.addEventListener(type, handler, false);
5604
5605 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
5606
5607 originalHandler = handler;
5608 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
5609
5610 handler = function (e) {
5611 if (!L.DomEvent._checkMouse(obj, e)) { return; }
5612 return originalHandler(e);
5613 };
5614
5615 obj.addEventListener(newType, handler, false);
5616
5617 } else {
5618 obj.addEventListener(type, handler, false);
5619 }
5620
5621 } else if ('attachEvent' in obj) {
5622 obj.attachEvent("on" + type, handler);
5623 }
5624
5625 obj[key] = handler;
5626
5627 return this;
5628 },
5629
5630 removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
5631
5632 var id = L.stamp(fn),
5633 key = '_leaflet_' + type + id,
5634 handler = obj[key];
5635
5636 if (!handler) { return; }
5637
5638 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
5639 this.removeMsTouchListener(obj, type, id);
5640 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
5641 this.removeDoubleTapListener(obj, id);
5642
5643 } else if ('removeEventListener' in obj) {
5644
5645 if (type === 'mousewheel') {
5646 obj.removeEventListener('DOMMouseScroll', handler, false);
5647 obj.removeEventListener(type, handler, false);
5648
5649 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
5650 obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
5651 } else {
5652 obj.removeEventListener(type, handler, false);
5653 }
5654 } else if ('detachEvent' in obj) {
5655 obj.detachEvent("on" + type, handler);
5656 }
5657
5658 obj[key] = null;
5659
5660 return this;
5661 },
5662
5663 stopPropagation: function (e) {
5664
5665 if (e.stopPropagation) {
5666 e.stopPropagation();
5667 } else {
5668 e.cancelBubble = true;
5669 }
5670 return this;
5671 },
5672
5673 disableClickPropagation: function (el) {
5674
5675 var stop = L.DomEvent.stopPropagation;
5676
5677 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
5678 L.DomEvent.addListener(el, L.Draggable.START[i], stop);
5679 }
5680
5681 return L.DomEvent
5682 .addListener(el, 'click', stop)
5683 .addListener(el, 'dblclick', stop);
5684 },
5685
5686 preventDefault: function (e) {
5687
5688 if (e.preventDefault) {
5689 e.preventDefault();
5690 } else {
5691 e.returnValue = false;
5692 }
5693 return this;
5694 },
5695
5696 stop: function (e) {
5697 return L.DomEvent.preventDefault(e).stopPropagation(e);
5698 },
5699
5700 getMousePosition: function (e, container) {
5701
5702 var body = document.body,
5703 docEl = document.documentElement,
5704 x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
5705 y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
5706 pos = new L.Point(x, y);
5707
5708 return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
5709 },
5710
5711 getWheelDelta: function (e) {
5712
5713 var delta = 0;
5714
5715 if (e.wheelDelta) {
5716 delta = e.wheelDelta / 120;
5717 }
5718 if (e.detail) {
5719 delta = -e.detail / 3;
5720 }
5721 return delta;
5722 },
5723
5724 // check if element really left/entered the event target (for mouseenter/mouseleave)
5725 _checkMouse: function (el, e) {
5726
5727 var related = e.relatedTarget;
5728
5729 if (!related) { return true; }
5730
5731 try {
5732 while (related && (related !== el)) {
5733 related = related.parentNode;
5734 }
5735 } catch (err) {
5736 return false;
5737 }
5738 return (related !== el);
5739 },
5740
5741 _getEvent: function () { // evil magic for IE
5742 /*jshint noarg:false */
5743 var e = window.event;
5744 if (!e) {
5745 var caller = arguments.callee.caller;
5746 while (caller) {
5747 e = caller['arguments'][0];
5748 if (e && window.Event === e.constructor) {
5749 break;
5750 }
5751 caller = caller.caller;
5752 }
5753 }
5754 return e;
5755 }
5756 };
5757
5758 L.DomEvent.on = L.DomEvent.addListener;
5759 L.DomEvent.off = L.DomEvent.removeListener;
5760
5761
5762 /*
5763 * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
5764 */
5765
5766 L.Draggable = L.Class.extend({
5767 includes: L.Mixin.Events,
5768
5769 statics: {
5770 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
5771 END: {
5772 mousedown: 'mouseup',
5773 touchstart: 'touchend',
5774 MSPointerDown: 'touchend'
5775 },
5776 MOVE: {
5777 mousedown: 'mousemove',
5778 touchstart: 'touchmove',
5779 MSPointerDown: 'touchmove'
5780 },
5781 TAP_TOLERANCE: 15
5782 },
5783
5784 initialize: function (element, dragStartTarget, longPress) {
5785 this._element = element;
5786 this._dragStartTarget = dragStartTarget || element;
5787 this._longPress = longPress && !L.Browser.msTouch;
5788 },
5789
5790 enable: function () {
5791 if (this._enabled) { return; }
5792
5793 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
5794 L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
5795 }
5796 this._enabled = true;
5797 },
5798
5799 disable: function () {
5800 if (!this._enabled) { return; }
5801
5802 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
5803 L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
5804 }
5805 this._enabled = false;
5806 this._moved = false;
5807 },
5808
5809 _onDown: function (e) {
5810 if ((!L.Browser.touch && e.shiftKey) ||
5811 ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5812
5813 L.DomEvent.preventDefault(e);
5814 L.DomEvent.stopPropagation(e);
5815
5816 if (L.Draggable._disabled) { return; }
5817
5818 this._simulateClick = true;
5819
5820 if (e.touches && e.touches.length > 1) {
5821 this._simulateClick = false;
5822 clearTimeout(this._longPressTimeout);
5823 return;
5824 }
5825
5826 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5827 el = first.target;
5828
5829 if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
5830 L.DomUtil.addClass(el, 'leaflet-active');
5831 }
5832
5833 this._moved = false;
5834 if (this._moving) { return; }
5835
5836 this._startPoint = new L.Point(first.clientX, first.clientY);
5837 this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
5838
5839 //Touch contextmenu event emulation
5840 if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) {
5841 this._longPressTimeout = setTimeout(L.bind(function () {
5842 var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
5843
5844 if (dist < L.Draggable.TAP_TOLERANCE) {
5845 this._simulateClick = false;
5846 this._onUp();
5847 this._simulateEvent('contextmenu', first);
5848 }
5849 }, this), 1000);
5850 }
5851
5852 L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this);
5853 L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this);
5854 },
5855
5856 _onMove: function (e) {
5857 if (e.touches && e.touches.length > 1) { return; }
5858
5859 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5860 newPoint = new L.Point(first.clientX, first.clientY),
5861 diffVec = newPoint.subtract(this._startPoint);
5862
5863 if (!diffVec.x && !diffVec.y) { return; }
5864
5865 L.DomEvent.preventDefault(e);
5866
5867 if (!this._moved) {
5868 this.fire('dragstart');
5869 this._moved = true;
5870
5871 this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec);
5872
5873 if (!L.Browser.touch) {
5874 L.DomUtil.disableTextSelection();
5875 this._setMovingCursor();
5876 }
5877 }
5878
5879 this._newPos = this._startPos.add(diffVec);
5880 this._moving = true;
5881
5882 L.Util.cancelAnimFrame(this._animRequest);
5883 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
5884 },
5885
5886 _updatePosition: function () {
5887 this.fire('predrag');
5888 L.DomUtil.setPosition(this._element, this._newPos);
5889 this.fire('drag');
5890 },
5891
5892 _onUp: function (e) {
5893 var simulateClickTouch;
5894 clearTimeout(this._longPressTimeout);
5895 if (this._simulateClick && e.changedTouches) {
5896 var first = e.changedTouches[0],
5897 el = first.target,
5898 dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
5899
5900 if (el.tagName.toLowerCase() === 'a') {
5901 L.DomUtil.removeClass(el, 'leaflet-active');
5902 }
5903
5904 if (dist < L.Draggable.TAP_TOLERANCE) {
5905 simulateClickTouch = first;
5906 }
5907 }
5908
5909 if (!L.Browser.touch) {
5910 L.DomUtil.enableTextSelection();
5911 this._restoreCursor();
5912 }
5913
5914 for (var i in L.Draggable.MOVE) {
5915 if (L.Draggable.MOVE.hasOwnProperty(i)) {
5916 L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove);
5917 L.DomEvent.off(document, L.Draggable.END[i], this._onUp);
5918 }
5919 }
5920
5921 if (this._moved) {
5922 // ensure drag is not fired after dragend
5923 L.Util.cancelAnimFrame(this._animRequest);
5924
5925 this.fire('dragend');
5926 }
5927 this._moving = false;
5928
5929 if (simulateClickTouch) {
5930 this._moved = false;
5931 this._simulateEvent('click', simulateClickTouch);
5932 }
5933 },
5934
5935 _setMovingCursor: function () {
5936 L.DomUtil.addClass(document.body, 'leaflet-dragging');
5937 },
5938
5939 _restoreCursor: function () {
5940 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
5941 },
5942
5943 _simulateEvent: function (type, e) {
5944 var simulatedEvent = document.createEvent('MouseEvents');
5945
5946 simulatedEvent.initMouseEvent(
5947 type, true, true, window, 1,
5948 e.screenX, e.screenY,
5949 e.clientX, e.clientY,
5950 false, false, false, false, 0, null);
5951
5952 e.target.dispatchEvent(simulatedEvent);
5953 }
5954 });
5919
5920 /*
5921 * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
5922 */
5923
5924 L.GeoJSON = L.FeatureGroup.extend({
5925
5926 initialize: function (geojson, options) {
5927 L.setOptions(this, options);
5928
5929 this._layers = {};
5930
5931 if (geojson) {
5932 this.addData(geojson);
5933 }
5934 },
5935
5936 addData: function (geojson) {
5937 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
5938 i, len;
5939
5940 if (features) {
5941 for (i = 0, len = features.length; i < len; i++) {
5942 // Only add this if geometry or geometries are set and not null
5943 if (features[i].geometries || features[i].geometry || features[i].features) {
5944 this.addData(features[i]);
5945 }
5946 }
5947 return this;
5948 }
5949
5950 var options = this.options;
5951
5952 if (options.filter && !options.filter(geojson)) { return; }
5953
5954 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
5955 layer.feature = L.GeoJSON.asFeature(geojson);
5956
5957 layer.defaultOptions = layer.options;
5958 this.resetStyle(layer);
5959
5960 if (options.onEachFeature) {
5961 options.onEachFeature(geojson, layer);
5962 }
5963
5964 return this.addLayer(layer);
5965 },
5966
5967 resetStyle: function (layer) {
5968 var style = this.options.style;
5969 if (style) {
5970 // reset any custom styles
5971 L.Util.extend(layer.options, layer.defaultOptions);
5972
5973 this._setLayerStyle(layer, style);
5974 }
5975 },
5976
5977 setStyle: function (style) {
5978 this.eachLayer(function (layer) {
5979 this._setLayerStyle(layer, style);
5980 }, this);
5981 },
5982
5983 _setLayerStyle: function (layer, style) {
5984 if (typeof style === 'function') {
5985 style = style(layer.feature);
5986 }
5987 if (layer.setStyle) {
5988 layer.setStyle(style);
5989 }
5990 }
5991 });
5992
5993 L.extend(L.GeoJSON, {
5994 geometryToLayer: function (geojson, pointToLayer, coordsToLatLng) {
5995 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
5996 coords = geometry.coordinates,
5997 layers = [],
5998 latlng, latlngs, i, len, layer;
5999
6000 coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
6001
6002 switch (geometry.type) {
6003 case 'Point':
6004 latlng = coordsToLatLng(coords);
6005 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
6006
6007 case 'MultiPoint':
6008 for (i = 0, len = coords.length; i < len; i++) {
6009 latlng = coordsToLatLng(coords[i]);
6010 layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
6011 layers.push(layer);
6012 }
6013 return new L.FeatureGroup(layers);
6014
6015 case 'LineString':
6016 latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
6017 return new L.Polyline(latlngs);
6018
6019 case 'Polygon':
6020 latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
6021 return new L.Polygon(latlngs);
6022
6023 case 'MultiLineString':
6024 latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
6025 return new L.MultiPolyline(latlngs);
6026
6027 case 'MultiPolygon':
6028 latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
6029 return new L.MultiPolygon(latlngs);
6030
6031 case 'GeometryCollection':
6032 for (i = 0, len = geometry.geometries.length; i < len; i++) {
6033
6034 layer = this.geometryToLayer({
6035 geometry: geometry.geometries[i],
6036 type: 'Feature',
6037 properties: geojson.properties
6038 }, pointToLayer, coordsToLatLng);
6039
6040 layers.push(layer);
6041 }
6042 return new L.FeatureGroup(layers);
6043
6044 default:
6045 throw new Error('Invalid GeoJSON object.');
6046 }
6047 },
6048
6049 coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
6050 return new L.LatLng(coords[1], coords[0]);
6051 },
6052
6053 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
6054 var latlng, i, len,
6055 latlngs = [];
6056
6057 for (i = 0, len = coords.length; i < len; i++) {
6058 latlng = levelsDeep ?
6059 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
6060 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
6061
6062 latlngs.push(latlng);
6063 }
6064
6065 return latlngs;
6066 },
6067
6068 latLngToCoords: function (latLng) {
6069 return [latLng.lng, latLng.lat];
6070 },
6071
6072 latLngsToCoords: function (latLngs) {
6073 var coords = [];
6074
6075 for (var i = 0, len = latLngs.length; i < len; i++) {
6076 coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));
6077 }
6078
6079 return coords;
6080 },
6081
6082 getFeature: function (layer, newGeometry) {
6083 return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);
6084 },
6085
6086 asFeature: function (geoJSON) {
6087 if (geoJSON.type === 'Feature') {
6088 return geoJSON;
6089 }
6090
6091 return {
6092 type: 'Feature',
6093 properties: {},
6094 geometry: geoJSON
6095 };
6096 }
6097 });
6098
6099 var PointToGeoJSON = {
6100 toGeoJSON: function () {
6101 return L.GeoJSON.getFeature(this, {
6102 type: 'Point',
6103 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
6104 });
6105 }
6106 };
6107
6108 L.Marker.include(PointToGeoJSON);
6109 L.Circle.include(PointToGeoJSON);
6110 L.CircleMarker.include(PointToGeoJSON);
6111
6112 L.Polyline.include({
6113 toGeoJSON: function () {
6114 return L.GeoJSON.getFeature(this, {
6115 type: 'LineString',
6116 coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())
6117 });
6118 }
6119 });
6120
6121 L.Polygon.include({
6122 toGeoJSON: function () {
6123 var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],
6124 i, len, hole;
6125
6126 coords[0].push(coords[0][0]);
6127
6128 if (this._holes) {
6129 for (i = 0, len = this._holes.length; i < len; i++) {
6130 hole = L.GeoJSON.latLngsToCoords(this._holes[i]);
6131 hole.push(hole[0]);
6132 coords.push(hole);
6133 }
6134 }
6135
6136 return L.GeoJSON.getFeature(this, {
6137 type: 'Polygon',
6138 coordinates: coords
6139 });
6140 }
6141 });
6142
6143 (function () {
6144 function includeMulti(Klass, type) {
6145 Klass.include({
6146 toGeoJSON: function () {
6147 var coords = [];
6148
6149 this.eachLayer(function (layer) {
6150 coords.push(layer.toGeoJSON().geometry.coordinates);
6151 });
6152
6153 return L.GeoJSON.getFeature(this, {
6154 type: type,
6155 coordinates: coords
6156 });
6157 }
6158 });
6159 }
6160
6161 includeMulti(L.MultiPolyline, 'MultiLineString');
6162 includeMulti(L.MultiPolygon, 'MultiPolygon');
6163 }());
6164
6165 L.LayerGroup.include({
6166 toGeoJSON: function () {
6167 var features = [];
6168
6169 this.eachLayer(function (layer) {
6170 if (layer.toGeoJSON) {
6171 features.push(L.GeoJSON.asFeature(layer.toGeoJSON()));
6172 }
6173 });
6174
6175 return {
6176 type: 'FeatureCollection',
6177 features: features
6178 };
6179 }
6180 });
6181
6182 L.geoJson = function (geojson, options) {
6183 return new L.GeoJSON(geojson, options);
6184 };
6185
6186
6187 /*
6188 * L.DomEvent contains functions for working with DOM events.
6189 */
6190
6191 L.DomEvent = {
6192 WHEEL:
6193 'onwheel' in document ? 'wheel' :
6194 'onmousewheel' in document ? 'mousewheel' :
6195 'MozMousePixelScroll',
6196
6197 /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
6198 addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
6199
6200 var id = L.stamp(fn),
6201 key = '_leaflet_' + type + id,
6202 handler, originalHandler, newType;
6203
6204 if (obj[key]) { return this; }
6205
6206 handler = function (e) {
6207 return fn.call(context || obj, e || L.DomEvent._getEvent());
6208 };
6209
6210 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6211 return this.addMsTouchListener(obj, type, handler, id);
6212 }
6213 if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
6214 this.addDoubleTapListener(obj, handler, id);
6215 }
6216
6217 if (type === 'wheel' || type === 'mousewheel') {
6218 type = L.DomEvent.WHEEL;
6219 }
6220
6221 if ('addEventListener' in obj) {
6222
6223 if ((type === 'mouseenter') || (type === 'mouseleave')) {
6224
6225 originalHandler = handler;
6226 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
6227
6228 handler = function (e) {
6229 if (!L.DomEvent._checkMouse(obj, e)) { return; }
6230 return originalHandler(e);
6231 };
6232
6233 obj.addEventListener(newType, handler, false);
6234
6235 } else if (type === 'click' && L.Browser.android) {
6236 originalHandler = handler;
6237 handler = function (e) {
6238 return L.DomEvent._filterClick(e, originalHandler);
6239 };
6240
6241 obj.addEventListener(type, handler, false);
6242 } else {
6243 obj.addEventListener(type, handler, false);
6244 }
6245
6246 } else if ('attachEvent' in obj) {
6247 obj.attachEvent('on' + type, handler);
6248 }
6249
6250 obj[key] = handler;
6251
6252 return this;
6253 },
6254
6255 removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
6256
6257 var id = L.stamp(fn),
6258 key = '_leaflet_' + type + id,
6259 handler = obj[key];
6260
6261 if (!handler) { return this; }
6262
6263 if (type === 'wheel' || type === 'mousewheel') {
6264 type = L.DomEvent.WHEEL;
6265 }
6266
6267 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6268 this.removeMsTouchListener(obj, type, id);
6269 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
6270 this.removeDoubleTapListener(obj, id);
6271
6272 } else if ('removeEventListener' in obj) {
6273
6274 if ((type === 'mouseenter') || (type === 'mouseleave')) {
6275 obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
6276 } else {
6277 obj.removeEventListener(type, handler, false);
6278 }
6279 } else if ('detachEvent' in obj) {
6280 obj.detachEvent('on' + type, handler);
6281 }
6282
6283 obj[key] = null;
6284
6285 return this;
6286 },
6287
6288 stopPropagation: function (e) {
6289
6290 if (e.stopPropagation) {
6291 e.stopPropagation();
6292 } else {
6293 e.cancelBubble = true;
6294 }
6295 return this;
6296 },
6297
6298 disableClickPropagation: function (el) {
6299 var stop = L.DomEvent.stopPropagation;
6300
6301 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6302 L.DomEvent.addListener(el, L.Draggable.START[i], stop);
6303 }
6304
6305 return L.DomEvent
6306 .addListener(el, 'click', L.DomEvent._fakeStop)
6307 .addListener(el, 'dblclick', stop);
6308 },
6309
6310 preventDefault: function (e) {
6311
6312 if (e.preventDefault) {
6313 e.preventDefault();
6314 } else {
6315 e.returnValue = false;
6316 }
6317 return this;
6318 },
6319
6320 stop: function (e) {
6321 return L.DomEvent.preventDefault(e).stopPropagation(e);
6322 },
6323
6324 getMousePosition: function (e, container) {
6325
6326 var body = document.body,
6327 docEl = document.documentElement,
6328 x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
6329 y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
6330 pos = new L.Point(x, y);
6331
6332 return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
6333 },
6334
6335 getWheelDelta: function (e) {
6336 var delta = 0;
6337
6338 if (e.type === 'wheel') {
6339 delta = -e.deltaY / (e.deltaMode ? 1 : 120);
6340 } else if (e.type === 'mousewheel') {
6341 delta = e.wheelDelta / 120;
6342 } else if (e.type === 'MozMousePixelScroll') {
6343 delta = -e.detail;
6344 }
6345
6346 return delta;
6347 },
6348
6349 _fakeStop: function stop(e) {
6350 // fakes stopPropagation by setting a special event flag checked in Map mouse events handler
6351 // jshint camelcase: false
6352 e._leaflet_stop = true;
6353 },
6354
6355 // check if element really left/entered the event target (for mouseenter/mouseleave)
6356 _checkMouse: function (el, e) {
6357
6358 var related = e.relatedTarget;
6359
6360 if (!related) { return true; }
6361
6362 try {
6363 while (related && (related !== el)) {
6364 related = related.parentNode;
6365 }
6366 } catch (err) {
6367 return false;
6368 }
6369 return (related !== el);
6370 },
6371
6372 _getEvent: function () { // evil magic for IE
6373 /*jshint noarg:false */
6374 var e = window.event;
6375 if (!e) {
6376 var caller = arguments.callee.caller;
6377 while (caller) {
6378 e = caller['arguments'][0];
6379 if (e && window.Event === e.constructor) {
6380 break;
6381 }
6382 caller = caller.caller;
6383 }
6384 }
6385 return e;
6386 },
6387
6388 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
6389 _filterClick: function (e, handler) {
6390 var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
6391 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
6392
6393 // are they closer together than 1000ms yet more than 100ms?
6394 // Android typically triggers them ~300ms apart while multiple listeners
6395 // on the same event should be triggered far faster;
6396 // or check if click is simulated on the element, and if it is, reject any non-simulated events
6397
6398 if ((elapsed && elapsed > 100 && elapsed < 1000) || (e.target._simulatedClick && !e._simulated)) {
6399 L.DomEvent.stop(e);
6400 return;
6401 }
6402 L.DomEvent._lastClick = timeStamp;
6403
6404 return handler(e);
6405 }
6406 };
6407
6408 L.DomEvent.on = L.DomEvent.addListener;
6409 L.DomEvent.off = L.DomEvent.removeListener;
6410
6411
6412 /*
6413 * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
6414 */
6415
6416 L.Draggable = L.Class.extend({
6417 includes: L.Mixin.Events,
6418
6419 statics: {
6420 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
6421 END: {
6422 mousedown: 'mouseup',
6423 touchstart: 'touchend',
6424 MSPointerDown: 'touchend'
6425 },
6426 MOVE: {
6427 mousedown: 'mousemove',
6428 touchstart: 'touchmove',
6429 MSPointerDown: 'touchmove'
6430 }
6431 },
6432
6433 initialize: function (element, dragStartTarget) {
6434 this._element = element;
6435 this._dragStartTarget = dragStartTarget || element;
6436 },
6437
6438 enable: function () {
6439 if (this._enabled) { return; }
6440
6441 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6442 L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
6443 }
6444
6445 this._enabled = true;
6446 },
6447
6448 disable: function () {
6449 if (!this._enabled) { return; }
6450
6451 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6452 L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
6453 }
6454
6455 this._enabled = false;
6456 this._moved = false;
6457 },
6458
6459 _onDown: function (e) {
6460 if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
6461
6462 L.DomEvent
6463 .stopPropagation(e);
6464
6465 if (L.Draggable._disabled) { return; }
6466
6467 L.DomUtil.disableImageDrag();
6468
6469 var first = e.touches ? e.touches[0] : e,
6470 el = first.target;
6471
6472 // if touching a link, highlight it
6473 if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
6474 L.DomUtil.addClass(el, 'leaflet-active');
6475 }
6476
6477 this._moved = false;
6478
6479 if (this._moving) { return; }
6480
6481 this._startPoint = new L.Point(first.clientX, first.clientY);
6482 this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
6483
6484 L.DomEvent
6485 .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
6486 .on(document, L.Draggable.END[e.type], this._onUp, this);
6487 },
6488
6489 _onMove: function (e) {
6490 if (e.touches && e.touches.length > 1) { return; }
6491
6492 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
6493 newPoint = new L.Point(first.clientX, first.clientY),
6494 offset = newPoint.subtract(this._startPoint);
6495
6496 if (!offset.x && !offset.y) { return; }
6497
6498 L.DomEvent.preventDefault(e);
6499
6500 if (!this._moved) {
6501 this.fire('dragstart');
6502
6503 this._moved = true;
6504 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
6505
6506 if (!L.Browser.touch) {
6507 L.DomUtil.disableTextSelection();
6508 L.DomUtil.addClass(document.body, 'leaflet-dragging');
6509 }
6510 }
6511
6512 this._newPos = this._startPos.add(offset);
6513 this._moving = true;
6514
6515 L.Util.cancelAnimFrame(this._animRequest);
6516 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
6517 },
6518
6519 _updatePosition: function () {
6520 this.fire('predrag');
6521 L.DomUtil.setPosition(this._element, this._newPos);
6522 this.fire('drag');
6523 },
6524
6525 _onUp: function () {
6526 if (!L.Browser.touch) {
6527 L.DomUtil.enableTextSelection();
6528 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
6529 }
6530
6531 for (var i in L.Draggable.MOVE) {
6532 L.DomEvent
6533 .off(document, L.Draggable.MOVE[i], this._onMove)
6534 .off(document, L.Draggable.END[i], this._onUp);
6535 }
6536
6537 L.DomUtil.enableImageDrag();
6538
6539 if (this._moved) {
6540 // ensure drag is not fired after dragend
6541 L.Util.cancelAnimFrame(this._animRequest);
6542
6543 this.fire('dragend');
6544 }
6545
6546 this._moving = false;
6547 }
6548 });
59556549
59566550
59576551 /*
59976591 inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
59986592 easeLinearity: 0.25,
59996593
6000 longPress: true,
6001
60026594 // TODO refactor, move to CRS
60036595 worldCopyJump: false
60046596 });
60086600 if (!this._draggable) {
60096601 var map = this._map;
60106602
6011 this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress);
6603 this._draggable = new L.Draggable(map._mapPane, map._container);
60126604
60136605 this._draggable.on({
60146606 'dragstart': this._onDragStart,
60716663 _onViewReset: function () {
60726664 // TODO fix hardcoded Earth values
60736665 var pxCenter = this._map.getSize()._divideBy(2),
6074 pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
6666 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
60756667
60766668 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
6077 this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
6669 this._worldWidth = this._map.project([0, 180]).x;
60786670 },
60796671
60806672 _onPreDrag: function () {
60976689
60986690 noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
60996691
6692 map.fire('dragend');
6693
61006694 if (noInertia) {
61016695 map.fire('moveend');
61026696
61076701 ease = options.easeLinearity,
61086702
61096703 speedVector = direction.multiplyBy(ease / duration),
6110 speed = speedVector.distanceTo(new L.Point(0, 0)),
6704 speed = speedVector.distanceTo([0, 0]),
61116705
61126706 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
61136707 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
61156709 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
61166710 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
61176711
6118 L.Util.requestAnimFrame(function () {
6119 map.panBy(offset, decelerationDuration, ease);
6120 });
6712 if (!offset.x || !offset.y) {
6713 map.fire('moveend');
6714
6715 } else {
6716 L.Util.requestAnimFrame(function () {
6717 map.panBy(offset, {
6718 duration: decelerationDuration,
6719 easeLinearity: ease,
6720 noMoveStart: true
6721 });
6722 });
6723 }
61216724 }
6122
6123 map.fire('dragend');
6124
6125 if (options.maxBounds) {
6126 // TODO predrag validation instead of animation
6127 L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
6128 }
6129 },
6130
6131 _panInsideMaxBounds: function () {
6132 this.panInsideBounds(this.options.maxBounds);
61336725 }
61346726 });
61356727
61546746 },
61556747
61566748 _onDoubleClick: function (e) {
6157 this.setView(e.latlng, this._zoom + 1);
6749 this.setZoomAround(e.containerPoint, this._zoom + 1);
61586750 }
61596751 });
61606752
61716763
61726764 L.Map.ScrollWheelZoom = L.Handler.extend({
61736765 addHooks: function () {
6174 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
6766 L.DomEvent.on(this._map._container, 'wheel', this._onWheelScroll, this);
61756767 this._delta = 0;
61766768 },
61776769
61786770 removeHooks: function () {
6179 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
6771 L.DomEvent.off(this._map._container, 'wheel', this._onWheelScroll);
61806772 },
61816773
61826774 _onWheelScroll: function (e) {
62036795 delta = this._delta,
62046796 zoom = map.getZoom();
62056797
6206 delta = delta > 0 ? Math.ceil(delta) : Math.round(delta);
6798 delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
62076799 delta = Math.max(Math.min(delta, 4), -4);
62086800 delta = map._limitZoom(zoom + delta) - zoom;
62096801
62106802 this._delta = 0;
6211
62126803 this._startTime = null;
62136804
62146805 if (!delta) { return; }
62156806
6216 var newZoom = zoom + delta,
6217 newCenter = this._getCenterForScrollWheelZoom(newZoom);
6218
6219 map.setView(newCenter, newZoom);
6220 },
6221
6222 _getCenterForScrollWheelZoom: function (newZoom) {
6223 var map = this._map,
6224 scale = map.getZoomScale(newZoom),
6225 viewHalf = map.getSize()._divideBy(2),
6226 centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
6227 newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
6228
6229 return map.unproject(newCenterPoint);
6807 map.setZoomAround(this._lastMousePos, zoom + delta);
62306808 }
62316809 });
62326810
62336811 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
62346812
62356813
6236 /*
6237 * Extends the event handling code with double tap support for mobile browsers.
6238 */
6239
6240 L.extend(L.DomEvent, {
6241
6242 _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
6243 _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
6244
6245 // inspired by Zepto touch code by Thomas Fuchs
6246 addDoubleTapListener: function (obj, handler, id) {
6247 var last,
6248 doubleTap = false,
6249 delay = 250,
6250 touch,
6251 pre = '_leaflet_',
6252 touchstart = this._touchstart,
6253 touchend = this._touchend,
6254 trackedTouches = [];
6255
6256 function onTouchStart(e) {
6257 var count;
6258 if (L.Browser.msTouch) {
6259 trackedTouches.push(e.pointerId);
6260 count = trackedTouches.length;
6261 } else {
6262 count = e.touches.length;
6263 }
6264 if (count > 1) {
6265 return;
6266 }
6267
6268 var now = Date.now(),
6269 delta = now - (last || now);
6270
6271 touch = e.touches ? e.touches[0] : e;
6272 doubleTap = (delta > 0 && delta <= delay);
6273 last = now;
6274 }
6275
6276 function onTouchEnd(e) {
6277 /*jshint forin:false */
6278 if (L.Browser.msTouch) {
6279 var idx = trackedTouches.indexOf(e.pointerId);
6280 if (idx === -1) {
6281 return;
6282 }
6283 trackedTouches.splice(idx, 1);
6284 }
6285
6286 if (doubleTap) {
6287 if (L.Browser.msTouch) {
6288 //Work around .type being readonly with MSPointer* events
6289 var newTouch = { },
6290 prop;
6291
6292 for (var i in touch) {
6293 prop = touch[i];
6294 if (typeof prop === 'function') {
6295 newTouch[i] = prop.bind(touch);
6296 } else {
6297 newTouch[i] = prop;
6298 }
6299 }
6300 touch = newTouch;
6301 }
6302 touch.type = 'dblclick';
6303 handler(touch);
6304 last = null;
6305 }
6306 }
6307 obj[pre + touchstart + id] = onTouchStart;
6308 obj[pre + touchend + id] = onTouchEnd;
6309
6310 //On msTouch we need to listen on the document otherwise a drag starting on the map and moving off screen will not come through to us
6311 // so we will lose track of how many touches are ongoing
6312 var endElement = L.Browser.msTouch ? document.documentElement : obj;
6313
6314 obj.addEventListener(touchstart, onTouchStart, false);
6315 endElement.addEventListener(touchend, onTouchEnd, false);
6316 if (L.Browser.msTouch) {
6317 endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
6318 }
6319 return this;
6320 },
6321
6322 removeDoubleTapListener: function (obj, id) {
6323 var pre = '_leaflet_';
6324 obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
6325 (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false);
6326 if (L.Browser.msTouch) {
6327 document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
6328 }
6329 return this;
6330 }
6331 });
6814 /*
6815 * Extends the event handling code with double tap support for mobile browsers.
6816 */
6817
6818 L.extend(L.DomEvent, {
6819
6820 _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
6821 _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
6822
6823 // inspired by Zepto touch code by Thomas Fuchs
6824 addDoubleTapListener: function (obj, handler, id) {
6825 var last,
6826 doubleTap = false,
6827 delay = 250,
6828 touch,
6829 pre = '_leaflet_',
6830 touchstart = this._touchstart,
6831 touchend = this._touchend,
6832 trackedTouches = [];
6833
6834 function onTouchStart(e) {
6835 var count;
6836
6837 if (L.Browser.msTouch) {
6838 trackedTouches.push(e.pointerId);
6839 count = trackedTouches.length;
6840 } else {
6841 count = e.touches.length;
6842 }
6843 if (count > 1) {
6844 return;
6845 }
6846
6847 var now = Date.now(),
6848 delta = now - (last || now);
6849
6850 touch = e.touches ? e.touches[0] : e;
6851 doubleTap = (delta > 0 && delta <= delay);
6852 last = now;
6853 }
6854
6855 function onTouchEnd(e) {
6856 if (L.Browser.msTouch) {
6857 var idx = trackedTouches.indexOf(e.pointerId);
6858 if (idx === -1) {
6859 return;
6860 }
6861 trackedTouches.splice(idx, 1);
6862 }
6863
6864 if (doubleTap) {
6865 if (L.Browser.msTouch) {
6866 // work around .type being readonly with MSPointer* events
6867 var newTouch = { },
6868 prop;
6869
6870 // jshint forin:false
6871 for (var i in touch) {
6872 prop = touch[i];
6873 if (typeof prop === 'function') {
6874 newTouch[i] = prop.bind(touch);
6875 } else {
6876 newTouch[i] = prop;
6877 }
6878 }
6879 touch = newTouch;
6880 }
6881 touch.type = 'dblclick';
6882 handler(touch);
6883 last = null;
6884 }
6885 }
6886 obj[pre + touchstart + id] = onTouchStart;
6887 obj[pre + touchend + id] = onTouchEnd;
6888
6889 // on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen
6890 // will not come through to us, so we will lose track of how many touches are ongoing
6891 var endElement = L.Browser.msTouch ? document.documentElement : obj;
6892
6893 obj.addEventListener(touchstart, onTouchStart, false);
6894 endElement.addEventListener(touchend, onTouchEnd, false);
6895
6896 if (L.Browser.msTouch) {
6897 endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
6898 }
6899
6900 return this;
6901 },
6902
6903 removeDoubleTapListener: function (obj, id) {
6904 var pre = '_leaflet_';
6905
6906 obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
6907 (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(
6908 this._touchend, obj[pre + this._touchend + id], false);
6909
6910 if (L.Browser.msTouch) {
6911 document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
6912 }
6913
6914 return this;
6915 }
6916 });
63326917
63336918
63346919 /*
65257110 },
65267111
65277112 _onTouchMove: function (e) {
6528 if (!e.touches || e.touches.length !== 2) { return; }
6529
65307113 var map = this._map;
7114
7115 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
65317116
65327117 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
65337118 p2 = map.mouseEventToLayerPoint(e.touches[1]);
65387123 if (this._scale === 1) { return; }
65397124
65407125 if (!this._moved) {
6541 L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
7126 L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
65427127
65437128 map
65447129 .fire('movestart')
6545 .fire('zoomstart')
6546 ._prepareTileBg();
7130 .fire('zoomstart');
65477131
65487132 this._moved = true;
65497133 }
65587142 _updateOnMove: function () {
65597143 var map = this._map,
65607144 origin = this._getScaleOrigin(),
6561 center = map.layerPointToLatLng(origin);
6562
6563 map.fire('zoomanim', {
6564 center: center,
6565 zoom: map.getScaleZoom(this._scale)
6566 });
6567
6568 // Used 2 translates instead of transform-origin because of a very strange bug -
6569 // it didn't count the origin on the first touch-zoom but worked correctly afterwards
6570
6571 map._tileBg.style[L.DomUtil.TRANSFORM] =
6572 L.DomUtil.getTranslateString(this._delta) + ' ' +
6573 L.DomUtil.getScaleString(this._scale, this._startCenter);
7145 center = map.layerPointToLatLng(origin),
7146 zoom = map.getScaleZoom(this._scale);
7147
7148 map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta);
65747149 },
65757150
65767151 _onTouchEnd: function () {
6577 if (!this._moved || !this._zooming) { return; }
7152 if (!this._moved || !this._zooming) {
7153 this._zooming = false;
7154 return;
7155 }
65787156
65797157 var map = this._map;
65807158
65817159 this._zooming = false;
65827160 L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
7161 L.Util.cancelAnimFrame(this._animRequest);
65837162
65847163 L.DomEvent
65857164 .off(document, 'touchmove', this._onTouchMove)
65937172 roundZoomDelta = (floatZoomDelta > 0 ?
65947173 Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
65957174
6596 zoom = map._limitZoom(oldZoom + roundZoomDelta);
6597
6598 map.fire('zoomanim', {
6599 center: center,
6600 zoom: zoom
6601 });
6602
6603 map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
7175 zoom = map._limitZoom(oldZoom + roundZoomDelta),
7176 scale = map.getZoomScale(zoom) / this._scale;
7177
7178 map._animateZoom(center, zoom, origin, scale);
66047179 },
66057180
66067181 _getScaleOrigin: function () {
66107185 });
66117186
66127187 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
7188
7189
7190 /*
7191 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
7192 */
7193
7194 L.Map.mergeOptions({
7195 tap: true,
7196 tapTolerance: 15
7197 });
7198
7199 L.Map.Tap = L.Handler.extend({
7200 addHooks: function () {
7201 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
7202 },
7203
7204 removeHooks: function () {
7205 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
7206 },
7207
7208 _onDown: function (e) {
7209 if (!e.touches) { return; }
7210
7211 L.DomEvent.preventDefault(e);
7212
7213 this._fireClick = true;
7214
7215 // don't simulate click or track longpress if more than 1 touch
7216 if (e.touches.length > 1) {
7217 this._fireClick = false;
7218 clearTimeout(this._holdTimeout);
7219 return;
7220 }
7221
7222 var first = e.touches[0],
7223 el = first.target;
7224
7225 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
7226
7227 // if touching a link, highlight it
7228 if (el.tagName.toLowerCase() === 'a') {
7229 L.DomUtil.addClass(el, 'leaflet-active');
7230 }
7231
7232 // simulate long hold but setting a timeout
7233 this._holdTimeout = setTimeout(L.bind(function () {
7234 if (this._isTapValid()) {
7235 this._fireClick = false;
7236 this._onUp();
7237 this._simulateEvent('contextmenu', first);
7238 }
7239 }, this), 1000);
7240
7241 L.DomEvent
7242 .on(document, 'touchmove', this._onMove, this)
7243 .on(document, 'touchend', this._onUp, this);
7244 },
7245
7246 _onUp: function (e) {
7247 clearTimeout(this._holdTimeout);
7248
7249 L.DomEvent
7250 .off(document, 'touchmove', this._onMove, this)
7251 .off(document, 'touchend', this._onUp, this);
7252
7253 if (this._fireClick && e && e.changedTouches) {
7254
7255 var first = e.changedTouches[0],
7256 el = first.target;
7257
7258 if (el.tagName.toLowerCase() === 'a') {
7259 L.DomUtil.removeClass(el, 'leaflet-active');
7260 }
7261
7262 // simulate click if the touch didn't move too much
7263 if (this._isTapValid()) {
7264 this._simulateEvent('click', first);
7265 }
7266 }
7267 },
7268
7269 _isTapValid: function () {
7270 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
7271 },
7272
7273 _onMove: function (e) {
7274 var first = e.touches[0];
7275 this._newPos = new L.Point(first.clientX, first.clientY);
7276 },
7277
7278 _simulateEvent: function (type, e) {
7279 var simulatedEvent = document.createEvent('MouseEvents');
7280
7281 simulatedEvent._simulated = true;
7282 e.target._simulatedClick = true;
7283
7284 simulatedEvent.initMouseEvent(
7285 type, true, true, window, 1,
7286 e.screenX, e.screenY,
7287 e.clientX, e.clientY,
7288 false, false, false, false, 0, null);
7289
7290 e.target.dispatchEvent(simulatedEvent);
7291 }
7292 });
7293
7294 if (L.Browser.touch && !L.Browser.msTouch) {
7295 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
7296 }
66137297
66147298
66157299 /*
66407324 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
66417325
66427326 L.DomUtil.disableTextSelection();
7327 L.DomUtil.disableImageDrag();
66437328
66447329 this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
66457330
66527337 L.DomEvent
66537338 .on(document, 'mousemove', this._onMouseMove, this)
66547339 .on(document, 'mouseup', this._onMouseUp, this)
6655 .preventDefault(e);
6656
6657 this._map.fire("boxzoomstart");
7340 .on(document, 'keydown', this._onKeyDown, this);
7341
7342 this._map.fire('boxzoomstart');
66587343 },
66597344
66607345 _onMouseMove: function (e) {
66757360 box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
66767361 },
66777362
6678 _onMouseUp: function (e) {
7363 _finish: function () {
66797364 this._pane.removeChild(this._box);
66807365 this._container.style.cursor = '';
66817366
66827367 L.DomUtil.enableTextSelection();
7368 L.DomUtil.enableImageDrag();
66837369
66847370 L.DomEvent
66857371 .off(document, 'mousemove', this._onMouseMove)
6686 .off(document, 'mouseup', this._onMouseUp);
7372 .off(document, 'mouseup', this._onMouseUp)
7373 .off(document, 'keydown', this._onKeyDown);
7374 },
7375
7376 _onMouseUp: function (e) {
7377
7378 this._finish();
66877379
66887380 var map = this._map,
66897381 layerPoint = map.mouseEventToLayerPoint(e);
66967388
66977389 map.fitBounds(bounds);
66987390
6699 map.fire("boxzoomend", {
7391 map.fire('boxzoomend', {
67007392 boxZoomBounds: bounds
67017393 });
7394 },
7395
7396 _onKeyDown: function (e) {
7397 if (e.keyCode === 27) {
7398 this._finish();
7399 }
67027400 }
67037401 });
67047402
67387436
67397437 // make the container focusable by tabbing
67407438 if (container.tabIndex === -1) {
6741 container.tabIndex = "0";
7439 container.tabIndex = '0';
67427440 }
67437441
67447442 L.DomEvent
67677465 },
67687466
67697467 _onMouseDown: function () {
6770 if (!this._focused) {
6771 this._map._container.focus();
6772 }
7468 if (this._focused) { return; }
7469
7470 var body = document.body,
7471 docEl = document.documentElement,
7472 top = body.scrollTop || docEl.scrollTop,
7473 left = body.scrollTop || docEl.scrollLeft;
7474
7475 this._map._container.focus();
7476
7477 window.scrollTo(left, top);
67737478 },
67747479
67757480 _onFocus: function () {
68267531 var key = e.keyCode,
68277532 map = this._map;
68287533
6829 if (this._panKeys.hasOwnProperty(key)) {
7534 if (key in this._panKeys) {
7535
7536 if (map._panAnim && map._panAnim._inProgress) { return; }
7537
68307538 map.panBy(this._panKeys[key]);
68317539
68327540 if (map.options.maxBounds) {
68337541 map.panInsideBounds(map.options.maxBounds);
68347542 }
68357543
6836 } else if (this._zoomKeys.hasOwnProperty(key)) {
7544 } else if (key in this._zoomKeys) {
68377545 map.setZoom(map.getZoom() + this._zoomKeys[key]);
68387546
68397547 } else {
68597567 addHooks: function () {
68607568 var icon = this._marker._icon;
68617569 if (!this._draggable) {
6862 this._draggable = new L.Draggable(icon, icon)
6863 .on('dragstart', this._onDragStart, this)
6864 .on('drag', this._onDrag, this)
6865 .on('dragend', this._onDragEnd, this);
7570 this._draggable = new L.Draggable(icon, icon);
68667571 }
7572
7573 this._draggable
7574 .on('dragstart', this._onDragStart, this)
7575 .on('drag', this._onDrag, this)
7576 .on('dragend', this._onDragEnd, this);
68677577 this._draggable.enable();
68687578 },
68697579
68707580 removeHooks: function () {
7581 this._draggable
7582 .off('dragstart', this._onDragStart, this)
7583 .off('drag', this._onDrag, this)
7584 .off('dragend', this._onDragEnd, this);
7585
68717586 this._draggable.disable();
68727587 },
68737588
69087623 });
69097624
69107625
6911 /*
6912 * L.Handler.PolyEdit is an editing handler for polylines and polygons.
6913 */
6914
6915 L.Handler.PolyEdit = L.Handler.extend({
6916 options: {
6917 icon: new L.DivIcon({
6918 iconSize: new L.Point(8, 8),
6919 className: 'leaflet-div-icon leaflet-editing-icon'
6920 })
6921 },
6922
6923 initialize: function (poly, options) {
6924 this._poly = poly;
6925 L.setOptions(this, options);
6926 },
6927
6928 addHooks: function () {
6929 if (this._poly._map) {
6930 if (!this._markerGroup) {
6931 this._initMarkers();
6932 }
6933 this._poly._map.addLayer(this._markerGroup);
6934 }
6935 },
6936
6937 removeHooks: function () {
6938 if (this._poly._map) {
6939 this._poly._map.removeLayer(this._markerGroup);
6940 delete this._markerGroup;
6941 delete this._markers;
6942 }
6943 },
6944
6945 updateMarkers: function () {
6946 this._markerGroup.clearLayers();
6947 this._initMarkers();
6948 },
6949
6950 _initMarkers: function () {
6951 if (!this._markerGroup) {
6952 this._markerGroup = new L.LayerGroup();
6953 }
6954 this._markers = [];
6955
6956 var latlngs = this._poly._latlngs,
6957 i, j, len, marker;
6958
6959 // TODO refactor holes implementation in Polygon to support it here
6960
6961 for (i = 0, len = latlngs.length; i < len; i++) {
6962
6963 marker = this._createMarker(latlngs[i], i);
6964 marker.on('click', this._onMarkerClick, this);
6965 this._markers.push(marker);
6966 }
6967
6968 var markerLeft, markerRight;
6969
6970 for (i = 0, j = len - 1; i < len; j = i++) {
6971 if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
6972 continue;
6973 }
6974
6975 markerLeft = this._markers[j];
6976 markerRight = this._markers[i];
6977
6978 this._createMiddleMarker(markerLeft, markerRight);
6979 this._updatePrevNext(markerLeft, markerRight);
6980 }
6981 },
6982
6983 _createMarker: function (latlng, index) {
6984 var marker = new L.Marker(latlng, {
6985 draggable: true,
6986 icon: this.options.icon
6987 });
6988
6989 marker._origLatLng = latlng;
6990 marker._index = index;
6991
6992 marker.on('drag', this._onMarkerDrag, this);
6993 marker.on('dragend', this._fireEdit, this);
6994
6995 this._markerGroup.addLayer(marker);
6996
6997 return marker;
6998 },
6999
7000 _fireEdit: function () {
7001 this._poly.fire('edit');
7002 },
7003
7004 _onMarkerDrag: function (e) {
7005 var marker = e.target;
7006
7007 L.extend(marker._origLatLng, marker._latlng);
7008
7009 if (marker._middleLeft) {
7010 marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
7011 }
7012 if (marker._middleRight) {
7013 marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
7014 }
7015
7016 this._poly.redraw();
7017 },
7018
7019 _onMarkerClick: function (e) {
7020 // we want to remove the marker on click, but if latlng count < 3, polyline would be invalid
7021 if (this._poly._latlngs.length < 3) { return; }
7022
7023 var marker = e.target,
7024 i = marker._index;
7025
7026 // remove the marker
7027 this._markerGroup.removeLayer(marker);
7028 this._markers.splice(i, 1);
7029 this._poly.spliceLatLngs(i, 1);
7030 this._updateIndexes(i, -1);
7031
7032 // update prev/next links of adjacent markers
7033 this._updatePrevNext(marker._prev, marker._next);
7034
7035 // remove ghost markers near the removed marker
7036 if (marker._middleLeft) {
7037 this._markerGroup.removeLayer(marker._middleLeft);
7038 }
7039 if (marker._middleRight) {
7040 this._markerGroup.removeLayer(marker._middleRight);
7041 }
7042
7043 // create a ghost marker in place of the removed one
7044 if (marker._prev && marker._next) {
7045 this._createMiddleMarker(marker._prev, marker._next);
7046
7047 } else if (!marker._prev) {
7048 marker._next._middleLeft = null;
7049
7050 } else if (!marker._next) {
7051 marker._prev._middleRight = null;
7052 }
7053
7054 this._poly.fire('edit');
7055 },
7056
7057 _updateIndexes: function (index, delta) {
7058 this._markerGroup.eachLayer(function (marker) {
7059 if (marker._index > index) {
7060 marker._index += delta;
7061 }
7062 });
7063 },
7064
7065 _createMiddleMarker: function (marker1, marker2) {
7066 var latlng = this._getMiddleLatLng(marker1, marker2),
7067 marker = this._createMarker(latlng),
7068 onClick,
7069 onDragStart,
7070 onDragEnd;
7071
7072 marker.setOpacity(0.6);
7073
7074 marker1._middleRight = marker2._middleLeft = marker;
7075
7076 onDragStart = function () {
7077 var i = marker2._index;
7078
7079 marker._index = i;
7080
7081 marker
7082 .off('click', onClick)
7083 .on('click', this._onMarkerClick, this);
7084
7085 latlng.lat = marker.getLatLng().lat;
7086 latlng.lng = marker.getLatLng().lng;
7087 this._poly.spliceLatLngs(i, 0, latlng);
7088 this._markers.splice(i, 0, marker);
7089
7090 marker.setOpacity(1);
7091
7092 this._updateIndexes(i, 1);
7093 marker2._index++;
7094 this._updatePrevNext(marker1, marker);
7095 this._updatePrevNext(marker, marker2);
7096 };
7097
7098 onDragEnd = function () {
7099 marker.off('dragstart', onDragStart, this);
7100 marker.off('dragend', onDragEnd, this);
7101
7102 this._createMiddleMarker(marker1, marker);
7103 this._createMiddleMarker(marker, marker2);
7104 };
7105
7106 onClick = function () {
7107 onDragStart.call(this);
7108 onDragEnd.call(this);
7109 this._poly.fire('edit');
7110 };
7111
7112 marker
7113 .on('click', onClick, this)
7114 .on('dragstart', onDragStart, this)
7115 .on('dragend', onDragEnd, this);
7116
7117 this._markerGroup.addLayer(marker);
7118 },
7119
7120 _updatePrevNext: function (marker1, marker2) {
7121 if (marker1) {
7122 marker1._next = marker2;
7123 }
7124 if (marker2) {
7125 marker2._prev = marker1;
7126 }
7127 },
7128
7129 _getMiddleLatLng: function (marker1, marker2) {
7130 var map = this._poly._map,
7131 p1 = map.latLngToLayerPoint(marker1.getLatLng()),
7132 p2 = map.latLngToLayerPoint(marker2.getLatLng());
7133
7134 return map.layerPointToLatLng(p1._add(p2)._divideBy(2));
7135 }
7136 });
7137
7138 L.Polyline.addInitHook(function () {
7139
7140 if (L.Handler.PolyEdit) {
7141 this.editing = new L.Handler.PolyEdit(this);
7142
7143 if (this.options.editable) {
7144 this.editing.enable();
7145 }
7146 }
7147
7148 this.on('add', function () {
7149 if (this.editing && this.editing.enabled()) {
7150 this.editing.addHooks();
7151 }
7152 });
7153
7154 this.on('remove', function () {
7155 if (this.editing && this.editing.enabled()) {
7156 this.editing.removeHooks();
7157 }
7158 });
7159 });
7160
7161
7162 /*
7163 * L.Control is a base class for implementing map controls. Handles positioning.
7164 * All other controls extend from this class.
7165 */
7166
7167 L.Control = L.Class.extend({
7168 options: {
7169 position: 'topright'
7170 },
7171
7172 initialize: function (options) {
7173 L.setOptions(this, options);
7174 },
7175
7176 getPosition: function () {
7177 return this.options.position;
7178 },
7179
7180 setPosition: function (position) {
7181 var map = this._map;
7182
7183 if (map) {
7184 map.removeControl(this);
7185 }
7186
7187 this.options.position = position;
7188
7189 if (map) {
7190 map.addControl(this);
7191 }
7192
7193 return this;
7194 },
7195
7196 addTo: function (map) {
7197 this._map = map;
7198
7199 var container = this._container = this.onAdd(map),
7200 pos = this.getPosition(),
7201 corner = map._controlCorners[pos];
7202
7203 L.DomUtil.addClass(container, 'leaflet-control');
7204
7205 if (pos.indexOf('bottom') !== -1) {
7206 corner.insertBefore(container, corner.firstChild);
7207 } else {
7208 corner.appendChild(container);
7209 }
7210
7211 return this;
7212 },
7213
7214 removeFrom: function (map) {
7215 var pos = this.getPosition(),
7216 corner = map._controlCorners[pos];
7217
7218 corner.removeChild(this._container);
7219 this._map = null;
7220
7221 if (this.onRemove) {
7222 this.onRemove(map);
7223 }
7224
7225 return this;
7226 }
7227 });
7228
7229 L.control = function (options) {
7230 return new L.Control(options);
7231 };
7232
7233
7234 /*
7235 * Adds control-related methods to L.Map.
7236 */
7237
7238 L.Map.include({
7239 addControl: function (control) {
7240 control.addTo(this);
7241 return this;
7242 },
7243
7244 removeControl: function (control) {
7245 control.removeFrom(this);
7246 return this;
7247 },
7248
7249 _initControlPos: function () {
7250 var corners = this._controlCorners = {},
7251 l = 'leaflet-',
7252 container = this._controlContainer =
7253 L.DomUtil.create('div', l + 'control-container', this._container);
7254
7255 function createCorner(vSide, hSide) {
7256 var className = l + vSide + ' ' + l + hSide;
7257
7258 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
7259 }
7260
7261 createCorner('top', 'left');
7262 createCorner('top', 'right');
7263 createCorner('bottom', 'left');
7264 createCorner('bottom', 'right');
7265 }
7266 });
7267
7268
7269 /*
7270 * L.Control.Zoom is used for the default zoom buttons on the map.
7271 */
7272
7273 L.Control.Zoom = L.Control.extend({
7274 options: {
7275 position: 'topleft'
7276 },
7277
7278 onAdd: function (map) {
7279 var zoomName = 'leaflet-control-zoom',
7280 barName = 'leaflet-bar',
7281 partName = barName + '-part',
7282 container = L.DomUtil.create('div', zoomName + ' ' + barName);
7283
7284 this._map = map;
7285
7286 this._zoomInButton = this._createButton('+', 'Zoom in',
7287 zoomName + '-in ' +
7288 partName + ' ' +
7289 partName + '-top',
7290 container, this._zoomIn, this);
7291
7292 this._zoomOutButton = this._createButton('-', 'Zoom out',
7293 zoomName + '-out ' +
7294 partName + ' ' +
7295 partName + '-bottom',
7296 container, this._zoomOut, this);
7297
7298 map.on('zoomend', this._updateDisabled, this);
7299
7300 return container;
7301 },
7302
7303 onRemove: function (map) {
7304 map.off('zoomend', this._updateDisabled, this);
7305 },
7306
7307 _zoomIn: function (e) {
7308 this._map.zoomIn(e.shiftKey ? 3 : 1);
7309 },
7310
7311 _zoomOut: function (e) {
7312 this._map.zoomOut(e.shiftKey ? 3 : 1);
7313 },
7314
7315 _createButton: function (html, title, className, container, fn, context) {
7316 var link = L.DomUtil.create('a', className, container);
7317 link.innerHTML = html;
7318 link.href = '#';
7319 link.title = title;
7320
7321 var stop = L.DomEvent.stopPropagation;
7322
7323 L.DomEvent
7324 .on(link, 'click', stop)
7325 .on(link, 'mousedown', stop)
7326 .on(link, 'dblclick', stop)
7327 .on(link, 'click', L.DomEvent.preventDefault)
7328 .on(link, 'click', fn, context);
7329
7330 return link;
7331 },
7332
7333 _updateDisabled: function () {
7334 var map = this._map,
7335 className = 'leaflet-control-zoom-disabled';
7336
7337 L.DomUtil.removeClass(this._zoomInButton, className);
7338 L.DomUtil.removeClass(this._zoomOutButton, className);
7339
7340 if (map._zoom === map.getMinZoom()) {
7341 L.DomUtil.addClass(this._zoomOutButton, className);
7342 }
7343 if (map._zoom === map.getMaxZoom()) {
7344 L.DomUtil.addClass(this._zoomInButton, className);
7345 }
7346 }
7347 });
7348
7349 L.Map.mergeOptions({
7350 zoomControl: true
7351 });
7352
7353 L.Map.addInitHook(function () {
7354 if (this.options.zoomControl) {
7355 this.zoomControl = new L.Control.Zoom();
7356 this.addControl(this.zoomControl);
7357 }
7358 });
7359
7360 L.control.zoom = function (options) {
7361 return new L.Control.Zoom(options);
7362 };
7363
7364
7365
7366 /*
7367 * L.Control.Attribution is used for displaying attribution on the map (added by default).
7368 */
7369
7370 L.Control.Attribution = L.Control.extend({
7371 options: {
7372 position: 'bottomright',
7373 prefix: 'Powered by <a href="http://leafletjs.com">Leaflet</a>'
7374 },
7375
7376 initialize: function (options) {
7377 L.setOptions(this, options);
7378
7379 this._attributions = {};
7380 },
7381
7382 onAdd: function (map) {
7383 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
7384 L.DomEvent.disableClickPropagation(this._container);
7385
7386 map
7387 .on('layeradd', this._onLayerAdd, this)
7388 .on('layerremove', this._onLayerRemove, this);
7389
7390 this._update();
7391
7392 return this._container;
7393 },
7394
7395 onRemove: function (map) {
7396 map
7397 .off('layeradd', this._onLayerAdd)
7398 .off('layerremove', this._onLayerRemove);
7399
7400 },
7401
7402 setPrefix: function (prefix) {
7403 this.options.prefix = prefix;
7404 this._update();
7405 return this;
7406 },
7407
7408 addAttribution: function (text) {
7409 if (!text) { return; }
7410
7411 if (!this._attributions[text]) {
7412 this._attributions[text] = 0;
7413 }
7414 this._attributions[text]++;
7415
7416 this._update();
7417
7418 return this;
7419 },
7420
7421 removeAttribution: function (text) {
7422 if (!text) { return; }
7423
7424 this._attributions[text]--;
7425 this._update();
7426
7427 return this;
7428 },
7429
7430 _update: function () {
7431 if (!this._map) { return; }
7432
7433 var attribs = [];
7434
7435 for (var i in this._attributions) {
7436 if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
7437 attribs.push(i);
7438 }
7439 }
7440
7441 var prefixAndAttribs = [];
7442
7443 if (this.options.prefix) {
7444 prefixAndAttribs.push(this.options.prefix);
7445 }
7446 if (attribs.length) {
7447 prefixAndAttribs.push(attribs.join(', '));
7448 }
7449
7450 this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
7451 },
7452
7453 _onLayerAdd: function (e) {
7454 if (e.layer.getAttribution) {
7455 this.addAttribution(e.layer.getAttribution());
7456 }
7457 },
7458
7459 _onLayerRemove: function (e) {
7460 if (e.layer.getAttribution) {
7461 this.removeAttribution(e.layer.getAttribution());
7462 }
7463 }
7464 });
7465
7466 L.Map.mergeOptions({
7467 attributionControl: true
7468 });
7469
7470 L.Map.addInitHook(function () {
7471 if (this.options.attributionControl) {
7472 this.attributionControl = (new L.Control.Attribution()).addTo(this);
7473 }
7474 });
7475
7476 L.control.attribution = function (options) {
7477 return new L.Control.Attribution(options);
7478 };
7626 /*
7627 * L.Control is a base class for implementing map controls. Handles positioning.
7628 * All other controls extend from this class.
7629 */
7630
7631 L.Control = L.Class.extend({
7632 options: {
7633 position: 'topright'
7634 },
7635
7636 initialize: function (options) {
7637 L.setOptions(this, options);
7638 },
7639
7640 getPosition: function () {
7641 return this.options.position;
7642 },
7643
7644 setPosition: function (position) {
7645 var map = this._map;
7646
7647 if (map) {
7648 map.removeControl(this);
7649 }
7650
7651 this.options.position = position;
7652
7653 if (map) {
7654 map.addControl(this);
7655 }
7656
7657 return this;
7658 },
7659
7660 getContainer: function () {
7661 return this._container;
7662 },
7663
7664 addTo: function (map) {
7665 this._map = map;
7666
7667 var container = this._container = this.onAdd(map),
7668 pos = this.getPosition(),
7669 corner = map._controlCorners[pos];
7670
7671 L.DomUtil.addClass(container, 'leaflet-control');
7672
7673 if (pos.indexOf('bottom') !== -1) {
7674 corner.insertBefore(container, corner.firstChild);
7675 } else {
7676 corner.appendChild(container);
7677 }
7678
7679 return this;
7680 },
7681
7682 removeFrom: function (map) {
7683 var pos = this.getPosition(),
7684 corner = map._controlCorners[pos];
7685
7686 corner.removeChild(this._container);
7687 this._map = null;
7688
7689 if (this.onRemove) {
7690 this.onRemove(map);
7691 }
7692
7693 return this;
7694 }
7695 });
7696
7697 L.control = function (options) {
7698 return new L.Control(options);
7699 };
7700
7701
7702 // adds control-related methods to L.Map
7703
7704 L.Map.include({
7705 addControl: function (control) {
7706 control.addTo(this);
7707 return this;
7708 },
7709
7710 removeControl: function (control) {
7711 control.removeFrom(this);
7712 return this;
7713 },
7714
7715 _initControlPos: function () {
7716 var corners = this._controlCorners = {},
7717 l = 'leaflet-',
7718 container = this._controlContainer =
7719 L.DomUtil.create('div', l + 'control-container', this._container);
7720
7721 function createCorner(vSide, hSide) {
7722 var className = l + vSide + ' ' + l + hSide;
7723
7724 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
7725 }
7726
7727 createCorner('top', 'left');
7728 createCorner('top', 'right');
7729 createCorner('bottom', 'left');
7730 createCorner('bottom', 'right');
7731 },
7732
7733 _clearControlPos: function () {
7734 this._container.removeChild(this._controlContainer);
7735 }
7736 });
7737
7738
7739 /*
7740 * L.Control.Zoom is used for the default zoom buttons on the map.
7741 */
7742
7743 L.Control.Zoom = L.Control.extend({
7744 options: {
7745 position: 'topleft'
7746 },
7747
7748 onAdd: function (map) {
7749 var zoomName = 'leaflet-control-zoom',
7750 container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
7751
7752 this._map = map;
7753
7754 this._zoomInButton = this._createButton(
7755 '+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this);
7756 this._zoomOutButton = this._createButton(
7757 '-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this);
7758
7759 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
7760
7761 return container;
7762 },
7763
7764 onRemove: function (map) {
7765 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
7766 },
7767
7768 _zoomIn: function (e) {
7769 this._map.zoomIn(e.shiftKey ? 3 : 1);
7770 },
7771
7772 _zoomOut: function (e) {
7773 this._map.zoomOut(e.shiftKey ? 3 : 1);
7774 },
7775
7776 _createButton: function (html, title, className, container, fn, context) {
7777 var link = L.DomUtil.create('a', className, container);
7778 link.innerHTML = html;
7779 link.href = '#';
7780 link.title = title;
7781
7782 var stop = L.DomEvent.stopPropagation;
7783
7784 L.DomEvent
7785 .on(link, 'click', stop)
7786 .on(link, 'mousedown', stop)
7787 .on(link, 'dblclick', stop)
7788 .on(link, 'click', L.DomEvent.preventDefault)
7789 .on(link, 'click', fn, context);
7790
7791 return link;
7792 },
7793
7794 _updateDisabled: function () {
7795 var map = this._map,
7796 className = 'leaflet-disabled';
7797
7798 L.DomUtil.removeClass(this._zoomInButton, className);
7799 L.DomUtil.removeClass(this._zoomOutButton, className);
7800
7801 if (map._zoom === map.getMinZoom()) {
7802 L.DomUtil.addClass(this._zoomOutButton, className);
7803 }
7804 if (map._zoom === map.getMaxZoom()) {
7805 L.DomUtil.addClass(this._zoomInButton, className);
7806 }
7807 }
7808 });
7809
7810 L.Map.mergeOptions({
7811 zoomControl: true
7812 });
7813
7814 L.Map.addInitHook(function () {
7815 if (this.options.zoomControl) {
7816 this.zoomControl = new L.Control.Zoom();
7817 this.addControl(this.zoomControl);
7818 }
7819 });
7820
7821 L.control.zoom = function (options) {
7822 return new L.Control.Zoom(options);
7823 };
7824
7825
7826
7827 /*
7828 * L.Control.Attribution is used for displaying attribution on the map (added by default).
7829 */
7830
7831 L.Control.Attribution = L.Control.extend({
7832 options: {
7833 position: 'bottomright',
7834 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
7835 },
7836
7837 initialize: function (options) {
7838 L.setOptions(this, options);
7839
7840 this._attributions = {};
7841 },
7842
7843 onAdd: function (map) {
7844 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
7845 L.DomEvent.disableClickPropagation(this._container);
7846
7847 map
7848 .on('layeradd', this._onLayerAdd, this)
7849 .on('layerremove', this._onLayerRemove, this);
7850
7851 this._update();
7852
7853 return this._container;
7854 },
7855
7856 onRemove: function (map) {
7857 map
7858 .off('layeradd', this._onLayerAdd)
7859 .off('layerremove', this._onLayerRemove);
7860
7861 },
7862
7863 setPrefix: function (prefix) {
7864 this.options.prefix = prefix;
7865 this._update();
7866 return this;
7867 },
7868
7869 addAttribution: function (text) {
7870 if (!text) { return; }
7871
7872 if (!this._attributions[text]) {
7873 this._attributions[text] = 0;
7874 }
7875 this._attributions[text]++;
7876
7877 this._update();
7878
7879 return this;
7880 },
7881
7882 removeAttribution: function (text) {
7883 if (!text) { return; }
7884
7885 if (this._attributions[text]) {
7886 this._attributions[text]--;
7887 this._update();
7888 }
7889
7890 return this;
7891 },
7892
7893 _update: function () {
7894 if (!this._map) { return; }
7895
7896 var attribs = [];
7897
7898 for (var i in this._attributions) {
7899 if (this._attributions[i]) {
7900 attribs.push(i);
7901 }
7902 }
7903
7904 var prefixAndAttribs = [];
7905
7906 if (this.options.prefix) {
7907 prefixAndAttribs.push(this.options.prefix);
7908 }
7909 if (attribs.length) {
7910 prefixAndAttribs.push(attribs.join(', '));
7911 }
7912
7913 this._container.innerHTML = prefixAndAttribs.join(' | ');
7914 },
7915
7916 _onLayerAdd: function (e) {
7917 if (e.layer.getAttribution) {
7918 this.addAttribution(e.layer.getAttribution());
7919 }
7920 },
7921
7922 _onLayerRemove: function (e) {
7923 if (e.layer.getAttribution) {
7924 this.removeAttribution(e.layer.getAttribution());
7925 }
7926 }
7927 });
7928
7929 L.Map.mergeOptions({
7930 attributionControl: true
7931 });
7932
7933 L.Map.addInitHook(function () {
7934 if (this.options.attributionControl) {
7935 this.attributionControl = (new L.Control.Attribution()).addTo(this);
7936 }
7937 });
7938
7939 L.control.attribution = function (options) {
7940 return new L.Control.Attribution(options);
7941 };
74797942
74807943
74817944 /*
75928055 };
75938056
75948057
7595 /*
7596 * L.Control.Layers is a control to allow users to switch between different layers on the map.
7597 */
7598
7599 L.Control.Layers = L.Control.extend({
7600 options: {
7601 collapsed: true,
7602 position: 'topright',
7603 autoZIndex: true
7604 },
7605
7606 initialize: function (baseLayers, overlays, options) {
7607 L.setOptions(this, options);
7608
7609 this._layers = {};
7610 this._lastZIndex = 0;
7611 this._handlingClick = false;
7612
7613 for (var i in baseLayers) {
7614 if (baseLayers.hasOwnProperty(i)) {
7615 this._addLayer(baseLayers[i], i);
7616 }
7617 }
7618
7619 for (i in overlays) {
7620 if (overlays.hasOwnProperty(i)) {
7621 this._addLayer(overlays[i], i, true);
7622 }
7623 }
7624 },
7625
7626 onAdd: function (map) {
7627 this._initLayout();
7628 this._update();
7629
7630 map
7631 .on('layeradd', this._onLayerChange, this)
7632 .on('layerremove', this._onLayerChange, this);
7633
7634 return this._container;
7635 },
7636
7637 onRemove: function (map) {
7638 map
7639 .off('layeradd', this._onLayerChange)
7640 .off('layerremove', this._onLayerChange);
7641 },
7642
7643 addBaseLayer: function (layer, name) {
7644 this._addLayer(layer, name);
7645 this._update();
7646 return this;
7647 },
7648
7649 addOverlay: function (layer, name) {
7650 this._addLayer(layer, name, true);
7651 this._update();
7652 return this;
7653 },
7654
7655 removeLayer: function (layer) {
7656 var id = L.stamp(layer);
7657 delete this._layers[id];
7658 this._update();
7659 return this;
7660 },
7661
7662 _initLayout: function () {
7663 var className = 'leaflet-control-layers',
7664 container = this._container = L.DomUtil.create('div', className);
7665
7666 if (!L.Browser.touch) {
7667 L.DomEvent.disableClickPropagation(container);
7668 L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
7669 } else {
7670 L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
7671 }
7672
7673 var form = this._form = L.DomUtil.create('form', className + '-list');
7674
7675 if (this.options.collapsed) {
7676 L.DomEvent
7677 .on(container, 'mouseover', this._expand, this)
7678 .on(container, 'mouseout', this._collapse, this);
7679
7680 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
7681 link.href = '#';
7682 link.title = 'Layers';
7683
7684 if (L.Browser.touch) {
7685 L.DomEvent
7686 .on(link, 'click', L.DomEvent.stopPropagation)
7687 .on(link, 'click', L.DomEvent.preventDefault)
7688 .on(link, 'click', this._expand, this);
7689 }
7690 else {
7691 L.DomEvent.on(link, 'focus', this._expand, this);
7692 }
7693
7694 this._map.on('movestart', this._collapse, this);
7695 // TODO keyboard accessibility
7696 } else {
7697 this._expand();
7698 }
7699
7700 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
7701 this._separator = L.DomUtil.create('div', className + '-separator', form);
7702 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
7703
7704 container.appendChild(form);
7705 },
7706
7707 _addLayer: function (layer, name, overlay) {
7708 var id = L.stamp(layer);
7709
7710 this._layers[id] = {
7711 layer: layer,
7712 name: name,
7713 overlay: overlay
7714 };
7715
7716 if (this.options.autoZIndex && layer.setZIndex) {
7717 this._lastZIndex++;
7718 layer.setZIndex(this._lastZIndex);
7719 }
7720 },
7721
7722 _update: function () {
7723 if (!this._container) {
7724 return;
7725 }
7726
7727 this._baseLayersList.innerHTML = '';
7728 this._overlaysList.innerHTML = '';
7729
7730 var baseLayersPresent = false,
7731 overlaysPresent = false;
7732
7733 for (var i in this._layers) {
7734 if (this._layers.hasOwnProperty(i)) {
7735 var obj = this._layers[i];
7736 this._addItem(obj);
7737 overlaysPresent = overlaysPresent || obj.overlay;
7738 baseLayersPresent = baseLayersPresent || !obj.overlay;
7739 }
7740 }
7741
7742 this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
7743 },
7744
7745 _onLayerChange: function (e) {
7746 var id = L.stamp(e.layer);
7747
7748 if (this._layers[id] && !this._handlingClick) {
7749 this._update();
7750 }
7751 },
7752
7753 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
7754 _createRadioElement: function (name, checked) {
7755
7756 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
7757 if (checked) {
7758 radioHtml += ' checked="checked"';
7759 }
7760 radioHtml += '/>';
7761
7762 var radioFragment = document.createElement('div');
7763 radioFragment.innerHTML = radioHtml;
7764
7765 return radioFragment.firstChild;
7766 },
7767
7768 _addItem: function (obj) {
7769 var label = document.createElement('label'),
7770 input,
7771 checked = this._map.hasLayer(obj.layer);
7772
7773 if (obj.overlay) {
7774 input = document.createElement('input');
7775 input.type = 'checkbox';
7776 input.className = 'leaflet-control-layers-selector';
7777 input.defaultChecked = checked;
7778 } else {
7779 input = this._createRadioElement('leaflet-base-layers', checked);
7780 }
7781
7782 input.layerId = L.stamp(obj.layer);
7783
7784 L.DomEvent.on(input, 'click', this._onInputClick, this);
7785
7786 var name = document.createElement('span');
7787 name.innerHTML = ' ' + obj.name;
7788
7789 label.appendChild(input);
7790 label.appendChild(name);
7791
7792 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
7793 container.appendChild(label);
7794
7795 return label;
7796 },
7797
7798 _onInputClick: function () {
7799 var i, input, obj,
7800 inputs = this._form.getElementsByTagName('input'),
7801 inputsLen = inputs.length,
7802 baseLayer;
7803
7804 this._handlingClick = true;
7805
7806 for (i = 0; i < inputsLen; i++) {
7807 input = inputs[i];
7808 obj = this._layers[input.layerId];
7809
7810 if (input.checked && !this._map.hasLayer(obj.layer)) {
7811 this._map.addLayer(obj.layer);
7812 if (!obj.overlay) {
7813 baseLayer = obj.layer;
7814 }
7815 } else if (!input.checked && this._map.hasLayer(obj.layer)) {
7816 this._map.removeLayer(obj.layer);
7817 }
7818 }
7819
7820 if (baseLayer) {
7821 this._map.setZoom(this._map.getZoom());
7822 this._map.fire('baselayerchange', {layer: baseLayer});
7823 }
7824
7825 this._handlingClick = false;
7826 },
7827
7828 _expand: function () {
7829 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
7830 },
7831
7832 _collapse: function () {
7833 this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
7834 }
7835 });
7836
7837 L.control.layers = function (baseLayers, overlays, options) {
7838 return new L.Control.Layers(baseLayers, overlays, options);
7839 };
8058 /*
8059 * L.Control.Layers is a control to allow users to switch between different layers on the map.
8060 */
8061
8062 L.Control.Layers = L.Control.extend({
8063 options: {
8064 collapsed: true,
8065 position: 'topright',
8066 autoZIndex: true
8067 },
8068
8069 initialize: function (baseLayers, overlays, options) {
8070 L.setOptions(this, options);
8071
8072 this._layers = {};
8073 this._lastZIndex = 0;
8074 this._handlingClick = false;
8075
8076 for (var i in baseLayers) {
8077 this._addLayer(baseLayers[i], i);
8078 }
8079
8080 for (i in overlays) {
8081 this._addLayer(overlays[i], i, true);
8082 }
8083 },
8084
8085 onAdd: function (map) {
8086 this._initLayout();
8087 this._update();
8088
8089 map
8090 .on('layeradd', this._onLayerChange, this)
8091 .on('layerremove', this._onLayerChange, this);
8092
8093 return this._container;
8094 },
8095
8096 onRemove: function (map) {
8097 map
8098 .off('layeradd', this._onLayerChange)
8099 .off('layerremove', this._onLayerChange);
8100 },
8101
8102 addBaseLayer: function (layer, name) {
8103 this._addLayer(layer, name);
8104 this._update();
8105 return this;
8106 },
8107
8108 addOverlay: function (layer, name) {
8109 this._addLayer(layer, name, true);
8110 this._update();
8111 return this;
8112 },
8113
8114 removeLayer: function (layer) {
8115 var id = L.stamp(layer);
8116 delete this._layers[id];
8117 this._update();
8118 return this;
8119 },
8120
8121 _initLayout: function () {
8122 var className = 'leaflet-control-layers',
8123 container = this._container = L.DomUtil.create('div', className);
8124
8125 //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
8126 container.setAttribute('aria-haspopup', true);
8127
8128 if (!L.Browser.touch) {
8129 L.DomEvent.disableClickPropagation(container);
8130 L.DomEvent.on(container, 'wheel', L.DomEvent.stopPropagation);
8131 } else {
8132 L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
8133 }
8134
8135 var form = this._form = L.DomUtil.create('form', className + '-list');
8136
8137 if (this.options.collapsed) {
8138 if (!L.Browser.android) {
8139 L.DomEvent
8140 .on(container, 'mouseover', this._expand, this)
8141 .on(container, 'mouseout', this._collapse, this);
8142 }
8143 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
8144 link.href = '#';
8145 link.title = 'Layers';
8146
8147 if (L.Browser.touch) {
8148 L.DomEvent
8149 .on(link, 'click', L.DomEvent.stop)
8150 .on(link, 'click', this._expand, this);
8151 }
8152 else {
8153 L.DomEvent.on(link, 'focus', this._expand, this);
8154 }
8155
8156 this._map.on('click', this._collapse, this);
8157 // TODO keyboard accessibility
8158 } else {
8159 this._expand();
8160 }
8161
8162 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
8163 this._separator = L.DomUtil.create('div', className + '-separator', form);
8164 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
8165
8166 container.appendChild(form);
8167 },
8168
8169 _addLayer: function (layer, name, overlay) {
8170 var id = L.stamp(layer);
8171
8172 this._layers[id] = {
8173 layer: layer,
8174 name: name,
8175 overlay: overlay
8176 };
8177
8178 if (this.options.autoZIndex && layer.setZIndex) {
8179 this._lastZIndex++;
8180 layer.setZIndex(this._lastZIndex);
8181 }
8182 },
8183
8184 _update: function () {
8185 if (!this._container) {
8186 return;
8187 }
8188
8189 this._baseLayersList.innerHTML = '';
8190 this._overlaysList.innerHTML = '';
8191
8192 var baseLayersPresent = false,
8193 overlaysPresent = false,
8194 i, obj;
8195
8196 for (i in this._layers) {
8197 obj = this._layers[i];
8198 this._addItem(obj);
8199 overlaysPresent = overlaysPresent || obj.overlay;
8200 baseLayersPresent = baseLayersPresent || !obj.overlay;
8201 }
8202
8203 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
8204 },
8205
8206 _onLayerChange: function (e) {
8207 var obj = this._layers[L.stamp(e.layer)];
8208
8209 if (!obj) { return; }
8210
8211 if (!this._handlingClick) {
8212 this._update();
8213 }
8214
8215 var type = obj.overlay ?
8216 (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
8217 (e.type === 'layeradd' ? 'baselayerchange' : null);
8218
8219 if (type) {
8220 this._map.fire(type, obj);
8221 }
8222 },
8223
8224 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
8225 _createRadioElement: function (name, checked) {
8226
8227 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
8228 if (checked) {
8229 radioHtml += ' checked="checked"';
8230 }
8231 radioHtml += '/>';
8232
8233 var radioFragment = document.createElement('div');
8234 radioFragment.innerHTML = radioHtml;
8235
8236 return radioFragment.firstChild;
8237 },
8238
8239 _addItem: function (obj) {
8240 var label = document.createElement('label'),
8241 input,
8242 checked = this._map.hasLayer(obj.layer);
8243
8244 if (obj.overlay) {
8245 input = document.createElement('input');
8246 input.type = 'checkbox';
8247 input.className = 'leaflet-control-layers-selector';
8248 input.defaultChecked = checked;
8249 } else {
8250 input = this._createRadioElement('leaflet-base-layers', checked);
8251 }
8252
8253 input.layerId = L.stamp(obj.layer);
8254
8255 L.DomEvent.on(input, 'click', this._onInputClick, this);
8256
8257 var name = document.createElement('span');
8258 name.innerHTML = ' ' + obj.name;
8259
8260 label.appendChild(input);
8261 label.appendChild(name);
8262
8263 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
8264 container.appendChild(label);
8265
8266 return label;
8267 },
8268
8269 _onInputClick: function () {
8270 var i, input, obj,
8271 inputs = this._form.getElementsByTagName('input'),
8272 inputsLen = inputs.length;
8273
8274 this._handlingClick = true;
8275
8276 for (i = 0; i < inputsLen; i++) {
8277 input = inputs[i];
8278 obj = this._layers[input.layerId];
8279
8280 if (input.checked && !this._map.hasLayer(obj.layer)) {
8281 this._map.addLayer(obj.layer);
8282
8283 } else if (!input.checked && this._map.hasLayer(obj.layer)) {
8284 this._map.removeLayer(obj.layer);
8285 }
8286 }
8287
8288 this._handlingClick = false;
8289 },
8290
8291 _expand: function () {
8292 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
8293 },
8294
8295 _collapse: function () {
8296 this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
8297 }
8298 });
8299
8300 L.control.layers = function (baseLayers, overlays, options) {
8301 return new L.Control.Layers(baseLayers, overlays, options);
8302 };
78408303
78418304
78428305 /*
78518314
78528315 this._el = el;
78538316 this._inProgress = true;
8317 this._newPos = newPos;
78548318
78558319 this.fire('start');
78568320
78648328 L.Util.falseFn(el.offsetWidth);
78658329
78668330 // there's no native way to track value updates of transitioned properties, so we imitate this
7867 this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
8331 this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
78688332 },
78698333
78708334 stop: function () {
78788342 L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
78798343 },
78808344
8345 _onStep: function () {
8346 // jshint camelcase: false
8347 // make L.DomUtil.getPosition return intermediate position value during animation
8348 this._el._leaflet_pos = this._getPos();
8349
8350 this.fire('step');
8351 },
8352
78818353 // you can't easily get intermediate values of properties animated with CSS3 Transitions,
78828354 // we need to parse computed style (in case of transform it returns matrix string)
78838355
7884 _transformRe: /(-?[\d\.]+), (-?[\d\.]+)\)/,
8356 _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
78858357
78868358 _getPos: function () {
78878359 var left, top, matches,
78908362
78918363 if (L.Browser.any3d) {
78928364 matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
7893 left = parseFloat(matches[1]);
7894 top = parseFloat(matches[2]);
8365 left = matches ? parseFloat(matches[1]) : 0;
8366 top = matches ? parseFloat(matches[2]) : 0;
78958367 } else {
78968368 left = parseFloat(style.left);
78978369 top = parseFloat(style.top);
79088380
79098381 this._el.style[L.DomUtil.TRANSITION] = '';
79108382
8383 // jshint camelcase: false
8384 // make sure L.DomUtil.getPosition returns the final position value after animation
8385 this._el._leaflet_pos = this._newPos;
8386
79118387 clearInterval(this._stepTimer);
79128388
79138389 this.fire('step').fire('end');
79228398
79238399 L.Map.include({
79248400
7925 setView: function (center, zoom, forceReset) {
8401 setView: function (center, zoom, options) {
8402
79268403 zoom = this._limitZoom(zoom);
7927
7928 var zoomChanged = (this._zoom !== zoom);
7929
7930 if (this._loaded && !forceReset && this._layers) {
7931
7932 if (this._panAnim) {
7933 this._panAnim.stop();
8404 center = L.latLng(center);
8405 options = options || {};
8406
8407 if (this._panAnim) {
8408 this._panAnim.stop();
8409 }
8410
8411 if (this._loaded && !options.reset && options !== true) {
8412
8413 if (options.animate !== undefined) {
8414 options.zoom = L.extend({animate: options.animate}, options.zoom);
8415 options.pan = L.extend({animate: options.animate}, options.pan);
79348416 }
79358417
7936 var done = (zoomChanged ?
7937 this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
7938 this._panByIfClose(center));
7939
7940 // exit if animated pan or zoom started
7941 if (done) {
8418 // try animating pan or zoom
8419 var animated = (this._zoom !== zoom) ?
8420 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
8421 this._tryAnimatedPan(center, options.pan);
8422
8423 if (animated) {
8424 // prevent resize handler call, the view will refresh after animation anyway
79428425 clearTimeout(this._sizeTimer);
79438426 return this;
79448427 }
79458428 }
79468429
7947 // reset the map view
8430 // animation didn't start, just reset the map view
79488431 this._resetView(center, zoom);
79498432
79508433 return this;
79518434 },
79528435
7953 panBy: function (offset, duration, easeLinearity) {
7954 offset = L.point(offset);
7955
7956 if (!(offset.x || offset.y)) {
8436 panBy: function (offset, options) {
8437 offset = L.point(offset).round();
8438 options = options || {};
8439
8440 if (!offset.x && !offset.y) {
79578441 return this;
79588442 }
79598443
79668450 }, this);
79678451 }
79688452
7969 this.fire('movestart');
7970
7971 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
7972
7973 var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round();
7974 this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
8453 // don't fire movestart if animating inertia
8454 if (!options.noMoveStart) {
8455 this.fire('movestart');
8456 }
8457
8458 // animate pan unless animate: false specified
8459 if (options.animate !== false) {
8460 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
8461
8462 var newPos = this._getMapPanePos().subtract(offset);
8463 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
8464 } else {
8465 this._rawPanBy(offset);
8466 this.fire('move').fire('moveend');
8467 }
79758468
79768469 return this;
79778470 },
79858478 this.fire('moveend');
79868479 },
79878480
7988 _panByIfClose: function (center) {
8481 _tryAnimatedPan: function (center, options) {
79898482 // difference between the new and current centers in pixels
79908483 var offset = this._getCenterOffset(center)._floor();
79918484
7992 if (this._offsetIsWithinView(offset)) {
7993 this.panBy(offset);
7994 return true;
7995 }
7996 return false;
7997 },
7998
7999 _offsetIsWithinView: function (offset, multiplyFactor) {
8000 var m = multiplyFactor || 1,
8001 size = this.getSize();
8002
8003 return (Math.abs(offset.x) <= size.x * m) &&
8004 (Math.abs(offset.y) <= size.y * m);
8485 // don't animate too far unless animate: true specified in options
8486 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
8487
8488 this.panBy(offset, options);
8489
8490 return true;
80058491 }
80068492 });
80078493
80808566 */
80818567
80828568 L.Map.mergeOptions({
8083 zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
8569 zoomAnimation: true,
8570 zoomAnimationThreshold: 4
80848571 });
80858572
80868573 if (L.DomUtil.TRANSITION) {
8574
80878575 L.Map.addInitHook(function () {
8088 L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
8576 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
8577 this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
8578 L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
8579
8580 // zoom transitions run with the same duration for all layers, so if one of transitionend events
8581 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
8582 if (this._zoomAnimated) {
8583 L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
8584 }
80898585 });
80908586 }
80918587
80928588 L.Map.include(!L.DomUtil.TRANSITION ? {} : {
8093
8094 _zoomToIfClose: function (center, zoom) {
8095
8096 if (this._animatingZoom) { return true; }
8097
8098 if (!this.options.zoomAnimation) { return false; }
8099
8100 var scale = this.getZoomScale(zoom),
8101 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
8102
8103 // if offset does not exceed half of the view
8104 if (!this._offsetIsWithinView(offset, 1)) { return false; }
8105
8106 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
8107
8108 this
8109 .fire('movestart')
8110 .fire('zoomstart');
8111
8112 this.fire('zoomanim', {
8113 center: center,
8114 zoom: zoom
8115 });
8116
8117 var origin = this._getCenterLayerPoint().add(offset);
8118
8119 this._prepareTileBg();
8120 this._runAnimation(center, zoom, scale, origin);
8121
8122 return true;
8123 },
81248589
81258590 _catchTransitionEnd: function () {
81268591 if (this._animatingZoom) {
81288593 }
81298594 },
81308595
8131 _runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
8596 _tryAnimatedZoom: function (center, zoom, options) {
8597
8598 if (this._animatingZoom) { return true; }
8599
8600 options = options || {};
8601
8602 // don't animate if disabled, not supported or zoom difference is too large
8603 if (!this._zoomAnimated || options.animate === false ||
8604 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
8605
8606 // offset is the pixel coords of the zoom origin relative to the current center
8607 var scale = this.getZoomScale(zoom),
8608 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
8609 origin = this._getCenterLayerPoint()._add(offset);
8610
8611 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
8612 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
8613
8614 this
8615 .fire('movestart')
8616 .fire('zoomstart');
8617
8618 this._animateZoom(center, zoom, origin, scale, null, true);
8619
8620 return true;
8621 },
8622
8623 _animateZoom: function (center, zoom, origin, scale, delta, backwards) {
8624
8625 this._animatingZoom = true;
8626
8627 // put transform transition on all layers with leaflet-zoom-animated class
8628 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
8629
8630 // remember what center/zoom to set after animation
81328631 this._animateToCenter = center;
81338632 this._animateToZoom = zoom;
8134 this._animatingZoom = true;
8135
8633
8634 // disable any dragging during animation
81368635 if (L.Draggable) {
81378636 L.Draggable._disabled = true;
81388637 }
81398638
8639 this.fire('zoomanim', {
8640 center: center,
8641 zoom: zoom,
8642 origin: origin,
8643 scale: scale,
8644 delta: delta,
8645 backwards: backwards
8646 });
8647 },
8648
8649 _onZoomTransitionEnd: function () {
8650
8651 this._animatingZoom = false;
8652
8653 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
8654
8655 this._resetView(this._animateToCenter, this._animateToZoom, true, true);
8656
8657 if (L.Draggable) {
8658 L.Draggable._disabled = false;
8659 }
8660 }
8661 });
8662
8663
8664 /*
8665 Zoom animation logic for L.TileLayer.
8666 */
8667
8668 L.TileLayer.include({
8669 _animateZoom: function (e) {
8670 var firstFrame = false;
8671
8672 if (!this._animating) {
8673 this._animating = true;
8674 firstFrame = true;
8675 }
8676
8677 if (firstFrame) {
8678 this._prepareBgBuffer();
8679 }
8680
8681 var bg = this._bgBuffer;
8682
8683 if (firstFrame) {
8684 //prevent bg buffer from clearing right after zoom
8685 clearTimeout(this._clearBgBufferTimer);
8686
8687 // hack to make sure transform is updated before running animation
8688 L.Util.falseFn(bg.offsetWidth);
8689 }
8690
81408691 var transform = L.DomUtil.TRANSFORM,
8141 tileBg = this._tileBg;
8142
8143 clearTimeout(this._clearTileBgTimer);
8144
8145 L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
8146
8147 var scaleStr = L.DomUtil.getScaleString(scale, origin),
8148 oldTransform = tileBg.style[transform];
8149
8150 tileBg.style[transform] = backwardsTransform ?
8151 oldTransform + ' ' + scaleStr :
8152 scaleStr + ' ' + oldTransform;
8692 initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
8693 scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
8694
8695 bg.style[transform] = e.backwards ?
8696 scaleStr + ' ' + initialTransform :
8697 initialTransform + ' ' + scaleStr;
81538698 },
81548699
8155 _prepareTileBg: function () {
8156 var tilePane = this._tilePane,
8157 tileBg = this._tileBg;
8158
8159 // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
8160 if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
8161 this._getLoadedTilesPercentage(tilePane) < 0.5) {
8162
8163 tilePane.style.visibility = 'hidden';
8164 tilePane.empty = true;
8165 this._stopLoadingImages(tilePane);
8700 _endZoomAnim: function () {
8701 var front = this._tileContainer,
8702 bg = this._bgBuffer;
8703
8704 front.style.visibility = '';
8705 front.style.zIndex = 2;
8706
8707 bg.style.zIndex = 1;
8708
8709 // force reflow
8710 L.Util.falseFn(bg.offsetWidth);
8711
8712 this._animating = false;
8713 },
8714
8715 _clearBgBuffer: function () {
8716 var map = this._map;
8717
8718 if (map && !map._animatingZoom && !map.touchZoom._zooming) {
8719 this._bgBuffer.innerHTML = '';
8720 this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
8721 }
8722 },
8723
8724 _prepareBgBuffer: function () {
8725
8726 var front = this._tileContainer,
8727 bg = this._bgBuffer;
8728
8729 // if foreground layer doesn't have many tiles but bg layer does,
8730 // keep the existing bg layer and just zoom it some more
8731
8732 var bgLoaded = this._getLoadedTilesPercentage(bg),
8733 frontLoaded = this._getLoadedTilesPercentage(front);
8734
8735 if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
8736
8737 front.style.visibility = 'hidden';
8738 this._stopLoadingImages(front);
81668739 return;
81678740 }
81688741
8169 if (!tileBg) {
8170 tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
8171 tileBg.style.zIndex = 1;
8172 }
8173
8174 // prepare the background pane to become the main tile pane
8175 tileBg.style[L.DomUtil.TRANSFORM] = '';
8176 tileBg.style.visibility = 'hidden';
8177
8178 // tells tile layers to reinitialize their containers
8179 tileBg.empty = true; //new FG
8180 tilePane.empty = false; //new BG
8181
8182 //Switch out the current layer to be the new bg layer (And vice-versa)
8183 this._tilePane = this._panes.tilePane = tileBg;
8184 var newTileBg = this._tileBg = tilePane;
8185
8186 L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated');
8187
8188 this._stopLoadingImages(newTileBg);
8742 // prepare the buffer to become the front tile pane
8743 bg.style.visibility = 'hidden';
8744 bg.style[L.DomUtil.TRANSFORM] = '';
8745
8746 // switch out the current layer to be the new bg layer (and vice-versa)
8747 this._tileContainer = bg;
8748 bg = this._bgBuffer = front;
8749
8750 this._stopLoadingImages(bg);
81898751 },
81908752
81918753 _getLoadedTilesPercentage: function (container) {
82168778 tile.parentNode.removeChild(tile);
82178779 }
82188780 }
8219 },
8220
8221 _onZoomTransitionEnd: function () {
8222 this._restoreTileFront();
8223
8224 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
8225 L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
8226 this._animatingZoom = false;
8227 this._resetView(this._animateToCenter, this._animateToZoom, true, true);
8228
8229 if (L.Draggable) {
8230 L.Draggable._disabled = false;
8231 }
8232 },
8233
8234 _restoreTileFront: function () {
8235 this._tilePane.innerHTML = '';
8236 this._tilePane.style.visibility = '';
8237 this._tilePane.style.zIndex = 2;
8238 this._tileBg.style.zIndex = 1;
8239 },
8240
8241 _clearTileBg: function () {
8242 if (!this._animatingZoom && !this.touchZoom._zooming) {
8243 this._tileBg.innerHTML = '';
8244 }
82458781 }
82468782 });
82478783
82488784
8249 /*
8250 * Provides L.Map with convenient shortcuts for using browser geolocation features.
8251 */
8252
8253 L.Map.include({
8254 _defaultLocateOptions: {
8255 watch: false,
8256 setView: false,
8257 maxZoom: Infinity,
8258 timeout: 10000,
8259 maximumAge: 0,
8260 enableHighAccuracy: false
8261 },
8262
8263 locate: function (/*Object*/ options) {
8264
8265 options = this._locationOptions = L.extend(this._defaultLocateOptions, options);
8266
8267 if (!navigator.geolocation) {
8268 this._handleGeolocationError({
8269 code: 0,
8270 message: "Geolocation not supported."
8271 });
8272 return this;
8273 }
8274
8275 var onResponse = L.bind(this._handleGeolocationResponse, this),
8276 onError = L.bind(this._handleGeolocationError, this);
8277
8278 if (options.watch) {
8279 this._locationWatchId =
8280 navigator.geolocation.watchPosition(onResponse, onError, options);
8281 } else {
8282 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
8283 }
8284 return this;
8285 },
8286
8287 stopLocate: function () {
8288 if (navigator.geolocation) {
8289 navigator.geolocation.clearWatch(this._locationWatchId);
8290 }
8291 return this;
8292 },
8293
8294 _handleGeolocationError: function (error) {
8295 var c = error.code,
8296 message = error.message ||
8297 (c === 1 ? "permission denied" :
8298 (c === 2 ? "position unavailable" : "timeout"));
8299
8300 if (this._locationOptions.setView && !this._loaded) {
8301 this.fitWorld();
8302 }
8303
8304 this.fire('locationerror', {
8305 code: c,
8306 message: "Geolocation error: " + message + "."
8307 });
8308 },
8309
8310 _handleGeolocationResponse: function (pos) {
8311 var latAccuracy = 180 * pos.coords.accuracy / 4e7,
8312 lngAccuracy = latAccuracy * 2,
8313
8314 lat = pos.coords.latitude,
8315 lng = pos.coords.longitude,
8316 latlng = new L.LatLng(lat, lng),
8317
8318 sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
8319 ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
8320 bounds = new L.LatLngBounds(sw, ne),
8321
8322 options = this._locationOptions;
8323
8324 if (options.setView) {
8325 var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
8326 this.setView(latlng, zoom);
8327 }
8328
8329 this.fire('locationfound', {
8330 latlng: latlng,
8331 bounds: bounds,
8332 accuracy: pos.coords.accuracy
8333 });
8334 }
8335 });
8336
8337
8338 }(this, document));
8785 /*
8786 * Provides L.Map with convenient shortcuts for using browser geolocation features.
8787 */
8788
8789 L.Map.include({
8790 _defaultLocateOptions: {
8791 watch: false,
8792 setView: false,
8793 maxZoom: Infinity,
8794 timeout: 10000,
8795 maximumAge: 0,
8796 enableHighAccuracy: false
8797 },
8798
8799 locate: function (/*Object*/ options) {
8800
8801 options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
8802
8803 if (!navigator.geolocation) {
8804 this._handleGeolocationError({
8805 code: 0,
8806 message: 'Geolocation not supported.'
8807 });
8808 return this;
8809 }
8810
8811 var onResponse = L.bind(this._handleGeolocationResponse, this),
8812 onError = L.bind(this._handleGeolocationError, this);
8813
8814 if (options.watch) {
8815 this._locationWatchId =
8816 navigator.geolocation.watchPosition(onResponse, onError, options);
8817 } else {
8818 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
8819 }
8820 return this;
8821 },
8822
8823 stopLocate: function () {
8824 if (navigator.geolocation) {
8825 navigator.geolocation.clearWatch(this._locationWatchId);
8826 }
8827 if (this._locateOptions) {
8828 this._locateOptions.setView = false;
8829 }
8830 return this;
8831 },
8832
8833 _handleGeolocationError: function (error) {
8834 var c = error.code,
8835 message = error.message ||
8836 (c === 1 ? 'permission denied' :
8837 (c === 2 ? 'position unavailable' : 'timeout'));
8838
8839 if (this._locateOptions.setView && !this._loaded) {
8840 this.fitWorld();
8841 }
8842
8843 this.fire('locationerror', {
8844 code: c,
8845 message: 'Geolocation error: ' + message + '.'
8846 });
8847 },
8848
8849 _handleGeolocationResponse: function (pos) {
8850 var lat = pos.coords.latitude,
8851 lng = pos.coords.longitude,
8852 latlng = new L.LatLng(lat, lng),
8853
8854 latAccuracy = 180 * pos.coords.accuracy / 40075017,
8855 lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),
8856
8857 bounds = L.latLngBounds(
8858 [lat - latAccuracy, lng - lngAccuracy],
8859 [lat + latAccuracy, lng + lngAccuracy]),
8860
8861 options = this._locateOptions;
8862
8863 if (options.setView) {
8864 var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
8865 this.setView(latlng, zoom);
8866 }
8867
8868 var data = {
8869 latlng: latlng,
8870 bounds: bounds,
8871 };
8872
8873 for (var i in pos.coords) {
8874 if (typeof pos.coords[i] === 'number') {
8875 data[i] = pos.coords[i];
8876 }
8877 }
8878
8879 this.fire('locationfound', data);
8880 }
8881 });
8882
8883
8884 }(window, document));
44 .leaflet-marker-icon,
55 .leaflet-marker-shadow,
66 .leaflet-tile-pane,
7 .leaflet-tile-container,
78 .leaflet-overlay-pane,
89 .leaflet-shadow-pane,
910 .leaflet-marker-pane,
2627 -webkit-user-select: none;
2728 -moz-user-select: none;
2829 user-select: none;
30 -webkit-user-drag: none;
2931 }
3032 .leaflet-marker-icon,
3133 .leaflet-marker-shadow {
190192 /* general toolbar styles */
191193
192194 .leaflet-bar {
193 box-shadow: 0 0 8px rgba(0,0,0,0.4);
194 border: 1px solid #888;
195 -webkit-border-radius: 5px;
196 border-radius: 5px;
197 }
198 .leaflet-bar-part {
199 background-color: rgba(255, 255, 255, 0.8);
200 border-bottom: 1px solid #aaa;
201 }
202 .leaflet-bar-part-top {
203 -webkit-border-radius: 4px 4px 0 0;
204 border-radius: 4px 4px 0 0;
205 }
206 .leaflet-bar-part-bottom {
207 -webkit-border-radius: 0 0 4px 4px;
208 border-radius: 0 0 4px 4px;
209 border-bottom: none;
210 }
211
212 .leaflet-touch .leaflet-bar {
213 -webkit-border-radius: 10px;
214 border-radius: 10px;
215 }
216 .leaflet-touch .leaflet-bar-part {
217 border-bottom: 4px solid rgba(0,0,0,0.3);
218 }
219 .leaflet-touch .leaflet-bar-part-top {
220 -webkit-border-radius: 7px 7px 0 0;
221 border-radius: 7px 7px 0 0;
222 }
223 .leaflet-touch .leaflet-bar-part-bottom {
224 -webkit-border-radius: 0 0 7px 7px;
225 border-radius: 0 0 7px 7px;
226 border-bottom: none;
227 }
228
229
230 /* zoom control */
231
232 .leaflet-container .leaflet-control-zoom {
233 margin-left: 13px;
234 margin-top: 12px;
235 }
236 .leaflet-control-zoom a {
237 width: 22px;
238 height: 22px;
195 box-shadow: 0 1px 7px rgba(0,0,0,0.65);
196 -webkit-border-radius: 4px;
197 border-radius: 4px;
198 }
199 .leaflet-bar a {
200 background-color: #fff;
201 border-bottom: 1px solid #ccc;
202 width: 26px;
203 height: 26px;
204 line-height: 26px;
205 display: block;
239206 text-align: center;
240207 text-decoration: none;
241208 color: black;
242209 }
243 .leaflet-control-zoom a,
210 .leaflet-bar a,
244211 .leaflet-control-layers-toggle {
245212 background-position: 50% 50%;
246213 background-repeat: no-repeat;
247214 display: block;
248215 }
249 .leaflet-control-zoom a:hover {
250 background-color: #fff;
251 color: #777;
252 }
253 .leaflet-control-zoom-in {
254 font: bold 18px/24px Arial, Helvetica, sans-serif;
255 }
256 .leaflet-control-zoom-out {
257 font: bold 23px/20px Tahoma, Verdana, sans-serif;
258 }
259 .leaflet-control-zoom a.leaflet-control-zoom-disabled {
216 .leaflet-bar a:hover {
217 background-color: #f4f4f4;
218 }
219 .leaflet-bar a:first-child {
220 -webkit-border-top-left-radius: 4px;
221 border-top-left-radius: 4px;
222 -webkit-border-top-right-radius: 4px;
223 border-top-right-radius: 4px;
224 }
225 .leaflet-bar a:last-child {
226 -webkit-border-bottom-left-radius: 4px;
227 border-bottom-left-radius: 4px;
228 -webkit-border-bottom-right-radius: 4px;
229 border-bottom-right-radius: 4px;
230 border-bottom: none;
231 }
232 .leaflet-bar a.leaflet-disabled {
260233 cursor: default;
261 background-color: rgba(255, 255, 255, 0.8);
234 background-color: #f4f4f4;
262235 color: #bbb;
263236 }
264237
265 .leaflet-touch .leaflet-control-zoom a {
238 .leaflet-touch .leaflet-bar {
239 -webkit-border-radius: 10px;
240 border-radius: 10px;
241 }
242 .leaflet-touch .leaflet-bar a {
266243 width: 30px;
267244 height: 30px;
268245 }
246 .leaflet-touch .leaflet-bar a:first-child {
247 -webkit-border-top-left-radius: 7px;
248 border-top-left-radius: 7px;
249 -webkit-border-top-right-radius: 7px;
250 border-top-right-radius: 7px;
251 }
252 .leaflet-touch .leaflet-bar a:last-child {
253 -webkit-border-bottom-left-radius: 7px;
254 border-bottom-left-radius: 7px;
255 -webkit-border-bottom-right-radius: 7px;
256 border-bottom-right-radius: 7px;
257 border-bottom: none;
258 }
259
260
261 /* zoom control */
262
263 .leaflet-control-zoom-in {
264 font: bold 18px 'Lucida Console', Monaco, monospace;
265 }
266 .leaflet-control-zoom-out {
267 font: bold 22px 'Lucida Console', Monaco, monospace;
268 }
269
269270 .leaflet-touch .leaflet-control-zoom-in {
270 font-size: 24px;
271 line-height: 29px;
271 font-size: 22px;
272 line-height: 30px;
272273 }
273274 .leaflet-touch .leaflet-control-zoom-out {
274275 font-size: 28px;
275 line-height: 24px;
276 }
276 line-height: 30px;
277 }
278
277279
278280 /* layers control */
279281
280282 .leaflet-control-layers {
281283 box-shadow: 0 1px 7px rgba(0,0,0,0.4);
282284 background: #f8f8f9;
283 -webkit-border-radius: 8px;
284 border-radius: 8px;
285 -webkit-border-radius: 5px;
286 border-radius: 5px;
285287 }
286288 .leaflet-control-layers-toggle {
287289 background-image: url(images/layers.png);
288290 width: 36px;
289291 height: 36px;
292 }
293 .leaflet-retina .leaflet-control-layers-toggle {
294 background-image: url(images/layers-2x.png);
295 background-size: 26px 26px;
290296 }
291297 .leaflet-touch .leaflet-control-layers-toggle {
292298 width: 44px;
385391 .leaflet-popup-content-wrapper {
386392 padding: 1px;
387393 text-align: left;
388 -webkit-border-radius: 20px;
389 border-radius: 20px;
394 -webkit-border-radius: 12px;
395 border-radius: 12px;
390396 }
391397 .leaflet-popup-content {
392 margin: 14px 20px;
398 margin: 13px 19px;
393399 line-height: 1.4;
394400 }
395401 .leaflet-popup-content p {
403409 overflow: hidden;
404410 }
405411 .leaflet-popup-tip {
406 width: 15px;
407 height: 15px;
412 width: 17px;
413 height: 17px;
408414 padding: 1px;
409415
410 margin: -8px auto 0;
416 margin: -10px auto 0;
411417
412418 -webkit-transform: rotate(45deg);
413419 -moz-transform: rotate(45deg);
424430 position: absolute;
425431 top: 0;
426432 right: 0;
427 padding: 4px 5px 0 0;
433 padding: 4px 4px 0 0;
428434 text-align: center;
429435 width: 18px;
430436 height: 14px;
3434 .leaflet-control-layers {
3535 border: 3px solid #999;
3636 }
37 .leaflet-control-zoom a {
38 background-color: #eee;
39 }
40 .leaflet-control-zoom a:hover {
41 background-color: #fff;
42 }
4337 .leaflet-control-layers-toggle {
4438 }
4539 .leaflet-control-attribution,
00 /*
11 Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
2 (c) 2010-2013, Vladimir Agafonkin, CloudMade
2 (c) 2010-2013, Vladimir Agafonkin
3 (c) 2010-2011, CloudMade
34 */
4 (function(t,e,i){var n,o;typeof exports!=i+""?n=exports:(o=t.L,n={},n.noConflict=function(){return t.L=o,this},t.L=n),n.version="0.5.1",n.Util={extend:function(t){var e,i,n,o,s=Array.prototype.slice.call(arguments,1);for(i=0,n=s.length;n>i;i++){o=s[i]||{};for(e in o)o.hasOwnProperty(e)&&(t[e]=o[e])}return t},bind:function(t,e){var i=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return t.apply(e,i||arguments)}},stamp:function(){var t=0,e="_leaflet_id";return function(i){return i[e]=i[e]||++t,i[e]}}(),limitExecByInterval:function(t,e,n){var o,s;return function a(){var r=arguments;return o?(s=!0,i):(o=!0,setTimeout(function(){o=!1,s&&(a.apply(n,r),s=!1)},e),t.apply(n,r),i)}},falseFn:function(){return!1},formatNum:function(t,e){var i=Math.pow(10,e||5);return Math.round(t*i)/i},splitWords:function(t){return t.replace(/^\s+|\s+$/g,"").split(/\s+/)},setOptions:function(t,e){return t.options=n.extend({},t.options,e),t.options},getParamString:function(t,e){var i=[];for(var n in t)t.hasOwnProperty(n)&&i.push(n+"="+t[n]);return(e&&-1!==e.indexOf("?")?"&":"?")+i.join("&")},template:function(t,e){return t.replace(/\{ *([\w_]+) *\}/g,function(t,i){var n=e[i];if(!e.hasOwnProperty(i))throw Error("No value provided for variable "+t);return n})},isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function e(e){var i,n,o=["webkit","moz","o","ms"];for(i=0;o.length>i&&!n;i++)n=t[o[i]+e];return n}function o(e){var i=+new Date,n=Math.max(0,16-(i-s));return s=i+n,t.setTimeout(e,n)}var s=0,a=t.requestAnimationFrame||e("RequestAnimationFrame")||o,r=t.cancelAnimationFrame||e("CancelAnimationFrame")||e("CancelRequestAnimationFrame")||function(e){t.clearTimeout(e)};n.Util.requestAnimFrame=function(e,s,r,h){return e=n.bind(e,s),r&&a===o?(e(),i):a.call(t,e,h)},n.Util.cancelAnimFrame=function(e){e&&r.call(t,e)}}(),n.extend=n.Util.extend,n.bind=n.Util.bind,n.stamp=n.Util.stamp,n.setOptions=n.Util.setOptions,n.Class=function(){},n.Class.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this._initHooks&&this.callInitHooks()},i=function(){};i.prototype=this.prototype;var o=new i;o.constructor=e,e.prototype=o;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&(e[s]=this[s]);t.statics&&(n.extend(e,t.statics),delete t.statics),t.includes&&(n.Util.extend.apply(null,[o].concat(t.includes)),delete t.includes),t.options&&o.options&&(t.options=n.extend({},o.options,t.options)),n.extend(o,t),o._initHooks=[];var a=this;return o.callInitHooks=function(){if(!this._initHooksCalled){a.prototype.callInitHooks&&a.prototype.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=o._initHooks.length;e>t;t++)o._initHooks[t].call(this)}},e},n.Class.include=function(t){n.extend(this.prototype,t)},n.Class.mergeOptions=function(t){n.extend(this.prototype.options,t)},n.Class.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i)};var s="_leaflet_events";n.Mixin={},n.Mixin.Events={addEventListener:function(t,e,i){var o,a,r,h=this[s]=this[s]||{};if("object"==typeof t){for(o in t)t.hasOwnProperty(o)&&this.addEventListener(o,t[o],e);return this}for(t=n.Util.splitWords(t),a=0,r=t.length;r>a;a++)h[t[a]]=h[t[a]]||[],h[t[a]].push({action:e,context:i||this});return this},hasEventListeners:function(t){return s in this&&t in this[s]&&this[s][t].length>0},removeEventListener:function(t,e,i){var o,a,r,h,l,u=this[s];if("object"==typeof t){for(o in t)t.hasOwnProperty(o)&&this.removeEventListener(o,t[o],e);return this}for(t=n.Util.splitWords(t),a=0,r=t.length;r>a;a++)if(this.hasEventListeners(t[a]))for(h=u[t[a]],l=h.length-1;l>=0;l--)e&&h[l].action!==e||i&&h[l].context!==i||h.splice(l,1);return this},fireEvent:function(t,e){if(!this.hasEventListeners(t))return this;for(var i=n.extend({type:t,target:this},e),o=this[s][t].slice(),a=0,r=o.length;r>a;a++)o[a].action.call(o[a].context||this,i);return this}},n.Mixin.Events.on=n.Mixin.Events.addEventListener,n.Mixin.Events.off=n.Mixin.Events.removeEventListener,n.Mixin.Events.fire=n.Mixin.Events.fireEvent,function(){var o=!!t.ActiveXObject,s=o&&!t.XMLHttpRequest,a=o&&!e.querySelector,r=navigator.userAgent.toLowerCase(),h=-1!==r.indexOf("webkit"),l=-1!==r.indexOf("chrome"),u=-1!==r.indexOf("android"),c=-1!==r.search("android [23]"),_=typeof orientation!=i+"",d=t.navigator&&t.navigator.msPointerEnabled&&t.navigator.msMaxTouchPoints,p="devicePixelRatio"in t&&t.devicePixelRatio>1||"matchMedia"in t&&t.matchMedia("(min-resolution:144dpi)")&&t.matchMedia("(min-resolution:144dpi)").matches,m=e.documentElement,f=o&&"transition"in m.style,g="WebKitCSSMatrix"in t&&"m11"in new t.WebKitCSSMatrix,v="MozPerspective"in m.style,y="OTransition"in m.style,L=!t.L_DISABLE_3D&&(f||g||v||y),P=!t.L_NO_TOUCH&&function(){var t="ontouchstart";if(d||t in m)return!0;var i=e.createElement("div"),n=!1;return i.setAttribute?(i.setAttribute(t,"return;"),"function"==typeof i[t]&&(n=!0),i.removeAttribute(t),i=null,n):!1}();n.Browser={ie:o,ie6:s,ie7:a,webkit:h,android:u,android23:c,chrome:l,ie3d:f,webkit3d:g,gecko3d:v,opera3d:y,any3d:L,mobile:_,mobileWebkit:_&&h,mobileWebkit3d:_&&g,mobileOpera:_&&t.opera,touch:P,msTouch:d,retina:p}}(),n.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},n.Point.prototype={clone:function(){return new n.Point(this.x,this.y)},add:function(t){return this.clone()._add(n.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(n.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},distanceTo:function(t){t=n.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t.x===this.x&&t.y===this.y},toString:function(){return"Point("+n.Util.formatNum(this.x)+", "+n.Util.formatNum(this.y)+")"}},n.point=function(t,e,i){return t instanceof n.Point?t:n.Util.isArray(t)?new n.Point(t[0],t[1]):isNaN(t)?t:new n.Point(t,e,i)},n.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},n.Bounds.prototype={extend:function(t){return t=n.point(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new n.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new n.Point(this.min.x,this.max.y)},getTopRight:function(){return new n.Point(this.max.x,this.min.y)},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return t="number"==typeof t[0]||t instanceof n.Point?n.point(t):n.bounds(t),t instanceof n.Bounds?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=n.bounds(t);var e=this.min,i=this.max,o=t.min,s=t.max,a=s.x>=e.x&&o.x<=i.x,r=s.y>=e.y&&o.y<=i.y;return a&&r},isValid:function(){return!(!this.min||!this.max)}},n.bounds=function(t,e){return!t||t instanceof n.Bounds?t:new n.Bounds(t,e)},n.Transformation=function(t,e,i,n){this._a=t,this._b=e,this._c=i,this._d=n},n.Transformation.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return e=e||1,t.x=e*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return e=e||1,new n.Point((t.x/e-this._b)/this._a,(t.y/e-this._d)/this._c)}},n.DomUtil={get:function(t){return"string"==typeof t?e.getElementById(t):t},getStyle:function(t,i){var n=t.style[i];if(!n&&t.currentStyle&&(n=t.currentStyle[i]),(!n||"auto"===n)&&e.defaultView){var o=e.defaultView.getComputedStyle(t,null);n=o?o[i]:null}return"auto"===n?null:n},getViewportOffset:function(t){var i,o=0,s=0,a=t,r=e.body,h=n.Browser.ie7;do{if(o+=a.offsetTop||0,s+=a.offsetLeft||0,o+=parseInt(n.DomUtil.getStyle(a,"borderTopWidth"),10)||0,s+=parseInt(n.DomUtil.getStyle(a,"borderLeftWidth"),10)||0,i=n.DomUtil.getStyle(a,"position"),a.offsetParent===r&&"absolute"===i)break;if("fixed"===i){o+=r.scrollTop||0,s+=r.scrollLeft||0;break}a=a.offsetParent}while(a);a=t;do{if(a===r)break;o-=a.scrollTop||0,s-=a.scrollLeft||0,n.DomUtil.documentIsLtr()||!n.Browser.webkit&&!h||(s+=a.scrollWidth-a.clientWidth,h&&"hidden"!==n.DomUtil.getStyle(a,"overflow-y")&&"hidden"!==n.DomUtil.getStyle(a,"overflow")&&(s+=17)),a=a.parentNode}while(a);return new n.Point(s,o)},documentIsLtr:function(){return n.DomUtil._docIsLtrCached||(n.DomUtil._docIsLtrCached=!0,n.DomUtil._docIsLtr="ltr"===n.DomUtil.getStyle(e.body,"direction")),n.DomUtil._docIsLtr},create:function(t,i,n){var o=e.createElement(t);return o.className=i,n&&n.appendChild(o),o},disableTextSelection:function(){e.selection&&e.selection.empty&&e.selection.empty(),this._onselectstart||(this._onselectstart=e.onselectstart||null,e.onselectstart=n.Util.falseFn)},enableTextSelection:function(){e.onselectstart===n.Util.falseFn&&(e.onselectstart=this._onselectstart,this._onselectstart=null)},hasClass:function(t,e){return t.className.length>0&&RegExp("(^|\\s)"+e+"(\\s|$)").test(t.className)},addClass:function(t,e){n.DomUtil.hasClass(t,e)||(t.className+=(t.className?" ":"")+e)},removeClass:function(t,e){function i(t,i){return i===e?"":t}t.className=t.className.replace(/(\S+)\s*/g,i).replace(/(^\s+|\s+$)/,"")},setOpacity:function(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(o){}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}},testProp:function(t){for(var i=e.documentElement.style,n=0;t.length>n;n++)if(t[n]in i)return t[n];return!1},getTranslateString:function(t){var e=n.Browser.webkit3d,i="translate"+(e?"3d":"")+"(",o=(e?",0":"")+")";return i+t.x+"px,"+t.y+"px"+o},getScaleString:function(t,e){var i=n.DomUtil.getTranslateString(e.add(e.multiplyBy(-1*t))),o=" scale("+t+") ";return i+o},setPosition:function(t,e,i){t._leaflet_pos=e,!i&&n.Browser.any3d?(t.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(e),n.Browser.mobileWebkit3d&&(t.style.WebkitBackfaceVisibility="hidden")):(t.style.left=e.x+"px",t.style.top=e.y+"px")},getPosition:function(t){return t._leaflet_pos}},n.DomUtil.TRANSFORM=n.DomUtil.testProp(["transform","WebkitTransform","OTransform","MozTransform","msTransform"]),n.DomUtil.TRANSITION=n.DomUtil.testProp(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),n.DomUtil.TRANSITION_END="webkitTransition"===n.DomUtil.TRANSITION||"OTransition"===n.DomUtil.TRANSITION?n.DomUtil.TRANSITION+"End":"transitionend",n.LatLng=function(t,e){var i=parseFloat(t),n=parseFloat(e);if(isNaN(i)||isNaN(n))throw Error("Invalid LatLng object: ("+t+", "+e+")");this.lat=i,this.lng=n},n.extend(n.LatLng,{DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,MAX_MARGIN:1e-9}),n.LatLng.prototype={equals:function(t){if(!t)return!1;t=n.latLng(t);var e=Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng));return n.LatLng.MAX_MARGIN>=e},toString:function(t){return"LatLng("+n.Util.formatNum(this.lat,t)+", "+n.Util.formatNum(this.lng,t)+")"},distanceTo:function(t){t=n.latLng(t);var e=6378137,i=n.LatLng.DEG_TO_RAD,o=(t.lat-this.lat)*i,s=(t.lng-this.lng)*i,a=this.lat*i,r=t.lat*i,h=Math.sin(o/2),l=Math.sin(s/2),u=h*h+l*l*Math.cos(a)*Math.cos(r);return 2*e*Math.atan2(Math.sqrt(u),Math.sqrt(1-u))},wrap:function(t,e){var i=this.lng;return t=t||-180,e=e||180,i=(i+e)%(e-t)+(t>i||i===e?e:t),new n.LatLng(this.lat,i)}},n.latLng=function(t,e){return t instanceof n.LatLng?t:n.Util.isArray(t)?new n.LatLng(t[0],t[1]):isNaN(t)?t:new n.LatLng(t,e)},n.LatLngBounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},n.LatLngBounds.prototype={extend:function(t){return t="number"==typeof t[0]||"string"==typeof t[0]||t instanceof n.LatLng?n.latLng(t):n.latLngBounds(t),t instanceof n.LatLng?this._southWest||this._northEast?(this._southWest.lat=Math.min(t.lat,this._southWest.lat),this._southWest.lng=Math.min(t.lng,this._southWest.lng),this._northEast.lat=Math.max(t.lat,this._northEast.lat),this._northEast.lng=Math.max(t.lng,this._northEast.lng)):(this._southWest=new n.LatLng(t.lat,t.lng),this._northEast=new n.LatLng(t.lat,t.lng)):t instanceof n.LatLngBounds&&(this.extend(t._southWest),this.extend(t._northEast)),this},pad:function(t){var e=this._southWest,i=this._northEast,o=Math.abs(e.lat-i.lat)*t,s=Math.abs(e.lng-i.lng)*t;return new n.LatLngBounds(new n.LatLng(e.lat-o,e.lng-s),new n.LatLng(i.lat+o,i.lng+s))},getCenter:function(){return new n.LatLng((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new n.LatLng(this._northEast.lat,this._southWest.lng)},getSouthEast:function(){return new n.LatLng(this._southWest.lat,this._northEast.lng)},contains:function(t){t="number"==typeof t[0]||t instanceof n.LatLng?n.latLng(t):n.latLngBounds(t);var e,i,o=this._southWest,s=this._northEast;return t instanceof n.LatLngBounds?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=o.lat&&i.lat<=s.lat&&e.lng>=o.lng&&i.lng<=s.lng},intersects:function(t){t=n.latLngBounds(t);var e=this._southWest,i=this._northEast,o=t.getSouthWest(),s=t.getNorthEast(),a=s.lat>=e.lat&&o.lat<=i.lat,r=s.lng>=e.lng&&o.lng<=i.lng;return a&&r},toBBoxString:function(){var t=this._southWest,e=this._northEast;return[t.lng,t.lat,e.lng,e.lat].join(",")},equals:function(t){return t?(t=n.latLngBounds(t),this._southWest.equals(t.getSouthWest())&&this._northEast.equals(t.getNorthEast())):!1},isValid:function(){return!(!this._southWest||!this._northEast)}},n.latLngBounds=function(t,e){return!t||t instanceof n.LatLngBounds?t:new n.LatLngBounds(t,e)},n.Projection={},n.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(t){var e=n.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,o=Math.max(Math.min(i,t.lat),-i),s=t.lng*e,a=o*e;return a=Math.log(Math.tan(Math.PI/4+a/2)),new n.Point(s,a)},unproject:function(t){var e=n.LatLng.RAD_TO_DEG,i=t.x*e,o=(2*Math.atan(Math.exp(t.y))-Math.PI/2)*e;return new n.LatLng(o,i)}},n.Projection.LonLat={project:function(t){return new n.Point(t.lng,t.lat)},unproject:function(t){return new n.LatLng(t.y,t.x)}},n.CRS={latLngToPoint:function(t,e){var i=this.projection.project(t),n=this.scale(e);return this.transformation._transform(i,n)},pointToLatLng:function(t,e){var i=this.scale(e),n=this.transformation.untransform(t,i);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},scale:function(t){return 256*Math.pow(2,t)}},n.CRS.Simple=n.extend({},n.CRS,{projection:n.Projection.LonLat,transformation:new n.Transformation(1,0,-1,0),scale:function(t){return Math.pow(2,t)}}),n.CRS.EPSG3857=n.extend({},n.CRS,{code:"EPSG:3857",projection:n.Projection.SphericalMercator,transformation:new n.Transformation(.5/Math.PI,.5,-.5/Math.PI,.5),project:function(t){var e=this.projection.project(t),i=6378137;return e.multiplyBy(i)}}),n.CRS.EPSG900913=n.extend({},n.CRS.EPSG3857,{code:"EPSG:900913"}),n.CRS.EPSG4326=n.extend({},n.CRS,{code:"EPSG:4326",projection:n.Projection.LonLat,transformation:new n.Transformation(1/360,.5,-1/360,.5)}),n.Map=n.Class.extend({includes:n.Mixin.Events,options:{crs:n.CRS.EPSG3857,fadeAnimation:n.DomUtil.TRANSITION&&!n.Browser.android23,trackResize:!0,markerZoomAnimation:n.DomUtil.TRANSITION&&n.Browser.any3d},initialize:function(t,e){e=n.setOptions(this,e),this._initContainer(t),this._initLayout(),this.callInitHooks(),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),e.center&&e.zoom!==i&&this.setView(n.latLng(e.center),e.zoom,!0),this._initLayers(e.layers)},setView:function(t,e){return this._resetView(n.latLng(t),this._limitZoom(e)),this},setZoom:function(t){return this.setView(this.getCenter(),t)},zoomIn:function(t){return this.setZoom(this._zoom+(t||1))},zoomOut:function(t){return this.setZoom(this._zoom-(t||1))},fitBounds:function(t){var e=this.getBoundsZoom(t);return this.setView(n.latLngBounds(t).getCenter(),e)},fitWorld:function(){var t=new n.LatLng(-60,-170),e=new n.LatLng(85,179);return this.fitBounds(new n.LatLngBounds(t,e))},panTo:function(t){return this.setView(t,this._zoom)},panBy:function(t){return this.fire("movestart"),this._rawPanBy(n.point(t)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(t){if(t=n.latLngBounds(t),this.options.maxBounds=t,!t)return this._boundsMinZoom=null,this;var e=this.getBoundsZoom(t,!0);return this._boundsMinZoom=e,this._loaded&&(e>this._zoom?this.setView(t.getCenter(),e):this.panInsideBounds(t)),this},panInsideBounds:function(t){t=n.latLngBounds(t);var e=this.getBounds(),i=this.project(e.getSouthWest()),o=this.project(e.getNorthEast()),s=this.project(t.getSouthWest()),a=this.project(t.getNorthEast()),r=0,h=0;return o.y<a.y&&(h=a.y-o.y),o.x>a.x&&(r=a.x-o.x),i.y>s.y&&(h=s.y-i.y),i.x<s.x&&(r=s.x-i.x),this.panBy(new n.Point(r,h,!0))},addLayer:function(t){var e=n.stamp(t);return this._layers[e]?this:(this._layers[e]=t,!t.options||isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[e]=t,this._updateZoomLevels()),this.options.zoomAnimation&&n.TileLayer&&t instanceof n.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,t.on("load",this._onTileLayerLoad,this)),this.whenReady(function(){t.onAdd(this),this.fire("layeradd",{layer:t})},this),this)},removeLayer:function(t){var e=n.stamp(t);if(this._layers[e])return t.onRemove(this),delete this._layers[e],this._zoomBoundLayers[e]&&(delete this._zoomBoundLayers[e],this._updateZoomLevels()),this.options.zoomAnimation&&n.TileLayer&&t instanceof n.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,t.off("load",this._onTileLayerLoad,this)),this.fire("layerremove",{layer:t})},hasLayer:function(t){var e=n.stamp(t);return this._layers.hasOwnProperty(e)},invalidateSize:function(t){var e=this.getSize();if(this._sizeChanged=!0,this.options.maxBounds&&this.setMaxBounds(this.options.maxBounds),!this._loaded)return this;var i=e._subtract(this.getSize())._divideBy(2)._round();return t===!0?this.panBy(i):(this._rawPanBy(i),this.fire("move"),clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(n.bind(this.fire,this,"moveend"),200)),this},addHandler:function(t,e){return e?(this[t]=new e(this),this.options[t]&&this[t].enable(),this):i},getCenter:function(){return this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new n.LatLngBounds(e,i)},getMinZoom:function(){var t=this.options.minZoom||0,e=this._layersMinZoom||0,i=this._boundsMinZoom||0;return Math.max(t,e,i)},getMaxZoom:function(){var t=this.options.maxZoom===i?1/0:this.options.maxZoom,e=this._layersMaxZoom===i?1/0:this._layersMaxZoom;return Math.min(t,e)},getBoundsZoom:function(t,e){t=n.latLngBounds(t);var i,o,s,a=this.getSize(),r=this.options.minZoom||0,h=this.getMaxZoom(),l=t.getNorthEast(),u=t.getSouthWest(),c=!0;e&&r--;do r++,o=this.project(l,r),s=this.project(u,r),i=new n.Point(Math.abs(o.x-s.x),Math.abs(s.y-o.y)),c=e?i.x<a.x||i.y<a.y:i.x<=a.x&&i.y<=a.y;while(c&&h>=r);return c&&e?null:e?r:r-1},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new n.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(){var t=this._getTopLeftPoint();return new n.Bounds(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t){var e=this.options.crs;return e.scale(t)/e.scale(this._zoom)},getScaleZoom:function(t){return this._zoom+Math.log(t)/Math.LN2},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(n.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(n.point(t),e)},layerPointToLatLng:function(t){var e=n.point(t).add(this._initialTopLeftPoint);return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(n.latLng(t))._round();return e._subtract(this._initialTopLeftPoint)},containerPointToLayerPoint:function(t){return n.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return n.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(n.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(n.latLng(t)))},mouseEventToContainerPoint:function(t){return n.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=n.DomUtil.get(t);if(e._leaflet)throw Error("Map container is already initialized.");e._leaflet=!0},_initLayout:function(){var t=this._container;n.DomUtil.addClass(t,"leaflet-container"),n.Browser.touch&&n.DomUtil.addClass(t,"leaflet-touch"),this.options.fadeAnimation&&n.DomUtil.addClass(t,"leaflet-fade-anim");var e=n.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._mapPane=t.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=t.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),t.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),t.shadowPane=this._createPane("leaflet-shadow-pane"),t.overlayPane=this._createPane("leaflet-overlay-pane"),t.markerPane=this._createPane("leaflet-marker-pane"),t.popupPane=this._createPane("leaflet-popup-pane");var e=" leaflet-zoom-hide";this.options.markerZoomAnimation||(n.DomUtil.addClass(t.markerPane,e),n.DomUtil.addClass(t.shadowPane,e),n.DomUtil.addClass(t.popupPane,e))},_createPane:function(t,e){return n.DomUtil.create("div",t,e||this._panes.objectsPane)},_initLayers:function(t){t=t?n.Util.isArray(t)?t:[t]:[],this._layers={},this._zoomBoundLayers={},this._tileLayersNum=0;var e,i;for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},_resetView:function(t,e,i,o){var s=this._zoom!==e;o||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=e,this._initialTopLeftPoint=this._getNewTopLeftPoint(t),i?this._initialTopLeftPoint._add(this._getMapPanePos()):n.DomUtil.setPosition(this._mapPane,new n.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum;var a=!this._loaded;this._loaded=!0,this.fire("viewreset",{hard:!i}),this.fire("move"),(s||o)&&this.fire("zoomend"),this.fire("moveend",{hard:!i}),a&&this.fire("load")},_rawPanBy:function(t){n.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_updateZoomLevels:function(){var t,e=1/0,n=-1/0;for(t in this._zoomBoundLayers)if(this._zoomBoundLayers.hasOwnProperty(t)){var o=this._zoomBoundLayers[t];isNaN(o.options.minZoom)||(e=Math.min(e,o.options.minZoom)),isNaN(o.options.maxZoom)||(n=Math.max(n,o.options.maxZoom))}t===i?this._layersMaxZoom=this._layersMinZoom=i:(this._layersMaxZoom=n,this._layersMinZoom=e)},_initEvents:function(){if(n.DomEvent){n.DomEvent.on(this._container,"click",this._onMouseClick,this);var e,i,o=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"];for(e=0,i=o.length;i>e;e++)n.DomEvent.on(this._container,o[e],this._fireMouseEvent,this);this.options.trackResize&&n.DomEvent.on(t,"resize",this._onResize,this)}},_onResize:function(){n.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=n.Util.requestAnimFrame(this.invalidateSize,this,!1,this._container)},_onMouseClick:function(t){!this._loaded||this.dragging&&this.dragging.moved()||(this.fire("preclick"),this._fireMouseEvent(t))},_fireMouseEvent:function(t){if(this._loaded){var e=t.type;if(e="mouseenter"===e?"mouseover":"mouseleave"===e?"mouseout":e,this.hasEventListeners(e)){"contextmenu"===e&&n.DomEvent.preventDefault(t);var i=this.mouseEventToContainerPoint(t),o=this.containerPointToLayerPoint(i),s=this.layerPointToLatLng(o);this.fire(e,{latlng:s,layerPoint:o,containerPoint:i,originalEvent:t})}}},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this._tileBg&&(clearTimeout(this._clearTileBgTimer),this._clearTileBgTimer=setTimeout(n.bind(this._clearTileBg,this),500))},whenReady:function(t,e){return this._loaded?t.call(e||this,this):this.on("load",t,e),this},_getMapPanePos:function(){return n.DomUtil.getPosition(this._mapPane)},_getTopLeftPoint:function(){if(!this._loaded)throw Error("Set map center and zoom first.");return this._initialTopLeftPoint.subtract(this._getMapPanePos())},_getNewTopLeftPoint:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._round()},_latLngToNewLayerPoint:function(t,e,i){var n=this._getNewTopLeftPoint(i,e).add(this._getMapPanePos());return this.project(t,e)._subtract(n)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom();return Math.max(e,Math.min(i,t))}}),n.map=function(t,e){return new n.Map(t,e)},n.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.3142,R_MAJOR:6378137,project:function(t){var e=n.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,o=Math.max(Math.min(i,t.lat),-i),s=this.R_MAJOR,a=this.R_MINOR,r=t.lng*e*s,h=o*e,l=a/s,u=Math.sqrt(1-l*l),c=u*Math.sin(h);c=Math.pow((1-c)/(1+c),.5*u);var _=Math.tan(.5*(.5*Math.PI-h))/c;return h=-a*Math.log(_),new n.Point(r,h)},unproject:function(t){for(var e,i=n.LatLng.RAD_TO_DEG,o=this.R_MAJOR,s=this.R_MINOR,a=t.x*i/o,r=s/o,h=Math.sqrt(1-r*r),l=Math.exp(-t.y/s),u=Math.PI/2-2*Math.atan(l),c=15,_=1e-7,d=c,p=.1;Math.abs(p)>_&&--d>0;)e=h*Math.sin(u),p=Math.PI/2-2*Math.atan(l*Math.pow((1-e)/(1+e),.5*h))-u,u+=p;return new n.LatLng(u*i,a)}},n.CRS.EPSG3395=n.extend({},n.CRS,{code:"EPSG:3395",projection:n.Projection.Mercator,transformation:function(){var t=n.Projection.Mercator,e=t.R_MAJOR,i=t.R_MINOR;return new n.Transformation(.5/(Math.PI*e),.5,-.5/(Math.PI*i),.5)}()}),n.TileLayer=n.Class.extend({includes:n.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",zoomOffset:0,opacity:1,unloadInvisibleTiles:n.Browser.mobile,updateWhenIdle:n.Browser.mobile},initialize:function(t,e){e=n.setOptions(this,e),e.detectRetina&&n.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomOffset++,e.minZoom>0&&e.minZoom--,this.options.maxZoom--),this._url=t;var i=this.options.subdomains;"string"==typeof i&&(this.options.subdomains=i.split(""))},onAdd:function(t){this._map=t,this._initContainer(),this._createTileProto(),t.on({viewreset:this._resetCallback,moveend:this._update},this),this.options.updateWhenIdle||(this._limitedUpdate=n.Util.limitExecByInterval(this._update,150,this),t.on("move",this._limitedUpdate,this)),this._reset(),this._update()},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._container.parentNode.removeChild(this._container),t.off({viewreset:this._resetCallback,moveend:this._update},this),this.options.updateWhenIdle||t.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var t=this._map._panes.tilePane;return this._container&&(t.appendChild(this._container),this._setAutoZIndex(t,Math.max)),this},bringToBack:function(){var t=this._map._panes.tilePane;return this._container&&(t.insertBefore(this._container,t.firstChild),this._setAutoZIndex(t,Math.min)),this},getAttribution:function(){return this.options.attribution},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},redraw:function(){return this._map&&(this._map._panes.tilePane.empty=!1,this._reset(!0),this._update()),this},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t,e){var i,n,o,s=t.children,a=-e(1/0,-1/0);for(n=0,o=s.length;o>n;n++)s[n]!==this._container&&(i=parseInt(s[n].style.zIndex,10),isNaN(i)||(a=e(a,i)));this.options.zIndex=this._container.style.zIndex=(isFinite(a)?a:0)+e(1,-1)},_updateOpacity:function(){n.DomUtil.setOpacity(this._container,this.options.opacity);var t,e=this._tiles;if(n.Browser.webkit)for(t in e)e.hasOwnProperty(t)&&(e[t].style.webkitTransform+=" translate(0,0)")},_initContainer:function(){var t=this._map._panes.tilePane;(!this._container||t.empty)&&(this._container=n.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),t.appendChild(this._container),1>this.options.opacity&&this._updateOpacity())},_resetCallback:function(t){this._reset(t.hard)},_reset:function(t){var e=this._tiles;for(var i in e)e.hasOwnProperty(i)&&this.fire("tileunload",{tile:e[i]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),t&&this._container&&(this._container.innerHTML=""),this._initContainer()},_update:function(){if(this._map){var t=this._map.getPixelBounds(),e=this._map.getZoom(),i=this.options.tileSize;if(!(e>this.options.maxZoom||this.options.minZoom>e)){var o=new n.Point(Math.floor(t.min.x/i),Math.floor(t.min.y/i)),s=new n.Point(Math.floor(t.max.x/i),Math.floor(t.max.y/i)),a=new n.Bounds(o,s);this._addTilesFromCenterOut(a),(this.options.unloadInvisibleTiles||this.options.reuseTiles)&&this._removeOtherTiles(a)}}},_addTilesFromCenterOut:function(t){var i,o,s,a=[],r=t.getCenter();for(i=t.min.y;t.max.y>=i;i++)for(o=t.min.x;t.max.x>=o;o++)s=new n.Point(o,i),this._tileShouldBeLoaded(s)&&a.push(s);var h=a.length;if(0!==h){a.sort(function(t,e){return t.distanceTo(r)-e.distanceTo(r)});var l=e.createDocumentFragment();for(this._tilesToLoad||this.fire("loading"),this._tilesToLoad+=h,o=0;h>o;o++)this._addTile(a[o],l);this._container.appendChild(l)}},_tileShouldBeLoaded:function(t){if(t.x+":"+t.y in this._tiles)return!1;if(!this.options.continuousWorld){var e=this._getWrapTileNum();if(this.options.noWrap&&(0>t.x||t.x>=e)||0>t.y||t.y>=e)return!1}return!0},_removeOtherTiles:function(t){var e,i,n,o;for(o in this._tiles)this._tiles.hasOwnProperty(o)&&(e=o.split(":"),i=parseInt(e[0],10),n=parseInt(e[1],10),(t.min.x>i||i>t.max.x||t.min.y>n||n>t.max.y)&&this._removeTile(o))},_removeTile:function(t){var e=this._tiles[t];this.fire("tileunload",{tile:e,url:e.src}),this.options.reuseTiles?(n.DomUtil.removeClass(e,"leaflet-tile-loaded"),this._unusedTiles.push(e)):e.parentNode===this._container&&this._container.removeChild(e),n.Browser.android||(e.src=n.Util.emptyImageUrl),delete this._tiles[t]},_addTile:function(t,e){var i=this._getTilePos(t),o=this._getTile();n.DomUtil.setPosition(o,i,n.Browser.chrome||n.Browser.android23),this._tiles[t.x+":"+t.y]=o,this._loadTile(o,t),o.parentNode!==this._container&&e.appendChild(o)
5 },_getZoomForUrl:function(){var t=this.options,e=this._map.getZoom();return t.zoomReverse&&(e=t.maxZoom-e),e+t.zoomOffset},_getTilePos:function(t){var e=this._map.getPixelOrigin(),i=this.options.tileSize;return t.multiplyBy(i).subtract(e)},getTileUrl:function(t){return this._adjustTilePoint(t),n.Util.template(this._url,n.extend({s:this._getSubdomain(t),z:this._getZoomForUrl(),x:t.x,y:t.y},this.options))},_getWrapTileNum:function(){return Math.pow(2,this._getZoomForUrl())},_adjustTilePoint:function(t){var e=this._getWrapTileNum();this.options.continuousWorld||this.options.noWrap||(t.x=(t.x%e+e)%e),this.options.tms&&(t.y=e-t.y-1)},_getSubdomain:function(t){var e=(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_createTileProto:function(){var t=this._tileImg=n.DomUtil.create("img","leaflet-tile");t.style.width=t.style.height=this.options.tileSize+"px",t.galleryimg="no"},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var t=this._unusedTiles.pop();return this._resetTile(t),t}return this._createTile()},_resetTile:function(){},_createTile:function(){var t=this._tileImg.cloneNode(!1);return t.onselectstart=t.onmousemove=n.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t.onload=this._tileOnLoad,t.onerror=this._tileOnError,t.src=this.getTileUrl(e)},_tileLoaded:function(){this._tilesToLoad--,this._tilesToLoad||this.fire("load")},_tileOnLoad:function(){var t=this._layer;this.src!==n.Util.emptyImageUrl&&(n.DomUtil.addClass(this,"leaflet-tile-loaded"),t.fire("tileload",{tile:this,url:this.src})),t._tileLoaded()},_tileOnError:function(){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var e=t.options.errorTileUrl;e&&(this.src=e),t._tileLoaded()}}),n.tileLayer=function(t,e){return new n.TileLayer(t,e)},n.TileLayer.WMS=n.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",version:"1.1.1",layers:"",styles:"",format:"image/jpeg",transparent:!1},initialize:function(t,e){this._url=t;var i=n.extend({},this.defaultWmsParams);i.width=i.height=e.detectRetina&&n.Browser.retina?2*this.options.tileSize:this.options.tileSize;for(var o in e)this.options.hasOwnProperty(o)||(i[o]=e[o]);this.wmsParams=i,n.setOptions(this,e)},onAdd:function(t){var e=parseFloat(this.wmsParams.version)>=1.3?"crs":"srs";this.wmsParams[e]=t.options.crs.code,n.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t,e){this._adjustTilePoint(t);var i=this._map,o=i.options.crs,s=this.options.tileSize,a=t.multiplyBy(s),r=a.add(new n.Point(s,s)),h=o.project(i.unproject(a,e)),l=o.project(i.unproject(r,e)),u=[h.x,l.y,l.x,h.y].join(","),c=n.Util.template(this._url,{s:this._getSubdomain(t)});return c+n.Util.getParamString(this.wmsParams,c)+"&bbox="+u},setParams:function(t,e){return n.extend(this.wmsParams,t),e||this.redraw(),this}}),n.tileLayer.wms=function(t,e){return new n.TileLayer.WMS(t,e)},n.TileLayer.Canvas=n.TileLayer.extend({options:{async:!1},initialize:function(t){n.setOptions(this,t)},redraw:function(){var t=this._tiles;for(var e in t)t.hasOwnProperty(e)&&this._redrawTile(t[e])},_redrawTile:function(t){this.drawTile(t,t._tilePoint,this._map._zoom)},_createTileProto:function(){var t=this._canvasProto=n.DomUtil.create("canvas","leaflet-tile");t.width=t.height=this.options.tileSize},_createTile:function(){var t=this._canvasProto.cloneNode(!1);return t.onselectstart=t.onmousemove=n.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t._tilePoint=e,this._redrawTile(t),this.options.async||this.tileDrawn(t)},drawTile:function(){},tileDrawn:function(t){this._tileOnLoad.call(t)}}),n.tileLayer.canvas=function(t){return new n.TileLayer.Canvas(t)},n.ImageOverlay=n.Class.extend({includes:n.Mixin.Events,options:{opacity:1},initialize:function(t,e,i){this._url=t,this._bounds=n.latLngBounds(e),n.setOptions(this,i)},onAdd:function(t){this._map=t,this._image||this._initImage(),t._panes.overlayPane.appendChild(this._image),t.on("viewreset",this._reset,this),t.options.zoomAnimation&&n.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._image),t.off("viewreset",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var t=this._map._panes.overlayPane;return this._image&&t.insertBefore(this._image,t.firstChild),this},_initImage:function(){this._image=n.DomUtil.create("img","leaflet-image-layer"),this._map.options.zoomAnimation&&n.Browser.any3d?n.DomUtil.addClass(this._image,"leaflet-zoom-animated"):n.DomUtil.addClass(this._image,"leaflet-zoom-hide"),this._updateOpacity(),n.extend(this._image,{galleryimg:"no",onselectstart:n.Util.falseFn,onmousemove:n.Util.falseFn,onload:n.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(t){var e=this._map,i=this._image,o=e.getZoomScale(t.zoom),s=this._bounds.getNorthWest(),a=this._bounds.getSouthEast(),r=e._latLngToNewLayerPoint(s,t.zoom,t.center),h=e._latLngToNewLayerPoint(a,t.zoom,t.center)._subtract(r),l=r._add(h._multiplyBy(.5*(1-1/o)));i.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(l)+" scale("+o+") "},_reset:function(){var t=this._image,e=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),i=this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(e);n.DomUtil.setPosition(t,e),t.style.width=i.x+"px",t.style.height=i.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){n.DomUtil.setOpacity(this._image,this.options.opacity)}}),n.imageOverlay=function(t,e,i){return new n.ImageOverlay(t,e,i)},n.Icon=n.Class.extend({options:{className:""},initialize:function(t){n.setOptions(this,t)},createIcon:function(){return this._createIcon("icon")},createShadow:function(){return this._createIcon("shadow")},_createIcon:function(t){var e=this._getIconUrl(t);if(!e){if("icon"===t)throw Error("iconUrl not set in Icon options (see the docs).");return null}var i=this._createImg(e);return this._setIconStyles(i,t),i},_setIconStyles:function(t,e){var i,o=this.options,s=n.point(o[e+"Size"]);i="shadow"===e?n.point(o.shadowAnchor||o.iconAnchor):n.point(o.iconAnchor),!i&&s&&(i=s.divideBy(2,!0)),t.className="leaflet-marker-"+e+" "+o.className,i&&(t.style.marginLeft=-i.x+"px",t.style.marginTop=-i.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t){var i;return n.Browser.ie6?(i=e.createElement("div"),i.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+t+'")'):(i=e.createElement("img"),i.src=t),i},_getIconUrl:function(t){return n.Browser.retina&&this.options[t+"RetinaUrl"]?this.options[t+"RetinaUrl"]:this.options[t+"Url"]}}),n.icon=function(t){return new n.Icon(t)},n.Icon.Default=n.Icon.extend({options:{iconSize:new n.Point(25,41),iconAnchor:new n.Point(12,41),popupAnchor:new n.Point(1,-34),shadowSize:new n.Point(41,41)},_getIconUrl:function(t){var e=t+"Url";if(this.options[e])return this.options[e];n.Browser.retina&&"icon"===t&&(t+="@2x");var i=n.Icon.Default.imagePath;if(!i)throw Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return i+"/marker-"+t+".png"}}),n.Icon.Default.imagePath=function(){var t,i,n,o,s=e.getElementsByTagName("script"),a=/\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;for(t=0,i=s.length;i>t;t++)if(n=s[t].src,o=n.match(a))return n.split(a)[0]+"/images"}(),n.Marker=n.Class.extend({includes:n.Mixin.Events,options:{icon:new n.Icon.Default,title:"",clickable:!0,draggable:!1,zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250},initialize:function(t,e){n.setOptions(this,e),this._latlng=n.latLng(t)},onAdd:function(t){this._map=t,t.on("viewreset",this.update,this),this._initIcon(),this.update(),t.options.zoomAnimation&&t.options.markerZoomAnimation&&t.on("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._removeIcon(),this.fire("remove"),t.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=n.latLng(t),this.update(),this.fire("move",{latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update(),this},setIcon:function(t){return this._map&&this._removeIcon(),this.options.icon=t,this._map&&(this._initIcon(),this.update()),this},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e=this._map,i=e.options.zoomAnimation&&e.options.markerZoomAnimation,o=i?"leaflet-zoom-animated":"leaflet-zoom-hide",s=!1;this._icon||(this._icon=t.icon.createIcon(),t.title&&(this._icon.title=t.title),this._initInteraction(),s=1>this.options.opacity,n.DomUtil.addClass(this._icon,o),t.riseOnHover&&n.DomEvent.on(this._icon,"mouseover",this._bringToFront,this).on(this._icon,"mouseout",this._resetZIndex,this)),this._shadow||(this._shadow=t.icon.createShadow(),this._shadow&&(n.DomUtil.addClass(this._shadow,o),s=1>this.options.opacity)),s&&this._updateOpacity();var a=this._map._panes;a.markerPane.appendChild(this._icon),this._shadow&&a.shadowPane.appendChild(this._shadow)},_removeIcon:function(){var t=this._map._panes;this.options.riseOnHover&&n.DomEvent.off(this._icon,"mouseover",this._bringToFront).off(this._icon,"mouseout",this._resetZIndex),t.markerPane.removeChild(this._icon),this._shadow&&t.shadowPane.removeChild(this._shadow),this._icon=this._shadow=null},_setPos:function(t){n.DomUtil.setPosition(this._icon,t),this._shadow&&n.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPos(e)},_initInteraction:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];n.DomUtil.addClass(t,"leaflet-clickable"),n.DomEvent.on(t,"click",this._onMouseClick,this);for(var i=0;e.length>i;i++)n.DomEvent.on(t,e[i],this._fireMouseEvent,this);n.Handler.MarkerDrag&&(this.dragging=new n.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())}},_onMouseClick:function(t){var e=this.dragging&&this.dragging.moved();(this.hasEventListeners(t.type)||e)&&n.DomEvent.stopPropagation(t),e||(this.dragging&&this.dragging._enabled||!this._map.dragging||!this._map.dragging.moved())&&this.fire(t.type,{originalEvent:t})},_fireMouseEvent:function(t){this.fire(t.type,{originalEvent:t}),"contextmenu"===t.type&&this.hasEventListeners(t.type)&&n.DomEvent.preventDefault(t),"mousedown"!==t.type&&n.DomEvent.stopPropagation(t)},setOpacity:function(t){this.options.opacity=t,this._map&&this._updateOpacity()},_updateOpacity:function(){n.DomUtil.setOpacity(this._icon,this.options.opacity),this._shadow&&n.DomUtil.setOpacity(this._shadow,this.options.opacity)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)}}),n.marker=function(t,e){return new n.Marker(t,e)},n.DivIcon=n.Icon.extend({options:{iconSize:new n.Point(12,12),className:"leaflet-div-icon"},createIcon:function(){var t=e.createElement("div"),i=this.options;return i.html&&(t.innerHTML=i.html),i.bgPos&&(t.style.backgroundPosition=-i.bgPos.x+"px "+-i.bgPos.y+"px"),this._setIconStyles(t,"icon"),t},createShadow:function(){return null}}),n.divIcon=function(t){return new n.DivIcon(t)},n.Map.mergeOptions({closePopupOnClick:!0}),n.Popup=n.Class.extend({includes:n.Mixin.Events,options:{minWidth:50,maxWidth:300,maxHeight:null,autoPan:!0,closeButton:!0,offset:new n.Point(0,6),autoPanPadding:new n.Point(5,5),className:"",zoomAnimation:!0},initialize:function(t,e){n.setOptions(this,t),this._source=e,this._animated=n.Browser.any3d&&this.options.zoomAnimation},onAdd:function(t){this._map=t,this._container||this._initLayout(),this._updateContent();var e=t.options.fadeAnimation;e&&n.DomUtil.setOpacity(this._container,0),t._panes.popupPane.appendChild(this._container),t.on("viewreset",this._updatePosition,this),this._animated&&t.on("zoomanim",this._zoomAnimation,this),t.options.closePopupOnClick&&t.on("preclick",this._close,this),this._update(),e&&n.DomUtil.setOpacity(this._container,1)},addTo:function(t){return t.addLayer(this),this},openOn:function(t){return t.openPopup(this),this},onRemove:function(t){t._panes.popupPane.removeChild(this._container),n.Util.falseFn(this._container.offsetWidth),t.off({viewreset:this._updatePosition,preclick:this._close,zoomanim:this._zoomAnimation},this),t.options.fadeAnimation&&n.DomUtil.setOpacity(this._container,0),this._map=null},setLatLng:function(t){return this._latlng=n.latLng(t),this._update(),this},setContent:function(t){return this._content=t,this._update(),this},_close:function(){var t=this._map;t&&(t._popup=null,t.removeLayer(this).fire("popupclose",{popup:this}))},_initLayout:function(){var t,e="leaflet-popup",i=e+" "+this.options.className+" leaflet-zoom-"+(this._animated?"animated":"hide"),o=this._container=n.DomUtil.create("div",i);this.options.closeButton&&(t=this._closeButton=n.DomUtil.create("a",e+"-close-button",o),t.href="#close",t.innerHTML="&#215;",n.DomEvent.on(t,"click",this._onCloseButtonClick,this));var s=this._wrapper=n.DomUtil.create("div",e+"-content-wrapper",o);n.DomEvent.disableClickPropagation(s),this._contentNode=n.DomUtil.create("div",e+"-content",s),n.DomEvent.on(this._contentNode,"mousewheel",n.DomEvent.stopPropagation),this._tipContainer=n.DomUtil.create("div",e+"-tip-container",o),this._tip=n.DomUtil.create("div",e+"-tip",this._tipContainer)},_update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},_updateContent:function(){if(this._content){if("string"==typeof this._content)this._contentNode.innerHTML=this._content;else{for(;this._contentNode.hasChildNodes();)this._contentNode.removeChild(this._contentNode.firstChild);this._contentNode.appendChild(this._content)}this.fire("contentupdate")}},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var o=t.offsetHeight,s=this.options.maxHeight,a="leaflet-popup-scrolled";s&&o>s?(e.height=s+"px",n.DomUtil.addClass(t,a)):n.DomUtil.removeClass(t,a),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=this._animated,i=this.options.offset;e&&n.DomUtil.setPosition(this._container,t),this._containerBottom=-i.y-(e?0:t.y),this._containerLeft=-Math.round(this._containerWidth/2)+i.x+(e?0:t.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"}},_zoomAnimation:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);n.DomUtil.setPosition(this._container,e)},_adjustPan:function(){if(this.options.autoPan){var t=this._map,e=this._container.offsetHeight,i=this._containerWidth,o=new n.Point(this._containerLeft,-e-this._containerBottom);this._animated&&o._add(n.DomUtil.getPosition(this._container));var s=t.layerPointToContainerPoint(o),a=this.options.autoPanPadding,r=t.getSize(),h=0,l=0;0>s.x&&(h=s.x-a.x),s.x+i>r.x&&(h=s.x+i-r.x+a.x),0>s.y&&(l=s.y-a.y),s.y+e>r.y&&(l=s.y+e-r.y+a.y),(h||l)&&t.panBy(new n.Point(h,l))}},_onCloseButtonClick:function(t){this._close(),n.DomEvent.stop(t)}}),n.popup=function(t,e){return new n.Popup(t,e)},n.Marker.include({openPopup:function(){return this._popup&&this._map&&(this._popup.setLatLng(this._latlng),this._map.openPopup(this._popup)),this},closePopup:function(){return this._popup&&this._popup._close(),this},bindPopup:function(t,e){var i=n.point(this.options.icon.options.popupAnchor)||new n.Point(0,0);return i=i.add(n.Popup.prototype.options.offset),e&&e.offset&&(i=i.add(e.offset)),e=n.extend({offset:i},e),this._popup||this.on("click",this.openPopup,this).on("remove",this.closePopup,this).on("move",this._movePopup,this),this._popup=new n.Popup(e,this).setContent(t),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.openPopup).off("remove",this.closePopup).off("move",this._movePopup)),this},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),n.Map.include({openPopup:function(t){return this.closePopup(),this._popup=t,this.addLayer(t).fire("popupopen",{popup:this._popup})},closePopup:function(){return this._popup&&this._popup._close(),this}}),n.LayerGroup=n.Class.extend({initialize:function(t){this._layers={};var e,i;if(t)for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},addLayer:function(t){var e=n.stamp(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var e=n.stamp(t);return delete this._layers[e],this._map&&this._map.removeLayer(t),this},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)this._layers.hasOwnProperty(e)&&(i=this._layers[e],i[t]&&i[t].apply(i,n));return this},onAdd:function(t){this._map=t,this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t),this._map=null},addTo:function(t){return t.addLayer(this),this},eachLayer:function(t,e){for(var i in this._layers)this._layers.hasOwnProperty(i)&&t.call(e,this._layers[i])},setZIndex:function(t){return this.invoke("setZIndex",t)}}),n.layerGroup=function(t){return new n.LayerGroup(t)},n.FeatureGroup=n.LayerGroup.extend({includes:n.Mixin.Events,statics:{EVENTS:"click dblclick mouseover mouseout mousemove contextmenu"},addLayer:function(t){return this._layers[n.stamp(t)]?this:(t.on(n.FeatureGroup.EVENTS,this._propagateEvent,this),n.LayerGroup.prototype.addLayer.call(this,t),this._popupContent&&t.bindPopup&&t.bindPopup(this._popupContent,this._popupOptions),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return t.off(n.FeatureGroup.EVENTS,this._propagateEvent,this),n.LayerGroup.prototype.removeLayer.call(this,t),this._popupContent&&this.invoke("unbindPopup"),this.fire("layerremove",{layer:t})},bindPopup:function(t,e){return this._popupContent=t,this._popupOptions=e,this.invoke("bindPopup",t,e)},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new n.LatLngBounds;return this.eachLayer(function(e){t.extend(e instanceof n.Marker?e.getLatLng():e.getBounds())}),t},_propagateEvent:function(t){t.layer=t.target,t.target=this,this.fire(t.type,t)}}),n.featureGroup=function(t){return new n.FeatureGroup(t)},n.Path=n.Class.extend({includes:[n.Mixin.Events],statics:{CLIP_PADDING:n.Browser.mobile?Math.max(0,Math.min(.5,(1280/Math.max(t.innerWidth,t.innerHeight)-1)/2)):.5},options:{stroke:!0,color:"#0033ff",dashArray:null,weight:5,opacity:.5,fill:!1,fillColor:null,fillOpacity:.2,clickable:!0},initialize:function(t){n.setOptions(this,t)},onAdd:function(t){this._map=t,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),this.fire("add"),t.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){t._pathRoot.removeChild(this._container),this.fire("remove"),this._map=null,n.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),t.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(t){return n.setOptions(this,t),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),n.Map.include({_updatePathViewport:function(){var t=n.Path.CLIP_PADDING,e=this.getSize(),i=n.DomUtil.getPosition(this._mapPane),o=i.multiplyBy(-1)._subtract(e.multiplyBy(t)._round()),s=o.add(e.multiplyBy(1+2*t)._round());this._pathViewport=new n.Bounds(o,s)}}),n.Path.SVG_NS="http://www.w3.org/2000/svg",n.Browser.svg=!(!e.createElementNS||!e.createElementNS(n.Path.SVG_NS,"svg").createSVGRect),n.Path=n.Path.extend({statics:{SVG:n.Browser.svg},bringToFront:function(){var t=this._map._pathRoot,e=this._container;return e&&t.lastChild!==e&&t.appendChild(e),this},bringToBack:function(){var t=this._map._pathRoot,e=this._container,i=t.firstChild;return e&&i!==e&&t.insertBefore(e,i),this},getPathString:function(){},_createElement:function(t){return e.createElementNS(n.Path.SVG_NS,t)},_initElements:function(){this._map._initPathRoot(),this._initPath(),this._initStyle()},_initPath:function(){this._container=this._createElement("g"),this._path=this._createElement("path"),this._container.appendChild(this._path)},_initStyle:function(){this.options.stroke&&(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round")),this.options.fill&&this._path.setAttribute("fill-rule","evenodd"),this._updateStyle()},_updateStyle:function(){this.options.stroke?(this._path.setAttribute("stroke",this.options.color),this._path.setAttribute("stroke-opacity",this.options.opacity),this._path.setAttribute("stroke-width",this.options.weight),this.options.dashArray?this._path.setAttribute("stroke-dasharray",this.options.dashArray):this._path.removeAttribute("stroke-dasharray")):this._path.setAttribute("stroke","none"),this.options.fill?(this._path.setAttribute("fill",this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity)):this._path.setAttribute("fill","none")},_updatePath:function(){var t=this.getPathString();t||(t="M0 0"),this._path.setAttribute("d",t)},_initEvents:function(){if(this.options.clickable){(n.Browser.svg||!n.Browser.vml)&&this._path.setAttribute("class","leaflet-clickable"),n.DomEvent.on(this._container,"click",this._onMouseClick,this);for(var t=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"],e=0;t.length>e;e++)n.DomEvent.on(this._container,t[e],this._fireMouseEvent,this)}},_onMouseClick:function(t){this._map.dragging&&this._map.dragging.moved()||this._fireMouseEvent(t)},_fireMouseEvent:function(t){if(this.hasEventListeners(t.type)){var e=this._map,i=e.mouseEventToContainerPoint(t),o=e.containerPointToLayerPoint(i),s=e.layerPointToLatLng(o);this.fire(t.type,{latlng:s,layerPoint:o,containerPoint:i,originalEvent:t}),"contextmenu"===t.type&&n.DomEvent.preventDefault(t),"mousemove"!==t.type&&n.DomEvent.stopPropagation(t)}}}),n.Map.include({_initPathRoot:function(){this._pathRoot||(this._pathRoot=n.Path.prototype._createElement("svg"),this._panes.overlayPane.appendChild(this._pathRoot),this.options.zoomAnimation&&n.Browser.any3d?(this._pathRoot.setAttribute("class"," leaflet-zoom-animated"),this.on({zoomanim:this._animatePathZoom,zoomend:this._endPathZoom})):this._pathRoot.setAttribute("class"," leaflet-zoom-hide"),this.on("moveend",this._updateSvgViewport),this._updateSvgViewport())},_animatePathZoom:function(t){var e=this.getZoomScale(t.zoom),i=this._getCenterOffset(t.center)._multiplyBy(-e)._add(this._pathViewport.min);this._pathRoot.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(i)+" scale("+e+") ",this._pathZooming=!0},_endPathZoom:function(){this._pathZooming=!1},_updateSvgViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max,o=i.x-e.x,s=i.y-e.y,a=this._pathRoot,r=this._panes.overlayPane;n.Browser.mobileWebkit&&r.removeChild(a),n.DomUtil.setPosition(a,e),a.setAttribute("width",o),a.setAttribute("height",s),a.setAttribute("viewBox",[e.x,e.y,o,s].join(" ")),n.Browser.mobileWebkit&&r.appendChild(a)}}}),n.Path.include({bindPopup:function(t,e){return(!this._popup||e)&&(this._popup=new n.Popup(e,this)),this._popup.setContent(t),this._popupHandlersAdded||(this.on("click",this._openPopup,this).on("remove",this.closePopup,this),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this._openPopup).off("remove",this.closePopup),this._popupHandlersAdded=!1),this},openPopup:function(t){return this._popup&&(t=t||this._latlng||this._latlngs[Math.floor(this._latlngs.length/2)],this._openPopup({latlng:t})),this},closePopup:function(){return this._popup&&this._popup._close(),this},_openPopup:function(t){this._popup.setLatLng(t.latlng),this._map.openPopup(this._popup)}}),n.Browser.vml=!n.Browser.svg&&function(){try{var t=e.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(n){return!1}}(),n.Path=n.Browser.svg||!n.Browser.vml?n.Path:n.Path.extend({statics:{VML:!0,CLIP_PADDING:.02},_createElement:function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var t=this._container=this._createElement("shape");n.DomUtil.addClass(t,"leaflet-vml-shape"),this.options.clickable&&n.DomUtil.addClass(t,"leaflet-clickable"),t.coordsize="1 1",this._path=this._createElement("path"),t.appendChild(this._path),this._map._pathRoot.appendChild(t)},_initStyle:function(){this._updateStyle()},_updateStyle:function(){var t=this._stroke,e=this._fill,i=this.options,n=this._container;n.stroked=i.stroke,n.filled=i.fill,i.stroke?(t||(t=this._stroke=this._createElement("stroke"),t.endcap="round",n.appendChild(t)),t.weight=i.weight+"px",t.color=i.color,t.opacity=i.opacity,t.dashStyle=i.dashArray?i.dashArray instanceof Array?i.dashArray.join(" "):i.dashArray.replace(/ *, */g," "):""):t&&(n.removeChild(t),this._stroke=null),i.fill?(e||(e=this._fill=this._createElement("fill"),n.appendChild(e)),e.color=i.fillColor||i.color,e.opacity=i.fillOpacity):e&&(n.removeChild(e),this._fill=null)},_updatePath:function(){var t=this._container.style;t.display="none",this._path.v=this.getPathString()+" ",t.display=""}}),n.Map.include(n.Browser.svg||!n.Browser.vml?{}:{_initPathRoot:function(){if(!this._pathRoot){var t=this._pathRoot=e.createElement("div");t.className="leaflet-vml-container",this._panes.overlayPane.appendChild(t),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}}),n.Browser.canvas=function(){return!!e.createElement("canvas").getContext}(),n.Path=n.Path.SVG&&!t.L_PREFER_CANVAS||!n.Browser.canvas?n.Path:n.Path.extend({statics:{CANVAS:!0,SVG:!1},redraw:function(){return this._map&&(this.projectLatlngs(),this._requestUpdate()),this},setStyle:function(t){return n.setOptions(this,t),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(t){t.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this.options.clickable&&this._map.off("click",this._onClick,this),this._requestUpdate(),this._map=null},_requestUpdate:function(){this._map&&!n.Path._updateRequest&&(n.Path._updateRequest=n.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){n.Path._updateRequest=null,this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var t=this.options;t.stroke&&(this._ctx.lineWidth=t.weight,this._ctx.strokeStyle=t.color),t.fill&&(this._ctx.fillStyle=t.fillColor||t.color)},_drawPath:function(){var t,e,i,o,s,a;for(this._ctx.beginPath(),t=0,i=this._parts.length;i>t;t++){for(e=0,o=this._parts[t].length;o>e;e++)s=this._parts[t][e],a=(0===e?"move":"line")+"To",this._ctx[a](s.x,s.y);this instanceof n.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(!this._checkIfEmpty()){var t=this._ctx,e=this.options;this._drawPath(),t.save(),this._updateStyle(),e.fill&&(t.globalAlpha=e.fillOpacity,t.fill()),e.stroke&&(t.globalAlpha=e.opacity,t.stroke()),t.restore()}},_initEvents:function(){this.options.clickable&&this._map.on("click",this._onClick,this)},_onClick:function(t){this._containsPoint(t.layerPoint)&&this.fire("click",{latlng:t.latlng,layerPoint:t.layerPoint,containerPoint:t.containerPoint,originalEvent:t})}}),n.Map.include(n.Path.SVG&&!t.L_PREFER_CANVAS||!n.Browser.canvas?{}:{_initPathRoot:function(){var t,i=this._pathRoot;i||(i=this._pathRoot=e.createElement("canvas"),i.style.position="absolute",t=this._canvasCtx=i.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(i),this.options.zoomAnimation&&(this._pathRoot.className="leaflet-zoom-animated",this.on("zoomanim",this._animatePathZoom),this.on("zoomend",this._endPathZoom)),this.on("moveend",this._updateCanvasViewport),this._updateCanvasViewport())},_updateCanvasViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max.subtract(e),o=this._pathRoot;n.DomUtil.setPosition(o,e),o.width=i.x,o.height=i.y,o.getContext("2d").translate(-e.x,-e.y)}}}),n.LineUtil={simplify:function(t,e){if(!e||!t.length)return t.slice();var i=e*e;return t=this._reducePoints(t,i),t=this._simplifyDP(t,i)},pointToSegmentDistance:function(t,e,i){return Math.sqrt(this._sqClosestPointOnSegment(t,e,i,!0))},closestPointOnSegment:function(t,e,i){return this._sqClosestPointOnSegment(t,e,i)},_simplifyDP:function(t,e){var n=t.length,o=typeof Uint8Array!=i+""?Uint8Array:Array,s=new o(n);s[0]=s[n-1]=1,this._simplifyDPStep(t,s,e,0,n-1);var a,r=[];for(a=0;n>a;a++)s[a]&&r.push(t[a]);return r},_simplifyDPStep:function(t,e,i,n,o){var s,a,r,h=0;for(a=n+1;o-1>=a;a++)r=this._sqClosestPointOnSegment(t[a],t[n],t[o],!0),r>h&&(s=a,h=r);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;s>n;n++)this._sqDist(t[n],t[o])>e&&(i.push(t[n]),o=n);return s-1>o&&i.push(t[s-1]),i},clipSegment:function(t,e,i,n){var o,s,a,r=n?this._lastCode:this._getBitCode(t,i),h=this._getBitCode(e,i);for(this._lastCode=h;;){if(!(r|h))return[t,e];if(r&h)return!1;o=r||h,s=this._getEdgeIntersection(t,e,o,i),a=this._getBitCode(s,i),o===r?(t=s,r=a):(e=s,h=a)}},_getEdgeIntersection:function(t,e,o,s){var a=e.x-t.x,r=e.y-t.y,h=s.min,l=s.max;return 8&o?new n.Point(t.x+a*(l.y-t.y)/r,l.y):4&o?new n.Point(t.x+a*(h.y-t.y)/r,h.y):2&o?new n.Point(l.x,t.y+r*(l.x-t.x)/a):1&o?new n.Point(h.x,t.y+r*(h.x-t.x)/a):i},_getBitCode:function(t,e){var i=0;return t.x<e.min.x?i|=1:t.x>e.max.x&&(i|=2),t.y<e.min.y?i|=4:t.y>e.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,o){var s,a=e.x,r=e.y,h=i.x-a,l=i.y-r,u=h*h+l*l;return u>0&&(s=((t.x-a)*h+(t.y-r)*l)/u,s>1?(a=i.x,r=i.y):s>0&&(a+=h*s,r+=l*s)),h=t.x-a,l=t.y-r,o?h*h+l*l:new n.Point(a,r)}},n.Polyline=n.Path.extend({initialize:function(t,e){n.Path.prototype.initialize.call(this,e),this._latlngs=this._convertLatLngs(t)},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var t=0,e=this._latlngs.length;e>t;t++)this._originalPoints[t]=this._map.latLngToLayerPoint(this._latlngs[t])},getPathString:function(){for(var t=0,e=this._parts.length,i="";e>t;t++)i+=this._getPathPartStr(this._parts[t]);return i},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._latlngs=this._convertLatLngs(t),this.redraw()},addLatLng:function(t){return this._latlngs.push(n.latLng(t)),this.redraw()},spliceLatLngs:function(){var t=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs),this.redraw(),t},closestLayerPoint:function(t){for(var e,i,o=1/0,s=this._parts,a=null,r=0,h=s.length;h>r;r++)for(var l=s[r],u=1,c=l.length;c>u;u++){e=l[u-1],i=l[u];
6 var _=n.LineUtil._sqClosestPointOnSegment(t,e,i,!0);o>_&&(o=_,a=n.LineUtil._sqClosestPointOnSegment(t,e,i))}return a&&(a.distance=Math.sqrt(o)),a},getBounds:function(){var t,e,i=new n.LatLngBounds,o=this.getLatLngs();for(t=0,e=o.length;e>t;t++)i.extend(o[t]);return i},_convertLatLngs:function(t){var e,i;for(e=0,i=t.length;i>e;e++){if(n.Util.isArray(t[e])&&"number"!=typeof t[e][0])return;t[e]=n.latLng(t[e])}return t},_initEvents:function(){n.Path.prototype._initEvents.call(this)},_getPathPartStr:function(t){for(var e,i=n.Path.VML,o=0,s=t.length,a="";s>o;o++)e=t[o],i&&e._round(),a+=(o?"L":"M")+e.x+" "+e.y;return a},_clipPoints:function(){var t,e,o,s=this._originalPoints,a=s.length;if(this.options.noClip)return this._parts=[s],i;this._parts=[];var r=this._parts,h=this._map._pathViewport,l=n.LineUtil;for(t=0,e=0;a-1>t;t++)o=l.clipSegment(s[t],s[t+1],h,t),o&&(r[e]=r[e]||[],r[e].push(o[0]),(o[1]!==s[t+1]||t===a-2)&&(r[e].push(o[1]),e++))},_simplifyPoints:function(){for(var t=this._parts,e=n.LineUtil,i=0,o=t.length;o>i;i++)t[i]=e.simplify(t[i],this.options.smoothFactor)},_updatePath:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),n.Path.prototype._updatePath.call(this))}}),n.polyline=function(t,e){return new n.Polyline(t,e)},n.PolyUtil={},n.PolyUtil.clipPolygon=function(t,e){var i,o,s,a,r,h,l,u,c,_=[1,4,2,8],d=n.LineUtil;for(o=0,l=t.length;l>o;o++)t[o]._code=d._getBitCode(t[o],e);for(a=0;4>a;a++){for(u=_[a],i=[],o=0,l=t.length,s=l-1;l>o;s=o++)r=t[o],h=t[s],r._code&u?h._code&u||(c=d._getEdgeIntersection(h,r,u,e),c._code=d._getBitCode(c,e),i.push(c)):(h._code&u&&(c=d._getEdgeIntersection(h,r,u,e),c._code=d._getBitCode(c,e),i.push(c)),i.push(r));t=i}return t},n.Polygon=n.Polyline.extend({options:{fill:!0},initialize:function(t,e){n.Polyline.prototype.initialize.call(this,t,e),t&&n.Util.isArray(t[0])&&"number"!=typeof t[0][0]&&(this._latlngs=this._convertLatLngs(t[0]),this._holes=t.slice(1))},projectLatlngs:function(){if(n.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[],this._holes){var t,e,i,o;for(t=0,i=this._holes.length;i>t;t++)for(this._holePoints[t]=[],e=0,o=this._holes[t].length;o>e;e++)this._holePoints[t][e]=this._map.latLngToLayerPoint(this._holes[t][e])}},_clipPoints:function(){var t=this._originalPoints,e=[];if(this._parts=[t].concat(this._holePoints),!this.options.noClip){for(var i=0,o=this._parts.length;o>i;i++){var s=n.PolyUtil.clipPolygon(this._parts[i],this._map._pathViewport);s.length&&e.push(s)}this._parts=e}},_getPathPartStr:function(t){var e=n.Polyline.prototype._getPathPartStr.call(this,t);return e+(n.Browser.svg?"z":"x")}}),n.polygon=function(t,e){return new n.Polygon(t,e)},function(){function t(t){return n.FeatureGroup.extend({initialize:function(t,e){this._layers={},this._options=e,this.setLatLngs(t)},setLatLngs:function(e){var i=0,n=e.length;for(this.eachLayer(function(t){n>i?t.setLatLngs(e[i++]):this.removeLayer(t)},this);n>i;)this.addLayer(new t(e[i++],this._options));return this}})}n.MultiPolyline=t(n.Polyline),n.MultiPolygon=t(n.Polygon),n.multiPolyline=function(t,e){return new n.MultiPolyline(t,e)},n.multiPolygon=function(t,e){return new n.MultiPolygon(t,e)}}(),n.Rectangle=n.Polygon.extend({initialize:function(t,e){n.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=n.latLngBounds(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),n.rectangle=function(t,e){return new n.Rectangle(t,e)},n.Circle=n.Path.extend({initialize:function(t,e,i){n.Path.prototype.initialize.call(this,i),this._latlng=n.latLng(t),this._mRadius=e},options:{fill:!0},setLatLng:function(t){return this._latlng=n.latLng(t),this.redraw()},setRadius:function(t){return this._mRadius=t,this.redraw()},projectLatlngs:function(){var t=this._getLngRadius(),e=new n.LatLng(this._latlng.lat,this._latlng.lng-t),i=this._map.latLngToLayerPoint(e);this._point=this._map.latLngToLayerPoint(this._latlng),this._radius=Math.max(Math.round(this._point.x-i.x),1)},getBounds:function(){var t=this._getLngRadius(),e=360*(this._mRadius/40075017),i=this._latlng,o=new n.LatLng(i.lat-e,i.lng-t),s=new n.LatLng(i.lat+e,i.lng+t);return new n.LatLngBounds(o,s)},getLatLng:function(){return this._latlng},getPathString:function(){var t=this._point,e=this._radius;return this._checkIfEmpty()?"":n.Browser.svg?"M"+t.x+","+(t.y-e)+"A"+e+","+e+",0,1,1,"+(t.x-.1)+","+(t.y-e)+" z":(t._round(),e=Math.round(e),"AL "+t.x+","+t.y+" "+e+","+e+" 0,"+23592600)},getRadius:function(){return this._mRadius},_getLatRadius:function(){return 360*(this._mRadius/40075017)},_getLngRadius:function(){return this._getLatRadius()/Math.cos(n.LatLng.DEG_TO_RAD*this._latlng.lat)},_checkIfEmpty:function(){if(!this._map)return!1;var t=this._map._pathViewport,e=this._radius,i=this._point;return i.x-e>t.max.x||i.y-e>t.max.y||i.x+e<t.min.x||i.y+e<t.min.y}}),n.circle=function(t,e,i){return new n.Circle(t,e,i)},n.CircleMarker=n.Circle.extend({options:{radius:10,weight:2},initialize:function(t,e){n.Circle.prototype.initialize.call(this,t,null,e),this._radius=this.options.radius},projectLatlngs:function(){this._point=this._map.latLngToLayerPoint(this._latlng)},_updateStyle:function(){n.Circle.prototype._updateStyle.call(this),this.setRadius(this.options.radius)},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()}}),n.circleMarker=function(t,e){return new n.CircleMarker(t,e)},n.Polyline.include(n.Path.CANVAS?{_containsPoint:function(t,e){var i,o,s,a,r,h,l,u=this.options.weight/2;for(n.Browser.touch&&(u+=10),i=0,a=this._parts.length;a>i;i++)for(l=this._parts[i],o=0,r=l.length,s=r-1;r>o;s=o++)if((e||0!==o)&&(h=n.LineUtil.pointToSegmentDistance(t,l[s],l[o]),u>=h))return!0;return!1}}:{}),n.Polygon.include(n.Path.CANVAS?{_containsPoint:function(t){var e,i,o,s,a,r,h,l,u=!1;if(n.Polyline.prototype._containsPoint.call(this,t,!0))return!0;for(s=0,h=this._parts.length;h>s;s++)for(e=this._parts[s],a=0,l=e.length,r=l-1;l>a;r=a++)i=e[a],o=e[r],i.y>t.y!=o.y>t.y&&t.x<(o.x-i.x)*(t.y-i.y)/(o.y-i.y)+i.x&&(u=!u);return u}}:{}),n.Circle.include(n.Path.CANVAS?{_drawPath:function(){var t=this._point;this._ctx.beginPath(),this._ctx.arc(t.x,t.y,this._radius,0,2*Math.PI,!1)},_containsPoint:function(t){var e=this._point,i=this.options.stroke?this.options.weight/2:0;return t.distanceTo(e)<=this._radius+i}}:{}),n.GeoJSON=n.FeatureGroup.extend({initialize:function(t,e){n.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,o=n.Util.isArray(t)?t:t.features;if(o){for(e=0,i=o.length;i>e;e++)(o[e].geometries||o[e].geometry||o[e].features)&&this.addData(o[e]);return this}var s=this.options;if(!s.filter||s.filter(t)){var a=n.GeoJSON.geometryToLayer(t,s.pointToLayer);return a.feature=t,a.defaultOptions=a.options,this.resetStyle(a),s.onEachFeature&&s.onEachFeature(t,a),this.addLayer(a)}},resetStyle:function(t){var e=this.options.style;e&&(n.Util.extend(t.options,t.defaultOptions),this._setLayerStyle(t,e))},setStyle:function(t){this.eachLayer(function(e){this._setLayerStyle(e,t)},this)},_setLayerStyle:function(t,e){"function"==typeof e&&(e=e(t.feature)),t.setStyle&&t.setStyle(e)}}),n.extend(n.GeoJSON,{geometryToLayer:function(t,e){var i,o,s,a,r,h="Feature"===t.type?t.geometry:t,l=h.coordinates,u=[];switch(h.type){case"Point":return i=this.coordsToLatLng(l),e?e(t,i):new n.Marker(i);case"MultiPoint":for(s=0,a=l.length;a>s;s++)i=this.coordsToLatLng(l[s]),r=e?e(t,i):new n.Marker(i),u.push(r);return new n.FeatureGroup(u);case"LineString":return o=this.coordsToLatLngs(l),new n.Polyline(o);case"Polygon":return o=this.coordsToLatLngs(l,1),new n.Polygon(o);case"MultiLineString":return o=this.coordsToLatLngs(l,1),new n.MultiPolyline(o);case"MultiPolygon":return o=this.coordsToLatLngs(l,2),new n.MultiPolygon(o);case"GeometryCollection":for(s=0,a=h.geometries.length;a>s;s++)r=this.geometryToLayer({geometry:h.geometries[s],type:"Feature",properties:t.properties},e),u.push(r);return new n.FeatureGroup(u);default:throw Error("Invalid GeoJSON object.")}},coordsToLatLng:function(t,e){var i=parseFloat(t[e?0:1]),o=parseFloat(t[e?1:0]);return new n.LatLng(i,o)},coordsToLatLngs:function(t,e,i){var n,o,s,a=[];for(o=0,s=t.length;s>o;o++)n=e?this.coordsToLatLngs(t[o],e-1,i):this.coordsToLatLng(t[o],i),a.push(n);return a}}),n.geoJson=function(t,e){return new n.GeoJSON(t,e)},n.DomEvent={addListener:function(t,e,o,s){var a,r,h,l=n.stamp(o),u="_leaflet_"+e+l;return t[u]?this:(a=function(e){return o.call(s||t,e||n.DomEvent._getEvent())},n.Browser.msTouch&&0===e.indexOf("touch")?this.addMsTouchListener(t,e,a,l):(n.Browser.touch&&"dblclick"===e&&this.addDoubleTapListener&&this.addDoubleTapListener(t,a,l),"addEventListener"in t?"mousewheel"===e?(t.addEventListener("DOMMouseScroll",a,!1),t.addEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?(r=a,h="mouseenter"===e?"mouseover":"mouseout",a=function(e){return n.DomEvent._checkMouse(t,e)?r(e):i},t.addEventListener(h,a,!1)):t.addEventListener(e,a,!1):"attachEvent"in t&&t.attachEvent("on"+e,a),t[u]=a,this))},removeListener:function(t,e,i){var o=n.stamp(i),s="_leaflet_"+e+o,a=t[s];if(a)return n.Browser.msTouch&&0===e.indexOf("touch")?this.removeMsTouchListener(t,e,o):n.Browser.touch&&"dblclick"===e&&this.removeDoubleTapListener?this.removeDoubleTapListener(t,o):"removeEventListener"in t?"mousewheel"===e?(t.removeEventListener("DOMMouseScroll",a,!1),t.removeEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?t.removeEventListener("mouseenter"===e?"mouseover":"mouseout",a,!1):t.removeEventListener(e,a,!1):"detachEvent"in t&&t.detachEvent("on"+e,a),t[s]=null,this},stopPropagation:function(t){return t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,this},disableClickPropagation:function(t){for(var e=n.DomEvent.stopPropagation,i=n.Draggable.START.length-1;i>=0;i--)n.DomEvent.addListener(t,n.Draggable.START[i],e);return n.DomEvent.addListener(t,"click",e).addListener(t,"dblclick",e)},preventDefault:function(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this},stop:function(t){return n.DomEvent.preventDefault(t).stopPropagation(t)},getMousePosition:function(t,i){var o=e.body,s=e.documentElement,a=t.pageX?t.pageX:t.clientX+o.scrollLeft+s.scrollLeft,r=t.pageY?t.pageY:t.clientY+o.scrollTop+s.scrollTop,h=new n.Point(a,r);return i?h._subtract(n.DomUtil.getViewportOffset(i)):h},getWheelDelta:function(t){var e=0;return t.wheelDelta&&(e=t.wheelDelta/120),t.detail&&(e=-t.detail/3),e},_checkMouse:function(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(n){return!1}return i!==t},_getEvent:function(){var e=t.event;if(!e)for(var i=arguments.callee.caller;i&&(e=i.arguments[0],!e||t.Event!==e.constructor);)i=i.caller;return e}},n.DomEvent.on=n.DomEvent.addListener,n.DomEvent.off=n.DomEvent.removeListener,n.Draggable=n.Class.extend({includes:n.Mixin.Events,statics:{START:n.Browser.touch?["touchstart","mousedown"]:["mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",MSPointerDown:"touchmove"},TAP_TOLERANCE:15},initialize:function(t,e,i){this._element=t,this._dragStartTarget=e||t,this._longPress=i&&!n.Browser.msTouch},enable:function(){if(!this._enabled){for(var t=n.Draggable.START.length-1;t>=0;t--)n.DomEvent.on(this._dragStartTarget,n.Draggable.START[t],this._onDown,this);this._enabled=!0}},disable:function(){if(this._enabled){for(var t=n.Draggable.START.length-1;t>=0;t--)n.DomEvent.off(this._dragStartTarget,n.Draggable.START[t],this._onDown,this);this._enabled=!1,this._moved=!1}},_onDown:function(t){if(!(!n.Browser.touch&&t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(n.DomEvent.preventDefault(t),n.DomEvent.stopPropagation(t),n.Draggable._disabled))){if(this._simulateClick=!0,t.touches&&t.touches.length>1)return this._simulateClick=!1,clearTimeout(this._longPressTimeout),i;var o=t.touches&&1===t.touches.length?t.touches[0]:t,s=o.target;n.Browser.touch&&"a"===s.tagName.toLowerCase()&&n.DomUtil.addClass(s,"leaflet-active"),this._moved=!1,this._moving||(this._startPoint=new n.Point(o.clientX,o.clientY),this._startPos=this._newPos=n.DomUtil.getPosition(this._element),t.touches&&1===t.touches.length&&n.Browser.touch&&this._longPress&&(this._longPressTimeout=setTimeout(n.bind(function(){var t=this._newPos&&this._newPos.distanceTo(this._startPos)||0;n.Draggable.TAP_TOLERANCE>t&&(this._simulateClick=!1,this._onUp(),this._simulateEvent("contextmenu",o))},this),1e3)),n.DomEvent.on(e,n.Draggable.MOVE[t.type],this._onMove,this),n.DomEvent.on(e,n.Draggable.END[t.type],this._onUp,this))}},_onMove:function(t){if(!(t.touches&&t.touches.length>1)){var e=t.touches&&1===t.touches.length?t.touches[0]:t,i=new n.Point(e.clientX,e.clientY),o=i.subtract(this._startPoint);(o.x||o.y)&&(n.DomEvent.preventDefault(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=n.DomUtil.getPosition(this._element).subtract(o),n.Browser.touch||(n.DomUtil.disableTextSelection(),this._setMovingCursor())),this._newPos=this._startPos.add(o),this._moving=!0,n.Util.cancelAnimFrame(this._animRequest),this._animRequest=n.Util.requestAnimFrame(this._updatePosition,this,!0,this._dragStartTarget))}},_updatePosition:function(){this.fire("predrag"),n.DomUtil.setPosition(this._element,this._newPos),this.fire("drag")},_onUp:function(t){var i;if(clearTimeout(this._longPressTimeout),this._simulateClick&&t.changedTouches){var o=t.changedTouches[0],s=o.target,a=this._newPos&&this._newPos.distanceTo(this._startPos)||0;"a"===s.tagName.toLowerCase()&&n.DomUtil.removeClass(s,"leaflet-active"),n.Draggable.TAP_TOLERANCE>a&&(i=o)}n.Browser.touch||(n.DomUtil.enableTextSelection(),this._restoreCursor());for(var r in n.Draggable.MOVE)n.Draggable.MOVE.hasOwnProperty(r)&&(n.DomEvent.off(e,n.Draggable.MOVE[r],this._onMove),n.DomEvent.off(e,n.Draggable.END[r],this._onUp));this._moved&&(n.Util.cancelAnimFrame(this._animRequest),this.fire("dragend")),this._moving=!1,i&&(this._moved=!1,this._simulateEvent("click",i))},_setMovingCursor:function(){n.DomUtil.addClass(e.body,"leaflet-dragging")},_restoreCursor:function(){n.DomUtil.removeClass(e.body,"leaflet-dragging")},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),n.Handler=n.Class.extend({initialize:function(t){this._map=t},enable:function(){this._enabled||(this._enabled=!0,this.addHooks())},disable:function(){this._enabled&&(this._enabled=!1,this.removeHooks())},enabled:function(){return!!this._enabled}}),n.Map.mergeOptions({dragging:!0,inertia:!n.Browser.android23,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,inertiaThreshold:n.Browser.touch?32:18,easeLinearity:.25,longPress:!0,worldCopyJump:!1}),n.Map.Drag=n.Handler.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new n.Draggable(t._mapPane,t._container,t.options.longPress),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),t.on("viewreset",this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var t=this._map;t._panAnim&&t._panAnim.stop(),t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var t=this._lastTime=+new Date,e=this._lastPos=this._draggable._newPos;this._positions.push(e),this._times.push(t),t-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var t=this._map.getSize()._divideBy(2),e=this._map.latLngToLayerPoint(new n.LatLng(0,0));this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.project(new n.LatLng(0,180)).x},_onPreDrag:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,a=Math.abs(o+i)<Math.abs(s+i)?o:s;this._draggable._newPos.x=a},_onDragEnd:function(){var t=this._map,e=t.options,i=+new Date-this._lastTime,o=!e.inertia||i>e.inertiaThreshold||!this._positions[0];if(o)t.fire("moveend");else{var s=this._lastPos.subtract(this._positions[0]),a=(this._lastTime+i-this._times[0])/1e3,r=e.easeLinearity,h=s.multiplyBy(r/a),l=h.distanceTo(new n.Point(0,0)),u=Math.min(e.inertiaMaxSpeed,l),c=h.multiplyBy(u/l),_=u/(e.inertiaDeceleration*r),d=c.multiplyBy(-_/2).round();n.Util.requestAnimFrame(function(){t.panBy(d,_,r)})}t.fire("dragend"),e.maxBounds&&n.Util.requestAnimFrame(this._panInsideMaxBounds,t,!0,t._container)},_panInsideMaxBounds:function(){this.panInsideBounds(this.options.maxBounds)}}),n.Map.addInitHook("addHandler","dragging",n.Map.Drag),n.Map.mergeOptions({doubleClickZoom:!0}),n.Map.DoubleClickZoom=n.Handler.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick)},_onDoubleClick:function(t){this.setView(t.latlng,this._zoom+1)}}),n.Map.addInitHook("addHandler","doubleClickZoom",n.Map.DoubleClickZoom),n.Map.mergeOptions({scrollWheelZoom:!0}),n.Map.ScrollWheelZoom=n.Handler.extend({addHooks:function(){n.DomEvent.on(this._map._container,"mousewheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){n.DomEvent.off(this._map._container,"mousewheel",this._onWheelScroll)},_onWheelScroll:function(t){var e=n.DomEvent.getWheelDelta(t);this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var i=Math.max(40-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(n.bind(this._performZoom,this),i),n.DomEvent.preventDefault(t),n.DomEvent.stopPropagation(t)},_performZoom:function(){var t=this._map,e=this._delta,i=t.getZoom();if(e=e>0?Math.ceil(e):Math.round(e),e=Math.max(Math.min(e,4),-4),e=t._limitZoom(i+e)-i,this._delta=0,this._startTime=null,e){var n=i+e,o=this._getCenterForScrollWheelZoom(n);t.setView(o,n)}},_getCenterForScrollWheelZoom:function(t){var e=this._map,i=e.getZoomScale(t),n=e.getSize()._divideBy(2),o=this._lastMousePos._subtract(n)._multiplyBy(1-1/i),s=e._getTopLeftPoint()._add(n)._add(o);return e.unproject(s)}}),n.Map.addInitHook("addHandler","scrollWheelZoom",n.Map.ScrollWheelZoom),n.extend(n.DomEvent,{_touchstart:n.Browser.msTouch?"MSPointerDown":"touchstart",_touchend:n.Browser.msTouch?"MSPointerUp":"touchend",addDoubleTapListener:function(t,i,o){function s(t){var e;if(n.Browser.msTouch?(p.push(t.pointerId),e=p.length):e=t.touches.length,!(e>1)){var i=Date.now(),o=i-(r||i);h=t.touches?t.touches[0]:t,l=o>0&&u>=o,r=i}}function a(t){if(n.Browser.msTouch){var e=p.indexOf(t.pointerId);if(-1===e)return;p.splice(e,1)}if(l){if(n.Browser.msTouch){var o,s={};for(var a in h)o=h[a],s[a]="function"==typeof o?o.bind(h):o;h=s}h.type="dblclick",i(h),r=null}}var r,h,l=!1,u=250,c="_leaflet_",_=this._touchstart,d=this._touchend,p=[];t[c+_+o]=s,t[c+d+o]=a;var m=n.Browser.msTouch?e.documentElement:t;return t.addEventListener(_,s,!1),m.addEventListener(d,a,!1),n.Browser.msTouch&&m.addEventListener("MSPointerCancel",a,!1),this},removeDoubleTapListener:function(t,i){var o="_leaflet_";return t.removeEventListener(this._touchstart,t[o+this._touchstart+i],!1),(n.Browser.msTouch?e.documentElement:t).removeEventListener(this._touchend,t[o+this._touchend+i],!1),n.Browser.msTouch&&e.documentElement.removeEventListener("MSPointerCancel",t[o+this._touchend+i],!1),this}}),n.extend(n.DomEvent,{_msTouches:[],_msDocumentListener:!1,addMsTouchListener:function(t,e,i,n){switch(e){case"touchstart":return this.addMsTouchListenerStart(t,e,i,n);case"touchend":return this.addMsTouchListenerEnd(t,e,i,n);case"touchmove":return this.addMsTouchListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addMsTouchListenerStart:function(t,i,n,o){var s="_leaflet_",a=this._msTouches,r=function(t){for(var e=!1,i=0;a.length>i;i++)if(a[i].pointerId===t.pointerId){e=!0;break}e||a.push(t),t.touches=a.slice(),t.changedTouches=[t],n(t)};if(t[s+"touchstart"+o]=r,t.addEventListener("MSPointerDown",r,!1),!this._msDocumentListener){var h=function(t){for(var e=0;a.length>e;e++)if(a[e].pointerId===t.pointerId){a.splice(e,1);break}};e.documentElement.addEventListener("MSPointerUp",h,!1),e.documentElement.addEventListener("MSPointerCancel",h,!1),this._msDocumentListener=!0}return this},addMsTouchListenerMove:function(t,e,i,n){function o(t){if(t.pointerType!==t.MSPOINTER_TYPE_MOUSE||0!==t.buttons){for(var e=0;a.length>e;e++)if(a[e].pointerId===t.pointerId){a[e]=t;break}t.touches=a.slice(),t.changedTouches=[t],i(t)}}var s="_leaflet_",a=this._msTouches;return t[s+"touchmove"+n]=o,t.addEventListener("MSPointerMove",o,!1),this},addMsTouchListenerEnd:function(t,e,i,n){var o="_leaflet_",s=this._msTouches,a=function(t){for(var e=0;s.length>e;e++)if(s[e].pointerId===t.pointerId){s.splice(e,1);break}t.touches=s.slice(),t.changedTouches=[t],i(t)};return t[o+"touchend"+n]=a,t.addEventListener("MSPointerUp",a,!1),t.addEventListener("MSPointerCancel",a,!1),this},removeMsTouchListener:function(t,e,i){var n="_leaflet_",o=t[n+e+i];switch(e){case"touchstart":t.removeEventListener("MSPointerDown",o,!1);break;case"touchmove":t.removeEventListener("MSPointerMove",o,!1);break;case"touchend":t.removeEventListener("MSPointerUp",o,!1),t.removeEventListener("MSPointerCancel",o,!1)}return this}}),n.Map.mergeOptions({touchZoom:n.Browser.touch&&!n.Browser.android23}),n.Map.TouchZoom=n.Handler.extend({addHooks:function(){n.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){n.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var o=i.mouseEventToLayerPoint(t.touches[0]),s=i.mouseEventToLayerPoint(t.touches[1]),a=i._getCenterLayerPoint();this._startCenter=o.add(s)._divideBy(2),this._startDist=o.distanceTo(s),this._moved=!1,this._zooming=!0,this._centerOffset=a.subtract(this._startCenter),i._panAnim&&i._panAnim.stop(),n.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),n.DomEvent.preventDefault(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length){var e=this._map,i=e.mouseEventToLayerPoint(t.touches[0]),o=e.mouseEventToLayerPoint(t.touches[1]);this._scale=i.distanceTo(o)/this._startDist,this._delta=i._add(o)._divideBy(2)._subtract(this._startCenter),1!==this._scale&&(this._moved||(n.DomUtil.addClass(e._mapPane,"leaflet-zoom-anim leaflet-touching"),e.fire("movestart").fire("zoomstart")._prepareTileBg(),this._moved=!0),n.Util.cancelAnimFrame(this._animRequest),this._animRequest=n.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),n.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e);t.fire("zoomanim",{center:i,zoom:t.getScaleZoom(this._scale)}),t._tileBg.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(this._delta)+" "+n.DomUtil.getScaleString(this._scale,this._startCenter)},_onTouchEnd:function(){if(this._moved&&this._zooming){var t=this._map;this._zooming=!1,n.DomUtil.removeClass(t._mapPane,"leaflet-touching"),n.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),o=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r);t.fire("zoomanim",{center:o,zoom:h}),t._runAnimation(o,h,t.getZoomScale(h)/this._scale,i,!0)}},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),n.Map.addInitHook("addHandler","touchZoom",n.Map.TouchZoom),n.Map.mergeOptions({boxZoom:!0}),n.Map.BoxZoom=n.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane},addHooks:function(){n.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){n.DomEvent.off(this._container,"mousedown",this._onMouseDown)},_onMouseDown:function(t){return!t.shiftKey||1!==t.which&&1!==t.button?!1:(n.DomUtil.disableTextSelection(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),this._box=n.DomUtil.create("div","leaflet-zoom-box",this._pane),n.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",n.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).preventDefault(t),this._map.fire("boxzoomstart"),i)},_onMouseMove:function(t){var e=this._startLayerPoint,i=this._box,o=this._map.mouseEventToLayerPoint(t),s=o.subtract(e),a=new n.Point(Math.min(o.x,e.x),Math.min(o.y,e.y));n.DomUtil.setPosition(i,a),i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_onMouseUp:function(t){this._pane.removeChild(this._box),this._container.style.cursor="",n.DomUtil.enableTextSelection(),n.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp);var i=this._map,o=i.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(o)){var s=new n.LatLngBounds(i.layerPointToLatLng(this._startLayerPoint),i.layerPointToLatLng(o));i.fitBounds(s),i.fire("boxzoomend",{boxZoomBounds:s})}}}),n.Map.addInitHook("addHandler","boxZoom",n.Map.BoxZoom),n.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),n.Map.Keyboard=n.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),n.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;n.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){this._focused||this._map._container.focus()},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){n.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){n.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(this._panKeys.hasOwnProperty(e))i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds);else{if(!this._zoomKeys.hasOwnProperty(e))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}n.DomEvent.stop(t)}}),n.Map.addInitHook("addHandler","keyboard",n.Map.Keyboard),n.Handler.MarkerDrag=n.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new n.Draggable(t,t).on("dragstart",this._onDragStart,this).on("drag",this._onDrag,this).on("dragend",this._onDragEnd,this)),this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=n.DomUtil.getPosition(t._icon),o=t._map.layerPointToLatLng(i);e&&n.DomUtil.setPosition(e,i),t._latlng=o,t.fire("move",{latlng:o}).fire("drag")},_onDragEnd:function(){this._marker.fire("moveend").fire("dragend")}}),n.Handler.PolyEdit=n.Handler.extend({options:{icon:new n.DivIcon({iconSize:new n.Point(8,8),className:"leaflet-div-icon leaflet-editing-icon"})},initialize:function(t,e){this._poly=t,n.setOptions(this,e)},addHooks:function(){this._poly._map&&(this._markerGroup||this._initMarkers(),this._poly._map.addLayer(this._markerGroup))},removeHooks:function(){this._poly._map&&(this._poly._map.removeLayer(this._markerGroup),delete this._markerGroup,delete this._markers)},updateMarkers:function(){this._markerGroup.clearLayers(),this._initMarkers()},_initMarkers:function(){this._markerGroup||(this._markerGroup=new n.LayerGroup),this._markers=[];var t,e,i,o,s=this._poly._latlngs;for(t=0,i=s.length;i>t;t++)o=this._createMarker(s[t],t),o.on("click",this._onMarkerClick,this),this._markers.push(o);var a,r;for(t=0,e=i-1;i>t;e=t++)(0!==t||n.Polygon&&this._poly instanceof n.Polygon)&&(a=this._markers[e],r=this._markers[t],this._createMiddleMarker(a,r),this._updatePrevNext(a,r))},_createMarker:function(t,e){var i=new n.Marker(t,{draggable:!0,icon:this.options.icon});return i._origLatLng=t,i._index=e,i.on("drag",this._onMarkerDrag,this),i.on("dragend",this._fireEdit,this),this._markerGroup.addLayer(i),i},_fireEdit:function(){this._poly.fire("edit")},_onMarkerDrag:function(t){var e=t.target;n.extend(e._origLatLng,e._latlng),e._middleLeft&&e._middleLeft.setLatLng(this._getMiddleLatLng(e._prev,e)),e._middleRight&&e._middleRight.setLatLng(this._getMiddleLatLng(e,e._next)),this._poly.redraw()},_onMarkerClick:function(t){if(!(3>this._poly._latlngs.length)){var e=t.target,i=e._index;this._markerGroup.removeLayer(e),this._markers.splice(i,1),this._poly.spliceLatLngs(i,1),this._updateIndexes(i,-1),this._updatePrevNext(e._prev,e._next),e._middleLeft&&this._markerGroup.removeLayer(e._middleLeft),e._middleRight&&this._markerGroup.removeLayer(e._middleRight),e._prev&&e._next?this._createMiddleMarker(e._prev,e._next):e._prev?e._next||(e._prev._middleRight=null):e._next._middleLeft=null,this._poly.fire("edit")}},_updateIndexes:function(t,e){this._markerGroup.eachLayer(function(i){i._index>t&&(i._index+=e)})},_createMiddleMarker:function(t,e){var i,n,o,s=this._getMiddleLatLng(t,e),a=this._createMarker(s);a.setOpacity(.6),t._middleRight=e._middleLeft=a,n=function(){var n=e._index;a._index=n,a.off("click",i).on("click",this._onMarkerClick,this),s.lat=a.getLatLng().lat,s.lng=a.getLatLng().lng,this._poly.spliceLatLngs(n,0,s),this._markers.splice(n,0,a),a.setOpacity(1),this._updateIndexes(n,1),e._index++,this._updatePrevNext(t,a),this._updatePrevNext(a,e)},o=function(){a.off("dragstart",n,this),a.off("dragend",o,this),this._createMiddleMarker(t,a),this._createMiddleMarker(a,e)},i=function(){n.call(this),o.call(this),this._poly.fire("edit")},a.on("click",i,this).on("dragstart",n,this).on("dragend",o,this),this._markerGroup.addLayer(a)},_updatePrevNext:function(t,e){t&&(t._next=e),e&&(e._prev=t)},_getMiddleLatLng:function(t,e){var i=this._poly._map,n=i.latLngToLayerPoint(t.getLatLng()),o=i.latLngToLayerPoint(e.getLatLng());return i.layerPointToLatLng(n._add(o)._divideBy(2))}}),n.Polyline.addInitHook(function(){n.Handler.PolyEdit&&(this.editing=new n.Handler.PolyEdit(this),this.options.editable&&this.editing.enable()),this.on("add",function(){this.editing&&this.editing.enabled()&&this.editing.addHooks()}),this.on("remove",function(){this.editing&&this.editing.enabled()&&this.editing.removeHooks()})}),n.Control=n.Class.extend({options:{position:"topright"},initialize:function(t){n.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this
7 },addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),o=t._controlCorners[i];return n.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?o.insertBefore(e,o.firstChild):o.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this}}),n.control=function(t){return new n.Control(t)},n.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=n.DomUtil.create("div",a,o)}var e=this._controlCorners={},i="leaflet-",o=this._controlContainer=n.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")}}),n.Control.Zoom=n.Control.extend({options:{position:"topleft"},onAdd:function(t){var e="leaflet-control-zoom",i="leaflet-bar",o=i+"-part",s=n.DomUtil.create("div",e+" "+i);return this._map=t,this._zoomInButton=this._createButton("+","Zoom in",e+"-in "+o+" "+o+"-top",s,this._zoomIn,this),this._zoomOutButton=this._createButton("-","Zoom out",e+"-out "+o+" "+o+"-bottom",s,this._zoomOut,this),t.on("zoomend",this._updateDisabled,this),s},onRemove:function(t){t.off("zoomend",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,o,s,a){var r=n.DomUtil.create("a",i,o);r.innerHTML=t,r.href="#",r.title=e;var h=n.DomEvent.stopPropagation;return n.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",n.DomEvent.preventDefault).on(r,"click",s,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-control-zoom-disabled";n.DomUtil.removeClass(this._zoomInButton,e),n.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&n.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&n.DomUtil.addClass(this._zoomInButton,e)}}),n.Map.mergeOptions({zoomControl:!0}),n.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new n.Control.Zoom,this.addControl(this.zoomControl))}),n.control.zoom=function(t){return new n.Control.Zoom(t)},n.Control.Attribution=n.Control.extend({options:{position:"bottomright",prefix:'Powered by <a href="http://leafletjs.com">Leaflet</a>'},initialize:function(t){n.setOptions(this,t),this._attributions={}},onAdd:function(t){return this._container=n.DomUtil.create("div","leaflet-control-attribution"),n.DomEvent.disableClickPropagation(this._container),t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):i},removeAttribution:function(t){return t?(this._attributions[t]--,this._update(),this):i},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions.hasOwnProperty(e)&&this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" &#8212; ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),n.Map.mergeOptions({attributionControl:!0}),n.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new n.Control.Attribution).addTo(this))}),n.control.attribution=function(t){return new n.Control.Attribution(t)},n.Control.Scale=n.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=n.DomUtil.create("div",e),o=this.options;return this._addScales(o,e,i),t.on(o.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=n.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=n.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),n.control.scale=function(t){return new n.Control.Scale(t)},n.Control.Layers=n.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){n.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var o in t)t.hasOwnProperty(o)&&this._addLayer(t[o],o);for(o in e)e.hasOwnProperty(o)&&this._addLayer(e[o],o,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange).off("layerremove",this._onLayerChange)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=n.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=n.DomUtil.create("div",t);n.Browser.touch?n.DomEvent.on(e,"click",n.DomEvent.stopPropagation):(n.DomEvent.disableClickPropagation(e),n.DomEvent.on(e,"mousewheel",n.DomEvent.stopPropagation));var i=this._form=n.DomUtil.create("form",t+"-list");if(this.options.collapsed){n.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var o=this._layersLink=n.DomUtil.create("a",t+"-toggle",e);o.href="#",o.title="Layers",n.Browser.touch?n.DomEvent.on(o,"click",n.DomEvent.stopPropagation).on(o,"click",n.DomEvent.preventDefault).on(o,"click",this._expand,this):n.DomEvent.on(o,"focus",this._expand,this),this._map.on("movestart",this._collapse,this)}else this._expand();this._baseLayersList=n.DomUtil.create("div",t+"-base",i),this._separator=n.DomUtil.create("div",t+"-separator",i),this._overlaysList=n.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var o=n.stamp(t);this._layers[o]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t=!1,e=!1;for(var i in this._layers)if(this._layers.hasOwnProperty(i)){var n=this._layers[i];this._addItem(n),e=e||n.overlay,t=t||!n.overlay}this._separator.style.display=e&&t?"":"none"}},_onLayerChange:function(t){var e=n.stamp(t.layer);this._layers[e]&&!this._handlingClick&&this._update()},_createRadioElement:function(t,i){var n='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"';i&&(n+=' checked="checked"'),n+="/>";var o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,o=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=n.stamp(t.layer),n.DomEvent.on(i,"click",this._onInputClick,this);var a=e.createElement("span");a.innerHTML=" "+t.name,o.appendChild(i),o.appendChild(a);var r=t.overlay?this._overlaysList:this._baseLayersList;return r.appendChild(o),o},_onInputClick:function(){var t,e,i,n,o=this._form.getElementsByTagName("input"),s=o.length;for(this._handlingClick=!0,t=0;s>t;t++)e=o[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?(this._map.addLayer(i.layer),i.overlay||(n=i.layer)):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);n&&(this._map.setZoom(this._map.getZoom()),this._map.fire("baselayerchange",{layer:n})),this._handlingClick=!1},_expand:function(){n.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),n.control.layers=function(t,e,i){return new n.Control.Layers(t,e,i)},n.PosAnimation=n.Class.extend({includes:n.Mixin.Events,run:function(t,e,i,o){this.stop(),this._el=t,this._inProgress=!0,this.fire("start"),t.style[n.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(o||.5)+",1)",n.DomEvent.on(t,n.DomUtil.TRANSITION_END,this._onTransitionEnd,this),n.DomUtil.setPosition(t,e),n.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(n.bind(this.fire,this,"step"),50)},stop:function(){this._inProgress&&(n.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),n.Util.falseFn(this._el.offsetWidth))},_transformRe:/(-?[\d\.]+), (-?[\d\.]+)\)/,_getPos:function(){var e,i,o,s=this._el,a=t.getComputedStyle(s);return n.Browser.any3d?(o=a[n.DomUtil.TRANSFORM].match(this._transformRe),e=parseFloat(o[1]),i=parseFloat(o[2])):(e=parseFloat(a.left),i=parseFloat(a.top)),new n.Point(e,i,!0)},_onTransitionEnd:function(){n.DomEvent.off(this._el,n.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[n.DomUtil.TRANSITION]="",clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),n.Map.include({setView:function(t,e,i){e=this._limitZoom(e);var n=this._zoom!==e;if(this._loaded&&!i&&this._layers){this._panAnim&&this._panAnim.stop();var o=n?this._zoomToIfClose&&this._zoomToIfClose(t,e):this._panByIfClose(t);if(o)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e,i){if(t=n.point(t),!t.x&&!t.y)return this;this._panAnim||(this._panAnim=new n.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),this.fire("movestart"),n.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var o=n.DomUtil.getPosition(this._mapPane).subtract(t)._round();return this._panAnim.run(this._mapPane,o,e||.25,i),this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){n.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_panByIfClose:function(t){var e=this._getCenterOffset(t)._floor();return this._offsetIsWithinView(e)?(this.panBy(e),!0):!1},_offsetIsWithinView:function(t,e){var i=e||1,n=this.getSize();return Math.abs(t.x)<=n.x*i&&Math.abs(t.y)<=n.y*i}}),n.PosAnimation=n.DomUtil.TRANSITION?n.PosAnimation:n.PosAnimation.extend({run:function(t,e,i,o){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(o||.5,.2),this._startPos=n.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=n.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));n.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){n.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),n.Map.mergeOptions({zoomAnimation:n.DomUtil.TRANSITION&&!n.Browser.android23&&!n.Browser.mobileOpera}),n.DomUtil.TRANSITION&&n.Map.addInitHook(function(){n.DomEvent.on(this._mapPane,n.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),n.Map.include(n.DomUtil.TRANSITION?{_zoomToIfClose:function(t,e){if(this._animatingZoom)return!0;if(!this.options.zoomAnimation)return!1;var i=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/i);if(!this._offsetIsWithinView(o,1))return!1;n.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this.fire("movestart").fire("zoomstart"),this.fire("zoomanim",{center:t,zoom:e});var s=this._getCenterLayerPoint().add(o);return this._prepareTileBg(),this._runAnimation(t,e,i,s),!0},_catchTransitionEnd:function(){this._animatingZoom&&this._onZoomTransitionEnd()},_runAnimation:function(t,e,i,o,s){this._animateToCenter=t,this._animateToZoom=e,this._animatingZoom=!0,n.Draggable&&(n.Draggable._disabled=!0);var a=n.DomUtil.TRANSFORM,r=this._tileBg;clearTimeout(this._clearTileBgTimer),n.Util.falseFn(r.offsetWidth);var h=n.DomUtil.getScaleString(i,o),l=r.style[a];r.style[a]=s?l+" "+h:h+" "+l},_prepareTileBg:function(){var t=this._tilePane,e=this._tileBg;if(e&&this._getLoadedTilesPercentage(e)>.5&&.5>this._getLoadedTilesPercentage(t))return t.style.visibility="hidden",t.empty=!0,this._stopLoadingImages(t),i;e||(e=this._tileBg=this._createPane("leaflet-tile-pane",this._mapPane),e.style.zIndex=1),e.style[n.DomUtil.TRANSFORM]="",e.style.visibility="hidden",e.empty=!0,t.empty=!1,this._tilePane=this._panes.tilePane=e;var o=this._tileBg=t;n.DomUtil.addClass(o,"leaflet-zoom-animated"),this._stopLoadingImages(o)},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,o,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)o=s[e],o.complete||(o.onload=n.Util.falseFn,o.onerror=n.Util.falseFn,o.src=n.Util.emptyImageUrl,o.parentNode.removeChild(o))},_onZoomTransitionEnd:function(){this._restoreTileFront(),n.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),n.Util.falseFn(this._tileBg.offsetWidth),this._animatingZoom=!1,this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),n.Draggable&&(n.Draggable._disabled=!1)},_restoreTileFront:function(){this._tilePane.innerHTML="",this._tilePane.style.visibility="",this._tilePane.style.zIndex=2,this._tileBg.style.zIndex=1},_clearTileBg:function(){this._animatingZoom||this.touchZoom._zooming||(this._tileBg.innerHTML="")}}:{}),n.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locationOptions=n.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=n.bind(this._handleGeolocationResponse,this),i=n.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locationOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=180*t.coords.accuracy/4e7,i=2*e,o=t.coords.latitude,s=t.coords.longitude,a=new n.LatLng(o,s),r=new n.LatLng(o-e,s-i),h=new n.LatLng(o+e,s+i),l=new n.LatLngBounds(r,h),u=this._locationOptions;if(u.setView){var c=Math.min(this.getBoundsZoom(l),u.maxZoom);this.setView(a,c)}this.fire("locationfound",{latlng:a,bounds:l,accuracy:t.coords.accuracy})}})})(this,document);
5 !function(t,e,i){var n=t.L,o={};o.version="0.6","object"==typeof module&&"object"==typeof module.exports?module.exports=o:"function"==typeof define&&define.amd&&define(o),o.noConflict=function(){return t.L=n,this},t.L=o,o.Util={extend:function(t){var e,i,n,o,s=Array.prototype.slice.call(arguments,1);for(i=0,n=s.length;n>i;i++){o=s[i]||{};for(e in o)o.hasOwnProperty(e)&&(t[e]=o[e])}return t},bind:function(t,e){var i=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return t.apply(e,i||arguments)}},stamp:function(){var t=0,e="_leaflet_id";return function(i){return i[e]=i[e]||++t,i[e]}}(),invokeEach:function(t,e,i){var n,o;if("object"==typeof t){o=Array.prototype.slice.call(arguments,3);for(n in t)e.apply(i,[n,t[n]].concat(o));return!0}return!1},limitExecByInterval:function(t,e,i){var n,o;return function s(){var a=arguments;return n?(o=!0,void 0):(n=!0,setTimeout(function(){n=!1,o&&(s.apply(i,a),o=!1)},e),t.apply(i,a),void 0)}},falseFn:function(){return!1},formatNum:function(t,e){var i=Math.pow(10,e||5);return Math.round(t*i)/i},trim:function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")},splitWords:function(t){return o.Util.trim(t).split(/\s+/)},setOptions:function(t,e){return t.options=o.extend({},t.options,e),t.options},getParamString:function(t,e,i){var n=[];for(var o in t)n.push(encodeURIComponent(i?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(e&&-1!==e.indexOf("?")?"&":"?")+n.join("&")},template:function(t,e){return t.replace(/\{ *([\w_]+) *\}/g,function(t,n){var o=e[n];if(o===i)throw new Error("No value provided for variable "+t);return"function"==typeof o&&(o=o(e)),o})},isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function e(e){var i,n,o=["webkit","moz","o","ms"];for(i=0;i<o.length&&!n;i++)n=t[o[i]+e];return n}function i(e){var i=+new Date,o=Math.max(0,16-(i-n));return n=i+o,t.setTimeout(e,o)}var n=0,s=t.requestAnimationFrame||e("RequestAnimationFrame")||i,a=t.cancelAnimationFrame||e("CancelAnimationFrame")||e("CancelRequestAnimationFrame")||function(e){t.clearTimeout(e)};o.Util.requestAnimFrame=function(e,n,a,r){return e=o.bind(e,n),a&&s===i?(e(),void 0):s.call(t,e,r)},o.Util.cancelAnimFrame=function(e){e&&a.call(t,e)}}(),o.extend=o.Util.extend,o.bind=o.Util.bind,o.stamp=o.Util.stamp,o.setOptions=o.Util.setOptions,o.Class=function(){},o.Class.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this._initHooks&&this.callInitHooks()},i=function(){};i.prototype=this.prototype;var n=new i;n.constructor=e,e.prototype=n;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&(e[s]=this[s]);t.statics&&(o.extend(e,t.statics),delete t.statics),t.includes&&(o.Util.extend.apply(null,[n].concat(t.includes)),delete t.includes),t.options&&n.options&&(t.options=o.extend({},n.options,t.options)),o.extend(n,t),n._initHooks=[];var a=this;return e.__super__=a.prototype,n.callInitHooks=function(){if(!this._initHooksCalled){a.prototype.callInitHooks&&a.prototype.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=n._initHooks.length;e>t;t++)n._initHooks[t].call(this)}},e},o.Class.include=function(t){o.extend(this.prototype,t)},o.Class.mergeOptions=function(t){o.extend(this.prototype.options,t)},o.Class.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i)};var s="_leaflet_events";o.Mixin={},o.Mixin.Events={addEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d=this[s]=this[s]||{},p=i&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)r={action:e,context:i||this},h=t[n],i?(l=h+"_idx",u=l+"_len",c=d[l]=d[l]||{},c[p]||(c[p]=[],d[u]=(d[u]||0)+1),c[p].push(r)):(d[h]=d[h]||[],d[h].push(r));return this},hasEventListeners:function(t){var e=this[s];return!!e&&(t in e&&e[t].length>0||t+"_idx"in e&&e[t+"_idx_len"]>0)},removeEventListener:function(t,e,i){if(!this[s])return this;if(!t)return this.clearAllEventListeners();if(o.Util.invokeEach(t,this.removeEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d,p,_=this[s],m=i&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)if(r=t[n],u=r+"_idx",c=u+"_len",d=_[u],e){if(h=i&&d?d[m]:_[r]){for(l=h.length-1;l>=0;l--)h[l].action!==e||i&&h[l].context!==i||(p=h.splice(l,1),p[0].action=o.Util.falseFn);i&&d&&0===h.length&&(delete d[m],_[c]--)}}else delete _[r],delete _[u];return this},clearAllEventListeners:function(){return delete this[s],this},fireEvent:function(t,e){if(!this.hasEventListeners(t))return this;var i,n,a,r,h,l=o.Util.extend({},e,{type:t,target:this}),u=this[s];if(u[t])for(i=u[t].slice(),n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context||this,l);r=u[t+"_idx"];for(h in r)if(i=r[h].slice())for(n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context||this,l);return this},addOneTimeEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addOneTimeEventListener,this,e,i))return this;var n=o.bind(function(){this.removeEventListener(t,e,i).removeEventListener(t,n,i)},this);return this.addEventListener(t,e,i).addEventListener(t,n,i)}},o.Mixin.Events.on=o.Mixin.Events.addEventListener,o.Mixin.Events.off=o.Mixin.Events.removeEventListener,o.Mixin.Events.once=o.Mixin.Events.addOneTimeEventListener,o.Mixin.Events.fire=o.Mixin.Events.fireEvent,function(){var n=!!t.ActiveXObject,s=n&&!t.XMLHttpRequest,a=n&&!e.querySelector,r=n&&!e.addEventListener,h=navigator.userAgent.toLowerCase(),l=-1!==h.indexOf("webkit"),u=-1!==h.indexOf("chrome"),c=-1!==h.indexOf("phantom"),d=-1!==h.indexOf("android"),p=-1!==h.search("android [23]"),_=typeof orientation!=i+"",m=t.navigator&&t.navigator.msPointerEnabled&&t.navigator.msMaxTouchPoints,f="devicePixelRatio"in t&&t.devicePixelRatio>1||"matchMedia"in t&&t.matchMedia("(min-resolution:144dpi)")&&t.matchMedia("(min-resolution:144dpi)").matches,g=e.documentElement,v=n&&"transition"in g.style,y="WebKitCSSMatrix"in t&&"m11"in new t.WebKitCSSMatrix,L="MozPerspective"in g.style,P="OTransition"in g.style,x=!t.L_DISABLE_3D&&(v||y||L||P)&&!c,w=!t.L_NO_TOUCH&&!c&&function(){var t="ontouchstart";if(m||t in g)return!0;var i=e.createElement("div"),n=!1;return i.setAttribute?(i.setAttribute(t,"return;"),"function"==typeof i[t]&&(n=!0),i.removeAttribute(t),i=null,n):!1}();o.Browser={ie:n,ie6:s,ie7:a,ielt9:r,webkit:l,android:d,android23:p,chrome:u,ie3d:v,webkit3d:y,gecko3d:L,opera3d:P,any3d:x,mobile:_,mobileWebkit:_&&l,mobileWebkit3d:_&&y,mobileOpera:_&&t.opera,touch:w,msTouch:m,retina:f}}(),o.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},o.Point.prototype={clone:function(){return new o.Point(this.x,this.y)},add:function(t){return this.clone()._add(o.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(o.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},distanceTo:function(t){t=o.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t=o.point(t),t.x===this.x&&t.y===this.y},contains:function(t){return t=o.point(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+o.Util.formatNum(this.x)+", "+o.Util.formatNum(this.y)+")"}},o.point=function(t,e,n){return t instanceof o.Point?t:o.Util.isArray(t)?new o.Point(t[0],t[1]):t===i||null===t?t:new o.Point(t,e,n)},o.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.Bounds.prototype={extend:function(t){return t=o.point(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new o.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new o.Point(this.min.x,this.max.y)},getTopRight:function(){return new o.Point(this.max.x,this.min.y)},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return t="number"==typeof t[0]||t instanceof o.Point?o.point(t):o.bounds(t),t instanceof o.Bounds?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=o.bounds(t);var e=this.min,i=this.max,n=t.min,s=t.max,a=s.x>=e.x&&n.x<=i.x,r=s.y>=e.y&&n.y<=i.y;return a&&r},isValid:function(){return!(!this.min||!this.max)}},o.bounds=function(t,e){return!t||t instanceof o.Bounds?t:new o.Bounds(t,e)},o.Transformation=function(t,e,i,n){this._a=t,this._b=e,this._c=i,this._d=n},o.Transformation.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return e=e||1,t.x=e*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return e=e||1,new o.Point((t.x/e-this._b)/this._a,(t.y/e-this._d)/this._c)}},o.DomUtil={get:function(t){return"string"==typeof t?e.getElementById(t):t},getStyle:function(t,i){var n=t.style[i];if(!n&&t.currentStyle&&(n=t.currentStyle[i]),(!n||"auto"===n)&&e.defaultView){var o=e.defaultView.getComputedStyle(t,null);n=o?o[i]:null}return"auto"===n?null:n},getViewportOffset:function(t){var i,n=0,s=0,a=t,r=e.body,h=e.documentElement,l=o.Browser.ie7;do{if(n+=a.offsetTop||0,s+=a.offsetLeft||0,n+=parseInt(o.DomUtil.getStyle(a,"borderTopWidth"),10)||0,s+=parseInt(o.DomUtil.getStyle(a,"borderLeftWidth"),10)||0,i=o.DomUtil.getStyle(a,"position"),a.offsetParent===r&&"absolute"===i)break;if("fixed"===i){n+=r.scrollTop||h.scrollTop||0,s+=r.scrollLeft||h.scrollLeft||0;break}if("relative"===i&&!a.offsetLeft){var u=o.DomUtil.getStyle(a,"width"),c=o.DomUtil.getStyle(a,"max-width"),d=a.getBoundingClientRect();("none"!==u||"none"!==c)&&(s+=d.left+a.clientLeft),n+=d.top+(r.scrollTop||h.scrollTop||0);break}a=a.offsetParent}while(a);a=t;do{if(a===r)break;n-=a.scrollTop||0,s-=a.scrollLeft||0,o.DomUtil.documentIsLtr()||!o.Browser.webkit&&!l||(s+=a.scrollWidth-a.clientWidth,l&&"hidden"!==o.DomUtil.getStyle(a,"overflow-y")&&"hidden"!==o.DomUtil.getStyle(a,"overflow")&&(s+=17)),a=a.parentNode}while(a);return new o.Point(s,n)},documentIsLtr:function(){return o.DomUtil._docIsLtrCached||(o.DomUtil._docIsLtrCached=!0,o.DomUtil._docIsLtr="ltr"===o.DomUtil.getStyle(e.body,"direction")),o.DomUtil._docIsLtr},create:function(t,i,n){var o=e.createElement(t);return o.className=i,n&&n.appendChild(o),o},hasClass:function(t,e){return t.className.length>0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(t.className)},addClass:function(t,e){o.DomUtil.hasClass(t,e)||(t.className+=(t.className?" ":"")+e)},removeClass:function(t,e){t.className=o.Util.trim((" "+t.className+" ").replace(" "+e+" "," "))},setOpacity:function(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(o){if(1===e)return}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}},testProp:function(t){for(var i=e.documentElement.style,n=0;n<t.length;n++)if(t[n]in i)return t[n];return!1},getTranslateString:function(t){var e=o.Browser.webkit3d,i="translate"+(e?"3d":"")+"(",n=(e?",0":"")+")";return i+t.x+"px,"+t.y+"px"+n},getScaleString:function(t,e){var i=o.DomUtil.getTranslateString(e.add(e.multiplyBy(-1*t))),n=" scale("+t+") ";return i+n},setPosition:function(t,e,i){t._leaflet_pos=e,!i&&o.Browser.any3d?(t.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(e),o.Browser.mobileWebkit3d&&(t.style.WebkitBackfaceVisibility="hidden")):(t.style.left=e.x+"px",t.style.top=e.y+"px")},getPosition:function(t){return t._leaflet_pos}},o.DomUtil.TRANSFORM=o.DomUtil.testProp(["transform","WebkitTransform","OTransform","MozTransform","msTransform"]),o.DomUtil.TRANSITION=o.DomUtil.testProp(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),o.DomUtil.TRANSITION_END="webkitTransition"===o.DomUtil.TRANSITION||"OTransition"===o.DomUtil.TRANSITION?o.DomUtil.TRANSITION+"End":"transitionend",function(){var i=o.DomUtil.testProp(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]),n=o.DomUtil.testProp(["userDrag","WebkitUserDrag","OUserDrag","MozUserDrag","msUserDrag"]);o.extend(o.DomUtil,{disableTextSelection:function(){if(i){var n=e.documentElement.style;this._userSelect=n[i],n[i]="none"}else o.DomEvent.on(t,"selectstart",o.DomEvent.stop)},enableTextSelection:function(){i?(e.documentElement.style[i]=this._userSelect,delete this._userSelect):o.DomEvent.off(t,"selectstart",o.DomEvent.stop)},disableImageDrag:function(){if(n){var i=e.documentElement.style;this._userDrag=i[n],i[n]="none"}else o.DomEvent.on(t,"dragstart",o.DomEvent.stop)},enableImageDrag:function(){n?(e.documentElement.style[n]=this._userDrag,delete this._userDrag):o.DomEvent.off(t,"dragstart",o.DomEvent.stop)}})}(),o.LatLng=function(t,e){var i=parseFloat(t),n=parseFloat(e);if(isNaN(i)||isNaN(n))throw new Error("Invalid LatLng object: ("+t+", "+e+")");this.lat=i,this.lng=n},o.extend(o.LatLng,{DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,MAX_MARGIN:1e-9}),o.LatLng.prototype={equals:function(t){if(!t)return!1;t=o.latLng(t);var e=Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng));return e<=o.LatLng.MAX_MARGIN},toString:function(t){return"LatLng("+o.Util.formatNum(this.lat,t)+", "+o.Util.formatNum(this.lng,t)+")"},distanceTo:function(t){t=o.latLng(t);var e=6378137,i=o.LatLng.DEG_TO_RAD,n=(t.lat-this.lat)*i,s=(t.lng-this.lng)*i,a=this.lat*i,r=t.lat*i,h=Math.sin(n/2),l=Math.sin(s/2),u=h*h+l*l*Math.cos(a)*Math.cos(r);return 2*e*Math.atan2(Math.sqrt(u),Math.sqrt(1-u))},wrap:function(t,e){var i=this.lng;return t=t||-180,e=e||180,i=(i+e)%(e-t)+(t>i||i===e?e:t),new o.LatLng(this.lat,i)}},o.latLng=function(t,e){return t instanceof o.LatLng?t:o.Util.isArray(t)?new o.LatLng(t[0],t[1]):t===i||null===t?t:"object"==typeof t&&"lat"in t?new o.LatLng(t.lat,"lng"in t?t.lng:t.lon):new o.LatLng(t,e)},o.LatLngBounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.LatLngBounds.prototype={extend:function(t){return t?(t="number"==typeof t[0]||"string"==typeof t[0]||t instanceof o.LatLng?o.latLng(t):o.latLngBounds(t),t instanceof o.LatLng?this._southWest||this._northEast?(this._southWest.lat=Math.min(t.lat,this._southWest.lat),this._southWest.lng=Math.min(t.lng,this._southWest.lng),this._northEast.lat=Math.max(t.lat,this._northEast.lat),this._northEast.lng=Math.max(t.lng,this._northEast.lng)):(this._southWest=new o.LatLng(t.lat,t.lng),this._northEast=new o.LatLng(t.lat,t.lng)):t instanceof o.LatLngBounds&&(this.extend(t._southWest),this.extend(t._northEast)),this):this},pad:function(t){var e=this._southWest,i=this._northEast,n=Math.abs(e.lat-i.lat)*t,s=Math.abs(e.lng-i.lng)*t;return new o.LatLngBounds(new o.LatLng(e.lat-n,e.lng-s),new o.LatLng(i.lat+n,i.lng+s))},getCenter:function(){return new o.LatLng((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new o.LatLng(this.getNorth(),this.getWest())},getSouthEast:function(){return new o.LatLng(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof o.LatLng?o.latLng(t):o.latLngBounds(t);var e,i,n=this._southWest,s=this._northEast;return t instanceof o.LatLngBounds?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=n.lat&&i.lat<=s.lat&&e.lng>=n.lng&&i.lng<=s.lng},intersects:function(t){t=o.latLngBounds(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),s=t.getNorthEast(),a=s.lat>=e.lat&&n.lat<=i.lat,r=s.lng>=e.lng&&n.lng<=i.lng;return a&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t){return t?(t=o.latLngBounds(t),this._southWest.equals(t.getSouthWest())&&this._northEast.equals(t.getNorthEast())):!1},isValid:function(){return!(!this._southWest||!this._northEast)}},o.latLngBounds=function(t,e){return!t||t instanceof o.LatLngBounds?t:new o.LatLngBounds(t,e)},o.Projection={},o.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=t.lng*e,a=n*e;return a=Math.log(Math.tan(Math.PI/4+a/2)),new o.Point(s,a)},unproject:function(t){var e=o.LatLng.RAD_TO_DEG,i=t.x*e,n=(2*Math.atan(Math.exp(t.y))-Math.PI/2)*e;return new o.LatLng(n,i)}},o.Projection.LonLat={project:function(t){return new o.Point(t.lng,t.lat)},unproject:function(t){return new o.LatLng(t.y,t.x)}},o.CRS={latLngToPoint:function(t,e){var i=this.projection.project(t),n=this.scale(e);return this.transformation._transform(i,n)},pointToLatLng:function(t,e){var i=this.scale(e),n=this.transformation.untransform(t,i);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},scale:function(t){return 256*Math.pow(2,t)}},o.CRS.Simple=o.extend({},o.CRS,{projection:o.Projection.LonLat,transformation:new o.Transformation(1,0,-1,0),scale:function(t){return Math.pow(2,t)}}),o.CRS.EPSG3857=o.extend({},o.CRS,{code:"EPSG:3857",projection:o.Projection.SphericalMercator,transformation:new o.Transformation(.5/Math.PI,.5,-.5/Math.PI,.5),project:function(t){var e=this.projection.project(t),i=6378137;return e.multiplyBy(i)}}),o.CRS.EPSG900913=o.extend({},o.CRS.EPSG3857,{code:"EPSG:900913"}),o.CRS.EPSG4326=o.extend({},o.CRS,{code:"EPSG:4326",projection:o.Projection.LonLat,transformation:new o.Transformation(1/360,.5,-1/360,.5)}),o.Map=o.Class.extend({includes:o.Mixin.Events,options:{crs:o.CRS.EPSG3857,fadeAnimation:o.DomUtil.TRANSITION&&!o.Browser.android23,trackResize:!0,markerZoomAnimation:o.DomUtil.TRANSITION&&o.Browser.any3d},initialize:function(t,e){e=o.setOptions(this,e),this._initContainer(t),this._initLayout(),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),e.center&&e.zoom!==i&&this.setView(o.latLng(e.center),e.zoom,{reset:!0}),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._tileLayersNum=0,this.callInitHooks(),this._addLayers(e.layers)},setView:function(t,e){return this._resetView(o.latLng(t),this._limitZoom(e)),this},setZoom:function(t,e){return this.setView(this.getCenter(),t,{zoom:e})},zoomIn:function(t,e){return this.setZoom(this._zoom+(t||1),e)},zoomOut:function(t,e){return this.setZoom(this._zoom-(t||1),e)},setZoomAround:function(t,e,i){var n=this.getZoomScale(e),s=this.getSize().divideBy(2),a=t instanceof o.Point?t:this.latLngToContainerPoint(t),r=a.subtract(s).multiplyBy(1-1/n),h=this.containerPointToLatLng(s.add(r));return this.setView(h,e,{zoom:i})},fitBounds:function(t,e){e=e||{},t=t.getBounds?t.getBounds():o.latLngBounds(t);var i=o.point(e.paddingTopLeft||e.padding||[0,0]),n=o.point(e.paddingBottomRight||e.padding||[0,0]),s=this.getBoundsZoom(t,!1,i.add(n)),a=n.subtract(i).divideBy(2),r=this.project(t.getSouthWest(),s),h=this.project(t.getNorthEast(),s),l=this.unproject(r.add(h).divideBy(2).add(a),s);return this.setView(l,s,e)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,e){return this.setView(t,this._zoom,{pan:e})},panBy:function(t){return this.fire("movestart"),this._rawPanBy(o.point(t)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(t){if(t=o.latLngBounds(t),this.options.maxBounds=t,!t)return this._boundsMinZoom=null,this.off("moveend",this._panInsideMaxBounds,this),this;var e=this.getBoundsZoom(t,!0);return this._boundsMinZoom=e,this._loaded&&(this._zoom<e?this.setView(t.getCenter(),e):this.panInsideBounds(t)),this.on("moveend",this._panInsideMaxBounds,this),this},panInsideBounds:function(t){t=o.latLngBounds(t);var e=this.getPixelBounds(),i=e.getBottomLeft(),n=e.getTopRight(),s=this.project(t.getSouthWest()),a=this.project(t.getNorthEast()),r=0,h=0;return n.y<a.y&&(h=Math.ceil(a.y-n.y)),n.x>a.x&&(r=Math.floor(a.x-n.x)),i.y>s.y&&(h=Math.floor(s.y-i.y)),i.x<s.x&&(r=Math.ceil(s.x-i.x)),r||h?this.panBy([r,h]):this},addLayer:function(t){var e=o.stamp(t);return this._layers[e]?this:(this._layers[e]=t,!t.options||isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[e]=t,this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,t.on("load",this._onTileLayerLoad,this)),this._loaded&&this._layerAdd(t),this)},removeLayer:function(t){var e=o.stamp(t);if(this._layers[e])return this._loaded&&(t.onRemove(this),this.fire("layerremove",{layer:t})),delete this._layers[e],this._zoomBoundLayers[e]&&(delete this._zoomBoundLayers[e],this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,t.off("load",this._onTileLayerLoad,this)),this},hasLayer:function(t){return t?o.stamp(t)in this._layers:!1},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},invalidateSize:function(t){t=o.extend({animate:!1,pan:!0},t===!0?{animate:!0}:t);var e=this.getSize();if(this._sizeChanged=!0,this.options.maxBounds&&this.setMaxBounds(this.options.maxBounds),!this._loaded)return this;var i=this.getSize(),n=e.subtract(i).divideBy(2).round();return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(o.bind(this.fire,this,"moveend"),200)),this.fire("resize",{oldSize:e,newSize:i})):this},addHandler:function(t,e){if(e){var i=this[t]=new e(this);return this._handlers.push(i),this.options[t]&&i.enable(),this}},remove:function(){return this._loaded&&this.fire("unload"),this._initEvents("off"),delete this._container._leaflet,this._clearPanes(),this._clearControlPos&&this._clearControlPos(),this._clearHandlers(),this},getCenter:function(){return this._checkIfLoaded(),this._moved()?this.layerPointToLatLng(this._getCenterLayerPoint()):this._initialCenter},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new o.LatLngBounds(e,i)},getMinZoom:function(){var t=this.options.minZoom||0,e=this._layersMinZoom||0,i=this._boundsMinZoom||0;return Math.max(t,e,i)},getMaxZoom:function(){var t=this.options.maxZoom===i?1/0:this.options.maxZoom,e=this._layersMaxZoom===i?1/0:this._layersMaxZoom;return Math.min(t,e)},getBoundsZoom:function(t,e,i){t=o.latLngBounds(t);var n,s=this.getMinZoom()-(e?1:0),a=this.getMaxZoom(),r=this.getSize(),h=t.getNorthWest(),l=t.getSouthEast(),u=!0;i=o.point(i||[0,0]);do s++,n=this.project(l,s).subtract(this.project(h,s)).add(i),u=e?n.x<r.x||n.y<r.y:r.contains(n);while(u&&a>=s);return u&&e?null:e?s:s-1},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new o.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(){var t=this._getTopLeftPoint();return new o.Bounds(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t){var e=this.options.crs;return e.scale(t)/e.scale(this._zoom)},getScaleZoom:function(t){return this._zoom+Math.log(t)/Math.LN2},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(o.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(o.point(t),e)},layerPointToLatLng:function(t){var e=o.point(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(o.latLng(t))._round();return e._subtract(this.getPixelOrigin())},containerPointToLayerPoint:function(t){return o.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return o.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(o.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(o.latLng(t)))},mouseEventToContainerPoint:function(t){return o.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=o.DomUtil.get(t);if(!e)throw new Error("Map container not found.");if(e._leaflet)throw new Error("Map container is already initialized.");e._leaflet=!0},_initLayout:function(){var t=this._container;o.DomUtil.addClass(t,"leaflet-container"+(o.Browser.touch?" leaflet-touch":"")+(o.Browser.retina?" leaflet-retina":"")+(this.options.fadeAnimation?" leaflet-fade-anim":""));var e=o.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._mapPane=t.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=t.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),t.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),t.shadowPane=this._createPane("leaflet-shadow-pane"),t.overlayPane=this._createPane("leaflet-overlay-pane"),t.markerPane=this._createPane("leaflet-marker-pane"),t.popupPane=this._createPane("leaflet-popup-pane");var e=" leaflet-zoom-hide";this.options.markerZoomAnimation||(o.DomUtil.addClass(t.markerPane,e),o.DomUtil.addClass(t.shadowPane,e),o.DomUtil.addClass(t.popupPane,e))},_createPane:function(t,e){return o.DomUtil.create("div",t,e||this._panes.objectsPane)},_clearPanes:function(){this._container.removeChild(this._mapPane)},_addLayers:function(t){t=t?o.Util.isArray(t)?t:[t]:[];for(var e=0,i=t.length;i>e;e++)this.addLayer(t[e])},_resetView:function(t,e,i,n){var s=this._zoom!==e;n||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=e,this._initialCenter=t,this._initialTopLeftPoint=this._getNewTopLeftPoint(t),i?this._initialTopLeftPoint._add(this._getMapPanePos()):o.DomUtil.setPosition(this._mapPane,new o.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum;var a=!this._loaded;this._loaded=!0,a&&(this.fire("load"),this.eachLayer(this._layerAdd,this)),this.fire("viewreset",{hard:!i}),this.fire("move"),(s||n)&&this.fire("zoomend"),this.fire("moveend",{hard:!i})},_rawPanBy:function(t){o.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_updateZoomLevels:function(){var t,e=1/0,n=-1/0,o=this._getZoomSpan();for(t in this._zoomBoundLayers){var s=this._zoomBoundLayers[t];isNaN(s.options.minZoom)||(e=Math.min(e,s.options.minZoom)),isNaN(s.options.maxZoom)||(n=Math.max(n,s.options.maxZoom))}t===i?this._layersMaxZoom=this._layersMinZoom=i:(this._layersMaxZoom=n,this._layersMinZoom=e),o!==this._getZoomSpan()&&this.fire("zoomlevelschange")},_panInsideMaxBounds:function(){this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){if(o.DomEvent){e=e||"on",o.DomEvent[e](this._container,"click",this._onMouseClick,this);var i,n,s=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"];for(i=0,n=s.length;n>i;i++)o.DomEvent[e](this._container,s[i],this._fireMouseEvent,this);this.options.trackResize&&o.DomEvent[e](t,"resize",this._onResize,this)}},_onResize:function(){o.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=o.Util.requestAnimFrame(this.invalidateSize,this,!1,this._container)},_onMouseClick:function(t){!this._loaded||!t._simulated&&this.dragging&&this.dragging.moved()||t._leaflet_stop||(this.fire("preclick"),this._fireMouseEvent(t))},_fireMouseEvent:function(t){if(this._loaded&&!t._leaflet_stop){var e=t.type;if(e="mouseenter"===e?"mouseover":"mouseleave"===e?"mouseout":e,this.hasEventListeners(e)){"contextmenu"===e&&o.DomEvent.preventDefault(t);var i=this.mouseEventToContainerPoint(t),n=this.containerPointToLayerPoint(i),s=this.layerPointToLatLng(n);this.fire(e,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t})}}},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this.fire("tilelayersload")},_clearHandlers:function(){for(var t=0,e=this._handlers.length;e>t;t++)this._handlers[t].disable()},whenReady:function(t,e){return this._loaded?t.call(e||this,this):this.on("load",t,e),this},_layerAdd:function(t){t.onAdd(this),this.fire("layeradd",{layer:t})},_getMapPanePos:function(){return o.DomUtil.getPosition(this._mapPane)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(){return this.getPixelOrigin().subtract(this._getMapPanePos())},_getNewTopLeftPoint:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._round()},_latLngToNewLayerPoint:function(t,e,i){var n=this._getNewTopLeftPoint(i,e).add(this._getMapPanePos());return this.project(t,e)._subtract(n)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom();return Math.max(e,Math.min(i,t))}}),o.map=function(t,e){return new o.Map(t,e)},o.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.314245179,R_MAJOR:6378137,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=this.R_MAJOR,a=this.R_MINOR,r=t.lng*e*s,h=n*e,l=a/s,u=Math.sqrt(1-l*l),c=u*Math.sin(h);c=Math.pow((1-c)/(1+c),.5*u);var d=Math.tan(.5*(.5*Math.PI-h))/c;return h=-s*Math.log(d),new o.Point(r,h)},unproject:function(t){for(var e,i=o.LatLng.RAD_TO_DEG,n=this.R_MAJOR,s=this.R_MINOR,a=t.x*i/n,r=s/n,h=Math.sqrt(1-r*r),l=Math.exp(-t.y/s),u=Math.PI/2-2*Math.atan(l),c=15,d=1e-7,p=c,_=.1;Math.abs(_)>d&&--p>0;)e=h*Math.sin(u),_=Math.PI/2-2*Math.atan(l*Math.pow((1-e)/(1+e),.5*h))-u,u+=_;return new o.LatLng(u*i,a)}},o.CRS.EPSG3395=o.extend({},o.CRS,{code:"EPSG:3395",projection:o.Projection.Mercator,transformation:function(){var t=o.Projection.Mercator,e=t.R_MAJOR,i=t.R_MINOR;return new o.Transformation(.5/(Math.PI*e),.5,-.5/(Math.PI*i),.5)}()}),o.TileLayer=o.Class.extend({includes:o.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",zoomOffset:0,opacity:1,unloadInvisibleTiles:o.Browser.mobile,updateWhenIdle:o.Browser.mobile},initialize:function(t,e){e=o.setOptions(this,e),e.detectRetina&&o.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomOffset++,e.minZoom>0&&e.minZoom--,this.options.maxZoom--),e.bounds&&(e.bounds=o.latLngBounds(e.bounds)),this._url=t;var i=this.options.subdomains;"string"==typeof i&&(this.options.subdomains=i.split(""))},onAdd:function(t){this._map=t,this._animated=t._zoomAnimated,this._initContainer(),this._createTileProto(),t.on({viewreset:this._reset,moveend:this._update},this),this._animated&&t.on({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||(this._limitedUpdate=o.Util.limitExecByInterval(this._update,150,this),t.on("move",this._limitedUpdate,this)),this._reset(),this._update()
6 },addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._container.parentNode.removeChild(this._container),t.off({viewreset:this._reset,moveend:this._update},this),this._animated&&t.off({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||t.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var t=this._map._panes.tilePane;return this._container&&(t.appendChild(this._container),this._setAutoZIndex(t,Math.max)),this},bringToBack:function(){var t=this._map._panes.tilePane;return this._container&&(t.insertBefore(this._container,t.firstChild),this._setAutoZIndex(t,Math.min)),this},getAttribution:function(){return this.options.attribution},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},redraw:function(){return this._map&&(this._reset({hard:!0}),this._update()),this},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t,e){var i,n,o,s=t.children,a=-e(1/0,-1/0);for(n=0,o=s.length;o>n;n++)s[n]!==this._container&&(i=parseInt(s[n].style.zIndex,10),isNaN(i)||(a=e(a,i)));this.options.zIndex=this._container.style.zIndex=(isFinite(a)?a:0)+e(1,-1)},_updateOpacity:function(){var t,e=this._tiles;if(o.Browser.ielt9)for(t in e)o.DomUtil.setOpacity(e[t],this.options.opacity);else o.DomUtil.setOpacity(this._container,this.options.opacity)},_initContainer:function(){var t=this._map._panes.tilePane;if(!this._container){if(this._container=o.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),this._animated){var e="leaflet-tile-container leaflet-zoom-animated";this._bgBuffer=o.DomUtil.create("div",e,this._container),this._bgBuffer.style.zIndex=1,this._tileContainer=o.DomUtil.create("div",e,this._container),this._tileContainer.style.zIndex=2}else this._tileContainer=this._container;t.appendChild(this._container),this.options.opacity<1&&this._updateOpacity()}},_reset:function(t){for(var e in this._tiles)this.fire("tileunload",{tile:this._tiles[e]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),this._tileContainer.innerHTML="",this._animated&&t&&t.hard&&this._clearBgBuffer(),this._initContainer()},_update:function(){if(this._map){var t=this._map.getPixelBounds(),e=this._map.getZoom(),i=this.options.tileSize;if(!(e>this.options.maxZoom||e<this.options.minZoom)){var n=o.bounds(t.min.divideBy(i)._floor(),t.max.divideBy(i)._floor());this._addTilesFromCenterOut(n),(this.options.unloadInvisibleTiles||this.options.reuseTiles)&&this._removeOtherTiles(n)}}},_addTilesFromCenterOut:function(t){var i,n,s,a=[],r=t.getCenter();for(i=t.min.y;i<=t.max.y;i++)for(n=t.min.x;n<=t.max.x;n++)s=new o.Point(n,i),this._tileShouldBeLoaded(s)&&a.push(s);var h=a.length;if(0!==h){a.sort(function(t,e){return t.distanceTo(r)-e.distanceTo(r)});var l=e.createDocumentFragment();for(this._tilesToLoad||this.fire("loading"),this._tilesToLoad+=h,n=0;h>n;n++)this._addTile(a[n],l);this._tileContainer.appendChild(l)}},_tileShouldBeLoaded:function(t){if(t.x+":"+t.y in this._tiles)return!1;var e=this.options;if(!e.continuousWorld){var i=this._getWrapTileNum();if(e.noWrap&&(t.x<0||t.x>=i)||t.y<0||t.y>=i)return!1}if(e.bounds){var n=e.tileSize,o=t.multiplyBy(n),s=o.add([n,n]),a=this._map.unproject(o),r=this._map.unproject(s);if(e.continuousWorld||e.noWrap||(a=a.wrap(),r=r.wrap()),!e.bounds.intersects([a,r]))return!1}return!0},_removeOtherTiles:function(t){var e,i,n,o;for(o in this._tiles)e=o.split(":"),i=parseInt(e[0],10),n=parseInt(e[1],10),(i<t.min.x||i>t.max.x||n<t.min.y||n>t.max.y)&&this._removeTile(o)},_removeTile:function(t){var e=this._tiles[t];this.fire("tileunload",{tile:e,url:e.src}),this.options.reuseTiles?(o.DomUtil.removeClass(e,"leaflet-tile-loaded"),this._unusedTiles.push(e)):e.parentNode===this._tileContainer&&this._tileContainer.removeChild(e),o.Browser.android||(e.onload=null,e.src=o.Util.emptyImageUrl),delete this._tiles[t]},_addTile:function(t,e){var i=this._getTilePos(t),n=this._getTile();o.DomUtil.setPosition(n,i,o.Browser.chrome||o.Browser.android23),this._tiles[t.x+":"+t.y]=n,this._loadTile(n,t),n.parentNode!==this._tileContainer&&e.appendChild(n)},_getZoomForUrl:function(){var t=this.options,e=this._map.getZoom();return t.zoomReverse&&(e=t.maxZoom-e),e+t.zoomOffset},_getTilePos:function(t){var e=this._map.getPixelOrigin(),i=this.options.tileSize;return t.multiplyBy(i).subtract(e)},getTileUrl:function(t){return o.Util.template(this._url,o.extend({s:this._getSubdomain(t),z:t.z,x:t.x,y:t.y},this.options))},_getWrapTileNum:function(){return Math.pow(2,this._getZoomForUrl())},_adjustTilePoint:function(t){var e=this._getWrapTileNum();this.options.continuousWorld||this.options.noWrap||(t.x=(t.x%e+e)%e),this.options.tms&&(t.y=e-t.y-1),t.z=this._getZoomForUrl()},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_createTileProto:function(){var t=this._tileImg=o.DomUtil.create("img","leaflet-tile");t.style.width=t.style.height=this.options.tileSize+"px",t.galleryimg="no"},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var t=this._unusedTiles.pop();return this._resetTile(t),t}return this._createTile()},_resetTile:function(){},_createTile:function(){var t=this._tileImg.cloneNode(!1);return t.onselectstart=t.onmousemove=o.Util.falseFn,o.Browser.ielt9&&this.options.opacity!==i&&o.DomUtil.setOpacity(t,this.options.opacity),t},_loadTile:function(t,e){t._layer=this,t.onload=this._tileOnLoad,t.onerror=this._tileOnError,this._adjustTilePoint(e),t.src=this.getTileUrl(e)},_tileLoaded:function(){this._tilesToLoad--,this._tilesToLoad||(this.fire("load"),this._animated&&(clearTimeout(this._clearBgBufferTimer),this._clearBgBufferTimer=setTimeout(o.bind(this._clearBgBuffer,this),500)))},_tileOnLoad:function(){var t=this._layer;this.src!==o.Util.emptyImageUrl&&(o.DomUtil.addClass(this,"leaflet-tile-loaded"),t.fire("tileload",{tile:this,url:this.src})),t._tileLoaded()},_tileOnError:function(){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var e=t.options.errorTileUrl;e&&(this.src=e),t._tileLoaded()}}),o.tileLayer=function(t,e){return new o.TileLayer(t,e)},o.TileLayer.WMS=o.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",version:"1.1.1",layers:"",styles:"",format:"image/jpeg",transparent:!1},initialize:function(t,e){this._url=t;var i=o.extend({},this.defaultWmsParams),n=e.tileSize||this.options.tileSize;i.width=i.height=e.detectRetina&&o.Browser.retina?2*n:n;for(var s in e)this.options.hasOwnProperty(s)||"crs"===s||(i[s]=e[s]);this.wmsParams=i,o.setOptions(this,e)},onAdd:function(t){this._crs=this.options.crs||t.options.crs;var e=parseFloat(this.wmsParams.version)>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,o.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t,e){var i=this._map,n=this.options.tileSize,s=t.multiplyBy(n),a=s.add([n,n]),r=this._crs.project(i.unproject(s,e)),h=this._crs.project(i.unproject(a,e)),l=[r.x,h.y,h.x,r.y].join(","),u=o.Util.template(this._url,{s:this._getSubdomain(t)});return u+o.Util.getParamString(this.wmsParams,u,!0)+"&BBOX="+l},setParams:function(t,e){return o.extend(this.wmsParams,t),e||this.redraw(),this}}),o.tileLayer.wms=function(t,e){return new o.TileLayer.WMS(t,e)},o.TileLayer.Canvas=o.TileLayer.extend({options:{async:!1},initialize:function(t){o.setOptions(this,t)},redraw:function(){for(var t in this._tiles)this._redrawTile(this._tiles[t]);return this},_redrawTile:function(t){this.drawTile(t,t._tilePoint,this._map._zoom)},_createTileProto:function(){var t=this._canvasProto=o.DomUtil.create("canvas","leaflet-tile");t.width=t.height=this.options.tileSize},_createTile:function(){var t=this._canvasProto.cloneNode(!1);return t.onselectstart=t.onmousemove=o.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t._tilePoint=e,this._redrawTile(t),this.options.async||this.tileDrawn(t)},drawTile:function(){},tileDrawn:function(t){this._tileOnLoad.call(t)}}),o.tileLayer.canvas=function(t){return new o.TileLayer.Canvas(t)},o.ImageOverlay=o.Class.extend({includes:o.Mixin.Events,options:{opacity:1},initialize:function(t,e,i){this._url=t,this._bounds=o.latLngBounds(e),o.setOptions(this,i)},onAdd:function(t){this._map=t,this._image||this._initImage(),t._panes.overlayPane.appendChild(this._image),t.on("viewreset",this._reset,this),t.options.zoomAnimation&&o.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._image),t.off("viewreset",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var t=this._map._panes.overlayPane;return this._image&&t.insertBefore(this._image,t.firstChild),this},_initImage:function(){this._image=o.DomUtil.create("img","leaflet-image-layer"),this._map.options.zoomAnimation&&o.Browser.any3d?o.DomUtil.addClass(this._image,"leaflet-zoom-animated"):o.DomUtil.addClass(this._image,"leaflet-zoom-hide"),this._updateOpacity(),o.extend(this._image,{galleryimg:"no",onselectstart:o.Util.falseFn,onmousemove:o.Util.falseFn,onload:o.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(t){var e=this._map,i=this._image,n=e.getZoomScale(t.zoom),s=this._bounds.getNorthWest(),a=this._bounds.getSouthEast(),r=e._latLngToNewLayerPoint(s,t.zoom,t.center),h=e._latLngToNewLayerPoint(a,t.zoom,t.center)._subtract(r),l=r._add(h._multiplyBy(.5*(1-1/n)));i.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(l)+" scale("+n+") "},_reset:function(){var t=this._image,e=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),i=this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(e);o.DomUtil.setPosition(t,e),t.style.width=i.x+"px",t.style.height=i.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){o.DomUtil.setOpacity(this._image,this.options.opacity)}}),o.imageOverlay=function(t,e,i){return new o.ImageOverlay(t,e,i)},o.Icon=o.Class.extend({options:{className:""},initialize:function(t){o.setOptions(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(!i){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n;return n=e&&"IMG"===e.tagName?this._createImg(i,e):this._createImg(i),this._setIconStyles(n,t),n},_setIconStyles:function(t,e){var i,n=this.options,s=o.point(n[e+"Size"]);i="shadow"===e?o.point(n.shadowAnchor||n.iconAnchor):o.point(n.iconAnchor),!i&&s&&(i=s.divideBy(2,!0)),t.className="leaflet-marker-"+e+" "+n.className,i&&(t.style.marginLeft=-i.x+"px",t.style.marginTop=-i.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t,i){return o.Browser.ie6?(i||(i=e.createElement("div")),i.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+t+'")'):(i||(i=e.createElement("img")),i.src=t),i},_getIconUrl:function(t){return o.Browser.retina&&this.options[t+"RetinaUrl"]?this.options[t+"RetinaUrl"]:this.options[t+"Url"]}}),o.icon=function(t){return new o.Icon(t)},o.Icon.Default=o.Icon.extend({options:{iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],shadowSize:[41,41]},_getIconUrl:function(t){var e=t+"Url";if(this.options[e])return this.options[e];o.Browser.retina&&"icon"===t&&(t+="-2x");var i=o.Icon.Default.imagePath;if(!i)throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return i+"/marker-"+t+".png"}}),o.Icon.Default.imagePath=function(){var t,i,n,o,s,a=e.getElementsByTagName("script"),r=/[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;for(t=0,i=a.length;i>t;t++)if(n=a[t].src,o=n.match(r))return s=n.split(r)[0],(s?s+"/":"")+"images"}(),o.Marker=o.Class.extend({includes:o.Mixin.Events,options:{icon:new o.Icon.Default,title:"",clickable:!0,draggable:!1,keyboard:!0,zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250},initialize:function(t,e){o.setOptions(this,e),this._latlng=o.latLng(t)},onAdd:function(t){this._map=t,t.on("viewreset",this.update,this),this._initIcon(),this.update(),t.options.zoomAnimation&&t.options.markerZoomAnimation&&t.on("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this.dragging&&this.dragging.disable(),this._removeIcon(),this._removeShadow(),this.fire("remove"),t.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this.update(),this.fire("move",{latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update(),this},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e=this._map,i=e.options.zoomAnimation&&e.options.markerZoomAnimation,n=i?"leaflet-zoom-animated":"leaflet-zoom-hide",s=t.icon.createIcon(this._icon),a=!1;s!==this._icon&&(this._icon&&this._removeIcon(),a=!0,t.title&&(s.title=t.title)),o.DomUtil.addClass(s,n),t.keyboard&&(s.tabIndex="0"),this._icon=s,this._initInteraction(),t.riseOnHover&&o.DomEvent.on(s,"mouseover",this._bringToFront,this).on(s,"mouseout",this._resetZIndex,this);var r=t.icon.createShadow(this._shadow),h=!1;r!==this._shadow&&(this._removeShadow(),h=!0,r&&o.DomUtil.addClass(r,n)),this._shadow=r,t.opacity<1&&this._updateOpacity();var l=this._map._panes;a&&l.markerPane.appendChild(this._icon),r&&h&&l.shadowPane.appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&o.DomEvent.off(this._icon,"mouseover",this._bringToFront).off(this._icon,"mouseout",this._resetZIndex),this._map._panes.markerPane.removeChild(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&this._map._panes.shadowPane.removeChild(this._shadow),this._shadow=null},_setPos:function(t){o.DomUtil.setPosition(this._icon,t),this._shadow&&o.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPos(e)},_initInteraction:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];o.DomUtil.addClass(t,"leaflet-clickable"),o.DomEvent.on(t,"click",this._onMouseClick,this),o.DomEvent.on(t,"keypress",this._onKeyPress,this);for(var i=0;i<e.length;i++)o.DomEvent.on(t,e[i],this._fireMouseEvent,this);o.Handler.MarkerDrag&&(this.dragging=new o.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())}},_onMouseClick:function(t){var e=this.dragging&&this.dragging.moved();(this.hasEventListeners(t.type)||e)&&o.DomEvent.stopPropagation(t),e||(this.dragging&&this.dragging._enabled||!this._map.dragging||!this._map.dragging.moved())&&this.fire(t.type,{originalEvent:t,latlng:this._latlng})},_onKeyPress:function(t){13===t.keyCode&&this.fire("click",{originalEvent:t,latlng:this._latlng})},_fireMouseEvent:function(t){this.fire(t.type,{originalEvent:t,latlng:this._latlng}),"contextmenu"===t.type&&this.hasEventListeners(t.type)&&o.DomEvent.preventDefault(t),"mousedown"!==t.type?o.DomEvent.stopPropagation(t):o.DomEvent.preventDefault(t)},setOpacity:function(t){this.options.opacity=t,this._map&&this._updateOpacity()},_updateOpacity:function(){o.DomUtil.setOpacity(this._icon,this.options.opacity),this._shadow&&o.DomUtil.setOpacity(this._shadow,this.options.opacity)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)}}),o.marker=function(t,e){return new o.Marker(t,e)},o.DivIcon=o.Icon.extend({options:{iconSize:[12,12],className:"leaflet-div-icon",html:!1},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:e.createElement("div"),n=this.options;return i.innerHTML=n.html!==!1?n.html:"",n.bgPos&&(i.style.backgroundPosition=-n.bgPos.x+"px "+-n.bgPos.y+"px"),this._setIconStyles(i,"icon"),i},createShadow:function(){return null}}),o.divIcon=function(t){return new o.DivIcon(t)},o.Map.mergeOptions({closePopupOnClick:!0}),o.Popup=o.Class.extend({includes:o.Mixin.Events,options:{minWidth:50,maxWidth:300,maxHeight:null,autoPan:!0,closeButton:!0,offset:[0,7],autoPanPadding:[5,5],keepInView:!1,className:"",zoomAnimation:!0},initialize:function(t,e){o.setOptions(this,t),this._source=e,this._animated=o.Browser.any3d&&this.options.zoomAnimation,this._isOpen=!1},onAdd:function(t){this._map=t,this._container||this._initLayout(),this._updateContent();var e=t.options.fadeAnimation;e&&o.DomUtil.setOpacity(this._container,0),t._panes.popupPane.appendChild(this._container),t.on(this._getEvents(),this),this._update(),e&&o.DomUtil.setOpacity(this._container,1),this.fire("open"),t.fire("popupopen",{popup:this}),this._source&&this._source.fire("popupopen",{popup:this})},addTo:function(t){return t.addLayer(this),this},openOn:function(t){return t.openPopup(this),this},onRemove:function(t){t._panes.popupPane.removeChild(this._container),o.Util.falseFn(this._container.offsetWidth),t.off(this._getEvents(),this),t.options.fadeAnimation&&o.DomUtil.setOpacity(this._container,0),this._map=null,this.fire("close"),t.fire("popupclose",{popup:this}),this._source&&this._source.fire("popupclose",{popup:this})},setLatLng:function(t){return this._latlng=o.latLng(t),this._update(),this},setContent:function(t){return this._content=t,this._update(),this},_getEvents:function(){var t={viewreset:this._updatePosition};return this._animated&&(t.zoomanim=this._zoomAnimation),("closeOnClick"in this.options?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t,e="leaflet-popup",i=e+" "+this.options.className+" leaflet-zoom-"+(this._animated?"animated":"hide"),n=this._container=o.DomUtil.create("div",i);this.options.closeButton&&(t=this._closeButton=o.DomUtil.create("a",e+"-close-button",n),t.href="#close",t.innerHTML="&#215;",o.DomEvent.disableClickPropagation(t),o.DomEvent.on(t,"click",this._onCloseButtonClick,this));var s=this._wrapper=o.DomUtil.create("div",e+"-content-wrapper",n);o.DomEvent.disableClickPropagation(s),this._contentNode=o.DomUtil.create("div",e+"-content",s),o.DomEvent.on(this._contentNode,"wheel",o.DomEvent.stopPropagation),o.DomEvent.on(s,"contextmenu",o.DomEvent.stopPropagation),this._tipContainer=o.DomUtil.create("div",e+"-tip-container",n),this._tip=o.DomUtil.create("div",e+"-tip",this._tipContainer)},_update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},_updateContent:function(){if(this._content){if("string"==typeof this._content)this._contentNode.innerHTML=this._content;else{for(;this._contentNode.hasChildNodes();)this._contentNode.removeChild(this._contentNode.firstChild);this._contentNode.appendChild(this._content)}this.fire("contentupdate")}},_updateLayout:function(){var t=this._contentNode,e=t.style;e.width="",e.whiteSpace="nowrap";var i=t.offsetWidth;i=Math.min(i,this.options.maxWidth),i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="";var n=t.offsetHeight,s=this.options.maxHeight,a="leaflet-popup-scrolled";s&&n>s?(e.height=s+"px",o.DomUtil.addClass(t,a)):o.DomUtil.removeClass(t,a),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=this._animated,i=o.point(this.options.offset);e&&o.DomUtil.setPosition(this._container,t),this._containerBottom=-i.y-(e?0:t.y),this._containerLeft=-Math.round(this._containerWidth/2)+i.x+(e?0:t.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"}},_zoomAnimation:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);o.DomUtil.setPosition(this._container,e)},_adjustPan:function(){if(this.options.autoPan){var t=this._map,e=this._container.offsetHeight,i=this._containerWidth,n=new o.Point(this._containerLeft,-e-this._containerBottom);this._animated&&n._add(o.DomUtil.getPosition(this._container));var s=t.layerPointToContainerPoint(n),a=o.point(this.options.autoPanPadding),r=t.getSize(),h=0,l=0;s.x+i>r.x&&(h=s.x+i-r.x+a.x),s.x-h<0&&(h=s.x-a.x),s.y+e>r.y&&(l=s.y+e-r.y+a.y),s.y-l<0&&(l=s.y-a.y),(h||l)&&t.fire("autopanstart").panBy([h,l])}},_onCloseButtonClick:function(t){this._close(),o.DomEvent.stop(t)}}),o.popup=function(t,e){return new o.Popup(t,e)},o.Map.include({openPopup:function(t,e,i){if(this.closePopup(),!(t instanceof o.Popup)){var n=t;t=new o.Popup(i).setLatLng(e).setContent(n)}return t._isOpen=!0,this._popup=t,this.addLayer(t)},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&(this.removeLayer(t),t._isOpen=!1),this}}),o.Marker.include({openPopup:function(){return this._popup&&this._map&&!this._map.hasLayer(this._popup)&&(this._popup.setLatLng(this._latlng),this._map.openPopup(this._popup)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(){return this._popup&&(this._popup._isOpen?this.closePopup():this.openPopup()),this},bindPopup:function(t,e){var i=o.point(this.options.icon.options.popupAnchor||[0,0]);return i=i.add(o.Popup.prototype.options.offset),e&&e.offset&&(i=i.add(e.offset)),e=o.extend({offset:i},e),this._popup||this.on("click",this.togglePopup,this).on("remove",this.closePopup,this).on("move",this._movePopup,this),t instanceof o.Popup?(o.setOptions(t,e),this._popup=t):this._popup=new o.Popup(e,this).setContent(t),this},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.togglePopup).off("remove",this.closePopup).off("move",this._movePopup)),this},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),o.LayerGroup=o.Class.extend({initialize:function(t){this._layers={};var e,i;if(t)for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},addLayer:function(t){var e=this.getLayerId(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var e=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[e]&&this._map.removeLayer(this._layers[e]),delete this._layers[e],this},hasLayer:function(t){return t?t in this._layers||this.getLayerId(t)in this._layers:!1},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)i=this._layers[e],i[t]&&i[t].apply(i,n);return this},onAdd:function(t){this._map=t,this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t),this._map=null},addTo:function(t){return t.addLayer(this),this},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];for(var e in this._layers)t.push(this._layers[e]);return t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return o.stamp(t)}}),o.layerGroup=function(t){return new o.LayerGroup(t)},o.FeatureGroup=o.LayerGroup.extend({includes:o.Mixin.Events,statics:{EVENTS:"click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose"},addLayer:function(t){return this.hasLayer(t)?this:(t.on(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.addLayer.call(this,t),this._popupContent&&t.bindPopup&&t.bindPopup(this._popupContent,this._popupOptions),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return t in this._layers&&(t=this._layers[t]),t.off(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.removeLayer.call(this,t),this._popupContent&&this.invoke("unbindPopup"),this.fire("layerremove",{layer:t})},bindPopup:function(t,e){return this._popupContent=t,this._popupOptions=e,this.invoke("bindPopup",t,e)},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new o.LatLngBounds;return this.eachLayer(function(e){t.extend(e instanceof o.Marker?e.getLatLng():e.getBounds())}),t},_propagateEvent:function(t){t.layer||(t.layer=t.target),t.target=this,this.fire(t.type,t)}}),o.featureGroup=function(t){return new o.FeatureGroup(t)},o.Path=o.Class.extend({includes:[o.Mixin.Events],statics:{CLIP_PADDING:o.Browser.mobile?Math.max(0,Math.min(.5,(1280/Math.max(t.innerWidth,t.innerHeight)-1)/2)):.5},options:{stroke:!0,color:"#0033ff",dashArray:null,weight:5,opacity:.5,fill:!1,fillColor:null,fillOpacity:.2,clickable:!0},initialize:function(t){o.setOptions(this,t)},onAdd:function(t){this._map=t,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),this.fire("add"),t.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){t._pathRoot.removeChild(this._container),this.fire("remove"),this._map=null,o.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),t.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(t){return o.setOptions(this,t),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),o.Map.include({_updatePathViewport:function(){var t=o.Path.CLIP_PADDING,e=this.getSize(),i=o.DomUtil.getPosition(this._mapPane),n=i.multiplyBy(-1)._subtract(e.multiplyBy(t)._round()),s=n.add(e.multiplyBy(1+2*t)._round());this._pathViewport=new o.Bounds(n,s)}}),o.Path.SVG_NS="http://www.w3.org/2000/svg",o.Browser.svg=!(!e.createElementNS||!e.createElementNS(o.Path.SVG_NS,"svg").createSVGRect),o.Path=o.Path.extend({statics:{SVG:o.Browser.svg},bringToFront:function(){var t=this._map._pathRoot,e=this._container;return e&&t.lastChild!==e&&t.appendChild(e),this},bringToBack:function(){var t=this._map._pathRoot,e=this._container,i=t.firstChild;return e&&i!==e&&t.insertBefore(e,i),this},getPathString:function(){},_createElement:function(t){return e.createElementNS(o.Path.SVG_NS,t)},_initElements:function(){this._map._initPathRoot(),this._initPath(),this._initStyle()},_initPath:function(){this._container=this._createElement("g"),this._path=this._createElement("path"),this._container.appendChild(this._path)},_initStyle:function(){this.options.stroke&&(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round")),this.options.fill&&this._path.setAttribute("fill-rule","evenodd"),this.options.pointerEvents&&this._path.setAttribute("pointer-events",this.options.pointerEvents),this.options.clickable||this.options.pointerEvents||this._path.setAttribute("pointer-events","none"),this._updateStyle()},_updateStyle:function(){this.options.stroke?(this._path.setAttribute("stroke",this.options.color),this._path.setAttribute("stroke-opacity",this.options.opacity),this._path.setAttribute("stroke-width",this.options.weight),this.options.dashArray?this._path.setAttribute("stroke-dasharray",this.options.dashArray):this._path.removeAttribute("stroke-dasharray")):this._path.setAttribute("stroke","none"),this.options.fill?(this._path.setAttribute("fill",this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity)):this._path.setAttribute("fill","none")},_updatePath:function(){var t=this.getPathString();t||(t="M0 0"),this._path.setAttribute("d",t)},_initEvents:function(){if(this.options.clickable){(o.Browser.svg||!o.Browser.vml)&&this._path.setAttribute("class","leaflet-clickable"),o.DomEvent.on(this._container,"click",this._onMouseClick,this);for(var t=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"],e=0;e<t.length;e++)o.DomEvent.on(this._container,t[e],this._fireMouseEvent,this)}},_onMouseClick:function(t){this._map.dragging&&this._map.dragging.moved()||this._fireMouseEvent(t)},_fireMouseEvent:function(t){if(this.hasEventListeners(t.type)){var e=this._map,i=e.mouseEventToContainerPoint(t),n=e.containerPointToLayerPoint(i),s=e.layerPointToLatLng(n);this.fire(t.type,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t}),"contextmenu"===t.type&&o.DomEvent.preventDefault(t),"mousemove"!==t.type&&o.DomEvent.stopPropagation(t)}}}),o.Map.include({_initPathRoot:function(){this._pathRoot||(this._pathRoot=o.Path.prototype._createElement("svg"),this._panes.overlayPane.appendChild(this._pathRoot),this.options.zoomAnimation&&o.Browser.any3d?(this._pathRoot.setAttribute("class"," leaflet-zoom-animated"),this.on({zoomanim:this._animatePathZoom,zoomend:this._endPathZoom})):this._pathRoot.setAttribute("class"," leaflet-zoom-hide"),this.on("moveend",this._updateSvgViewport),this._updateSvgViewport())},_animatePathZoom:function(t){var e=this.getZoomScale(t.zoom),i=this._getCenterOffset(t.center)._multiplyBy(-e)._add(this._pathViewport.min);this._pathRoot.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(i)+" scale("+e+") ",this._pathZooming=!0},_endPathZoom:function(){this._pathZooming=!1},_updateSvgViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max,n=i.x-e.x,s=i.y-e.y,a=this._pathRoot,r=this._panes.overlayPane;o.Browser.mobileWebkit&&r.removeChild(a),o.DomUtil.setPosition(a,e),a.setAttribute("width",n),a.setAttribute("height",s),a.setAttribute("viewBox",[e.x,e.y,n,s].join(" ")),o.Browser.mobileWebkit&&r.appendChild(a)}}}),o.Path.include({bindPopup:function(t,e){return t instanceof o.Popup?this._popup=t:((!this._popup||e)&&(this._popup=new o.Popup(e,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on("click",this._openPopup,this).on("remove",this.closePopup,this),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this._openPopup).off("remove",this.closePopup),this._popupHandlersAdded=!1),this},openPopup:function(t){return this._popup&&(t=t||this._latlng||this._latlngs[Math.floor(this._latlngs.length/2)],this._openPopup({latlng:t})),this},closePopup:function(){return this._popup&&this._popup._close(),this},_openPopup:function(t){this._popup.setLatLng(t.latlng),this._map.openPopup(this._popup)}}),o.Browser.vml=!o.Browser.svg&&function(){try{var t=e.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(n){return!1}}(),o.Path=o.Browser.svg||!o.Browser.vml?o.Path:o.Path.extend({statics:{VML:!0,CLIP_PADDING:.02},_createElement:function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var t=this._container=this._createElement("shape");o.DomUtil.addClass(t,"leaflet-vml-shape"),this.options.clickable&&o.DomUtil.addClass(t,"leaflet-clickable"),t.coordsize="1 1",this._path=this._createElement("path"),t.appendChild(this._path),this._map._pathRoot.appendChild(t)
7 },_initStyle:function(){this._updateStyle()},_updateStyle:function(){var t=this._stroke,e=this._fill,i=this.options,n=this._container;n.stroked=i.stroke,n.filled=i.fill,i.stroke?(t||(t=this._stroke=this._createElement("stroke"),t.endcap="round",n.appendChild(t)),t.weight=i.weight+"px",t.color=i.color,t.opacity=i.opacity,t.dashStyle=i.dashArray?i.dashArray instanceof Array?i.dashArray.join(" "):i.dashArray.replace(/( *, *)/g," "):""):t&&(n.removeChild(t),this._stroke=null),i.fill?(e||(e=this._fill=this._createElement("fill"),n.appendChild(e)),e.color=i.fillColor||i.color,e.opacity=i.fillOpacity):e&&(n.removeChild(e),this._fill=null)},_updatePath:function(){var t=this._container.style;t.display="none",this._path.v=this.getPathString()+" ",t.display=""}}),o.Map.include(o.Browser.svg||!o.Browser.vml?{}:{_initPathRoot:function(){if(!this._pathRoot){var t=this._pathRoot=e.createElement("div");t.className="leaflet-vml-container",this._panes.overlayPane.appendChild(t),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}}),o.Browser.canvas=function(){return!!e.createElement("canvas").getContext}(),o.Path=o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?o.Path:o.Path.extend({statics:{CANVAS:!0,SVG:!1},redraw:function(){return this._map&&(this.projectLatlngs(),this._requestUpdate()),this},setStyle:function(t){return o.setOptions(this,t),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(t){t.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this.options.clickable&&(this._map.off("click",this._onClick,this),this._map.off("mousemove",this._onMouseMove,this)),this._requestUpdate(),this._map=null},_requestUpdate:function(){this._map&&!o.Path._updateRequest&&(o.Path._updateRequest=o.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){o.Path._updateRequest=null,this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var t=this.options;t.stroke&&(this._ctx.lineWidth=t.weight,this._ctx.strokeStyle=t.color),t.fill&&(this._ctx.fillStyle=t.fillColor||t.color)},_drawPath:function(){var t,e,i,n,s,a;for(this._ctx.beginPath(),t=0,i=this._parts.length;i>t;t++){for(e=0,n=this._parts[t].length;n>e;e++)s=this._parts[t][e],a=(0===e?"move":"line")+"To",this._ctx[a](s.x,s.y);this instanceof o.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(!this._checkIfEmpty()){var t=this._ctx,e=this.options;this._drawPath(),t.save(),this._updateStyle(),e.fill&&(t.globalAlpha=e.fillOpacity,t.fill()),e.stroke&&(t.globalAlpha=e.opacity,t.stroke()),t.restore()}},_initEvents:function(){this.options.clickable&&(this._map.on("mousemove",this._onMouseMove,this),this._map.on("click",this._onClick,this))},_onClick:function(t){this._containsPoint(t.layerPoint)&&this.fire("click",t)},_onMouseMove:function(t){this._map&&!this._map._animatingZoom&&(this._containsPoint(t.layerPoint)?(this._ctx.canvas.style.cursor="pointer",this._mouseInside=!0,this.fire("mouseover",t)):this._mouseInside&&(this._ctx.canvas.style.cursor="",this._mouseInside=!1,this.fire("mouseout",t)))}}),o.Map.include(o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?{}:{_initPathRoot:function(){var t,i=this._pathRoot;i||(i=this._pathRoot=e.createElement("canvas"),i.style.position="absolute",t=this._canvasCtx=i.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(i),this.options.zoomAnimation&&(this._pathRoot.className="leaflet-zoom-animated",this.on("zoomanim",this._animatePathZoom),this.on("zoomend",this._endPathZoom)),this.on("moveend",this._updateCanvasViewport),this._updateCanvasViewport())},_updateCanvasViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max.subtract(e),n=this._pathRoot;o.DomUtil.setPosition(n,e),n.width=i.x,n.height=i.y,n.getContext("2d").translate(-e.x,-e.y)}}}),o.LineUtil={simplify:function(t,e){if(!e||!t.length)return t.slice();var i=e*e;return t=this._reducePoints(t,i),t=this._simplifyDP(t,i)},pointToSegmentDistance:function(t,e,i){return Math.sqrt(this._sqClosestPointOnSegment(t,e,i,!0))},closestPointOnSegment:function(t,e,i){return this._sqClosestPointOnSegment(t,e,i)},_simplifyDP:function(t,e){var n=t.length,o=typeof Uint8Array!=i+""?Uint8Array:Array,s=new o(n);s[0]=s[n-1]=1,this._simplifyDPStep(t,s,e,0,n-1);var a,r=[];for(a=0;n>a;a++)s[a]&&r.push(t[a]);return r},_simplifyDPStep:function(t,e,i,n,o){var s,a,r,h=0;for(a=n+1;o-1>=a;a++)r=this._sqClosestPointOnSegment(t[a],t[n],t[o],!0),r>h&&(s=a,h=r);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;s>n;n++)this._sqDist(t[n],t[o])>e&&(i.push(t[n]),o=n);return s-1>o&&i.push(t[s-1]),i},clipSegment:function(t,e,i,n){var o,s,a,r=n?this._lastCode:this._getBitCode(t,i),h=this._getBitCode(e,i);for(this._lastCode=h;;){if(!(r|h))return[t,e];if(r&h)return!1;o=r||h,s=this._getEdgeIntersection(t,e,o,i),a=this._getBitCode(s,i),o===r?(t=s,r=a):(e=s,h=a)}},_getEdgeIntersection:function(t,e,i,n){var s=e.x-t.x,a=e.y-t.y,r=n.min,h=n.max;return 8&i?new o.Point(t.x+s*(h.y-t.y)/a,h.y):4&i?new o.Point(t.x+s*(r.y-t.y)/a,r.y):2&i?new o.Point(h.x,t.y+a*(h.x-t.x)/s):1&i?new o.Point(r.x,t.y+a*(r.x-t.x)/s):void 0},_getBitCode:function(t,e){var i=0;return t.x<e.min.x?i|=1:t.x>e.max.x&&(i|=2),t.y<e.min.y?i|=4:t.y>e.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,n){var s,a=e.x,r=e.y,h=i.x-a,l=i.y-r,u=h*h+l*l;return u>0&&(s=((t.x-a)*h+(t.y-r)*l)/u,s>1?(a=i.x,r=i.y):s>0&&(a+=h*s,r+=l*s)),h=t.x-a,l=t.y-r,n?h*h+l*l:new o.Point(a,r)}},o.Polyline=o.Path.extend({initialize:function(t,e){o.Path.prototype.initialize.call(this,e),this._latlngs=this._convertLatLngs(t)},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var t=0,e=this._latlngs.length;e>t;t++)this._originalPoints[t]=this._map.latLngToLayerPoint(this._latlngs[t])},getPathString:function(){for(var t=0,e=this._parts.length,i="";e>t;t++)i+=this._getPathPartStr(this._parts[t]);return i},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._latlngs=this._convertLatLngs(t),this.redraw()},addLatLng:function(t){return this._latlngs.push(o.latLng(t)),this.redraw()},spliceLatLngs:function(){var t=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs,!0),this.redraw(),t},closestLayerPoint:function(t){for(var e,i,n=1/0,s=this._parts,a=null,r=0,h=s.length;h>r;r++)for(var l=s[r],u=1,c=l.length;c>u;u++){e=l[u-1],i=l[u];var d=o.LineUtil._sqClosestPointOnSegment(t,e,i,!0);n>d&&(n=d,a=o.LineUtil._sqClosestPointOnSegment(t,e,i))}return a&&(a.distance=Math.sqrt(n)),a},getBounds:function(){return new o.LatLngBounds(this.getLatLngs())},_convertLatLngs:function(t,e){var i,n,s=e?t:[];for(i=0,n=t.length;n>i;i++){if(o.Util.isArray(t[i])&&"number"!=typeof t[i][0])return;s[i]=o.latLng(t[i])}return s},_initEvents:function(){o.Path.prototype._initEvents.call(this)},_getPathPartStr:function(t){for(var e,i=o.Path.VML,n=0,s=t.length,a="";s>n;n++)e=t[n],i&&e._round(),a+=(n?"L":"M")+e.x+" "+e.y;return a},_clipPoints:function(){var t,e,i,n=this._originalPoints,s=n.length;if(this.options.noClip)return this._parts=[n],void 0;this._parts=[];var a=this._parts,r=this._map._pathViewport,h=o.LineUtil;for(t=0,e=0;s-1>t;t++)i=h.clipSegment(n[t],n[t+1],r,t),i&&(a[e]=a[e]||[],a[e].push(i[0]),(i[1]!==n[t+1]||t===s-2)&&(a[e].push(i[1]),e++))},_simplifyPoints:function(){for(var t=this._parts,e=o.LineUtil,i=0,n=t.length;n>i;i++)t[i]=e.simplify(t[i],this.options.smoothFactor)},_updatePath:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),o.Path.prototype._updatePath.call(this))}}),o.polyline=function(t,e){return new o.Polyline(t,e)},o.PolyUtil={},o.PolyUtil.clipPolygon=function(t,e){var i,n,s,a,r,h,l,u,c,d=[1,4,2,8],p=o.LineUtil;for(n=0,l=t.length;l>n;n++)t[n]._code=p._getBitCode(t[n],e);for(a=0;4>a;a++){for(u=d[a],i=[],n=0,l=t.length,s=l-1;l>n;s=n++)r=t[n],h=t[s],r._code&u?h._code&u||(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)):(h._code&u&&(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)),i.push(r));t=i}return t},o.Polygon=o.Polyline.extend({options:{fill:!0},initialize:function(t,e){var i,n,s;if(o.Polyline.prototype.initialize.call(this,t,e),t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0])for(this._latlngs=this._convertLatLngs(t[0]),this._holes=t.slice(1),i=0,n=this._holes.length;n>i;i++)s=this._holes[i]=this._convertLatLngs(this._holes[i]),s[0].equals(s[s.length-1])&&s.pop();t=this._latlngs,t.length>=2&&t[0].equals(t[t.length-1])&&t.pop()},projectLatlngs:function(){if(o.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[],this._holes){var t,e,i,n;for(t=0,i=this._holes.length;i>t;t++)for(this._holePoints[t]=[],e=0,n=this._holes[t].length;n>e;e++)this._holePoints[t][e]=this._map.latLngToLayerPoint(this._holes[t][e])}},_clipPoints:function(){var t=this._originalPoints,e=[];if(this._parts=[t].concat(this._holePoints),!this.options.noClip){for(var i=0,n=this._parts.length;n>i;i++){var s=o.PolyUtil.clipPolygon(this._parts[i],this._map._pathViewport);s.length&&e.push(s)}this._parts=e}},_getPathPartStr:function(t){var e=o.Polyline.prototype._getPathPartStr.call(this,t);return e+(o.Browser.svg?"z":"x")}}),o.polygon=function(t,e){return new o.Polygon(t,e)},function(){function t(t){return o.FeatureGroup.extend({initialize:function(t,e){this._layers={},this._options=e,this.setLatLngs(t)},setLatLngs:function(e){var i=0,n=e.length;for(this.eachLayer(function(t){n>i?t.setLatLngs(e[i++]):this.removeLayer(t)},this);n>i;)this.addLayer(new t(e[i++],this._options));return this}})}o.MultiPolyline=t(o.Polyline),o.MultiPolygon=t(o.Polygon),o.multiPolyline=function(t,e){return new o.MultiPolyline(t,e)},o.multiPolygon=function(t,e){return new o.MultiPolygon(t,e)}}(),o.Rectangle=o.Polygon.extend({initialize:function(t,e){o.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=o.latLngBounds(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),o.rectangle=function(t,e){return new o.Rectangle(t,e)},o.Circle=o.Path.extend({initialize:function(t,e,i){o.Path.prototype.initialize.call(this,i),this._latlng=o.latLng(t),this._mRadius=e},options:{fill:!0},setLatLng:function(t){return this._latlng=o.latLng(t),this.redraw()},setRadius:function(t){return this._mRadius=t,this.redraw()},projectLatlngs:function(){var t=this._getLngRadius(),e=this._latlng,i=this._map.latLngToLayerPoint([e.lat,e.lng-t]);this._point=this._map.latLngToLayerPoint(e),this._radius=Math.max(this._point.x-i.x,1)},getBounds:function(){var t=this._getLngRadius(),e=360*(this._mRadius/40075017),i=this._latlng;return new o.LatLngBounds([i.lat-e,i.lng-t],[i.lat+e,i.lng+t])},getLatLng:function(){return this._latlng},getPathString:function(){var t=this._point,e=this._radius;return this._checkIfEmpty()?"":o.Browser.svg?"M"+t.x+","+(t.y-e)+"A"+e+","+e+",0,1,1,"+(t.x-.1)+","+(t.y-e)+" z":(t._round(),e=Math.round(e),"AL "+t.x+","+t.y+" "+e+","+e+" 0,"+23592600)},getRadius:function(){return this._mRadius},_getLatRadius:function(){return 360*(this._mRadius/40075017)},_getLngRadius:function(){return this._getLatRadius()/Math.cos(o.LatLng.DEG_TO_RAD*this._latlng.lat)},_checkIfEmpty:function(){if(!this._map)return!1;var t=this._map._pathViewport,e=this._radius,i=this._point;return i.x-e>t.max.x||i.y-e>t.max.y||i.x+e<t.min.x||i.y+e<t.min.y}}),o.circle=function(t,e,i){return new o.Circle(t,e,i)},o.CircleMarker=o.Circle.extend({options:{radius:10,weight:2},initialize:function(t,e){o.Circle.prototype.initialize.call(this,t,null,e),this._radius=this.options.radius},projectLatlngs:function(){this._point=this._map.latLngToLayerPoint(this._latlng)},_updateStyle:function(){o.Circle.prototype._updateStyle.call(this),this.setRadius(this.options.radius)},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()}}),o.circleMarker=function(t,e){return new o.CircleMarker(t,e)},o.Polyline.include(o.Path.CANVAS?{_containsPoint:function(t,e){var i,n,s,a,r,h,l,u=this.options.weight/2;for(o.Browser.touch&&(u+=10),i=0,a=this._parts.length;a>i;i++)for(l=this._parts[i],n=0,r=l.length,s=r-1;r>n;s=n++)if((e||0!==n)&&(h=o.LineUtil.pointToSegmentDistance(t,l[s],l[n]),u>=h))return!0;return!1}}:{}),o.Polygon.include(o.Path.CANVAS?{_containsPoint:function(t){var e,i,n,s,a,r,h,l,u=!1;if(o.Polyline.prototype._containsPoint.call(this,t,!0))return!0;for(s=0,h=this._parts.length;h>s;s++)for(e=this._parts[s],a=0,l=e.length,r=l-1;l>a;r=a++)i=e[a],n=e[r],i.y>t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(u=!u);return u}}:{}),o.Circle.include(o.Path.CANVAS?{_drawPath:function(){var t=this._point;this._ctx.beginPath(),this._ctx.arc(t.x,t.y,this._radius,0,2*Math.PI,!1)},_containsPoint:function(t){var e=this._point,i=this.options.stroke?this.options.weight/2:0;return t.distanceTo(e)<=this._radius+i}}:{}),o.CircleMarker.include(o.Path.CANVAS?{_updateStyle:function(){o.Path.prototype._updateStyle.call(this)}}:{}),o.GeoJSON=o.FeatureGroup.extend({initialize:function(t,e){o.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n=o.Util.isArray(t)?t:t.features;if(n){for(e=0,i=n.length;i>e;e++)(n[e].geometries||n[e].geometry||n[e].features)&&this.addData(n[e]);return this}var s=this.options;if(!s.filter||s.filter(t)){var a=o.GeoJSON.geometryToLayer(t,s.pointToLayer,s.coordsToLatLng);return a.feature=o.GeoJSON.asFeature(t),a.defaultOptions=a.options,this.resetStyle(a),s.onEachFeature&&s.onEachFeature(t,a),this.addLayer(a)}},resetStyle:function(t){var e=this.options.style;e&&(o.Util.extend(t.options,t.defaultOptions),this._setLayerStyle(t,e))},setStyle:function(t){this.eachLayer(function(e){this._setLayerStyle(e,t)},this)},_setLayerStyle:function(t,e){"function"==typeof e&&(e=e(t.feature)),t.setStyle&&t.setStyle(e)}}),o.extend(o.GeoJSON,{geometryToLayer:function(t,e,i){var n,s,a,r,h,l="Feature"===t.type?t.geometry:t,u=l.coordinates,c=[];switch(i=i||this.coordsToLatLng,l.type){case"Point":return n=i(u),e?e(t,n):new o.Marker(n);case"MultiPoint":for(a=0,r=u.length;r>a;a++)n=i(u[a]),h=e?e(t,n):new o.Marker(n),c.push(h);return new o.FeatureGroup(c);case"LineString":return s=this.coordsToLatLngs(u,0,i),new o.Polyline(s);case"Polygon":return s=this.coordsToLatLngs(u,1,i),new o.Polygon(s);case"MultiLineString":return s=this.coordsToLatLngs(u,1,i),new o.MultiPolyline(s);case"MultiPolygon":return s=this.coordsToLatLngs(u,2,i),new o.MultiPolygon(s);case"GeometryCollection":for(a=0,r=l.geometries.length;r>a;a++)h=this.geometryToLayer({geometry:l.geometries[a],type:"Feature",properties:t.properties},e,i),c.push(h);return new o.FeatureGroup(c);default:throw new Error("Invalid GeoJSON object.")}},coordsToLatLng:function(t){return new o.LatLng(t[1],t[0])},coordsToLatLngs:function(t,e,i){var n,o,s,a=[];for(o=0,s=t.length;s>o;o++)n=e?this.coordsToLatLngs(t[o],e-1,i):(i||this.coordsToLatLng)(t[o]),a.push(n);return a},latLngToCoords:function(t){return[t.lng,t.lat]},latLngsToCoords:function(t){for(var e=[],i=0,n=t.length;n>i;i++)e.push(o.GeoJSON.latLngToCoords(t[i]));return e},getFeature:function(t,e){return t.feature?o.extend({},t.feature,{geometry:e}):o.GeoJSON.asFeature(e)},asFeature:function(t){return"Feature"===t.type?t:{type:"Feature",properties:{},geometry:t}}});var a={toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"Point",coordinates:o.GeoJSON.latLngToCoords(this.getLatLng())})}};o.Marker.include(a),o.Circle.include(a),o.CircleMarker.include(a),o.Polyline.include({toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"LineString",coordinates:o.GeoJSON.latLngsToCoords(this.getLatLngs())})}}),o.Polygon.include({toGeoJSON:function(){var t,e,i,n=[o.GeoJSON.latLngsToCoords(this.getLatLngs())];if(n[0].push(n[0][0]),this._holes)for(t=0,e=this._holes.length;e>t;t++)i=o.GeoJSON.latLngsToCoords(this._holes[t]),i.push(i[0]),n.push(i);return o.GeoJSON.getFeature(this,{type:"Polygon",coordinates:n})}}),function(){function t(t,e){t.include({toGeoJSON:function(){var t=[];return this.eachLayer(function(e){t.push(e.toGeoJSON().geometry.coordinates)}),o.GeoJSON.getFeature(this,{type:e,coordinates:t})}})}t(o.MultiPolyline,"MultiLineString"),t(o.MultiPolygon,"MultiPolygon")}(),o.LayerGroup.include({toGeoJSON:function(){var t=[];return this.eachLayer(function(e){e.toGeoJSON&&t.push(o.GeoJSON.asFeature(e.toGeoJSON()))}),{type:"FeatureCollection",features:t}}}),o.geoJson=function(t,e){return new o.GeoJSON(t,e)},o.DomEvent={WHEEL:"onwheel"in e?"wheel":"onmousewheel"in e?"mousewheel":"MozMousePixelScroll",addListener:function(t,e,i,n){var s,a,r,h=o.stamp(i),l="_leaflet_"+e+h;return t[l]?this:(s=function(e){return i.call(n||t,e||o.DomEvent._getEvent())},o.Browser.msTouch&&0===e.indexOf("touch")?this.addMsTouchListener(t,e,s,h):(o.Browser.touch&&"dblclick"===e&&this.addDoubleTapListener&&this.addDoubleTapListener(t,s,h),("wheel"===e||"mousewheel"===e)&&(e=o.DomEvent.WHEEL),"addEventListener"in t?"mouseenter"===e||"mouseleave"===e?(a=s,r="mouseenter"===e?"mouseover":"mouseout",s=function(e){return o.DomEvent._checkMouse(t,e)?a(e):void 0},t.addEventListener(r,s,!1)):"click"===e&&o.Browser.android?(a=s,s=function(t){return o.DomEvent._filterClick(t,a)},t.addEventListener(e,s,!1)):t.addEventListener(e,s,!1):"attachEvent"in t&&t.attachEvent("on"+e,s),t[l]=s,this))},removeListener:function(t,e,i){var n=o.stamp(i),s="_leaflet_"+e+n,a=t[s];return a?(("wheel"===e||"mousewheel"===e)&&(e=o.DomEvent.WHEEL),o.Browser.msTouch&&0===e.indexOf("touch")?this.removeMsTouchListener(t,e,n):o.Browser.touch&&"dblclick"===e&&this.removeDoubleTapListener?this.removeDoubleTapListener(t,n):"removeEventListener"in t?"mouseenter"===e||"mouseleave"===e?t.removeEventListener("mouseenter"===e?"mouseover":"mouseout",a,!1):t.removeEventListener(e,a,!1):"detachEvent"in t&&t.detachEvent("on"+e,a),t[s]=null,this):this},stopPropagation:function(t){return t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,this},disableClickPropagation:function(t){for(var e=o.DomEvent.stopPropagation,i=o.Draggable.START.length-1;i>=0;i--)o.DomEvent.addListener(t,o.Draggable.START[i],e);return o.DomEvent.addListener(t,"click",o.DomEvent._fakeStop).addListener(t,"dblclick",e)},preventDefault:function(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this},stop:function(t){return o.DomEvent.preventDefault(t).stopPropagation(t)},getMousePosition:function(t,i){var n=e.body,s=e.documentElement,a=t.pageX?t.pageX:t.clientX+n.scrollLeft+s.scrollLeft,r=t.pageY?t.pageY:t.clientY+n.scrollTop+s.scrollTop,h=new o.Point(a,r);return i?h._subtract(o.DomUtil.getViewportOffset(i)):h},getWheelDelta:function(t){var e=0;return"wheel"===t.type?e=-t.deltaY/(t.deltaMode?1:120):"mousewheel"===t.type?e=t.wheelDelta/120:"MozMousePixelScroll"===t.type&&(e=-t.detail),e},_fakeStop:function(t){t._leaflet_stop=!0},_checkMouse:function(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(n){return!1}return i!==t},_getEvent:function(){var e=t.event;if(!e)for(var i=arguments.callee.caller;i&&(e=i.arguments[0],!e||t.Event!==e.constructor);)i=i.caller;return e},_filterClick:function(t,e){var i=t.timeStamp||t.originalEvent.timeStamp,n=o.DomEvent._lastClick&&i-o.DomEvent._lastClick;return n&&n>100&&1e3>n||t.target._simulatedClick&&!t._simulated?(o.DomEvent.stop(t),void 0):(o.DomEvent._lastClick=i,e(t))}},o.DomEvent.on=o.DomEvent.addListener,o.DomEvent.off=o.DomEvent.removeListener,o.Draggable=o.Class.extend({includes:o.Mixin.Events,statics:{START:o.Browser.touch?["touchstart","mousedown"]:["mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",MSPointerDown:"touchmove"}},initialize:function(t,e){this._element=t,this._dragStartTarget=e||t},enable:function(){if(!this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.on(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!0}},disable:function(){if(this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.off(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!1,this._moved=!1}},_onDown:function(t){if(!t.shiftKey&&(1===t.which||1===t.button||t.touches)&&(o.DomEvent.stopPropagation(t),!o.Draggable._disabled)){o.DomUtil.disableImageDrag();var i=t.touches?t.touches[0]:t,n=i.target;o.Browser.touch&&"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._moved=!1,this._moving||(this._startPoint=new o.Point(i.clientX,i.clientY),this._startPos=this._newPos=o.DomUtil.getPosition(this._element),o.DomEvent.on(e,o.Draggable.MOVE[t.type],this._onMove,this).on(e,o.Draggable.END[t.type],this._onUp,this))}},_onMove:function(t){if(!(t.touches&&t.touches.length>1)){var i=t.touches&&1===t.touches.length?t.touches[0]:t,n=new o.Point(i.clientX,i.clientY),s=n.subtract(this._startPoint);(s.x||s.y)&&(o.DomEvent.preventDefault(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=o.DomUtil.getPosition(this._element).subtract(s),o.Browser.touch||(o.DomUtil.disableTextSelection(),o.DomUtil.addClass(e.body,"leaflet-dragging"))),this._newPos=this._startPos.add(s),this._moving=!0,o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updatePosition,this,!0,this._dragStartTarget))}},_updatePosition:function(){this.fire("predrag"),o.DomUtil.setPosition(this._element,this._newPos),this.fire("drag")},_onUp:function(){o.Browser.touch||(o.DomUtil.enableTextSelection(),o.DomUtil.removeClass(e.body,"leaflet-dragging"));for(var t in o.Draggable.MOVE)o.DomEvent.off(e,o.Draggable.MOVE[t],this._onMove).off(e,o.Draggable.END[t],this._onUp);o.DomUtil.enableImageDrag(),this._moved&&(o.Util.cancelAnimFrame(this._animRequest),this.fire("dragend")),this._moving=!1}}),o.Handler=o.Class.extend({initialize:function(t){this._map=t},enable:function(){this._enabled||(this._enabled=!0,this.addHooks())},disable:function(){this._enabled&&(this._enabled=!1,this.removeHooks())},enabled:function(){return!!this._enabled}}),o.Map.mergeOptions({dragging:!0,inertia:!o.Browser.android23,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,inertiaThreshold:o.Browser.touch?32:18,easeLinearity:.25,worldCopyJump:!1}),o.Map.Drag=o.Handler.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new o.Draggable(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),t.on("viewreset",this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var t=this._map;t._panAnim&&t._panAnim.stop(),t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var t=this._lastTime=+new Date,e=this._lastPos=this._draggable._newPos;this._positions.push(e),this._times.push(t),t-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var t=this._map.getSize()._divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.project([0,180]).x},_onPreDrag:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,a=Math.abs(o+i)<Math.abs(s+i)?o:s;this._draggable._newPos.x=a},_onDragEnd:function(){var t=this._map,e=t.options,i=+new Date-this._lastTime,n=!e.inertia||i>e.inertiaThreshold||!this._positions[0];if(t.fire("dragend"),n)t.fire("moveend");else{var s=this._lastPos.subtract(this._positions[0]),a=(this._lastTime+i-this._times[0])/1e3,r=e.easeLinearity,h=s.multiplyBy(r/a),l=h.distanceTo([0,0]),u=Math.min(e.inertiaMaxSpeed,l),c=h.multiplyBy(u/l),d=u/(e.inertiaDeceleration*r),p=c.multiplyBy(-d/2).round();p.x&&p.y?o.Util.requestAnimFrame(function(){t.panBy(p,{duration:d,easeLinearity:r,noMoveStart:!0})}):t.fire("moveend")}}}),o.Map.addInitHook("addHandler","dragging",o.Map.Drag),o.Map.mergeOptions({doubleClickZoom:!0}),o.Map.DoubleClickZoom=o.Handler.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick)},_onDoubleClick:function(t){this.setZoomAround(t.containerPoint,this._zoom+1)}}),o.Map.addInitHook("addHandler","doubleClickZoom",o.Map.DoubleClickZoom),o.Map.mergeOptions({scrollWheelZoom:!0}),o.Map.ScrollWheelZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"wheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){o.DomEvent.off(this._map._container,"wheel",this._onWheelScroll)},_onWheelScroll:function(t){var e=o.DomEvent.getWheelDelta(t);this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var i=Math.max(40-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(o.bind(this._performZoom,this),i),o.DomEvent.preventDefault(t),o.DomEvent.stopPropagation(t)},_performZoom:function(){var t=this._map,e=this._delta,i=t.getZoom();e=e>0?Math.ceil(e):Math.floor(e),e=Math.max(Math.min(e,4),-4),e=t._limitZoom(i+e)-i,this._delta=0,this._startTime=null,e&&t.setZoomAround(this._lastMousePos,i+e)}}),o.Map.addInitHook("addHandler","scrollWheelZoom",o.Map.ScrollWheelZoom),o.extend(o.DomEvent,{_touchstart:o.Browser.msTouch?"MSPointerDown":"touchstart",_touchend:o.Browser.msTouch?"MSPointerUp":"touchend",addDoubleTapListener:function(t,i,n){function s(t){var e;if(o.Browser.msTouch?(_.push(t.pointerId),e=_.length):e=t.touches.length,!(e>1)){var i=Date.now(),n=i-(r||i);h=t.touches?t.touches[0]:t,l=n>0&&u>=n,r=i}}function a(t){if(o.Browser.msTouch){var e=_.indexOf(t.pointerId);if(-1===e)return;_.splice(e,1)}if(l){if(o.Browser.msTouch){var n,s={};for(var a in h)n=h[a],s[a]="function"==typeof n?n.bind(h):n;h=s}h.type="dblclick",i(h),r=null}}var r,h,l=!1,u=250,c="_leaflet_",d=this._touchstart,p=this._touchend,_=[];t[c+d+n]=s,t[c+p+n]=a;var m=o.Browser.msTouch?e.documentElement:t;return t.addEventListener(d,s,!1),m.addEventListener(p,a,!1),o.Browser.msTouch&&m.addEventListener("MSPointerCancel",a,!1),this},removeDoubleTapListener:function(t,i){var n="_leaflet_";return t.removeEventListener(this._touchstart,t[n+this._touchstart+i],!1),(o.Browser.msTouch?e.documentElement:t).removeEventListener(this._touchend,t[n+this._touchend+i],!1),o.Browser.msTouch&&e.documentElement.removeEventListener("MSPointerCancel",t[n+this._touchend+i],!1),this}}),o.extend(o.DomEvent,{_msTouches:[],_msDocumentListener:!1,addMsTouchListener:function(t,e,i,n){switch(e){case"touchstart":return this.addMsTouchListenerStart(t,e,i,n);case"touchend":return this.addMsTouchListenerEnd(t,e,i,n);case"touchmove":return this.addMsTouchListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addMsTouchListenerStart:function(t,i,n,o){var s="_leaflet_",a=this._msTouches,r=function(t){for(var e=!1,i=0;i<a.length;i++)if(a[i].pointerId===t.pointerId){e=!0;break}e||a.push(t),t.touches=a.slice(),t.changedTouches=[t],n(t)};if(t[s+"touchstart"+o]=r,t.addEventListener("MSPointerDown",r,!1),!this._msDocumentListener){var h=function(t){for(var e=0;e<a.length;e++)if(a[e].pointerId===t.pointerId){a.splice(e,1);break}};e.documentElement.addEventListener("MSPointerUp",h,!1),e.documentElement.addEventListener("MSPointerCancel",h,!1),this._msDocumentListener=!0}return this},addMsTouchListenerMove:function(t,e,i,n){function o(t){if(t.pointerType!==t.MSPOINTER_TYPE_MOUSE||0!==t.buttons){for(var e=0;e<a.length;e++)if(a[e].pointerId===t.pointerId){a[e]=t;break}t.touches=a.slice(),t.changedTouches=[t],i(t)}}var s="_leaflet_",a=this._msTouches;return t[s+"touchmove"+n]=o,t.addEventListener("MSPointerMove",o,!1),this},addMsTouchListenerEnd:function(t,e,i,n){var o="_leaflet_",s=this._msTouches,a=function(t){for(var e=0;e<s.length;e++)if(s[e].pointerId===t.pointerId){s.splice(e,1);break}t.touches=s.slice(),t.changedTouches=[t],i(t)};return t[o+"touchend"+n]=a,t.addEventListener("MSPointerUp",a,!1),t.addEventListener("MSPointerCancel",a,!1),this},removeMsTouchListener:function(t,e,i){var n="_leaflet_",o=t[n+e+i];switch(e){case"touchstart":t.removeEventListener("MSPointerDown",o,!1);break;case"touchmove":t.removeEventListener("MSPointerMove",o,!1);break;case"touchend":t.removeEventListener("MSPointerUp",o,!1),t.removeEventListener("MSPointerCancel",o,!1)}return this}}),o.Map.mergeOptions({touchZoom:o.Browser.touch&&!o.Browser.android23}),o.Map.TouchZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var n=i.mouseEventToLayerPoint(t.touches[0]),s=i.mouseEventToLayerPoint(t.touches[1]),a=i._getCenterLayerPoint();this._startCenter=n.add(s)._divideBy(2),this._startDist=n.distanceTo(s),this._moved=!1,this._zooming=!0,this._centerOffset=a.subtract(this._startCenter),i._panAnim&&i._panAnim.stop(),o.DomEvent.on(e,"touchmove",this._onTouchMove,this).on(e,"touchend",this._onTouchEnd,this),o.DomEvent.preventDefault(t)}},_onTouchMove:function(t){var e=this._map;if(t.touches&&2===t.touches.length&&this._zooming){var i=e.mouseEventToLayerPoint(t.touches[0]),n=e.mouseEventToLayerPoint(t.touches[1]);this._scale=i.distanceTo(n)/this._startDist,this._delta=i._add(n)._divideBy(2)._subtract(this._startCenter),1!==this._scale&&(this._moved||(o.DomUtil.addClass(e._mapPane,"leaflet-touching"),e.fire("movestart").fire("zoomstart"),this._moved=!0),o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),o.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e),n=t.getScaleZoom(this._scale);t._animateZoom(i,n,this._startCenter,this._scale,this._delta)},_onTouchEnd:function(){if(!this._moved||!this._zooming)return this._zooming=!1,void 0;var t=this._map;this._zooming=!1,o.DomUtil.removeClass(t._mapPane,"leaflet-touching"),o.Util.cancelAnimFrame(this._animRequest),o.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),n=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r),l=t.getZoomScale(h)/this._scale;t._animateZoom(n,h,i,l)},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),o.Map.addInitHook("addHandler","touchZoom",o.Map.TouchZoom),o.Map.mergeOptions({tap:!0,tapTolerance:15}),o.Map.Tap=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(o.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,clearTimeout(this._holdTimeout),void 0;var i=t.touches[0],n=i.target;this._startPos=this._newPos=new o.Point(i.clientX,i.clientY),"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._holdTimeout=setTimeout(o.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),o.DomEvent.on(e,"touchmove",this._onMove,this).on(e,"touchend",this._onUp,this)
8 }},_onUp:function(t){if(clearTimeout(this._holdTimeout),o.DomEvent.off(e,"touchmove",this._onMove,this).off(e,"touchend",this._onUp,this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],n=i.target;"a"===n.tagName.toLowerCase()&&o.DomUtil.removeClass(n,"leaflet-active"),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new o.Point(e.clientX,e.clientY)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),o.Browser.touch&&!o.Browser.msTouch&&o.Map.addInitHook("addHandler","tap",o.Map.Tap),o.Map.mergeOptions({boxZoom:!0}),o.Map.BoxZoom=o.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane},addHooks:function(){o.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){o.DomEvent.off(this._container,"mousedown",this._onMouseDown)},_onMouseDown:function(t){return!t.shiftKey||1!==t.which&&1!==t.button?!1:(o.DomUtil.disableTextSelection(),o.DomUtil.disableImageDrag(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),this._box=o.DomUtil.create("div","leaflet-zoom-box",this._pane),o.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",o.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).on(e,"keydown",this._onKeyDown,this),this._map.fire("boxzoomstart"),void 0)},_onMouseMove:function(t){var e=this._startLayerPoint,i=this._box,n=this._map.mouseEventToLayerPoint(t),s=n.subtract(e),a=new o.Point(Math.min(n.x,e.x),Math.min(n.y,e.y));o.DomUtil.setPosition(i,a),i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_finish:function(){this._pane.removeChild(this._box),this._container.style.cursor="",o.DomUtil.enableTextSelection(),o.DomUtil.enableImageDrag(),o.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp).off(e,"keydown",this._onKeyDown)},_onMouseUp:function(t){this._finish();var e=this._map,i=e.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(i)){var n=new o.LatLngBounds(e.layerPointToLatLng(this._startLayerPoint),e.layerPointToLatLng(i));e.fitBounds(n),e.fire("boxzoomend",{boxZoomBounds:n})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),o.Map.addInitHook("addHandler","boxZoom",o.Map.BoxZoom),o.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),o.Map.Keyboard=o.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),o.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;o.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollTop||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){o.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){o.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(e in this._panKeys){if(i._panAnim&&i._panAnim._inProgress)return;i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds)}else{if(!(e in this._zoomKeys))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}o.DomEvent.stop(t)}}),o.Map.addInitHook("addHandler","keyboard",o.Map.Keyboard),o.Handler.MarkerDrag=o.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new o.Draggable(t,t)),this._draggable.on("dragstart",this._onDragStart,this).on("drag",this._onDrag,this).on("dragend",this._onDragEnd,this),this._draggable.enable()},removeHooks:function(){this._draggable.off("dragstart",this._onDragStart,this).off("drag",this._onDrag,this).off("dragend",this._onDragEnd,this),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=o.DomUtil.getPosition(t._icon),n=t._map.layerPointToLatLng(i);e&&o.DomUtil.setPosition(e,i),t._latlng=n,t.fire("move",{latlng:n}).fire("drag")},_onDragEnd:function(){this._marker.fire("moveend").fire("dragend")}}),o.Control=o.Class.extend({options:{position:"topright"},initialize:function(t){o.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),n=t._controlCorners[i];return o.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?n.insertBefore(e,n.firstChild):n.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this}}),o.control=function(t){return new o.Control(t)},o.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=o.DomUtil.create("div",a,n)}var e=this._controlCorners={},i="leaflet-",n=this._controlContainer=o.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){this._container.removeChild(this._controlContainer)}}),o.Control.Zoom=o.Control.extend({options:{position:"topleft"},onAdd:function(t){var e="leaflet-control-zoom",i=o.DomUtil.create("div",e+" leaflet-bar");return this._map=t,this._zoomInButton=this._createButton("+","Zoom in",e+"-in",i,this._zoomIn,this),this._zoomOutButton=this._createButton("-","Zoom out",e+"-out",i,this._zoomOut,this),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,n,s,a){var r=o.DomUtil.create("a",i,n);r.innerHTML=t,r.href="#",r.title=e;var h=o.DomEvent.stopPropagation;return o.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",o.DomEvent.preventDefault).on(r,"click",s,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";o.DomUtil.removeClass(this._zoomInButton,e),o.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&o.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&o.DomUtil.addClass(this._zoomInButton,e)}}),o.Map.mergeOptions({zoomControl:!0}),o.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new o.Control.Zoom,this.addControl(this.zoomControl))}),o.control.zoom=function(t){return new o.Control.Zoom(t)},o.Control.Attribution=o.Control.extend({options:{position:"bottomright",prefix:'<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'},initialize:function(t){o.setOptions(this,t),this._attributions={}},onAdd:function(t){return this._container=o.DomUtil.create("div","leaflet-control-attribution"),o.DomEvent.disableClickPropagation(this._container),t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):void 0},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):void 0},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),o.Map.mergeOptions({attributionControl:!0}),o.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new o.Control.Attribution).addTo(this))}),o.control.attribution=function(t){return new o.Control.Attribution(t)},o.Control.Scale=o.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=o.DomUtil.create("div",e),n=this.options;return this._addScales(n,e,i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=o.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=o.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),o.control.scale=function(t){return new o.Control.Scale(t)},o.Control.Layers=o.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){o.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in e)this._addLayer(e[n],n,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange).off("layerremove",this._onLayerChange)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=o.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=o.DomUtil.create("div",t);e.setAttribute("aria-haspopup",!0),o.Browser.touch?o.DomEvent.on(e,"click",o.DomEvent.stopPropagation):(o.DomEvent.disableClickPropagation(e),o.DomEvent.on(e,"wheel",o.DomEvent.stopPropagation));var i=this._form=o.DomUtil.create("form",t+"-list");if(this.options.collapsed){o.Browser.android||o.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var n=this._layersLink=o.DomUtil.create("a",t+"-toggle",e);n.href="#",n.title="Layers",o.Browser.touch?o.DomEvent.on(n,"click",o.DomEvent.stop).on(n,"click",this._expand,this):o.DomEvent.on(n,"focus",this._expand,this),this._map.on("click",this._collapse,this)}else this._expand();this._baseLayersList=o.DomUtil.create("div",t+"-base",i),this._separator=o.DomUtil.create("div",t+"-separator",i),this._overlaysList=o.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var n=o.stamp(t);this._layers[n]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t,e,i=!1,n=!1;for(t in this._layers)e=this._layers[t],this._addItem(e),n=n||e.overlay,i=i||!e.overlay;this._separator.style.display=n&&i?"":"none"}},_onLayerChange:function(t){var e=this._layers[o.stamp(t.layer)];if(e){this._handlingClick||this._update();var i=e.overlay?"layeradd"===t.type?"overlayadd":"overlayremove":"layeradd"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)}},_createRadioElement:function(t,i){var n='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"';i&&(n+=' checked="checked"'),n+="/>";var o=e.createElement("div");return o.innerHTML=n,o.firstChild},_addItem:function(t){var i,n=e.createElement("label"),s=this._map.hasLayer(t.layer);t.overlay?(i=e.createElement("input"),i.type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=s):i=this._createRadioElement("leaflet-base-layers",s),i.layerId=o.stamp(t.layer),o.DomEvent.on(i,"click",this._onInputClick,this);var a=e.createElement("span");a.innerHTML=" "+t.name,n.appendChild(i),n.appendChild(a);var r=t.overlay?this._overlaysList:this._baseLayersList;return r.appendChild(n),n},_onInputClick:function(){var t,e,i,n=this._form.getElementsByTagName("input"),o=n.length;for(this._handlingClick=!0,t=0;o>t;t++)e=n[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?this._map.addLayer(i.layer):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);this._handlingClick=!1},_expand:function(){o.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),o.control.layers=function(t,e,i){return new o.Control.Layers(t,e,i)},o.PosAnimation=o.Class.extend({includes:o.Mixin.Events,run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._newPos=e,this.fire("start"),t.style[o.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(n||.5)+",1)",o.DomEvent.on(t,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),o.DomUtil.setPosition(t,e),o.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(o.bind(this._onStep,this),50)},stop:function(){this._inProgress&&(o.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),o.Util.falseFn(this._el.offsetWidth))},_onStep:function(){this._el._leaflet_pos=this._getPos(),this.fire("step")},_transformRe:/([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,_getPos:function(){var e,i,n,s=this._el,a=t.getComputedStyle(s);return o.Browser.any3d?(n=a[o.DomUtil.TRANSFORM].match(this._transformRe),e=n?parseFloat(n[1]):0,i=n?parseFloat(n[2]):0):(e=parseFloat(a.left),i=parseFloat(a.top)),new o.Point(e,i,!0)},_onTransitionEnd:function(){o.DomEvent.off(this._el,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[o.DomUtil.TRANSITION]="",this._el._leaflet_pos=this._newPos,clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),o.Map.include({setView:function(t,e,n){if(e=this._limitZoom(e),t=o.latLng(t),n=n||{},this._panAnim&&this._panAnim.stop(),this._loaded&&!n.reset&&n!==!0){n.animate!==i&&(n.zoom=o.extend({animate:n.animate},n.zoom),n.pan=o.extend({animate:n.animate},n.pan));var s=this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan);if(s)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e){if(t=o.point(t).round(),e=e||{},!t.x&&!t.y)return this;if(this._panAnim||(this._panAnim=new o.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),e.noMoveStart||this.fire("movestart"),e.animate!==!1){o.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var i=this._getMapPanePos().subtract(t);this._panAnim.run(this._mapPane,i,e.duration||.25,e.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){o.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return(e&&e.animate)===!0||this.getSize().contains(i)?(this.panBy(i,e),!0):!1}}),o.PosAnimation=o.DomUtil.TRANSITION?o.PosAnimation:o.PosAnimation.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=o.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=o.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));o.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){o.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),o.Map.mergeOptions({zoomAnimation:!0,zoomAnimationThreshold:4}),o.DomUtil.TRANSITION&&o.Map.addInitHook(function(){this._zoomAnimated=this.options.zoomAnimation&&o.DomUtil.TRANSITION&&o.Browser.any3d&&!o.Browser.android23&&!o.Browser.mobileOpera,this._zoomAnimated&&o.DomEvent.on(this._mapPane,o.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),o.Map.include(o.DomUtil.TRANSITION?{_catchTransitionEnd:function(){this._animatingZoom&&this._onZoomTransitionEnd()},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/n),s=this._getCenterLayerPoint()._add(o);return i.animate===!0||this.getSize().contains(o)?(this.fire("movestart").fire("zoomstart"),this._animateZoom(t,e,s,n,null,!0),!0):!1},_animateZoom:function(t,e,i,n,s,a){this._animatingZoom=!0,o.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this._animateToCenter=t,this._animateToZoom=e,o.Draggable&&(o.Draggable._disabled=!0),this.fire("zoomanim",{center:t,zoom:e,origin:i,scale:n,delta:s,backwards:a})},_onZoomTransitionEnd:function(){this._animatingZoom=!1,o.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),o.Draggable&&(o.Draggable._disabled=!1)}}:{}),o.TileLayer.include({_animateZoom:function(t){var e=!1;this._animating||(this._animating=!0,e=!0),e&&this._prepareBgBuffer();var i=this._bgBuffer;e&&(clearTimeout(this._clearBgBufferTimer),o.Util.falseFn(i.offsetWidth));var n=o.DomUtil.TRANSFORM,s=t.delta?o.DomUtil.getTranslateString(t.delta):i.style[n],a=o.DomUtil.getScaleString(t.scale,t.origin);i.style[n]=t.backwards?a+" "+s:s+" "+a},_endZoomAnim:function(){var t=this._tileContainer,e=this._bgBuffer;t.style.visibility="",t.style.zIndex=2,e.style.zIndex=1,o.Util.falseFn(e.offsetWidth),this._animating=!1},_clearBgBuffer:function(){var t=this._map;!t||t._animatingZoom||t.touchZoom._zooming||(this._bgBuffer.innerHTML="",this._bgBuffer.style[o.DomUtil.TRANSFORM]="")},_prepareBgBuffer:function(){var t=this._tileContainer,e=this._bgBuffer,i=this._getLoadedTilesPercentage(e),n=this._getLoadedTilesPercentage(t);return e&&i>.5&&.5>n?(t.style.visibility="hidden",this._stopLoadingImages(t),void 0):(e.style.visibility="hidden",e.style[o.DomUtil.TRANSFORM]="",this._tileContainer=e,e=this._bgBuffer=t,this._stopLoadingImages(e),void 0)},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,n,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)n=s[e],n.complete||(n.onload=o.Util.falseFn,n.onerror=o.Util.falseFn,n.src=o.Util.emptyImageUrl,n.parentNode.removeChild(n))}}),o.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locateOptions=o.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=o.bind(this._handleGeolocationResponse,this),i=o.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,i=t.coords.longitude,n=new o.LatLng(e,i),s=180*t.coords.accuracy/40075017,a=s/Math.cos(o.LatLng.DEG_TO_RAD*e),r=o.latLngBounds([e-s,i-a],[e+s,i+a]),h=this._locateOptions;if(h.setView){var l=Math.min(this.getBoundsZoom(r),h.maxZoom);this.setView(n,l)}var u={latlng:n,bounds:r};for(var c in t.coords)"number"==typeof t.coords[c]&&(u[c]=t.coords[c]);this.fire("locationfound",u)}})}(window,document);
0 {
1 "name": "Leaflet",
2 "version": "0.6.0",
3 "description": "JavaScript library for mobile-friendly interactive maps",
4 "devDependencies": {
5 "jshint": "~2.1.3",
6 "mocha": "~1.10.0",
7 "karma": "~0.8.5",
8 "uglify-js": "~2.3.6",
9 "jake": "~0.5.16"
10 },
11 "main": "dist/leaflet.js",
12 "scripts": {
13 "test": "jake test",
14 "prepublish": "jake"
15 },
16 "repository": {
17 "type": "git",
18 "url": "git://github.com/Leaflet/Leaflet.git"
19 },
20 "keywords": ["gis", "map"]
21 }
0 // put after Leaflet files as imagePath can't be detected in a PhantomJS env
1 L.Icon.Default.imagePath = "../dist/images";
0 // set up before Leaflet files to test L#noConflict later
1 L = 'test';
2
0
1 (function (global, module) {
2
3 if ('undefined' == typeof module) {
4 var module = { exports: {} }
5 , exports = module.exports
6 }
7
8 /**
9 * Exports.
10 */
11
12 module.exports = expect;
13 expect.Assertion = Assertion;
14
15 /**
16 * Exports version.
17 */
18
19 expect.version = '0.1.2';
20
21 /**
22 * Possible assertion flags.
23 */
24
25 var flags = {
26 not: ['to', 'be', 'have', 'include', 'only']
27 , to: ['be', 'have', 'include', 'only', 'not']
28 , only: ['have']
29 , have: ['own']
30 , be: ['an']
31 };
32
33 function expect (obj) {
34 return new Assertion(obj);
35 }
36
37 /**
38 * Constructor
39 *
40 * @api private
41 */
42
43 function Assertion (obj, flag, parent) {
44 this.obj = obj;
45 this.flags = {};
46
47 if (undefined != parent) {
48 this.flags[flag] = true;
49
50 for (var i in parent.flags) {
51 if (parent.flags.hasOwnProperty(i)) {
52 this.flags[i] = true;
53 }
54 }
55 }
56
57 var $flags = flag ? flags[flag] : keys(flags)
58 , self = this
59
60 if ($flags) {
61 for (var i = 0, l = $flags.length; i < l; i++) {
62 // avoid recursion
63 if (this.flags[$flags[i]]) continue;
64
65 var name = $flags[i]
66 , assertion = new Assertion(this.obj, name, this)
67
68 if ('function' == typeof Assertion.prototype[name]) {
69 // clone the function, make sure we dont touch the prot reference
70 var old = this[name];
71 this[name] = function () {
72 return old.apply(self, arguments);
73 }
74
75 for (var fn in Assertion.prototype) {
76 if (Assertion.prototype.hasOwnProperty(fn) && fn != name) {
77 this[name][fn] = bind(assertion[fn], assertion);
78 }
79 }
80 } else {
81 this[name] = assertion;
82 }
83 }
84 }
85 };
86
87 /**
88 * Performs an assertion
89 *
90 * @api private
91 */
92
93 Assertion.prototype.assert = function (truth, msg, error) {
94 var msg = this.flags.not ? error : msg
95 , ok = this.flags.not ? !truth : truth;
96
97 if (!ok) {
98 throw new Error(msg.call(this));
99 }
100
101 this.and = new Assertion(this.obj);
102 };
103
104 /**
105 * Check if the value is truthy
106 *
107 * @api public
108 */
109
110 Assertion.prototype.ok = function () {
111 this.assert(
112 !!this.obj
113 , function(){ return 'expected ' + i(this.obj) + ' to be truthy' }
114 , function(){ return 'expected ' + i(this.obj) + ' to be falsy' });
115 };
116
117 /**
118 * Assert that the function throws.
119 *
120 * @param {Function|RegExp} callback, or regexp to match error string against
121 * @api public
122 */
123
124 Assertion.prototype.throwError =
125 Assertion.prototype.throwException = function (fn) {
126 expect(this.obj).to.be.a('function');
127
128 var thrown = false
129 , not = this.flags.not
130
131 try {
132 this.obj();
133 } catch (e) {
134 if ('function' == typeof fn) {
135 fn(e);
136 } else if ('object' == typeof fn) {
137 var subject = 'string' == typeof e ? e : e.message;
138 if (not) {
139 expect(subject).to.not.match(fn);
140 } else {
141 expect(subject).to.match(fn);
142 }
143 }
144 thrown = true;
145 }
146
147 if ('object' == typeof fn && not) {
148 // in the presence of a matcher, ensure the `not` only applies to
149 // the matching.
150 this.flags.not = false;
151 }
152
153 var name = this.obj.name || 'fn';
154 this.assert(
155 thrown
156 , function(){ return 'expected ' + name + ' to throw an exception' }
157 , function(){ return 'expected ' + name + ' not to throw an exception' });
158 };
159
160 /**
161 * Checks if the array is empty.
162 *
163 * @api public
164 */
165
166 Assertion.prototype.empty = function () {
167 var expectation;
168
169 if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) {
170 if ('number' == typeof this.obj.length) {
171 expectation = !this.obj.length;
172 } else {
173 expectation = !keys(this.obj).length;
174 }
175 } else {
176 if ('string' != typeof this.obj) {
177 expect(this.obj).to.be.an('object');
178 }
179
180 expect(this.obj).to.have.property('length');
181 expectation = !this.obj.length;
182 }
183
184 this.assert(
185 expectation
186 , function(){ return 'expected ' + i(this.obj) + ' to be empty' }
187 , function(){ return 'expected ' + i(this.obj) + ' to not be empty' });
188 return this;
189 };
190
191 /**
192 * Checks if the obj exactly equals another.
193 *
194 * @api public
195 */
196
197 Assertion.prototype.be =
198 Assertion.prototype.equal = function (obj) {
199 this.assert(
200 obj === this.obj
201 , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) }
202 , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) });
203 return this;
204 };
205
206 /**
207 * Checks if the obj sortof equals another.
208 *
209 * @api public
210 */
211
212 Assertion.prototype.eql = function (obj) {
213 this.assert(
214 expect.eql(obj, this.obj)
215 , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) }
216 , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) });
217 return this;
218 };
219
220 /**
221 * Assert within start to finish (inclusive).
222 *
223 * @param {Number} start
224 * @param {Number} finish
225 * @api public
226 */
227
228 Assertion.prototype.within = function (start, finish) {
229 var range = start + '..' + finish;
230 this.assert(
231 this.obj >= start && this.obj <= finish
232 , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range }
233 , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range });
234 return this;
235 };
236
237 /**
238 * Assert typeof / instance of
239 *
240 * @api public
241 */
242
243 Assertion.prototype.a =
244 Assertion.prototype.an = function (type) {
245 if ('string' == typeof type) {
246 // proper english in error msg
247 var n = /^[aeiou]/.test(type) ? 'n' : '';
248
249 // typeof with support for 'array'
250 this.assert(
251 'array' == type ? isArray(this.obj) :
252 'object' == type
253 ? 'object' == typeof this.obj && null !== this.obj
254 : type == typeof this.obj
255 , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type }
256 , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type });
257 } else {
258 // instanceof
259 var name = type.name || 'supplied constructor';
260 this.assert(
261 this.obj instanceof type
262 , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name }
263 , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name });
264 }
265
266 return this;
267 };
268
269 /**
270 * Assert numeric value above _n_.
271 *
272 * @param {Number} n
273 * @api public
274 */
275
276 Assertion.prototype.greaterThan =
277 Assertion.prototype.above = function (n) {
278 this.assert(
279 this.obj > n
280 , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }
281 , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n });
282 return this;
283 };
284
285 /**
286 * Assert numeric value below _n_.
287 *
288 * @param {Number} n
289 * @api public
290 */
291
292 Assertion.prototype.lessThan =
293 Assertion.prototype.below = function (n) {
294 this.assert(
295 this.obj < n
296 , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }
297 , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n });
298 return this;
299 };
300
301 /**
302 * Assert string value matches _regexp_.
303 *
304 * @param {RegExp} regexp
305 * @api public
306 */
307
308 Assertion.prototype.match = function (regexp) {
309 this.assert(
310 regexp.exec(this.obj)
311 , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp }
312 , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp });
313 return this;
314 };
315
316 /**
317 * Assert property "length" exists and has value of _n_.
318 *
319 * @param {Number} n
320 * @api public
321 */
322
323 Assertion.prototype.length = function (n) {
324 expect(this.obj).to.have.property('length');
325 var len = this.obj.length;
326 this.assert(
327 n == len
328 , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len }
329 , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len });
330 return this;
331 };
332
333 /**
334 * Assert property _name_ exists, with optional _val_.
335 *
336 * @param {String} name
337 * @param {Mixed} val
338 * @api public
339 */
340
341 Assertion.prototype.property = function (name, val) {
342 if (this.flags.own) {
343 this.assert(
344 Object.prototype.hasOwnProperty.call(this.obj, name)
345 , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) }
346 , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) });
347 return this;
348 }
349
350 if (this.flags.not && undefined !== val) {
351 if (undefined === this.obj[name]) {
352 throw new Error(i(this.obj) + ' has no property ' + i(name));
353 }
354 } else {
355 var hasProp;
356 try {
357 hasProp = name in this.obj
358 } catch (e) {
359 hasProp = undefined !== this.obj[name]
360 }
361
362 this.assert(
363 hasProp
364 , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) }
365 , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) });
366 }
367
368 if (undefined !== val) {
369 this.assert(
370 val === this.obj[name]
371 , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name)
372 + ' of ' + i(val) + ', but got ' + i(this.obj[name]) }
373 , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name)
374 + ' of ' + i(val) });
375 }
376
377 this.obj = this.obj[name];
378 return this;
379 };
380
381 /**
382 * Assert that the array contains _obj_ or string contains _obj_.
383 *
384 * @param {Mixed} obj|string
385 * @api public
386 */
387
388 Assertion.prototype.string =
389 Assertion.prototype.contain = function (obj) {
390 if ('string' == typeof this.obj) {
391 this.assert(
392 ~this.obj.indexOf(obj)
393 , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) }
394 , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) });
395 } else {
396 this.assert(
397 ~indexOf(this.obj, obj)
398 , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) }
399 , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) });
400 }
401 return this;
402 };
403
404 /**
405 * Assert exact keys or inclusion of keys by using
406 * the `.own` modifier.
407 *
408 * @param {Array|String ...} keys
409 * @api public
410 */
411
412 Assertion.prototype.key =
413 Assertion.prototype.keys = function ($keys) {
414 var str
415 , ok = true;
416
417 $keys = isArray($keys)
418 ? $keys
419 : Array.prototype.slice.call(arguments);
420
421 if (!$keys.length) throw new Error('keys required');
422
423 var actual = keys(this.obj)
424 , len = $keys.length;
425
426 // Inclusion
427 ok = every($keys, function (key) {
428 return ~indexOf(actual, key);
429 });
430
431 // Strict
432 if (!this.flags.not && this.flags.only) {
433 ok = ok && $keys.length == actual.length;
434 }
435
436 // Key string
437 if (len > 1) {
438 $keys = map($keys, function (key) {
439 return i(key);
440 });
441 var last = $keys.pop();
442 str = $keys.join(', ') + ', and ' + last;
443 } else {
444 str = i($keys[0]);
445 }
446
447 // Form
448 str = (len > 1 ? 'keys ' : 'key ') + str;
449
450 // Have / include
451 str = (!this.flags.only ? 'include ' : 'only have ') + str;
452
453 // Assertion
454 this.assert(
455 ok
456 , function(){ return 'expected ' + i(this.obj) + ' to ' + str }
457 , function(){ return 'expected ' + i(this.obj) + ' to not ' + str });
458
459 return this;
460 };
461 /**
462 * Assert a failure.
463 *
464 * @param {String ...} custom message
465 * @api public
466 */
467 Assertion.prototype.fail = function (msg) {
468 msg = msg || "explicit failure";
469 this.assert(false, msg, msg);
470 return this;
471 };
472
473 /**
474 * Function bind implementation.
475 */
476
477 function bind (fn, scope) {
478 return function () {
479 return fn.apply(scope, arguments);
480 }
481 }
482
483 /**
484 * Array every compatibility
485 *
486 * @see bit.ly/5Fq1N2
487 * @api public
488 */
489
490 function every (arr, fn, thisObj) {
491 var scope = thisObj || global;
492 for (var i = 0, j = arr.length; i < j; ++i) {
493 if (!fn.call(scope, arr[i], i, arr)) {
494 return false;
495 }
496 }
497 return true;
498 };
499
500 /**
501 * Array indexOf compatibility.
502 *
503 * @see bit.ly/a5Dxa2
504 * @api public
505 */
506
507 function indexOf (arr, o, i) {
508 if (Array.prototype.indexOf) {
509 return Array.prototype.indexOf.call(arr, o, i);
510 }
511
512 if (arr.length === undefined) {
513 return -1;
514 }
515
516 for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0
517 ; i < j && arr[i] !== o; i++);
518
519 return j <= i ? -1 : i;
520 };
521
522 // https://gist.github.com/1044128/
523 var getOuterHTML = function(element) {
524 if ('outerHTML' in element) return element.outerHTML;
525 var ns = "http://www.w3.org/1999/xhtml";
526 var container = document.createElementNS(ns, '_');
527 var elemProto = (window.HTMLElement || window.Element).prototype;
528 var xmlSerializer = new XMLSerializer();
529 var html;
530 if (document.xmlVersion) {
531 return xmlSerializer.serializeToString(element);
532 } else {
533 container.appendChild(element.cloneNode(false));
534 html = container.innerHTML.replace('><', '>' + element.innerHTML + '<');
535 container.innerHTML = '';
536 return html;
537 }
538 };
539
540 // Returns true if object is a DOM element.
541 var isDOMElement = function (object) {
542 if (typeof HTMLElement === 'object') {
543 return object instanceof HTMLElement;
544 } else {
545 return object &&
546 typeof object === 'object' &&
547 object.nodeType === 1 &&
548 typeof object.nodeName === 'string';
549 }
550 };
551
552 /**
553 * Inspects an object.
554 *
555 * @see taken from node.js `util` module (copyright Joyent, MIT license)
556 * @api private
557 */
558
559 function i (obj, showHidden, depth) {
560 var seen = [];
561
562 function stylize (str) {
563 return str;
564 };
565
566 function format (value, recurseTimes) {
567 // Provide a hook for user-specified inspect functions.
568 // Check that value is an object with an inspect function on it
569 if (value && typeof value.inspect === 'function' &&
570 // Filter out the util module, it's inspect function is special
571 value !== exports &&
572 // Also filter out any prototype objects using the circular check.
573 !(value.constructor && value.constructor.prototype === value)) {
574 return value.inspect(recurseTimes);
575 }
576
577 // Primitive types cannot have properties
578 switch (typeof value) {
579 case 'undefined':
580 return stylize('undefined', 'undefined');
581
582 case 'string':
583 var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '')
584 .replace(/'/g, "\\'")
585 .replace(/\\"/g, '"') + '\'';
586 return stylize(simple, 'string');
587
588 case 'number':
589 return stylize('' + value, 'number');
590
591 case 'boolean':
592 return stylize('' + value, 'boolean');
593 }
594 // For some reason typeof null is "object", so special case here.
595 if (value === null) {
596 return stylize('null', 'null');
597 }
598
599 if (isDOMElement(value)) {
600 return getOuterHTML(value);
601 }
602
603 // Look up the keys of the object.
604 var visible_keys = keys(value);
605 var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys;
606
607 // Functions without properties can be shortcutted.
608 if (typeof value === 'function' && $keys.length === 0) {
609 if (isRegExp(value)) {
610 return stylize('' + value, 'regexp');
611 } else {
612 var name = value.name ? ': ' + value.name : '';
613 return stylize('[Function' + name + ']', 'special');
614 }
615 }
616
617 // Dates without properties can be shortcutted
618 if (isDate(value) && $keys.length === 0) {
619 return stylize(value.toUTCString(), 'date');
620 }
621
622 var base, type, braces;
623 // Determine the object type
624 if (isArray(value)) {
625 type = 'Array';
626 braces = ['[', ']'];
627 } else {
628 type = 'Object';
629 braces = ['{', '}'];
630 }
631
632 // Make functions say that they are functions
633 if (typeof value === 'function') {
634 var n = value.name ? ': ' + value.name : '';
635 base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']';
636 } else {
637 base = '';
638 }
639
640 // Make dates with properties first say the date
641 if (isDate(value)) {
642 base = ' ' + value.toUTCString();
643 }
644
645 if ($keys.length === 0) {
646 return braces[0] + base + braces[1];
647 }
648
649 if (recurseTimes < 0) {
650 if (isRegExp(value)) {
651 return stylize('' + value, 'regexp');
652 } else {
653 return stylize('[Object]', 'special');
654 }
655 }
656
657 seen.push(value);
658
659 var output = map($keys, function (key) {
660 var name, str;
661 if (value.__lookupGetter__) {
662 if (value.__lookupGetter__(key)) {
663 if (value.__lookupSetter__(key)) {
664 str = stylize('[Getter/Setter]', 'special');
665 } else {
666 str = stylize('[Getter]', 'special');
667 }
668 } else {
669 if (value.__lookupSetter__(key)) {
670 str = stylize('[Setter]', 'special');
671 }
672 }
673 }
674 if (indexOf(visible_keys, key) < 0) {
675 name = '[' + key + ']';
676 }
677 if (!str) {
678 if (indexOf(seen, value[key]) < 0) {
679 if (recurseTimes === null) {
680 str = format(value[key]);
681 } else {
682 str = format(value[key], recurseTimes - 1);
683 }
684 if (str.indexOf('\n') > -1) {
685 if (isArray(value)) {
686 str = map(str.split('\n'), function (line) {
687 return ' ' + line;
688 }).join('\n').substr(2);
689 } else {
690 str = '\n' + map(str.split('\n'), function (line) {
691 return ' ' + line;
692 }).join('\n');
693 }
694 }
695 } else {
696 str = stylize('[Circular]', 'special');
697 }
698 }
699 if (typeof name === 'undefined') {
700 if (type === 'Array' && key.match(/^\d+$/)) {
701 return str;
702 }
703 name = json.stringify('' + key);
704 if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
705 name = name.substr(1, name.length - 2);
706 name = stylize(name, 'name');
707 } else {
708 name = name.replace(/'/g, "\\'")
709 .replace(/\\"/g, '"')
710 .replace(/(^"|"$)/g, "'");
711 name = stylize(name, 'string');
712 }
713 }
714
715 return name + ': ' + str;
716 });
717
718 seen.pop();
719
720 var numLinesEst = 0;
721 var length = reduce(output, function (prev, cur) {
722 numLinesEst++;
723 if (indexOf(cur, '\n') >= 0) numLinesEst++;
724 return prev + cur.length + 1;
725 }, 0);
726
727 if (length > 50) {
728 output = braces[0] +
729 (base === '' ? '' : base + '\n ') +
730 ' ' +
731 output.join(',\n ') +
732 ' ' +
733 braces[1];
734
735 } else {
736 output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
737 }
738
739 return output;
740 }
741 return format(obj, (typeof depth === 'undefined' ? 2 : depth));
742 };
743
744 function isArray (ar) {
745 return Object.prototype.toString.call(ar) == '[object Array]';
746 };
747
748 function isRegExp(re) {
749 var s;
750 try {
751 s = '' + re;
752 } catch (e) {
753 return false;
754 }
755
756 return re instanceof RegExp || // easy case
757 // duck-type for context-switching evalcx case
758 typeof(re) === 'function' &&
759 re.constructor.name === 'RegExp' &&
760 re.compile &&
761 re.test &&
762 re.exec &&
763 s.match(/^\/.*\/[gim]{0,3}$/);
764 };
765
766 function isDate(d) {
767 if (d instanceof Date) return true;
768 return false;
769 };
770
771 function keys (obj) {
772 if (Object.keys) {
773 return Object.keys(obj);
774 }
775
776 var keys = [];
777
778 for (var i in obj) {
779 if (Object.prototype.hasOwnProperty.call(obj, i)) {
780 keys.push(i);
781 }
782 }
783
784 return keys;
785 }
786
787 function map (arr, mapper, that) {
788 if (Array.prototype.map) {
789 return Array.prototype.map.call(arr, mapper, that);
790 }
791
792 var other= new Array(arr.length);
793
794 for (var i= 0, n = arr.length; i<n; i++)
795 if (i in arr)
796 other[i] = mapper.call(that, arr[i], i, arr);
797
798 return other;
799 };
800
801 function reduce (arr, fun) {
802 if (Array.prototype.reduce) {
803 return Array.prototype.reduce.apply(
804 arr
805 , Array.prototype.slice.call(arguments, 1)
806 );
807 }
808
809 var len = +this.length;
810
811 if (typeof fun !== "function")
812 throw new TypeError();
813
814 // no value to return if no initial value and an empty array
815 if (len === 0 && arguments.length === 1)
816 throw new TypeError();
817
818 var i = 0;
819 if (arguments.length >= 2) {
820 var rv = arguments[1];
821 } else {
822 do {
823 if (i in this) {
824 rv = this[i++];
825 break;
826 }
827
828 // if array contains no values, no initial value to return
829 if (++i >= len)
830 throw new TypeError();
831 } while (true);
832 }
833
834 for (; i < len; i++) {
835 if (i in this)
836 rv = fun.call(null, rv, this[i], i, this);
837 }
838
839 return rv;
840 };
841
842 /**
843 * Asserts deep equality
844 *
845 * @see taken from node.js `assert` module (copyright Joyent, MIT license)
846 * @api private
847 */
848
849 expect.eql = function eql (actual, expected) {
850 // 7.1. All identical values are equivalent, as determined by ===.
851 if (actual === expected) {
852 return true;
853 } else if ('undefined' != typeof Buffer
854 && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
855 if (actual.length != expected.length) return false;
856
857 for (var i = 0; i < actual.length; i++) {
858 if (actual[i] !== expected[i]) return false;
859 }
860
861 return true;
862
863 // 7.2. If the expected value is a Date object, the actual value is
864 // equivalent if it is also a Date object that refers to the same time.
865 } else if (actual instanceof Date && expected instanceof Date) {
866 return actual.getTime() === expected.getTime();
867
868 // 7.3. Other pairs that do not both pass typeof value == "object",
869 // equivalence is determined by ==.
870 } else if (typeof actual != 'object' && typeof expected != 'object') {
871 return actual == expected;
872
873 // 7.4. For all other Object pairs, including Array objects, equivalence is
874 // determined by having the same number of owned properties (as verified
875 // with Object.prototype.hasOwnProperty.call), the same set of keys
876 // (although not necessarily the same order), equivalent values for every
877 // corresponding key, and an identical "prototype" property. Note: this
878 // accounts for both named and indexed properties on Arrays.
879 } else {
880 return objEquiv(actual, expected);
881 }
882 }
883
884 function isUndefinedOrNull (value) {
885 return value === null || value === undefined;
886 }
887
888 function isArguments (object) {
889 return Object.prototype.toString.call(object) == '[object Arguments]';
890 }
891
892 function objEquiv (a, b) {
893 if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
894 return false;
895 // an identical "prototype" property.
896 if (a.prototype !== b.prototype) return false;
897 //~~~I've managed to break Object.keys through screwy arguments passing.
898 // Converting to array solves the problem.
899 if (isArguments(a)) {
900 if (!isArguments(b)) {
901 return false;
902 }
903 a = pSlice.call(a);
904 b = pSlice.call(b);
905 return expect.eql(a, b);
906 }
907 try{
908 var ka = keys(a),
909 kb = keys(b),
910 key, i;
911 } catch (e) {//happens when one is a string literal and the other isn't
912 return false;
913 }
914 // having the same number of owned properties (keys incorporates hasOwnProperty)
915 if (ka.length != kb.length)
916 return false;
917 //the same set of keys (although not necessarily the same order),
918 ka.sort();
919 kb.sort();
920 //~~~cheap key test
921 for (i = ka.length - 1; i >= 0; i--) {
922 if (ka[i] != kb[i])
923 return false;
924 }
925 //equivalent values for every corresponding key, and
926 //~~~possibly expensive deep test
927 for (i = ka.length - 1; i >= 0; i--) {
928 key = ka[i];
929 if (!expect.eql(a[key], b[key]))
930 return false;
931 }
932 return true;
933 }
934
935 var json = (function () {
936 "use strict";
937
938 if ('object' == typeof JSON && JSON.parse && JSON.stringify) {
939 return {
940 parse: nativeJSON.parse
941 , stringify: nativeJSON.stringify
942 }
943 }
944
945 var JSON = {};
946
947 function f(n) {
948 // Format integers to have at least two digits.
949 return n < 10 ? '0' + n : n;
950 }
951
952 function date(d, key) {
953 return isFinite(d.valueOf()) ?
954 d.getUTCFullYear() + '-' +
955 f(d.getUTCMonth() + 1) + '-' +
956 f(d.getUTCDate()) + 'T' +
957 f(d.getUTCHours()) + ':' +
958 f(d.getUTCMinutes()) + ':' +
959 f(d.getUTCSeconds()) + 'Z' : null;
960 };
961
962 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
963 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
964 gap,
965 indent,
966 meta = { // table of character substitutions
967 '\b': '\\b',
968 '\t': '\\t',
969 '\n': '\\n',
970 '\f': '\\f',
971 '\r': '\\r',
972 '"' : '\\"',
973 '\\': '\\\\'
974 },
975 rep;
976
977
978 function quote(string) {
979
980 // If the string contains no control characters, no quote characters, and no
981 // backslash characters, then we can safely slap some quotes around it.
982 // Otherwise we must also replace the offending characters with safe escape
983 // sequences.
984
985 escapable.lastIndex = 0;
986 return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
987 var c = meta[a];
988 return typeof c === 'string' ? c :
989 '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
990 }) + '"' : '"' + string + '"';
991 }
992
993
994 function str(key, holder) {
995
996 // Produce a string from holder[key].
997
998 var i, // The loop counter.
999 k, // The member key.
1000 v, // The member value.
1001 length,
1002 mind = gap,
1003 partial,
1004 value = holder[key];
1005
1006 // If the value has a toJSON method, call it to obtain a replacement value.
1007
1008 if (value instanceof Date) {
1009 value = date(key);
1010 }
1011
1012 // If we were called with a replacer function, then call the replacer to
1013 // obtain a replacement value.
1014
1015 if (typeof rep === 'function') {
1016 value = rep.call(holder, key, value);
1017 }
1018
1019 // What happens next depends on the value's type.
1020
1021 switch (typeof value) {
1022 case 'string':
1023 return quote(value);
1024
1025 case 'number':
1026
1027 // JSON numbers must be finite. Encode non-finite numbers as null.
1028
1029 return isFinite(value) ? String(value) : 'null';
1030
1031 case 'boolean':
1032 case 'null':
1033
1034 // If the value is a boolean or null, convert it to a string. Note:
1035 // typeof null does not produce 'null'. The case is included here in
1036 // the remote chance that this gets fixed someday.
1037
1038 return String(value);
1039
1040 // If the type is 'object', we might be dealing with an object or an array or
1041 // null.
1042
1043 case 'object':
1044
1045 // Due to a specification blunder in ECMAScript, typeof null is 'object',
1046 // so watch out for that case.
1047
1048 if (!value) {
1049 return 'null';
1050 }
1051
1052 // Make an array to hold the partial results of stringifying this object value.
1053
1054 gap += indent;
1055 partial = [];
1056
1057 // Is the value an array?
1058
1059 if (Object.prototype.toString.apply(value) === '[object Array]') {
1060
1061 // The value is an array. Stringify every element. Use null as a placeholder
1062 // for non-JSON values.
1063
1064 length = value.length;
1065 for (i = 0; i < length; i += 1) {
1066 partial[i] = str(i, value) || 'null';
1067 }
1068
1069 // Join all of the elements together, separated with commas, and wrap them in
1070 // brackets.
1071
1072 v = partial.length === 0 ? '[]' : gap ?
1073 '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
1074 '[' + partial.join(',') + ']';
1075 gap = mind;
1076 return v;
1077 }
1078
1079 // If the replacer is an array, use it to select the members to be stringified.
1080
1081 if (rep && typeof rep === 'object') {
1082 length = rep.length;
1083 for (i = 0; i < length; i += 1) {
1084 if (typeof rep[i] === 'string') {
1085 k = rep[i];
1086 v = str(k, value);
1087 if (v) {
1088 partial.push(quote(k) + (gap ? ': ' : ':') + v);
1089 }
1090 }
1091 }
1092 } else {
1093
1094 // Otherwise, iterate through all of the keys in the object.
1095
1096 for (k in value) {
1097 if (Object.prototype.hasOwnProperty.call(value, k)) {
1098 v = str(k, value);
1099 if (v) {
1100 partial.push(quote(k) + (gap ? ': ' : ':') + v);
1101 }
1102 }
1103 }
1104 }
1105
1106 // Join all of the member texts together, separated with commas,
1107 // and wrap them in braces.
1108
1109 v = partial.length === 0 ? '{}' : gap ?
1110 '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
1111 '{' + partial.join(',') + '}';
1112 gap = mind;
1113 return v;
1114 }
1115 }
1116
1117 // If the JSON object does not yet have a stringify method, give it one.
1118
1119 JSON.stringify = function (value, replacer, space) {
1120
1121 // The stringify method takes a value and an optional replacer, and an optional
1122 // space parameter, and returns a JSON text. The replacer can be a function
1123 // that can replace values, or an array of strings that will select the keys.
1124 // A default replacer method can be provided. Use of the space parameter can
1125 // produce text that is more easily readable.
1126
1127 var i;
1128 gap = '';
1129 indent = '';
1130
1131 // If the space parameter is a number, make an indent string containing that
1132 // many spaces.
1133
1134 if (typeof space === 'number') {
1135 for (i = 0; i < space; i += 1) {
1136 indent += ' ';
1137 }
1138
1139 // If the space parameter is a string, it will be used as the indent string.
1140
1141 } else if (typeof space === 'string') {
1142 indent = space;
1143 }
1144
1145 // If there is a replacer, it must be a function or an array.
1146 // Otherwise, throw an error.
1147
1148 rep = replacer;
1149 if (replacer && typeof replacer !== 'function' &&
1150 (typeof replacer !== 'object' ||
1151 typeof replacer.length !== 'number')) {
1152 throw new Error('JSON.stringify');
1153 }
1154
1155 // Make a fake root object containing our value under the key of ''.
1156 // Return the result of stringifying the value.
1157
1158 return str('', {'': value});
1159 };
1160
1161 // If the JSON object does not yet have a parse method, give it one.
1162
1163 JSON.parse = function (text, reviver) {
1164 // The parse method takes a text and an optional reviver function, and returns
1165 // a JavaScript value if the text is a valid JSON text.
1166
1167 var j;
1168
1169 function walk(holder, key) {
1170
1171 // The walk method is used to recursively walk the resulting structure so
1172 // that modifications can be made.
1173
1174 var k, v, value = holder[key];
1175 if (value && typeof value === 'object') {
1176 for (k in value) {
1177 if (Object.prototype.hasOwnProperty.call(value, k)) {
1178 v = walk(value, k);
1179 if (v !== undefined) {
1180 value[k] = v;
1181 } else {
1182 delete value[k];
1183 }
1184 }
1185 }
1186 }
1187 return reviver.call(holder, key, value);
1188 }
1189
1190
1191 // Parsing happens in four stages. In the first stage, we replace certain
1192 // Unicode characters with escape sequences. JavaScript handles many characters
1193 // incorrectly, either silently deleting them, or treating them as line endings.
1194
1195 text = String(text);
1196 cx.lastIndex = 0;
1197 if (cx.test(text)) {
1198 text = text.replace(cx, function (a) {
1199 return '\\u' +
1200 ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
1201 });
1202 }
1203
1204 // In the second stage, we run the text against regular expressions that look
1205 // for non-JSON patterns. We are especially concerned with '()' and 'new'
1206 // because they can cause invocation, and '=' because it can cause mutation.
1207 // But just to be safe, we want to reject all unexpected forms.
1208
1209 // We split the second stage into 4 regexp operations in order to work around
1210 // crippling inefficiencies in IE's and Safari's regexp engines. First we
1211 // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
1212 // replace all simple value tokens with ']' characters. Third, we delete all
1213 // open brackets that follow a colon or comma or that begin the text. Finally,
1214 // we look to see that the remaining characters are only whitespace or ']' or
1215 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
1216
1217 if (/^[\],:{}\s]*$/
1218 .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
1219 .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
1220 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
1221
1222 // In the third stage we use the eval function to compile the text into a
1223 // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
1224 // in JavaScript: it can begin a block or an object literal. We wrap the text
1225 // in parens to eliminate the ambiguity.
1226
1227 j = eval('(' + text + ')');
1228
1229 // In the optional fourth stage, we recursively walk the new structure, passing
1230 // each name/value pair to a reviver function for possible transformation.
1231
1232 return typeof reviver === 'function' ?
1233 walk({'': j}, '') : j;
1234 }
1235
1236 // If the text is not JSON parseable, then a SyntaxError is thrown.
1237
1238 throw new SyntaxError('JSON.parse');
1239 };
1240
1241 return JSON;
1242 })();
1243
1244 if ('undefined' != typeof window) {
1245 window.expect = module.exports;
1246 }
1247
1248 })(
1249 this
1250 , 'undefined' != typeof module ? module : {}
1251 , 'undefined' != typeof exports ? exports : {}
1252 );
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <meta charset="utf-8">
4 <title>Spec Runner</title>
5 <link rel="stylesheet" type="text/css" href="../node_modules/mocha/mocha.css">
6 </head>
7 <body>
8 <div id="mocha"></div>
9 <script src="expect.js"></script>
10 <script type="text/javascript" src="../node_modules/mocha/mocha.js"></script>
11 <script type="text/javascript" src="happen.js"></script>
12 <script type="text/javascript" src="sinon.js"></script>
13
14 <!-- source files -->
15 <script type="text/javascript" src="before.js"></script>
16 <script type="text/javascript" src="../build/deps.js"></script>
17
18 <script type="text/javascript" src="../debug/leaflet-include.js"></script>
19
20 <script>
21 mocha.setup('bdd');
22 mocha.ignoreLeaks();
23 </script>
24
25 <!-- spec files -->
26
27 <script type="text/javascript" src="suites/SpecHelper.js"></script>
28 <script type="text/javascript" src="suites/LeafletSpec.js"></script>
29
30 <!-- /control -->
31 <script type="text/javascript" src="suites/control/Control.LayersSpec.js"></script>
32 <script type="text/javascript" src="suites/control/Control.ScaleSpec.js"></script>
33 <script type="text/javascript" src="suites/control/Control.AttributionSpec.js"></script>
34
35 <!-- /core -->
36 <script type="text/javascript" src="suites/core/UtilSpec.js"></script>
37 <script type="text/javascript" src="suites/core/ClassSpec.js"></script>
38 <script type="text/javascript" src="suites/core/EventsSpec.js"></script>
39
40 <!-- /geometry -->
41 <script type="text/javascript" src="suites/geometry/PointSpec.js"></script>
42 <script type="text/javascript" src="suites/geometry/BoundsSpec.js"></script>
43 <script type="text/javascript" src="suites/geometry/TransformationSpec.js"></script>
44 <script type="text/javascript" src="suites/geometry/LineUtilSpec.js"></script>
45 <script type="text/javascript" src="suites/geometry/PolyUtilSpec.js"></script>
46
47 <!-- /geo -->
48 <script type="text/javascript" src="suites/geo/LatLngSpec.js"></script>
49 <script type="text/javascript" src="suites/geo/LatLngBoundsSpec.js"></script>
50 <script type="text/javascript" src="suites/geo/ProjectionSpec.js"></script>
51
52 <!-- /dom -->
53 <script type="text/javascript" src="suites/dom/DomEventSpec.js"></script>
54 <script type="text/javascript" src="suites/dom/DomUtilSpec.js"></script>
55
56 <!-- /layer -->
57 <script type="text/javascript" src="suites/layer/FeatureGroupSpec.js"></script>
58 <script type="text/javascript" src="suites/layer/GeoJSONSpec.js"></script>
59 <script type="text/javascript" src="suites/layer/LayerGroupSpec.js"></script>
60 <script type="text/javascript" src="suites/layer/TileLayerSpec.js"></script>
61 <script type="text/javascript" src="suites/layer/PopupSpec.js"></script>
62
63 <!-- /layer/marker/ -->
64 <script type="text/javascript" src="suites/layer/marker/MarkerSpec.js"></script>
65
66 <!-- /layer/vector/ -->
67 <script type="text/javascript" src="suites/layer/vector/CircleSpec.js"></script>
68 <script type="text/javascript" src="suites/layer/vector/CircleMarkerSpec.js"></script>
69 <script type="text/javascript" src="suites/layer/vector/PolygonSpec.js"></script>
70 <script type="text/javascript" src="suites/layer/vector/PolylineSpec.js"></script>
71 <script type="text/javascript" src="suites/layer/vector/PolylineGeometrySpec.js"></script>
72
73 <!-- /map -->
74 <script type="text/javascript" src="suites/map/MapSpec.js"></script>
75
76 <script>
77 (window.mochaPhantomJS || window.mocha).run();
78 </script>
79 </body>
80 </html>
+0
-20
spec/jasmine/MIT.LICENSE less more
0 Copyright (c) 2008-2011 Pivotal Labs
1
2 Permission is hereby granted, free of charge, to any person obtaining
3 a copy of this software and associated documentation files (the
4 "Software"), to deal in the Software without restriction, including
5 without limitation the rights to use, copy, modify, merge, publish,
6 distribute, sublicense, and/or sell copies of the Software, and to
7 permit persons to whom the Software is furnished to do so, subject to
8 the following conditions:
9
10 The above copyright notice and this permission notice shall be
11 included in all copies or substantial portions of the Software.
12
13 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+0
-616
spec/jasmine/jasmine-html.js less more
0 jasmine.HtmlReporterHelpers = {};
1
2 jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
3 var el = document.createElement(type);
4
5 for (var i = 2; i < arguments.length; i++) {
6 var child = arguments[i];
7
8 if (typeof child === 'string') {
9 el.appendChild(document.createTextNode(child));
10 } else {
11 if (child) {
12 el.appendChild(child);
13 }
14 }
15 }
16
17 for (var attr in attrs) {
18 if (attr == "className") {
19 el[attr] = attrs[attr];
20 } else {
21 el.setAttribute(attr, attrs[attr]);
22 }
23 }
24
25 return el;
26 };
27
28 jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
29 var results = child.results();
30 var status = results.passed() ? 'passed' : 'failed';
31 if (results.skipped) {
32 status = 'skipped';
33 }
34
35 return status;
36 };
37
38 jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
39 var parentDiv = this.dom.summary;
40 var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
41 var parent = child[parentSuite];
42
43 if (parent) {
44 if (typeof this.views.suites[parent.id] == 'undefined') {
45 this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
46 }
47 parentDiv = this.views.suites[parent.id].element;
48 }
49
50 parentDiv.appendChild(childElement);
51 };
52
53
54 jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
55 for(var fn in jasmine.HtmlReporterHelpers) {
56 ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
57 }
58 };
59
60 jasmine.HtmlReporter = function(_doc) {
61 var self = this;
62 var doc = _doc || window.document;
63
64 var reporterView;
65
66 var dom = {};
67
68 // Jasmine Reporter Public Interface
69 self.logRunningSpecs = false;
70
71 self.reportRunnerStarting = function(runner) {
72 var specs = runner.specs() || [];
73
74 if (specs.length == 0) {
75 return;
76 }
77
78 createReporterDom(runner.env.versionString());
79 doc.body.appendChild(dom.reporter);
80
81 reporterView = new jasmine.HtmlReporter.ReporterView(dom);
82 reporterView.addSpecs(specs, self.specFilter);
83 };
84
85 self.reportRunnerResults = function(runner) {
86 reporterView && reporterView.complete();
87 };
88
89 self.reportSuiteResults = function(suite) {
90 reporterView.suiteComplete(suite);
91 };
92
93 self.reportSpecStarting = function(spec) {
94 if (self.logRunningSpecs) {
95 self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
96 }
97 };
98
99 self.reportSpecResults = function(spec) {
100 reporterView.specComplete(spec);
101 };
102
103 self.log = function() {
104 var console = jasmine.getGlobal().console;
105 if (console && console.log) {
106 if (console.log.apply) {
107 console.log.apply(console, arguments);
108 } else {
109 console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
110 }
111 }
112 };
113
114 self.specFilter = function(spec) {
115 if (!focusedSpecName()) {
116 return true;
117 }
118
119 return spec.getFullName().indexOf(focusedSpecName()) === 0;
120 };
121
122 return self;
123
124 function focusedSpecName() {
125 var specName;
126
127 (function memoizeFocusedSpec() {
128 if (specName) {
129 return;
130 }
131
132 var paramMap = [];
133 var params = doc.location.search.substring(1).split('&');
134
135 for (var i = 0; i < params.length; i++) {
136 var p = params[i].split('=');
137 paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
138 }
139
140 specName = paramMap.spec;
141 })();
142
143 return specName;
144 }
145
146 function createReporterDom(version) {
147 dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
148 dom.banner = self.createDom('div', { className: 'banner' },
149 self.createDom('span', { className: 'title' }, "Jasmine "),
150 self.createDom('span', { className: 'version' }, version)),
151
152 dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
153 dom.alert = self.createDom('div', {className: 'alert'}),
154 dom.results = self.createDom('div', {className: 'results'},
155 dom.summary = self.createDom('div', { className: 'summary' }),
156 dom.details = self.createDom('div', { id: 'details' }))
157 );
158 }
159 };
160 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) {
161 this.startedAt = new Date();
162 this.runningSpecCount = 0;
163 this.completeSpecCount = 0;
164 this.passedCount = 0;
165 this.failedCount = 0;
166 this.skippedCount = 0;
167
168 this.createResultsMenu = function() {
169 this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
170 this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
171 ' | ',
172 this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
173
174 this.summaryMenuItem.onclick = function() {
175 dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
176 };
177
178 this.detailsMenuItem.onclick = function() {
179 showDetails();
180 };
181 };
182
183 this.addSpecs = function(specs, specFilter) {
184 this.totalSpecCount = specs.length;
185
186 this.views = {
187 specs: {},
188 suites: {}
189 };
190
191 for (var i = 0; i < specs.length; i++) {
192 var spec = specs[i];
193 this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
194 if (specFilter(spec)) {
195 this.runningSpecCount++;
196 }
197 }
198 };
199
200 this.specComplete = function(spec) {
201 this.completeSpecCount++;
202
203 if (isUndefined(this.views.specs[spec.id])) {
204 this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
205 }
206
207 var specView = this.views.specs[spec.id];
208
209 switch (specView.status()) {
210 case 'passed':
211 this.passedCount++;
212 break;
213
214 case 'failed':
215 this.failedCount++;
216 break;
217
218 case 'skipped':
219 this.skippedCount++;
220 break;
221 }
222
223 specView.refresh();
224 this.refresh();
225 };
226
227 this.suiteComplete = function(suite) {
228 var suiteView = this.views.suites[suite.id];
229 if (isUndefined(suiteView)) {
230 return;
231 }
232 suiteView.refresh();
233 };
234
235 this.refresh = function() {
236
237 if (isUndefined(this.resultsMenu)) {
238 this.createResultsMenu();
239 }
240
241 // currently running UI
242 if (isUndefined(this.runningAlert)) {
243 this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
244 dom.alert.appendChild(this.runningAlert);
245 }
246 this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
247
248 // skipped specs UI
249 if (isUndefined(this.skippedAlert)) {
250 this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
251 }
252
253 this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
254
255 if (this.skippedCount === 1 && isDefined(dom.alert)) {
256 dom.alert.appendChild(this.skippedAlert);
257 }
258
259 // passing specs UI
260 if (isUndefined(this.passedAlert)) {
261 this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
262 }
263 this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
264
265 // failing specs UI
266 if (isUndefined(this.failedAlert)) {
267 this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
268 }
269 this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
270
271 if (this.failedCount === 1 && isDefined(dom.alert)) {
272 dom.alert.appendChild(this.failedAlert);
273 dom.alert.appendChild(this.resultsMenu);
274 }
275
276 // summary info
277 this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
278 this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
279 };
280
281 this.complete = function() {
282 dom.alert.removeChild(this.runningAlert);
283
284 this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
285
286 if (this.failedCount === 0) {
287 dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
288 } else {
289 showDetails();
290 }
291
292 dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
293 };
294
295 return this;
296
297 function showDetails() {
298 if (dom.reporter.className.search(/showDetails/) === -1) {
299 dom.reporter.className += " showDetails";
300 }
301 }
302
303 function isUndefined(obj) {
304 return typeof obj === 'undefined';
305 }
306
307 function isDefined(obj) {
308 return !isUndefined(obj);
309 }
310
311 function specPluralizedFor(count) {
312 var str = count + " spec";
313 if (count > 1) {
314 str += "s"
315 }
316 return str;
317 }
318
319 };
320
321 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
322
323
324 jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
325 this.spec = spec;
326 this.dom = dom;
327 this.views = views;
328
329 this.symbol = this.createDom('li', { className: 'pending' });
330 this.dom.symbolSummary.appendChild(this.symbol);
331
332 this.summary = this.createDom('div', { className: 'specSummary' },
333 this.createDom('a', {
334 className: 'description',
335 href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
336 title: this.spec.getFullName()
337 }, this.spec.description)
338 );
339
340 this.detail = this.createDom('div', { className: 'specDetail' },
341 this.createDom('a', {
342 className: 'description',
343 href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
344 title: this.spec.getFullName()
345 }, this.spec.getFullName())
346 );
347 };
348
349 jasmine.HtmlReporter.SpecView.prototype.status = function() {
350 return this.getSpecStatus(this.spec);
351 };
352
353 jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
354 this.symbol.className = this.status();
355
356 switch (this.status()) {
357 case 'skipped':
358 break;
359
360 case 'passed':
361 this.appendSummaryToSuiteDiv();
362 break;
363
364 case 'failed':
365 this.appendSummaryToSuiteDiv();
366 this.appendFailureDetail();
367 break;
368 }
369 };
370
371 jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
372 this.summary.className += ' ' + this.status();
373 this.appendToSummary(this.spec, this.summary);
374 };
375
376 jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
377 this.detail.className += ' ' + this.status();
378
379 var resultItems = this.spec.results().getItems();
380 var messagesDiv = this.createDom('div', { className: 'messages' });
381
382 for (var i = 0; i < resultItems.length; i++) {
383 var result = resultItems[i];
384
385 if (result.type == 'log') {
386 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
387 } else if (result.type == 'expect' && result.passed && !result.passed()) {
388 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
389
390 if (result.trace.stack) {
391 messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
392 }
393 }
394 }
395
396 if (messagesDiv.childNodes.length > 0) {
397 this.detail.appendChild(messagesDiv);
398 this.dom.details.appendChild(this.detail);
399 }
400 };
401
402 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
403 this.suite = suite;
404 this.dom = dom;
405 this.views = views;
406
407 this.element = this.createDom('div', { className: 'suite' },
408 this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
409 );
410
411 this.appendToSummary(this.suite, this.element);
412 };
413
414 jasmine.HtmlReporter.SuiteView.prototype.status = function() {
415 return this.getSpecStatus(this.suite);
416 };
417
418 jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
419 this.element.className += " " + this.status();
420 };
421
422 jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
423
424 /* @deprecated Use jasmine.HtmlReporter instead
425 */
426 jasmine.TrivialReporter = function(doc) {
427 this.document = doc || document;
428 this.suiteDivs = {};
429 this.logRunningSpecs = false;
430 };
431
432 jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
433 var el = document.createElement(type);
434
435 for (var i = 2; i < arguments.length; i++) {
436 var child = arguments[i];
437
438 if (typeof child === 'string') {
439 el.appendChild(document.createTextNode(child));
440 } else {
441 if (child) { el.appendChild(child); }
442 }
443 }
444
445 for (var attr in attrs) {
446 if (attr == "className") {
447 el[attr] = attrs[attr];
448 } else {
449 el.setAttribute(attr, attrs[attr]);
450 }
451 }
452
453 return el;
454 };
455
456 jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
457 var showPassed, showSkipped;
458
459 this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
460 this.createDom('div', { className: 'banner' },
461 this.createDom('div', { className: 'logo' },
462 this.createDom('span', { className: 'title' }, "Jasmine"),
463 this.createDom('span', { className: 'version' }, runner.env.versionString())),
464 this.createDom('div', { className: 'options' },
465 "Show ",
466 showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
467 this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
468 showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
469 this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
470 )
471 ),
472
473 this.runnerDiv = this.createDom('div', { className: 'runner running' },
474 this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
475 this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
476 this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
477 );
478
479 this.document.body.appendChild(this.outerDiv);
480
481 var suites = runner.suites();
482 for (var i = 0; i < suites.length; i++) {
483 var suite = suites[i];
484 var suiteDiv = this.createDom('div', { className: 'suite' },
485 this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
486 this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
487 this.suiteDivs[suite.id] = suiteDiv;
488 var parentDiv = this.outerDiv;
489 if (suite.parentSuite) {
490 parentDiv = this.suiteDivs[suite.parentSuite.id];
491 }
492 parentDiv.appendChild(suiteDiv);
493 }
494
495 this.startedAt = new Date();
496
497 var self = this;
498 showPassed.onclick = function(evt) {
499 if (showPassed.checked) {
500 self.outerDiv.className += ' show-passed';
501 } else {
502 self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
503 }
504 };
505
506 showSkipped.onclick = function(evt) {
507 if (showSkipped.checked) {
508 self.outerDiv.className += ' show-skipped';
509 } else {
510 self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
511 }
512 };
513 };
514
515 jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
516 var results = runner.results();
517 var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
518 this.runnerDiv.setAttribute("class", className);
519 //do it twice for IE
520 this.runnerDiv.setAttribute("className", className);
521 var specs = runner.specs();
522 var specCount = 0;
523 for (var i = 0; i < specs.length; i++) {
524 if (this.specFilter(specs[i])) {
525 specCount++;
526 }
527 }
528 var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
529 message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
530 this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
531
532 this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
533 };
534
535 jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
536 var results = suite.results();
537 var status = results.passed() ? 'passed' : 'failed';
538 if (results.totalCount === 0) { // todo: change this to check results.skipped
539 status = 'skipped';
540 }
541 this.suiteDivs[suite.id].className += " " + status;
542 };
543
544 jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
545 if (this.logRunningSpecs) {
546 this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
547 }
548 };
549
550 jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
551 var results = spec.results();
552 var status = results.passed() ? 'passed' : 'failed';
553 if (results.skipped) {
554 status = 'skipped';
555 }
556 var specDiv = this.createDom('div', { className: 'spec ' + status },
557 this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
558 this.createDom('a', {
559 className: 'description',
560 href: '?spec=' + encodeURIComponent(spec.getFullName()),
561 title: spec.getFullName()
562 }, spec.description));
563
564
565 var resultItems = results.getItems();
566 var messagesDiv = this.createDom('div', { className: 'messages' });
567 for (var i = 0; i < resultItems.length; i++) {
568 var result = resultItems[i];
569
570 if (result.type == 'log') {
571 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
572 } else if (result.type == 'expect' && result.passed && !result.passed()) {
573 messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
574
575 if (result.trace.stack) {
576 messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
577 }
578 }
579 }
580
581 if (messagesDiv.childNodes.length > 0) {
582 specDiv.appendChild(messagesDiv);
583 }
584
585 this.suiteDivs[spec.suite.id].appendChild(specDiv);
586 };
587
588 jasmine.TrivialReporter.prototype.log = function() {
589 var console = jasmine.getGlobal().console;
590 if (console && console.log) {
591 if (console.log.apply) {
592 console.log.apply(console, arguments);
593 } else {
594 console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
595 }
596 }
597 };
598
599 jasmine.TrivialReporter.prototype.getLocation = function() {
600 return this.document.location;
601 };
602
603 jasmine.TrivialReporter.prototype.specFilter = function(spec) {
604 var paramMap = {};
605 var params = this.getLocation().search.substring(1).split('&');
606 for (var i = 0; i < params.length; i++) {
607 var p = params[i].split('=');
608 paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
609 }
610
611 if (!paramMap.spec) {
612 return true;
613 }
614 return spec.getFullName().indexOf(paramMap.spec) === 0;
615 };
+0
-81
spec/jasmine/jasmine.css less more
0 body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
1
2 #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
3 #HTMLReporter a { text-decoration: none; }
4 #HTMLReporter a:hover { text-decoration: underline; }
5 #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
6 #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
7 #HTMLReporter #jasmine_content { position: fixed; right: 100%; }
8 #HTMLReporter .version { color: #aaaaaa; }
9 #HTMLReporter .banner { margin-top: 14px; }
10 #HTMLReporter .duration { color: #aaaaaa; float: right; }
11 #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
12 #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
13 #HTMLReporter .symbolSummary li.passed { font-size: 14px; }
14 #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
15 #HTMLReporter .symbolSummary li.failed { line-height: 9px; }
16 #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
17 #HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
18 #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
19 #HTMLReporter .symbolSummary li.pending { line-height: 11px; }
20 #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
21 #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
22 #HTMLReporter .runningAlert { background-color: #666666; }
23 #HTMLReporter .skippedAlert { background-color: #aaaaaa; }
24 #HTMLReporter .skippedAlert:first-child { background-color: #333333; }
25 #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
26 #HTMLReporter .passingAlert { background-color: #a6b779; }
27 #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
28 #HTMLReporter .failingAlert { background-color: #cf867e; }
29 #HTMLReporter .failingAlert:first-child { background-color: #b03911; }
30 #HTMLReporter .results { margin-top: 14px; }
31 #HTMLReporter #details { display: none; }
32 #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
33 #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
34 #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
35 #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
36 #HTMLReporter.showDetails .summary { display: none; }
37 #HTMLReporter.showDetails #details { display: block; }
38 #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
39 #HTMLReporter .summary { margin-top: 14px; }
40 #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
41 #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
42 #HTMLReporter .summary .specSummary.failed a { color: #b03911; }
43 #HTMLReporter .description + .suite { margin-top: 0; }
44 #HTMLReporter .suite { margin-top: 14px; }
45 #HTMLReporter .suite a { color: #333333; }
46 #HTMLReporter #details .specDetail { margin-bottom: 28px; }
47 #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
48 #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
49 #HTMLReporter .resultMessage span.result { display: block; }
50 #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
51
52 #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
53 #TrivialReporter a:visited, #TrivialReporter a { color: #303; }
54 #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
55 #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
56 #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
57 #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
58 #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
59 #TrivialReporter .runner.running { background-color: yellow; }
60 #TrivialReporter .options { text-align: right; font-size: .8em; }
61 #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
62 #TrivialReporter .suite .suite { margin: 5px; }
63 #TrivialReporter .suite.passed { background-color: #dfd; }
64 #TrivialReporter .suite.failed { background-color: #fdd; }
65 #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
66 #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
67 #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
68 #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
69 #TrivialReporter .spec.skipped { background-color: #bbb; }
70 #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
71 #TrivialReporter .passed { background-color: #cfc; display: none; }
72 #TrivialReporter .failed { background-color: #fbb; }
73 #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
74 #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
75 #TrivialReporter .resultMessage .mismatch { color: black; }
76 #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
77 #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
78 #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
79 #TrivialReporter #jasmine_content { position: fixed; right: 100%; }
80 #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
+0
-2529
spec/jasmine/jasmine.js less more
0 var isCommonJS = typeof window == "undefined";
1
2 /**
3 * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
4 *
5 * @namespace
6 */
7 var jasmine = {};
8 if (isCommonJS) exports.jasmine = jasmine;
9 /**
10 * @private
11 */
12 jasmine.unimplementedMethod_ = function() {
13 throw new Error("unimplemented method");
14 };
15
16 /**
17 * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
18 * a plain old variable and may be redefined by somebody else.
19 *
20 * @private
21 */
22 jasmine.undefined = jasmine.___undefined___;
23
24 /**
25 * Show diagnostic messages in the console if set to true
26 *
27 */
28 jasmine.VERBOSE = false;
29
30 /**
31 * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
32 *
33 */
34 jasmine.DEFAULT_UPDATE_INTERVAL = 250;
35
36 /**
37 * Default timeout interval in milliseconds for waitsFor() blocks.
38 */
39 jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
40
41 jasmine.getGlobal = function() {
42 function getGlobal() {
43 return this;
44 }
45
46 return getGlobal();
47 };
48
49 /**
50 * Allows for bound functions to be compared. Internal use only.
51 *
52 * @ignore
53 * @private
54 * @param base {Object} bound 'this' for the function
55 * @param name {Function} function to find
56 */
57 jasmine.bindOriginal_ = function(base, name) {
58 var original = base[name];
59 if (original.apply) {
60 return function() {
61 return original.apply(base, arguments);
62 };
63 } else {
64 // IE support
65 return jasmine.getGlobal()[name];
66 }
67 };
68
69 jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
70 jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
71 jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
72 jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
73
74 jasmine.MessageResult = function(values) {
75 this.type = 'log';
76 this.values = values;
77 this.trace = new Error(); // todo: test better
78 };
79
80 jasmine.MessageResult.prototype.toString = function() {
81 var text = "";
82 for (var i = 0; i < this.values.length; i++) {
83 if (i > 0) text += " ";
84 if (jasmine.isString_(this.values[i])) {
85 text += this.values[i];
86 } else {
87 text += jasmine.pp(this.values[i]);
88 }
89 }
90 return text;
91 };
92
93 jasmine.ExpectationResult = function(params) {
94 this.type = 'expect';
95 this.matcherName = params.matcherName;
96 this.passed_ = params.passed;
97 this.expected = params.expected;
98 this.actual = params.actual;
99 this.message = this.passed_ ? 'Passed.' : params.message;
100
101 var trace = (params.trace || new Error(this.message));
102 this.trace = this.passed_ ? '' : trace;
103 };
104
105 jasmine.ExpectationResult.prototype.toString = function () {
106 return this.message;
107 };
108
109 jasmine.ExpectationResult.prototype.passed = function () {
110 return this.passed_;
111 };
112
113 /**
114 * Getter for the Jasmine environment. Ensures one gets created
115 */
116 jasmine.getEnv = function() {
117 var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
118 return env;
119 };
120
121 /**
122 * @ignore
123 * @private
124 * @param value
125 * @returns {Boolean}
126 */
127 jasmine.isArray_ = function(value) {
128 return jasmine.isA_("Array", value);
129 };
130
131 /**
132 * @ignore
133 * @private
134 * @param value
135 * @returns {Boolean}
136 */
137 jasmine.isString_ = function(value) {
138 return jasmine.isA_("String", value);
139 };
140
141 /**
142 * @ignore
143 * @private
144 * @param value
145 * @returns {Boolean}
146 */
147 jasmine.isNumber_ = function(value) {
148 return jasmine.isA_("Number", value);
149 };
150
151 /**
152 * @ignore
153 * @private
154 * @param {String} typeName
155 * @param value
156 * @returns {Boolean}
157 */
158 jasmine.isA_ = function(typeName, value) {
159 return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
160 };
161
162 /**
163 * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
164 *
165 * @param value {Object} an object to be outputted
166 * @returns {String}
167 */
168 jasmine.pp = function(value) {
169 var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
170 stringPrettyPrinter.format(value);
171 return stringPrettyPrinter.string;
172 };
173
174 /**
175 * Returns true if the object is a DOM Node.
176 *
177 * @param {Object} obj object to check
178 * @returns {Boolean}
179 */
180 jasmine.isDomNode = function(obj) {
181 return obj.nodeType > 0;
182 };
183
184 /**
185 * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
186 *
187 * @example
188 * // don't care about which function is passed in, as long as it's a function
189 * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
190 *
191 * @param {Class} clazz
192 * @returns matchable object of the type clazz
193 */
194 jasmine.any = function(clazz) {
195 return new jasmine.Matchers.Any(clazz);
196 };
197
198 /**
199 * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
200 * attributes on the object.
201 *
202 * @example
203 * // don't care about any other attributes than foo.
204 * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
205 *
206 * @param sample {Object} sample
207 * @returns matchable object for the sample
208 */
209 jasmine.objectContaining = function (sample) {
210 return new jasmine.Matchers.ObjectContaining(sample);
211 };
212
213 /**
214 * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
215 *
216 * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
217 * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
218 *
219 * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
220 *
221 * Spies are torn down at the end of every spec.
222 *
223 * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
224 *
225 * @example
226 * // a stub
227 * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
228 *
229 * // spy example
230 * var foo = {
231 * not: function(bool) { return !bool; }
232 * }
233 *
234 * // actual foo.not will not be called, execution stops
235 * spyOn(foo, 'not');
236
237 // foo.not spied upon, execution will continue to implementation
238 * spyOn(foo, 'not').andCallThrough();
239 *
240 * // fake example
241 * var foo = {
242 * not: function(bool) { return !bool; }
243 * }
244 *
245 * // foo.not(val) will return val
246 * spyOn(foo, 'not').andCallFake(function(value) {return value;});
247 *
248 * // mock example
249 * foo.not(7 == 7);
250 * expect(foo.not).toHaveBeenCalled();
251 * expect(foo.not).toHaveBeenCalledWith(true);
252 *
253 * @constructor
254 * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
255 * @param {String} name
256 */
257 jasmine.Spy = function(name) {
258 /**
259 * The name of the spy, if provided.
260 */
261 this.identity = name || 'unknown';
262 /**
263 * Is this Object a spy?
264 */
265 this.isSpy = true;
266 /**
267 * The actual function this spy stubs.
268 */
269 this.plan = function() {
270 };
271 /**
272 * Tracking of the most recent call to the spy.
273 * @example
274 * var mySpy = jasmine.createSpy('foo');
275 * mySpy(1, 2);
276 * mySpy.mostRecentCall.args = [1, 2];
277 */
278 this.mostRecentCall = {};
279
280 /**
281 * Holds arguments for each call to the spy, indexed by call count
282 * @example
283 * var mySpy = jasmine.createSpy('foo');
284 * mySpy(1, 2);
285 * mySpy(7, 8);
286 * mySpy.mostRecentCall.args = [7, 8];
287 * mySpy.argsForCall[0] = [1, 2];
288 * mySpy.argsForCall[1] = [7, 8];
289 */
290 this.argsForCall = [];
291 this.calls = [];
292 };
293
294 /**
295 * Tells a spy to call through to the actual implemenatation.
296 *
297 * @example
298 * var foo = {
299 * bar: function() { // do some stuff }
300 * }
301 *
302 * // defining a spy on an existing property: foo.bar
303 * spyOn(foo, 'bar').andCallThrough();
304 */
305 jasmine.Spy.prototype.andCallThrough = function() {
306 this.plan = this.originalValue;
307 return this;
308 };
309
310 /**
311 * For setting the return value of a spy.
312 *
313 * @example
314 * // defining a spy from scratch: foo() returns 'baz'
315 * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
316 *
317 * // defining a spy on an existing property: foo.bar() returns 'baz'
318 * spyOn(foo, 'bar').andReturn('baz');
319 *
320 * @param {Object} value
321 */
322 jasmine.Spy.prototype.andReturn = function(value) {
323 this.plan = function() {
324 return value;
325 };
326 return this;
327 };
328
329 /**
330 * For throwing an exception when a spy is called.
331 *
332 * @example
333 * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
334 * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
335 *
336 * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
337 * spyOn(foo, 'bar').andThrow('baz');
338 *
339 * @param {String} exceptionMsg
340 */
341 jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
342 this.plan = function() {
343 throw exceptionMsg;
344 };
345 return this;
346 };
347
348 /**
349 * Calls an alternate implementation when a spy is called.
350 *
351 * @example
352 * var baz = function() {
353 * // do some stuff, return something
354 * }
355 * // defining a spy from scratch: foo() calls the function baz
356 * var foo = jasmine.createSpy('spy on foo').andCall(baz);
357 *
358 * // defining a spy on an existing property: foo.bar() calls an anonymnous function
359 * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
360 *
361 * @param {Function} fakeFunc
362 */
363 jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
364 this.plan = fakeFunc;
365 return this;
366 };
367
368 /**
369 * Resets all of a spy's the tracking variables so that it can be used again.
370 *
371 * @example
372 * spyOn(foo, 'bar');
373 *
374 * foo.bar();
375 *
376 * expect(foo.bar.callCount).toEqual(1);
377 *
378 * foo.bar.reset();
379 *
380 * expect(foo.bar.callCount).toEqual(0);
381 */
382 jasmine.Spy.prototype.reset = function() {
383 this.wasCalled = false;
384 this.callCount = 0;
385 this.argsForCall = [];
386 this.calls = [];
387 this.mostRecentCall = {};
388 };
389
390 jasmine.createSpy = function(name) {
391
392 var spyObj = function() {
393 spyObj.wasCalled = true;
394 spyObj.callCount++;
395 var args = jasmine.util.argsToArray(arguments);
396 spyObj.mostRecentCall.object = this;
397 spyObj.mostRecentCall.args = args;
398 spyObj.argsForCall.push(args);
399 spyObj.calls.push({object: this, args: args});
400 return spyObj.plan.apply(this, arguments);
401 };
402
403 var spy = new jasmine.Spy(name);
404
405 for (var prop in spy) {
406 spyObj[prop] = spy[prop];
407 }
408
409 spyObj.reset();
410
411 return spyObj;
412 };
413
414 /**
415 * Determines whether an object is a spy.
416 *
417 * @param {jasmine.Spy|Object} putativeSpy
418 * @returns {Boolean}
419 */
420 jasmine.isSpy = function(putativeSpy) {
421 return putativeSpy && putativeSpy.isSpy;
422 };
423
424 /**
425 * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
426 * large in one call.
427 *
428 * @param {String} baseName name of spy class
429 * @param {Array} methodNames array of names of methods to make spies
430 */
431 jasmine.createSpyObj = function(baseName, methodNames) {
432 if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
433 throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
434 }
435 var obj = {};
436 for (var i = 0; i < methodNames.length; i++) {
437 obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
438 }
439 return obj;
440 };
441
442 /**
443 * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
444 *
445 * Be careful not to leave calls to <code>jasmine.log</code> in production code.
446 */
447 jasmine.log = function() {
448 var spec = jasmine.getEnv().currentSpec;
449 spec.log.apply(spec, arguments);
450 };
451
452 /**
453 * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
454 *
455 * @example
456 * // spy example
457 * var foo = {
458 * not: function(bool) { return !bool; }
459 * }
460 * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
461 *
462 * @see jasmine.createSpy
463 * @param obj
464 * @param methodName
465 * @returns a Jasmine spy that can be chained with all spy methods
466 */
467 var spyOn = function(obj, methodName) {
468 return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
469 };
470 if (isCommonJS) exports.spyOn = spyOn;
471
472 /**
473 * Creates a Jasmine spec that will be added to the current suite.
474 *
475 * // TODO: pending tests
476 *
477 * @example
478 * it('should be true', function() {
479 * expect(true).toEqual(true);
480 * });
481 *
482 * @param {String} desc description of this specification
483 * @param {Function} func defines the preconditions and expectations of the spec
484 */
485 var it = function(desc, func) {
486 return jasmine.getEnv().it(desc, func);
487 };
488 if (isCommonJS) exports.it = it;
489
490 /**
491 * Creates a <em>disabled</em> Jasmine spec.
492 *
493 * A convenience method that allows existing specs to be disabled temporarily during development.
494 *
495 * @param {String} desc description of this specification
496 * @param {Function} func defines the preconditions and expectations of the spec
497 */
498 var xit = function(desc, func) {
499 return jasmine.getEnv().xit(desc, func);
500 };
501 if (isCommonJS) exports.xit = xit;
502
503 /**
504 * Starts a chain for a Jasmine expectation.
505 *
506 * It is passed an Object that is the actual value and should chain to one of the many
507 * jasmine.Matchers functions.
508 *
509 * @param {Object} actual Actual value to test against and expected value
510 */
511 var expect = function(actual) {
512 return jasmine.getEnv().currentSpec.expect(actual);
513 };
514 if (isCommonJS) exports.expect = expect;
515
516 /**
517 * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
518 *
519 * @param {Function} func Function that defines part of a jasmine spec.
520 */
521 var runs = function(func) {
522 jasmine.getEnv().currentSpec.runs(func);
523 };
524 if (isCommonJS) exports.runs = runs;
525
526 /**
527 * Waits a fixed time period before moving to the next block.
528 *
529 * @deprecated Use waitsFor() instead
530 * @param {Number} timeout milliseconds to wait
531 */
532 var waits = function(timeout) {
533 jasmine.getEnv().currentSpec.waits(timeout);
534 };
535 if (isCommonJS) exports.waits = waits;
536
537 /**
538 * Waits for the latchFunction to return true before proceeding to the next block.
539 *
540 * @param {Function} latchFunction
541 * @param {String} optional_timeoutMessage
542 * @param {Number} optional_timeout
543 */
544 var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
545 jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
546 };
547 if (isCommonJS) exports.waitsFor = waitsFor;
548
549 /**
550 * A function that is called before each spec in a suite.
551 *
552 * Used for spec setup, including validating assumptions.
553 *
554 * @param {Function} beforeEachFunction
555 */
556 var beforeEach = function(beforeEachFunction) {
557 jasmine.getEnv().beforeEach(beforeEachFunction);
558 };
559 if (isCommonJS) exports.beforeEach = beforeEach;
560
561 /**
562 * A function that is called after each spec in a suite.
563 *
564 * Used for restoring any state that is hijacked during spec execution.
565 *
566 * @param {Function} afterEachFunction
567 */
568 var afterEach = function(afterEachFunction) {
569 jasmine.getEnv().afterEach(afterEachFunction);
570 };
571 if (isCommonJS) exports.afterEach = afterEach;
572
573 /**
574 * Defines a suite of specifications.
575 *
576 * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
577 * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
578 * of setup in some tests.
579 *
580 * @example
581 * // TODO: a simple suite
582 *
583 * // TODO: a simple suite with a nested describe block
584 *
585 * @param {String} description A string, usually the class under test.
586 * @param {Function} specDefinitions function that defines several specs.
587 */
588 var describe = function(description, specDefinitions) {
589 return jasmine.getEnv().describe(description, specDefinitions);
590 };
591 if (isCommonJS) exports.describe = describe;
592
593 /**
594 * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
595 *
596 * @param {String} description A string, usually the class under test.
597 * @param {Function} specDefinitions function that defines several specs.
598 */
599 var xdescribe = function(description, specDefinitions) {
600 return jasmine.getEnv().xdescribe(description, specDefinitions);
601 };
602 if (isCommonJS) exports.xdescribe = xdescribe;
603
604
605 // Provide the XMLHttpRequest class for IE 5.x-6.x:
606 jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
607 function tryIt(f) {
608 try {
609 return f();
610 } catch(e) {
611 }
612 return null;
613 }
614
615 var xhr = tryIt(function() {
616 return new ActiveXObject("Msxml2.XMLHTTP.6.0");
617 }) ||
618 tryIt(function() {
619 return new ActiveXObject("Msxml2.XMLHTTP.3.0");
620 }) ||
621 tryIt(function() {
622 return new ActiveXObject("Msxml2.XMLHTTP");
623 }) ||
624 tryIt(function() {
625 return new ActiveXObject("Microsoft.XMLHTTP");
626 });
627
628 if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
629
630 return xhr;
631 } : XMLHttpRequest;
632 /**
633 * @namespace
634 */
635 jasmine.util = {};
636
637 /**
638 * Declare that a child class inherit it's prototype from the parent class.
639 *
640 * @private
641 * @param {Function} childClass
642 * @param {Function} parentClass
643 */
644 jasmine.util.inherit = function(childClass, parentClass) {
645 /**
646 * @private
647 */
648 var subclass = function() {
649 };
650 subclass.prototype = parentClass.prototype;
651 childClass.prototype = new subclass();
652 };
653
654 jasmine.util.formatException = function(e) {
655 var lineNumber;
656 if (e.line) {
657 lineNumber = e.line;
658 }
659 else if (e.lineNumber) {
660 lineNumber = e.lineNumber;
661 }
662
663 var file;
664
665 if (e.sourceURL) {
666 file = e.sourceURL;
667 }
668 else if (e.fileName) {
669 file = e.fileName;
670 }
671
672 var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
673
674 if (file && lineNumber) {
675 message += ' in ' + file + ' (line ' + lineNumber + ')';
676 }
677
678 return message;
679 };
680
681 jasmine.util.htmlEscape = function(str) {
682 if (!str) return str;
683 return str.replace(/&/g, '&amp;')
684 .replace(/</g, '&lt;')
685 .replace(/>/g, '&gt;');
686 };
687
688 jasmine.util.argsToArray = function(args) {
689 var arrayOfArgs = [];
690 for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
691 return arrayOfArgs;
692 };
693
694 jasmine.util.extend = function(destination, source) {
695 for (var property in source) destination[property] = source[property];
696 return destination;
697 };
698
699 /**
700 * Environment for Jasmine
701 *
702 * @constructor
703 */
704 jasmine.Env = function() {
705 this.currentSpec = null;
706 this.currentSuite = null;
707 this.currentRunner_ = new jasmine.Runner(this);
708
709 this.reporter = new jasmine.MultiReporter();
710
711 this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
712 this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
713 this.lastUpdate = 0;
714 this.specFilter = function() {
715 return true;
716 };
717
718 this.nextSpecId_ = 0;
719 this.nextSuiteId_ = 0;
720 this.equalityTesters_ = [];
721
722 // wrap matchers
723 this.matchersClass = function() {
724 jasmine.Matchers.apply(this, arguments);
725 };
726 jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
727
728 jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
729 };
730
731
732 jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
733 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
734 jasmine.Env.prototype.setInterval = jasmine.setInterval;
735 jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
736
737 /**
738 * @returns an object containing jasmine version build info, if set.
739 */
740 jasmine.Env.prototype.version = function () {
741 if (jasmine.version_) {
742 return jasmine.version_;
743 } else {
744 throw new Error('Version not set');
745 }
746 };
747
748 /**
749 * @returns string containing jasmine version build info, if set.
750 */
751 jasmine.Env.prototype.versionString = function() {
752 if (!jasmine.version_) {
753 return "version unknown";
754 }
755
756 var version = this.version();
757 var versionString = version.major + "." + version.minor + "." + version.build;
758 if (version.release_candidate) {
759 versionString += ".rc" + version.release_candidate;
760 }
761 versionString += " revision " + version.revision;
762 return versionString;
763 };
764
765 /**
766 * @returns a sequential integer starting at 0
767 */
768 jasmine.Env.prototype.nextSpecId = function () {
769 return this.nextSpecId_++;
770 };
771
772 /**
773 * @returns a sequential integer starting at 0
774 */
775 jasmine.Env.prototype.nextSuiteId = function () {
776 return this.nextSuiteId_++;
777 };
778
779 /**
780 * Register a reporter to receive status updates from Jasmine.
781 * @param {jasmine.Reporter} reporter An object which will receive status updates.
782 */
783 jasmine.Env.prototype.addReporter = function(reporter) {
784 this.reporter.addReporter(reporter);
785 };
786
787 jasmine.Env.prototype.execute = function() {
788 this.currentRunner_.execute();
789 };
790
791 jasmine.Env.prototype.describe = function(description, specDefinitions) {
792 var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
793
794 var parentSuite = this.currentSuite;
795 if (parentSuite) {
796 parentSuite.add(suite);
797 } else {
798 this.currentRunner_.add(suite);
799 }
800
801 this.currentSuite = suite;
802
803 var declarationError = null;
804 try {
805 specDefinitions.call(suite);
806 } catch(e) {
807 declarationError = e;
808 }
809
810 if (declarationError) {
811 this.it("encountered a declaration exception", function() {
812 throw declarationError;
813 });
814 }
815
816 this.currentSuite = parentSuite;
817
818 return suite;
819 };
820
821 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
822 if (this.currentSuite) {
823 this.currentSuite.beforeEach(beforeEachFunction);
824 } else {
825 this.currentRunner_.beforeEach(beforeEachFunction);
826 }
827 };
828
829 jasmine.Env.prototype.currentRunner = function () {
830 return this.currentRunner_;
831 };
832
833 jasmine.Env.prototype.afterEach = function(afterEachFunction) {
834 if (this.currentSuite) {
835 this.currentSuite.afterEach(afterEachFunction);
836 } else {
837 this.currentRunner_.afterEach(afterEachFunction);
838 }
839
840 };
841
842 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
843 return {
844 execute: function() {
845 }
846 };
847 };
848
849 jasmine.Env.prototype.it = function(description, func) {
850 var spec = new jasmine.Spec(this, this.currentSuite, description);
851 this.currentSuite.add(spec);
852 this.currentSpec = spec;
853
854 if (func) {
855 spec.runs(func);
856 }
857
858 return spec;
859 };
860
861 jasmine.Env.prototype.xit = function(desc, func) {
862 return {
863 id: this.nextSpecId(),
864 runs: function() {
865 }
866 };
867 };
868
869 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
870 if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
871 return true;
872 }
873
874 a.__Jasmine_been_here_before__ = b;
875 b.__Jasmine_been_here_before__ = a;
876
877 var hasKey = function(obj, keyName) {
878 return obj !== null && obj[keyName] !== jasmine.undefined;
879 };
880
881 for (var property in b) {
882 if (!hasKey(a, property) && hasKey(b, property)) {
883 mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
884 }
885 }
886 for (property in a) {
887 if (!hasKey(b, property) && hasKey(a, property)) {
888 mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
889 }
890 }
891 for (property in b) {
892 if (property == '__Jasmine_been_here_before__') continue;
893 if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
894 mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
895 }
896 }
897
898 if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
899 mismatchValues.push("arrays were not the same length");
900 }
901
902 delete a.__Jasmine_been_here_before__;
903 delete b.__Jasmine_been_here_before__;
904 return (mismatchKeys.length === 0 && mismatchValues.length === 0);
905 };
906
907 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
908 mismatchKeys = mismatchKeys || [];
909 mismatchValues = mismatchValues || [];
910
911 for (var i = 0; i < this.equalityTesters_.length; i++) {
912 var equalityTester = this.equalityTesters_[i];
913 var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
914 if (result !== jasmine.undefined) return result;
915 }
916
917 if (a === b) return true;
918
919 if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
920 return (a == jasmine.undefined && b == jasmine.undefined);
921 }
922
923 if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
924 return a === b;
925 }
926
927 if (a instanceof Date && b instanceof Date) {
928 return a.getTime() == b.getTime();
929 }
930
931 if (a.jasmineMatches) {
932 return a.jasmineMatches(b);
933 }
934
935 if (b.jasmineMatches) {
936 return b.jasmineMatches(a);
937 }
938
939 if (a instanceof jasmine.Matchers.ObjectContaining) {
940 return a.matches(b);
941 }
942
943 if (b instanceof jasmine.Matchers.ObjectContaining) {
944 return b.matches(a);
945 }
946
947 if (jasmine.isString_(a) && jasmine.isString_(b)) {
948 return (a == b);
949 }
950
951 if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
952 return (a == b);
953 }
954
955 if (typeof a === "object" && typeof b === "object") {
956 return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
957 }
958
959 //Straight check
960 return (a === b);
961 };
962
963 jasmine.Env.prototype.contains_ = function(haystack, needle) {
964 if (jasmine.isArray_(haystack)) {
965 for (var i = 0; i < haystack.length; i++) {
966 if (this.equals_(haystack[i], needle)) return true;
967 }
968 return false;
969 }
970 return haystack.indexOf(needle) >= 0;
971 };
972
973 jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
974 this.equalityTesters_.push(equalityTester);
975 };
976 /** No-op base class for Jasmine reporters.
977 *
978 * @constructor
979 */
980 jasmine.Reporter = function() {
981 };
982
983 //noinspection JSUnusedLocalSymbols
984 jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
985 };
986
987 //noinspection JSUnusedLocalSymbols
988 jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
989 };
990
991 //noinspection JSUnusedLocalSymbols
992 jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
993 };
994
995 //noinspection JSUnusedLocalSymbols
996 jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
997 };
998
999 //noinspection JSUnusedLocalSymbols
1000 jasmine.Reporter.prototype.reportSpecResults = function(spec) {
1001 };
1002
1003 //noinspection JSUnusedLocalSymbols
1004 jasmine.Reporter.prototype.log = function(str) {
1005 };
1006
1007 /**
1008 * Blocks are functions with executable code that make up a spec.
1009 *
1010 * @constructor
1011 * @param {jasmine.Env} env
1012 * @param {Function} func
1013 * @param {jasmine.Spec} spec
1014 */
1015 jasmine.Block = function(env, func, spec) {
1016 this.env = env;
1017 this.func = func;
1018 this.spec = spec;
1019 };
1020
1021 jasmine.Block.prototype.execute = function(onComplete) {
1022 try {
1023 this.func.apply(this.spec);
1024 } catch (e) {
1025 this.spec.fail(e);
1026 }
1027 onComplete();
1028 };
1029 /** JavaScript API reporter.
1030 *
1031 * @constructor
1032 */
1033 jasmine.JsApiReporter = function() {
1034 this.started = false;
1035 this.finished = false;
1036 this.suites_ = [];
1037 this.results_ = {};
1038 };
1039
1040 jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
1041 this.started = true;
1042 var suites = runner.topLevelSuites();
1043 for (var i = 0; i < suites.length; i++) {
1044 var suite = suites[i];
1045 this.suites_.push(this.summarize_(suite));
1046 }
1047 };
1048
1049 jasmine.JsApiReporter.prototype.suites = function() {
1050 return this.suites_;
1051 };
1052
1053 jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
1054 var isSuite = suiteOrSpec instanceof jasmine.Suite;
1055 var summary = {
1056 id: suiteOrSpec.id,
1057 name: suiteOrSpec.description,
1058 type: isSuite ? 'suite' : 'spec',
1059 children: []
1060 };
1061
1062 if (isSuite) {
1063 var children = suiteOrSpec.children();
1064 for (var i = 0; i < children.length; i++) {
1065 summary.children.push(this.summarize_(children[i]));
1066 }
1067 }
1068 return summary;
1069 };
1070
1071 jasmine.JsApiReporter.prototype.results = function() {
1072 return this.results_;
1073 };
1074
1075 jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
1076 return this.results_[specId];
1077 };
1078
1079 //noinspection JSUnusedLocalSymbols
1080 jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
1081 this.finished = true;
1082 };
1083
1084 //noinspection JSUnusedLocalSymbols
1085 jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
1086 };
1087
1088 //noinspection JSUnusedLocalSymbols
1089 jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
1090 this.results_[spec.id] = {
1091 messages: spec.results().getItems(),
1092 result: spec.results().failedCount > 0 ? "failed" : "passed"
1093 };
1094 };
1095
1096 //noinspection JSUnusedLocalSymbols
1097 jasmine.JsApiReporter.prototype.log = function(str) {
1098 };
1099
1100 jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
1101 var results = {};
1102 for (var i = 0; i < specIds.length; i++) {
1103 var specId = specIds[i];
1104 results[specId] = this.summarizeResult_(this.results_[specId]);
1105 }
1106 return results;
1107 };
1108
1109 jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
1110 var summaryMessages = [];
1111 var messagesLength = result.messages.length;
1112 for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
1113 var resultMessage = result.messages[messageIndex];
1114 summaryMessages.push({
1115 text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
1116 passed: resultMessage.passed ? resultMessage.passed() : true,
1117 type: resultMessage.type,
1118 message: resultMessage.message,
1119 trace: {
1120 stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
1121 }
1122 });
1123 }
1124
1125 return {
1126 result : result.result,
1127 messages : summaryMessages
1128 };
1129 };
1130
1131 /**
1132 * @constructor
1133 * @param {jasmine.Env} env
1134 * @param actual
1135 * @param {jasmine.Spec} spec
1136 */
1137 jasmine.Matchers = function(env, actual, spec, opt_isNot) {
1138 this.env = env;
1139 this.actual = actual;
1140 this.spec = spec;
1141 this.isNot = opt_isNot || false;
1142 this.reportWasCalled_ = false;
1143 };
1144
1145 // todo: @deprecated as of Jasmine 0.11, remove soon [xw]
1146 jasmine.Matchers.pp = function(str) {
1147 throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
1148 };
1149
1150 // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
1151 jasmine.Matchers.prototype.report = function(result, failing_message, details) {
1152 throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
1153 };
1154
1155 jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
1156 for (var methodName in prototype) {
1157 if (methodName == 'report') continue;
1158 var orig = prototype[methodName];
1159 matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
1160 }
1161 };
1162
1163 jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
1164 return function() {
1165 var matcherArgs = jasmine.util.argsToArray(arguments);
1166 var result = matcherFunction.apply(this, arguments);
1167
1168 if (this.isNot) {
1169 result = !result;
1170 }
1171
1172 if (this.reportWasCalled_) return result;
1173
1174 var message;
1175 if (!result) {
1176 if (this.message) {
1177 message = this.message.apply(this, arguments);
1178 if (jasmine.isArray_(message)) {
1179 message = message[this.isNot ? 1 : 0];
1180 }
1181 } else {
1182 var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1183 message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
1184 if (matcherArgs.length > 0) {
1185 for (var i = 0; i < matcherArgs.length; i++) {
1186 if (i > 0) message += ",";
1187 message += " " + jasmine.pp(matcherArgs[i]);
1188 }
1189 }
1190 message += ".";
1191 }
1192 }
1193 var expectationResult = new jasmine.ExpectationResult({
1194 matcherName: matcherName,
1195 passed: result,
1196 expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
1197 actual: this.actual,
1198 message: message
1199 });
1200 this.spec.addMatcherResult(expectationResult);
1201 return jasmine.undefined;
1202 };
1203 };
1204
1205
1206
1207
1208 /**
1209 * toBe: compares the actual to the expected using ===
1210 * @param expected
1211 */
1212 jasmine.Matchers.prototype.toBe = function(expected) {
1213 return this.actual === expected;
1214 };
1215
1216 /**
1217 * toNotBe: compares the actual to the expected using !==
1218 * @param expected
1219 * @deprecated as of 1.0. Use not.toBe() instead.
1220 */
1221 jasmine.Matchers.prototype.toNotBe = function(expected) {
1222 return this.actual !== expected;
1223 };
1224
1225 /**
1226 * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
1227 *
1228 * @param expected
1229 */
1230 jasmine.Matchers.prototype.toEqual = function(expected) {
1231 return this.env.equals_(this.actual, expected);
1232 };
1233
1234 /**
1235 * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
1236 * @param expected
1237 * @deprecated as of 1.0. Use not.toEqual() instead.
1238 */
1239 jasmine.Matchers.prototype.toNotEqual = function(expected) {
1240 return !this.env.equals_(this.actual, expected);
1241 };
1242
1243 /**
1244 * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
1245 * a pattern or a String.
1246 *
1247 * @param expected
1248 */
1249 jasmine.Matchers.prototype.toMatch = function(expected) {
1250 return new RegExp(expected).test(this.actual);
1251 };
1252
1253 /**
1254 * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
1255 * @param expected
1256 * @deprecated as of 1.0. Use not.toMatch() instead.
1257 */
1258 jasmine.Matchers.prototype.toNotMatch = function(expected) {
1259 return !(new RegExp(expected).test(this.actual));
1260 };
1261
1262 /**
1263 * Matcher that compares the actual to jasmine.undefined.
1264 */
1265 jasmine.Matchers.prototype.toBeDefined = function() {
1266 return (this.actual !== jasmine.undefined);
1267 };
1268
1269 /**
1270 * Matcher that compares the actual to jasmine.undefined.
1271 */
1272 jasmine.Matchers.prototype.toBeUndefined = function() {
1273 return (this.actual === jasmine.undefined);
1274 };
1275
1276 /**
1277 * Matcher that compares the actual to null.
1278 */
1279 jasmine.Matchers.prototype.toBeNull = function() {
1280 return (this.actual === null);
1281 };
1282
1283 /**
1284 * Matcher that boolean not-nots the actual.
1285 */
1286 jasmine.Matchers.prototype.toBeTruthy = function() {
1287 return !!this.actual;
1288 };
1289
1290
1291 /**
1292 * Matcher that boolean nots the actual.
1293 */
1294 jasmine.Matchers.prototype.toBeFalsy = function() {
1295 return !this.actual;
1296 };
1297
1298
1299 /**
1300 * Matcher that checks to see if the actual, a Jasmine spy, was called.
1301 */
1302 jasmine.Matchers.prototype.toHaveBeenCalled = function() {
1303 if (arguments.length > 0) {
1304 throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
1305 }
1306
1307 if (!jasmine.isSpy(this.actual)) {
1308 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1309 }
1310
1311 this.message = function() {
1312 return [
1313 "Expected spy " + this.actual.identity + " to have been called.",
1314 "Expected spy " + this.actual.identity + " not to have been called."
1315 ];
1316 };
1317
1318 return this.actual.wasCalled;
1319 };
1320
1321 /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
1322 jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
1323
1324 /**
1325 * Matcher that checks to see if the actual, a Jasmine spy, was not called.
1326 *
1327 * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
1328 */
1329 jasmine.Matchers.prototype.wasNotCalled = function() {
1330 if (arguments.length > 0) {
1331 throw new Error('wasNotCalled does not take arguments');
1332 }
1333
1334 if (!jasmine.isSpy(this.actual)) {
1335 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1336 }
1337
1338 this.message = function() {
1339 return [
1340 "Expected spy " + this.actual.identity + " to not have been called.",
1341 "Expected spy " + this.actual.identity + " to have been called."
1342 ];
1343 };
1344
1345 return !this.actual.wasCalled;
1346 };
1347
1348 /**
1349 * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
1350 *
1351 * @example
1352 *
1353 */
1354 jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
1355 var expectedArgs = jasmine.util.argsToArray(arguments);
1356 if (!jasmine.isSpy(this.actual)) {
1357 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1358 }
1359 this.message = function() {
1360 if (this.actual.callCount === 0) {
1361 // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
1362 return [
1363 "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
1364 "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
1365 ];
1366 } else {
1367 return [
1368 "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
1369 "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
1370 ];
1371 }
1372 };
1373
1374 return this.env.contains_(this.actual.argsForCall, expectedArgs);
1375 };
1376
1377 /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
1378 jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
1379
1380 /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
1381 jasmine.Matchers.prototype.wasNotCalledWith = function() {
1382 var expectedArgs = jasmine.util.argsToArray(arguments);
1383 if (!jasmine.isSpy(this.actual)) {
1384 throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1385 }
1386
1387 this.message = function() {
1388 return [
1389 "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
1390 "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
1391 ];
1392 };
1393
1394 return !this.env.contains_(this.actual.argsForCall, expectedArgs);
1395 };
1396
1397 /**
1398 * Matcher that checks that the expected item is an element in the actual Array.
1399 *
1400 * @param {Object} expected
1401 */
1402 jasmine.Matchers.prototype.toContain = function(expected) {
1403 return this.env.contains_(this.actual, expected);
1404 };
1405
1406 /**
1407 * Matcher that checks that the expected item is NOT an element in the actual Array.
1408 *
1409 * @param {Object} expected
1410 * @deprecated as of 1.0. Use not.toContain() instead.
1411 */
1412 jasmine.Matchers.prototype.toNotContain = function(expected) {
1413 return !this.env.contains_(this.actual, expected);
1414 };
1415
1416 jasmine.Matchers.prototype.toBeLessThan = function(expected) {
1417 return this.actual < expected;
1418 };
1419
1420 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
1421 return this.actual > expected;
1422 };
1423
1424 /**
1425 * Matcher that checks that the expected item is equal to the actual item
1426 * up to a given level of decimal precision (default 2).
1427 *
1428 * @param {Number} expected
1429 * @param {Number} precision
1430 */
1431 jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
1432 if (!(precision === 0)) {
1433 precision = precision || 2;
1434 }
1435 var multiplier = Math.pow(10, precision);
1436 var actual = Math.round(this.actual * multiplier);
1437 expected = Math.round(expected * multiplier);
1438 return expected == actual;
1439 };
1440
1441 /**
1442 * Matcher that checks that the expected exception was thrown by the actual.
1443 *
1444 * @param {String} expected
1445 */
1446 jasmine.Matchers.prototype.toThrow = function(expected) {
1447 var result = false;
1448 var exception;
1449 if (typeof this.actual != 'function') {
1450 throw new Error('Actual is not a function');
1451 }
1452 try {
1453 this.actual();
1454 } catch (e) {
1455 exception = e;
1456 }
1457 if (exception) {
1458 result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
1459 }
1460
1461 var not = this.isNot ? "not " : "";
1462
1463 this.message = function() {
1464 if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
1465 return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
1466 } else {
1467 return "Expected function to throw an exception.";
1468 }
1469 };
1470
1471 return result;
1472 };
1473
1474 jasmine.Matchers.Any = function(expectedClass) {
1475 this.expectedClass = expectedClass;
1476 };
1477
1478 jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
1479 if (this.expectedClass == String) {
1480 return typeof other == 'string' || other instanceof String;
1481 }
1482
1483 if (this.expectedClass == Number) {
1484 return typeof other == 'number' || other instanceof Number;
1485 }
1486
1487 if (this.expectedClass == Function) {
1488 return typeof other == 'function' || other instanceof Function;
1489 }
1490
1491 if (this.expectedClass == Object) {
1492 return typeof other == 'object';
1493 }
1494
1495 return other instanceof this.expectedClass;
1496 };
1497
1498 jasmine.Matchers.Any.prototype.jasmineToString = function() {
1499 return '<jasmine.any(' + this.expectedClass + ')>';
1500 };
1501
1502 jasmine.Matchers.ObjectContaining = function (sample) {
1503 this.sample = sample;
1504 };
1505
1506 jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
1507 mismatchKeys = mismatchKeys || [];
1508 mismatchValues = mismatchValues || [];
1509
1510 var env = jasmine.getEnv();
1511
1512 var hasKey = function(obj, keyName) {
1513 return obj != null && obj[keyName] !== jasmine.undefined;
1514 };
1515
1516 for (var property in this.sample) {
1517 if (!hasKey(other, property) && hasKey(this.sample, property)) {
1518 mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
1519 }
1520 else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
1521 mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
1522 }
1523 }
1524
1525 return (mismatchKeys.length === 0 && mismatchValues.length === 0);
1526 };
1527
1528 jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
1529 return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
1530 };
1531 // Mock setTimeout, clearTimeout
1532 // Contributed by Pivotal Computer Systems, www.pivotalsf.com
1533
1534 jasmine.FakeTimer = function() {
1535 this.reset();
1536
1537 var self = this;
1538 self.setTimeout = function(funcToCall, millis) {
1539 self.timeoutsMade++;
1540 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
1541 return self.timeoutsMade;
1542 };
1543
1544 self.setInterval = function(funcToCall, millis) {
1545 self.timeoutsMade++;
1546 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
1547 return self.timeoutsMade;
1548 };
1549
1550 self.clearTimeout = function(timeoutKey) {
1551 self.scheduledFunctions[timeoutKey] = jasmine.undefined;
1552 };
1553
1554 self.clearInterval = function(timeoutKey) {
1555 self.scheduledFunctions[timeoutKey] = jasmine.undefined;
1556 };
1557
1558 };
1559
1560 jasmine.FakeTimer.prototype.reset = function() {
1561 this.timeoutsMade = 0;
1562 this.scheduledFunctions = {};
1563 this.nowMillis = 0;
1564 };
1565
1566 jasmine.FakeTimer.prototype.tick = function(millis) {
1567 var oldMillis = this.nowMillis;
1568 var newMillis = oldMillis + millis;
1569 this.runFunctionsWithinRange(oldMillis, newMillis);
1570 this.nowMillis = newMillis;
1571 };
1572
1573 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
1574 var scheduledFunc;
1575 var funcsToRun = [];
1576 for (var timeoutKey in this.scheduledFunctions) {
1577 scheduledFunc = this.scheduledFunctions[timeoutKey];
1578 if (scheduledFunc != jasmine.undefined &&
1579 scheduledFunc.runAtMillis >= oldMillis &&
1580 scheduledFunc.runAtMillis <= nowMillis) {
1581 funcsToRun.push(scheduledFunc);
1582 this.scheduledFunctions[timeoutKey] = jasmine.undefined;
1583 }
1584 }
1585
1586 if (funcsToRun.length > 0) {
1587 funcsToRun.sort(function(a, b) {
1588 return a.runAtMillis - b.runAtMillis;
1589 });
1590 for (var i = 0; i < funcsToRun.length; ++i) {
1591 try {
1592 var funcToRun = funcsToRun[i];
1593 this.nowMillis = funcToRun.runAtMillis;
1594 funcToRun.funcToCall();
1595 if (funcToRun.recurring) {
1596 this.scheduleFunction(funcToRun.timeoutKey,
1597 funcToRun.funcToCall,
1598 funcToRun.millis,
1599 true);
1600 }
1601 } catch(e) {
1602 }
1603 }
1604 this.runFunctionsWithinRange(oldMillis, nowMillis);
1605 }
1606 };
1607
1608 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
1609 this.scheduledFunctions[timeoutKey] = {
1610 runAtMillis: this.nowMillis + millis,
1611 funcToCall: funcToCall,
1612 recurring: recurring,
1613 timeoutKey: timeoutKey,
1614 millis: millis
1615 };
1616 };
1617
1618 /**
1619 * @namespace
1620 */
1621 jasmine.Clock = {
1622 defaultFakeTimer: new jasmine.FakeTimer(),
1623
1624 reset: function() {
1625 jasmine.Clock.assertInstalled();
1626 jasmine.Clock.defaultFakeTimer.reset();
1627 },
1628
1629 tick: function(millis) {
1630 jasmine.Clock.assertInstalled();
1631 jasmine.Clock.defaultFakeTimer.tick(millis);
1632 },
1633
1634 runFunctionsWithinRange: function(oldMillis, nowMillis) {
1635 jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
1636 },
1637
1638 scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
1639 jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
1640 },
1641
1642 useMock: function() {
1643 if (!jasmine.Clock.isInstalled()) {
1644 var spec = jasmine.getEnv().currentSpec;
1645 spec.after(jasmine.Clock.uninstallMock);
1646
1647 jasmine.Clock.installMock();
1648 }
1649 },
1650
1651 installMock: function() {
1652 jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
1653 },
1654
1655 uninstallMock: function() {
1656 jasmine.Clock.assertInstalled();
1657 jasmine.Clock.installed = jasmine.Clock.real;
1658 },
1659
1660 real: {
1661 setTimeout: jasmine.getGlobal().setTimeout,
1662 clearTimeout: jasmine.getGlobal().clearTimeout,
1663 setInterval: jasmine.getGlobal().setInterval,
1664 clearInterval: jasmine.getGlobal().clearInterval
1665 },
1666
1667 assertInstalled: function() {
1668 if (!jasmine.Clock.isInstalled()) {
1669 throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
1670 }
1671 },
1672
1673 isInstalled: function() {
1674 return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
1675 },
1676
1677 installed: null
1678 };
1679 jasmine.Clock.installed = jasmine.Clock.real;
1680
1681 //else for IE support
1682 jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
1683 if (jasmine.Clock.installed.setTimeout.apply) {
1684 return jasmine.Clock.installed.setTimeout.apply(this, arguments);
1685 } else {
1686 return jasmine.Clock.installed.setTimeout(funcToCall, millis);
1687 }
1688 };
1689
1690 jasmine.getGlobal().setInterval = function(funcToCall, millis) {
1691 if (jasmine.Clock.installed.setInterval.apply) {
1692 return jasmine.Clock.installed.setInterval.apply(this, arguments);
1693 } else {
1694 return jasmine.Clock.installed.setInterval(funcToCall, millis);
1695 }
1696 };
1697
1698 jasmine.getGlobal().clearTimeout = function(timeoutKey) {
1699 if (jasmine.Clock.installed.clearTimeout.apply) {
1700 return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
1701 } else {
1702 return jasmine.Clock.installed.clearTimeout(timeoutKey);
1703 }
1704 };
1705
1706 jasmine.getGlobal().clearInterval = function(timeoutKey) {
1707 if (jasmine.Clock.installed.clearTimeout.apply) {
1708 return jasmine.Clock.installed.clearInterval.apply(this, arguments);
1709 } else {
1710 return jasmine.Clock.installed.clearInterval(timeoutKey);
1711 }
1712 };
1713
1714 /**
1715 * @constructor
1716 */
1717 jasmine.MultiReporter = function() {
1718 this.subReporters_ = [];
1719 };
1720 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
1721
1722 jasmine.MultiReporter.prototype.addReporter = function(reporter) {
1723 this.subReporters_.push(reporter);
1724 };
1725
1726 (function() {
1727 var functionNames = [
1728 "reportRunnerStarting",
1729 "reportRunnerResults",
1730 "reportSuiteResults",
1731 "reportSpecStarting",
1732 "reportSpecResults",
1733 "log"
1734 ];
1735 for (var i = 0; i < functionNames.length; i++) {
1736 var functionName = functionNames[i];
1737 jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
1738 return function() {
1739 for (var j = 0; j < this.subReporters_.length; j++) {
1740 var subReporter = this.subReporters_[j];
1741 if (subReporter[functionName]) {
1742 subReporter[functionName].apply(subReporter, arguments);
1743 }
1744 }
1745 };
1746 })(functionName);
1747 }
1748 })();
1749 /**
1750 * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
1751 *
1752 * @constructor
1753 */
1754 jasmine.NestedResults = function() {
1755 /**
1756 * The total count of results
1757 */
1758 this.totalCount = 0;
1759 /**
1760 * Number of passed results
1761 */
1762 this.passedCount = 0;
1763 /**
1764 * Number of failed results
1765 */
1766 this.failedCount = 0;
1767 /**
1768 * Was this suite/spec skipped?
1769 */
1770 this.skipped = false;
1771 /**
1772 * @ignore
1773 */
1774 this.items_ = [];
1775 };
1776
1777 /**
1778 * Roll up the result counts.
1779 *
1780 * @param result
1781 */
1782 jasmine.NestedResults.prototype.rollupCounts = function(result) {
1783 this.totalCount += result.totalCount;
1784 this.passedCount += result.passedCount;
1785 this.failedCount += result.failedCount;
1786 };
1787
1788 /**
1789 * Adds a log message.
1790 * @param values Array of message parts which will be concatenated later.
1791 */
1792 jasmine.NestedResults.prototype.log = function(values) {
1793 this.items_.push(new jasmine.MessageResult(values));
1794 };
1795
1796 /**
1797 * Getter for the results: message & results.
1798 */
1799 jasmine.NestedResults.prototype.getItems = function() {
1800 return this.items_;
1801 };
1802
1803 /**
1804 * Adds a result, tracking counts (total, passed, & failed)
1805 * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
1806 */
1807 jasmine.NestedResults.prototype.addResult = function(result) {
1808 if (result.type != 'log') {
1809 if (result.items_) {
1810 this.rollupCounts(result);
1811 } else {
1812 this.totalCount++;
1813 if (result.passed()) {
1814 this.passedCount++;
1815 } else {
1816 this.failedCount++;
1817 }
1818 }
1819 }
1820 this.items_.push(result);
1821 };
1822
1823 /**
1824 * @returns {Boolean} True if <b>everything</b> below passed
1825 */
1826 jasmine.NestedResults.prototype.passed = function() {
1827 return this.passedCount === this.totalCount;
1828 };
1829 /**
1830 * Base class for pretty printing for expectation results.
1831 */
1832 jasmine.PrettyPrinter = function() {
1833 this.ppNestLevel_ = 0;
1834 };
1835
1836 /**
1837 * Formats a value in a nice, human-readable string.
1838 *
1839 * @param value
1840 */
1841 jasmine.PrettyPrinter.prototype.format = function(value) {
1842 if (this.ppNestLevel_ > 40) {
1843 throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
1844 }
1845
1846 this.ppNestLevel_++;
1847 try {
1848 if (value === jasmine.undefined) {
1849 this.emitScalar('undefined');
1850 } else if (value === null) {
1851 this.emitScalar('null');
1852 } else if (value === jasmine.getGlobal()) {
1853 this.emitScalar('<global>');
1854 } else if (value.jasmineToString) {
1855 this.emitScalar(value.jasmineToString());
1856 } else if (typeof value === 'string') {
1857 this.emitString(value);
1858 } else if (jasmine.isSpy(value)) {
1859 this.emitScalar("spy on " + value.identity);
1860 } else if (value instanceof RegExp) {
1861 this.emitScalar(value.toString());
1862 } else if (typeof value === 'function') {
1863 this.emitScalar('Function');
1864 } else if (typeof value.nodeType === 'number') {
1865 this.emitScalar('HTMLNode');
1866 } else if (value instanceof Date) {
1867 this.emitScalar('Date(' + value + ')');
1868 } else if (value.__Jasmine_been_here_before__) {
1869 this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
1870 } else if (jasmine.isArray_(value) || typeof value == 'object') {
1871 value.__Jasmine_been_here_before__ = true;
1872 if (jasmine.isArray_(value)) {
1873 this.emitArray(value);
1874 } else {
1875 this.emitObject(value);
1876 }
1877 delete value.__Jasmine_been_here_before__;
1878 } else {
1879 this.emitScalar(value.toString());
1880 }
1881 } finally {
1882 this.ppNestLevel_--;
1883 }
1884 };
1885
1886 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
1887 for (var property in obj) {
1888 if (property == '__Jasmine_been_here_before__') continue;
1889 fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
1890 obj.__lookupGetter__(property) !== null) : false);
1891 }
1892 };
1893
1894 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
1895 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
1896 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
1897 jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
1898
1899 jasmine.StringPrettyPrinter = function() {
1900 jasmine.PrettyPrinter.call(this);
1901
1902 this.string = '';
1903 };
1904 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
1905
1906 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
1907 this.append(value);
1908 };
1909
1910 jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
1911 this.append("'" + value + "'");
1912 };
1913
1914 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
1915 this.append('[ ');
1916 for (var i = 0; i < array.length; i++) {
1917 if (i > 0) {
1918 this.append(', ');
1919 }
1920 this.format(array[i]);
1921 }
1922 this.append(' ]');
1923 };
1924
1925 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
1926 var self = this;
1927 this.append('{ ');
1928 var first = true;
1929
1930 this.iterateObject(obj, function(property, isGetter) {
1931 if (first) {
1932 first = false;
1933 } else {
1934 self.append(', ');
1935 }
1936
1937 self.append(property);
1938 self.append(' : ');
1939 if (isGetter) {
1940 self.append('<getter>');
1941 } else {
1942 self.format(obj[property]);
1943 }
1944 });
1945
1946 this.append(' }');
1947 };
1948
1949 jasmine.StringPrettyPrinter.prototype.append = function(value) {
1950 this.string += value;
1951 };
1952 jasmine.Queue = function(env) {
1953 this.env = env;
1954 this.blocks = [];
1955 this.running = false;
1956 this.index = 0;
1957 this.offset = 0;
1958 this.abort = false;
1959 };
1960
1961 jasmine.Queue.prototype.addBefore = function(block) {
1962 this.blocks.unshift(block);
1963 };
1964
1965 jasmine.Queue.prototype.add = function(block) {
1966 this.blocks.push(block);
1967 };
1968
1969 jasmine.Queue.prototype.insertNext = function(block) {
1970 this.blocks.splice((this.index + this.offset + 1), 0, block);
1971 this.offset++;
1972 };
1973
1974 jasmine.Queue.prototype.start = function(onComplete) {
1975 this.running = true;
1976 this.onComplete = onComplete;
1977 this.next_();
1978 };
1979
1980 jasmine.Queue.prototype.isRunning = function() {
1981 return this.running;
1982 };
1983
1984 jasmine.Queue.LOOP_DONT_RECURSE = true;
1985
1986 jasmine.Queue.prototype.next_ = function() {
1987 var self = this;
1988 var goAgain = true;
1989
1990 while (goAgain) {
1991 goAgain = false;
1992
1993 if (self.index < self.blocks.length && !this.abort) {
1994 var calledSynchronously = true;
1995 var completedSynchronously = false;
1996
1997 var onComplete = function () {
1998 if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
1999 completedSynchronously = true;
2000 return;
2001 }
2002
2003 if (self.blocks[self.index].abort) {
2004 self.abort = true;
2005 }
2006
2007 self.offset = 0;
2008 self.index++;
2009
2010 var now = new Date().getTime();
2011 if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
2012 self.env.lastUpdate = now;
2013 self.env.setTimeout(function() {
2014 self.next_();
2015 }, 0);
2016 } else {
2017 if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
2018 goAgain = true;
2019 } else {
2020 self.next_();
2021 }
2022 }
2023 };
2024 self.blocks[self.index].execute(onComplete);
2025
2026 calledSynchronously = false;
2027 if (completedSynchronously) {
2028 onComplete();
2029 }
2030
2031 } else {
2032 self.running = false;
2033 if (self.onComplete) {
2034 self.onComplete();
2035 }
2036 }
2037 }
2038 };
2039
2040 jasmine.Queue.prototype.results = function() {
2041 var results = new jasmine.NestedResults();
2042 for (var i = 0; i < this.blocks.length; i++) {
2043 if (this.blocks[i].results) {
2044 results.addResult(this.blocks[i].results());
2045 }
2046 }
2047 return results;
2048 };
2049
2050
2051 /**
2052 * Runner
2053 *
2054 * @constructor
2055 * @param {jasmine.Env} env
2056 */
2057 jasmine.Runner = function(env) {
2058 var self = this;
2059 self.env = env;
2060 self.queue = new jasmine.Queue(env);
2061 self.before_ = [];
2062 self.after_ = [];
2063 self.suites_ = [];
2064 };
2065
2066 jasmine.Runner.prototype.execute = function() {
2067 var self = this;
2068 if (self.env.reporter.reportRunnerStarting) {
2069 self.env.reporter.reportRunnerStarting(this);
2070 }
2071 self.queue.start(function () {
2072 self.finishCallback();
2073 });
2074 };
2075
2076 jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
2077 beforeEachFunction.typeName = 'beforeEach';
2078 this.before_.splice(0,0,beforeEachFunction);
2079 };
2080
2081 jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
2082 afterEachFunction.typeName = 'afterEach';
2083 this.after_.splice(0,0,afterEachFunction);
2084 };
2085
2086
2087 jasmine.Runner.prototype.finishCallback = function() {
2088 this.env.reporter.reportRunnerResults(this);
2089 };
2090
2091 jasmine.Runner.prototype.addSuite = function(suite) {
2092 this.suites_.push(suite);
2093 };
2094
2095 jasmine.Runner.prototype.add = function(block) {
2096 if (block instanceof jasmine.Suite) {
2097 this.addSuite(block);
2098 }
2099 this.queue.add(block);
2100 };
2101
2102 jasmine.Runner.prototype.specs = function () {
2103 var suites = this.suites();
2104 var specs = [];
2105 for (var i = 0; i < suites.length; i++) {
2106 specs = specs.concat(suites[i].specs());
2107 }
2108 return specs;
2109 };
2110
2111 jasmine.Runner.prototype.suites = function() {
2112 return this.suites_;
2113 };
2114
2115 jasmine.Runner.prototype.topLevelSuites = function() {
2116 var topLevelSuites = [];
2117 for (var i = 0; i < this.suites_.length; i++) {
2118 if (!this.suites_[i].parentSuite) {
2119 topLevelSuites.push(this.suites_[i]);
2120 }
2121 }
2122 return topLevelSuites;
2123 };
2124
2125 jasmine.Runner.prototype.results = function() {
2126 return this.queue.results();
2127 };
2128 /**
2129 * Internal representation of a Jasmine specification, or test.
2130 *
2131 * @constructor
2132 * @param {jasmine.Env} env
2133 * @param {jasmine.Suite} suite
2134 * @param {String} description
2135 */
2136 jasmine.Spec = function(env, suite, description) {
2137 if (!env) {
2138 throw new Error('jasmine.Env() required');
2139 }
2140 if (!suite) {
2141 throw new Error('jasmine.Suite() required');
2142 }
2143 var spec = this;
2144 spec.id = env.nextSpecId ? env.nextSpecId() : null;
2145 spec.env = env;
2146 spec.suite = suite;
2147 spec.description = description;
2148 spec.queue = new jasmine.Queue(env);
2149
2150 spec.afterCallbacks = [];
2151 spec.spies_ = [];
2152
2153 spec.results_ = new jasmine.NestedResults();
2154 spec.results_.description = description;
2155 spec.matchersClass = null;
2156 };
2157
2158 jasmine.Spec.prototype.getFullName = function() {
2159 return this.suite.getFullName() + ' ' + this.description + '.';
2160 };
2161
2162
2163 jasmine.Spec.prototype.results = function() {
2164 return this.results_;
2165 };
2166
2167 /**
2168 * All parameters are pretty-printed and concatenated together, then written to the spec's output.
2169 *
2170 * Be careful not to leave calls to <code>jasmine.log</code> in production code.
2171 */
2172 jasmine.Spec.prototype.log = function() {
2173 return this.results_.log(arguments);
2174 };
2175
2176 jasmine.Spec.prototype.runs = function (func) {
2177 var block = new jasmine.Block(this.env, func, this);
2178 this.addToQueue(block);
2179 return this;
2180 };
2181
2182 jasmine.Spec.prototype.addToQueue = function (block) {
2183 if (this.queue.isRunning()) {
2184 this.queue.insertNext(block);
2185 } else {
2186 this.queue.add(block);
2187 }
2188 };
2189
2190 /**
2191 * @param {jasmine.ExpectationResult} result
2192 */
2193 jasmine.Spec.prototype.addMatcherResult = function(result) {
2194 this.results_.addResult(result);
2195 };
2196
2197 jasmine.Spec.prototype.expect = function(actual) {
2198 var positive = new (this.getMatchersClass_())(this.env, actual, this);
2199 positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
2200 return positive;
2201 };
2202
2203 /**
2204 * Waits a fixed time period before moving to the next block.
2205 *
2206 * @deprecated Use waitsFor() instead
2207 * @param {Number} timeout milliseconds to wait
2208 */
2209 jasmine.Spec.prototype.waits = function(timeout) {
2210 var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
2211 this.addToQueue(waitsFunc);
2212 return this;
2213 };
2214
2215 /**
2216 * Waits for the latchFunction to return true before proceeding to the next block.
2217 *
2218 * @param {Function} latchFunction
2219 * @param {String} optional_timeoutMessage
2220 * @param {Number} optional_timeout
2221 */
2222 jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
2223 var latchFunction_ = null;
2224 var optional_timeoutMessage_ = null;
2225 var optional_timeout_ = null;
2226
2227 for (var i = 0; i < arguments.length; i++) {
2228 var arg = arguments[i];
2229 switch (typeof arg) {
2230 case 'function':
2231 latchFunction_ = arg;
2232 break;
2233 case 'string':
2234 optional_timeoutMessage_ = arg;
2235 break;
2236 case 'number':
2237 optional_timeout_ = arg;
2238 break;
2239 }
2240 }
2241
2242 var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
2243 this.addToQueue(waitsForFunc);
2244 return this;
2245 };
2246
2247 jasmine.Spec.prototype.fail = function (e) {
2248 var expectationResult = new jasmine.ExpectationResult({
2249 passed: false,
2250 message: e ? jasmine.util.formatException(e) : 'Exception',
2251 trace: { stack: e.stack }
2252 });
2253 this.results_.addResult(expectationResult);
2254 };
2255
2256 jasmine.Spec.prototype.getMatchersClass_ = function() {
2257 return this.matchersClass || this.env.matchersClass;
2258 };
2259
2260 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
2261 var parent = this.getMatchersClass_();
2262 var newMatchersClass = function() {
2263 parent.apply(this, arguments);
2264 };
2265 jasmine.util.inherit(newMatchersClass, parent);
2266 jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
2267 this.matchersClass = newMatchersClass;
2268 };
2269
2270 jasmine.Spec.prototype.finishCallback = function() {
2271 this.env.reporter.reportSpecResults(this);
2272 };
2273
2274 jasmine.Spec.prototype.finish = function(onComplete) {
2275 this.removeAllSpies();
2276 this.finishCallback();
2277 if (onComplete) {
2278 onComplete();
2279 }
2280 };
2281
2282 jasmine.Spec.prototype.after = function(doAfter) {
2283 if (this.queue.isRunning()) {
2284 this.queue.add(new jasmine.Block(this.env, doAfter, this));
2285 } else {
2286 this.afterCallbacks.unshift(doAfter);
2287 }
2288 };
2289
2290 jasmine.Spec.prototype.execute = function(onComplete) {
2291 var spec = this;
2292 if (!spec.env.specFilter(spec)) {
2293 spec.results_.skipped = true;
2294 spec.finish(onComplete);
2295 return;
2296 }
2297
2298 this.env.reporter.reportSpecStarting(this);
2299
2300 spec.env.currentSpec = spec;
2301
2302 spec.addBeforesAndAftersToQueue();
2303
2304 spec.queue.start(function () {
2305 spec.finish(onComplete);
2306 });
2307 };
2308
2309 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
2310 var runner = this.env.currentRunner();
2311 var i;
2312
2313 for (var suite = this.suite; suite; suite = suite.parentSuite) {
2314 for (i = 0; i < suite.before_.length; i++) {
2315 this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
2316 }
2317 }
2318 for (i = 0; i < runner.before_.length; i++) {
2319 this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
2320 }
2321 for (i = 0; i < this.afterCallbacks.length; i++) {
2322 this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
2323 }
2324 for (suite = this.suite; suite; suite = suite.parentSuite) {
2325 for (i = 0; i < suite.after_.length; i++) {
2326 this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
2327 }
2328 }
2329 for (i = 0; i < runner.after_.length; i++) {
2330 this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
2331 }
2332 };
2333
2334 jasmine.Spec.prototype.explodes = function() {
2335 throw 'explodes function should not have been called';
2336 };
2337
2338 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
2339 if (obj == jasmine.undefined) {
2340 throw "spyOn could not find an object to spy upon for " + methodName + "()";
2341 }
2342
2343 if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
2344 throw methodName + '() method does not exist';
2345 }
2346
2347 if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
2348 throw new Error(methodName + ' has already been spied upon');
2349 }
2350
2351 var spyObj = jasmine.createSpy(methodName);
2352
2353 this.spies_.push(spyObj);
2354 spyObj.baseObj = obj;
2355 spyObj.methodName = methodName;
2356 spyObj.originalValue = obj[methodName];
2357
2358 obj[methodName] = spyObj;
2359
2360 return spyObj;
2361 };
2362
2363 jasmine.Spec.prototype.removeAllSpies = function() {
2364 for (var i = 0; i < this.spies_.length; i++) {
2365 var spy = this.spies_[i];
2366 spy.baseObj[spy.methodName] = spy.originalValue;
2367 }
2368 this.spies_ = [];
2369 };
2370
2371 /**
2372 * Internal representation of a Jasmine suite.
2373 *
2374 * @constructor
2375 * @param {jasmine.Env} env
2376 * @param {String} description
2377 * @param {Function} specDefinitions
2378 * @param {jasmine.Suite} parentSuite
2379 */
2380 jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
2381 var self = this;
2382 self.id = env.nextSuiteId ? env.nextSuiteId() : null;
2383 self.description = description;
2384 self.queue = new jasmine.Queue(env);
2385 self.parentSuite = parentSuite;
2386 self.env = env;
2387 self.before_ = [];
2388 self.after_ = [];
2389 self.children_ = [];
2390 self.suites_ = [];
2391 self.specs_ = [];
2392 };
2393
2394 jasmine.Suite.prototype.getFullName = function() {
2395 var fullName = this.description;
2396 for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
2397 fullName = parentSuite.description + ' ' + fullName;
2398 }
2399 return fullName;
2400 };
2401
2402 jasmine.Suite.prototype.finish = function(onComplete) {
2403 this.env.reporter.reportSuiteResults(this);
2404 this.finished = true;
2405 if (typeof(onComplete) == 'function') {
2406 onComplete();
2407 }
2408 };
2409
2410 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
2411 beforeEachFunction.typeName = 'beforeEach';
2412 this.before_.unshift(beforeEachFunction);
2413 };
2414
2415 jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
2416 afterEachFunction.typeName = 'afterEach';
2417 this.after_.unshift(afterEachFunction);
2418 };
2419
2420 jasmine.Suite.prototype.results = function() {
2421 return this.queue.results();
2422 };
2423
2424 jasmine.Suite.prototype.add = function(suiteOrSpec) {
2425 this.children_.push(suiteOrSpec);
2426 if (suiteOrSpec instanceof jasmine.Suite) {
2427 this.suites_.push(suiteOrSpec);
2428 this.env.currentRunner().addSuite(suiteOrSpec);
2429 } else {
2430 this.specs_.push(suiteOrSpec);
2431 }
2432 this.queue.add(suiteOrSpec);
2433 };
2434
2435 jasmine.Suite.prototype.specs = function() {
2436 return this.specs_;
2437 };
2438
2439 jasmine.Suite.prototype.suites = function() {
2440 return this.suites_;
2441 };
2442
2443 jasmine.Suite.prototype.children = function() {
2444 return this.children_;
2445 };
2446
2447 jasmine.Suite.prototype.execute = function(onComplete) {
2448 var self = this;
2449 this.queue.start(function () {
2450 self.finish(onComplete);
2451 });
2452 };
2453 jasmine.WaitsBlock = function(env, timeout, spec) {
2454 this.timeout = timeout;
2455 jasmine.Block.call(this, env, null, spec);
2456 };
2457
2458 jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
2459
2460 jasmine.WaitsBlock.prototype.execute = function (onComplete) {
2461 if (jasmine.VERBOSE) {
2462 this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
2463 }
2464 this.env.setTimeout(function () {
2465 onComplete();
2466 }, this.timeout);
2467 };
2468 /**
2469 * A block which waits for some condition to become true, with timeout.
2470 *
2471 * @constructor
2472 * @extends jasmine.Block
2473 * @param {jasmine.Env} env The Jasmine environment.
2474 * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
2475 * @param {Function} latchFunction A function which returns true when the desired condition has been met.
2476 * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
2477 * @param {jasmine.Spec} spec The Jasmine spec.
2478 */
2479 jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
2480 this.timeout = timeout || env.defaultTimeoutInterval;
2481 this.latchFunction = latchFunction;
2482 this.message = message;
2483 this.totalTimeSpentWaitingForLatch = 0;
2484 jasmine.Block.call(this, env, null, spec);
2485 };
2486 jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
2487
2488 jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
2489
2490 jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
2491 if (jasmine.VERBOSE) {
2492 this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
2493 }
2494 var latchFunctionResult;
2495 try {
2496 latchFunctionResult = this.latchFunction.apply(this.spec);
2497 } catch (e) {
2498 this.spec.fail(e);
2499 onComplete();
2500 return;
2501 }
2502
2503 if (latchFunctionResult) {
2504 onComplete();
2505 } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
2506 var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
2507 this.spec.fail({
2508 name: 'timeout',
2509 message: message
2510 });
2511
2512 this.abort = true;
2513 onComplete();
2514 } else {
2515 this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
2516 var self = this;
2517 this.env.setTimeout(function() {
2518 self.execute(onComplete);
2519 }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
2520 }
2521 };
2522
2523 jasmine.version_= {
2524 "major": 1,
2525 "minor": 2,
2526 "build": 0,
2527 "revision": 1337005947
2528 };
0 // Karma configuration
1 var libSources = require(__dirname+'/../build/build.js').getFiles();
2
3 // base path, that will be used to resolve files and exclude
4 basePath = '';
5
6 for (var i=0; i < libSources.length; i++) {
7 libSources[i] = "../" + libSources[i];
8 }
9
10 // list of files / patterns to load in the browser
11 files = [].concat([
12 "../node_modules/mocha/mocha.js",
13 MOCHA_ADAPTER,
14 "before.js",
15 "sinon.js",
16 "expect.js"
17 ], libSources, [
18 "after.js",
19 "happen.js",
20 "suites/SpecHelper.js",
21 "suites/**/*.js"
22 ]);
23
24 // list of files to exclude
25 exclude = [
26 ];
27
28 // test results reporter to use
29 // possible values: 'dots', 'progress', 'junit'
30 reporters = ['dots'];
31
32 // web server port
33 port = 8080;
34
35 // cli runner port
36 runnerPort = 9100;
37
38 // enable / disable colors in the output (reporters and logs)
39 colors = true;
40
41 // level of logging
42 // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
43 logLevel = LOG_WARN;
44
45 // enable / disable watching file and executing tests whenever any file changes
46 autoWatch = false;
47
48 // Start these browsers, currently available:
49 // - Chrome
50 // - ChromeCanary
51 // - Firefox
52 // - Opera
53 // - Safari (only Mac)
54 // - PhantomJS
55 // - IE (only Windows)
56 browsers = ['PhantomJS'];
57
58 // If browser does not capture in given timeout [ms], kill it
59 captureTimeout = 5000;
60
61 // Continuous Integration mode
62 // if true, it capture browsers, run tests and exit
63 singleRun = true;
+0
-72
spec/runner.html less more
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Jasmine Spec Runner</title>
4 <link rel="stylesheet" type="text/css" href="jasmine/jasmine.css">
5 <script type="text/javascript" src="jasmine/jasmine.js"></script>
6 <script type="text/javascript" src="jasmine/jasmine-html.js"></script>
7 <script type="text/javascript" src="happen.js"></script>
8
9 <!-- source files -->
10
11 <script type="text/javascript">
12 L = 'test'; //to test L#noConflict later
13 </script>
14
15 <script type="text/javascript" src="../debug/leaflet-include.js"></script>
16
17
18 <!-- spec files -->
19
20 <script type="text/javascript" src="suites/SpecHelper.js"></script>
21 <script type="text/javascript" src="suites/LeafletSpec.js"></script>
22
23 <!-- /control -->
24 <script type="text/javascript" src="suites/control/Control.LayersSpec.js"></script>
25 <script type="text/javascript" src="suites/control/Control.ScaleSpec.js"></script>
26
27 <!-- /core -->
28 <script type="text/javascript" src="suites/core/UtilSpec.js"></script>
29 <script type="text/javascript" src="suites/core/ClassSpec.js"></script>
30 <script type="text/javascript" src="suites/core/EventsSpec.js"></script>
31
32 <!-- /geometry -->
33 <script type="text/javascript" src="suites/geometry/PointSpec.js"></script>
34 <script type="text/javascript" src="suites/geometry/BoundsSpec.js"></script>
35 <script type="text/javascript" src="suites/geometry/TransformationSpec.js"></script>
36
37 <!-- /geo -->
38 <script type="text/javascript" src="suites/geo/LatLngSpec.js"></script>
39 <script type="text/javascript" src="suites/geo/LatLngBoundsSpec.js"></script>
40 <script type="text/javascript" src="suites/geo/ProjectionSpec.js"></script>
41
42 <!-- /dom -->
43 <script type="text/javascript" src="suites/dom/DomEventSpec.js"></script>
44 <script type="text/javascript" src="suites/dom/DomUtilSpec.js"></script>
45
46 <!-- /layer -->
47 <script type="text/javascript" src="suites/layer/TileLayerSpec.js"></script>
48 <script type="text/javascript" src="suites/layer/vector/PolylineGeometrySpec.js"></script>
49 <script type="text/javascript" src="suites/layer/vector/CircleSpec.js"></script>
50
51 <!-- /map -->
52 <script type="text/javascript" src="suites/map/MapSpec.js"></script>
53 </head>
54 <body>
55 <script type="text/javascript">
56 (function() {
57 var jasmineEnv = jasmine.getEnv();
58 jasmineEnv.updateInterval = 1000;
59
60 var htmlReporter = new jasmine.HtmlReporter();
61 jasmineEnv.addReporter(htmlReporter);
62
63 jasmineEnv.specFilter = function(spec) {
64 return htmlReporter.specFilter(spec);
65 };
66
67 jasmineEnv.execute();
68 })();
69 </script>
70 </body>
71 </html>
0 /**
1 * Sinon.JS 1.6.0, 2013/02/18
2 *
3 * @author Christian Johansen (christian@cjohansen.no)
4 * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
5 *
6 * (The BSD License)
7 *
8 * Copyright (c) 2010-2013, Christian Johansen, christian@cjohansen.no
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without modification,
12 * are permitted provided that the following conditions are met:
13 *
14 * * Redistributions of source code must retain the above copyright notice,
15 * this list of conditions and the following disclaimer.
16 * * Redistributions in binary form must reproduce the above copyright notice,
17 * this list of conditions and the following disclaimer in the documentation
18 * and/or other materials provided with the distribution.
19 * * Neither the name of Christian Johansen nor the names of his contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 var sinon = (function () {
36 "use strict";
37
38 var buster = (function (setTimeout, B) {
39 var isNode = typeof require == "function" && typeof module == "object";
40 var div = typeof document != "undefined" && document.createElement("div");
41 var F = function () {};
42
43 var buster = {
44 bind: function bind(obj, methOrProp) {
45 var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp;
46 var args = Array.prototype.slice.call(arguments, 2);
47 return function () {
48 var allArgs = args.concat(Array.prototype.slice.call(arguments));
49 return method.apply(obj, allArgs);
50 };
51 },
52
53 partial: function partial(fn) {
54 var args = [].slice.call(arguments, 1);
55 return function () {
56 return fn.apply(this, args.concat([].slice.call(arguments)));
57 };
58 },
59
60 create: function create(object) {
61 F.prototype = object;
62 return new F();
63 },
64
65 extend: function extend(target) {
66 if (!target) { return; }
67 for (var i = 1, l = arguments.length, prop; i < l; ++i) {
68 for (prop in arguments[i]) {
69 target[prop] = arguments[i][prop];
70 }
71 }
72 return target;
73 },
74
75 nextTick: function nextTick(callback) {
76 if (typeof process != "undefined" && process.nextTick) {
77 return process.nextTick(callback);
78 }
79 setTimeout(callback, 0);
80 },
81
82 functionName: function functionName(func) {
83 if (!func) return "";
84 if (func.displayName) return func.displayName;
85 if (func.name) return func.name;
86 var matches = func.toString().match(/function\s+([^\(]+)/m);
87 return matches && matches[1] || "";
88 },
89
90 isNode: function isNode(obj) {
91 if (!div) return false;
92 try {
93 obj.appendChild(div);
94 obj.removeChild(div);
95 } catch (e) {
96 return false;
97 }
98 return true;
99 },
100
101 isElement: function isElement(obj) {
102 return obj && obj.nodeType === 1 && buster.isNode(obj);
103 },
104
105 isArray: function isArray(arr) {
106 return Object.prototype.toString.call(arr) == "[object Array]";
107 },
108
109 flatten: function flatten(arr) {
110 var result = [], arr = arr || [];
111 for (var i = 0, l = arr.length; i < l; ++i) {
112 result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]);
113 }
114 return result;
115 },
116
117 each: function each(arr, callback) {
118 for (var i = 0, l = arr.length; i < l; ++i) {
119 callback(arr[i]);
120 }
121 },
122
123 map: function map(arr, callback) {
124 var results = [];
125 for (var i = 0, l = arr.length; i < l; ++i) {
126 results.push(callback(arr[i]));
127 }
128 return results;
129 },
130
131 parallel: function parallel(fns, callback) {
132 function cb(err, res) {
133 if (typeof callback == "function") {
134 callback(err, res);
135 callback = null;
136 }
137 }
138 if (fns.length == 0) { return cb(null, []); }
139 var remaining = fns.length, results = [];
140 function makeDone(num) {
141 return function done(err, result) {
142 if (err) { return cb(err); }
143 results[num] = result;
144 if (--remaining == 0) { cb(null, results); }
145 };
146 }
147 for (var i = 0, l = fns.length; i < l; ++i) {
148 fns[i](makeDone(i));
149 }
150 },
151
152 series: function series(fns, callback) {
153 function cb(err, res) {
154 if (typeof callback == "function") {
155 callback(err, res);
156 }
157 }
158 var remaining = fns.slice();
159 var results = [];
160 function callNext() {
161 if (remaining.length == 0) return cb(null, results);
162 var promise = remaining.shift()(next);
163 if (promise && typeof promise.then == "function") {
164 promise.then(buster.partial(next, null), next);
165 }
166 }
167 function next(err, result) {
168 if (err) return cb(err);
169 results.push(result);
170 callNext();
171 }
172 callNext();
173 },
174
175 countdown: function countdown(num, done) {
176 return function () {
177 if (--num == 0) done();
178 };
179 }
180 };
181
182 if (typeof process === "object" &&
183 typeof require === "function" && typeof module === "object") {
184 var crypto = require("crypto");
185 var path = require("path");
186
187 buster.tmpFile = function (fileName) {
188 var hashed = crypto.createHash("sha1");
189 hashed.update(fileName);
190 var tmpfileName = hashed.digest("hex");
191
192 if (process.platform == "win32") {
193 return path.join(process.env["TEMP"], tmpfileName);
194 } else {
195 return path.join("/tmp", tmpfileName);
196 }
197 };
198 }
199
200 if (Array.prototype.some) {
201 buster.some = function (arr, fn, thisp) {
202 return arr.some(fn, thisp);
203 };
204 } else {
205 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
206 buster.some = function (arr, fun, thisp) {
207 if (arr == null) { throw new TypeError(); }
208 arr = Object(arr);
209 var len = arr.length >>> 0;
210 if (typeof fun !== "function") { throw new TypeError(); }
211
212 for (var i = 0; i < len; i++) {
213 if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) {
214 return true;
215 }
216 }
217
218 return false;
219 };
220 }
221
222 if (Array.prototype.filter) {
223 buster.filter = function (arr, fn, thisp) {
224 return arr.filter(fn, thisp);
225 };
226 } else {
227 // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
228 buster.filter = function (fn, thisp) {
229 if (this == null) { throw new TypeError(); }
230
231 var t = Object(this);
232 var len = t.length >>> 0;
233 if (typeof fn != "function") { throw new TypeError(); }
234
235 var res = [];
236 for (var i = 0; i < len; i++) {
237 if (i in t) {
238 var val = t[i]; // in case fun mutates this
239 if (fn.call(thisp, val, i, t)) { res.push(val); }
240 }
241 }
242
243 return res;
244 };
245 }
246
247 if (isNode) {
248 module.exports = buster;
249 buster.eventEmitter = require("./buster-event-emitter");
250 Object.defineProperty(buster, "defineVersionGetter", {
251 get: function () {
252 return require("./define-version-getter");
253 }
254 });
255 }
256
257 return buster.extend(B || {}, buster);
258 }(setTimeout, buster));
259 if (typeof buster === "undefined") {
260 var buster = {};
261 }
262
263 if (typeof module === "object" && typeof require === "function") {
264 buster = require("buster-core");
265 }
266
267 buster.format = buster.format || {};
268 buster.format.excludeConstructors = ["Object", /^.$/];
269 buster.format.quoteStrings = true;
270
271 buster.format.ascii = (function () {
272
273 var hasOwn = Object.prototype.hasOwnProperty;
274
275 var specialObjects = [];
276 if (typeof global != "undefined") {
277 specialObjects.push({ obj: global, value: "[object global]" });
278 }
279 if (typeof document != "undefined") {
280 specialObjects.push({ obj: document, value: "[object HTMLDocument]" });
281 }
282 if (typeof window != "undefined") {
283 specialObjects.push({ obj: window, value: "[object Window]" });
284 }
285
286 function keys(object) {
287 var k = Object.keys && Object.keys(object) || [];
288
289 if (k.length == 0) {
290 for (var prop in object) {
291 if (hasOwn.call(object, prop)) {
292 k.push(prop);
293 }
294 }
295 }
296
297 return k.sort();
298 }
299
300 function isCircular(object, objects) {
301 if (typeof object != "object") {
302 return false;
303 }
304
305 for (var i = 0, l = objects.length; i < l; ++i) {
306 if (objects[i] === object) {
307 return true;
308 }
309 }
310
311 return false;
312 }
313
314 function ascii(object, processed, indent) {
315 if (typeof object == "string") {
316 var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings;
317 return processed || quote ? '"' + object + '"' : object;
318 }
319
320 if (typeof object == "function" && !(object instanceof RegExp)) {
321 return ascii.func(object);
322 }
323
324 processed = processed || [];
325
326 if (isCircular(object, processed)) {
327 return "[Circular]";
328 }
329
330 if (Object.prototype.toString.call(object) == "[object Array]") {
331 return ascii.array.call(this, object, processed);
332 }
333
334 if (!object) {
335 return "" + object;
336 }
337
338 if (buster.isElement(object)) {
339 return ascii.element(object);
340 }
341
342 if (typeof object.toString == "function" &&
343 object.toString !== Object.prototype.toString) {
344 return object.toString();
345 }
346
347 for (var i = 0, l = specialObjects.length; i < l; i++) {
348 if (object === specialObjects[i].obj) {
349 return specialObjects[i].value;
350 }
351 }
352
353 return ascii.object.call(this, object, processed, indent);
354 }
355
356 ascii.func = function (func) {
357 return "function " + buster.functionName(func) + "() {}";
358 };
359
360 ascii.array = function (array, processed) {
361 processed = processed || [];
362 processed.push(array);
363 var pieces = [];
364
365 for (var i = 0, l = array.length; i < l; ++i) {
366 pieces.push(ascii.call(this, array[i], processed));
367 }
368
369 return "[" + pieces.join(", ") + "]";
370 };
371
372 ascii.object = function (object, processed, indent) {
373 processed = processed || [];
374 processed.push(object);
375 indent = indent || 0;
376 var pieces = [], properties = keys(object), prop, str, obj;
377 var is = "";
378 var length = 3;
379
380 for (var i = 0, l = indent; i < l; ++i) {
381 is += " ";
382 }
383
384 for (i = 0, l = properties.length; i < l; ++i) {
385 prop = properties[i];
386 obj = object[prop];
387
388 if (isCircular(obj, processed)) {
389 str = "[Circular]";
390 } else {
391 str = ascii.call(this, obj, processed, indent + 2);
392 }
393
394 str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;
395 length += str.length;
396 pieces.push(str);
397 }
398
399 var cons = ascii.constructorName.call(this, object);
400 var prefix = cons ? "[" + cons + "] " : ""
401
402 return (length + indent) > 80 ?
403 prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" :
404 prefix + "{ " + pieces.join(", ") + " }";
405 };
406
407 ascii.element = function (element) {
408 var tagName = element.tagName.toLowerCase();
409 var attrs = element.attributes, attribute, pairs = [], attrName;
410
411 for (var i = 0, l = attrs.length; i < l; ++i) {
412 attribute = attrs.item(i);
413 attrName = attribute.nodeName.toLowerCase().replace("html:", "");
414
415 if (attrName == "contenteditable" && attribute.nodeValue == "inherit") {
416 continue;
417 }
418
419 if (!!attribute.nodeValue) {
420 pairs.push(attrName + "=\"" + attribute.nodeValue + "\"");
421 }
422 }
423
424 var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");
425 var content = element.innerHTML;
426
427 if (content.length > 20) {
428 content = content.substr(0, 20) + "[...]";
429 }
430
431 var res = formatted + pairs.join(" ") + ">" + content + "</" + tagName + ">";
432
433 return res.replace(/ contentEditable="inherit"/, "");
434 };
435
436 ascii.constructorName = function (object) {
437 var name = buster.functionName(object && object.constructor);
438 var excludes = this.excludeConstructors || buster.format.excludeConstructors || [];
439
440 for (var i = 0, l = excludes.length; i < l; ++i) {
441 if (typeof excludes[i] == "string" && excludes[i] == name) {
442 return "";
443 } else if (excludes[i].test && excludes[i].test(name)) {
444 return "";
445 }
446 }
447
448 return name;
449 };
450
451 return ascii;
452 }());
453
454 if (typeof module != "undefined") {
455 module.exports = buster.format;
456 }
457 /*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/
458 /*global module, require, __dirname, document*/
459 /**
460 * Sinon core utilities. For internal use only.
461 *
462 * @author Christian Johansen (christian@cjohansen.no)
463 * @license BSD
464 *
465 * Copyright (c) 2010-2013 Christian Johansen
466 */
467
468 var sinon = (function (buster) {
469 var div = typeof document != "undefined" && document.createElement("div");
470 var hasOwn = Object.prototype.hasOwnProperty;
471
472 function isDOMNode(obj) {
473 var success = false;
474
475 try {
476 obj.appendChild(div);
477 success = div.parentNode == obj;
478 } catch (e) {
479 return false;
480 } finally {
481 try {
482 obj.removeChild(div);
483 } catch (e) {
484 // Remove failed, not much we can do about that
485 }
486 }
487
488 return success;
489 }
490
491 function isElement(obj) {
492 return div && obj && obj.nodeType === 1 && isDOMNode(obj);
493 }
494
495 function isFunction(obj) {
496 return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply);
497 }
498
499 function mirrorProperties(target, source) {
500 for (var prop in source) {
501 if (!hasOwn.call(target, prop)) {
502 target[prop] = source[prop];
503 }
504 }
505 }
506
507 var sinon = {
508 wrapMethod: function wrapMethod(object, property, method) {
509 if (!object) {
510 throw new TypeError("Should wrap property of object");
511 }
512
513 if (typeof method != "function") {
514 throw new TypeError("Method wrapper should be function");
515 }
516
517 var wrappedMethod = object[property];
518
519 if (!isFunction(wrappedMethod)) {
520 throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
521 property + " as function");
522 }
523
524 if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
525 throw new TypeError("Attempted to wrap " + property + " which is already wrapped");
526 }
527
528 if (wrappedMethod.calledBefore) {
529 var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
530 throw new TypeError("Attempted to wrap " + property + " which is already " + verb);
531 }
532
533 // IE 8 does not support hasOwnProperty on the window object.
534 var owned = hasOwn.call(object, property);
535 object[property] = method;
536 method.displayName = property;
537
538 method.restore = function () {
539 // For prototype properties try to reset by delete first.
540 // If this fails (ex: localStorage on mobile safari) then force a reset
541 // via direct assignment.
542 if (!owned) {
543 delete object[property];
544 }
545 if (object[property] === method) {
546 object[property] = wrappedMethod;
547 }
548 };
549
550 method.restore.sinon = true;
551 mirrorProperties(method, wrappedMethod);
552
553 return method;
554 },
555
556 extend: function extend(target) {
557 for (var i = 1, l = arguments.length; i < l; i += 1) {
558 for (var prop in arguments[i]) {
559 if (arguments[i].hasOwnProperty(prop)) {
560 target[prop] = arguments[i][prop];
561 }
562
563 // DONT ENUM bug, only care about toString
564 if (arguments[i].hasOwnProperty("toString") &&
565 arguments[i].toString != target.toString) {
566 target.toString = arguments[i].toString;
567 }
568 }
569 }
570
571 return target;
572 },
573
574 create: function create(proto) {
575 var F = function () {};
576 F.prototype = proto;
577 return new F();
578 },
579
580 deepEqual: function deepEqual(a, b) {
581 if (sinon.match && sinon.match.isMatcher(a)) {
582 return a.test(b);
583 }
584 if (typeof a != "object" || typeof b != "object") {
585 return a === b;
586 }
587
588 if (isElement(a) || isElement(b)) {
589 return a === b;
590 }
591
592 if (a === b) {
593 return true;
594 }
595
596 if ((a === null && b !== null) || (a !== null && b === null)) {
597 return false;
598 }
599
600 var aString = Object.prototype.toString.call(a);
601 if (aString != Object.prototype.toString.call(b)) {
602 return false;
603 }
604
605 if (aString == "[object Array]") {
606 if (a.length !== b.length) {
607 return false;
608 }
609
610 for (var i = 0, l = a.length; i < l; i += 1) {
611 if (!deepEqual(a[i], b[i])) {
612 return false;
613 }
614 }
615
616 return true;
617 }
618
619 var prop, aLength = 0, bLength = 0;
620
621 for (prop in a) {
622 aLength += 1;
623
624 if (!deepEqual(a[prop], b[prop])) {
625 return false;
626 }
627 }
628
629 for (prop in b) {
630 bLength += 1;
631 }
632
633 if (aLength != bLength) {
634 return false;
635 }
636
637 return true;
638 },
639
640 functionName: function functionName(func) {
641 var name = func.displayName || func.name;
642
643 // Use function decomposition as a last resort to get function
644 // name. Does not rely on function decomposition to work - if it
645 // doesn't debugging will be slightly less informative
646 // (i.e. toString will say 'spy' rather than 'myFunc').
647 if (!name) {
648 var matches = func.toString().match(/function ([^\s\(]+)/);
649 name = matches && matches[1];
650 }
651
652 return name;
653 },
654
655 functionToString: function toString() {
656 if (this.getCall && this.callCount) {
657 var thisValue, prop, i = this.callCount;
658
659 while (i--) {
660 thisValue = this.getCall(i).thisValue;
661
662 for (prop in thisValue) {
663 if (thisValue[prop] === this) {
664 return prop;
665 }
666 }
667 }
668 }
669
670 return this.displayName || "sinon fake";
671 },
672
673 getConfig: function (custom) {
674 var config = {};
675 custom = custom || {};
676 var defaults = sinon.defaultConfig;
677
678 for (var prop in defaults) {
679 if (defaults.hasOwnProperty(prop)) {
680 config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
681 }
682 }
683
684 return config;
685 },
686
687 format: function (val) {
688 return "" + val;
689 },
690
691 defaultConfig: {
692 injectIntoThis: true,
693 injectInto: null,
694 properties: ["spy", "stub", "mock", "clock", "server", "requests"],
695 useFakeTimers: true,
696 useFakeServer: true
697 },
698
699 timesInWords: function timesInWords(count) {
700 return count == 1 && "once" ||
701 count == 2 && "twice" ||
702 count == 3 && "thrice" ||
703 (count || 0) + " times";
704 },
705
706 calledInOrder: function (spies) {
707 for (var i = 1, l = spies.length; i < l; i++) {
708 if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) {
709 return false;
710 }
711 }
712
713 return true;
714 },
715
716 orderByFirstCall: function (spies) {
717 return spies.sort(function (a, b) {
718 // uuid, won't ever be equal
719 var aCall = a.getCall(0);
720 var bCall = b.getCall(0);
721 var aId = aCall && aCall.callId || -1;
722 var bId = bCall && bCall.callId || -1;
723
724 return aId < bId ? -1 : 1;
725 });
726 },
727
728 log: function () {},
729
730 logError: function (label, err) {
731 var msg = label + " threw exception: "
732 sinon.log(msg + "[" + err.name + "] " + err.message);
733 if (err.stack) { sinon.log(err.stack); }
734
735 setTimeout(function () {
736 err.message = msg + err.message;
737 throw err;
738 }, 0);
739 },
740
741 typeOf: function (value) {
742 if (value === null) {
743 return "null";
744 }
745 else if (value === undefined) {
746 return "undefined";
747 }
748 var string = Object.prototype.toString.call(value);
749 return string.substring(8, string.length - 1).toLowerCase();
750 },
751
752 createStubInstance: function (constructor) {
753 if (typeof constructor !== "function") {
754 throw new TypeError("The constructor should be a function.");
755 }
756 return sinon.stub(sinon.create(constructor.prototype));
757 }
758 };
759
760 var isNode = typeof module == "object" && typeof require == "function";
761
762 if (isNode) {
763 try {
764 buster = { format: require("buster-format") };
765 } catch (e) {}
766 module.exports = sinon;
767 module.exports.spy = require("./sinon/spy");
768 module.exports.stub = require("./sinon/stub");
769 module.exports.mock = require("./sinon/mock");
770 module.exports.collection = require("./sinon/collection");
771 module.exports.assert = require("./sinon/assert");
772 module.exports.sandbox = require("./sinon/sandbox");
773 module.exports.test = require("./sinon/test");
774 module.exports.testCase = require("./sinon/test_case");
775 module.exports.assert = require("./sinon/assert");
776 module.exports.match = require("./sinon/match");
777 }
778
779 if (buster) {
780 var formatter = sinon.create(buster.format);
781 formatter.quoteStrings = false;
782 sinon.format = function () {
783 return formatter.ascii.apply(formatter, arguments);
784 };
785 } else if (isNode) {
786 try {
787 var util = require("util");
788 sinon.format = function (value) {
789 return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value;
790 };
791 } catch (e) {
792 /* Node, but no util module - would be very old, but better safe than
793 sorry */
794 }
795 }
796
797 return sinon;
798 }(typeof buster == "object" && buster));
799
800 /* @depend ../sinon.js */
801 /*jslint eqeqeq: false, onevar: false, plusplus: false*/
802 /*global module, require, sinon*/
803 /**
804 * Match functions
805 *
806 * @author Maximilian Antoni (mail@maxantoni.de)
807 * @license BSD
808 *
809 * Copyright (c) 2012 Maximilian Antoni
810 */
811
812 (function (sinon) {
813 var commonJSModule = typeof module == "object" && typeof require == "function";
814
815 if (!sinon && commonJSModule) {
816 sinon = require("../sinon");
817 }
818
819 if (!sinon) {
820 return;
821 }
822
823 function assertType(value, type, name) {
824 var actual = sinon.typeOf(value);
825 if (actual !== type) {
826 throw new TypeError("Expected type of " + name + " to be " +
827 type + ", but was " + actual);
828 }
829 }
830
831 var matcher = {
832 toString: function () {
833 return this.message;
834 }
835 };
836
837 function isMatcher(object) {
838 return matcher.isPrototypeOf(object);
839 }
840
841 function matchObject(expectation, actual) {
842 if (actual === null || actual === undefined) {
843 return false;
844 }
845 for (var key in expectation) {
846 if (expectation.hasOwnProperty(key)) {
847 var exp = expectation[key];
848 var act = actual[key];
849 if (match.isMatcher(exp)) {
850 if (!exp.test(act)) {
851 return false;
852 }
853 } else if (sinon.typeOf(exp) === "object") {
854 if (!matchObject(exp, act)) {
855 return false;
856 }
857 } else if (!sinon.deepEqual(exp, act)) {
858 return false;
859 }
860 }
861 }
862 return true;
863 }
864
865 matcher.or = function (m2) {
866 if (!isMatcher(m2)) {
867 throw new TypeError("Matcher expected");
868 }
869 var m1 = this;
870 var or = sinon.create(matcher);
871 or.test = function (actual) {
872 return m1.test(actual) || m2.test(actual);
873 };
874 or.message = m1.message + ".or(" + m2.message + ")";
875 return or;
876 };
877
878 matcher.and = function (m2) {
879 if (!isMatcher(m2)) {
880 throw new TypeError("Matcher expected");
881 }
882 var m1 = this;
883 var and = sinon.create(matcher);
884 and.test = function (actual) {
885 return m1.test(actual) && m2.test(actual);
886 };
887 and.message = m1.message + ".and(" + m2.message + ")";
888 return and;
889 };
890
891 var match = function (expectation, message) {
892 var m = sinon.create(matcher);
893 var type = sinon.typeOf(expectation);
894 switch (type) {
895 case "object":
896 if (typeof expectation.test === "function") {
897 m.test = function (actual) {
898 return expectation.test(actual) === true;
899 };
900 m.message = "match(" + sinon.functionName(expectation.test) + ")";
901 return m;
902 }
903 var str = [];
904 for (var key in expectation) {
905 if (expectation.hasOwnProperty(key)) {
906 str.push(key + ": " + expectation[key]);
907 }
908 }
909 m.test = function (actual) {
910 return matchObject(expectation, actual);
911 };
912 m.message = "match(" + str.join(", ") + ")";
913 break;
914 case "number":
915 m.test = function (actual) {
916 return expectation == actual;
917 };
918 break;
919 case "string":
920 m.test = function (actual) {
921 if (typeof actual !== "string") {
922 return false;
923 }
924 return actual.indexOf(expectation) !== -1;
925 };
926 m.message = "match(\"" + expectation + "\")";
927 break;
928 case "regexp":
929 m.test = function (actual) {
930 if (typeof actual !== "string") {
931 return false;
932 }
933 return expectation.test(actual);
934 };
935 break;
936 case "function":
937 m.test = expectation;
938 if (message) {
939 m.message = message;
940 } else {
941 m.message = "match(" + sinon.functionName(expectation) + ")";
942 }
943 break;
944 default:
945 m.test = function (actual) {
946 return sinon.deepEqual(expectation, actual);
947 };
948 }
949 if (!m.message) {
950 m.message = "match(" + expectation + ")";
951 }
952 return m;
953 };
954
955 match.isMatcher = isMatcher;
956
957 match.any = match(function () {
958 return true;
959 }, "any");
960
961 match.defined = match(function (actual) {
962 return actual !== null && actual !== undefined;
963 }, "defined");
964
965 match.truthy = match(function (actual) {
966 return !!actual;
967 }, "truthy");
968
969 match.falsy = match(function (actual) {
970 return !actual;
971 }, "falsy");
972
973 match.same = function (expectation) {
974 return match(function (actual) {
975 return expectation === actual;
976 }, "same(" + expectation + ")");
977 };
978
979 match.typeOf = function (type) {
980 assertType(type, "string", "type");
981 return match(function (actual) {
982 return sinon.typeOf(actual) === type;
983 }, "typeOf(\"" + type + "\")");
984 };
985
986 match.instanceOf = function (type) {
987 assertType(type, "function", "type");
988 return match(function (actual) {
989 return actual instanceof type;
990 }, "instanceOf(" + sinon.functionName(type) + ")");
991 };
992
993 function createPropertyMatcher(propertyTest, messagePrefix) {
994 return function (property, value) {
995 assertType(property, "string", "property");
996 var onlyProperty = arguments.length === 1;
997 var message = messagePrefix + "(\"" + property + "\"";
998 if (!onlyProperty) {
999 message += ", " + value;
1000 }
1001 message += ")";
1002 return match(function (actual) {
1003 if (actual === undefined || actual === null ||
1004 !propertyTest(actual, property)) {
1005 return false;
1006 }
1007 return onlyProperty || sinon.deepEqual(value, actual[property]);
1008 }, message);
1009 };
1010 }
1011
1012 match.has = createPropertyMatcher(function (actual, property) {
1013 if (typeof actual === "object") {
1014 return property in actual;
1015 }
1016 return actual[property] !== undefined;
1017 }, "has");
1018
1019 match.hasOwn = createPropertyMatcher(function (actual, property) {
1020 return actual.hasOwnProperty(property);
1021 }, "hasOwn");
1022
1023 match.bool = match.typeOf("boolean");
1024 match.number = match.typeOf("number");
1025 match.string = match.typeOf("string");
1026 match.object = match.typeOf("object");
1027 match.func = match.typeOf("function");
1028 match.array = match.typeOf("array");
1029 match.regexp = match.typeOf("regexp");
1030 match.date = match.typeOf("date");
1031
1032 if (commonJSModule) {
1033 module.exports = match;
1034 } else {
1035 sinon.match = match;
1036 }
1037 }(typeof sinon == "object" && sinon || null));
1038
1039 /**
1040 * @depend ../sinon.js
1041 * @depend match.js
1042 */
1043 /*jslint eqeqeq: false, onevar: false, plusplus: false*/
1044 /*global module, require, sinon*/
1045 /**
1046 * Spy functions
1047 *
1048 * @author Christian Johansen (christian@cjohansen.no)
1049 * @license BSD
1050 *
1051 * Copyright (c) 2010-2013 Christian Johansen
1052 */
1053
1054 (function (sinon) {
1055 var commonJSModule = typeof module == "object" && typeof require == "function";
1056 var spyCall;
1057 var callId = 0;
1058 var push = [].push;
1059 var slice = Array.prototype.slice;
1060
1061 if (!sinon && commonJSModule) {
1062 sinon = require("../sinon");
1063 }
1064
1065 if (!sinon) {
1066 return;
1067 }
1068
1069 function spy(object, property) {
1070 if (!property && typeof object == "function") {
1071 return spy.create(object);
1072 }
1073
1074 if (!object && !property) {
1075 return spy.create(function () { });
1076 }
1077
1078 var method = object[property];
1079 return sinon.wrapMethod(object, property, spy.create(method));
1080 }
1081
1082 sinon.extend(spy, (function () {
1083
1084 function delegateToCalls(api, method, matchAny, actual, notCalled) {
1085 api[method] = function () {
1086 if (!this.called) {
1087 if (notCalled) {
1088 return notCalled.apply(this, arguments);
1089 }
1090 return false;
1091 }
1092
1093 var currentCall;
1094 var matches = 0;
1095
1096 for (var i = 0, l = this.callCount; i < l; i += 1) {
1097 currentCall = this.getCall(i);
1098
1099 if (currentCall[actual || method].apply(currentCall, arguments)) {
1100 matches += 1;
1101
1102 if (matchAny) {
1103 return true;
1104 }
1105 }
1106 }
1107
1108 return matches === this.callCount;
1109 };
1110 }
1111
1112 function matchingFake(fakes, args, strict) {
1113 if (!fakes) {
1114 return;
1115 }
1116
1117 var alen = args.length;
1118
1119 for (var i = 0, l = fakes.length; i < l; i++) {
1120 if (fakes[i].matches(args, strict)) {
1121 return fakes[i];
1122 }
1123 }
1124 }
1125
1126 function incrementCallCount() {
1127 this.called = true;
1128 this.callCount += 1;
1129 this.notCalled = false;
1130 this.calledOnce = this.callCount == 1;
1131 this.calledTwice = this.callCount == 2;
1132 this.calledThrice = this.callCount == 3;
1133 }
1134
1135 function createCallProperties() {
1136 this.firstCall = this.getCall(0);
1137 this.secondCall = this.getCall(1);
1138 this.thirdCall = this.getCall(2);
1139 this.lastCall = this.getCall(this.callCount - 1);
1140 }
1141
1142 var vars = "a,b,c,d,e,f,g,h,i,j,k,l";
1143 function createProxy(func) {
1144 // Retain the function length:
1145 var p;
1146 if (func.length) {
1147 eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) +
1148 ") { return p.invoke(func, this, slice.call(arguments)); });");
1149 }
1150 else {
1151 p = function proxy() {
1152 return p.invoke(func, this, slice.call(arguments));
1153 };
1154 }
1155 return p;
1156 }
1157
1158 var uuid = 0;
1159
1160 // Public API
1161 var spyApi = {
1162 reset: function () {
1163 this.called = false;
1164 this.notCalled = true;
1165 this.calledOnce = false;
1166 this.calledTwice = false;
1167 this.calledThrice = false;
1168 this.callCount = 0;
1169 this.firstCall = null;
1170 this.secondCall = null;
1171 this.thirdCall = null;
1172 this.lastCall = null;
1173 this.args = [];
1174 this.returnValues = [];
1175 this.thisValues = [];
1176 this.exceptions = [];
1177 this.callIds = [];
1178 if (this.fakes) {
1179 for (var i = 0; i < this.fakes.length; i++) {
1180 this.fakes[i].reset();
1181 }
1182 }
1183 },
1184
1185 create: function create(func) {
1186 var name;
1187
1188 if (typeof func != "function") {
1189 func = function () { };
1190 } else {
1191 name = sinon.functionName(func);
1192 }
1193
1194 var proxy = createProxy(func);
1195
1196 sinon.extend(proxy, spy);
1197 delete proxy.create;
1198 sinon.extend(proxy, func);
1199
1200 proxy.reset();
1201 proxy.prototype = func.prototype;
1202 proxy.displayName = name || "spy";
1203 proxy.toString = sinon.functionToString;
1204 proxy._create = sinon.spy.create;
1205 proxy.id = "spy#" + uuid++;
1206
1207 return proxy;
1208 },
1209
1210 invoke: function invoke(func, thisValue, args) {
1211 var matching = matchingFake(this.fakes, args);
1212 var exception, returnValue;
1213
1214 incrementCallCount.call(this);
1215 push.call(this.thisValues, thisValue);
1216 push.call(this.args, args);
1217 push.call(this.callIds, callId++);
1218
1219 try {
1220 if (matching) {
1221 returnValue = matching.invoke(func, thisValue, args);
1222 } else {
1223 returnValue = (this.func || func).apply(thisValue, args);
1224 }
1225 } catch (e) {
1226 push.call(this.returnValues, undefined);
1227 exception = e;
1228 throw e;
1229 } finally {
1230 push.call(this.exceptions, exception);
1231 }
1232
1233 push.call(this.returnValues, returnValue);
1234
1235 createCallProperties.call(this);
1236
1237 return returnValue;
1238 },
1239
1240 getCall: function getCall(i) {
1241 if (i < 0 || i >= this.callCount) {
1242 return null;
1243 }
1244
1245 return spyCall.create(this, this.thisValues[i], this.args[i],
1246 this.returnValues[i], this.exceptions[i],
1247 this.callIds[i]);
1248 },
1249
1250 calledBefore: function calledBefore(spyFn) {
1251 if (!this.called) {
1252 return false;
1253 }
1254
1255 if (!spyFn.called) {
1256 return true;
1257 }
1258
1259 return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1];
1260 },
1261
1262 calledAfter: function calledAfter(spyFn) {
1263 if (!this.called || !spyFn.called) {
1264 return false;
1265 }
1266
1267 return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];
1268 },
1269
1270 withArgs: function () {
1271 var args = slice.call(arguments);
1272
1273 if (this.fakes) {
1274 var match = matchingFake(this.fakes, args, true);
1275
1276 if (match) {
1277 return match;
1278 }
1279 } else {
1280 this.fakes = [];
1281 }
1282
1283 var original = this;
1284 var fake = this._create();
1285 fake.matchingAguments = args;
1286 push.call(this.fakes, fake);
1287
1288 fake.withArgs = function () {
1289 return original.withArgs.apply(original, arguments);
1290 };
1291
1292 for (var i = 0; i < this.args.length; i++) {
1293 if (fake.matches(this.args[i])) {
1294 incrementCallCount.call(fake);
1295 push.call(fake.thisValues, this.thisValues[i]);
1296 push.call(fake.args, this.args[i]);
1297 push.call(fake.returnValues, this.returnValues[i]);
1298 push.call(fake.exceptions, this.exceptions[i]);
1299 push.call(fake.callIds, this.callIds[i]);
1300 }
1301 }
1302 createCallProperties.call(fake);
1303
1304 return fake;
1305 },
1306
1307 matches: function (args, strict) {
1308 var margs = this.matchingAguments;
1309
1310 if (margs.length <= args.length &&
1311 sinon.deepEqual(margs, args.slice(0, margs.length))) {
1312 return !strict || margs.length == args.length;
1313 }
1314 },
1315
1316 printf: function (format) {
1317 var spy = this;
1318 var args = slice.call(arguments, 1);
1319 var formatter;
1320
1321 return (format || "").replace(/%(.)/g, function (match, specifyer) {
1322 formatter = spyApi.formatters[specifyer];
1323
1324 if (typeof formatter == "function") {
1325 return formatter.call(null, spy, args);
1326 } else if (!isNaN(parseInt(specifyer), 10)) {
1327 return sinon.format(args[specifyer - 1]);
1328 }
1329
1330 return "%" + specifyer;
1331 });
1332 }
1333 };
1334
1335 delegateToCalls(spyApi, "calledOn", true);
1336 delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn");
1337 delegateToCalls(spyApi, "calledWith", true);
1338 delegateToCalls(spyApi, "calledWithMatch", true);
1339 delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith");
1340 delegateToCalls(spyApi, "alwaysCalledWithMatch", false, "calledWithMatch");
1341 delegateToCalls(spyApi, "calledWithExactly", true);
1342 delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly");
1343 delegateToCalls(spyApi, "neverCalledWith", false, "notCalledWith",
1344 function () { return true; });
1345 delegateToCalls(spyApi, "neverCalledWithMatch", false, "notCalledWithMatch",
1346 function () { return true; });
1347 delegateToCalls(spyApi, "threw", true);
1348 delegateToCalls(spyApi, "alwaysThrew", false, "threw");
1349 delegateToCalls(spyApi, "returned", true);
1350 delegateToCalls(spyApi, "alwaysReturned", false, "returned");
1351 delegateToCalls(spyApi, "calledWithNew", true);
1352 delegateToCalls(spyApi, "alwaysCalledWithNew", false, "calledWithNew");
1353 delegateToCalls(spyApi, "callArg", false, "callArgWith", function () {
1354 throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
1355 });
1356 spyApi.callArgWith = spyApi.callArg;
1357 delegateToCalls(spyApi, "callArgOn", false, "callArgOnWith", function () {
1358 throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
1359 });
1360 spyApi.callArgOnWith = spyApi.callArgOn;
1361 delegateToCalls(spyApi, "yield", false, "yield", function () {
1362 throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
1363 });
1364 // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
1365 spyApi.invokeCallback = spyApi.yield;
1366 delegateToCalls(spyApi, "yieldOn", false, "yieldOn", function () {
1367 throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
1368 });
1369 delegateToCalls(spyApi, "yieldTo", false, "yieldTo", function (property) {
1370 throw new Error(this.toString() + " cannot yield to '" + property +
1371 "' since it was not yet invoked.");
1372 });
1373 delegateToCalls(spyApi, "yieldToOn", false, "yieldToOn", function (property) {
1374 throw new Error(this.toString() + " cannot yield to '" + property +
1375 "' since it was not yet invoked.");
1376 });
1377
1378 spyApi.formatters = {
1379 "c": function (spy) {
1380 return sinon.timesInWords(spy.callCount);
1381 },
1382
1383 "n": function (spy) {
1384 return spy.toString();
1385 },
1386
1387 "C": function (spy) {
1388 var calls = [];
1389
1390 for (var i = 0, l = spy.callCount; i < l; ++i) {
1391 var stringifiedCall = " " + spy.getCall(i).toString();
1392 if (/\n/.test(calls[i - 1])) {
1393 stringifiedCall = "\n" + stringifiedCall;
1394 }
1395 push.call(calls, stringifiedCall);
1396 }
1397
1398 return calls.length > 0 ? "\n" + calls.join("\n") : "";
1399 },
1400
1401 "t": function (spy) {
1402 var objects = [];
1403
1404 for (var i = 0, l = spy.callCount; i < l; ++i) {
1405 push.call(objects, sinon.format(spy.thisValues[i]));
1406 }
1407
1408 return objects.join(", ");
1409 },
1410
1411 "*": function (spy, args) {
1412 var formatted = [];
1413
1414 for (var i = 0, l = args.length; i < l; ++i) {
1415 push.call(formatted, sinon.format(args[i]));
1416 }
1417
1418 return formatted.join(", ");
1419 }
1420 };
1421
1422 return spyApi;
1423 }()));
1424
1425 spyCall = (function () {
1426
1427 function throwYieldError(proxy, text, args) {
1428 var msg = sinon.functionName(proxy) + text;
1429 if (args.length) {
1430 msg += " Received [" + slice.call(args).join(", ") + "]";
1431 }
1432 throw new Error(msg);
1433 }
1434
1435 var callApi = {
1436 create: function create(spy, thisValue, args, returnValue, exception, id) {
1437 var proxyCall = sinon.create(spyCall);
1438 delete proxyCall.create;
1439 proxyCall.proxy = spy;
1440 proxyCall.thisValue = thisValue;
1441 proxyCall.args = args;
1442 proxyCall.returnValue = returnValue;
1443 proxyCall.exception = exception;
1444 proxyCall.callId = typeof id == "number" && id || callId++;
1445
1446 return proxyCall;
1447 },
1448
1449 calledOn: function calledOn(thisValue) {
1450 if (sinon.match && sinon.match.isMatcher(thisValue)) {
1451 return thisValue.test(this.thisValue);
1452 }
1453 return this.thisValue === thisValue;
1454 },
1455
1456 calledWith: function calledWith() {
1457 for (var i = 0, l = arguments.length; i < l; i += 1) {
1458 if (!sinon.deepEqual(arguments[i], this.args[i])) {
1459 return false;
1460 }
1461 }
1462
1463 return true;
1464 },
1465
1466 calledWithMatch: function calledWithMatch() {
1467 for (var i = 0, l = arguments.length; i < l; i += 1) {
1468 var actual = this.args[i];
1469 var expectation = arguments[i];
1470 if (!sinon.match || !sinon.match(expectation).test(actual)) {
1471 return false;
1472 }
1473 }
1474 return true;
1475 },
1476
1477 calledWithExactly: function calledWithExactly() {
1478 return arguments.length == this.args.length &&
1479 this.calledWith.apply(this, arguments);
1480 },
1481
1482 notCalledWith: function notCalledWith() {
1483 return !this.calledWith.apply(this, arguments);
1484 },
1485
1486 notCalledWithMatch: function notCalledWithMatch() {
1487 return !this.calledWithMatch.apply(this, arguments);
1488 },
1489
1490 returned: function returned(value) {
1491 return sinon.deepEqual(value, this.returnValue);
1492 },
1493
1494 threw: function threw(error) {
1495 if (typeof error == "undefined" || !this.exception) {
1496 return !!this.exception;
1497 }
1498
1499 if (typeof error == "string") {
1500 return this.exception.name == error;
1501 }
1502
1503 return this.exception === error;
1504 },
1505
1506 calledWithNew: function calledWithNew(thisValue) {
1507 return this.thisValue instanceof this.proxy;
1508 },
1509
1510 calledBefore: function (other) {
1511 return this.callId < other.callId;
1512 },
1513
1514 calledAfter: function (other) {
1515 return this.callId > other.callId;
1516 },
1517
1518 callArg: function (pos) {
1519 this.args[pos]();
1520 },
1521
1522 callArgOn: function (pos, thisValue) {
1523 this.args[pos].apply(thisValue);
1524 },
1525
1526 callArgWith: function (pos) {
1527 this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1)));
1528 },
1529
1530 callArgOnWith: function (pos, thisValue) {
1531 var args = slice.call(arguments, 2);
1532 this.args[pos].apply(thisValue, args);
1533 },
1534
1535 "yield": function () {
1536 this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0)));
1537 },
1538
1539 yieldOn: function (thisValue) {
1540 var args = this.args;
1541 for (var i = 0, l = args.length; i < l; ++i) {
1542 if (typeof args[i] === "function") {
1543 args[i].apply(thisValue, slice.call(arguments, 1));
1544 return;
1545 }
1546 }
1547 throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);
1548 },
1549
1550 yieldTo: function (prop) {
1551 this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1)));
1552 },
1553
1554 yieldToOn: function (prop, thisValue) {
1555 var args = this.args;
1556 for (var i = 0, l = args.length; i < l; ++i) {
1557 if (args[i] && typeof args[i][prop] === "function") {
1558 args[i][prop].apply(thisValue, slice.call(arguments, 2));
1559 return;
1560 }
1561 }
1562 throwYieldError(this.proxy, " cannot yield to '" + prop +
1563 "' since no callback was passed.", args);
1564 },
1565
1566 toString: function () {
1567 var callStr = this.proxy.toString() + "(";
1568 var args = [];
1569
1570 for (var i = 0, l = this.args.length; i < l; ++i) {
1571 push.call(args, sinon.format(this.args[i]));
1572 }
1573
1574 callStr = callStr + args.join(", ") + ")";
1575
1576 if (typeof this.returnValue != "undefined") {
1577 callStr += " => " + sinon.format(this.returnValue);
1578 }
1579
1580 if (this.exception) {
1581 callStr += " !" + this.exception.name;
1582
1583 if (this.exception.message) {
1584 callStr += "(" + this.exception.message + ")";
1585 }
1586 }
1587
1588 return callStr;
1589 }
1590 };
1591 callApi.invokeCallback = callApi.yield;
1592 return callApi;
1593 }());
1594
1595 spy.spyCall = spyCall;
1596
1597 // This steps outside the module sandbox and will be removed
1598 sinon.spyCall = spyCall;
1599
1600 if (commonJSModule) {
1601 module.exports = spy;
1602 } else {
1603 sinon.spy = spy;
1604 }
1605 }(typeof sinon == "object" && sinon || null));
1606
1607 /**
1608 * @depend ../sinon.js
1609 * @depend spy.js
1610 */
1611 /*jslint eqeqeq: false, onevar: false*/
1612 /*global module, require, sinon*/
1613 /**
1614 * Stub functions
1615 *
1616 * @author Christian Johansen (christian@cjohansen.no)
1617 * @license BSD
1618 *
1619 * Copyright (c) 2010-2013 Christian Johansen
1620 */
1621
1622 (function (sinon) {
1623 var commonJSModule = typeof module == "object" && typeof require == "function";
1624
1625 if (!sinon && commonJSModule) {
1626 sinon = require("../sinon");
1627 }
1628
1629 if (!sinon) {
1630 return;
1631 }
1632
1633 function stub(object, property, func) {
1634 if (!!func && typeof func != "function") {
1635 throw new TypeError("Custom stub should be function");
1636 }
1637
1638 var wrapper;
1639
1640 if (func) {
1641 wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
1642 } else {
1643 wrapper = stub.create();
1644 }
1645
1646 if (!object && !property) {
1647 return sinon.stub.create();
1648 }
1649
1650 if (!property && !!object && typeof object == "object") {
1651 for (var prop in object) {
1652 if (typeof object[prop] === "function") {
1653 stub(object, prop);
1654 }
1655 }
1656
1657 return object;
1658 }
1659
1660 return sinon.wrapMethod(object, property, wrapper);
1661 }
1662
1663 function getChangingValue(stub, property) {
1664 var index = stub.callCount - 1;
1665 var values = stub[property];
1666 var prop = index in values ? values[index] : values[values.length - 1];
1667 stub[property + "Last"] = prop;
1668
1669 return prop;
1670 }
1671
1672 function getCallback(stub, args) {
1673 var callArgAt = getChangingValue(stub, "callArgAts");
1674
1675 if (callArgAt < 0) {
1676 var callArgProp = getChangingValue(stub, "callArgProps");
1677
1678 for (var i = 0, l = args.length; i < l; ++i) {
1679 if (!callArgProp && typeof args[i] == "function") {
1680 return args[i];
1681 }
1682
1683 if (callArgProp && args[i] &&
1684 typeof args[i][callArgProp] == "function") {
1685 return args[i][callArgProp];
1686 }
1687 }
1688
1689 return null;
1690 }
1691
1692 return args[callArgAt];
1693 }
1694
1695 var join = Array.prototype.join;
1696
1697 function getCallbackError(stub, func, args) {
1698 if (stub.callArgAtsLast < 0) {
1699 var msg;
1700
1701 if (stub.callArgPropsLast) {
1702 msg = sinon.functionName(stub) +
1703 " expected to yield to '" + stub.callArgPropsLast +
1704 "', but no object with such a property was passed."
1705 } else {
1706 msg = sinon.functionName(stub) +
1707 " expected to yield, but no callback was passed."
1708 }
1709
1710 if (args.length > 0) {
1711 msg += " Received [" + join.call(args, ", ") + "]";
1712 }
1713
1714 return msg;
1715 }
1716
1717 return "argument at index " + stub.callArgAtsLast + " is not a function: " + func;
1718 }
1719
1720 var nextTick = (function () {
1721 if (typeof process === "object" && typeof process.nextTick === "function") {
1722 return process.nextTick;
1723 } else if (typeof setImmediate === "function") {
1724 return setImmediate;
1725 } else {
1726 return function (callback) {
1727 setTimeout(callback, 0);
1728 };
1729 }
1730 })();
1731
1732 function callCallback(stub, args) {
1733 if (stub.callArgAts.length > 0) {
1734 var func = getCallback(stub, args);
1735
1736 if (typeof func != "function") {
1737 throw new TypeError(getCallbackError(stub, func, args));
1738 }
1739
1740 var callbackArguments = getChangingValue(stub, "callbackArguments");
1741 var callbackContext = getChangingValue(stub, "callbackContexts");
1742
1743 if (stub.callbackAsync) {
1744 nextTick(function() {
1745 func.apply(callbackContext, callbackArguments);
1746 });
1747 } else {
1748 func.apply(callbackContext, callbackArguments);
1749 }
1750 }
1751 }
1752
1753 var uuid = 0;
1754
1755 sinon.extend(stub, (function () {
1756 var slice = Array.prototype.slice, proto;
1757
1758 function throwsException(error, message) {
1759 if (typeof error == "string") {
1760 this.exception = new Error(message || "");
1761 this.exception.name = error;
1762 } else if (!error) {
1763 this.exception = new Error("Error");
1764 } else {
1765 this.exception = error;
1766 }
1767
1768 return this;
1769 }
1770
1771 proto = {
1772 create: function create() {
1773 var functionStub = function () {
1774
1775 callCallback(functionStub, arguments);
1776
1777 if (functionStub.exception) {
1778 throw functionStub.exception;
1779 } else if (typeof functionStub.returnArgAt == 'number') {
1780 return arguments[functionStub.returnArgAt];
1781 } else if (functionStub.returnThis) {
1782 return this;
1783 }
1784 return functionStub.returnValue;
1785 };
1786
1787 functionStub.id = "stub#" + uuid++;
1788 var orig = functionStub;
1789 functionStub = sinon.spy.create(functionStub);
1790 functionStub.func = orig;
1791
1792 functionStub.callArgAts = [];
1793 functionStub.callbackArguments = [];
1794 functionStub.callbackContexts = [];
1795 functionStub.callArgProps = [];
1796
1797 sinon.extend(functionStub, stub);
1798 functionStub._create = sinon.stub.create;
1799 functionStub.displayName = "stub";
1800 functionStub.toString = sinon.functionToString;
1801
1802 return functionStub;
1803 },
1804
1805 resetBehavior: function () {
1806 var i;
1807
1808 this.callArgAts = [];
1809 this.callbackArguments = [];
1810 this.callbackContexts = [];
1811 this.callArgProps = [];
1812
1813 delete this.returnValue;
1814 delete this.returnArgAt;
1815 this.returnThis = false;
1816
1817 if (this.fakes) {
1818 for (i = 0; i < this.fakes.length; i++) {
1819 this.fakes[i].resetBehavior();
1820 }
1821 }
1822 },
1823
1824 returns: function returns(value) {
1825 this.returnValue = value;
1826
1827 return this;
1828 },
1829
1830 returnsArg: function returnsArg(pos) {
1831 if (typeof pos != "number") {
1832 throw new TypeError("argument index is not number");
1833 }
1834
1835 this.returnArgAt = pos;
1836
1837 return this;
1838 },
1839
1840 returnsThis: function returnsThis() {
1841 this.returnThis = true;
1842
1843 return this;
1844 },
1845
1846 "throws": throwsException,
1847 throwsException: throwsException,
1848
1849 callsArg: function callsArg(pos) {
1850 if (typeof pos != "number") {
1851 throw new TypeError("argument index is not number");
1852 }
1853
1854 this.callArgAts.push(pos);
1855 this.callbackArguments.push([]);
1856 this.callbackContexts.push(undefined);
1857 this.callArgProps.push(undefined);
1858
1859 return this;
1860 },
1861
1862 callsArgOn: function callsArgOn(pos, context) {
1863 if (typeof pos != "number") {
1864 throw new TypeError("argument index is not number");
1865 }
1866 if (typeof context != "object") {
1867 throw new TypeError("argument context is not an object");
1868 }
1869
1870 this.callArgAts.push(pos);
1871 this.callbackArguments.push([]);
1872 this.callbackContexts.push(context);
1873 this.callArgProps.push(undefined);
1874
1875 return this;
1876 },
1877
1878 callsArgWith: function callsArgWith(pos) {
1879 if (typeof pos != "number") {
1880 throw new TypeError("argument index is not number");
1881 }
1882
1883 this.callArgAts.push(pos);
1884 this.callbackArguments.push(slice.call(arguments, 1));
1885 this.callbackContexts.push(undefined);
1886 this.callArgProps.push(undefined);
1887
1888 return this;
1889 },
1890
1891 callsArgOnWith: function callsArgWith(pos, context) {
1892 if (typeof pos != "number") {
1893 throw new TypeError("argument index is not number");
1894 }
1895 if (typeof context != "object") {
1896 throw new TypeError("argument context is not an object");
1897 }
1898
1899 this.callArgAts.push(pos);
1900 this.callbackArguments.push(slice.call(arguments, 2));
1901 this.callbackContexts.push(context);
1902 this.callArgProps.push(undefined);
1903
1904 return this;
1905 },
1906
1907 yields: function () {
1908 this.callArgAts.push(-1);
1909 this.callbackArguments.push(slice.call(arguments, 0));
1910 this.callbackContexts.push(undefined);
1911 this.callArgProps.push(undefined);
1912
1913 return this;
1914 },
1915
1916 yieldsOn: function (context) {
1917 if (typeof context != "object") {
1918 throw new TypeError("argument context is not an object");
1919 }
1920
1921 this.callArgAts.push(-1);
1922 this.callbackArguments.push(slice.call(arguments, 1));
1923 this.callbackContexts.push(context);
1924 this.callArgProps.push(undefined);
1925
1926 return this;
1927 },
1928
1929 yieldsTo: function (prop) {
1930 this.callArgAts.push(-1);
1931 this.callbackArguments.push(slice.call(arguments, 1));
1932 this.callbackContexts.push(undefined);
1933 this.callArgProps.push(prop);
1934
1935 return this;
1936 },
1937
1938 yieldsToOn: function (prop, context) {
1939 if (typeof context != "object") {
1940 throw new TypeError("argument context is not an object");
1941 }
1942
1943 this.callArgAts.push(-1);
1944 this.callbackArguments.push(slice.call(arguments, 2));
1945 this.callbackContexts.push(context);
1946 this.callArgProps.push(prop);
1947
1948 return this;
1949 }
1950 };
1951
1952 // create asynchronous versions of callsArg* and yields* methods
1953 for (var method in proto) {
1954 // need to avoid creating anotherasync versions of the newly added async methods
1955 if (proto.hasOwnProperty(method) &&
1956 method.match(/^(callsArg|yields|thenYields$)/) &&
1957 !method.match(/Async/)) {
1958 proto[method + 'Async'] = (function (syncFnName) {
1959 return function () {
1960 this.callbackAsync = true;
1961 return this[syncFnName].apply(this, arguments);
1962 };
1963 })(method);
1964 }
1965 }
1966
1967 return proto;
1968
1969 }()));
1970
1971 if (commonJSModule) {
1972 module.exports = stub;
1973 } else {
1974 sinon.stub = stub;
1975 }
1976 }(typeof sinon == "object" && sinon || null));
1977
1978 /**
1979 * @depend ../sinon.js
1980 * @depend stub.js
1981 */
1982 /*jslint eqeqeq: false, onevar: false, nomen: false*/
1983 /*global module, require, sinon*/
1984 /**
1985 * Mock functions.
1986 *
1987 * @author Christian Johansen (christian@cjohansen.no)
1988 * @license BSD
1989 *
1990 * Copyright (c) 2010-2013 Christian Johansen
1991 */
1992
1993 (function (sinon) {
1994 var commonJSModule = typeof module == "object" && typeof require == "function";
1995 var push = [].push;
1996
1997 if (!sinon && commonJSModule) {
1998 sinon = require("../sinon");
1999 }
2000
2001 if (!sinon) {
2002 return;
2003 }
2004
2005 function mock(object) {
2006 if (!object) {
2007 return sinon.expectation.create("Anonymous mock");
2008 }
2009
2010 return mock.create(object);
2011 }
2012
2013 sinon.mock = mock;
2014
2015 sinon.extend(mock, (function () {
2016 function each(collection, callback) {
2017 if (!collection) {
2018 return;
2019 }
2020
2021 for (var i = 0, l = collection.length; i < l; i += 1) {
2022 callback(collection[i]);
2023 }
2024 }
2025
2026 return {
2027 create: function create(object) {
2028 if (!object) {
2029 throw new TypeError("object is null");
2030 }
2031
2032 var mockObject = sinon.extend({}, mock);
2033 mockObject.object = object;
2034 delete mockObject.create;
2035
2036 return mockObject;
2037 },
2038
2039 expects: function expects(method) {
2040 if (!method) {
2041 throw new TypeError("method is falsy");
2042 }
2043
2044 if (!this.expectations) {
2045 this.expectations = {};
2046 this.proxies = [];
2047 }
2048
2049 if (!this.expectations[method]) {
2050 this.expectations[method] = [];
2051 var mockObject = this;
2052
2053 sinon.wrapMethod(this.object, method, function () {
2054 return mockObject.invokeMethod(method, this, arguments);
2055 });
2056
2057 push.call(this.proxies, method);
2058 }
2059
2060 var expectation = sinon.expectation.create(method);
2061 push.call(this.expectations[method], expectation);
2062
2063 return expectation;
2064 },
2065
2066 restore: function restore() {
2067 var object = this.object;
2068
2069 each(this.proxies, function (proxy) {
2070 if (typeof object[proxy].restore == "function") {
2071 object[proxy].restore();
2072 }
2073 });
2074 },
2075
2076 verify: function verify() {
2077 var expectations = this.expectations || {};
2078 var messages = [], met = [];
2079
2080 each(this.proxies, function (proxy) {
2081 each(expectations[proxy], function (expectation) {
2082 if (!expectation.met()) {
2083 push.call(messages, expectation.toString());
2084 } else {
2085 push.call(met, expectation.toString());
2086 }
2087 });
2088 });
2089
2090 this.restore();
2091
2092 if (messages.length > 0) {
2093 sinon.expectation.fail(messages.concat(met).join("\n"));
2094 } else {
2095 sinon.expectation.pass(messages.concat(met).join("\n"));
2096 }
2097
2098 return true;
2099 },
2100
2101 invokeMethod: function invokeMethod(method, thisValue, args) {
2102 var expectations = this.expectations && this.expectations[method];
2103 var length = expectations && expectations.length || 0, i;
2104
2105 for (i = 0; i < length; i += 1) {
2106 if (!expectations[i].met() &&
2107 expectations[i].allowsCall(thisValue, args)) {
2108 return expectations[i].apply(thisValue, args);
2109 }
2110 }
2111
2112 var messages = [], available, exhausted = 0;
2113
2114 for (i = 0; i < length; i += 1) {
2115 if (expectations[i].allowsCall(thisValue, args)) {
2116 available = available || expectations[i];
2117 } else {
2118 exhausted += 1;
2119 }
2120 push.call(messages, " " + expectations[i].toString());
2121 }
2122
2123 if (exhausted === 0) {
2124 return available.apply(thisValue, args);
2125 }
2126
2127 messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({
2128 proxy: method,
2129 args: args
2130 }));
2131
2132 sinon.expectation.fail(messages.join("\n"));
2133 }
2134 };
2135 }()));
2136
2137 var times = sinon.timesInWords;
2138
2139 sinon.expectation = (function () {
2140 var slice = Array.prototype.slice;
2141 var _invoke = sinon.spy.invoke;
2142
2143 function callCountInWords(callCount) {
2144 if (callCount == 0) {
2145 return "never called";
2146 } else {
2147 return "called " + times(callCount);
2148 }
2149 }
2150
2151 function expectedCallCountInWords(expectation) {
2152 var min = expectation.minCalls;
2153 var max = expectation.maxCalls;
2154
2155 if (typeof min == "number" && typeof max == "number") {
2156 var str = times(min);
2157
2158 if (min != max) {
2159 str = "at least " + str + " and at most " + times(max);
2160 }
2161
2162 return str;
2163 }
2164
2165 if (typeof min == "number") {
2166 return "at least " + times(min);
2167 }
2168
2169 return "at most " + times(max);
2170 }
2171
2172 function receivedMinCalls(expectation) {
2173 var hasMinLimit = typeof expectation.minCalls == "number";
2174 return !hasMinLimit || expectation.callCount >= expectation.minCalls;
2175 }
2176
2177 function receivedMaxCalls(expectation) {
2178 if (typeof expectation.maxCalls != "number") {
2179 return false;
2180 }
2181
2182 return expectation.callCount == expectation.maxCalls;
2183 }
2184
2185 return {
2186 minCalls: 1,
2187 maxCalls: 1,
2188
2189 create: function create(methodName) {
2190 var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);
2191 delete expectation.create;
2192 expectation.method = methodName;
2193
2194 return expectation;
2195 },
2196
2197 invoke: function invoke(func, thisValue, args) {
2198 this.verifyCallAllowed(thisValue, args);
2199
2200 return _invoke.apply(this, arguments);
2201 },
2202
2203 atLeast: function atLeast(num) {
2204 if (typeof num != "number") {
2205 throw new TypeError("'" + num + "' is not number");
2206 }
2207
2208 if (!this.limitsSet) {
2209 this.maxCalls = null;
2210 this.limitsSet = true;
2211 }
2212
2213 this.minCalls = num;
2214
2215 return this;
2216 },
2217
2218 atMost: function atMost(num) {
2219 if (typeof num != "number") {
2220 throw new TypeError("'" + num + "' is not number");
2221 }
2222
2223 if (!this.limitsSet) {
2224 this.minCalls = null;
2225 this.limitsSet = true;
2226 }
2227
2228 this.maxCalls = num;
2229
2230 return this;
2231 },
2232
2233 never: function never() {
2234 return this.exactly(0);
2235 },
2236
2237 once: function once() {
2238 return this.exactly(1);
2239 },
2240
2241 twice: function twice() {
2242 return this.exactly(2);
2243 },
2244
2245 thrice: function thrice() {
2246 return this.exactly(3);
2247 },
2248
2249 exactly: function exactly(num) {
2250 if (typeof num != "number") {
2251 throw new TypeError("'" + num + "' is not a number");
2252 }
2253
2254 this.atLeast(num);
2255 return this.atMost(num);
2256 },
2257
2258 met: function met() {
2259 return !this.failed && receivedMinCalls(this);
2260 },
2261
2262 verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
2263 if (receivedMaxCalls(this)) {
2264 this.failed = true;
2265 sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));
2266 }
2267
2268 if ("expectedThis" in this && this.expectedThis !== thisValue) {
2269 sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +
2270 this.expectedThis);
2271 }
2272
2273 if (!("expectedArguments" in this)) {
2274 return;
2275 }
2276
2277 if (!args) {
2278 sinon.expectation.fail(this.method + " received no arguments, expected " +
2279 sinon.format(this.expectedArguments));
2280 }
2281
2282 if (args.length < this.expectedArguments.length) {
2283 sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) +
2284 "), expected " + sinon.format(this.expectedArguments));
2285 }
2286
2287 if (this.expectsExactArgCount &&
2288 args.length != this.expectedArguments.length) {
2289 sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) +
2290 "), expected " + sinon.format(this.expectedArguments));
2291 }
2292
2293 for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
2294 if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
2295 sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
2296 ", expected " + sinon.format(this.expectedArguments));
2297 }
2298 }
2299 },
2300
2301 allowsCall: function allowsCall(thisValue, args) {
2302 if (this.met() && receivedMaxCalls(this)) {
2303 return false;
2304 }
2305
2306 if ("expectedThis" in this && this.expectedThis !== thisValue) {
2307 return false;
2308 }
2309
2310 if (!("expectedArguments" in this)) {
2311 return true;
2312 }
2313
2314 args = args || [];
2315
2316 if (args.length < this.expectedArguments.length) {
2317 return false;
2318 }
2319
2320 if (this.expectsExactArgCount &&
2321 args.length != this.expectedArguments.length) {
2322 return false;
2323 }
2324
2325 for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
2326 if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
2327 return false;
2328 }
2329 }
2330
2331 return true;
2332 },
2333
2334 withArgs: function withArgs() {
2335 this.expectedArguments = slice.call(arguments);
2336 return this;
2337 },
2338
2339 withExactArgs: function withExactArgs() {
2340 this.withArgs.apply(this, arguments);
2341 this.expectsExactArgCount = true;
2342 return this;
2343 },
2344
2345 on: function on(thisValue) {
2346 this.expectedThis = thisValue;
2347 return this;
2348 },
2349
2350 toString: function () {
2351 var args = (this.expectedArguments || []).slice();
2352
2353 if (!this.expectsExactArgCount) {
2354 push.call(args, "[...]");
2355 }
2356
2357 var callStr = sinon.spyCall.toString.call({
2358 proxy: this.method || "anonymous mock expectation",
2359 args: args
2360 });
2361
2362 var message = callStr.replace(", [...", "[, ...") + " " +
2363 expectedCallCountInWords(this);
2364
2365 if (this.met()) {
2366 return "Expectation met: " + message;
2367 }
2368
2369 return "Expected " + message + " (" +
2370 callCountInWords(this.callCount) + ")";
2371 },
2372
2373 verify: function verify() {
2374 if (!this.met()) {
2375 sinon.expectation.fail(this.toString());
2376 } else {
2377 sinon.expectation.pass(this.toString());
2378 }
2379
2380 return true;
2381 },
2382
2383 pass: function(message) {
2384 sinon.assert.pass(message);
2385 },
2386 fail: function (message) {
2387 var exception = new Error(message);
2388 exception.name = "ExpectationError";
2389
2390 throw exception;
2391 }
2392 };
2393 }());
2394
2395 if (commonJSModule) {
2396 module.exports = mock;
2397 } else {
2398 sinon.mock = mock;
2399 }
2400 }(typeof sinon == "object" && sinon || null));
2401
2402 /**
2403 * @depend ../sinon.js
2404 * @depend stub.js
2405 * @depend mock.js
2406 */
2407 /*jslint eqeqeq: false, onevar: false, forin: true*/
2408 /*global module, require, sinon*/
2409 /**
2410 * Collections of stubs, spies and mocks.
2411 *
2412 * @author Christian Johansen (christian@cjohansen.no)
2413 * @license BSD
2414 *
2415 * Copyright (c) 2010-2013 Christian Johansen
2416 */
2417
2418 (function (sinon) {
2419 var commonJSModule = typeof module == "object" && typeof require == "function";
2420 var push = [].push;
2421 var hasOwnProperty = Object.prototype.hasOwnProperty;
2422
2423 if (!sinon && commonJSModule) {
2424 sinon = require("../sinon");
2425 }
2426
2427 if (!sinon) {
2428 return;
2429 }
2430
2431 function getFakes(fakeCollection) {
2432 if (!fakeCollection.fakes) {
2433 fakeCollection.fakes = [];
2434 }
2435
2436 return fakeCollection.fakes;
2437 }
2438
2439 function each(fakeCollection, method) {
2440 var fakes = getFakes(fakeCollection);
2441
2442 for (var i = 0, l = fakes.length; i < l; i += 1) {
2443 if (typeof fakes[i][method] == "function") {
2444 fakes[i][method]();
2445 }
2446 }
2447 }
2448
2449 function compact(fakeCollection) {
2450 var fakes = getFakes(fakeCollection);
2451 var i = 0;
2452 while (i < fakes.length) {
2453 fakes.splice(i, 1);
2454 }
2455 }
2456
2457 var collection = {
2458 verify: function resolve() {
2459 each(this, "verify");
2460 },
2461
2462 restore: function restore() {
2463 each(this, "restore");
2464 compact(this);
2465 },
2466
2467 verifyAndRestore: function verifyAndRestore() {
2468 var exception;
2469
2470 try {
2471 this.verify();
2472 } catch (e) {
2473 exception = e;
2474 }
2475
2476 this.restore();
2477
2478 if (exception) {
2479 throw exception;
2480 }
2481 },
2482
2483 add: function add(fake) {
2484 push.call(getFakes(this), fake);
2485 return fake;
2486 },
2487
2488 spy: function spy() {
2489 return this.add(sinon.spy.apply(sinon, arguments));
2490 },
2491
2492 stub: function stub(object, property, value) {
2493 if (property) {
2494 var original = object[property];
2495
2496 if (typeof original != "function") {
2497 if (!hasOwnProperty.call(object, property)) {
2498 throw new TypeError("Cannot stub non-existent own property " + property);
2499 }
2500
2501 object[property] = value;
2502
2503 return this.add({
2504 restore: function () {
2505 object[property] = original;
2506 }
2507 });
2508 }
2509 }
2510 if (!property && !!object && typeof object == "object") {
2511 var stubbedObj = sinon.stub.apply(sinon, arguments);
2512
2513 for (var prop in stubbedObj) {
2514 if (typeof stubbedObj[prop] === "function") {
2515 this.add(stubbedObj[prop]);
2516 }
2517 }
2518
2519 return stubbedObj;
2520 }
2521
2522 return this.add(sinon.stub.apply(sinon, arguments));
2523 },
2524
2525 mock: function mock() {
2526 return this.add(sinon.mock.apply(sinon, arguments));
2527 },
2528
2529 inject: function inject(obj) {
2530 var col = this;
2531
2532 obj.spy = function () {
2533 return col.spy.apply(col, arguments);
2534 };
2535
2536 obj.stub = function () {
2537 return col.stub.apply(col, arguments);
2538 };
2539
2540 obj.mock = function () {
2541 return col.mock.apply(col, arguments);
2542 };
2543
2544 return obj;
2545 }
2546 };
2547
2548 if (commonJSModule) {
2549 module.exports = collection;
2550 } else {
2551 sinon.collection = collection;
2552 }
2553 }(typeof sinon == "object" && sinon || null));
2554
2555 /*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/
2556 /*global module, require, window*/
2557 /**
2558 * Fake timer API
2559 * setTimeout
2560 * setInterval
2561 * clearTimeout
2562 * clearInterval
2563 * tick
2564 * reset
2565 * Date
2566 *
2567 * Inspired by jsUnitMockTimeOut from JsUnit
2568 *
2569 * @author Christian Johansen (christian@cjohansen.no)
2570 * @license BSD
2571 *
2572 * Copyright (c) 2010-2013 Christian Johansen
2573 */
2574
2575 if (typeof sinon == "undefined") {
2576 var sinon = {};
2577 }
2578
2579 (function (global) {
2580 var id = 1;
2581
2582 function addTimer(args, recurring) {
2583 if (args.length === 0) {
2584 throw new Error("Function requires at least 1 parameter");
2585 }
2586
2587 var toId = id++;
2588 var delay = args[1] || 0;
2589
2590 if (!this.timeouts) {
2591 this.timeouts = {};
2592 }
2593
2594 this.timeouts[toId] = {
2595 id: toId,
2596 func: args[0],
2597 callAt: this.now + delay,
2598 invokeArgs: Array.prototype.slice.call(args, 2)
2599 };
2600
2601 if (recurring === true) {
2602 this.timeouts[toId].interval = delay;
2603 }
2604
2605 return toId;
2606 }
2607
2608 function parseTime(str) {
2609 if (!str) {
2610 return 0;
2611 }
2612
2613 var strings = str.split(":");
2614 var l = strings.length, i = l;
2615 var ms = 0, parsed;
2616
2617 if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
2618 throw new Error("tick only understands numbers and 'h:m:s'");
2619 }
2620
2621 while (i--) {
2622 parsed = parseInt(strings[i], 10);
2623
2624 if (parsed >= 60) {
2625 throw new Error("Invalid time " + str);
2626 }
2627
2628 ms += parsed * Math.pow(60, (l - i - 1));
2629 }
2630
2631 return ms * 1000;
2632 }
2633
2634 function createObject(object) {
2635 var newObject;
2636
2637 if (Object.create) {
2638 newObject = Object.create(object);
2639 } else {
2640 var F = function () {};
2641 F.prototype = object;
2642 newObject = new F();
2643 }
2644
2645 newObject.Date.clock = newObject;
2646 return newObject;
2647 }
2648
2649 sinon.clock = {
2650 now: 0,
2651
2652 create: function create(now) {
2653 var clock = createObject(this);
2654
2655 if (typeof now == "number") {
2656 clock.now = now;
2657 }
2658
2659 if (!!now && typeof now == "object") {
2660 throw new TypeError("now should be milliseconds since UNIX epoch");
2661 }
2662
2663 return clock;
2664 },
2665
2666 setTimeout: function setTimeout(callback, timeout) {
2667 return addTimer.call(this, arguments, false);
2668 },
2669
2670 clearTimeout: function clearTimeout(timerId) {
2671 if (!this.timeouts) {
2672 this.timeouts = [];
2673 }
2674
2675 if (timerId in this.timeouts) {
2676 delete this.timeouts[timerId];
2677 }
2678 },
2679
2680 setInterval: function setInterval(callback, timeout) {
2681 return addTimer.call(this, arguments, true);
2682 },
2683
2684 clearInterval: function clearInterval(timerId) {
2685 this.clearTimeout(timerId);
2686 },
2687
2688 tick: function tick(ms) {
2689 ms = typeof ms == "number" ? ms : parseTime(ms);
2690 var tickFrom = this.now, tickTo = this.now + ms, previous = this.now;
2691 var timer = this.firstTimerInRange(tickFrom, tickTo);
2692
2693 var firstException;
2694 while (timer && tickFrom <= tickTo) {
2695 if (this.timeouts[timer.id]) {
2696 tickFrom = this.now = timer.callAt;
2697 try {
2698 this.callTimer(timer);
2699 } catch (e) {
2700 firstException = firstException || e;
2701 }
2702 }
2703
2704 timer = this.firstTimerInRange(previous, tickTo);
2705 previous = tickFrom;
2706 }
2707
2708 this.now = tickTo;
2709
2710 if (firstException) {
2711 throw firstException;
2712 }
2713
2714 return this.now;
2715 },
2716
2717 firstTimerInRange: function (from, to) {
2718 var timer, smallest, originalTimer;
2719
2720 for (var id in this.timeouts) {
2721 if (this.timeouts.hasOwnProperty(id)) {
2722 if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) {
2723 continue;
2724 }
2725
2726 if (!smallest || this.timeouts[id].callAt < smallest) {
2727 originalTimer = this.timeouts[id];
2728 smallest = this.timeouts[id].callAt;
2729
2730 timer = {
2731 func: this.timeouts[id].func,
2732 callAt: this.timeouts[id].callAt,
2733 interval: this.timeouts[id].interval,
2734 id: this.timeouts[id].id,
2735 invokeArgs: this.timeouts[id].invokeArgs
2736 };
2737 }
2738 }
2739 }
2740
2741 return timer || null;
2742 },
2743
2744 callTimer: function (timer) {
2745 if (typeof timer.interval == "number") {
2746 this.timeouts[timer.id].callAt += timer.interval;
2747 } else {
2748 delete this.timeouts[timer.id];
2749 }
2750
2751 try {
2752 if (typeof timer.func == "function") {
2753 timer.func.apply(null, timer.invokeArgs);
2754 } else {
2755 eval(timer.func);
2756 }
2757 } catch (e) {
2758 var exception = e;
2759 }
2760
2761 if (!this.timeouts[timer.id]) {
2762 if (exception) {
2763 throw exception;
2764 }
2765 return;
2766 }
2767
2768 if (exception) {
2769 throw exception;
2770 }
2771 },
2772
2773 reset: function reset() {
2774 this.timeouts = {};
2775 },
2776
2777 Date: (function () {
2778 var NativeDate = Date;
2779
2780 function ClockDate(year, month, date, hour, minute, second, ms) {
2781 // Defensive and verbose to avoid potential harm in passing
2782 // explicit undefined when user does not pass argument
2783 switch (arguments.length) {
2784 case 0:
2785 return new NativeDate(ClockDate.clock.now);
2786 case 1:
2787 return new NativeDate(year);
2788 case 2:
2789 return new NativeDate(year, month);
2790 case 3:
2791 return new NativeDate(year, month, date);
2792 case 4:
2793 return new NativeDate(year, month, date, hour);
2794 case 5:
2795 return new NativeDate(year, month, date, hour, minute);
2796 case 6:
2797 return new NativeDate(year, month, date, hour, minute, second);
2798 default:
2799 return new NativeDate(year, month, date, hour, minute, second, ms);
2800 }
2801 }
2802
2803 return mirrorDateProperties(ClockDate, NativeDate);
2804 }())
2805 };
2806
2807 function mirrorDateProperties(target, source) {
2808 if (source.now) {
2809 target.now = function now() {
2810 return target.clock.now;
2811 };
2812 } else {
2813 delete target.now;
2814 }
2815
2816 if (source.toSource) {
2817 target.toSource = function toSource() {
2818 return source.toSource();
2819 };
2820 } else {
2821 delete target.toSource;
2822 }
2823
2824 target.toString = function toString() {
2825 return source.toString();
2826 };
2827
2828 target.prototype = source.prototype;
2829 target.parse = source.parse;
2830 target.UTC = source.UTC;
2831 target.prototype.toUTCString = source.prototype.toUTCString;
2832 return target;
2833 }
2834
2835 var methods = ["Date", "setTimeout", "setInterval",
2836 "clearTimeout", "clearInterval"];
2837
2838 function restore() {
2839 var method;
2840
2841 for (var i = 0, l = this.methods.length; i < l; i++) {
2842 method = this.methods[i];
2843 if (global[method].hadOwnProperty) {
2844 global[method] = this["_" + method];
2845 } else {
2846 delete global[method];
2847 }
2848 }
2849
2850 // Prevent multiple executions which will completely remove these props
2851 this.methods = [];
2852 }
2853
2854 function stubGlobal(method, clock) {
2855 clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method);
2856 clock["_" + method] = global[method];
2857
2858 if (method == "Date") {
2859 var date = mirrorDateProperties(clock[method], global[method]);
2860 global[method] = date;
2861 } else {
2862 global[method] = function () {
2863 return clock[method].apply(clock, arguments);
2864 };
2865
2866 for (var prop in clock[method]) {
2867 if (clock[method].hasOwnProperty(prop)) {
2868 global[method][prop] = clock[method][prop];
2869 }
2870 }
2871 }
2872
2873 global[method].clock = clock;
2874 }
2875
2876 sinon.useFakeTimers = function useFakeTimers(now) {
2877 var clock = sinon.clock.create(now);
2878 clock.restore = restore;
2879 clock.methods = Array.prototype.slice.call(arguments,
2880 typeof now == "number" ? 1 : 0);
2881
2882 if (clock.methods.length === 0) {
2883 clock.methods = methods;
2884 }
2885
2886 for (var i = 0, l = clock.methods.length; i < l; i++) {
2887 stubGlobal(clock.methods[i], clock);
2888 }
2889
2890 return clock;
2891 };
2892 }(typeof global != "undefined" && typeof global !== "function" ? global : this));
2893
2894 sinon.timers = {
2895 setTimeout: setTimeout,
2896 clearTimeout: clearTimeout,
2897 setInterval: setInterval,
2898 clearInterval: clearInterval,
2899 Date: Date
2900 };
2901
2902 if (typeof module == "object" && typeof require == "function") {
2903 module.exports = sinon;
2904 }
2905
2906 /*jslint eqeqeq: false, onevar: false*/
2907 /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
2908 /**
2909 * Minimal Event interface implementation
2910 *
2911 * Original implementation by Sven Fuchs: https://gist.github.com/995028
2912 * Modifications and tests by Christian Johansen.
2913 *
2914 * @author Sven Fuchs (svenfuchs@artweb-design.de)
2915 * @author Christian Johansen (christian@cjohansen.no)
2916 * @license BSD
2917 *
2918 * Copyright (c) 2011 Sven Fuchs, Christian Johansen
2919 */
2920
2921 if (typeof sinon == "undefined") {
2922 this.sinon = {};
2923 }
2924
2925 (function () {
2926 var push = [].push;
2927
2928 sinon.Event = function Event(type, bubbles, cancelable) {
2929 this.initEvent(type, bubbles, cancelable);
2930 };
2931
2932 sinon.Event.prototype = {
2933 initEvent: function(type, bubbles, cancelable) {
2934 this.type = type;
2935 this.bubbles = bubbles;
2936 this.cancelable = cancelable;
2937 },
2938
2939 stopPropagation: function () {},
2940
2941 preventDefault: function () {
2942 this.defaultPrevented = true;
2943 }
2944 };
2945
2946 sinon.EventTarget = {
2947 addEventListener: function addEventListener(event, listener, useCapture) {
2948 this.eventListeners = this.eventListeners || {};
2949 this.eventListeners[event] = this.eventListeners[event] || [];
2950 push.call(this.eventListeners[event], listener);
2951 },
2952
2953 removeEventListener: function removeEventListener(event, listener, useCapture) {
2954 var listeners = this.eventListeners && this.eventListeners[event] || [];
2955
2956 for (var i = 0, l = listeners.length; i < l; ++i) {
2957 if (listeners[i] == listener) {
2958 return listeners.splice(i, 1);
2959 }
2960 }
2961 },
2962
2963 dispatchEvent: function dispatchEvent(event) {
2964 var type = event.type;
2965 var listeners = this.eventListeners && this.eventListeners[type] || [];
2966
2967 for (var i = 0; i < listeners.length; i++) {
2968 if (typeof listeners[i] == "function") {
2969 listeners[i].call(this, event);
2970 } else {
2971 listeners[i].handleEvent(event);
2972 }
2973 }
2974
2975 return !!event.defaultPrevented;
2976 }
2977 };
2978 }());
2979
2980 /**
2981 * @depend ../../sinon.js
2982 * @depend event.js
2983 */
2984 /*jslint eqeqeq: false, onevar: false*/
2985 /*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
2986 /**
2987 * Fake XMLHttpRequest object
2988 *
2989 * @author Christian Johansen (christian@cjohansen.no)
2990 * @license BSD
2991 *
2992 * Copyright (c) 2010-2013 Christian Johansen
2993 */
2994
2995 if (typeof sinon == "undefined") {
2996 this.sinon = {};
2997 }
2998 sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest };
2999
3000 // wrapper for global
3001 (function(global) {
3002 var xhr = sinon.xhr;
3003 xhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
3004 xhr.GlobalActiveXObject = global.ActiveXObject;
3005 xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined";
3006 xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined";
3007 xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX
3008 ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false;
3009
3010 /*jsl:ignore*/
3011 var unsafeHeaders = {
3012 "Accept-Charset": true,
3013 "Accept-Encoding": true,
3014 "Connection": true,
3015 "Content-Length": true,
3016 "Cookie": true,
3017 "Cookie2": true,
3018 "Content-Transfer-Encoding": true,
3019 "Date": true,
3020 "Expect": true,
3021 "Host": true,
3022 "Keep-Alive": true,
3023 "Referer": true,
3024 "TE": true,
3025 "Trailer": true,
3026 "Transfer-Encoding": true,
3027 "Upgrade": true,
3028 "User-Agent": true,
3029 "Via": true
3030 };
3031 /*jsl:end*/
3032
3033 function FakeXMLHttpRequest() {
3034 this.readyState = FakeXMLHttpRequest.UNSENT;
3035 this.requestHeaders = {};
3036 this.requestBody = null;
3037 this.status = 0;
3038 this.statusText = "";
3039
3040 if (typeof FakeXMLHttpRequest.onCreate == "function") {
3041 FakeXMLHttpRequest.onCreate(this);
3042 }
3043 }
3044
3045 function verifyState(xhr) {
3046 if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
3047 throw new Error("INVALID_STATE_ERR");
3048 }
3049
3050 if (xhr.sendFlag) {
3051 throw new Error("INVALID_STATE_ERR");
3052 }
3053 }
3054
3055 // filtering to enable a white-list version of Sinon FakeXhr,
3056 // where whitelisted requests are passed through to real XHR
3057 function each(collection, callback) {
3058 if (!collection) return;
3059 for (var i = 0, l = collection.length; i < l; i += 1) {
3060 callback(collection[i]);
3061 }
3062 }
3063 function some(collection, callback) {
3064 for (var index = 0; index < collection.length; index++) {
3065 if(callback(collection[index]) === true) return true;
3066 };
3067 return false;
3068 }
3069 // largest arity in XHR is 5 - XHR#open
3070 var apply = function(obj,method,args) {
3071 switch(args.length) {
3072 case 0: return obj[method]();
3073 case 1: return obj[method](args[0]);
3074 case 2: return obj[method](args[0],args[1]);
3075 case 3: return obj[method](args[0],args[1],args[2]);
3076 case 4: return obj[method](args[0],args[1],args[2],args[3]);
3077 case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]);
3078 };
3079 };
3080
3081 FakeXMLHttpRequest.filters = [];
3082 FakeXMLHttpRequest.addFilter = function(fn) {
3083 this.filters.push(fn)
3084 };
3085 var IE6Re = /MSIE 6/;
3086 FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) {
3087 var xhr = new sinon.xhr.workingXHR();
3088 each(["open","setRequestHeader","send","abort","getResponseHeader",
3089 "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"],
3090 function(method) {
3091 fakeXhr[method] = function() {
3092 return apply(xhr,method,arguments);
3093 };
3094 });
3095
3096 var copyAttrs = function(args) {
3097 each(args, function(attr) {
3098 try {
3099 fakeXhr[attr] = xhr[attr]
3100 } catch(e) {
3101 if(!IE6Re.test(navigator.userAgent)) throw e;
3102 }
3103 });
3104 };
3105
3106 var stateChange = function() {
3107 fakeXhr.readyState = xhr.readyState;
3108 if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
3109 copyAttrs(["status","statusText"]);
3110 }
3111 if(xhr.readyState >= FakeXMLHttpRequest.LOADING) {
3112 copyAttrs(["responseText"]);
3113 }
3114 if(xhr.readyState === FakeXMLHttpRequest.DONE) {
3115 copyAttrs(["responseXML"]);
3116 }
3117 if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr);
3118 };
3119 if(xhr.addEventListener) {
3120 for(var event in fakeXhr.eventListeners) {
3121 if(fakeXhr.eventListeners.hasOwnProperty(event)) {
3122 each(fakeXhr.eventListeners[event],function(handler) {
3123 xhr.addEventListener(event, handler);
3124 });
3125 }
3126 }
3127 xhr.addEventListener("readystatechange",stateChange);
3128 } else {
3129 xhr.onreadystatechange = stateChange;
3130 }
3131 apply(xhr,"open",xhrArgs);
3132 };
3133 FakeXMLHttpRequest.useFilters = false;
3134
3135 function verifyRequestSent(xhr) {
3136 if (xhr.readyState == FakeXMLHttpRequest.DONE) {
3137 throw new Error("Request done");
3138 }
3139 }
3140
3141 function verifyHeadersReceived(xhr) {
3142 if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
3143 throw new Error("No headers received");
3144 }
3145 }
3146
3147 function verifyResponseBodyType(body) {
3148 if (typeof body != "string") {
3149 var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
3150 body + ", which is not a string.");
3151 error.name = "InvalidBodyException";
3152 throw error;
3153 }
3154 }
3155
3156 sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {
3157 async: true,
3158
3159 open: function open(method, url, async, username, password) {
3160 this.method = method;
3161 this.url = url;
3162 this.async = typeof async == "boolean" ? async : true;
3163 this.username = username;
3164 this.password = password;
3165 this.responseText = null;
3166 this.responseXML = null;
3167 this.requestHeaders = {};
3168 this.sendFlag = false;
3169 if(sinon.FakeXMLHttpRequest.useFilters === true) {
3170 var xhrArgs = arguments;
3171 var defake = some(FakeXMLHttpRequest.filters,function(filter) {
3172 return filter.apply(this,xhrArgs)
3173 });
3174 if (defake) {
3175 return sinon.FakeXMLHttpRequest.defake(this,arguments);
3176 }
3177 }
3178 this.readyStateChange(FakeXMLHttpRequest.OPENED);
3179 },
3180
3181 readyStateChange: function readyStateChange(state) {
3182 this.readyState = state;
3183
3184 if (typeof this.onreadystatechange == "function") {
3185 try {
3186 this.onreadystatechange();
3187 } catch (e) {
3188 sinon.logError("Fake XHR onreadystatechange handler", e);
3189 }
3190 }
3191
3192 this.dispatchEvent(new sinon.Event("readystatechange"));
3193 },
3194
3195 setRequestHeader: function setRequestHeader(header, value) {
3196 verifyState(this);
3197
3198 if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
3199 throw new Error("Refused to set unsafe header \"" + header + "\"");
3200 }
3201
3202 if (this.requestHeaders[header]) {
3203 this.requestHeaders[header] += "," + value;
3204 } else {
3205 this.requestHeaders[header] = value;
3206 }
3207 },
3208
3209 // Helps testing
3210 setResponseHeaders: function setResponseHeaders(headers) {
3211 this.responseHeaders = {};
3212
3213 for (var header in headers) {
3214 if (headers.hasOwnProperty(header)) {
3215 this.responseHeaders[header] = headers[header];
3216 }
3217 }
3218
3219 if (this.async) {
3220 this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
3221 } else {
3222 this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;
3223 }
3224 },
3225
3226 // Currently treats ALL data as a DOMString (i.e. no Document)
3227 send: function send(data) {
3228 verifyState(this);
3229
3230 if (!/^(get|head)$/i.test(this.method)) {
3231 if (this.requestHeaders["Content-Type"]) {
3232 var value = this.requestHeaders["Content-Type"].split(";");
3233 this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";
3234 } else {
3235 this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
3236 }
3237
3238 this.requestBody = data;
3239 }
3240
3241 this.errorFlag = false;
3242 this.sendFlag = this.async;
3243 this.readyStateChange(FakeXMLHttpRequest.OPENED);
3244
3245 if (typeof this.onSend == "function") {
3246 this.onSend(this);
3247 }
3248 },
3249
3250 abort: function abort() {
3251 this.aborted = true;
3252 this.responseText = null;
3253 this.errorFlag = true;
3254 this.requestHeaders = {};
3255
3256 if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) {
3257 this.readyStateChange(sinon.FakeXMLHttpRequest.DONE);
3258 this.sendFlag = false;
3259 }
3260
3261 this.readyState = sinon.FakeXMLHttpRequest.UNSENT;
3262 },
3263
3264 getResponseHeader: function getResponseHeader(header) {
3265 if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
3266 return null;
3267 }
3268
3269 if (/^Set-Cookie2?$/i.test(header)) {
3270 return null;
3271 }
3272
3273 header = header.toLowerCase();
3274
3275 for (var h in this.responseHeaders) {
3276 if (h.toLowerCase() == header) {
3277 return this.responseHeaders[h];
3278 }
3279 }
3280
3281 return null;
3282 },
3283
3284 getAllResponseHeaders: function getAllResponseHeaders() {
3285 if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
3286 return "";
3287 }
3288
3289 var headers = "";
3290
3291 for (var header in this.responseHeaders) {
3292 if (this.responseHeaders.hasOwnProperty(header) &&
3293 !/^Set-Cookie2?$/i.test(header)) {
3294 headers += header + ": " + this.responseHeaders[header] + "\r\n";
3295 }
3296 }
3297
3298 return headers;
3299 },
3300
3301 setResponseBody: function setResponseBody(body) {
3302 verifyRequestSent(this);
3303 verifyHeadersReceived(this);
3304 verifyResponseBodyType(body);
3305
3306 var chunkSize = this.chunkSize || 10;
3307 var index = 0;
3308 this.responseText = "";
3309
3310 do {
3311 if (this.async) {
3312 this.readyStateChange(FakeXMLHttpRequest.LOADING);
3313 }
3314
3315 this.responseText += body.substring(index, index + chunkSize);
3316 index += chunkSize;
3317 } while (index < body.length);
3318
3319 var type = this.getResponseHeader("Content-Type");
3320
3321 if (this.responseText &&
3322 (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
3323 try {
3324 this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
3325 } catch (e) {
3326 // Unable to parse XML - no biggie
3327 }
3328 }
3329
3330 if (this.async) {
3331 this.readyStateChange(FakeXMLHttpRequest.DONE);
3332 } else {
3333 this.readyState = FakeXMLHttpRequest.DONE;
3334 }
3335 },
3336
3337 respond: function respond(status, headers, body) {
3338 this.setResponseHeaders(headers || {});
3339 this.status = typeof status == "number" ? status : 200;
3340 this.statusText = FakeXMLHttpRequest.statusCodes[this.status];
3341 this.setResponseBody(body || "");
3342 }
3343 });
3344
3345 sinon.extend(FakeXMLHttpRequest, {
3346 UNSENT: 0,
3347 OPENED: 1,
3348 HEADERS_RECEIVED: 2,
3349 LOADING: 3,
3350 DONE: 4
3351 });
3352
3353 // Borrowed from JSpec
3354 FakeXMLHttpRequest.parseXML = function parseXML(text) {
3355 var xmlDoc;
3356
3357 if (typeof DOMParser != "undefined") {
3358 var parser = new DOMParser();
3359 xmlDoc = parser.parseFromString(text, "text/xml");
3360 } else {
3361 xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
3362 xmlDoc.async = "false";
3363 xmlDoc.loadXML(text);
3364 }
3365
3366 return xmlDoc;
3367 };
3368
3369 FakeXMLHttpRequest.statusCodes = {
3370 100: "Continue",
3371 101: "Switching Protocols",
3372 200: "OK",
3373 201: "Created",
3374 202: "Accepted",
3375 203: "Non-Authoritative Information",
3376 204: "No Content",
3377 205: "Reset Content",
3378 206: "Partial Content",
3379 300: "Multiple Choice",
3380 301: "Moved Permanently",
3381 302: "Found",
3382 303: "See Other",
3383 304: "Not Modified",
3384 305: "Use Proxy",
3385 307: "Temporary Redirect",
3386 400: "Bad Request",
3387 401: "Unauthorized",
3388 402: "Payment Required",
3389 403: "Forbidden",
3390 404: "Not Found",
3391 405: "Method Not Allowed",
3392 406: "Not Acceptable",
3393 407: "Proxy Authentication Required",
3394 408: "Request Timeout",
3395 409: "Conflict",
3396 410: "Gone",
3397 411: "Length Required",
3398 412: "Precondition Failed",
3399 413: "Request Entity Too Large",
3400 414: "Request-URI Too Long",
3401 415: "Unsupported Media Type",
3402 416: "Requested Range Not Satisfiable",
3403 417: "Expectation Failed",
3404 422: "Unprocessable Entity",
3405 500: "Internal Server Error",
3406 501: "Not Implemented",
3407 502: "Bad Gateway",
3408 503: "Service Unavailable",
3409 504: "Gateway Timeout",
3410 505: "HTTP Version Not Supported"
3411 };
3412
3413 sinon.useFakeXMLHttpRequest = function () {
3414 sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
3415 if (xhr.supportsXHR) {
3416 global.XMLHttpRequest = xhr.GlobalXMLHttpRequest;
3417 }
3418
3419 if (xhr.supportsActiveX) {
3420 global.ActiveXObject = xhr.GlobalActiveXObject;
3421 }
3422
3423 delete sinon.FakeXMLHttpRequest.restore;
3424
3425 if (keepOnCreate !== true) {
3426 delete sinon.FakeXMLHttpRequest.onCreate;
3427 }
3428 };
3429 if (xhr.supportsXHR) {
3430 global.XMLHttpRequest = sinon.FakeXMLHttpRequest;
3431 }
3432
3433 if (xhr.supportsActiveX) {
3434 global.ActiveXObject = function ActiveXObject(objId) {
3435 if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
3436
3437 return new sinon.FakeXMLHttpRequest();
3438 }
3439
3440 return new xhr.GlobalActiveXObject(objId);
3441 };
3442 }
3443
3444 return sinon.FakeXMLHttpRequest;
3445 };
3446
3447 sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
3448 })(this);
3449
3450 if (typeof module == "object" && typeof require == "function") {
3451 module.exports = sinon;
3452 }
3453
3454 /**
3455 * @depend fake_xml_http_request.js
3456 */
3457 /*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/
3458 /*global module, require, window*/
3459 /**
3460 * The Sinon "server" mimics a web server that receives requests from
3461 * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
3462 * both synchronously and asynchronously. To respond synchronuously, canned
3463 * answers have to be provided upfront.
3464 *
3465 * @author Christian Johansen (christian@cjohansen.no)
3466 * @license BSD
3467 *
3468 * Copyright (c) 2010-2013 Christian Johansen
3469 */
3470
3471 if (typeof sinon == "undefined") {
3472 var sinon = {};
3473 }
3474
3475 sinon.fakeServer = (function () {
3476 var push = [].push;
3477 function F() {}
3478
3479 function create(proto) {
3480 F.prototype = proto;
3481 return new F();
3482 }
3483
3484 function responseArray(handler) {
3485 var response = handler;
3486
3487 if (Object.prototype.toString.call(handler) != "[object Array]") {
3488 response = [200, {}, handler];
3489 }
3490
3491 if (typeof response[2] != "string") {
3492 throw new TypeError("Fake server response body should be string, but was " +
3493 typeof response[2]);
3494 }
3495
3496 return response;
3497 }
3498
3499 var wloc = typeof window !== "undefined" ? window.location : {};
3500 var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
3501
3502 function matchOne(response, reqMethod, reqUrl) {
3503 var rmeth = response.method;
3504 var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase();
3505 var url = response.url;
3506 var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl));
3507
3508 return matchMethod && matchUrl;
3509 }
3510
3511 function match(response, request) {
3512 var requestMethod = this.getHTTPMethod(request);
3513 var requestUrl = request.url;
3514
3515 if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
3516 requestUrl = requestUrl.replace(rCurrLoc, "");
3517 }
3518
3519 if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
3520 if (typeof response.response == "function") {
3521 var ru = response.url;
3522 var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1));
3523 return response.response.apply(response, args);
3524 }
3525
3526 return true;
3527 }
3528
3529 return false;
3530 }
3531
3532 function log(response, request) {
3533 var str;
3534
3535 str = "Request:\n" + sinon.format(request) + "\n\n";
3536 str += "Response:\n" + sinon.format(response) + "\n\n";
3537
3538 sinon.log(str);
3539 }
3540
3541 return {
3542 create: function () {
3543 var server = create(this);
3544 this.xhr = sinon.useFakeXMLHttpRequest();
3545 server.requests = [];
3546
3547 this.xhr.onCreate = function (xhrObj) {
3548 server.addRequest(xhrObj);
3549 };
3550
3551 return server;
3552 },
3553
3554 addRequest: function addRequest(xhrObj) {
3555 var server = this;
3556 push.call(this.requests, xhrObj);
3557
3558 xhrObj.onSend = function () {
3559 server.handleRequest(this);
3560 };
3561
3562 if (this.autoRespond && !this.responding) {
3563 setTimeout(function () {
3564 server.responding = false;
3565 server.respond();
3566 }, this.autoRespondAfter || 10);
3567
3568 this.responding = true;
3569 }
3570 },
3571
3572 getHTTPMethod: function getHTTPMethod(request) {
3573 if (this.fakeHTTPMethods && /post/i.test(request.method)) {
3574 var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
3575 return !!matches ? matches[1] : request.method;
3576 }
3577
3578 return request.method;
3579 },
3580
3581 handleRequest: function handleRequest(xhr) {
3582 if (xhr.async) {
3583 if (!this.queue) {
3584 this.queue = [];
3585 }
3586
3587 push.call(this.queue, xhr);
3588 } else {
3589 this.processRequest(xhr);
3590 }
3591 },
3592
3593 respondWith: function respondWith(method, url, body) {
3594 if (arguments.length == 1 && typeof method != "function") {
3595 this.response = responseArray(method);
3596 return;
3597 }
3598
3599 if (!this.responses) { this.responses = []; }
3600
3601 if (arguments.length == 1) {
3602 body = method;
3603 url = method = null;
3604 }
3605
3606 if (arguments.length == 2) {
3607 body = url;
3608 url = method;
3609 method = null;
3610 }
3611
3612 push.call(this.responses, {
3613 method: method,
3614 url: url,
3615 response: typeof body == "function" ? body : responseArray(body)
3616 });
3617 },
3618
3619 respond: function respond() {
3620 if (arguments.length > 0) this.respondWith.apply(this, arguments);
3621 var queue = this.queue || [];
3622 var request;
3623
3624 while(request = queue.shift()) {
3625 this.processRequest(request);
3626 }
3627 },
3628
3629 processRequest: function processRequest(request) {
3630 try {
3631 if (request.aborted) {
3632 return;
3633 }
3634
3635 var response = this.response || [404, {}, ""];
3636
3637 if (this.responses) {
3638 for (var i = 0, l = this.responses.length; i < l; i++) {
3639 if (match.call(this, this.responses[i], request)) {
3640 response = this.responses[i].response;
3641 break;
3642 }
3643 }
3644 }
3645
3646 if (request.readyState != 4) {
3647 log(response, request);
3648
3649 request.respond(response[0], response[1], response[2]);
3650 }
3651 } catch (e) {
3652 sinon.logError("Fake server request processing", e);
3653 }
3654 },
3655
3656 restore: function restore() {
3657 return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
3658 }
3659 };
3660 }());
3661
3662 if (typeof module == "object" && typeof require == "function") {
3663 module.exports = sinon;
3664 }
3665
3666 /**
3667 * @depend fake_server.js
3668 * @depend fake_timers.js
3669 */
3670 /*jslint browser: true, eqeqeq: false, onevar: false*/
3671 /*global sinon*/
3672 /**
3673 * Add-on for sinon.fakeServer that automatically handles a fake timer along with
3674 * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery
3675 * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead,
3676 * it polls the object for completion with setInterval. Dispite the direct
3677 * motivation, there is nothing jQuery-specific in this file, so it can be used
3678 * in any environment where the ajax implementation depends on setInterval or
3679 * setTimeout.
3680 *
3681 * @author Christian Johansen (christian@cjohansen.no)
3682 * @license BSD
3683 *
3684 * Copyright (c) 2010-2013 Christian Johansen
3685 */
3686
3687 (function () {
3688 function Server() {}
3689 Server.prototype = sinon.fakeServer;
3690
3691 sinon.fakeServerWithClock = new Server();
3692
3693 sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {
3694 if (xhr.async) {
3695 if (typeof setTimeout.clock == "object") {
3696 this.clock = setTimeout.clock;
3697 } else {
3698 this.clock = sinon.useFakeTimers();
3699 this.resetClock = true;
3700 }
3701
3702 if (!this.longestTimeout) {
3703 var clockSetTimeout = this.clock.setTimeout;
3704 var clockSetInterval = this.clock.setInterval;
3705 var server = this;
3706
3707 this.clock.setTimeout = function (fn, timeout) {
3708 server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
3709
3710 return clockSetTimeout.apply(this, arguments);
3711 };
3712
3713 this.clock.setInterval = function (fn, timeout) {
3714 server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
3715
3716 return clockSetInterval.apply(this, arguments);
3717 };
3718 }
3719 }
3720
3721 return sinon.fakeServer.addRequest.call(this, xhr);
3722 };
3723
3724 sinon.fakeServerWithClock.respond = function respond() {
3725 var returnVal = sinon.fakeServer.respond.apply(this, arguments);
3726
3727 if (this.clock) {
3728 this.clock.tick(this.longestTimeout || 0);
3729 this.longestTimeout = 0;
3730
3731 if (this.resetClock) {
3732 this.clock.restore();
3733 this.resetClock = false;
3734 }
3735 }
3736
3737 return returnVal;
3738 };
3739
3740 sinon.fakeServerWithClock.restore = function restore() {
3741 if (this.clock) {
3742 this.clock.restore();
3743 }
3744
3745 return sinon.fakeServer.restore.apply(this, arguments);
3746 };
3747 }());
3748
3749 /**
3750 * @depend ../sinon.js
3751 * @depend collection.js
3752 * @depend util/fake_timers.js
3753 * @depend util/fake_server_with_clock.js
3754 */
3755 /*jslint eqeqeq: false, onevar: false, plusplus: false*/
3756 /*global require, module*/
3757 /**
3758 * Manages fake collections as well as fake utilities such as Sinon's
3759 * timers and fake XHR implementation in one convenient object.
3760 *
3761 * @author Christian Johansen (christian@cjohansen.no)
3762 * @license BSD
3763 *
3764 * Copyright (c) 2010-2013 Christian Johansen
3765 */
3766
3767 if (typeof module == "object" && typeof require == "function") {
3768 var sinon = require("../sinon");
3769 sinon.extend(sinon, require("./util/fake_timers"));
3770 }
3771
3772 (function () {
3773 var push = [].push;
3774
3775 function exposeValue(sandbox, config, key, value) {
3776 if (!value) {
3777 return;
3778 }
3779
3780 if (config.injectInto) {
3781 config.injectInto[key] = value;
3782 } else {
3783 push.call(sandbox.args, value);
3784 }
3785 }
3786
3787 function prepareSandboxFromConfig(config) {
3788 var sandbox = sinon.create(sinon.sandbox);
3789
3790 if (config.useFakeServer) {
3791 if (typeof config.useFakeServer == "object") {
3792 sandbox.serverPrototype = config.useFakeServer;
3793 }
3794
3795 sandbox.useFakeServer();
3796 }
3797
3798 if (config.useFakeTimers) {
3799 if (typeof config.useFakeTimers == "object") {
3800 sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);
3801 } else {
3802 sandbox.useFakeTimers();
3803 }
3804 }
3805
3806 return sandbox;
3807 }
3808
3809 sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {
3810 useFakeTimers: function useFakeTimers() {
3811 this.clock = sinon.useFakeTimers.apply(sinon, arguments);
3812
3813 return this.add(this.clock);
3814 },
3815
3816 serverPrototype: sinon.fakeServer,
3817
3818 useFakeServer: function useFakeServer() {
3819 var proto = this.serverPrototype || sinon.fakeServer;
3820
3821 if (!proto || !proto.create) {
3822 return null;
3823 }
3824
3825 this.server = proto.create();
3826 return this.add(this.server);
3827 },
3828
3829 inject: function (obj) {
3830 sinon.collection.inject.call(this, obj);
3831
3832 if (this.clock) {
3833 obj.clock = this.clock;
3834 }
3835
3836 if (this.server) {
3837 obj.server = this.server;
3838 obj.requests = this.server.requests;
3839 }
3840
3841 return obj;
3842 },
3843
3844 create: function (config) {
3845 if (!config) {
3846 return sinon.create(sinon.sandbox);
3847 }
3848
3849 var sandbox = prepareSandboxFromConfig(config);
3850 sandbox.args = sandbox.args || [];
3851 var prop, value, exposed = sandbox.inject({});
3852
3853 if (config.properties) {
3854 for (var i = 0, l = config.properties.length; i < l; i++) {
3855 prop = config.properties[i];
3856 value = exposed[prop] || prop == "sandbox" && sandbox;
3857 exposeValue(sandbox, config, prop, value);
3858 }
3859 } else {
3860 exposeValue(sandbox, config, "sandbox", value);
3861 }
3862
3863 return sandbox;
3864 }
3865 });
3866
3867 sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;
3868
3869 if (typeof module == "object" && typeof require == "function") {
3870 module.exports = sinon.sandbox;
3871 }
3872 }());
3873
3874 /**
3875 * @depend ../sinon.js
3876 * @depend stub.js
3877 * @depend mock.js
3878 * @depend sandbox.js
3879 */
3880 /*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/
3881 /*global module, require, sinon*/
3882 /**
3883 * Test function, sandboxes fakes
3884 *
3885 * @author Christian Johansen (christian@cjohansen.no)
3886 * @license BSD
3887 *
3888 * Copyright (c) 2010-2013 Christian Johansen
3889 */
3890
3891 (function (sinon) {
3892 var commonJSModule = typeof module == "object" && typeof require == "function";
3893
3894 if (!sinon && commonJSModule) {
3895 sinon = require("../sinon");
3896 }
3897
3898 if (!sinon) {
3899 return;
3900 }
3901
3902 function test(callback) {
3903 var type = typeof callback;
3904
3905 if (type != "function") {
3906 throw new TypeError("sinon.test needs to wrap a test function, got " + type);
3907 }
3908
3909 return function () {
3910 var config = sinon.getConfig(sinon.config);
3911 config.injectInto = config.injectIntoThis && this || config.injectInto;
3912 var sandbox = sinon.sandbox.create(config);
3913 var exception, result;
3914 var args = Array.prototype.slice.call(arguments).concat(sandbox.args);
3915
3916 try {
3917 result = callback.apply(this, args);
3918 } catch (e) {
3919 exception = e;
3920 }
3921
3922 if (typeof exception !== "undefined") {
3923 sandbox.restore();
3924 throw exception;
3925 }
3926 else {
3927 sandbox.verifyAndRestore();
3928 }
3929
3930 return result;
3931 };
3932 }
3933
3934 test.config = {
3935 injectIntoThis: true,
3936 injectInto: null,
3937 properties: ["spy", "stub", "mock", "clock", "server", "requests"],
3938 useFakeTimers: true,
3939 useFakeServer: true
3940 };
3941
3942 if (commonJSModule) {
3943 module.exports = test;
3944 } else {
3945 sinon.test = test;
3946 }
3947 }(typeof sinon == "object" && sinon || null));
3948
3949 /**
3950 * @depend ../sinon.js
3951 * @depend test.js
3952 */
3953 /*jslint eqeqeq: false, onevar: false, eqeqeq: false*/
3954 /*global module, require, sinon*/
3955 /**
3956 * Test case, sandboxes all test functions
3957 *
3958 * @author Christian Johansen (christian@cjohansen.no)
3959 * @license BSD
3960 *
3961 * Copyright (c) 2010-2013 Christian Johansen
3962 */
3963
3964 (function (sinon) {
3965 var commonJSModule = typeof module == "object" && typeof require == "function";
3966
3967 if (!sinon && commonJSModule) {
3968 sinon = require("../sinon");
3969 }
3970
3971 if (!sinon || !Object.prototype.hasOwnProperty) {
3972 return;
3973 }
3974
3975 function createTest(property, setUp, tearDown) {
3976 return function () {
3977 if (setUp) {
3978 setUp.apply(this, arguments);
3979 }
3980
3981 var exception, result;
3982
3983 try {
3984 result = property.apply(this, arguments);
3985 } catch (e) {
3986 exception = e;
3987 }
3988
3989 if (tearDown) {
3990 tearDown.apply(this, arguments);
3991 }
3992
3993 if (exception) {
3994 throw exception;
3995 }
3996
3997 return result;
3998 };
3999 }
4000
4001 function testCase(tests, prefix) {
4002 /*jsl:ignore*/
4003 if (!tests || typeof tests != "object") {
4004 throw new TypeError("sinon.testCase needs an object with test functions");
4005 }
4006 /*jsl:end*/
4007
4008 prefix = prefix || "test";
4009 var rPrefix = new RegExp("^" + prefix);
4010 var methods = {}, testName, property, method;
4011 var setUp = tests.setUp;
4012 var tearDown = tests.tearDown;
4013
4014 for (testName in tests) {
4015 if (tests.hasOwnProperty(testName)) {
4016 property = tests[testName];
4017
4018 if (/^(setUp|tearDown)$/.test(testName)) {
4019 continue;
4020 }
4021
4022 if (typeof property == "function" && rPrefix.test(testName)) {
4023 method = property;
4024
4025 if (setUp || tearDown) {
4026 method = createTest(property, setUp, tearDown);
4027 }
4028
4029 methods[testName] = sinon.test(method);
4030 } else {
4031 methods[testName] = tests[testName];
4032 }
4033 }
4034 }
4035
4036 return methods;
4037 }
4038
4039 if (commonJSModule) {
4040 module.exports = testCase;
4041 } else {
4042 sinon.testCase = testCase;
4043 }
4044 }(typeof sinon == "object" && sinon || null));
4045
4046 /**
4047 * @depend ../sinon.js
4048 * @depend stub.js
4049 */
4050 /*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/
4051 /*global module, require, sinon*/
4052 /**
4053 * Assertions matching the test spy retrieval interface.
4054 *
4055 * @author Christian Johansen (christian@cjohansen.no)
4056 * @license BSD
4057 *
4058 * Copyright (c) 2010-2013 Christian Johansen
4059 */
4060
4061 (function (sinon, global) {
4062 var commonJSModule = typeof module == "object" && typeof require == "function";
4063 var slice = Array.prototype.slice;
4064 var assert;
4065
4066 if (!sinon && commonJSModule) {
4067 sinon = require("../sinon");
4068 }
4069
4070 if (!sinon) {
4071 return;
4072 }
4073
4074 function verifyIsStub() {
4075 var method;
4076
4077 for (var i = 0, l = arguments.length; i < l; ++i) {
4078 method = arguments[i];
4079
4080 if (!method) {
4081 assert.fail("fake is not a spy");
4082 }
4083
4084 if (typeof method != "function") {
4085 assert.fail(method + " is not a function");
4086 }
4087
4088 if (typeof method.getCall != "function") {
4089 assert.fail(method + " is not stubbed");
4090 }
4091 }
4092 }
4093
4094 function failAssertion(object, msg) {
4095 object = object || global;
4096 var failMethod = object.fail || assert.fail;
4097 failMethod.call(object, msg);
4098 }
4099
4100 function mirrorPropAsAssertion(name, method, message) {
4101 if (arguments.length == 2) {
4102 message = method;
4103 method = name;
4104 }
4105
4106 assert[name] = function (fake) {
4107 verifyIsStub(fake);
4108
4109 var args = slice.call(arguments, 1);
4110 var failed = false;
4111
4112 if (typeof method == "function") {
4113 failed = !method(fake);
4114 } else {
4115 failed = typeof fake[method] == "function" ?
4116 !fake[method].apply(fake, args) : !fake[method];
4117 }
4118
4119 if (failed) {
4120 failAssertion(this, fake.printf.apply(fake, [message].concat(args)));
4121 } else {
4122 assert.pass(name);
4123 }
4124 };
4125 }
4126
4127 function exposedName(prefix, prop) {
4128 return !prefix || /^fail/.test(prop) ? prop :
4129 prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);
4130 };
4131
4132 assert = {
4133 failException: "AssertError",
4134
4135 fail: function fail(message) {
4136 var error = new Error(message);
4137 error.name = this.failException || assert.failException;
4138
4139 throw error;
4140 },
4141
4142 pass: function pass(assertion) {},
4143
4144 callOrder: function assertCallOrder() {
4145 verifyIsStub.apply(null, arguments);
4146 var expected = "", actual = "";
4147
4148 if (!sinon.calledInOrder(arguments)) {
4149 try {
4150 expected = [].join.call(arguments, ", ");
4151 actual = sinon.orderByFirstCall(slice.call(arguments)).join(", ");
4152 } catch (e) {
4153 // If this fails, we'll just fall back to the blank string
4154 }
4155
4156 failAssertion(this, "expected " + expected + " to be " +
4157 "called in order but were called as " + actual);
4158 } else {
4159 assert.pass("callOrder");
4160 }
4161 },
4162
4163 callCount: function assertCallCount(method, count) {
4164 verifyIsStub(method);
4165
4166 if (method.callCount != count) {
4167 var msg = "expected %n to be called " + sinon.timesInWords(count) +
4168 " but was called %c%C";
4169 failAssertion(this, method.printf(msg));
4170 } else {
4171 assert.pass("callCount");
4172 }
4173 },
4174
4175 expose: function expose(target, options) {
4176 if (!target) {
4177 throw new TypeError("target is null or undefined");
4178 }
4179
4180 var o = options || {};
4181 var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix;
4182 var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail;
4183
4184 for (var method in this) {
4185 if (method != "export" && (includeFail || !/^(fail)/.test(method))) {
4186 target[exposedName(prefix, method)] = this[method];
4187 }
4188 }
4189
4190 return target;
4191 }
4192 };
4193
4194 mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called");
4195 mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; },
4196 "expected %n to not have been called but was called %c%C");
4197 mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C");
4198 mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C");
4199 mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C");
4200 mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t");
4201 mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t");
4202 mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new");
4203 mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new");
4204 mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C");
4205 mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C");
4206 mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C");
4207 mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C");
4208 mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C");
4209 mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C");
4210 mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C");
4211 mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C");
4212 mirrorPropAsAssertion("threw", "%n did not throw exception%C");
4213 mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");
4214
4215 if (commonJSModule) {
4216 module.exports = assert;
4217 } else {
4218 sinon.assert = assert;
4219 }
4220 }(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : global));
4221
4222 return sinon;}.call(typeof window != 'undefined' && window || {}));
00 describe('L#noConflict', function() {
1 it('should restore the previous L value and return Leaflet namespace', function(){
2
3 expect(L.version).toBeDefined();
4
1 it('restores the previous L value and returns Leaflet namespace', function(){
2
3 expect(L.version).to.be.ok();
4
55 var L2 = L.noConflict();
6 this.after(function () {
7 window.L = L2;
8 });
96
10 expect(L).toEqual('test');
11 expect(L2.version).toBeDefined();
7 expect(L).to.eql('test');
8 expect(L2.version).to.be.ok();
9
10 window.L = L2;
1211 });
13 });
12 });
00 function noSpecs() {
1 xit('should have specs');
1 xit('has no specs');
22 }
33
44 if (!Array.prototype.map) {
0 describe("Control.Attribution", function () {
1
2 var map, control, container;
3
4 beforeEach(function () {
5 map = L.map(document.createElement('div'));
6 control = new L.Control.Attribution({
7 prefix: 'prefix'
8 }).addTo(map);
9 container = control.getContainer();
10 });
11
12 it("contains just prefix if no attributions added", function () {
13 expect(container.innerHTML).to.eql('prefix');
14 });
15
16 describe('#addAttribution', function () {
17 it('adds one attribution correctly', function () {
18 control.addAttribution('foo');
19 expect(container.innerHTML).to.eql('prefix | foo');
20 });
21
22 it('adds no duplicate attributions', function () {
23 control.addAttribution('foo');
24 control.addAttribution('foo');
25 expect(container.innerHTML).to.eql('prefix | foo');
26 });
27
28 it('adds several attributions listed with comma', function () {
29 control.addAttribution('foo');
30 control.addAttribution('bar');
31 expect(container.innerHTML).to.eql('prefix | foo, bar');
32 });
33 });
34
35 describe('#removeAttribution', function () {
36 it('removes attribution correctly', function () {
37 control.addAttribution('foo');
38 control.addAttribution('bar');
39 control.removeAttribution('foo');
40 expect(container.innerHTML).to.eql('prefix | bar');
41 });
42 it('does nothing if removing attribution that was not present', function () {
43 control.addAttribution('foo');
44 control.addAttribution('baz');
45 control.removeAttribution('bar');
46 control.removeAttribution('baz');
47 control.removeAttribution('baz');
48 control.removeAttribution('');
49 expect(container.innerHTML).to.eql('prefix | foo');
50 });
51 });
52
53 describe('#setPrefix', function () {
54 it('changes prefix', function () {
55 control.setPrefix('bla');
56 expect(container.innerHTML).to.eql('bla');
57 });
58 });
59
60 describe('control.attribution factory', function () {
61 it('creates Control.Attribution instance', function () {
62 var options = {prefix: 'prefix'};
63 expect(L.control.attribution(options)).to.eql(new L.Control.Attribution(options));
64 });
65 });
66
67 });
88 it("is fired on input that changes the base layer", function () {
99 var baseLayers = {"Layer 1": L.tileLayer(), "Layer 2": L.tileLayer()},
1010 layers = L.control.layers(baseLayers).addTo(map),
11 spy = jasmine.createSpy();
11 spy = sinon.spy();
1212
1313 map.on('baselayerchange', spy)
14 .whenReady(function(){
14 .whenReady(function() {
1515 happen.click(layers._baseLayersList.getElementsByTagName("input")[0]);
1616
17 expect(spy).toHaveBeenCalled();
18 expect(spy.mostRecentCall.args[0].layer).toBe(baseLayers["Layer 1"]);
17 expect(spy.called).to.be.ok();
18 expect(spy.mostRecentCall.args[0].layer).to.be(baseLayers["Layer 1"]);
1919 });
2020 });
2121
2222 it("is not fired on input that doesn't change the base layer", function () {
2323 var overlays = {"Marker 1": L.marker([0, 0]), "Marker 2": L.marker([0, 0])},
2424 layers = L.control.layers({}, overlays).addTo(map),
25 spy = jasmine.createSpy();
25 spy = sinon.spy();
2626
2727 map.on('baselayerchange', spy);
2828 happen.click(layers._overlaysList.getElementsByTagName("input")[0]);
2929
30 expect(spy).not.toHaveBeenCalled();
30 expect(spy.called).to.not.be.ok();
3131 });
3232 });
3333
4141 overlay = L.marker([0, 0]),
4242 layers = L.control.layers({"Base": baseLayer}, {"Overlay": overlay}).addTo(map);
4343
44 spyOn(layers, '_update').andCallThrough();
44 var spy = sinon.spy(layers, '_update');
4545
4646 map.addLayer(overlay);
4747 map.removeLayer(overlay);
4848
49 expect(layers._update).toHaveBeenCalled();
50 expect(layers._update.callCount).toEqual(2);
49 expect(spy.called).to.be.ok();
50 expect(spy.callCount).to.eql(2);
5151 });
5252
5353 it("not when a non-included layer is added or removed", function () {
5555 overlay = L.marker([0, 0]),
5656 layers = L.control.layers({"Base": baseLayer}).addTo(map);
5757
58 spyOn(layers, '_update').andCallThrough();
58 var spy = sinon.spy(layers, '_update');
5959
6060 map.addLayer(overlay);
6161 map.removeLayer(overlay);
6262
63 expect(layers._update).not.toHaveBeenCalled();
63 expect(spy.called).to.not.be.ok();
6464 });
6565 });
6666 });
11 it("can be added to an unloaded map", function () {
22 var map = L.map(document.createElement('div'));
33 new L.Control.Scale().addTo(map);
4 })
4 });
55 });
55 method;
66
77 beforeEach(function() {
8 constructor = jasmine.createSpy("Klass constructor");
9 method = jasmine.createSpy("Klass#bar method");
8 constructor = sinon.spy();
9 method = sinon.spy();
1010
1111 Klass = L.Class.extend({
1212 statics: {bla: 1},
1818 });
1919 });
2020
21 it("should create a class with the given constructor & properties", function() {
21 it("creates a class with the given constructor & properties", function() {
2222 var a = new Klass();
2323
24 expect(constructor).toHaveBeenCalled();
25 expect(a.foo).toEqual(5);
24 expect(constructor.called).to.be.ok();
25 expect(a.foo).to.eql(5);
2626
2727 a.bar();
2828
29 expect(method).toHaveBeenCalled();
29 expect(method.called).to.be.ok();
3030 });
3131
32 it("should inherit parent classes' constructor & properties", function() {
32 it("inherits parent classes' constructor & properties", function() {
3333 var Klass2 = Klass.extend({baz: 2});
3434
3535 var b = new Klass2();
3636
37 expect(b instanceof Klass).toBeTruthy();
38 expect(b instanceof Klass2).toBeTruthy();
37 expect(b instanceof Klass).to.be.ok();
38 expect(b instanceof Klass2).to.be.ok();
3939
40 expect(constructor).toHaveBeenCalled();
41 expect(b.baz).toEqual(2);
40 expect(constructor.called).to.be.ok();
41 expect(b.baz).to.eql(2);
4242
4343 b.bar();
4444
45 expect(method).toHaveBeenCalled();
45 expect(method.called).to.be.ok();
4646 });
4747
48 it("should support static properties", function() {
49 expect(Klass.bla).toEqual(1);
48 it("supports static properties", function() {
49 expect(Klass.bla).to.eql(1);
5050 });
5151
52 it("should inherit parent static properties", function() {
52 it("inherits parent static properties", function() {
5353 var Klass2 = Klass.extend({});
5454
55 expect(Klass2.bla).toEqual(1);
55 expect(Klass2.bla).to.eql(1);
5656 });
5757
58 it("should override parent static properties", function() {
58 it("overrides parent static properties", function() {
5959 var Klass2 = Klass.extend({statics: {bla: 2}});
6060
61 expect(Klass2.bla).toEqual(2);
61 expect(Klass2.bla).to.eql(2);
6262 });
6363
64 it("should include the given mixin", function() {
64 it("includes the given mixin", function() {
6565 var a = new Klass();
66 expect(a.mixin).toBeTruthy();
66 expect(a.mixin).to.be.ok();
6767 });
6868
69 it("should be able to include multiple mixins", function() {
69 it("includes multiple mixins", function() {
7070 var Klass2 = L.Class.extend({
7171 includes: [{mixin: true}, {mixin2: true}]
7272 });
7373 var a = new Klass2();
7474
75 expect(a.mixin).toBeTruthy();
76 expect(a.mixin2).toBeTruthy();
75 expect(a.mixin).to.be.ok();
76 expect(a.mixin2).to.be.ok();
7777 });
7878
79 it("should grant the ability to include the given mixin", function() {
79 it("grants the ability to include the given mixin", function() {
8080 Klass.include({mixin2: true});
8181
8282 var a = new Klass();
83 expect(a.mixin2).toBeTruthy();
83 expect(a.mixin2).to.be.ok();
8484 });
8585
86 it("should merge options instead of replacing them", function() {
86 it("merges options instead of replacing them", function() {
8787 var KlassWithOptions1 = L.Class.extend({
8888 options: {
8989 foo1: 1,
9999
100100 var a = new KlassWithOptions2();
101101
102 expect(a.options).toEqual({
102 expect(a.options).to.eql({
103103 foo1: 1,
104104 foo2: 3,
105105 foo3: 4
106106 });
107107 });
108108
109 it("should add constructor hooks correctly", function () {
110 var spy1 = jasmine.createSpy("init hook 1");
109 it("adds constructor hooks correctly", function () {
110 var spy1 = sinon.spy();
111111
112112 Klass.addInitHook(spy1);
113113 Klass.addInitHook('bar', 1, 2, 3);
114114
115115 var a = new Klass();
116116
117 expect(spy1).toHaveBeenCalled();
118 expect(method).toHaveBeenCalledWith(1, 2, 3);
117 expect(spy1.called).to.be.ok();
118 expect(method.calledWith(1, 2, 3));
119119 });
120120
121 it("should inherit constructor hooks", function () {
122 var spy1 = jasmine.createSpy("init hook 1"),
123 spy2 = jasmine.createSpy("init hook 2");
121 it("inherits constructor hooks", function () {
122 var spy1 = sinon.spy(),
123 spy2 = sinon.spy();
124124
125125 var Klass2 = Klass.extend({});
126126
129129
130130 var a = new Klass2();
131131
132 expect(spy1).toHaveBeenCalled();
133 expect(spy2).toHaveBeenCalled();
132 expect(spy1.called).to.be.ok();
133 expect(spy2.called).to.be.ok();
134134 });
135135
136 it("should not call child constructor hooks", function () {
137 var spy1 = jasmine.createSpy("init hook 1"),
138 spy2 = jasmine.createSpy("init hook 2");
136 it("does not call child constructor hooks", function () {
137 var spy1 = sinon.spy(),
138 spy2 = sinon.spy();
139139
140140 var Klass2 = Klass.extend({});
141141
144144
145145 var a = new Klass();
146146
147 expect(spy1).toHaveBeenCalled();
148 expect(spy2).not.toHaveBeenCalled();
147 expect(spy1.called).to.be.ok();
148 expect(spy2.called).to.eql(false);
149149 });
150150 });
151151
00 describe('Events', function() {
11 var Klass;
2
2
33 beforeEach(function() {
44 Klass = L.Class.extend({
55 includes: L.Mixin.Events
66 });
77 });
8
8
99 describe('#fireEvent', function() {
10
11 it('should fire all listeners added through #addEventListener', function() {
12 var obj = new Klass(),
13 spy = jasmine.createSpy(),
14 spy2 = jasmine.createSpy(),
15 spy3 = jasmine.createSpy(),
16 spy4 = jasmine.createSpy(),
17 spy5 = jasmine.createSpy();
18 spy6 = jasmine.createSpy();
19
20 obj.addEventListener('test', spy);
10
11 it('fires all listeners added through #addEventListener', function() {
12 var obj = new Klass(),
13 spy1 = sinon.spy(),
14 spy2 = sinon.spy(),
15 spy3 = sinon.spy(),
16 spy4 = sinon.spy(),
17 spy5 = sinon.spy();
18 spy6 = sinon.spy();
19
20 obj.addEventListener('test', spy1);
2121 obj.addEventListener('test', spy2);
2222 obj.addEventListener('other', spy3);
2323 obj.addEventListener({ test: spy4, other: spy5 });
24 obj.addEventListener({'test other': spy6 })
25
26 expect(spy).not.toHaveBeenCalled();
27 expect(spy2).not.toHaveBeenCalled();
28 expect(spy3).not.toHaveBeenCalled();
29 expect(spy4).not.toHaveBeenCalled();
30 expect(spy5).not.toHaveBeenCalled();
31 expect(spy6).not.toHaveBeenCalled();
32
33 obj.fireEvent('test');
34
35 expect(spy).toHaveBeenCalled();
36 expect(spy2).toHaveBeenCalled();
37 expect(spy3).not.toHaveBeenCalled();
38 expect(spy4).toHaveBeenCalled();
39 expect(spy5).not.toHaveBeenCalled();
40 expect(spy6).toHaveBeenCalled();
41 expect(spy6.calls.length).toEqual(1);
42 });
43
44
45 it('should provide event object to listeners and execute them in the right context', function() {
24 obj.addEventListener({'test other': spy6 });
25
26 expect(spy1.called).to.be(false);
27 expect(spy2.called).to.be(false);
28 expect(spy3.called).to.be(false);
29 expect(spy4.called).to.be(false);
30 expect(spy5.called).to.be(false);
31 expect(spy6.called).to.be(false);
32
33 obj.fireEvent('test');
34
35 expect(spy1.called).to.be(true);
36 expect(spy2.called).to.be(true);
37 expect(spy3.called).to.be(false);
38 expect(spy4.called).to.be(true);
39 expect(spy5.called).to.be(false);
40 expect(spy6.called).to.be(true);
41 expect(spy6.callCount).to.be(1);
42 });
43
44 it('provides event object to listeners and executes them in the right context', function() {
4645 var obj = new Klass(),
4746 obj2 = new Klass(),
4847 obj3 = new Klass(),
4948 obj4 = new Klass(),
5049 foo = {};
51
50
5251 function listener1(e) {
53 expect(e.type).toEqual('test');
54 expect(e.target).toEqual(obj);
55 expect(this).toEqual(obj);
56 expect(e.baz).toEqual(1);
52 expect(e.type).to.eql('test');
53 expect(e.target).to.eql(obj);
54 expect(this).to.eql(obj);
55 expect(e.baz).to.eql(1);
5756 }
58
57
5958 function listener2(e) {
60 expect(e.type).toEqual('test');
61 expect(e.target).toEqual(obj2);
62 expect(this).toEqual(foo);
63 expect(e.baz).toEqual(2);
59 expect(e.type).to.eql('test');
60 expect(e.target).to.eql(obj2);
61 expect(this).to.eql(foo);
62 expect(e.baz).to.eql(2);
6463 }
65
64
6665 function listener3(e) {
67 expect(e.type).toEqual('test');
68 expect(e.target).toEqual(obj3);
69 expect(this).toEqual(obj3);
70 expect(e.baz).toEqual(3);
66 expect(e.type).to.eql('test');
67 expect(e.target).to.eql(obj3);
68 expect(this).to.eql(obj3);
69 expect(e.baz).to.eql(3);
7170 }
72
71
7372 function listener4(e) {
74 expect(e.type).toEqual('test');
75 expect(e.target).toEqual(obj4);
76 expect(this).toEqual(foo);
77 expect(e.baz).toEqual(4);
73 expect(e.type).to.eql('test');
74 expect(e.target).to.eql(obj4);
75 expect(this).to.eql(foo);
76 expect(e.baz).to.eql(4);
7877 }
79
78
8079 obj.addEventListener('test', listener1);
8180 obj2.addEventListener('test', listener2, foo);
8281 obj3.addEventListener({ test: listener3 });
8382 obj4.addEventListener({ test: listener4 }, foo);
84
83
8584 obj.fireEvent('test', {baz: 1});
8685 obj2.fireEvent('test', {baz: 2});
8786 obj3.fireEvent('test', {baz: 3});
8887 obj4.fireEvent('test', {baz: 4});
8988 });
90
91 it('should not call listeners removed through #removeEventListener', function() {
92 var obj = new Klass(),
93 spy = jasmine.createSpy(),
94 spy2 = jasmine.createSpy(),
95 spy3 = jasmine.createSpy(),
96 spy4 = jasmine.createSpy(),
97 spy5 = jasmine.createSpy();
98
89
90 it('calls no listeners removed through #removeEventListener', function() {
91 var obj = new Klass(),
92 spy = sinon.spy(),
93 spy2 = sinon.spy(),
94 spy3 = sinon.spy(),
95 spy4 = sinon.spy(),
96 spy5 = sinon.spy();
97
9998 obj.addEventListener('test', spy);
10099 obj.removeEventListener('test', spy);
101
102 obj.fireEvent('test');
103
104 expect(spy).not.toHaveBeenCalled();
105
100
101 obj.fireEvent('test');
102
103 expect(spy.called).to.be(false);
104
106105 obj.addEventListener('test2', spy2);
107106 obj.addEventListener('test2', spy3);
108107 obj.removeEventListener('test2');
109
108
110109 obj.fireEvent('test2');
111
112 expect(spy2).not.toHaveBeenCalled();
113 expect(spy3).not.toHaveBeenCalled();
114
110
111 expect(spy2.called).to.be(false);
112 expect(spy3.called).to.be(false);
113
115114 obj.addEventListener('test3', spy4);
116115 obj.addEventListener('test4', spy5);
117116 obj.removeEventListener({
118117 test3: spy4,
119118 test4: spy5
120119 });
121
120
122121 obj.fireEvent('test3');
123122 obj.fireEvent('test4');
124
125 expect(spy4).not.toHaveBeenCalled();
126 expect(spy5).not.toHaveBeenCalled();
127 });
128 });
129
123
124 expect(spy4.called).to.be(false);
125 expect(spy5.called).to.be(false);
126 });
127
128 it('can handle calls to #removeEventListener on objects with no registered event listeners', function () {
129 var obj = new Klass();
130 var removeNonExistentListener = function () {
131 obj.removeEventListener('test');
132 };
133 expect(removeNonExistentListener).to.not.throwException();
134 });
135
136 // added due to context-sensitive removeListener optimization
137 it('fires multiple listeners with the same context with id', function () {
138 var obj = new Klass(),
139 spy1 = sinon.spy(),
140 spy2 = sinon.spy(),
141 foo = {};
142
143 L.Util.stamp(foo);
144
145 obj.addEventListener('test', spy1, foo);
146 obj.addEventListener('test', spy2, foo);
147
148 obj.fireEvent('test');
149
150 expect(spy1.called).to.be(true);
151 expect(spy2.called).to.be(true);
152 });
153
154 it('removes listeners with stamped contexts', function () {
155 var obj = new Klass(),
156 spy1 = sinon.spy(),
157 spy2 = sinon.spy(),
158 foo = {};
159
160 L.Util.stamp(foo);
161
162 obj.addEventListener('test', spy1, foo);
163 obj.addEventListener('test', spy2, foo);
164
165 obj.removeEventListener('test', spy1, foo);
166
167 obj.fireEvent('test');
168
169 expect(spy1.called).to.be(false);
170 expect(spy2.called).to.be(true);
171 });
172
173 it('removes listeners with a stamp originally added without one', function() {
174 var obj = new Klass(),
175 spy1 = sinon.spy(),
176 spy2 = sinon.spy(),
177 foo = {};
178
179 obj.addEventListener('test', spy1, foo);
180 L.Util.stamp(foo);
181 obj.addEventListener('test', spy2, foo);
182
183 obj.removeEventListener('test', spy1, foo);
184 obj.removeEventListener('test', spy2, foo);
185
186 obj.fireEvent('test');
187
188 expect(spy1.called).to.be(false);
189 expect(spy2.called).to.be(false);
190 });
191
192 it('doesnt lose track of listeners when removing non existent ones', function () {
193 var obj = new Klass(),
194 spy = sinon.spy(),
195 spy2 = sinon.spy(),
196 foo = {},
197 foo2 = {};
198
199 L.Util.stamp(foo);
200 L.Util.stamp(foo2);
201
202 obj.addEventListener('test', spy, foo2);
203
204 obj.removeEventListener('test', spy, foo); // Decrements test_idx to 0, even though event listener isn't registered with foo's _leaflet_id
205 obj.removeEventListener('test', spy, foo2); // Doesn't get removed
206
207 obj.addEventListener('test', spy2, foo);
208
209 obj.fireEvent('test');
210
211 expect(spy.called).to.be(false);
212 });
213
214 it('makes sure an event is not triggered if a listener is removed during dispatch',function() {
215 var obj = new Klass(),
216 spy = sinon.spy();
217 obj.addEventListener('test', function() { obj.removeEventListener('test',spy); });
218 obj.addEventListener('test', spy);
219 obj.fireEvent('test');
220
221 expect(spy.called).to.be(false);
222 });
223 });
224
130225 describe('#on, #off & #fire', function() {
131
132 it('should work like #addEventListener && #removeEventListener', function() {
133 var obj = new Klass(),
134 spy = jasmine.createSpy();
135
226
227 it('works like #addEventListener && #removeEventListener', function() {
228 var obj = new Klass(),
229 spy = sinon.spy();
230
136231 obj.on('test', spy);
137232 obj.fire('test');
138
139 expect(spy).toHaveBeenCalled();
140
233
234 expect(spy.called).to.be(true);
235
141236 obj.off('test', spy);
142237 obj.fireEvent('test');
143
144 expect(spy.callCount).toBeLessThan(2);
145 });
146
147 it('should not override existing methods with the same name', function() {
148 var spy1 = jasmine.createSpy(),
149 spy2 = jasmine.createSpy(),
150 spy3 = jasmine.createSpy();
151
238
239 expect(spy.callCount).to.be.lessThan(2);
240 });
241
242 it('does not override existing methods with the same name', function() {
243 var spy1 = sinon.spy(),
244 spy2 = sinon.spy(),
245 spy3 = sinon.spy();
246
152247 var Klass2 = L.Class.extend({
153248 includes: L.Mixin.Events,
154249 on: spy1,
155250 off: spy2,
156251 fire: spy3
157252 });
158
253
159254 var obj = new Klass2();
160
255
161256 obj.on();
162 expect(spy1).toHaveBeenCalled();
163
257 expect(spy1.called).to.be(true);
258
164259 obj.off();
165 expect(spy2).toHaveBeenCalled();
166
260 expect(spy2.called).to.be(true);
261
167262 obj.fire();
168 expect(spy3).toHaveBeenCalled();
169 });
170 });
171 });
263 expect(spy3.called).to.be(true);
264 });
265 });
266
267 describe("#clearEventListeners", function() {
268 it("clears all registered listeners on an object", function() {
269 var spy = sinon.spy(),
270 obj = new Klass()
271 otherObj = new Klass();
272
273 obj.on('test', spy, obj);
274 obj.on('testTwo', spy);
275 obj.on('test', spy, otherObj);
276 obj.off();
277
278 obj.fire('test');
279
280 expect(spy.called).to.be(false);
281 });
282 });
283
284 describe('#once', function() {
285 it('removes event listeners after first trigger', function() {
286 var obj = new Klass(),
287 spy = sinon.spy();
288
289 obj.once('test', spy, obj);
290 obj.fire('test');
291
292 expect(spy.called).to.be(true);
293
294 obj.fire('test');
295
296 expect(spy.callCount).to.be.lessThan(2);
297 });
298
299 it('works with an object hash', function() {
300 var obj = new Klass(),
301 spy = sinon.spy(),
302 otherSpy = sinon.spy();
303
304 obj.once({
305 'test': spy,
306 otherTest: otherSpy
307 }, obj);
308
309 obj.fire('test');
310 obj.fire('otherTest');
311
312 expect(spy.called).to.be(true);
313 expect(otherSpy.called).to.be(true);
314
315 obj.fire('test');
316 obj.fire('otherTest');
317
318 expect(spy.callCount).to.be.lessThan(2);
319 expect(otherSpy.callCount).to.be.lessThan(2);
320 });
321
322 it("doesn't call listeners to events that have been removed", function () {
323 var obj = new Klass(),
324 spy = sinon.spy();
325
326 obj.once('test', spy, obj);
327 obj.off('test', spy, obj);
328
329 obj.fire('test');
330
331 expect(spy.called).to.be(false);
332 });
333
334 it('works if called from a context that doesnt implement #Events', function() {
335 var obj = new Klass(),
336 spy = sinon.spy(),
337 foo = {};
338
339 obj.once('test', spy, foo);
340
341 obj.fire('test');
342
343 expect(spy.called).to.be(true);
344 });
345 });
346 });
11
22 describe('#extend', function() {
33 var a;
4
4
55 beforeEach(function() {
66 a = {
77 foo: 5,
88 bar: 'asd'
99 };
1010 });
11
12 it('should extend the first argument with the properties of the second', function() {
11
12 it('extends the first argument with the properties of the second', function() {
1313 L.Util.extend(a, {
1414 bar: 7,
1515 baz: 3
1616 });
17
18 expect(a).toEqual({
17
18 expect(a).to.eql({
1919 foo: 5,
2020 bar: 7,
2121 baz: 3
2222 });
2323 });
24
25 it('should work with more than 2 arguments', function() {
24
25 it('accepts more than 2 arguments', function() {
2626 L.Util.extend(a, {bar: 7}, {baz: 3});
27
28 expect(a).toEqual({
27
28 expect(a).to.eql({
2929 foo: 5,
3030 bar: 7,
3131 baz: 3
3434 });
3535
3636 describe('#bind', function() {
37 it('should return the given function with the given context', function() {
37 it('returns the given function with the given context', function() {
3838 var fn = function() {
3939 return this;
4040 };
41
42 var fn2 = L.Util.bind(fn, 5);
43
44 expect(fn2()).toEqual(5);
45 });
46 });
47
41
42 var fn2 = L.Util.bind(fn, { foo: 'bar' });
43
44 expect(fn2()).to.eql({ foo: 'bar' });
45 });
46
47 it('passes additional arguments to the bound function', function () {
48 var fn = sinon.spy(),
49 foo = {},
50 a = {},
51 b = {};
52
53 var fn2 = L.Util.bind(fn, foo, a, b);
54
55 fn2();
56
57 expect(fn.calledWith(a, b)).to.be.ok();
58 });
59 });
60
4861 describe('#stamp', function() {
49 it('should set a unique id on the given object and return it', function() {
62 it('sets a unique id on the given object and returns it', function() {
5063 var a = {},
5164 id = L.Util.stamp(a);
52
53 expect(typeof id).toEqual('number');
54 expect(L.Util.stamp(a)).toEqual(id);
55
65
66 expect(typeof id).to.eql('number');
67 expect(L.Util.stamp(a)).to.eql(id);
68
5669 var b = {},
5770 id2 = L.Util.stamp(b);
58
59 expect(id2).not.toEqual(id);
71
72 expect(id2).not.to.eql(id);
73 });
74 });
75
76 describe('#invokeEach', function () {
77 it('calls the given method/context with each key/value and additional arguments', function () {
78 var spy = sinon.spy(),
79 ctx = {};
80
81 var result = L.Util.invokeEach({
82 foo: 'bar',
83 yo: 'hey'
84 }, spy, ctx, 1, 2, 3);
85
86 expect(spy.firstCall.calledWith('foo', 'bar', 1, 2, 3)).to.be.ok();
87 expect(spy.secondCall.calledWith('yo', 'hey', 1, 2, 3)).to.be.ok();
88
89 expect(spy.firstCall.calledOn(ctx)).to.be.ok();
90 expect(spy.secondCall.calledOn(ctx)).to.be.ok();
91
92 expect(result).to.be(true);
93 });
94
95 it('returns false if the given agument is not object', function () {
96 var spy = sinon.spy();
97
98 expect(L.Util.invokeEach('foo', spy)).to.be(false);
99 expect(spy.called).to.be(false);
100 });
101 });
102
103 describe('#falseFn', function () {
104 it('returns false', function () {
105 expect(L.Util.falseFn()).to.be(false);
106 });
107 });
108
109 describe('#formatNum', function () {
110 it('formats numbers with a given precision', function () {
111 expect(L.Util.formatNum(13.12325555, 3)).to.eql(13.123);
112 expect(L.Util.formatNum(13.12325555)).to.eql(13.12326);
60113 });
61114 });
62115
63116
64117 describe('#getParamString', function() {
65 it('should create a valid query string for appending depending on url input', function() {
118 it('creates a valid query string for appending depending on url input', function() {
66119 var a = {
67120 url: "http://example.com/get",
68121 obj: {bar: 7, baz: 3},
69122 result: "?bar=7&baz=3"
70123 };
71124
72 expect(L.Util.getParamString(a.obj,a.url)).toEqual(a.result);
73
74 var b = {
125 expect(L.Util.getParamString(a.obj,a.url)).to.eql(a.result);
126
127 var b = {
75128 url: "http://example.com/get?justone=qs",
76129 obj: {bar: 7, baz: 3},
77130 result: "&bar=7&baz=3"
78131 };
79
80 expect(L.Util.getParamString(b.obj,b.url)).toEqual(b.result);
81
82 var c = {
132
133 expect(L.Util.getParamString(b.obj,b.url)).to.eql(b.result);
134
135 var c = {
83136 url: undefined,
84137 obj: {bar: 7, baz: 3},
85138 result: "?bar=7&baz=3"
86139 };
87
88 expect(L.Util.getParamString(c.obj,c.url)).toEqual(c.result);
89 });
90 });
91
92 // TODO cancel/requestAnimFrame?
93
94 // TODO limitExecByInterval
95
96 // TODO formatNum
97
98 // TODO splitWords
140
141 expect(L.Util.getParamString(c.obj,c.url)).to.eql(c.result);
142 });
143 });
144
145 describe('#requestAnimFrame', function () {
146 it('calles a function on next frame, unless canceled', function (done) {
147 var spy = sinon.spy(),
148 spy2 = sinon.spy(),
149 foo = {};
150
151 L.Util.requestAnimFrame(spy);
152
153 L.Util.requestAnimFrame(function () {
154 expect(this).to.eql(foo);
155 done();
156 }, foo);
157
158 L.Util.cancelAnimFrame(spy);
159 });
160 });
161
162 describe('#limitExecByInterval', function() {
163 it('limits execution to not more often than specified time interval', function (done) {
164 var spy = sinon.spy();
165
166 var fn = L.Util.limitExecByInterval(spy, 20);
167
168 fn();
169 fn();
170 fn();
171
172 expect(spy.callCount).to.eql(1);
173
174 setTimeout(function () {
175 expect(spy.callCount).to.eql(2);
176 done();
177 }, 30);
178 });
179 });
180
181 describe('#splitWords', function () {
182 it('splits words into an array', function () {
183 expect(L.Util.splitWords('foo bar baz')).to.eql(['foo', 'bar', 'baz']);
184 });
185 });
99186
100187 // TODO setOptions
101188
102 // TODO template
189 describe('#template', function () {
190 it('evaluates templates with a given data object', function () {
191 var tpl = 'Hello {foo} and {bar}!';
192
193 var str = L.Util.template(tpl, {
194 foo: 'Vlad',
195 bar: 'Dave'
196 });
197
198 expect(str).to.eql('Hello Vlad and Dave!');
199 });
200
201 it('does not modify text without a token variable', function () {
202 expect(L.Util.template('foo', {})).to.eql('foo');
203 });
204
205 it('throws when a template token is not given', function () {
206 expect(function () {
207 L.Util.template(tpl, {foo: 'bar'});
208 }).to.throwError();
209 });
210 });
103211 });
1010 return el.fireEvent('onclick');
1111 }
1212 }
13
13
1414 beforeEach(function() {
1515 el = document.createElement('div');
1616 el.style.position = 'absolute';
1717 el.style.top = el.style.left = '-10000px';
1818 document.body.appendChild(el);
1919 });
20
20
2121 afterEach(function() {
2222 document.body.removeChild(el);
2323 });
24
24
2525 describe('#addListener', function() {
26 it('should add a listener and call it on event', function() {
27 var listener1 = jasmine.createSpy('listener1'),
28 listener2 = jasmine.createSpy('listener2');
29
26 it('adds a listener and calls it on event', function() {
27 var listener1 = sinon.spy(),
28 listener2 = sinon.spy();
29
3030 L.DomEvent.addListener(el, 'click', listener1);
3131 L.DomEvent.addListener(el, 'click', listener2);
32
32
3333 simulateClick(el);
34
35 expect(listener1).toHaveBeenCalled();
36 expect(listener2).toHaveBeenCalled();
34
35 expect(listener1.called).to.be.ok();
36 expect(listener2.called).to.be.ok();
3737 });
38
39 it('should have "this" keyword point to the given context', function() {
38
39 it('binds "this" to the given context', function() {
4040 var obj = {foo: 'bar'},
4141 result;
42
42
4343 L.DomEvent.addListener(el, 'click', function() {
4444 result = this;
4545 }, obj);
46
46
4747 simulateClick(el);
48
49 expect(result).toEqual(obj);
48
49 expect(result).to.eql(obj);
5050 });
51
52 it('should pass an event object to the listener', function() {
51
52 it('passes an event object to the listener', function() {
5353 var type;
54
54
5555 L.DomEvent.addListener(el, 'click', function(e) {
5656 type = e && e.type;
5757 });
5858 simulateClick(el);
59
60 expect(type).toEqual('click');
59
60 expect(type).to.eql('click');
6161 });
6262 });
63
63
6464 describe('#removeListener', function() {
65 it('should remove previously added listener', function() {
66 var listener = jasmine.createSpy('listener');
67
65 it('removes a previously added listener', function() {
66 var listener = sinon.spy();
67
6868 L.DomEvent.addListener(el, 'click', listener);
6969 L.DomEvent.removeListener(el, 'click', listener);
70
70
7171 simulateClick(el);
72
73 expect(listener).not.toHaveBeenCalled();
72
73 expect(listener.called).to.not.be.ok();
7474 });
7575 });
76
76
7777 describe('#stopPropagation', function() {
78 it('should stop propagation of the given event', function() {
78 it('stops propagation of the given event', function() {
7979 var child = document.createElement('div'),
80 listener = jasmine.createSpy('listener');
81
80 listener = sinon.spy();
81
8282 el.appendChild(child);
83
83
8484 L.DomEvent.addListener(child, 'click', L.DomEvent.stopPropagation);
8585 L.DomEvent.addListener(el, 'click', listener);
86
86
8787 simulateClick(child);
88
89 expect(listener).not.toHaveBeenCalled();
90
88
89 expect(listener.called).to.not.be.ok();
90
9191 el.removeChild(child);
9292 });
9393 });
9494 describe('#preventDefault', function() {
95 it('should prevent the default action of event', function() {
95 it('prevents the default action of event', function() {
9696 L.DomEvent.addListener(el, 'click', L.DomEvent.preventDefault);
9797
98 expect(simulateClick(el)).toBe(false);
98 expect(simulateClick(el)).to.be(false);
9999 });
100100 });
101 });
101 });
00 describe('DomUtil', function() {
11 var el;
2
2
33 beforeEach(function() {
44 el = document.createElement('div');
55 el.style.position = 'absolute';
66 el.style.top = el.style.left = '-10000px';
77 document.body.appendChild(el);
88 });
9
9
1010 afterEach(function() {
1111 document.body.removeChild(el);
1212 });
13
13
1414 describe('#get', function() {
15 it('should get element by id if the given argument is string', function() {
15 it('gets element by id if the given argument is string', function() {
1616 el.id = 'testId';
17 expect(L.DomUtil.get(el.id)).toBe(el);
17 expect(L.DomUtil.get(el.id)).to.eql(el);
1818 });
19
20 it('should return the element if it is given as an argument', function() {
21 expect(L.DomUtil.get(el)).toBe(el);
19
20 it('returns the element if it is given as an argument', function() {
21 expect(L.DomUtil.get(el)).to.eql(el);
2222 });
2323 });
24
24
2525 describe('#addClass, #removeClass, #hasClass', function() {
26 it('should has defined class for test element', function() {
26 it('has defined class for test element', function() {
2727 el.className = 'bar foo baz ';
28 expect(L.DomUtil.hasClass(el, 'foo')).toBeTruthy();
29 expect(L.DomUtil.hasClass(el, 'bar')).toBeTruthy();
30 expect(L.DomUtil.hasClass(el, 'baz')).toBeTruthy();
31 expect(L.DomUtil.hasClass(el, 'boo')).toBeFalsy();
28 expect(L.DomUtil.hasClass(el, 'foo')).to.be.ok();
29 expect(L.DomUtil.hasClass(el, 'bar')).to.be.ok();
30 expect(L.DomUtil.hasClass(el, 'baz')).to.be.ok();
31 expect(L.DomUtil.hasClass(el, 'boo')).to.not.be.ok();
3232 });
33
34 it('should properly addClass and removeClass for element', function() {
33
34 it('adds or removes the class', function() {
3535 el.className = '';
3636 L.DomUtil.addClass(el, 'foo');
37
38 expect(el.className).toEqual('foo');
39 expect(L.DomUtil.hasClass(el, 'foo')).toBeTruthy();
40
37
38 expect(el.className).to.eql('foo');
39 expect(L.DomUtil.hasClass(el, 'foo')).to.be.ok();
40
4141 L.DomUtil.addClass(el, 'bar');
42 expect(el.className).toEqual('foo bar');
43 expect(L.DomUtil.hasClass(el, 'foo')).toBeTruthy();
44
42 expect(el.className).to.eql('foo bar');
43 expect(L.DomUtil.hasClass(el, 'foo')).to.be.ok();
44
4545 L.DomUtil.removeClass(el, 'foo');
46 expect(el.className).toEqual('bar');
47 expect(L.DomUtil.hasClass(el, 'foo')).toBeFalsy();
48
46 expect(el.className).to.eql('bar');
47 expect(L.DomUtil.hasClass(el, 'foo')).to.not.be.ok();
48
4949 el.className = 'foo bar barz';
5050 L.DomUtil.removeClass(el, 'bar');
51 expect(el.className).toEqual('foo barz');
52 })
51 expect(el.className).to.eql('foo barz');
52 });
5353 });
54
55 describe('#setPosition', noSpecs);
56
57 describe('#getStyle', noSpecs);
58 });
54
55 describe('#documentIsLtr', function () {
56 it('returns true if doc direction is ltr', function () {
57 expect(L.DomUtil.documentIsLtr()).to.eql(true);
58 expect(L.DomUtil.documentIsLtr()).to.eql(true); // cached
59 });
60 });
61
62 describe('#getViewportOffset', function () {
63 it('calculates the viewport offset of an element', function () {
64 var div = document.createElement('div');
65 div.style.position = 'absolute';
66 div.style.top = '100px';
67 div.style.left = '200px';
68 div.style.border = '10px solid black';
69 div.style.padding = '50px';
70 div.style.visibility = 'hidden';
71
72 var div2 = document.createElement('div');
73 div.style.marginTop = '100px';
74
75 div.appendChild(div2);
76
77 document.body.appendChild(div);
78
79 expect(L.DomUtil.getViewportOffset(div2)).to.eql(new L.Point(260, 260));
80
81 document.body.removeChild(div);
82 });
83 });
84
85 // describe('#setPosition', noSpecs);
86
87 // describe('#getStyle', noSpecs);
88 });
00 describe('LatLngBounds', function() {
11 var a, c;
2
2
33 beforeEach(function() {
44 a = new L.LatLngBounds(
55 new L.LatLng(14, 12),
66 new L.LatLng(30, 40));
77 c = new L.LatLngBounds();
88 });
9
10 describe('#isValid', function() {
11 it('should return true if properly set up', function() {
12 expect(a.isValid()).toBeTruthy();
13 });
14 it('should return false if is invalid', function() {
15 expect(c.isValid()).toBeFalsy();
16 });
17 it('should be valid if extended', function() {
18 c.extend([0, 0]);
19 expect(c.isValid()).toBeTruthy();
9
10 describe('constructor', function () {
11 it('instantiates either passing two latlngs or an array of latlngs', function () {
12 var b = new L.LatLngBounds([
13 new L.LatLng(14, 12),
14 new L.LatLng(30, 40)
15 ]);
16 expect(b).to.eql(a);
17 expect(b.getNorthWest()).to.eql(new L.LatLng(30, 12));
2018 });
2119 });
22 });
20
21 describe('#extend', function () {
22 it('extends the bounds by a given point', function () {
23 a.extend(new L.LatLng(20, 50));
24 expect(a.getNorthEast()).to.eql(new L.LatLng(30, 50));
25 });
26
27 it('extends the bounds by given bounds', function () {
28 a.extend([[20, 50], [8, 40]]);
29
30 expect(a.getSouthEast()).to.eql(new L.LatLng(8, 50));
31 });
32
33 it('extends the bounds by undefined', function () {
34 expect(a.extend()).to.eql(a);
35 });
36 });
37
38 describe('#getCenter', function () {
39 it('returns the bounds center', function () {
40 expect(a.getCenter()).to.eql(new L.LatLng(22, 26));
41 });
42 });
43
44 describe('#pad', function () {
45 it('pads the bounds by a given ratio', function () {
46 var b = a.pad(0.5);
47
48 expect(b).to.eql(L.latLngBounds([[6, -2], [38, 54]]));
49 });
50 });
51
52 describe('#equals', function () {
53 it('returns true if bounds equal', function () {
54 expect(a.equals([[14, 12], [30, 40]])).to.eql(true);
55 expect(a.equals([[14, 13], [30, 40]])).to.eql(false);
56 expect(a.equals(null)).to.eql(false);
57 });
58 });
59
60 describe('#isValid', function() {
61 it('returns true if properly set up', function() {
62 expect(a.isValid()).to.be.ok();
63 });
64 it('returns false if is invalid', function() {
65 expect(c.isValid()).to.not.be.ok();
66 });
67 it('returns true if extended', function() {
68 c.extend([0, 0]);
69 expect(c.isValid()).to.be.ok();
70 });
71 });
72
73 describe('#getWest', function () {
74 it('returns a proper bbox west value', function() {
75 expect(a.getWest()).to.eql(12);
76 });
77 });
78
79 describe('#getSouth', function () {
80 it('returns a proper bbox south value', function() {
81 expect(a.getSouth()).to.eql(14);
82 });
83
84 });
85
86 describe('#getEast', function () {
87 it('returns a proper bbox east value', function() {
88 expect(a.getEast()).to.eql(40);
89 });
90
91 });
92
93 describe('#getNorth', function () {
94 it('returns a proper bbox north value', function() {
95 expect(a.getNorth()).to.eql(30);
96 });
97
98 });
99
100 describe('#toBBoxString', function () {
101 it('returns a proper left,bottom,right,top bbox', function() {
102 expect(a.toBBoxString()).to.eql("12,14,40,30");
103 });
104
105 });
106
107 describe('#getNorthWest', function () {
108 it('returns a proper north-west LatLng', function() {
109 expect(a.getNorthWest()).to.eql(new L.LatLng(a.getNorth(), a.getWest()));
110 });
111
112 });
113
114 describe('#getSouthEast', function () {
115 it('returns a proper south-east LatLng', function() {
116 expect(a.getSouthEast()).to.eql(new L.LatLng(a.getSouth(), a.getEast()));
117 });
118 });
119
120 describe('#contains', function () {
121 it('returns true if contains latlng point', function () {
122 expect(a.contains([16, 20])).to.eql(true);
123 expect(L.latLngBounds(a).contains([5, 20])).to.eql(false);
124 });
125
126 it('returns true if contains bounds', function () {
127 expect(a.contains([[16, 20], [20, 40]])).to.eql(true);
128 expect(a.contains([[16, 50], [8, 40]])).to.eql(false);
129 });
130 });
131
132 describe('#intersects', function () {
133 it('returns true if intersects the given bounds', function () {
134 expect(a.intersects([[16, 20], [50, 60]])).to.eql(true);
135 expect(a.contains([[40, 50], [50, 60]])).to.eql(false);
136 });
137 });
138
139 });
00 describe('LatLng', function() {
11 describe('constructor', function() {
2 it("should set lat and lng", function() {
2 it("sets lat and lng", function() {
33 var a = new L.LatLng(25, 74);
4 expect(a.lat).toEqual(25);
5 expect(a.lng).toEqual(74);
4 expect(a.lat).to.eql(25);
5 expect(a.lng).to.eql(74);
66
7 var a = new L.LatLng(-25, -74);
8 expect(a.lat).toEqual(-25);
9 expect(a.lng).toEqual(-74);
7 var b = new L.LatLng(-25, -74);
8 expect(b.lat).to.eql(-25);
9 expect(b.lng).to.eql(-74);
10 });
11
12 it('throws an error if invalid lat or lng', function () {
13 expect(function () {
14 var a = new L.LatLng(NaN, NaN);
15 }).to.throwError();
1016 });
1117 });
1218
1319 describe('#equals', function() {
14 it("should return true if compared objects are equal within a certain margin", function() {
20 it("returns true if compared objects are equal within a certain margin", function() {
1521 var a = new L.LatLng(10, 20);
1622 var b = new L.LatLng(10 + 1.0E-10, 20 - 1.0E-10);
17 expect(a.equals(b)).toBe(true);
23 expect(a.equals(b)).to.eql(true);
1824 });
1925
20 it("should return false if compared objects are not equal within a certain margin", function() {
26 it("returns false if compared objects are not equal within a certain margin", function() {
2127 var a = new L.LatLng(10, 20);
2228 var b = new L.LatLng(10, 23.3);
23 expect(a.equals(b)).toBe(false);
29 expect(a.equals(b)).to.eql(false);
30 });
31
32 it('returns false if passed non-valid object', function () {
33 var a = new L.LatLng(10, 20);
34 expect(a.equals(null)).to.eql(false);
2435 });
2536 });
2637
2738 describe('#wrap', function () {
28 it("#wrap should wrap longitude to lie between -180 and 180 by default", function() {
39 it("wraps longitude to lie between -180 and 180 by default", function() {
2940 var a = new L.LatLng(0, 190).wrap().lng;
30 expect(a).toEqual(-170);
41 expect(a).to.eql(-170);
3142
3243 var b = new L.LatLng(0, 360).wrap().lng;
33 expect(b).toEqual(0);
44 expect(b).to.eql(0);
3445
3546 var c = new L.LatLng(0, 380).wrap().lng;
36 expect(c).toEqual(20);
47 expect(c).to.eql(20);
3748
3849 var d = new L.LatLng(0, -190).wrap().lng;
39 expect(d).toEqual(170);
50 expect(d).to.eql(170);
4051
4152 var e = new L.LatLng(0, -360).wrap().lng;
42 expect(e).toEqual(0);
53 expect(e).to.eql(0);
4354
4455 var f = new L.LatLng(0, -380).wrap().lng;
45 expect(f).toEqual(-20);
56 expect(f).to.eql(-20);
4657
4758 var g = new L.LatLng(0, 90).wrap().lng;
48 expect(g).toEqual(90);
59 expect(g).to.eql(90);
4960
5061 var h = new L.LatLng(0, 180).wrap().lng;
51 expect(h).toEqual(180);
62 expect(h).to.eql(180);
5263 });
5364
54 it("#wrap should wrap longitude within the given range", function() {
65 it("wraps longitude within the given range", function() {
5566 var a = new L.LatLng(0, 190).wrap(-100, 100).lng;
56 expect(a).toEqual(-10);
67 expect(a).to.eql(-10);
5768 });
5869
59 })
70 });
71
72 describe('#toString', function () {
73 it('formats a string', function () {
74 var a = new L.LatLng(10.333333333, 20.2222222);
75 expect(a.toString(3)).to.eql('LatLng(10.333, 20.222)');
76 });
77 });
78
79 describe('#distanceTo', function () {
80 it('calculates distance in meters', function () {
81 var a = new L.LatLng(50.5, 30.5);
82 var b = new L.LatLng(50, 1);
83
84 expect(Math.abs(Math.round(a.distanceTo(b) / 1000) - 2084) < 5).to.eql(true);
85 });
86 });
87
88 describe('L.latLng factory', function () {
89 it('returns LatLng instance as is', function () {
90 var a = new L.LatLng(50, 30);
91
92 expect(L.latLng(a)).to.eql(a);
93 });
94
95 it('accepts an array of coordinates', function () {
96 expect(L.latLng([50, 30])).to.eql(new L.LatLng(50, 30));
97 });
98
99 it('passes null or undefined as is', function () {
100 expect(L.latLng(undefined)).to.eql(undefined);
101 expect(L.latLng(null)).to.eql(null);
102 });
103
104 it('creates a LatLng object from two coordinates', function () {
105 expect(L.latLng(50, 30)).to.eql(new L.LatLng(50, 30));
106 });
107
108 it('accepts an object with lat/lng', function () {
109 expect(L.latLng({lat: 50, lng: 30})).to.eql(new L.LatLng(50, 30));
110 });
111
112 it('accepts an object with lat/lon', function () {
113 expect(L.latLng({lat: 50, lon: 30})).to.eql(new L.LatLng(50, 30));
114 });
115 });
60116 });
61117
0 xdescribe("Projection.Mercator", function() {
0 describe("Projection.Mercator", function() {
11 var p = L.Projection.Mercator;
2
3 beforeEach(function() {
4 function almostEqual(a, b, p) {
5 return Math.abs(a - b) <= (p || 1.0E-12);
6 };
7 this.addMatchers({
8 toAlmostEqual: function(expected, margin) {
9 var p1 = this.actual,
10 p2 = expected;
11 return almostEqual(p1.x, p2.x, margin) && almostEqual(p1.y, p2.y, margin);
12 }
13 });
14 });
15
2
3 expect.Assertion.prototype.near = function(expected, delta) {
4 delta = delta || 1;
5 expect(this.obj.x).to
6 .be.within(expected.x - delta, expected.x + delta);
7 expect(this.obj.y).to
8 .be.within(expected.y - delta, expected.y + delta);
9 };
1610
1711 describe("#project", function() {
18 it("should do projection properly", function() {
12 it("projects a center point", function() {
1913 //edge cases
20 expect(p.project(new L.LatLng(0, 0))).toAlmostEqual(new L.Point(0, 0));
21 expect(p.project(new L.LatLng(90, 180))).toAlmostEqual(new L.Point(-Math.PI, Math.PI));
22 expect(p.project(new L.LatLng(-90, -180))).toAlmostEqual(new L.Point(-Math.PI, -Math.PI));
23
24 expect(p.project(new L.LatLng(50, 30))).toAlmostEqual(new L.Point(0.523598775598, 1.010683188683));
14 expect(p.project(new L.LatLng(0, 0))).near(new L.Point(0, 0));
15 });
16
17 it("projects the northeast corner of the world", function() {
18 expect(p.project(new L.LatLng(90, 180))).near(new L.Point(20037508, 20037508));
19 });
20
21 it("projects the southwest corner of the world", function() {
22 expect(p.project(new L.LatLng(-90, -180))).near(new L.Point(-20037508, -20037508));
23 });
24
25 it("projects other points", function() {
26 expect(p.project(new L.LatLng(50, 30))).near(new L.Point(3339584, 6413524));
27
28 // from https://github.com/Leaflet/Leaflet/issues/1578
29 expect(p.project(new L.LatLng(51.9371170300465, 80.11230468750001)))
30 .near(new L.Point(8918060.964088084, 6755099.410887127));
2531 });
2632 });
2733
2834 describe("#unproject", function() {
29 it("should do unprojection properly", function() {
30 function pr(point) {
31 return p.project(p.unproject(point));
32 }
33
34 expect(pr(new L.Point(0, 0))).toAlmostEqual(new L.Point(0, 0));
35 expect(pr(new L.Point(-Math.PI, Math.PI))).toAlmostEqual(new L.Point(-Math.PI, Math.PI));
36 expect(pr(new L.Point(-Math.PI, -Math.PI))).toAlmostEqual(new L.Point(-Math.PI, -Math.PI));
37
38 expect(pr(new L.Point(0.523598775598, 1.010683188683))).toAlmostEqual(new L.Point(0.523598775598, 1.010683188683));
35 function pr(point) {
36 return p.project(p.unproject(point));
37 }
38
39 it("unprojects a center point", function() {
40 expect(pr(new L.Point(0, 0))).near(new L.Point(0, 0));
41 });
42
43 it("unprojects pi points", function() {
44 expect(pr(new L.Point(-Math.PI, Math.PI))).near(new L.Point(-Math.PI, Math.PI));
45 expect(pr(new L.Point(-Math.PI, -Math.PI))).near(new L.Point(-Math.PI, -Math.PI));
46
47 expect(pr(new L.Point(0.523598775598, 1.010683188683))).near(new L.Point(0.523598775598, 1.010683188683));
48 });
49
50 it('unprojects other points', function () {
51 // from https://github.com/Leaflet/Leaflet/issues/1578
52 expect(pr(new L.Point(8918060.964088084, 6755099.410887127)));
3953 });
4054 });
41 });
55 });
00 describe('Bounds', function() {
11 var a, b, c;
2
2
33 beforeEach(function() {
44 a = new L.Bounds(
55 new L.Point(14, 12),
1111 ]);
1212 c = new L.Bounds();
1313 });
14
14
1515 describe('constructor', function() {
16 it('should create bounds with proper min & max on (Point, Point)', function() {
17 expect(a.min).toEqual(new L.Point(14, 12));
18 expect(a.max).toEqual(new L.Point(30, 40));
16 it('creates bounds with proper min & max on (Point, Point)', function() {
17 expect(a.min).to.eql(new L.Point(14, 12));
18 expect(a.max).to.eql(new L.Point(30, 40));
1919 });
20 it('should create bounds with proper min & max on (Point[])', function() {
21 expect(b.min).toEqual(new L.Point(14, 12));
22 expect(b.max).toEqual(new L.Point(30, 40));
20 it('creates bounds with proper min & max on (Point[])', function() {
21 expect(b.min).to.eql(new L.Point(14, 12));
22 expect(b.max).to.eql(new L.Point(30, 40));
2323 });
2424 });
25
25
2626 describe('#extend', function() {
27 it('should extend the bounds to contain the given point', function() {
27 it('extends the bounds to contain the given point', function() {
2828 a.extend(new L.Point(50, 20));
29 expect(a.min).toEqual(new L.Point(14, 12));
30 expect(a.max).toEqual(new L.Point(50, 40));
31
29 expect(a.min).to.eql(new L.Point(14, 12));
30 expect(a.max).to.eql(new L.Point(50, 40));
31
3232 b.extend(new L.Point(25, 50));
33 expect(b.min).toEqual(new L.Point(14, 12));
34 expect(b.max).toEqual(new L.Point(30, 50));
33 expect(b.min).to.eql(new L.Point(14, 12));
34 expect(b.max).to.eql(new L.Point(30, 50));
3535 });
3636 });
37
37
3838 describe('#getCenter', function() {
39 it('should return the center point', function() {
40 expect(a.getCenter()).toEqual(new L.Point(22, 26));
39 it('returns the center point', function() {
40 expect(a.getCenter()).to.eql(new L.Point(22, 26));
4141 });
4242 });
43
43
4444 describe('#contains', function() {
45 it('should contains other bounds or point', function() {
45 it('contains other bounds or point', function() {
4646 a.extend(new L.Point(50, 10));
47 expect(a.contains(b)).toBeTruthy();
48 expect(b.contains(a)).toBeFalsy();
49 expect(a.contains(new L.Point(24, 25))).toBeTruthy();
50 expect(a.contains(new L.Point(54, 65))).toBeFalsy();
47 expect(a.contains(b)).to.be.ok();
48 expect(b.contains(a)).to.not.be.ok();
49 expect(a.contains(new L.Point(24, 25))).to.be.ok();
50 expect(a.contains(new L.Point(54, 65))).to.not.be.ok();
5151 });
5252 });
53
53
5454 describe('#isValid', function() {
55 it('should return true if properly set up', function() {
56 expect(a.isValid()).toBeTruthy();
55 it('returns true if properly set up', function() {
56 expect(a.isValid()).to.be.ok();
5757 });
58 it('should return false if is invalid', function() {
59 expect(c.isValid()).toBeFalsy();
58 it('returns false if is invalid', function() {
59 expect(c.isValid()).to.not.be.ok();
6060 });
61 it('should be valid if extended', function() {
61 it('returns true if extended', function() {
6262 c.extend([0, 0]);
63 expect(c.isValid()).toBeTruthy();
63 expect(c.isValid()).to.be.ok();
6464 });
6565 });
66 });
66
67 describe('#getSize', function () {
68 it('returns the size of the bounds as point', function () {
69 expect(a.getSize()).to.eql(new L.Point(16, 28));
70 });
71 });
72
73 describe('#intersects', function () {
74 it('returns true if bounds intersect', function () {
75 expect(a.intersects(b)).to.be(true);
76 expect(a.intersects(new L.Bounds(new L.Point(100, 100), new L.Point(120, 120)))).to.eql(false);
77 });
78 });
79
80 describe('L.bounds factory', function () {
81 it('creates bounds from array of number arrays', function () {
82 var bounds = L.bounds([[14, 12], [30, 40]]);
83 expect(bounds).to.eql(a);
84 });
85 });
86 });
0 describe('LineUtil', function () {
1
2 describe('#clipSegment', function () {
3
4 var bounds;
5
6 beforeEach(function () {
7 bounds = L.bounds([5, 0], [15, 10]);
8 });
9
10 it('clips a segment by bounds', function () {
11 var a = new L.Point(0, 0);
12 var b = new L.Point(15, 15);
13
14 var segment = L.LineUtil.clipSegment(a, b, bounds);
15
16 expect(segment[0]).to.eql(new L.Point(5, 5));
17 expect(segment[1]).to.eql(new L.Point(10, 10));
18
19 var c = new L.Point(5, -5);
20 var d = new L.Point(20, 10);
21
22 var segment2 = L.LineUtil.clipSegment(c, d, bounds);
23
24 expect(segment2[0]).to.eql(new L.Point(10, 0));
25 expect(segment2[1]).to.eql(new L.Point(15, 5));
26 });
27
28 it('uses last bit code and reject segments out of bounds', function () {
29 var a = new L.Point(15, 15);
30 var b = new L.Point(25, 20);
31 var segment = L.LineUtil.clipSegment(a, b, bounds, true);
32
33 expect(segment).to.be(false);
34 });
35 });
36
37 describe('#pointToSegmentDistance & #closestPointOnSegment', function () {
38
39 var p1 = new L.Point(0, 10);
40 var p2 = new L.Point(10, 0);
41 var p = new L.Point(0, 0);
42
43 it('calculates distance from point to segment', function () {
44 expect(L.LineUtil.pointToSegmentDistance(p, p1, p2)).to.eql(Math.sqrt(200) / 2);
45 });
46
47 it('calculates point closest to segment', function () {
48 expect(L.LineUtil.closestPointOnSegment(p, p1, p2)).to.eql(new L.Point(5, 5));
49 });
50 });
51
52 describe('#simplify', function () {
53 it('simplifies polylines according to tolerance', function () {
54 var points = [
55 new L.Point(0, 0),
56 new L.Point(0.01, 0),
57 new L.Point(0.5, 0.01),
58 new L.Point(0.7, 0),
59 new L.Point(1, 0),
60 new L.Point(1.999, 0.999),
61 new L.Point(2, 1)
62 ];
63
64 var simplified = L.LineUtil.simplify(points, 0.1);
65
66 expect(simplified).to.eql([
67 new L.Point(0, 0),
68 new L.Point(1, 0),
69 new L.Point(2, 1)
70 ]);
71 });
72 });
73
74 });
00 describe("Point", function() {
1
1
22 describe('constructor', function() {
3
4 it("should create a point with the given x and y", function() {
3
4 it("creates a point with the given x and y", function() {
55 var p = new L.Point(1.5, 2.5);
6 expect(p.x).toEqual(1.5);
7 expect(p.y).toEqual(2.5);
6 expect(p.x).to.eql(1.5);
7 expect(p.y).to.eql(2.5);
88 });
9
10 it("should round the given x and y if the third argument is true", function() {
9
10 it("rounds the given x and y if the third argument is true", function() {
1111 var p = new L.Point(1.3, 2.7, true);
12 expect(p.x).toEqual(1);
13 expect(p.y).toEqual(3);
12 expect(p.x).to.eql(1);
13 expect(p.y).to.eql(3);
1414 });
1515 });
16
16
1717 describe('#subtract', function() {
18 it('should subtract the given point from this one', function() {
18 it('subtracts the given point from this one', function() {
1919 var a = new L.Point(50, 30),
2020 b = new L.Point(20, 10);
21 expect(a.subtract(b)).toEqual(new L.Point(30, 20));
21 expect(a.subtract(b)).to.eql(new L.Point(30, 20));
2222 });
2323 });
24
24
2525 describe('#add', function() {
26 it('should add the given point to this one', function() {
27 expect(new L.Point(50, 30).add(new L.Point(20, 10))).toEqual(new L.Point(70, 40));
26 it('adds given point to this one', function() {
27 expect(new L.Point(50, 30).add(new L.Point(20, 10))).to.eql(new L.Point(70, 40));
2828 });
2929 });
30
30
3131 describe('#divideBy', function() {
32 it('should divide this point by the given amount', function() {
33 expect(new L.Point(50, 30).divideBy(5)).toEqual(new L.Point(10, 6));
32 it('divides this point by the given amount', function() {
33 expect(new L.Point(50, 30).divideBy(5)).to.eql(new L.Point(10, 6));
3434 });
3535 });
36
36
3737 describe('#multiplyBy', function() {
38 it('should multiply this point by the given amount', function() {
39 expect(new L.Point(50, 30).multiplyBy(2)).toEqual(new L.Point(100, 60));
38 it('multiplies this point by the given amount', function() {
39 expect(new L.Point(50, 30).multiplyBy(2)).to.eql(new L.Point(100, 60));
4040 });
4141 });
42
43 describe('#distanceTo', noSpecs);
44 });
42
43 describe('#floor', function () {
44 it('returns a new point with floored coordinates', function () {
45 expect(new L.Point(50.56, 30.123).floor()).to.eql(new L.Point(50, 30));
46 });
47 });
48
49 describe('#distanceTo', function () {
50 it('calculates distance between two points', function () {
51 var p1 = new L.Point(0, 30);
52 var p2 = new L.Point(40, 0);
53 expect(p1.distanceTo(p2)).to.eql(50.0);
54 });
55 });
56
57 describe('#equals', function () {
58 it('returns true if points are equal', function () {
59 var p1 = new L.Point(20.4, 50.12);
60 var p2 = new L.Point(20.4, 50.12);
61 var p3 = new L.Point(20.5, 50.13);
62
63 expect(p1.equals(p2)).to.be(true);
64 expect(p1.equals(p3)).to.be(false);
65 });
66 });
67
68 describe('#contains', function () {
69 it('returns true if the point is bigger in absolute dimensions than the passed one', function () {
70 var p1 = new L.Point(50, 30),
71 p2 = new L.Point(-40, 20),
72 p3 = new L.Point(60, -20),
73 p4 = new L.Point(-40, -40);
74
75 expect(p1.contains(p2)).to.be(true);
76 expect(p1.contains(p3)).to.be(false);
77 expect(p1.contains(p4)).to.be(false);
78 });
79 });
80
81 describe('#toString', function () {
82 it('formats a string out of point coordinates', function () {
83 expect(new L.Point(50, 30) + '').to.eql('Point(50, 30)');
84 });
85 });
86
87 describe('L.point factory', function () {
88 it('leaves L.Point instances as is', function () {
89 var p = new L.Point(50, 30);
90 expect(L.point(p)).to.be(p);
91 });
92 it('creates a point out of three arguments', function () {
93 expect(L.point(50.1, 30.1, true)).to.eql(new L.Point(50, 30));
94 });
95 it('creates a point from an array of coordinates', function () {
96 expect(L.point([50, 30])).to.eql(new L.Point(50, 30));
97 });
98 it('does not fail on invalid arguments', function () {
99 expect(L.point(undefined)).to.be(undefined);
100 expect(L.point(null)).to.be(null);
101 });
102 });
103 });
0 describe('PolyUtil', function () {
1
2 describe('#clipPolygon', function () {
3 it('clips polygon by bounds', function () {
4 var bounds = L.bounds([0, 0], [10, 10]);
5
6 var points = [
7 new L.Point(5, 5),
8 new L.Point(15, 10),
9 new L.Point(10, 15)
10 ];
11
12 var clipped = L.PolyUtil.clipPolygon(points, bounds);
13
14 for (var i = 0, len = clipped.length; i < len; i++) {
15 delete clipped[i]._code;
16 }
17
18 expect(clipped).to.eql([
19 new L.Point(7.5, 10),
20 new L.Point(5, 5),
21 new L.Point(10, 7.5),
22 new L.Point(10, 10)
23 ]);
24 });
25 });
26 });
00 describe("Transformation", function() {
11 var t, p;
2
2
33 beforeEach(function() {
44 t = new L.Transformation(1, 2, 3, 4);
55 p = new L.Point(10, 20);
66 });
7
8 it("#transform should perform a transformation", function() {
9 var p2 = t.transform(p, 2);
10 expect(p2).toEqual(new L.Point(24, 128));
7
8 describe('#transform', function () {
9 it("performs a transformation", function() {
10 var p2 = t.transform(p, 2);
11 expect(p2).to.eql(new L.Point(24, 128));
12 });
13 it('assumes a scale of 1 if not specified', function () {
14 var p2 = t.transform(p);
15 expect(p2).to.eql(new L.Point(12, 64));
16 });
1117 });
12
13 it("#untransform should perform a reverse transformation", function() {
14 var p2 = t.transform(p, 2);
15 var p3 = t.untransform(p2, 2);
16 expect(p3).toEqual(p);
18
19 describe('#untransform', function () {
20 it("performs a reverse transformation", function() {
21 var p2 = t.transform(p, 2);
22 var p3 = t.untransform(p2, 2);
23 expect(p3).to.eql(p);
24 });
25 it('assumes a scale of 1 if not specified', function () {
26 var p2 = t.transform(p);
27 expect(t.untransform(new L.Point(12, 64))).to.eql(new L.Point(10, 20));
28 });
1729 });
18 });
30 });
0 describe('CircleMarker', function () {
1 describe("#_propagateEvent", function () {
2 var map, marker;
3 beforeEach(function () {
4 map = L.map(document.createElement('div'));
5 map.setView([0, 0], 1);
6 marker = L.marker([0, 0]);
7 });
8 describe("when a Marker is added to multiple FeatureGroups ", function () {
9 it("e.layer should be the Marker", function () {
10 var fg1 = L.featureGroup(),
11 fg2 = L.featureGroup();
12
13 fg1.addLayer(marker);
14 fg2.addLayer(marker);
15
16 var wasClicked = 0;
17 fg2.on('click', function(e) {
18 expect(e.layer).to.be(marker);
19 expect(e.target).to.be(fg2);
20 wasClicked |= 1;
21 });
22
23 fg1.on('click', function (e) {
24 expect(e.layer).to.be(marker);
25 expect(e.target).to.be(fg1);
26 wasClicked |= 2;
27 });
28
29 marker.fire('click', { type: 'click' });
30
31 expect(wasClicked).to.be(3);
32 });
33 });
34 });
35 });
0 describe("L.GeoJSON", function () {
1 describe("addData", function () {
2 var geoJSON = {
3 type: 'Feature',
4 properties: {},
5 geometry: {
6 type: 'Point',
7 coordinates: [20, 10]
8 }
9 };
10
11 it("sets feature property on member layers", function () {
12 var layer = new L.GeoJSON();
13 layer.addData(geoJSON);
14 expect(layer.getLayers()[0].feature).to.eql(geoJSON);
15 });
16
17 it("normalizes a geometry to a Feature", function () {
18 var layer = new L.GeoJSON();
19 layer.addData(geoJSON.geometry);
20 expect(layer.getLayers()[0].feature).to.eql(geoJSON);
21 });
22 });
23 });
24
25 describe("L.Marker#toGeoJSON", function () {
26 it("returns a Point object", function () {
27 var marker = new L.Marker([10, 20]);
28 expect(marker.toGeoJSON().geometry).to.eql({
29 type: 'Point',
30 coordinates: [20, 10]
31 });
32 });
33 });
34
35 describe("L.Circle#toGeoJSON", function () {
36 it("returns a Point object", function () {
37 var circle = new L.Circle([10, 20], 100);
38 expect(circle.toGeoJSON().geometry).to.eql({
39 type: 'Point',
40 coordinates: [20, 10]
41 });
42 });
43 });
44
45 describe("L.CircleMarker#toGeoJSON", function () {
46 it("returns a Point object", function () {
47 var marker = new L.CircleMarker([10, 20]);
48 expect(marker.toGeoJSON().geometry).to.eql({
49 type: 'Point',
50 coordinates: [20, 10]
51 });
52 });
53 });
54
55 describe("L.Polyline#toGeoJSON", function () {
56 it("returns a LineString object", function () {
57 var polyline = new L.Polyline([[10, 20], [2, 5]]);
58 expect(polyline.toGeoJSON().geometry).to.eql({
59 type: 'LineString',
60 coordinates: [[20, 10], [5, 2]]
61 });
62 });
63 });
64
65 describe("L.MultiPolyline#toGeoJSON", function () {
66 it("returns a MultiLineString object", function () {
67 var multiPolyline = new L.MultiPolyline([[[10, 20], [2, 5]], [[1, 2], [3, 4]]]);
68 expect(multiPolyline.toGeoJSON().geometry).to.eql({
69 type: 'MultiLineString',
70 coordinates: [
71 [[20, 10], [5, 2]],
72 [[2, 1], [4, 3]]
73 ]
74 });
75 });
76 });
77
78 describe("L.Polygon#toGeoJSON", function () {
79 it("returns a Polygon object (no holes)", function () {
80 var polygon = new L.Polygon([[1, 2], [3, 4], [5, 6]]);
81 expect(polygon.toGeoJSON().geometry).to.eql({
82 type: 'Polygon',
83 coordinates: [[[2, 1], [4, 3], [6, 5], [2, 1]]]
84 });
85 });
86
87 it("returns a Polygon object (with holes)", function () {
88 var polygon = new L.Polygon([[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]);
89 expect(polygon.toGeoJSON().geometry).to.eql({
90 type: 'Polygon',
91 coordinates: [
92 [[2, 1], [4, 3], [6, 5], [2, 1]],
93 [[8, 7], [10, 9], [12, 11], [8, 7]]
94 ]
95 });
96 });
97 });
98
99 describe("L.MultiPolygon#toGeoJSON", function () {
100 it("returns a MultiPolygon object", function () {
101 var multiPolygon = new L.MultiPolygon([[[1, 2], [3, 4], [5, 6]]]);
102 expect(multiPolygon.toGeoJSON().geometry).to.eql({
103 type: 'MultiPolygon',
104 coordinates: [
105 [[[2, 1], [4, 3], [6, 5], [2, 1]]]
106 ]
107 });
108 });
109 });
110
111 describe("L.LayerGroup#toGeoJSON", function () {
112 it("returns a FeatureCollection object", function () {
113 var marker = new L.Marker([10, 20]),
114 polyline = new L.Polyline([[10, 20], [2, 5]]),
115 layerGroup = new L.LayerGroup([marker, polyline]);
116 expect(layerGroup.toGeoJSON()).to.eql({
117 type: 'FeatureCollection',
118 features: [marker.toGeoJSON(), polyline.toGeoJSON()]
119 });
120 });
121
122 it("ensures that every member is a Feature", function () {
123 var tileLayer = new L.TileLayer(),
124 layerGroup = new L.LayerGroup([tileLayer]);
125
126 tileLayer.toGeoJSON = function () {
127 return {
128 type: 'Point',
129 coordinates: [20, 10]
130 }
131 };
132
133 expect(layerGroup.toGeoJSON()).to.eql({
134 type: 'FeatureCollection',
135 features: [{
136 type: 'Feature',
137 properties: {},
138 geometry: {
139 type: 'Point',
140 coordinates: [20, 10]
141 }
142 }]
143 });
144 });
145
146 it("omits layers which do not implement toGeoJSON", function () {
147 var tileLayer = new L.TileLayer(),
148 layerGroup = new L.LayerGroup([tileLayer]);
149 expect(layerGroup.toGeoJSON()).to.eql({
150 type: 'FeatureCollection',
151 features: []
152 });
153 });
154 });
0 describe('LayerGroup', function () {
1 describe("#addLayer", function () {
2 it('adds a layer', function() {
3 var lg = L.layerGroup(),
4 marker = L.marker([0, 0]);
5
6 expect(lg.addLayer(marker)).to.eql(lg);
7
8 expect(lg.hasLayer(marker)).to.be(true);
9 });
10 });
11 describe("#removeLayer", function () {
12 it('removes a layer', function() {
13 var lg = L.layerGroup(),
14 marker = L.marker([0, 0]);
15
16 lg.addLayer(marker);
17 expect(lg.removeLayer(marker)).to.eql(lg);
18
19 expect(lg.hasLayer(marker)).to.be(false);
20 });
21 });
22 describe("#clearLayers", function () {
23 it('removes all layers', function() {
24 var lg = L.layerGroup(),
25 marker = L.marker([0, 0]);
26
27 lg.addLayer(marker);
28 expect(lg.clearLayers()).to.eql(lg);
29
30 expect(lg.hasLayer(marker)).to.be(false);
31 });
32 });
33 describe("#getLayers", function () {
34 it('gets all layers', function() {
35 var lg = L.layerGroup(),
36 marker = L.marker([0, 0]);
37
38 lg.addLayer(marker);
39
40 expect(lg.getLayers()).to.eql([marker]);
41 });
42 });
43 describe("#eachLayer", function () {
44 it('iterates over all layers', function() {
45 var lg = L.layerGroup(),
46 marker = L.marker([0, 0]),
47 ctx = { foo: 'bar' };
48
49 lg.addLayer(marker);
50
51 lg.eachLayer(function(layer) {
52 expect(layer).to.eql(marker);
53 expect(this).to.eql(ctx);
54 }, ctx);
55 });
56 });
57 });
0 describe('Popup', function() {
1
2 var c, map;
3
4 beforeEach(function () {
5 c = document.createElement('div');
6 c.style.width = '400px';
7 c.style.height = '400px';
8 map = new L.Map(c);
9 map.setView(new L.LatLng(55.8, 37.6), 6);
10 });
11
12 it("closes on map click when map has closePopupOnClick option", function() {
13 map.options.closePopupOnClick = true;
14
15 var popup = new L.Popup()
16 .setLatLng(new L.LatLng(55.8, 37.6))
17 .openOn(map);
18
19 happen.click(c);
20
21 expect(map.hasLayer(popup)).to.be(false);
22 });
23
24 it("closes on map click when popup has closeOnClick option", function() {
25 map.options.closePopupOnClick = false;
26
27 var popup = new L.Popup({closeOnClick: true})
28 .setLatLng(new L.LatLng(55.8, 37.6))
29 .openOn(map);
30
31 happen.click(c);
32
33 expect(map.hasLayer(popup)).to.be(false);
34 });
35
36 it("does not close on map click when popup has closeOnClick: false option", function() {
37 map.options.closePopupOnClick = true;
38
39 var popup = new L.Popup({closeOnClick: false})
40 .setLatLng(new L.LatLng(55.8, 37.6))
41 .openOn(map);
42
43 happen.click(c);
44
45 expect(map.hasLayer(popup)).to.be(true);
46 });
47
48 it("toggles its visibility when marker is clicked", function() {
49 var marker = new L.Marker(new L.LatLng(55.8, 37.6));
50 map.addLayer(marker);
51
52 marker.bindPopup('Popup1').openPopup();
53
54 map.options.closePopupOnClick = true;
55 happen.click(c);
56
57 // toggle open popup
58 sinon.spy(marker, "openPopup");
59 marker.fire('click');
60 expect(marker.openPopup.calledOnce).to.be(true);
61 expect(map.hasLayer(marker._popup)).to.be(true);
62 marker.openPopup.restore();
63
64 // toggle close popup
65 sinon.spy(marker, "closePopup");
66 marker.fire('click');
67 expect(marker.closePopup.calledOnce).to.be(true);
68 expect(map.hasLayer(marker._popup)).to.be(false);
69 marker.closePopup.restore();
70 });
71
72 it("should trigger popupopen on marker when popup opens", function() {
73 var marker1 = new L.Marker(new L.LatLng(55.8, 37.6));
74 var marker2 = new L.Marker(new L.LatLng(57.123076977278, 44.861962891635));
75
76 map.addLayer(marker1);
77 map.addLayer(marker2);
78
79 marker1.bindPopup('Popup1');
80 marker2.bindPopup('Popup2');
81
82 var spy = sinon.spy();
83
84 marker1.on('popupopen', spy);
85
86 expect(spy.called).to.be(false);
87 marker2.openPopup();
88 expect(spy.called).to.be(false);
89 marker1.openPopup();
90 expect(spy.called).to.be(true);
91 });
92
93 it("should trigger popupclose on marker when popup closes", function() {
94 var marker1 = new L.Marker(new L.LatLng(55.8, 37.6));
95 var marker2 = new L.Marker(new L.LatLng(57.123076977278, 44.861962891635));
96
97 map.addLayer(marker1);
98 map.addLayer(marker2);
99
100 marker1.bindPopup('Popup1');
101 marker2.bindPopup('Popup2');
102
103 var spy = sinon.spy();
104
105 marker1.on('popupclose', spy);
106
107 expect(spy.called).to.be(false);
108 marker2.openPopup();
109 expect(spy.called).to.be(false);
110 marker1.openPopup();
111 expect(spy.called).to.be(false);
112 marker2.openPopup();
113 expect(spy.called).to.be(true);
114 marker1.openPopup();
115 marker1.closePopup();
116 expect(spy.callCount).to.be(2);
117 });
118 });
00
11 describe('TileLayer', function () {
2 var tileUrl = '';
3
24 describe("#getMaxZoom, #getMinZoom", function () {
35 var map;
46 beforeEach(function () {
57 map = L.map(document.createElement('div'));
68 });
79 describe("when a tilelayer is added to a map with no other layers", function () {
8 it("should have the same zoomlevels as the tilelayer", function () {
10 it("has the same zoomlevels as the tilelayer", function () {
911 var maxZoom = 10,
1012 minZoom = 5;
1113 map.setView([0, 0], 1);
1214
13 L.tileLayer("{z}{x}{y}", {
15 L.tileLayer(tileUrl, {
1416 maxZoom: maxZoom,
1517 minZoom: minZoom
1618 }).addTo(map);
17 expect(map.getMaxZoom() === maxZoom).toBeTruthy();
18 expect(map.getMinZoom() === minZoom).toBeTruthy();
19 expect(map.getMaxZoom()).to.be(maxZoom);
20 expect(map.getMinZoom()).to.be(minZoom);
1921 });
2022 });
21 describe("when a tilelayer is added to a map that already has a tilelayer", function () {
22 it("should have its zoomlevels updated to fit the new layer", function () {
23
24 describe("accessing a tilelayer's properties", function () {
25 it('provides a container', function () {
2326 map.setView([0, 0], 1);
2427
25 L.tileLayer("{z}{x}{y}", { minZoom:10, maxZoom: 15 }).addTo(map);
26 expect(map.getMinZoom()).toBe(10);
27 expect(map.getMaxZoom()).toBe(15);
28 var layer = L.tileLayer(tileUrl).addTo(map);
29 expect(layer.getContainer()).to.be.ok();
30 });
31 });
2832
29 L.tileLayer("{z}{x}{y}", { minZoom:5, maxZoom: 10 }).addTo(map);
30 expect(map.getMinZoom()).toBe(5); // changed
31 expect(map.getMaxZoom()).toBe(15); // unchanged
33 describe("when a tilelayer is added to a map that already has a tilelayer", function () {
34 it("has its zoomlevels updated to fit the new layer", function () {
35 map.setView([0, 0], 1);
36
37 L.tileLayer(tileUrl, { minZoom:10, maxZoom: 15 }).addTo(map);
38 expect(map.getMinZoom()).to.be(10);
39 expect(map.getMaxZoom()).to.be(15);
40
41 L.tileLayer(tileUrl, { minZoom:5, maxZoom: 10 }).addTo(map);
42 expect(map.getMinZoom()).to.be(5); // changed
43 expect(map.getMaxZoom()).to.be(15); // unchanged
3244
3345
34 L.tileLayer("{z}{x}{y}",{ minZoom:10, maxZoom: 20 }).addTo(map);
35 expect(map.getMinZoom()).toBe(5); // unchanged
36 expect(map.getMaxZoom()).toBe(20); // changed
46 L.tileLayer(tileUrl,{ minZoom:10, maxZoom: 20 }).addTo(map);
47 expect(map.getMinZoom()).to.be(5); // unchanged
48 expect(map.getMaxZoom()).to.be(20); // changed
3749
3850
39 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 25 }).addTo(map);
40 expect(map.getMinZoom()).toBe(0); // changed
41 expect(map.getMaxZoom()).toBe(25); // changed
51 L.tileLayer(tileUrl, { minZoom:0, maxZoom: 25 }).addTo(map);
52 expect(map.getMinZoom()).to.be(0); // changed
53 expect(map.getMaxZoom()).to.be(25); // changed
4254 });
4355 });
4456 describe("when a tilelayer is removed from a map", function () {
45 it("it should have its zoomlevels updated to only fit the layers it currently has", function () {
46 var tiles = [ L.tileLayer("{z}{x}{y}", { minZoom:10, maxZoom: 15 }).addTo(map),
47 L.tileLayer("{z}{x}{y}", { minZoom:5, maxZoom: 10 }).addTo(map),
48 L.tileLayer("{z}{x}{y}", { minZoom:10, maxZoom: 20 }).addTo(map),
49 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 25 }).addTo(map)
57 it("has its zoomlevels updated to only fit the layers it currently has", function () {
58 var tiles = [ L.tileLayer(tileUrl, { minZoom:10, maxZoom: 15 }).addTo(map),
59 L.tileLayer(tileUrl, { minZoom:5, maxZoom: 10 }).addTo(map),
60 L.tileLayer(tileUrl, { minZoom:10, maxZoom: 20 }).addTo(map),
61 L.tileLayer(tileUrl, { minZoom:0, maxZoom: 25 }).addTo(map)
5062 ];
5163 map.whenReady(function() {
52 expect(map.getMinZoom()).toBe(0);
53 expect(map.getMaxZoom()).toBe(25);
64 expect(map.getMinZoom()).to.be(0);
65 expect(map.getMaxZoom()).to.be(25);
5466
5567 map.removeLayer(tiles[0]);
56 expect(map.getMinZoom()).toBe(0);
57 expect(map.getMaxZoom()).toBe(25);
68 expect(map.getMinZoom()).to.be(0);
69 expect(map.getMaxZoom()).to.be(25);
5870
5971 map.removeLayer(tiles[3]);
60 expect(map.getMinZoom()).toBe(5);
61 expect(map.getMaxZoom()).toBe(20);
72 expect(map.getMinZoom()).to.be(5);
73 expect(map.getMaxZoom()).to.be(20);
6274
6375 map.removeLayer(tiles[2]);
64 expect(map.getMinZoom()).toBe(5);
65 expect(map.getMaxZoom()).toBe(10);
76 expect(map.getMinZoom()).to.be(5);
77 expect(map.getMaxZoom()).to.be(10);
6678
6779 map.removeLayer(tiles[1]);
68 expect(map.getMinZoom()).toBe(0);
69 expect(map.getMaxZoom()).toBe(Infinity);
80 expect(map.getMinZoom()).to.be(0);
81 expect(map.getMaxZoom()).to.be(Infinity);
7082 });
7183 });
7284 });
7385 });
74 });
86 });
0 describe("Marker", function () {
1 var map,
2 spy;
3 beforeEach(function () {
4 map = L.map(document.createElement('div')).setView([0, 0], 0);
5 });
6
7 describe("#setIcon", function () {
8 it("changes the icon to another image", function () {
9 var marker = new L.Marker([0, 0], {icon: new L.Icon({iconUrl: 'icon1.png' }) });
10 map.addLayer(marker);
11
12 var beforeIcon = marker._icon;
13 marker.setIcon(new L.Icon({iconUrl: 'icon2.png' }));
14 var afterIcon = marker._icon;
15
16 expect(beforeIcon).to.be(afterIcon);
17 expect(afterIcon.src).to.contain('icon2.png');
18 });
19
20 it("changes the icon to another DivIcon", function () {
21 var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text' }) });
22 map.addLayer(marker);
23
24 var beforeIcon = marker._icon;
25 marker.setIcon(new L.DivIcon({html: 'Inner2Text' }));
26 var afterIcon = marker._icon;
27
28 expect(beforeIcon).to.be(afterIcon);
29 expect(afterIcon.innerHTML).to.contain('Inner2Text');
30 });
31
32 it("removes text when changing to a blank DivIcon", function () {
33 var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text' }) });
34 map.addLayer(marker);
35
36 marker.setIcon(new L.DivIcon());
37 var afterIcon = marker._icon;
38
39 expect(marker._icon.innerHTML).to.not.contain('Inner1Text');
40 });
41
42 it("changes a DivIcon to an image", function () {
43 var marker = new L.Marker([0, 0], {icon: new L.DivIcon({html: 'Inner1Text' }) });
44 map.addLayer(marker);
45 var oldIcon = marker._icon;
46
47 marker.setIcon(new L.Icon({iconUrl: 'icon1.png' }));
48
49 expect(oldIcon).to.not.be(marker._icon);
50 expect(oldIcon.parentNode).to.be(null);
51
52 expect(marker._icon.src).to.contain('icon1.png');
53 expect(marker._icon.parentNode).to.be(map._panes.markerPane);
54 });
55
56 it("changes an image to a DivIcon", function () {
57 var marker = new L.Marker([0, 0], {icon: new L.Icon({iconUrl: 'icon1.png' }) });
58 map.addLayer(marker);
59 var oldIcon = marker._icon;
60
61 marker.setIcon(new L.DivIcon({html: 'Inner1Text' }));
62
63 expect(oldIcon).to.not.be(marker._icon);
64 expect(oldIcon.parentNode).to.be(null);
65
66 expect(marker._icon.innerHTML).to.contain('Inner1Text');
67 expect(marker._icon.parentNode).to.be(map._panes.markerPane);
68 });
69
70 it("reuses the icon/shadow when changing icon", function () {
71 var marker = new L.Marker([0, 0], { icon: new L.Icon({ iconUrl: 'icon1.png', shadowUrl: 'shadow1.png', }) });
72 map.addLayer(marker);
73 var oldIcon = marker._icon;
74 var oldShadow = marker._shadow;
75
76 marker.setIcon(new L.Icon({ iconUrl: 'icon2.png', shadowUrl: 'shadow2.png', }));
77
78 expect(oldIcon).to.be(marker._icon);
79 expect(oldShadow).to.be(marker._shadow);
80
81 expect(marker._icon.parentNode).to.be(map._panes.markerPane);
82 expect(marker._shadow.parentNode).to.be(map._panes.shadowPane);
83 });
84 });
85 });
0 describe('CircleMarker', function() {
1 describe("#_radius", function() {
2 var map;
3 beforeEach(function() {
4 map = L.map(document.createElement('div'));
5 map.setView([0, 0], 1);
6 });
7 describe("when a CircleMarker is added to the map ", function() {
8 describe("with a radius set as an option", function() {
9 it("takes that radius", function() {
10 var marker = L.circleMarker([0, 0], { radius: 20 }).addTo(map);
11
12 expect(marker._radius).to.be(20);
13 });
14 });
15
16 describe("and radius is set before adding it", function () {
17 it("takes that radius", function () {
18 var marker = L.circleMarker([0, 0], { radius: 20 });
19 marker.setRadius(15);
20 marker.addTo(map);
21 expect(marker._radius).to.be(15);
22 });
23 });
24
25 describe("and radius is set after adding it", function () {
26 it("takes that radius", function () {
27 var marker = L.circleMarker([0, 0], { radius: 20 });
28 marker.addTo(map);
29 marker.setRadius(15);
30 expect(marker._radius).to.be(15);
31 });
32 });
33
34 describe("and setStyle is used to change the radius after adding", function () {
35 it("takes the given radius", function() {
36 var marker = L.circleMarker([0, 0], { radius: 20 });
37 marker.addTo(map);
38 marker.setStyle({ radius: 15 });
39 expect(marker._radius).to.be(15);
40 });
41 });
42 describe("and setStyle is used to change the radius before adding", function () {
43 it("takes the given radius", function () {
44 var marker = L.circleMarker([0, 0], { radius: 20 });
45 marker.setStyle({ radius: 15 });
46 marker.addTo(map);
47 expect(marker._radius).to.be(15);
48 });
49 });
50 });
51 });
52 });
66 circle = L.circle([50, 30], 200);
77 });
88
9 it('should return correct bounds', function () {
9 it('returns bounds', function () {
1010 var bounds = circle.getBounds();
1111
12 expect(bounds.getSouthWest().equals([49.998203369, 29.997204939])).toBeTruthy();
13 expect(bounds.getNorthEast().equals([50.001796631, 30.002795061])).toBeTruthy();
12 expect(bounds.getSouthWest().equals([49.998203369, 29.997204939])).to.be.ok();
13 expect(bounds.getNorthEast().equals([50.001796631, 30.002795061])).to.be.ok();
1414 });
1515 });
1616 });
0 describe('Polygon', function() {
1
2 var c = document.createElement('div');
3 c.style.width = '400px';
4 c.style.height = '400px';
5 var map = new L.Map(c);
6 map.setView(new L.LatLng(55.8, 37.6), 6);
7
8 describe("#initialize", function() {
9 it("doesn't overwrite the given latlng array", function () {
10 var originalLatLngs = [
11 [1, 2],
12 [3, 4]
13 ];
14 var sourceLatLngs = originalLatLngs.slice();
15
16 var polygon = new L.Polygon(sourceLatLngs);
17
18 expect(sourceLatLngs).to.eql(originalLatLngs);
19 expect(polygon._latlngs).to.not.eql(sourceLatLngs);
20 });
21
22 it("can be called with an empty array", function () {
23 var polygon = new L.Polygon([]);
24 expect(polygon.getLatLngs()).to.eql([]);
25 });
26 });
27
28 describe("#setLatLngs", function () {
29 it("doesn't overwrite the given latlng array", function () {
30 var originalLatLngs = [
31 [1, 2],
32 [3, 4]
33 ];
34 var sourceLatLngs = originalLatLngs.slice();
35
36 var polygon = new L.Polygon(sourceLatLngs);
37
38 polygon.setLatLngs(sourceLatLngs);
39
40 expect(sourceLatLngs).to.eql(originalLatLngs);
41 });
42 });
43
44 describe("#spliceLatLngs", function () {
45 it("splices the internal latLngs", function () {
46 var latLngs = [
47 [1, 2],
48 [3, 4],
49 [5, 6]
50 ];
51
52 var polygon = new L.Polygon(latLngs);
53
54 polygon.spliceLatLngs(1, 1, [7, 8]);
55
56 expect(polygon._latlngs).to.eql([L.latLng([1, 2]), L.latLng([7, 8]), L.latLng([5, 6])]);
57 });
58 });
59 });
00 describe('PolylineGeometry', function() {
1
1
22 var c = document.createElement('div');
33 c.style.width = '400px';
44 c.style.height = '400px';
55 var map = new L.Map(c);
66 map.setView(new L.LatLng(55.8, 37.6), 6);
7
7
88 describe("#distanceTo", function() {
9 it("should calculate correct distances to points", function() {
9 it("calculates distances to points", function() {
1010 var p1 = map.latLngToLayerPoint(new L.LatLng(55.8, 37.6));
1111 var p2 = map.latLngToLayerPoint(new L.LatLng(57.123076977278, 44.861962891635));
1212 var latlngs = [[56.485503424111, 35.545556640339], [55.972522915346, 36.116845702918], [55.502459116923, 34.930322265253], [55.31534617509, 38.973291015816]]
1717 'noClip': true
1818 });
1919 map.addLayer(polyline);
20
21 expect(polyline.closestLayerPoint(p1)).toEqual(null);
22
20
21 expect(polyline.closestLayerPoint(p1)).to.be(null);
22
2323 polyline.setLatLngs(latlngs);
2424 var point = polyline.closestLayerPoint(p1);
25 expect(point).not.toEqual(null);
26 expect(point.distance).not.toEqual(Infinity);
27 expect(point.distance).not.toEqual(NaN);
28
25 expect(point).not.to.be(null);
26 expect(point.distance).to.not.be(Infinity);
27 expect(point.distance).to.not.be(NaN);
28
2929 var point2 = polyline.closestLayerPoint(p2);
30
31 expect(point.distance).toBeLessThan(point2.distance);
30
31 expect(point.distance).to.be.lessThan(point2.distance);
3232 });
3333 });
3434 });
0 describe('Polyline', function() {
1
2 var c = document.createElement('div');
3 c.style.width = '400px';
4 c.style.height = '400px';
5 var map = new L.Map(c);
6 map.setView(new L.LatLng(55.8, 37.6), 6);
7
8 describe("#initialize", function() {
9 it("doesn't overwrite the given latlng array", function () {
10 var originalLatLngs = [
11 [1, 2],
12 [3, 4]
13 ];
14 var sourceLatLngs = originalLatLngs.slice();
15
16 var polyline = new L.Polyline(sourceLatLngs);
17
18 expect(sourceLatLngs).to.eql(originalLatLngs);
19 expect(polyline._latlngs).to.not.eql(sourceLatLngs);
20 });
21 });
22
23 describe("#setLatLngs", function () {
24 it("doesn't overwrite the given latlng array", function () {
25 var originalLatLngs = [
26 [1, 2],
27 [3, 4]
28 ];
29 var sourceLatLngs = originalLatLngs.slice();
30
31 var polyline = new L.Polyline(sourceLatLngs);
32
33 polyline.setLatLngs(sourceLatLngs);
34
35 expect(sourceLatLngs).to.eql(originalLatLngs);
36 });
37 });
38
39 describe("#spliceLatLngs", function () {
40 it("splices the internal latLngs", function () {
41 var latLngs = [
42 [1, 2],
43 [3, 4],
44 [5, 6]
45 ];
46
47 var polyline = new L.Polyline(latLngs);
48
49 polyline.spliceLatLngs(1, 1, [7, 8]);
50
51 expect(polyline._latlngs).to.eql([L.latLng([1, 2]), L.latLng([7, 8]), L.latLng([5, 6])]);
52 });
53 });
54 });
00 describe("Map", function () {
1 var map,
2 spy;
3 beforeEach(function () {
4 map = L.map(document.createElement('div'));
5 });
6
7 describe("#remove", function () {
8 it("fires an unload event if loaded", function () {
9 var container = document.createElement('div'),
10 map = new L.Map(container).setView([0, 0], 0),
11 spy = sinon.spy();
12 map.on('unload', spy);
13 map.remove();
14 expect(spy.called).to.be.ok();
15 });
16
17 it("fires no unload event if not loaded", function () {
18 var container = document.createElement('div'),
19 map = new L.Map(container),
20 spy = sinon.spy();
21 map.on('unload', spy);
22 map.remove();
23 expect(spy.called).not.to.be.ok();
24 });
25
26 describe("corner case checking", function () {
27 it("throws an exception upon reinitialization", function () {
28 var container = document.createElement('div'),
29 map = new L.Map(container);
30 expect(function () {
31 new L.Map(container);
32 }).to.throwException(function(e) {
33 expect(e.message).to.eql("Map container is already initialized.");
34 });
35 map.remove();
36 });
37
38 it("throws an exception if a container is not found", function () {
39 expect(function () {
40 new L.Map('nonexistentdivelement');
41 }).to.throwException(function(e) {
42 expect(e.message).to.eql("Map container not found.");
43 });
44 map.remove();
45 });
46 });
47
48 it("undefines container._leaflet", function () {
49 var container = document.createElement('div'),
50 map = new L.Map(container);
51 map.remove();
52 expect(container._leaflet).to.be(undefined);
53 });
54
55 it("unbinds events", function () {
56 var container = document.createElement('div'),
57 map = new L.Map(container).setView([0, 0], 1),
58 spy = sinon.spy();
59
60 map.on('click dblclick mousedown mouseup mousemove', spy);
61 map.remove();
62
63 happen.click(container);
64 happen.dblclick(container);
65 happen.mousedown(container);
66 happen.mouseup(container);
67 happen.mousemove(container);
68
69 expect(spy.called).to.not.be.ok();
70 });
71 });
72
73 describe('#getCenter', function () {
74 it ('throws if not set before', function () {
75 expect(function () {
76 map.getCenter();
77 }).to.throwError();
78 });
79 });
80
181 describe("#whenReady", function () {
282 describe("when the map has not yet been loaded", function () {
383 it("calls the callback when the map is loaded", function () {
4 var map = L.map(document.createElement('div')),
5 spy = jasmine.createSpy();
6
84 var spy = sinon.spy();
785 map.whenReady(spy);
8 expect(spy).not.toHaveBeenCalled();
86 expect(spy.called).to.not.be.ok();
987
1088 map.setView([0, 0], 1);
11 expect(spy).toHaveBeenCalled();
89 expect(spy.called).to.be.ok();
1290 });
1391 });
1492
1593 describe("when the map has already been loaded", function () {
1694 it("calls the callback immediately", function () {
17 var map = L.map(document.createElement('div')),
18 spy = jasmine.createSpy();
19
95 var spy = sinon.spy();
2096 map.setView([0, 0], 1);
2197 map.whenReady(spy);
2298
23 expect(spy).toHaveBeenCalled();
24 });
99 expect(spy.called).to.be.ok();
100 });
101 });
102 });
103
104 describe("#setView", function () {
105 it("sets the view of the map", function () {
106 expect(map.setView([51.505, -0.09], 13)).to.be(map);
107 expect(map.getZoom()).to.be(13);
108 expect(map.getCenter().distanceTo([51.505, -0.09])).to.be.lessThan(5);
25109 });
26110 });
27111
28112 describe("#getBounds", function () {
29 it("is safe to call from within a moveend callback during initial "
30 + "load (#1027)", function () {
113 it("is safe to call from within a moveend callback during initial load (#1027)", function () {
114 map.on("moveend", function () {
115 map.getBounds();
116 });
117
118 map.setView([51.505, -0.09], 13);
119 });
120 });
121
122 describe("#getMinZoom and #getMaxZoom", function () {
123 it("minZoom and maxZoom options overrides any minZoom and maxZoom set on layers", function () {
31124 var c = document.createElement('div'),
32 map = L.map(c);
33
34 map.on("moveend", function () {
35 map.getBounds();
36 });
37
38 map.setView([51.505, -0.09], 13);
39 });
40 });
41
42 describe("#getMinZoom and #getMaxZoom", function () {
43 it("The minZoom and maxZoom options overrides any"
44 + " minZoom and maxZoom set on layers", function () {
45 var c = document.createElement('div'),
46 map = L.map(c, { minZoom: 5, maxZoom: 10 });
47 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
48 L.tileLayer("{z}{x}{y}", { minZoom:5, maxZoom: 15 }).addTo(map);
49 expect(map.getMinZoom()).toBe(5);
50 expect(map.getMaxZoom()).toBe(10);
51 });
125 map = L.map(c, { minZoom: 5, maxZoom: 10 });
126 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
127 L.tileLayer("{z}{x}{y}", { minZoom:5, maxZoom: 15 }).addTo(map);
128 expect(map.getMinZoom()).to.be(5);
129 expect(map.getMaxZoom()).to.be(10);
130 });
131 });
132
133 describe("#addLayer", function () {
134 it("calls layer.onAdd immediately if the map is ready", function () {
135 var layer = { onAdd: sinon.spy() };
136 map.setView([0, 0], 0);
137 map.addLayer(layer);
138 expect(layer.onAdd.called).to.be.ok();
139 });
140
141 it("calls layer.onAdd when the map becomes ready", function () {
142 var layer = { onAdd: sinon.spy() };
143 map.addLayer(layer);
144 expect(layer.onAdd.called).not.to.be.ok();
145 map.setView([0, 0], 0);
146 expect(layer.onAdd.called).to.be.ok();
147 });
148
149 it("does not call layer.onAdd if the layer is removed before the map becomes ready", function () {
150 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
151 map.addLayer(layer);
152 map.removeLayer(layer);
153 map.setView([0, 0], 0);
154 expect(layer.onAdd.called).not.to.be.ok();
155 });
156
157 it("fires a layeradd event immediately if the map is ready", function () {
158 var layer = { onAdd: sinon.spy() },
159 spy = sinon.spy();
160 map.on('layeradd', spy);
161 map.setView([0, 0], 0);
162 map.addLayer(layer);
163 expect(spy.called).to.be.ok();
164 });
165
166 it("fires a layeradd event when the map becomes ready", function () {
167 var layer = { onAdd: sinon.spy() },
168 spy = sinon.spy();
169 map.on('layeradd', spy);
170 map.addLayer(layer);
171 expect(spy.called).not.to.be.ok();
172 map.setView([0, 0], 0);
173 expect(spy.called).to.be.ok();
174 });
175
176 it("does not fire a layeradd event if the layer is removed before the map becomes ready", function () {
177 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
178 spy = sinon.spy();
179 map.on('layeradd', spy);
180 map.addLayer(layer);
181 map.removeLayer(layer);
182 map.setView([0, 0], 0);
183 expect(spy.called).not.to.be.ok();
184 });
185
186 describe("When the first layer is added to a map", function () {
187 it("fires a zoomlevelschange event", function () {
188 var spy = sinon.spy();
189 map.on("zoomlevelschange", spy);
190 expect(spy.called).not.to.be.ok();
191 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
192 expect(spy.called).to.be.ok();
193 });
194 });
195
196 describe("when a new layer with greater zoomlevel coverage than the current layer is added to a map", function () {
197 it("fires a zoomlevelschange event", function () {
198 var spy = sinon.spy();
199 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
200 map.on("zoomlevelschange", spy);
201 expect(spy.called).not.to.be.ok();
202 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 }).addTo(map);
203 expect(spy.called).to.be.ok();
204 });
205 });
206
207 describe("when a new layer with the same or lower zoomlevel coverage as the current layer is added to a map", function () {
208 it("fires no zoomlevelschange event", function () {
209 var spy = sinon.spy();
210 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
211 map.on("zoomlevelschange", spy);
212 expect(spy.called).not.to.be.ok();
213 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
214 expect(spy.called).not.to.be.ok();
215 L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 }).addTo(map);
216 expect(spy.called).not.to.be.ok();
217 });
218 });
219 });
220
221 describe("#removeLayer", function () {
222 it("calls layer.onRemove if the map is ready", function () {
223 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
224 map.setView([0, 0], 0);
225 map.addLayer(layer);
226 map.removeLayer(layer);
227 expect(layer.onRemove.called).to.be.ok();
228 });
229
230 it("does not call layer.onRemove if the layer was not added", function () {
231 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
232 map.setView([0, 0], 0);
233 map.removeLayer(layer);
234 expect(layer.onRemove.called).not.to.be.ok();
235 });
236
237 it("does not call layer.onRemove if the map is not ready", function () {
238 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
239 map.addLayer(layer);
240 map.removeLayer(layer);
241 expect(layer.onRemove.called).not.to.be.ok();
242 });
243
244 it("fires a layerremove event if the map is ready", function () {
245 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
246 spy = sinon.spy();
247 map.on('layerremove', spy);
248 map.setView([0, 0], 0);
249 map.addLayer(layer);
250 map.removeLayer(layer);
251 expect(spy.called).to.be.ok();
252 });
253
254 it("does not fire a layerremove if the layer was not added", function () {
255 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
256 spy = sinon.spy();
257 map.on('layerremove', spy);
258 map.setView([0, 0], 0);
259 map.removeLayer(layer);
260 expect(spy.called).not.to.be.ok();
261 });
262
263 it("does not fire a layerremove if the map is not ready", function () {
264 var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
265 spy = sinon.spy();
266 map.on('layerremove', spy);
267 map.addLayer(layer);
268 map.removeLayer(layer);
269 expect(spy.called).not.to.be.ok();
270 });
271
272 describe("when the last tile layer on a map is removed", function () {
273 it("fires a zoomlevelschange event", function () {
274 map.whenReady(function(){
275 var spy = sinon.spy();
276 var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map);
277
278 map.on("zoomlevelschange", spy);
279 expect(spy.called).not.to.be.ok();
280 map.removeLayer(tl);
281 expect(spy.called).to.be.ok();
282 });
283 });
284 });
285
286 describe("when a tile layer is removed from a map and it had greater zoom level coverage than the remainding layer", function () {
287 it("fires a zoomlevelschange event", function () {
288 map.whenReady(function(){
289 var spy = sinon.spy(),
290 tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map),
291 t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 15 }).addTo(map);
292
293 map.on("zoomlevelschange", spy);
294 expect(spy.called).to.not.be.ok();
295 map.removeLayer(t2);
296 expect(spy.called).to.be.ok();
297 });
298 });
299 });
300
301 describe("when a tile layer is removed from a map it and it had lesser or the sa,e zoom level coverage as the remainding layer(s)", function () {
302 it("fires no zoomlevelschange event", function () {
303 map.whenReady(function(){
304 var tl = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map),
305 t2 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 10 }).addTo(map),
306 t3 = L.tileLayer("{z}{x}{y}", { minZoom:0, maxZoom: 5 }).addTo(map);
307
308 map.on("zoomlevelschange", spy);
309 expect(spy).not.toHaveBeenCalled();
310 map.removeLayer(t2);
311 expect(spy).not.toHaveBeenCalled();
312 map.removeLayer(t3);
313 expect(spy).not.toHaveBeenCalled();
314 });
315 });
316 });
317 });
318
319 describe("#eachLayer", function () {
320 it("returns self", function () {
321 expect(map.eachLayer(function () {})).to.be(map);
322 });
323
324 it("calls the provided function for each layer", function () {
325 var t1 = L.tileLayer("{z}{x}{y}").addTo(map),
326 t2 = L.tileLayer("{z}{x}{y}").addTo(map),
327 spy = sinon.spy();
328
329 map.eachLayer(spy);
330
331 expect(spy.callCount).to.eql(2);
332 expect(spy.firstCall.args).to.eql([t1]);
333 expect(spy.secondCall.args).to.eql([t2]);
334 });
335
336 it("calls the provided function with the provided context", function () {
337 var t1 = L.tileLayer("{z}{x}{y}").addTo(map),
338 spy = sinon.spy();
339
340 map.eachLayer(spy, map);
341
342 expect(spy.thisValues[0]).to.eql(map);
343 });
52344 });
53345 });
0 /*
1 * The L namespace contains all Leaflet classes and functions.
2 * This code allows you to handle any possible namespace conflicts.
3 */
40
5 var L, originalL;
1 var oldL = window.L,
2 L = {};
63
7 if (typeof exports !== undefined + '') {
8 L = exports;
9 } else {
10 originalL = window.L;
11 L = {};
4 L.version = '0.6';
125
13 L.noConflict = function () {
14 window.L = originalL;
15 return this;
16 };
6 // define Leaflet for Node module pattern loaders, including Browserify
7 if (typeof module === 'object' && typeof module.exports === 'object') {
8 module.exports = L;
179
18 window.L = L;
10 // define Leaflet as an AMD module
11 } else if (typeof define === 'function' && define.amd) {
12 define(L);
1913 }
2014
21 L.version = '0.5.1';
15 // define Leaflet as a global L variable, saving the original L to restore later if needed
16
17 L.noConflict = function () {
18 window.L = oldL;
19 return this;
20 };
21
22 window.L = L;
44 L.Control.Attribution = L.Control.extend({
55 options: {
66 position: 'bottomright',
7 prefix: 'Powered by <a href="http://leafletjs.com">Leaflet</a>'
7 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
88 },
99
1010 initialize: function (options) {
5555 removeAttribution: function (text) {
5656 if (!text) { return; }
5757
58 this._attributions[text]--;
59 this._update();
58 if (this._attributions[text]) {
59 this._attributions[text]--;
60 this._update();
61 }
6062
6163 return this;
6264 },
6769 var attribs = [];
6870
6971 for (var i in this._attributions) {
70 if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
72 if (this._attributions[i]) {
7173 attribs.push(i);
7274 }
7375 }
8183 prefixAndAttribs.push(attribs.join(', '));
8284 }
8385
84 this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
86 this._container.innerHTML = prefixAndAttribs.join(' | ');
8587 },
8688
8789 _onLayerAdd: function (e) {
1616 this._handlingClick = false;
1717
1818 for (var i in baseLayers) {
19 if (baseLayers.hasOwnProperty(i)) {
20 this._addLayer(baseLayers[i], i);
21 }
19 this._addLayer(baseLayers[i], i);
2220 }
2321
2422 for (i in overlays) {
25 if (overlays.hasOwnProperty(i)) {
26 this._addLayer(overlays[i], i, true);
27 }
23 this._addLayer(overlays[i], i, true);
2824 }
2925 },
3026
6864 var className = 'leaflet-control-layers',
6965 container = this._container = L.DomUtil.create('div', className);
7066
67 //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
68 container.setAttribute('aria-haspopup', true);
69
7170 if (!L.Browser.touch) {
7271 L.DomEvent.disableClickPropagation(container);
73 L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
72 L.DomEvent.on(container, 'wheel', L.DomEvent.stopPropagation);
7473 } else {
7574 L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
7675 }
7877 var form = this._form = L.DomUtil.create('form', className + '-list');
7978
8079 if (this.options.collapsed) {
81 L.DomEvent
82 .on(container, 'mouseover', this._expand, this)
83 .on(container, 'mouseout', this._collapse, this);
84
80 if (!L.Browser.android) {
81 L.DomEvent
82 .on(container, 'mouseover', this._expand, this)
83 .on(container, 'mouseout', this._collapse, this);
84 }
8585 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
8686 link.href = '#';
8787 link.title = 'Layers';
8888
8989 if (L.Browser.touch) {
9090 L.DomEvent
91 .on(link, 'click', L.DomEvent.stopPropagation)
92 .on(link, 'click', L.DomEvent.preventDefault)
91 .on(link, 'click', L.DomEvent.stop)
9392 .on(link, 'click', this._expand, this);
9493 }
9594 else {
9695 L.DomEvent.on(link, 'focus', this._expand, this);
9796 }
9897
99 this._map.on('movestart', this._collapse, this);
98 this._map.on('click', this._collapse, this);
10099 // TODO keyboard accessibility
101100 } else {
102101 this._expand();
133132 this._overlaysList.innerHTML = '';
134133
135134 var baseLayersPresent = false,
136 overlaysPresent = false;
137
138 for (var i in this._layers) {
139 if (this._layers.hasOwnProperty(i)) {
140 var obj = this._layers[i];
141 this._addItem(obj);
142 overlaysPresent = overlaysPresent || obj.overlay;
143 baseLayersPresent = baseLayersPresent || !obj.overlay;
144 }
145 }
146
147 this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
135 overlaysPresent = false,
136 i, obj;
137
138 for (i in this._layers) {
139 obj = this._layers[i];
140 this._addItem(obj);
141 overlaysPresent = overlaysPresent || obj.overlay;
142 baseLayersPresent = baseLayersPresent || !obj.overlay;
143 }
144
145 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
148146 },
149147
150148 _onLayerChange: function (e) {
151 var id = L.stamp(e.layer);
152
153 if (this._layers[id] && !this._handlingClick) {
149 var obj = this._layers[L.stamp(e.layer)];
150
151 if (!obj) { return; }
152
153 if (!this._handlingClick) {
154154 this._update();
155 }
156
157 var type = obj.overlay ?
158 (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
159 (e.type === 'layeradd' ? 'baselayerchange' : null);
160
161 if (type) {
162 this._map.fire(type, obj);
155163 }
156164 },
157165
203211 _onInputClick: function () {
204212 var i, input, obj,
205213 inputs = this._form.getElementsByTagName('input'),
206 inputsLen = inputs.length,
207 baseLayer;
214 inputsLen = inputs.length;
208215
209216 this._handlingClick = true;
210217
214221
215222 if (input.checked && !this._map.hasLayer(obj.layer)) {
216223 this._map.addLayer(obj.layer);
217 if (!obj.overlay) {
218 baseLayer = obj.layer;
219 }
224
220225 } else if (!input.checked && this._map.hasLayer(obj.layer)) {
221226 this._map.removeLayer(obj.layer);
222227 }
223 }
224
225 if (baseLayer) {
226 this._map.setZoom(this._map.getZoom());
227 this._map.fire('baselayerchange', {layer: baseLayer});
228228 }
229229
230230 this._handlingClick = false;
88
99 onAdd: function (map) {
1010 var zoomName = 'leaflet-control-zoom',
11 barName = 'leaflet-bar',
12 partName = barName + '-part',
13 container = L.DomUtil.create('div', zoomName + ' ' + barName);
11 container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
1412
1513 this._map = map;
1614
17 this._zoomInButton = this._createButton('+', 'Zoom in',
18 zoomName + '-in ' +
19 partName + ' ' +
20 partName + '-top',
21 container, this._zoomIn, this);
15 this._zoomInButton = this._createButton(
16 '+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this);
17 this._zoomOutButton = this._createButton(
18 '-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this);
2219
23 this._zoomOutButton = this._createButton('-', 'Zoom out',
24 zoomName + '-out ' +
25 partName + ' ' +
26 partName + '-bottom',
27 container, this._zoomOut, this);
28
29 map.on('zoomend', this._updateDisabled, this);
20 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
3021
3122 return container;
3223 },
3324
3425 onRemove: function (map) {
35 map.off('zoomend', this._updateDisabled, this);
26 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
3627 },
3728
3829 _zoomIn: function (e) {
6354
6455 _updateDisabled: function () {
6556 var map = this._map,
66 className = 'leaflet-control-zoom-disabled';
57 className = 'leaflet-disabled';
6758
6859 L.DomUtil.removeClass(this._zoomInButton, className);
6960 L.DomUtil.removeClass(this._zoomOutButton, className);
2929 }
3030
3131 return this;
32 },
33
34 getContainer: function () {
35 return this._container;
3236 },
3337
3438 addTo: function (map) {
6771 L.control = function (options) {
6872 return new L.Control(options);
6973 };
74
75
76 // adds control-related methods to L.Map
77
78 L.Map.include({
79 addControl: function (control) {
80 control.addTo(this);
81 return this;
82 },
83
84 removeControl: function (control) {
85 control.removeFrom(this);
86 return this;
87 },
88
89 _initControlPos: function () {
90 var corners = this._controlCorners = {},
91 l = 'leaflet-',
92 container = this._controlContainer =
93 L.DomUtil.create('div', l + 'control-container', this._container);
94
95 function createCorner(vSide, hSide) {
96 var className = l + vSide + ' ' + l + hSide;
97
98 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
99 }
100
101 createCorner('top', 'left');
102 createCorner('top', 'right');
103 createCorner('bottom', 'left');
104 createCorner('bottom', 'right');
105 },
106
107 _clearControlPos: function () {
108 this._container.removeChild(this._controlContainer);
109 }
110 });
00 /*
11 Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
2 (c) 2010-2013, Vladimir Agafonkin, CloudMade
2 (c) 2010-2013, Vladimir Agafonkin
3 (c) 2010-2011, CloudMade
34 */
66 var ie = !!window.ActiveXObject,
77 ie6 = ie && !window.XMLHttpRequest,
88 ie7 = ie && !document.querySelector,
9 ielt9 = ie && !document.addEventListener,
910
1011 // terrible browser detection to work around Safari / iOS / Android browser bugs
1112 ua = navigator.userAgent.toLowerCase(),
1213 webkit = ua.indexOf('webkit') !== -1,
1314 chrome = ua.indexOf('chrome') !== -1,
15 phantomjs = ua.indexOf('phantom') !== -1,
1416 android = ua.indexOf('android') !== -1,
1517 android23 = ua.search('android [23]') !== -1,
1618
2628 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
2729 gecko3d = 'MozPerspective' in doc.style,
2830 opera3d = 'OTransition' in doc.style,
29 any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d);
31 any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
3032
3133
32 var touch = !window.L_NO_TOUCH && (function () {
34 // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch.
35 // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151
36
37 var touch = !window.L_NO_TOUCH && !phantomjs && (function () {
3338
3439 var startName = 'ontouchstart';
3540
6267 ie: ie,
6368 ie6: ie6,
6469 ie7: ie7,
70 ielt9: ielt9,
6571 webkit: webkit,
6672
6773 android: android,
5959 proto._initHooks = [];
6060
6161 var parent = this;
62 // jshint camelcase: false
63 NewClass.__super__ = parent.prototype;
64
6265 // add method for calling all hooks
6366 proto.callInitHooks = function () {
6467
11 * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
22 */
33
4 var key = '_leaflet_events';
4 var eventsKey = '_leaflet_events';
55
66 L.Mixin = {};
77
88 L.Mixin.Events = {
99
1010 addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
11 var events = this[key] = this[key] || {},
12 type, i, len;
1311
14 // Types can be a map of types/handlers
15 if (typeof types === 'object') {
16 for (type in types) {
17 if (types.hasOwnProperty(type)) {
18 this.addEventListener(type, types[type], fn);
19 }
20 }
12 // types can be a map of types/handlers
13 if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
2114
22 return this;
23 }
15 var events = this[eventsKey] = this[eventsKey] || {},
16 contextId = context && L.stamp(context),
17 i, len, event, type, indexKey, indexLenKey, typeIndex;
2418
19 // types can be a string of space-separated words
2520 types = L.Util.splitWords(types);
2621
2722 for (i = 0, len = types.length; i < len; i++) {
28 events[types[i]] = events[types[i]] || [];
29 events[types[i]].push({
23 event = {
3024 action: fn,
3125 context: context || this
32 });
26 };
27 type = types[i];
28
29 if (context) {
30 // store listeners of a particular context in a separate hash (if it has an id)
31 // gives a major performance boost when removing thousands of map layers
32
33 indexKey = type + '_idx';
34 indexLenKey = indexKey + '_len';
35
36 typeIndex = events[indexKey] = events[indexKey] || {};
37
38 if (!typeIndex[contextId]) {
39 typeIndex[contextId] = [];
40
41 // keep track of the number of keys in the index to quickly check if it's empty
42 events[indexLenKey] = (events[indexLenKey] || 0) + 1;
43 }
44
45 typeIndex[contextId].push(event);
46
47
48 } else {
49 events[type] = events[type] || [];
50 events[type].push(event);
51 }
3352 }
3453
3554 return this;
3655 },
3756
3857 hasEventListeners: function (type) { // (String) -> Boolean
39 return (key in this) && (type in this[key]) && (this[key][type].length > 0);
58 var events = this[eventsKey];
59 return !!events && ((type in events && events[type].length > 0) ||
60 (type + '_idx' in events && events[type + '_idx_len'] > 0));
4061 },
4162
42 removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
43 var events = this[key],
44 type, i, len, listeners, j;
63 removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
4564
46 if (typeof types === 'object') {
47 for (type in types) {
48 if (types.hasOwnProperty(type)) {
49 this.removeEventListener(type, types[type], fn);
50 }
51 }
52
65 if (!this[eventsKey]) {
5366 return this;
5467 }
68
69 if (!types) {
70 return this.clearAllEventListeners();
71 }
72
73 if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
74
75 var events = this[eventsKey],
76 contextId = context && L.stamp(context),
77 i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
5578
5679 types = L.Util.splitWords(types);
5780
5881 for (i = 0, len = types.length; i < len; i++) {
82 type = types[i];
83 indexKey = type + '_idx';
84 indexLenKey = indexKey + '_len';
5985
60 if (this.hasEventListeners(types[i])) {
61 listeners = events[types[i]];
86 typeIndex = events[indexKey];
6287
63 for (j = listeners.length - 1; j >= 0; j--) {
64 if (
65 (!fn || listeners[j].action === fn) &&
66 (!context || (listeners[j].context === context))
67 ) {
68 listeners.splice(j, 1);
88 if (!fn) {
89 // clear all listeners for a type if function isn't specified
90 delete events[type];
91 delete events[indexKey];
92
93 } else {
94 listeners = context && typeIndex ? typeIndex[contextId] : events[type];
95
96 if (listeners) {
97 for (j = listeners.length - 1; j >= 0; j--) {
98 if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
99 removed = listeners.splice(j, 1);
100 // set the old action to a no-op, because it is possible
101 // that the listener is being iterated over as part of a dispatch
102 removed[0].action = L.Util.falseFn;
103 }
104 }
105
106 if (context && typeIndex && (listeners.length === 0)) {
107 delete typeIndex[contextId];
108 events[indexLenKey]--;
69109 }
70110 }
71111 }
74114 return this;
75115 },
76116
117 clearAllEventListeners: function () {
118 delete this[eventsKey];
119 return this;
120 },
121
77122 fireEvent: function (type, data) { // (String[, Object])
78123 if (!this.hasEventListeners(type)) {
79124 return this;
80125 }
81126
82 var event = L.extend({
83 type: type,
84 target: this
85 }, data);
127 var event = L.Util.extend({}, data, { type: type, target: this });
86128
87 var listeners = this[key][type].slice();
129 var events = this[eventsKey],
130 listeners, i, len, typeIndex, contextId;
88131
89 for (var i = 0, len = listeners.length; i < len; i++) {
90 listeners[i].action.call(listeners[i].context || this, event);
132 if (events[type]) {
133 // make sure adding/removing listeners inside other listeners won't cause infinite loop
134 listeners = events[type].slice();
135
136 for (i = 0, len = listeners.length; i < len; i++) {
137 listeners[i].action.call(listeners[i].context || this, event);
138 }
139 }
140
141 // fire event for the context-indexed listeners as well
142 typeIndex = events[type + '_idx'];
143
144 for (contextId in typeIndex) {
145 listeners = typeIndex[contextId].slice();
146
147 if (listeners) {
148 for (i = 0, len = listeners.length; i < len; i++) {
149 listeners[i].action.call(listeners[i].context || this, event);
150 }
151 }
91152 }
92153
93154 return this;
155 },
156
157 addOneTimeEventListener: function (types, fn, context) {
158
159 if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
160
161 var handler = L.bind(function () {
162 this
163 .removeEventListener(types, fn, context)
164 .removeEventListener(types, handler, context);
165 }, this);
166
167 return this
168 .addEventListener(types, fn, context)
169 .addEventListener(types, handler, context);
94170 }
95171 };
96172
97173 L.Mixin.Events.on = L.Mixin.Events.addEventListener;
98174 L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
175 L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
99176 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
2525 },
2626
2727 stamp: (function () {
28 var lastId = 0, key = '_leaflet_id';
29 return function (/*Object*/ obj) {
28 var lastId = 0,
29 key = '_leaflet_id';
30 return function (obj) {
3031 obj[key] = obj[key] || ++lastId;
3132 return obj[key];
3233 };
3334 }()),
35
36 invokeEach: function (obj, method, context) {
37 var i, args;
38
39 if (typeof obj === 'object') {
40 args = Array.prototype.slice.call(arguments, 3);
41
42 for (i in obj) {
43 method.apply(context, [i, obj[i]].concat(args));
44 }
45 return true;
46 }
47
48 return false;
49 },
3450
3551 limitExecByInterval: function (fn, time, context) {
3652 var lock, execOnUnlock;
6783 return Math.round(num * pow) / pow;
6884 },
6985
86 trim: function (str) {
87 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
88 },
89
7090 splitWords: function (str) {
71 return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
91 return L.Util.trim(str).split(/\s+/);
7292 },
7393
7494 setOptions: function (obj, options) {
7696 return obj.options;
7797 },
7898
79 getParamString: function (obj, existingUrl) {
99 getParamString: function (obj, existingUrl, uppercase) {
80100 var params = [];
81101 for (var i in obj) {
82 if (obj.hasOwnProperty(i)) {
83 params.push(i + '=' + obj[i]);
84 }
102 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
85103 }
86104 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
87105 },
89107 template: function (str, data) {
90108 return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
91109 var value = data[key];
92 if (!data.hasOwnProperty(key)) {
110 if (value === undefined) {
93111 throw new Error('No value provided for variable ' + str);
112 } else if (typeof value === 'function') {
113 value = value(data);
94114 }
95115 return value;
96116 });
98118
99119 isArray: function (obj) {
100120 return (Object.prototype.toString.call(obj) === '[object Array]');
101 },
121 },
102122
103123 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
104124 };
1919
2020 function onTouchStart(e) {
2121 var count;
22
2223 if (L.Browser.msTouch) {
2324 trackedTouches.push(e.pointerId);
2425 count = trackedTouches.length;
3839 }
3940
4041 function onTouchEnd(e) {
41 /*jshint forin:false */
4242 if (L.Browser.msTouch) {
4343 var idx = trackedTouches.indexOf(e.pointerId);
4444 if (idx === -1) {
4949
5050 if (doubleTap) {
5151 if (L.Browser.msTouch) {
52 //Work around .type being readonly with MSPointer* events
52 // work around .type being readonly with MSPointer* events
5353 var newTouch = { },
5454 prop;
5555
56 // jshint forin:false
5657 for (var i in touch) {
5758 prop = touch[i];
5859 if (typeof prop === 'function') {
7172 obj[pre + touchstart + id] = onTouchStart;
7273 obj[pre + touchend + id] = onTouchEnd;
7374
74 //On msTouch we need to listen on the document otherwise a drag starting on the map and moving off screen will not come through to us
75 // so we will lose track of how many touches are ongoing
75 // on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen
76 // will not come through to us, so we will lose track of how many touches are ongoing
7677 var endElement = L.Browser.msTouch ? document.documentElement : obj;
7778
7879 obj.addEventListener(touchstart, onTouchStart, false);
7980 endElement.addEventListener(touchend, onTouchEnd, false);
81
8082 if (L.Browser.msTouch) {
8183 endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
8284 }
85
8386 return this;
8487 },
8588
8689 removeDoubleTapListener: function (obj, id) {
8790 var pre = '_leaflet_';
91
8892 obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
89 (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(this._touchend, obj[pre + this._touchend + id], false);
93 (L.Browser.msTouch ? document.documentElement : obj).removeEventListener(
94 this._touchend, obj[pre + this._touchend + id], false);
95
9096 if (L.Browser.msTouch) {
9197 document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
9298 }
99
93100 return this;
94101 }
95102 });
22 */
33
44 L.DomEvent = {
5 WHEEL:
6 'onwheel' in document ? 'wheel' :
7 'onmousewheel' in document ? 'mousewheel' :
8 'MozMousePixelScroll',
9
510 /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
611 addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
712
2227 this.addDoubleTapListener(obj, handler, id);
2328 }
2429
30 if (type === 'wheel' || type === 'mousewheel') {
31 type = L.DomEvent.WHEEL;
32 }
33
2534 if ('addEventListener' in obj) {
2635
27 if (type === 'mousewheel') {
28 obj.addEventListener('DOMMouseScroll', handler, false);
29 obj.addEventListener(type, handler, false);
30
31 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
36 if ((type === 'mouseenter') || (type === 'mouseleave')) {
3237
3338 originalHandler = handler;
3439 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
4045
4146 obj.addEventListener(newType, handler, false);
4247
48 } else if (type === 'click' && L.Browser.android) {
49 originalHandler = handler;
50 handler = function (e) {
51 return L.DomEvent._filterClick(e, originalHandler);
52 };
53
54 obj.addEventListener(type, handler, false);
4355 } else {
4456 obj.addEventListener(type, handler, false);
4557 }
4658
4759 } else if ('attachEvent' in obj) {
48 obj.attachEvent("on" + type, handler);
60 obj.attachEvent('on' + type, handler);
4961 }
5062
5163 obj[key] = handler;
5971 key = '_leaflet_' + type + id,
6072 handler = obj[key];
6173
62 if (!handler) { return; }
74 if (!handler) { return this; }
75
76 if (type === 'wheel' || type === 'mousewheel') {
77 type = L.DomEvent.WHEEL;
78 }
6379
6480 if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6581 this.removeMsTouchListener(obj, type, id);
6884
6985 } else if ('removeEventListener' in obj) {
7086
71 if (type === 'mousewheel') {
72 obj.removeEventListener('DOMMouseScroll', handler, false);
73 obj.removeEventListener(type, handler, false);
74
75 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
87 if ((type === 'mouseenter') || (type === 'mouseleave')) {
7688 obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
7789 } else {
7890 obj.removeEventListener(type, handler, false);
7991 }
8092 } else if ('detachEvent' in obj) {
81 obj.detachEvent("on" + type, handler);
93 obj.detachEvent('on' + type, handler);
8294 }
8395
8496 obj[key] = null;
97109 },
98110
99111 disableClickPropagation: function (el) {
100
101112 var stop = L.DomEvent.stopPropagation;
102113
103114 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
105116 }
106117
107118 return L.DomEvent
108 .addListener(el, 'click', stop)
119 .addListener(el, 'click', L.DomEvent._fakeStop)
109120 .addListener(el, 'dblclick', stop);
110121 },
111122
135146 },
136147
137148 getWheelDelta: function (e) {
138
139149 var delta = 0;
140150
141 if (e.wheelDelta) {
151 if (e.type === 'wheel') {
152 delta = -e.deltaY / (e.deltaMode ? 1 : 120);
153 } else if (e.type === 'mousewheel') {
142154 delta = e.wheelDelta / 120;
143 }
144 if (e.detail) {
145 delta = -e.detail / 3;
146 }
155 } else if (e.type === 'MozMousePixelScroll') {
156 delta = -e.detail;
157 }
158
147159 return delta;
160 },
161
162 _fakeStop: function stop(e) {
163 // fakes stopPropagation by setting a special event flag checked in Map mouse events handler
164 // jshint camelcase: false
165 e._leaflet_stop = true;
148166 },
149167
150168 // check if element really left/entered the event target (for mouseenter/mouseleave)
178196 }
179197 }
180198 return e;
199 },
200
201 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
202 _filterClick: function (e, handler) {
203 var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
204 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
205
206 // are they closer together than 1000ms yet more than 100ms?
207 // Android typically triggers them ~300ms apart while multiple listeners
208 // on the same event should be triggered far faster;
209 // or check if click is simulated on the element, and if it is, reject any non-simulated events
210
211 if ((elapsed && elapsed > 100 && elapsed < 1000) || (e.target._simulatedClick && !e._simulated)) {
212 L.DomEvent.stop(e);
213 return;
214 }
215 L.DomEvent._lastClick = timeStamp;
216
217 return handler(e);
181218 }
182219 };
183220
2828 left = 0,
2929 el = element,
3030 docBody = document.body,
31 docEl = document.documentElement,
3132 pos,
3233 ie7 = L.Browser.ie7;
3334
3637 left += el.offsetLeft || 0;
3738
3839 //add borders
39 top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0;
40 left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0;
40 top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
41 left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
4142
4243 pos = L.DomUtil.getStyle(el, 'position');
4344
4445 if (el.offsetParent === docBody && pos === 'absolute') { break; }
4546
4647 if (pos === 'fixed') {
47 top += docBody.scrollTop || 0;
48 left += docBody.scrollLeft || 0;
48 top += docBody.scrollTop || docEl.scrollTop || 0;
49 left += docBody.scrollLeft || docEl.scrollLeft || 0;
4950 break;
5051 }
52
53 if (pos === 'relative' && !el.offsetLeft) {
54 var width = L.DomUtil.getStyle(el, 'width'),
55 maxWidth = L.DomUtil.getStyle(el, 'max-width'),
56 r = el.getBoundingClientRect();
57
58 if (width !== 'none' || maxWidth !== 'none') {
59 left += r.left + el.clientLeft;
60 }
61
62 //calculate full y offset since we're breaking out of the loop
63 top += r.top + (docBody.scrollTop || docEl.scrollTop || 0);
64
65 break;
66 }
67
5168 el = el.offsetParent;
5269
5370 } while (el);
8299 documentIsLtr: function () {
83100 if (!L.DomUtil._docIsLtrCached) {
84101 L.DomUtil._docIsLtrCached = true;
85 L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === "ltr";
102 L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
86103 }
87104 return L.DomUtil._docIsLtr;
88105 },
99116 return el;
100117 },
101118
102 disableTextSelection: function () {
103 if (document.selection && document.selection.empty) {
104 document.selection.empty();
105 }
106 if (!this._onselectstart) {
107 this._onselectstart = document.onselectstart || null;
108 document.onselectstart = L.Util.falseFn;
109 }
110 },
111
112 enableTextSelection: function () {
113 if (document.onselectstart === L.Util.falseFn) {
114 document.onselectstart = this._onselectstart;
115 this._onselectstart = null;
116 }
117 },
118
119119 hasClass: function (el, name) {
120120 return (el.className.length > 0) &&
121 new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
121 new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className);
122122 },
123123
124124 addClass: function (el, name) {
128128 },
129129
130130 removeClass: function (el, name) {
131
132 function replaceFn(w, match) {
133 if (match === name) { return ''; }
134 return w;
135 }
136
137 el.className = el.className
138 .replace(/(\S+)\s*/g, replaceFn)
139 .replace(/(^\s+|\s+$)/, '');
131 el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' '));
140132 },
141133
142134 setOpacity: function (el, value) {
150142 filterName = 'DXImageTransform.Microsoft.Alpha';
151143
152144 // filters collection throws an error if we try to retrieve a filter that doesn't exist
153 try { filter = el.filters.item(filterName); } catch (e) {}
145 try {
146 filter = el.filters.item(filterName);
147 } catch (e) {
148 // don't set opacity to 1 if we haven't already set an opacity,
149 // it isn't needed and breaks transparent pngs.
150 if (value === 1) { return; }
151 }
154152
155153 value = Math.round(value * 100);
156154
197195
198196 setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
199197
198 // jshint camelcase: false
200199 el._leaflet_pos = point;
201200
202201 if (!disable3D && L.Browser.any3d) {
215214 getPosition: function (el) {
216215 // this method is only used for elements previously positioned using setPosition,
217216 // so it's safe to cache the position for performance
217
218 // jshint camelcase: false
218219 return el._leaflet_pos;
219220 }
220221 };
234235 L.DomUtil.TRANSITION_END =
235236 L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
236237 L.DomUtil.TRANSITION + 'End' : 'transitionend';
238
239 (function () {
240 var userSelectProperty = L.DomUtil.testProp(
241 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
242
243 var userDragProperty = L.DomUtil.testProp(
244 ['userDrag', 'WebkitUserDrag', 'OUserDrag', 'MozUserDrag', 'msUserDrag']);
245
246 L.extend(L.DomUtil, {
247 disableTextSelection: function () {
248 if (userSelectProperty) {
249 var style = document.documentElement.style;
250 this._userSelect = style[userSelectProperty];
251 style[userSelectProperty] = 'none';
252 } else {
253 L.DomEvent.on(window, 'selectstart', L.DomEvent.stop);
254 }
255 },
256
257 enableTextSelection: function () {
258 if (userSelectProperty) {
259 document.documentElement.style[userSelectProperty] = this._userSelect;
260 delete this._userSelect;
261 } else {
262 L.DomEvent.off(window, 'selectstart', L.DomEvent.stop);
263 }
264 },
265
266 disableImageDrag: function () {
267 if (userDragProperty) {
268 var style = document.documentElement.style;
269 this._userDrag = style[userDragProperty];
270 style[userDragProperty] = 'none';
271 } else {
272 L.DomEvent.on(window, 'dragstart', L.DomEvent.stop);
273 }
274 },
275
276 enableImageDrag: function () {
277 if (userDragProperty) {
278 document.documentElement.style[userDragProperty] = this._userDrag;
279 delete this._userDrag;
280 } else {
281 L.DomEvent.off(window, 'dragstart', L.DomEvent.stop);
282 }
283 }
284 });
285 })();
1515 mousedown: 'mousemove',
1616 touchstart: 'touchmove',
1717 MSPointerDown: 'touchmove'
18 },
19 TAP_TOLERANCE: 15
18 }
2019 },
2120
22 initialize: function (element, dragStartTarget, longPress) {
21 initialize: function (element, dragStartTarget) {
2322 this._element = element;
2423 this._dragStartTarget = dragStartTarget || element;
25 this._longPress = longPress && !L.Browser.msTouch;
2624 },
2725
2826 enable: function () {
3129 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
3230 L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
3331 }
32
3433 this._enabled = true;
3534 },
3635
4039 for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
4140 L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
4241 }
42
4343 this._enabled = false;
4444 this._moved = false;
4545 },
4646
4747 _onDown: function (e) {
48 if ((!L.Browser.touch && e.shiftKey) ||
49 ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
48 if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5049
51 L.DomEvent.preventDefault(e);
52 L.DomEvent.stopPropagation(e);
50 L.DomEvent
51 .stopPropagation(e);
5352
5453 if (L.Draggable._disabled) { return; }
5554
56 this._simulateClick = true;
55 L.DomUtil.disableImageDrag();
5756
58 if (e.touches && e.touches.length > 1) {
59 this._simulateClick = false;
60 clearTimeout(this._longPressTimeout);
61 return;
62 }
63
64 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
57 var first = e.touches ? e.touches[0] : e,
6558 el = first.target;
6659
60 // if touching a link, highlight it
6761 if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
6862 L.DomUtil.addClass(el, 'leaflet-active');
6963 }
7064
7165 this._moved = false;
66
7267 if (this._moving) { return; }
7368
7469 this._startPoint = new L.Point(first.clientX, first.clientY);
7570 this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
7671
77 //Touch contextmenu event emulation
78 if (e.touches && e.touches.length === 1 && L.Browser.touch && this._longPress) {
79 this._longPressTimeout = setTimeout(L.bind(function () {
80 var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
81
82 if (dist < L.Draggable.TAP_TOLERANCE) {
83 this._simulateClick = false;
84 this._onUp();
85 this._simulateEvent('contextmenu', first);
86 }
87 }, this), 1000);
88 }
89
90 L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this);
91 L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this);
72 L.DomEvent
73 .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
74 .on(document, L.Draggable.END[e.type], this._onUp, this);
9275 },
9376
9477 _onMove: function (e) {
9679
9780 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
9881 newPoint = new L.Point(first.clientX, first.clientY),
99 diffVec = newPoint.subtract(this._startPoint);
82 offset = newPoint.subtract(this._startPoint);
10083
101 if (!diffVec.x && !diffVec.y) { return; }
84 if (!offset.x && !offset.y) { return; }
10285
10386 L.DomEvent.preventDefault(e);
10487
10588 if (!this._moved) {
10689 this.fire('dragstart');
90
10791 this._moved = true;
108
109 this._startPos = L.DomUtil.getPosition(this._element).subtract(diffVec);
92 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
11093
11194 if (!L.Browser.touch) {
11295 L.DomUtil.disableTextSelection();
113 this._setMovingCursor();
96 L.DomUtil.addClass(document.body, 'leaflet-dragging');
11497 }
11598 }
11699
117 this._newPos = this._startPos.add(diffVec);
100 this._newPos = this._startPos.add(offset);
118101 this._moving = true;
119102
120103 L.Util.cancelAnimFrame(this._animRequest);
127110 this.fire('drag');
128111 },
129112
130 _onUp: function (e) {
131 var simulateClickTouch;
132 clearTimeout(this._longPressTimeout);
133 if (this._simulateClick && e.changedTouches) {
134 var first = e.changedTouches[0],
135 el = first.target,
136 dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
137
138 if (el.tagName.toLowerCase() === 'a') {
139 L.DomUtil.removeClass(el, 'leaflet-active');
140 }
141
142 if (dist < L.Draggable.TAP_TOLERANCE) {
143 simulateClickTouch = first;
144 }
145 }
146
113 _onUp: function () {
147114 if (!L.Browser.touch) {
148115 L.DomUtil.enableTextSelection();
149 this._restoreCursor();
116 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
150117 }
151118
152119 for (var i in L.Draggable.MOVE) {
153 if (L.Draggable.MOVE.hasOwnProperty(i)) {
154 L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove);
155 L.DomEvent.off(document, L.Draggable.END[i], this._onUp);
156 }
120 L.DomEvent
121 .off(document, L.Draggable.MOVE[i], this._onMove)
122 .off(document, L.Draggable.END[i], this._onUp);
157123 }
124
125 L.DomUtil.enableImageDrag();
158126
159127 if (this._moved) {
160128 // ensure drag is not fired after dragend
162130
163131 this.fire('dragend');
164132 }
133
165134 this._moving = false;
166
167 if (simulateClickTouch) {
168 this._moved = false;
169 this._simulateEvent('click', simulateClickTouch);
170 }
171 },
172
173 _setMovingCursor: function () {
174 L.DomUtil.addClass(document.body, 'leaflet-dragging');
175 },
176
177 _restoreCursor: function () {
178 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
179 },
180
181 _simulateEvent: function (type, e) {
182 var simulatedEvent = document.createEvent('MouseEvents');
183
184 simulatedEvent.initMouseEvent(
185 type, true, true, window, 1,
186 e.screenX, e.screenY,
187 e.clientX, e.clientY,
188 false, false, false, false, 0, null);
189
190 e.target.dispatchEvent(simulatedEvent);
191135 }
192136 });
99
1010 this._el = el;
1111 this._inProgress = true;
12 this._newPos = newPos;
1213
1314 this.fire('start');
1415
2223 L.Util.falseFn(el.offsetWidth);
2324
2425 // there's no native way to track value updates of transitioned properties, so we imitate this
25 this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
26 this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
2627 },
2728
2829 stop: function () {
3637 L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
3738 },
3839
40 _onStep: function () {
41 // jshint camelcase: false
42 // make L.DomUtil.getPosition return intermediate position value during animation
43 this._el._leaflet_pos = this._getPos();
44
45 this.fire('step');
46 },
47
3948 // you can't easily get intermediate values of properties animated with CSS3 Transitions,
4049 // we need to parse computed style (in case of transform it returns matrix string)
4150
42 _transformRe: /(-?[\d\.]+), (-?[\d\.]+)\)/,
51 _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
4352
4453 _getPos: function () {
4554 var left, top, matches,
4857
4958 if (L.Browser.any3d) {
5059 matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
51 left = parseFloat(matches[1]);
52 top = parseFloat(matches[2]);
60 left = matches ? parseFloat(matches[1]) : 0;
61 top = matches ? parseFloat(matches[2]) : 0;
5362 } else {
5463 left = parseFloat(style.left);
5564 top = parseFloat(style.top);
6675
6776 this._el.style[L.DomUtil.TRANSITION] = '';
6877
78 // jshint camelcase: false
79 // make sure L.DomUtil.getPosition returns the final position value after animation
80 this._el._leaflet_pos = this._newPos;
81
6982 clearInterval(this._stepTimer);
7083
7184 this.fire('step').fire('end');
7676 if (L.Util.isArray(a)) {
7777 return new L.LatLng(a[0], a[1]);
7878 }
79 if (isNaN(a)) {
79 if (a === undefined || a === null) {
8080 return a;
81 }
82 if (typeof a === 'object' && 'lat' in a) {
83 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
8184 }
8285 return new L.LatLng(a, b);
8386 };
1414 L.LatLngBounds.prototype = {
1515 // extend the bounds to contain the given point or bounds
1616 extend: function (obj) { // (LatLng) or (LatLngBounds)
17 if (!obj) { return this; }
18
1719 if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
1820 obj = L.latLng(obj);
1921 } else {
6567 },
6668
6769 getNorthWest: function () {
68 return new L.LatLng(this._northEast.lat, this._southWest.lng);
70 return new L.LatLng(this.getNorth(), this.getWest());
6971 },
7072
7173 getSouthEast: function () {
72 return new L.LatLng(this._southWest.lat, this._northEast.lng);
74 return new L.LatLng(this.getSouth(), this.getEast());
75 },
76
77 getWest: function () {
78 return this._southWest.lng;
79 },
80
81 getSouth: function () {
82 return this._southWest.lat;
83 },
84
85 getEast: function () {
86 return this._northEast.lng;
87 },
88
89 getNorth: function () {
90 return this._northEast.lat;
7391 },
7492
7593 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
109127 },
110128
111129 toBBoxString: function () {
112 var sw = this._southWest,
113 ne = this._northEast;
114
115 return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
130 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
116131 },
117132
118133 equals: function (bounds) { // (LatLngBounds)
55 L.Projection.Mercator = {
66 MAX_LATITUDE: 85.0840591556,
77
8 R_MINOR: 6356752.3142,
8 R_MINOR: 6356752.314245179,
99 R_MAJOR: 6378137,
1010
1111 project: function (latlng) { // (LatLng) -> Point
2323 con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
2424
2525 var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
26 y = -r2 * Math.log(ts);
26 y = -r * Math.log(ts);
2727
2828 return new L.Point(x, y);
2929 },
116116 return false;
117117 // other cases
118118 } else {
119 codeOut = codeA || codeB,
120 p = this._getEdgeIntersection(a, b, codeOut, bounds),
119 codeOut = codeA || codeB;
120 p = this._getEdgeIntersection(a, b, codeOut, bounds);
121121 newCode = this._getBitCode(p, bounds);
122122
123123 if (codeOut === codeA) {
8484 },
8585
8686 equals: function (point) {
87 point = L.point(point);
88
8789 return point.x === this.x &&
8890 point.y === this.y;
91 },
92
93 contains: function (point) {
94 point = L.point(point);
95
96 return Math.abs(point.x) <= Math.abs(this.x) &&
97 Math.abs(point.y) <= Math.abs(this.y);
8998 },
9099
91100 toString: function () {
102111 if (L.Util.isArray(x)) {
103112 return new L.Point(x[0], x[1]);
104113 }
105 if (isNaN(x)) {
114 if (x === undefined || x === null) {
106115 return x;
107116 }
108117 return new L.Point(x, y, round);
0 <svg xmlns="http://www.w3.org/2000/svg" height="26" width="26">
1 <path d="M.032 17.056l13-8 13 8-13 8-13-8" fill="#b9b9b9"/>
2 <path d="M.032 17.056l-.032.93 13 8 13-8 .032-.93-13 8z" fill="#737373"/>
3 <path d="M0 13.076l13-8 13 8-13 8-13-8" fill="#cdcdcd"/>
4 <path d="M0 13.076v.91l13 8 13-8v-.91l-13 8z" fill="#737373"/>
5 <path d="M0 8.986l13-8 13 8-13 8-13-8" fill-opacity=".585" stroke="#797979" stroke-width=".1" fill="#e9e9e9"/>
6 <path d="M0 8.986v1l13 8 13-8v-1l-13 8z" fill="#737373"/>
7 </svg>
0 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
2
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://creativecommons.org/ns#"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:xlink="http://www.w3.org/1999/xlink"
10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12 width="744.09448819"
13 height="1052.3622047"
14 id="svg2"
15 version="1.1"
16 inkscape:version="0.48.2 r9819"
17 sodipodi:docname="marker.svg">
18 <defs
19 id="defs4">
20 <linearGradient
21 id="linearGradient3889">
22 <stop
23 style="stop-color:#2e6c97;stop-opacity:1;"
24 offset="0"
25 id="stop3891" />
26 <stop
27 style="stop-color:#3883b7;stop-opacity:1;"
28 offset="1"
29 id="stop3893" />
30 </linearGradient>
31 <linearGradient
32 id="linearGradient3788">
33 <stop
34 style="stop-color:#126fc6;stop-opacity:1;"
35 offset="0"
36 id="stop3790" />
37 <stop
38 style="stop-color:#4c9cd1;stop-opacity:1;"
39 offset="1"
40 id="stop3792" />
41 </linearGradient>
42 <linearGradient
43 inkscape:collect="always"
44 xlink:href="#linearGradient3788"
45 id="linearGradient3802"
46 x1="351.1384"
47 y1="551.58929"
48 x2="351.1384"
49 y2="512.92847"
50 gradientUnits="userSpaceOnUse"
51 gradientTransform="translate(-2.714785,0)" />
52 <linearGradient
53 inkscape:collect="always"
54 xlink:href="#linearGradient3788"
55 id="linearGradient3850"
56 x1="318.57059"
57 y1="550.05206"
58 x2="318.57059"
59 y2="512.42175"
60 gradientUnits="userSpaceOnUse"
61 gradientTransform="translate(94.732143,2.0535714)" />
62 <linearGradient
63 inkscape:collect="always"
64 xlink:href="#linearGradient3788"
65 id="linearGradient3857"
66 gradientUnits="userSpaceOnUse"
67 gradientTransform="matrix(-1,0,0,1,731.26786,2.0535714)"
68 x1="318.57059"
69 y1="550.05206"
70 x2="318.57059"
71 y2="512.42175" />
72 <linearGradient
73 inkscape:collect="always"
74 xlink:href="#linearGradient3788"
75 id="linearGradient3863"
76 gradientUnits="userSpaceOnUse"
77 gradientTransform="translate(94.232143,2.0535714)"
78 x1="318.57059"
79 y1="550.05206"
80 x2="318.57059"
81 y2="512.42175" />
82 <linearGradient
83 inkscape:collect="always"
84 xlink:href="#linearGradient3788"
85 id="linearGradient3809"
86 gradientUnits="userSpaceOnUse"
87 gradientTransform="translate(-28.580028,-0.437314)"
88 x1="445.30099"
89 y1="541.28564"
90 x2="445.30099"
91 y2="503.72021" />
92 <linearGradient
93 inkscape:collect="always"
94 xlink:href="#linearGradient3889"
95 id="linearGradient3811"
96 gradientUnits="userSpaceOnUse"
97 gradientTransform="translate(63,-0.4375)"
98 x1="351.74811"
99 y1="522.77393"
100 x2="351.74811"
101 y2="503.72079" />
102 <linearGradient
103 inkscape:collect="always"
104 xlink:href="#linearGradient3788"
105 id="linearGradient3807"
106 gradientUnits="userSpaceOnUse"
107 gradientTransform="translate(-28.580028,-0.437314)"
108 x1="445.30099"
109 y1="541.28564"
110 x2="445.30099"
111 y2="503.72021" />
112 <linearGradient
113 inkscape:collect="always"
114 xlink:href="#linearGradient3889"
115 id="linearGradient3810"
116 gradientUnits="userSpaceOnUse"
117 gradientTransform="translate(63,-0.4375)"
118 x1="351.74811"
119 y1="522.77393"
120 x2="351.74811"
121 y2="503.72079" />
122 <linearGradient
123 inkscape:collect="always"
124 xlink:href="#linearGradient3788"
125 id="linearGradient3832"
126 gradientUnits="userSpaceOnUse"
127 gradientTransform="translate(-28.580028,-0.437314)"
128 x1="445.30099"
129 y1="541.28564"
130 x2="445.30099"
131 y2="503.72021" />
132 <linearGradient
133 inkscape:collect="always"
134 xlink:href="#linearGradient3889"
135 id="linearGradient3834"
136 gradientUnits="userSpaceOnUse"
137 gradientTransform="translate(63,-0.4375)"
138 x1="351.74811"
139 y1="522.77393"
140 x2="351.74811"
141 y2="503.72079" />
142 <linearGradient
143 inkscape:collect="always"
144 xlink:href="#linearGradient3788"
145 id="linearGradient3844"
146 gradientUnits="userSpaceOnUse"
147 gradientTransform="translate(-28.846019,-0.2873201)"
148 x1="445.30099"
149 y1="541.28564"
150 x2="445.30099"
151 y2="503.72021" />
152 <linearGradient
153 inkscape:collect="always"
154 xlink:href="#linearGradient3889"
155 id="linearGradient3846"
156 gradientUnits="userSpaceOnUse"
157 gradientTransform="translate(62.734009,-0.2875061)"
158 x1="351.74811"
159 y1="522.77393"
160 x2="351.74811"
161 y2="503.72079" />
162 <linearGradient
163 inkscape:collect="always"
164 xlink:href="#linearGradient3788"
165 id="linearGradient3797"
166 gradientUnits="userSpaceOnUse"
167 gradientTransform="translate(-28.846019,-0.2873201)"
168 x1="445.30099"
169 y1="541.28564"
170 x2="445.30099"
171 y2="503.72021" />
172 <linearGradient
173 inkscape:collect="always"
174 xlink:href="#linearGradient3889"
175 id="linearGradient3799"
176 gradientUnits="userSpaceOnUse"
177 gradientTransform="translate(62.734009,-0.2875061)"
178 x1="351.74811"
179 y1="522.77393"
180 x2="351.74811"
181 y2="503.72079" />
182 </defs>
183 <sodipodi:namedview
184 id="base"
185 pagecolor="#ffffff"
186 bordercolor="#666666"
187 borderopacity="1.0"
188 inkscape:pageopacity="0.0"
189 inkscape:pageshadow="2"
190 inkscape:zoom="16.000001"
191 inkscape:cx="418.3474"
192 inkscape:cy="537.41791"
193 inkscape:document-units="px"
194 inkscape:current-layer="layer1"
195 showgrid="true"
196 inkscape:window-width="1931"
197 inkscape:window-height="1374"
198 inkscape:window-x="705"
199 inkscape:window-y="45"
200 inkscape:window-maximized="0"
201 showguides="true"
202 inkscape:guide-bbox="true">
203 <sodipodi:guide
204 orientation="0,1"
205 position="367.51875,543.94189"
206 id="guide3897" />
207 <inkscape:grid
208 type="xygrid"
209 id="grid3842" />
210 </sodipodi:namedview>
211 <metadata
212 id="metadata7">
213 <rdf:RDF>
214 <cc:Work
215 rdf:about="">
216 <dc:format>image/svg+xml</dc:format>
217 <dc:type
218 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
219 <dc:title></dc:title>
220 </cc:Work>
221 </rdf:RDF>
222 </metadata>
223 <g
224 inkscape:label="Layer 1"
225 inkscape:groupmode="layer"
226 id="layer1">
227 <image
228 y="502.23718"
229 x="369.375"
230 id="image2993"
231 xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAABHNCSVQICAgIfAhkiAAABgRJREFU WIWdl2uIXGcZx3/nOjNnZueWZlPUtgiKF2gRugWD9UswJfWD+MG7WC1SQcGP6ZdAkvpp28LK0ohQ qASE0tKgELtaGiwEi0EWKXFtbKRmQ2wys5nZy8ycM+f+Pn6YvZ2dmd2J7/DAYc77vL/3/1zel6PN PPcn7mF8CqhsPl8H3MXTJw500mae++N+7+vAj4ATwHHbstANDYAoSlBKNYGLwKuLp5+8PB5ydiTE AE4Cp+q1cqlYLFB08kOTkiTF9Xy6XY++H7wFPLt45smlEZCFUbt/rVYpH6/VylimSZgqvChFJDvR NnWmbAOAjU6PldaqDzy1eOarF3bP00WEXVYXkb/Wq5Xjh++rk4hO24vpBSlKgUjWwljR9mJW+zGV 8hSf+NiRQqnovDFzduGnGcieEL1Rr1U/Uz9UY91P6IUpSjjQUgUtL8bO5anVqwAvzZxdOLYD2dna yUp56litXmO9nxClMnJBU9eQMbDVfoJl2Rw5Mm0g8trMmTfrg3AhCFIX5FS5UqUfKqJEUIptyxs6 tYJFrWBha0J187mSNzPzlIJWL8ZxilSq1cOCnNxUAgjfK09VSoZh4oZpJu51x8I2NNZWV7m5vIze X+Pm8g1WVlYIg4C6YwHZXHlhSqVSBeEnj57+g2Fulsy3nWKJMFGoXRVUyZuEQUiz8RFPf/nTPPX4 FynlB4u+vXSbFxaWCPNlapUqbS/e9vMiRcGyyOdy9SAIjuqClAQ5att5Ov00I93QNDqdDU597RF+ 9pXPbgMAnnj44/zmmcfxe+ukiSJn6EOhyxUcBDmhI/KgZVqGEkh3JdEydMIoYrqg+PqjD+3tJUSE B+pFvvHYQ3S7HfKWkSmCfqwwTRtEPq+LSF3T9aFKyZk6QeDz8AO1kQAApRRfeLCG73tDFZcoAU1H RComIpEoNUia2llIpYBAnKixABEhiFM0TRvy10RDDTrY10Fup0k8tBMvSrHzRRb/c5c4HQZtQf7y wR1y+RLJnr4yNI00SQC5rYtIM0liN1UKTdO2JwWJoOkmrspz7u33h1QAXPl3g0sfrJMvVugG2dNB 1zXiOEJEbhn3f+mbAjxmWIXPlQoFvFBlar7olHjvxl2u3rjD/VWHQ6Ucy3c7XPjbDZ5/cwmnPI1h 5ugGO/2lAQXLoLfWQKXJz7VHnn0d4JidL/65Ov1JWm6cCY1j6xRtg8DbwHfXSCIfw7TJORUKpTq6 YQ75HC5ZBN4G3dWPLl19/ltPbDXjO5HvXgl99+ghp0jbS3YaK1T4kXCoWCXnVDOL9WOF188CNAZq /N4aiPwCwITtFn/R763+zrJLqGyeUQgr3exi48Z0ySL0OsSh+87VF77zLmTvk4thf+PDOA4o582J jviRBvTdVURkdgu8+6hPEZnze21MQ/u/ADXHJPJdYr/793+8+N1L25A9t935oNdupWlCwRo+BQ4y TdPw3TYizO4OoU6W4iPyctBt4VgGopjYanmTJOgTe+vXEfl9BrLnjkdE5oNOw1dKYZuTq9F1jcBt ISKzS3PfT7NKNm+tXdYC+W3otpnKGRMBynmDNA6IvfYtkFf3VtzecG3ZXLhxKxUR9K3Dbx+zDZ3I bYHI3D9/+YNoCDIiXIjIdRFZiLw16gUDpWSsOZZOmkREvWZLRF4Z1TvjlIDIXOzdRbF/qAq2TuSu gMi59+d/6I6EjFGCiFxOg+6VNOhxX9EcuQ/H0hGVEnfvuCIyPwowLvG7bT72VkAb3ZxOziDuNQH5 9bWXnu6Mh+yf1Quq3/5QYp9Kwci8ypk6KCHp/jdCZG4c4KBwISKpiMwnbhPb0IfKNnYbiMj5a+d+ 3NwXckC4ADmfeo2WpDFFeyDc0jVEhLR7MwWZHb/8tpL9e0AEV4SXE6+JYw+as+qYJN4KIrz+r189 s3wg5MBOG9g51b0ViRJy5qaL2wA5WMWk4QKkCXJe9ZtU8gbKbyGJtwAy9FU1RskkDECYF68xUOE1 QJjduVQPgGx+OkzyuyaJd1GCFhJ1LgvyrkxImTQn20eNeHcGudj6byLIvY3LRJ1XgLfuxel/1MGY 0YvhJSIAAAAASUVORK5CYII= "
232 height="41"
233 width="25"
234 inkscape:export-filename="C:\Users\dave.STK\Desktop\path3868.png"
235 inkscape:export-xdpi="644.65582"
236 inkscape:export-ydpi="644.65582" />
237 <rect
238 inkscape:export-ydpi="180"
239 inkscape:export-xdpi="180"
240 inkscape:export-filename="C:\Users\dave.STK\Desktop\git\Leaflet\Leaflet\dist\images\marker-icon@2x.png"
241 y="502.36218"
242 x="404"
243 height="41"
244 width="25"
245 id="rect3771"
246 style="opacity:0;fill:#ffffff;fill-opacity:0.49468085;stroke:none" />
247 <rect
248 inkscape:export-ydpi="180"
249 inkscape:export-xdpi="180"
250 inkscape:export-filename="C:\Users\dave.STK\Desktop\git\Leaflet\Leaflet\dist\images\marker-icon@2x.png"
251 y="507.57468"
252 x="410.27899"
253 height="14.5"
254 width="12.625"
255 id="rect3773"
256 style="fill:#ffffff;fill-opacity:1;stroke:none" />
257 <path
258 sodipodi:nodetypes="zscccszcscsc"
259 style="fill:url(#linearGradient3844);fill-opacity:1;stroke:url(#linearGradient3846);stroke-width:1.10000002;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
260 d="m 416.54436,503.61217 c -6.57257,0 -12.04436,5.69119 -12.04436,11.86601 0,2.77793 1.56377,6.30831 2.69401,8.746 l 9.30616,17.87172 9.26184,-17.87172 c 1.13024,-2.43769 2.73799,-5.79129 2.73799,-8.746 0,-6.17482 -5.38307,-11.86601 -11.95564,-11.86601 z m 0,7.15501 c 2.58449,0.0167 4.67909,2.1215 4.67909,4.70988 0,2.5884 -2.0946,4.66256 -4.67909,4.67912 -2.58449,-0.0165 -4.67909,-2.09048 -4.67909,-4.67888 0,-2.58837 2.0946,-4.69335 4.67909,-4.71012 z"
261 id="path3873"
262 inkscape:connector-curvature="0"
263 inkscape:export-filename="C:\Users\dave.STK\Desktop\git\Leaflet\Leaflet\dist\images\marker-icon@2x.png"
264 inkscape:export-xdpi="180"
265 inkscape:export-ydpi="180" />
266 <text
267 xml:space="preserve"
268 style="font-size:4px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
269 x="403.75"
270 y="489.36218"
271 id="text3899"
272 sodipodi:linespacing="125%"><tspan
273 sodipodi:role="line"
274 id="tspan3901"
275 x="403.75"
276 y="489.36218">Use this one.</tspan><tspan
277 sodipodi:role="line"
278 x="403.75"
279 y="494.36218"
280 id="tspan3903">Select it and the area around with box select.</tspan><tspan
281 sodipodi:role="line"
282 x="403.75"
283 y="499.36218"
284 id="tspan3905">should be exactly 25x41 at 90dpi</tspan></text>
285 <text
286 xml:space="preserve"
287 style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
288 x="408.70773"
289 y="563.7514"
290 id="text3901"
291 sodipodi:linespacing="125%"><tspan
292 sodipodi:role="line"
293 id="tspan3904"
294 x="408.70773"
295 y="563.7514"
296 style="font-size:4px">How to do the inset border:</tspan><tspan
297 sodipodi:role="line"
298 x="408.70773"
299 y="568.7514"
300 style="font-size:4px"
301 id="tspan3908">Delete the existing inset border. Select the background, duplicate.</tspan><tspan
302 sodipodi:role="line"
303 x="408.70773"
304 y="573.7514"
305 style="font-size:4px"
306 id="tspan3910">Path, Dynamic offset.</tspan><tspan
307 sodipodi:role="line"
308 x="408.70773"
309 y="578.7514"
310 style="font-size:4px"
311 id="tspan3912">Fill: None, Stroke Paint: RGBA #ffffff1f </tspan><tspan
312 sodipodi:role="line"
313 x="408.70773"
314 y="583.7514"
315 style="font-size:4px"
316 id="tspan3916">Zoom down to the top and grab the diamond, drag it down and fiddle it untill it looks in line</tspan><tspan
317 sodipodi:role="line"
318 x="408.70773"
319 y="588.7514"
320 style="font-size:4px"
321 id="tspan3918">with the main color layers border.</tspan><tspan
322 sodipodi:role="line"
323 x="408.70773"
324 y="593.7514"
325 style="font-size:4px"
326 id="tspan3914" /><tspan
327 sodipodi:role="line"
328 x="408.70773"
329 y="598.7514"
330 id="tspan3906"
331 style="font-size:4px" /></text>
332 <path
333 sodipodi:type="inkscape:offset"
334 inkscape:radius="-1.09294"
335 inkscape:original="M 416.53125 503.625 C 409.95868 503.625 404.5 509.29393 404.5 515.46875 C 404.5 518.24668 406.05726 521.78106 407.1875 524.21875 L 416.5 542.09375 L 425.75 524.21875 C 426.88024 521.78106 428.5 518.42346 428.5 515.46875 C 428.5 509.29393 423.10382 503.625 416.53125 503.625 z M 416.53125 510.78125 C 419.11574 510.79795 421.21875 512.88037 421.21875 515.46875 C 421.21875 518.05715 419.11574 520.13969 416.53125 520.15625 C 413.94676 520.13975 411.875 518.05715 411.875 515.46875 C 411.875 512.88038 413.94676 510.79802 416.53125 510.78125 z "
336 style="fill:none;stroke:#ffffff;stroke-width:1.10000002;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.12156863;stroke-dasharray:none"
337 id="path3792"
338 d="m 416.53125,504.71875 c -5.94412,0 -10.9375,5.2193 -10.9375,10.75 0,2.35872 1.44272,5.83174 2.5625,8.25 0.005,0.011 0.0262,0.0203 0.0312,0.0312 L 416.5,539.71875 424.75,523.75 c 0.005,-0.0113 0.026,-0.0199 0.0312,-0.0312 1.13492,-2.44805 2.625,-5.70649 2.625,-8.25 0,-5.53807 -4.93088,-10.75 -10.875,-10.75 z m 0,4.96875 c 3.1677,0.0205 5.78125,2.60087 5.78125,5.78125 0,3.18039 -2.61348,5.76095 -5.78125,5.78125 -3.16786,-0.0202 -5.75,-2.60966 -5.75,-5.78125 0,-3.17157 2.58227,-5.7607 5.75,-5.78125 z"
339 inkscape:export-filename="C:\Users\dave.STK\Desktop\git\Leaflet\Leaflet\dist\images\marker-icon@2x.png"
340 inkscape:export-xdpi="180"
341 inkscape:export-ydpi="180" />
342 </g>
0 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="744.094" height="1052.362">
1 <defs>
2 <linearGradient id="b">
3 <stop offset="0" stop-color="#2e6c97"/>
4 <stop offset="1" stop-color="#3883b7"/>
5 </linearGradient>
6 <linearGradient id="a">
7 <stop offset="0" stop-color="#126fc6"/>
8 <stop offset="1" stop-color="#4c9cd1"/>
9 </linearGradient>
10 <linearGradient xlink:href="#a" x1="351.138" y1="551.589" x2="351.138" y2="512.928" gradientUnits="userSpaceOnUse" gradientTransform="translate(-2.715)"/>
11 <linearGradient xlink:href="#a" x1="318.571" y1="550.052" x2="318.571" y2="512.422" gradientUnits="userSpaceOnUse" gradientTransform="translate(94.732 2.054)"/>
12 <linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 731.268 2.054)" x1="318.571" y1="550.052" x2="318.571" y2="512.422"/>
13 <linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(94.232 2.054)" x1="318.571" y1="550.052" x2="318.571" y2="512.422"/>
14 <linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.58 -.437)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/>
15 <linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(63 -.438)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/>
16 <linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.58 -.437)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/>
17 <linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(63 -.438)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/>
18 <linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.58 -.437)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/>
19 <linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(63 -.438)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/>
20 <linearGradient xlink:href="#a" id="c" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.846 -.287)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/>
21 <linearGradient xlink:href="#b" id="d" gradientUnits="userSpaceOnUse" gradientTransform="translate(62.734 -.288)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/>
22 <linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.846 -.287)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/>
23 <linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(62.734 -.288)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/>
24 </defs>
25 <image y="502.237" x="369.375" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAABHNCSVQICAgIfAhkiAAABgRJREFU WIWdl2uIXGcZx3/nOjNnZueWZlPUtgiKF2gRugWD9UswJfWD+MG7WC1SQcGP6ZdAkvpp28LK0ohQ qASE0tKgELtaGiwEi0EWKXFtbKRmQ2wys5nZy8ycM+f+Pn6YvZ2dmd2J7/DAYc77vL/3/1zel6PN PPcn7mF8CqhsPl8H3MXTJw500mae++N+7+vAj4ATwHHbstANDYAoSlBKNYGLwKuLp5+8PB5ydiTE AE4Cp+q1cqlYLFB08kOTkiTF9Xy6XY++H7wFPLt45smlEZCFUbt/rVYpH6/VylimSZgqvChFJDvR NnWmbAOAjU6PldaqDzy1eOarF3bP00WEXVYXkb/Wq5Xjh++rk4hO24vpBSlKgUjWwljR9mJW+zGV 8hSf+NiRQqnovDFzduGnGcieEL1Rr1U/Uz9UY91P6IUpSjjQUgUtL8bO5anVqwAvzZxdOLYD2dna yUp56litXmO9nxClMnJBU9eQMbDVfoJl2Rw5Mm0g8trMmTfrg3AhCFIX5FS5UqUfKqJEUIptyxs6 tYJFrWBha0J187mSNzPzlIJWL8ZxilSq1cOCnNxUAgjfK09VSoZh4oZpJu51x8I2NNZWV7m5vIze X+Pm8g1WVlYIg4C6YwHZXHlhSqVSBeEnj57+g2Fulsy3nWKJMFGoXRVUyZuEQUiz8RFPf/nTPPX4 FynlB4u+vXSbFxaWCPNlapUqbS/e9vMiRcGyyOdy9SAIjuqClAQ5att5Ov00I93QNDqdDU597RF+ 9pXPbgMAnnj44/zmmcfxe+ukiSJn6EOhyxUcBDmhI/KgZVqGEkh3JdEydMIoYrqg+PqjD+3tJUSE B+pFvvHYQ3S7HfKWkSmCfqwwTRtEPq+LSF3T9aFKyZk6QeDz8AO1kQAApRRfeLCG73tDFZcoAU1H RComIpEoNUia2llIpYBAnKixABEhiFM0TRvy10RDDTrY10Fup0k8tBMvSrHzRRb/c5c4HQZtQf7y wR1y+RLJnr4yNI00SQC5rYtIM0liN1UKTdO2JwWJoOkmrspz7u33h1QAXPl3g0sfrJMvVugG2dNB 1zXiOEJEbhn3f+mbAjxmWIXPlQoFvFBlar7olHjvxl2u3rjD/VWHQ6Ucy3c7XPjbDZ5/cwmnPI1h 5ugGO/2lAQXLoLfWQKXJz7VHnn0d4JidL/65Ov1JWm6cCY1j6xRtg8DbwHfXSCIfw7TJORUKpTq6 YQ75HC5ZBN4G3dWPLl19/ltPbDXjO5HvXgl99+ghp0jbS3YaK1T4kXCoWCXnVDOL9WOF188CNAZq /N4aiPwCwITtFn/R763+zrJLqGyeUQgr3exi48Z0ySL0OsSh+87VF77zLmTvk4thf+PDOA4o582J jviRBvTdVURkdgu8+6hPEZnze21MQ/u/ADXHJPJdYr/793+8+N1L25A9t935oNdupWlCwRo+BQ4y TdPw3TYizO4OoU6W4iPyctBt4VgGopjYanmTJOgTe+vXEfl9BrLnjkdE5oNOw1dKYZuTq9F1jcBt ISKzS3PfT7NKNm+tXdYC+W3otpnKGRMBynmDNA6IvfYtkFf3VtzecG3ZXLhxKxUR9K3Dbx+zDZ3I bYHI3D9/+YNoCDIiXIjIdRFZiLw16gUDpWSsOZZOmkREvWZLRF4Z1TvjlIDIXOzdRbF/qAq2TuSu gMi59+d/6I6EjFGCiFxOg+6VNOhxX9EcuQ/H0hGVEnfvuCIyPwowLvG7bT72VkAb3ZxOziDuNQH5 9bWXnu6Mh+yf1Quq3/5QYp9Kwci8ypk6KCHp/jdCZG4c4KBwISKpiMwnbhPb0IfKNnYbiMj5a+d+ 3NwXckC4ADmfeo2WpDFFeyDc0jVEhLR7MwWZHb/8tpL9e0AEV4SXE6+JYw+as+qYJN4KIrz+r189 s3wg5MBOG9g51b0ViRJy5qaL2wA5WMWk4QKkCXJe9ZtU8gbKbyGJtwAy9FU1RskkDECYF68xUOE1 QJjduVQPgGx+OkzyuyaJd1GCFhJ1LgvyrkxImTQn20eNeHcGudj6byLIvY3LRJ1XgLfuxel/1MGY 0YvhJSIAAAAASUVORK5CYII=" height="41" width="25"/>
26 <rect y="507.575" x="410.279" height="14.5" width="12.625" fill="#fff"/>
27 <path d="M416.544 503.612c-6.573 0-12.044 5.691-12.044 11.866 0 2.778 1.564 6.308 2.694 8.746l9.306 17.872 9.262-17.872c1.13-2.438 2.738-5.791 2.738-8.746 0-6.175-5.383-11.866-11.956-11.866zm0 7.155c2.584.017 4.679 2.122 4.679 4.71s-2.095 4.663-4.679 4.679c-2.584-.017-4.679-2.09-4.679-4.679 0-2.588 2.095-4.693 4.679-4.71z" fill="url(#c)" stroke="url(#d)" stroke-width="1.1" stroke-linecap="round"/>
28 <text style="line-height:125%" x="403.75" y="489.362" font-size="4" letter-spacing="0" word-spacing="0" font-family="Sans">
29 <tspan x="403.75" y="489.362">
30 Use this one.
31 </tspan>
32 <tspan x="403.75" y="494.362">
33 Select it and the area around with box select.
34 </tspan>
35 <tspan x="403.75" y="499.362">
36 should be exactly 25x41 at 90dpi
37 </tspan>
38 </text>
39 <text style="line-height:125%" x="408.708" y="563.751" font-size="40" letter-spacing="0" word-spacing="0" font-family="Sans">
40 <tspan x="408.708" y="563.751" font-size="4">
41 How to do the inset border:
42 </tspan>
43 <tspan x="408.708" y="568.751" font-size="4">
44 Delete the existing inset border. Select the background, duplicate.
45 </tspan>
46 <tspan x="408.708" y="573.751" font-size="4">
47 Path, Dynamic offset.
48 </tspan>
49 <tspan x="408.708" y="578.751" font-size="4">
50 Fill: None, Stroke Paint: RGBA #ffffff1f
51 </tspan>
52 <tspan x="408.708" y="583.751" font-size="4">
53 Zoom down to the top and grab the diamond, drag it down and fiddle it untill it looks in line
54 </tspan>
55 <tspan x="408.708" y="588.751" font-size="4">
56 with the main color layers border.
57 </tspan>
58 </text>
59 <path d="M416.531 504.719c-5.944 0-10.938 5.219-10.938 10.75 0 2.359 1.443 5.832 2.563 8.25l.031.031 8.313 15.969 8.25-15.969.031-.031c1.135-2.448 2.625-5.706 2.625-8.25 0-5.538-4.931-10.75-10.875-10.75zm0 4.969c3.168.021 5.781 2.601 5.781 5.781 0 3.18-2.613 5.761-5.781 5.781-3.168-.02-5.75-2.61-5.75-5.781 0-3.172 2.582-5.761 5.75-5.781z" stroke="#fff" stroke-width="1.1" stroke-linecap="round" stroke-opacity=".122" fill="none"/>
34360 </svg>
66 includes: L.Mixin.Events,
77
88 statics: {
9 EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu'
9 EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
1010 },
1111
1212 addLayer: function (layer) {
13 if (this._layers[L.stamp(layer)]) {
13 if (this.hasLayer(layer)) {
1414 return this;
1515 }
1616
2626 },
2727
2828 removeLayer: function (layer) {
29 if (layer in this._layers) {
30 layer = this._layers[layer];
31 }
32
2933 layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
3034
3135 L.LayerGroup.prototype.removeLayer.call(this, layer);
32
3336
3437 if (this._popupContent) {
3538 this.invoke('unbindPopup');
6770 },
6871
6972 _propagateEvent: function (e) {
70 e.layer = e.target;
73 if (!e.layer) {
74 e.layer = e.target;
75 }
7176 e.target = this;
7277
7378 this.fire(e.type, e);
3131
3232 if (options.filter && !options.filter(geojson)) { return; }
3333
34 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
35 layer.feature = geojson;
34 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
35 layer.feature = L.GeoJSON.asFeature(geojson);
3636
3737 layer.defaultOptions = layer.options;
3838 this.resetStyle(layer);
7171 });
7272
7373 L.extend(L.GeoJSON, {
74 geometryToLayer: function (geojson, pointToLayer) {
74 geometryToLayer: function (geojson, pointToLayer, coordsToLatLng) {
7575 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
7676 coords = geometry.coordinates,
7777 layers = [],
7878 latlng, latlngs, i, len, layer;
7979
80 coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
81
8082 switch (geometry.type) {
8183 case 'Point':
82 latlng = this.coordsToLatLng(coords);
84 latlng = coordsToLatLng(coords);
8385 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
8486
8587 case 'MultiPoint':
8688 for (i = 0, len = coords.length; i < len; i++) {
87 latlng = this.coordsToLatLng(coords[i]);
89 latlng = coordsToLatLng(coords[i]);
8890 layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
8991 layers.push(layer);
9092 }
9193 return new L.FeatureGroup(layers);
9294
9395 case 'LineString':
94 latlngs = this.coordsToLatLngs(coords);
96 latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
9597 return new L.Polyline(latlngs);
9698
9799 case 'Polygon':
98 latlngs = this.coordsToLatLngs(coords, 1);
100 latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
99101 return new L.Polygon(latlngs);
100102
101103 case 'MultiLineString':
102 latlngs = this.coordsToLatLngs(coords, 1);
104 latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
103105 return new L.MultiPolyline(latlngs);
104106
105107 case 'MultiPolygon':
106 latlngs = this.coordsToLatLngs(coords, 2);
108 latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
107109 return new L.MultiPolygon(latlngs);
108110
109111 case 'GeometryCollection':
110112 for (i = 0, len = geometry.geometries.length; i < len; i++) {
113
111114 layer = this.geometryToLayer({
112115 geometry: geometry.geometries[i],
113116 type: 'Feature',
114117 properties: geojson.properties
115 }, pointToLayer);
118 }, pointToLayer, coordsToLatLng);
119
116120 layers.push(layer);
117121 }
118122 return new L.FeatureGroup(layers);
122126 }
123127 },
124128
125 coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
126 var lat = parseFloat(coords[reverse ? 0 : 1]),
127 lng = parseFloat(coords[reverse ? 1 : 0]);
128
129 return new L.LatLng(lat, lng);
130 },
131
132 coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
133 var latlng,
134 latlngs = [],
135 i, len;
129 coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
130 return new L.LatLng(coords[1], coords[0]);
131 },
132
133 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
134 var latlng, i, len,
135 latlngs = [];
136136
137137 for (i = 0, len = coords.length; i < len; i++) {
138138 latlng = levelsDeep ?
139 this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
140 this.coordsToLatLng(coords[i], reverse);
139 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
140 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
141141
142142 latlngs.push(latlng);
143143 }
144144
145145 return latlngs;
146 },
147
148 latLngToCoords: function (latLng) {
149 return [latLng.lng, latLng.lat];
150 },
151
152 latLngsToCoords: function (latLngs) {
153 var coords = [];
154
155 for (var i = 0, len = latLngs.length; i < len; i++) {
156 coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));
157 }
158
159 return coords;
160 },
161
162 getFeature: function (layer, newGeometry) {
163 return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);
164 },
165
166 asFeature: function (geoJSON) {
167 if (geoJSON.type === 'Feature') {
168 return geoJSON;
169 }
170
171 return {
172 type: 'Feature',
173 properties: {},
174 geometry: geoJSON
175 };
176 }
177 });
178
179 var PointToGeoJSON = {
180 toGeoJSON: function () {
181 return L.GeoJSON.getFeature(this, {
182 type: 'Point',
183 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
184 });
185 }
186 };
187
188 L.Marker.include(PointToGeoJSON);
189 L.Circle.include(PointToGeoJSON);
190 L.CircleMarker.include(PointToGeoJSON);
191
192 L.Polyline.include({
193 toGeoJSON: function () {
194 return L.GeoJSON.getFeature(this, {
195 type: 'LineString',
196 coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())
197 });
198 }
199 });
200
201 L.Polygon.include({
202 toGeoJSON: function () {
203 var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],
204 i, len, hole;
205
206 coords[0].push(coords[0][0]);
207
208 if (this._holes) {
209 for (i = 0, len = this._holes.length; i < len; i++) {
210 hole = L.GeoJSON.latLngsToCoords(this._holes[i]);
211 hole.push(hole[0]);
212 coords.push(hole);
213 }
214 }
215
216 return L.GeoJSON.getFeature(this, {
217 type: 'Polygon',
218 coordinates: coords
219 });
220 }
221 });
222
223 (function () {
224 function includeMulti(Klass, type) {
225 Klass.include({
226 toGeoJSON: function () {
227 var coords = [];
228
229 this.eachLayer(function (layer) {
230 coords.push(layer.toGeoJSON().geometry.coordinates);
231 });
232
233 return L.GeoJSON.getFeature(this, {
234 type: type,
235 coordinates: coords
236 });
237 }
238 });
239 }
240
241 includeMulti(L.MultiPolyline, 'MultiLineString');
242 includeMulti(L.MultiPolygon, 'MultiPolygon');
243 }());
244
245 L.LayerGroup.include({
246 toGeoJSON: function () {
247 var features = [];
248
249 this.eachLayer(function (layer) {
250 if (layer.toGeoJSON) {
251 features.push(L.GeoJSON.asFeature(layer.toGeoJSON()));
252 }
253 });
254
255 return {
256 type: 'FeatureCollection',
257 features: features
258 };
146259 }
147260 });
148261
1616 },
1717
1818 addLayer: function (layer) {
19 var id = L.stamp(layer);
19 var id = this.getLayerId(layer);
2020
2121 this._layers[id] = layer;
2222
2828 },
2929
3030 removeLayer: function (layer) {
31 var id = L.stamp(layer);
31 var id = layer in this._layers ? layer : this.getLayerId(layer);
32
33 if (this._map && this._layers[id]) {
34 this._map.removeLayer(this._layers[id]);
35 }
3236
3337 delete this._layers[id];
3438
35 if (this._map) {
36 this._map.removeLayer(layer);
37 }
39 return this;
40 },
3841
39 return this;
42 hasLayer: function (layer) {
43 if (!layer) { return false; }
44
45 return (layer in this._layers || this.getLayerId(layer) in this._layers);
4046 },
4147
4248 clearLayers: function () {
4955 i, layer;
5056
5157 for (i in this._layers) {
52 if (this._layers.hasOwnProperty(i)) {
53 layer = this._layers[i];
58 layer = this._layers[i];
5459
55 if (layer[methodName]) {
56 layer[methodName].apply(layer, args);
57 }
60 if (layer[methodName]) {
61 layer[methodName].apply(layer, args);
5862 }
5963 }
6064
7882
7983 eachLayer: function (method, context) {
8084 for (var i in this._layers) {
81 if (this._layers.hasOwnProperty(i)) {
82 method.call(context, this._layers[i]);
83 }
85 method.call(context, this._layers[i]);
8486 }
87 return this;
88 },
89
90 getLayer: function (id) {
91 return this._layers[id];
92 },
93
94 getLayers: function () {
95 var layers = [];
96
97 for (var i in this._layers) {
98 layers.push(this._layers[i]);
99 }
100 return layers;
85101 },
86102
87103 setZIndex: function (zIndex) {
88104 return this.invoke('setZIndex', zIndex);
105 },
106
107 getLayerId: function (layer) {
108 return L.stamp(layer);
89109 }
90110 });
91111
1414 maxHeight: null,
1515 autoPan: true,
1616 closeButton: true,
17 offset: new L.Point(0, 6),
18 autoPanPadding: new L.Point(5, 5),
17 offset: [0, 7],
18 autoPanPadding: [5, 5],
19 keepInView: false,
1920 className: '',
2021 zoomAnimation: true
2122 },
2526
2627 this._source = source;
2728 this._animated = L.Browser.any3d && this.options.zoomAnimation;
29 this._isOpen = false;
2830 },
2931
3032 onAdd: function (map) {
4244 }
4345 map._panes.popupPane.appendChild(this._container);
4446
45 map.on('viewreset', this._updatePosition, this);
46
47 if (this._animated) {
48 map.on('zoomanim', this._zoomAnimation, this);
49 }
50
51 if (map.options.closePopupOnClick) {
52 map.on('preclick', this._close, this);
53 }
47 map.on(this._getEvents(), this);
5448
5549 this._update();
5650
5751 if (animFade) {
5852 L.DomUtil.setOpacity(this._container, 1);
5953 }
54
55 this.fire('open');
56
57 map.fire('popupopen', {popup: this});
58
59 if (this._source) {
60 this._source.fire('popupopen', {popup: this});
61 }
6062 },
6163
6264 addTo: function (map) {
7476
7577 L.Util.falseFn(this._container.offsetWidth); // force reflow
7678
77 map.off({
78 viewreset: this._updatePosition,
79 preclick: this._close,
80 zoomanim: this._zoomAnimation
81 }, this);
79 map.off(this._getEvents(), this);
8280
8381 if (map.options.fadeAnimation) {
8482 L.DomUtil.setOpacity(this._container, 0);
8583 }
8684
8785 this._map = null;
86
87 this.fire('close');
88
89 map.fire('popupclose', {popup: this});
90
91 if (this._source) {
92 this._source.fire('popupclose', {popup: this});
93 }
8894 },
8995
9096 setLatLng: function (latlng) {
99105 return this;
100106 },
101107
108 _getEvents: function () {
109 var events = {
110 viewreset: this._updatePosition
111 };
112
113 if (this._animated) {
114 events.zoomanim = this._zoomAnimation;
115 }
116 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
117 events.preclick = this._close;
118 }
119 if (this.options.keepInView) {
120 events.moveend = this._adjustPan;
121 }
122
123 return events;
124 },
125
102126 _close: function () {
103 var map = this._map;
104
105 if (map) {
106 map._popup = null;
107
108 map
109 .removeLayer(this)
110 .fire('popupclose', {popup: this});
127 if (this._map) {
128 this._map.closePopup(this);
111129 }
112130 },
113131
123141 L.DomUtil.create('a', prefix + '-close-button', container);
124142 closeButton.href = '#close';
125143 closeButton.innerHTML = '&#215;';
144 L.DomEvent.disableClickPropagation(closeButton);
126145
127146 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
128147 }
132151 L.DomEvent.disableClickPropagation(wrapper);
133152
134153 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
135 L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
136
154 L.DomEvent.on(this._contentNode, 'wheel', L.DomEvent.stopPropagation);
155 L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
137156 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
138157 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
139158 },
201220
202221 var pos = this._map.latLngToLayerPoint(this._latlng),
203222 animated = this._animated,
204 offset = this.options.offset;
223 offset = L.point(this.options.offset);
205224
206225 if (animated) {
207226 L.DomUtil.setPosition(this._container, pos);
210229 this._containerBottom = -offset.y - (animated ? 0 : pos.y);
211230 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
212231
213 //Bottom position the popup in case the height of the popup changes (images loading etc)
232 // bottom position the popup in case the height of the popup changes (images loading etc)
214233 this._container.style.bottom = this._containerBottom + 'px';
215234 this._container.style.left = this._containerLeft + 'px';
216235 },
235254 }
236255
237256 var containerPos = map.layerPointToContainerPoint(layerPos),
238 padding = this.options.autoPanPadding,
257 padding = L.point(this.options.autoPanPadding),
239258 size = map.getSize(),
240259 dx = 0,
241260 dy = 0;
242261
243 if (containerPos.x < 0) {
262 if (containerPos.x + containerWidth > size.x) { // right
263 dx = containerPos.x + containerWidth - size.x + padding.x;
264 }
265 if (containerPos.x - dx < 0) { // left
244266 dx = containerPos.x - padding.x;
245267 }
246 if (containerPos.x + containerWidth > size.x) {
247 dx = containerPos.x + containerWidth - size.x + padding.x;
248 }
249 if (containerPos.y < 0) {
268 if (containerPos.y + containerHeight > size.y) { // bottom
269 dy = containerPos.y + containerHeight - size.y + padding.y;
270 }
271 if (containerPos.y - dy < 0) { // top
250272 dy = containerPos.y - padding.y;
251273 }
252 if (containerPos.y + containerHeight > size.y) {
253 dy = containerPos.y + containerHeight - size.y + padding.y;
254 }
255274
256275 if (dx || dy) {
257 map.panBy(new L.Point(dx, dy));
276 map
277 .fire('autopanstart')
278 .panBy([dx, dy]);
258279 }
259280 },
260281
267288 L.popup = function (options, source) {
268289 return new L.Popup(options, source);
269290 };
291
292
293 L.Map.include({
294 openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])
295 this.closePopup();
296
297 if (!(popup instanceof L.Popup)) {
298 var content = popup;
299
300 popup = new L.Popup(options)
301 .setLatLng(latlng)
302 .setContent(content);
303 }
304 popup._isOpen = true;
305
306 this._popup = popup;
307 return this.addLayer(popup);
308 },
309
310 closePopup: function (popup) {
311 if (!popup || popup === this._popup) {
312 popup = this._popup;
313 this._popup = null;
314 }
315 if (popup) {
316 this.removeLayer(popup);
317 popup._isOpen = false;
318 }
319 return this;
320 }
321 });
44
55 L.DivIcon = L.Icon.extend({
66 options: {
7 iconSize: new L.Point(12, 12), // also can be set through CSS
7 iconSize: [12, 12], // also can be set through CSS
88 /*
99 iconAnchor: (Point)
1010 popupAnchor: (Point)
1111 html: (String)
1212 bgPos: (Point)
1313 */
14 className: 'leaflet-div-icon'
14 className: 'leaflet-div-icon',
15 html: false
1516 },
1617
17 createIcon: function () {
18 var div = document.createElement('div'),
18 createIcon: function (oldIcon) {
19 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
1920 options = this.options;
2021
21 if (options.html) {
22 if (options.html !== false) {
2223 div.innerHTML = options.html;
24 } else {
25 div.innerHTML = '';
2326 }
2427
2528 if (options.bgPos) {
44 L.Icon.Default = L.Icon.extend({
55
66 options: {
7 iconSize: new L.Point(25, 41),
8 iconAnchor: new L.Point(12, 41),
9 popupAnchor: new L.Point(1, -34),
7 iconSize: [25, 41],
8 iconAnchor: [12, 41],
9 popupAnchor: [1, -34],
1010
11 shadowSize: new L.Point(41, 41)
11 shadowSize: [41, 41]
1212 },
1313
1414 _getIconUrl: function (name) {
1919 }
2020
2121 if (L.Browser.retina && name === 'icon') {
22 name += '@2x';
22 name += '-2x';
2323 }
2424
2525 var path = L.Icon.Default.imagePath;
2626
2727 if (!path) {
28 throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
28 throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
2929 }
3030
3131 return path + '/marker-' + name + '.png';
3434
3535 L.Icon.Default.imagePath = (function () {
3636 var scripts = document.getElementsByTagName('script'),
37 leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
37 leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
3838
39 var i, len, src, matches;
39 var i, len, src, matches, path;
4040
4141 for (i = 0, len = scripts.length; i < len; i++) {
4242 src = scripts[i].src;
4343 matches = src.match(leafletRe);
4444
4545 if (matches) {
46 return src.split(leafletRe)[0] + '/images';
46 path = src.split(leafletRe)[0];
47 return (path ? path + '/' : '') + 'images';
4748 }
4849 }
4950 }());
99 iconSize: (Point) (can be set through CSS)
1010 iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
1111 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
12 shadowUrl: (Point) (no shadow by default)
12 shadowUrl: (String) (no shadow by default)
1313 shadowRetinaUrl: (String) (optional, used for retina devices if detected)
1414 shadowSize: (Point)
1515 shadowAnchor: (Point)
2121 L.setOptions(this, options);
2222 },
2323
24 createIcon: function () {
25 return this._createIcon('icon');
24 createIcon: function (oldIcon) {
25 return this._createIcon('icon', oldIcon);
2626 },
2727
28 createShadow: function () {
29 return this._createIcon('shadow');
28 createShadow: function (oldIcon) {
29 return this._createIcon('shadow', oldIcon);
3030 },
3131
32 _createIcon: function (name) {
32 _createIcon: function (name, oldIcon) {
3333 var src = this._getIconUrl(name);
3434
3535 if (!src) {
3636 if (name === 'icon') {
37 throw new Error("iconUrl not set in Icon options (see the docs).");
37 throw new Error('iconUrl not set in Icon options (see the docs).');
3838 }
3939 return null;
4040 }
4141
42 var img = this._createImg(src);
42 var img;
43 if (!oldIcon || oldIcon.tagName !== 'IMG') {
44 img = this._createImg(src);
45 } else {
46 img = this._createImg(src, oldIcon);
47 }
4348 this._setIconStyles(img, name);
4449
4550 return img;
7378 }
7479 },
7580
76 _createImg: function (src) {
77 var el;
81 _createImg: function (src, el) {
7882
7983 if (!L.Browser.ie6) {
80 el = document.createElement('img');
84 if (!el) {
85 el = document.createElement('img');
86 }
8187 el.src = src;
8288 } else {
83 el = document.createElement('div');
89 if (!el) {
90 el = document.createElement('div');
91 }
8492 el.style.filter =
8593 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
8694 }
99 addHooks: function () {
1010 var icon = this._marker._icon;
1111 if (!this._draggable) {
12 this._draggable = new L.Draggable(icon, icon)
13 .on('dragstart', this._onDragStart, this)
14 .on('drag', this._onDrag, this)
15 .on('dragend', this._onDragEnd, this);
12 this._draggable = new L.Draggable(icon, icon);
1613 }
14
15 this._draggable
16 .on('dragstart', this._onDragStart, this)
17 .on('drag', this._onDrag, this)
18 .on('dragend', this._onDragEnd, this);
1719 this._draggable.enable();
1820 },
1921
2022 removeHooks: function () {
23 this._draggable
24 .off('dragstart', this._onDragStart, this)
25 .off('drag', this._onDrag, this)
26 .off('dragend', this._onDragEnd, this);
27
2128 this._draggable.disable();
2229 },
2330
33
44 L.Marker.include({
55 openPopup: function () {
6 if (this._popup && this._map) {
6 if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
77 this._popup.setLatLng(this._latlng);
88 this._map.openPopup(this._popup);
99 }
1818 return this;
1919 },
2020
21 togglePopup: function () {
22 if (this._popup) {
23 if (this._popup._isOpen) {
24 this.closePopup();
25 } else {
26 this.openPopup();
27 }
28 }
29 return this;
30 },
31
2132 bindPopup: function (content, options) {
22 var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
33 var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
2334
2435 anchor = anchor.add(L.Popup.prototype.options.offset);
2536
3142
3243 if (!this._popup) {
3344 this
34 .on('click', this.openPopup, this)
45 .on('click', this.togglePopup, this)
3546 .on('remove', this.closePopup, this)
3647 .on('move', this._movePopup, this);
3748 }
3849
39 this._popup = new L.Popup(options, this)
40 .setContent(content);
50 if (content instanceof L.Popup) {
51 L.setOptions(content, options);
52 this._popup = content;
53 } else {
54 this._popup = new L.Popup(options, this)
55 .setContent(content);
56 }
4157
58 return this;
59 },
60
61 setPopupContent: function (content) {
62 if (this._popup) {
63 this._popup.setContent(content);
64 }
4265 return this;
4366 },
4467
4669 if (this._popup) {
4770 this._popup = null;
4871 this
49 .off('click', this.openPopup)
72 .off('click', this.togglePopup)
5073 .off('remove', this.closePopup)
5174 .off('move', this._movePopup);
5275 }
1010 title: '',
1111 clickable: true,
1212 draggable: false,
13 keyboard: true,
1314 zIndexOffset: 0,
1415 opacity: 1,
1516 riseOnHover: false,
4041 },
4142
4243 onRemove: function (map) {
44 if (this.dragging) {
45 this.dragging.disable();
46 }
47
4348 this._removeIcon();
49 this._removeShadow();
4450
4551 this.fire('remove');
4652
7278 },
7379
7480 setIcon: function (icon) {
75 if (this._map) {
76 this._removeIcon();
77 }
7881
7982 this.options.icon = icon;
8083
99102 var options = this.options,
100103 map = this._map,
101104 animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
102 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
103 needOpacityUpdate = false;
104
105 if (!this._icon) {
106 this._icon = options.icon.createIcon();
105 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
106
107 var icon = options.icon.createIcon(this._icon),
108 addIcon = false;
109
110 // if we're not reusing the icon, remove the old one and init new one
111 if (icon !== this._icon) {
112 if (this._icon) {
113 this._removeIcon();
114 }
115 addIcon = true;
107116
108117 if (options.title) {
109 this._icon.title = options.title;
118 icon.title = options.title;
110119 }
111
112 this._initInteraction();
113 needOpacityUpdate = (this.options.opacity < 1);
114
115 L.DomUtil.addClass(this._icon, classToAdd);
116
117 if (options.riseOnHover) {
118 L.DomEvent
119 .on(this._icon, 'mouseover', this._bringToFront, this)
120 .on(this._icon, 'mouseout', this._resetZIndex, this);
120 }
121
122 L.DomUtil.addClass(icon, classToAdd);
123
124 if (options.keyboard) {
125 icon.tabIndex = '0';
126 }
127
128 this._icon = icon;
129
130 this._initInteraction();
131
132 if (options.riseOnHover) {
133 L.DomEvent
134 .on(icon, 'mouseover', this._bringToFront, this)
135 .on(icon, 'mouseout', this._resetZIndex, this);
136 }
137
138 var newShadow = options.icon.createShadow(this._shadow),
139 addShadow = false;
140
141 if (newShadow !== this._shadow) {
142 this._removeShadow();
143 addShadow = true;
144
145 if (newShadow) {
146 L.DomUtil.addClass(newShadow, classToAdd);
121147 }
122148 }
123
124 if (!this._shadow) {
125 this._shadow = options.icon.createShadow();
126
127 if (this._shadow) {
128 L.DomUtil.addClass(this._shadow, classToAdd);
129 needOpacityUpdate = (this.options.opacity < 1);
130 }
131 }
132
133 if (needOpacityUpdate) {
149 this._shadow = newShadow;
150
151
152 if (options.opacity < 1) {
134153 this._updateOpacity();
135154 }
136155
156
137157 var panes = this._map._panes;
138158
139 panes.markerPane.appendChild(this._icon);
140
141 if (this._shadow) {
159 if (addIcon) {
160 panes.markerPane.appendChild(this._icon);
161 }
162
163 if (newShadow && addShadow) {
142164 panes.shadowPane.appendChild(this._shadow);
143165 }
144166 },
145167
146168 _removeIcon: function () {
147 var panes = this._map._panes;
148
149169 if (this.options.riseOnHover) {
150170 L.DomEvent
151171 .off(this._icon, 'mouseover', this._bringToFront)
152172 .off(this._icon, 'mouseout', this._resetZIndex);
153173 }
154174
155 panes.markerPane.removeChild(this._icon);
156
175 this._map._panes.markerPane.removeChild(this._icon);
176
177 this._icon = null;
178 },
179
180 _removeShadow: function () {
157181 if (this._shadow) {
158 panes.shadowPane.removeChild(this._shadow);
159 }
160
161 this._icon = this._shadow = null;
182 this._map._panes.shadowPane.removeChild(this._shadow);
183 }
184 this._shadow = null;
162185 },
163186
164187 _setPos: function (pos) {
194217
195218 L.DomUtil.addClass(icon, 'leaflet-clickable');
196219 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
220 L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
197221
198222 for (var i = 0; i < events.length; i++) {
199223 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
220244 if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
221245
222246 this.fire(e.type, {
223 originalEvent: e
247 originalEvent: e,
248 latlng: this._latlng
224249 });
225250 },
226251
252 _onKeyPress: function (e) {
253 if (e.keyCode === 13) {
254 this.fire('click', {
255 originalEvent: e,
256 latlng: this._latlng
257 });
258 }
259 },
260
227261 _fireMouseEvent: function (e) {
228262
229263 this.fire(e.type, {
230 originalEvent: e
264 originalEvent: e,
265 latlng: this._latlng
231266 });
232267
233268 // TODO proper custom event propagation
237272 }
238273 if (e.type !== 'mousedown') {
239274 L.DomEvent.stopPropagation(e);
275 } else {
276 L.DomEvent.preventDefault(e);
240277 }
241278 },
242279
0 /*
1 Zoom animation logic for L.TileLayer.
2 */
3
4 L.TileLayer.include({
5 _animateZoom: function (e) {
6 var firstFrame = false;
7
8 if (!this._animating) {
9 this._animating = true;
10 firstFrame = true;
11 }
12
13 if (firstFrame) {
14 this._prepareBgBuffer();
15 }
16
17 var bg = this._bgBuffer;
18
19 if (firstFrame) {
20 //prevent bg buffer from clearing right after zoom
21 clearTimeout(this._clearBgBufferTimer);
22
23 // hack to make sure transform is updated before running animation
24 L.Util.falseFn(bg.offsetWidth);
25 }
26
27 var transform = L.DomUtil.TRANSFORM,
28 initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
29 scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
30
31 bg.style[transform] = e.backwards ?
32 scaleStr + ' ' + initialTransform :
33 initialTransform + ' ' + scaleStr;
34 },
35
36 _endZoomAnim: function () {
37 var front = this._tileContainer,
38 bg = this._bgBuffer;
39
40 front.style.visibility = '';
41 front.style.zIndex = 2;
42
43 bg.style.zIndex = 1;
44
45 // force reflow
46 L.Util.falseFn(bg.offsetWidth);
47
48 this._animating = false;
49 },
50
51 _clearBgBuffer: function () {
52 var map = this._map;
53
54 if (map && !map._animatingZoom && !map.touchZoom._zooming) {
55 this._bgBuffer.innerHTML = '';
56 this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
57 }
58 },
59
60 _prepareBgBuffer: function () {
61
62 var front = this._tileContainer,
63 bg = this._bgBuffer;
64
65 // if foreground layer doesn't have many tiles but bg layer does,
66 // keep the existing bg layer and just zoom it some more
67
68 var bgLoaded = this._getLoadedTilesPercentage(bg),
69 frontLoaded = this._getLoadedTilesPercentage(front);
70
71 if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
72
73 front.style.visibility = 'hidden';
74 this._stopLoadingImages(front);
75 return;
76 }
77
78 // prepare the buffer to become the front tile pane
79 bg.style.visibility = 'hidden';
80 bg.style[L.DomUtil.TRANSFORM] = '';
81
82 // switch out the current layer to be the new bg layer (and vice-versa)
83 this._tileContainer = bg;
84 bg = this._bgBuffer = front;
85
86 this._stopLoadingImages(bg);
87 },
88
89 _getLoadedTilesPercentage: function (container) {
90 var tiles = container.getElementsByTagName('img'),
91 i, len, count = 0;
92
93 for (i = 0, len = tiles.length; i < len; i++) {
94 if (tiles[i].complete) {
95 count++;
96 }
97 }
98 return count / len;
99 },
100
101 // stops loading all tiles in the background layer
102 _stopLoadingImages: function (container) {
103 var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
104 i, len, tile;
105
106 for (i = 0, len = tiles.length; i < len; i++) {
107 tile = tiles[i];
108
109 if (!tile.complete) {
110 tile.onload = L.Util.falseFn;
111 tile.onerror = L.Util.falseFn;
112 tile.src = L.Util.emptyImageUrl;
113
114 tile.parentNode.removeChild(tile);
115 }
116 }
117 }
118 });
1212 },
1313
1414 redraw: function () {
15 var tiles = this._tiles;
16
17 for (var i in tiles) {
18 if (tiles.hasOwnProperty(i)) {
19 this._redrawTile(tiles[i]);
20 }
15 for (var i in this._tiles) {
16 this._redrawTile(this._tiles[i]);
2117 }
18 return this;
2219 },
2320
2421 _redrawTile: function (tile) {
1717
1818 this._url = url;
1919
20 var wmsParams = L.extend({}, this.defaultWmsParams);
20 var wmsParams = L.extend({}, this.defaultWmsParams),
21 tileSize = options.tileSize || this.options.tileSize;
2122
2223 if (options.detectRetina && L.Browser.retina) {
23 wmsParams.width = wmsParams.height = this.options.tileSize * 2;
24 wmsParams.width = wmsParams.height = tileSize * 2;
2425 } else {
25 wmsParams.width = wmsParams.height = this.options.tileSize;
26 wmsParams.width = wmsParams.height = tileSize;
2627 }
2728
2829 for (var i in options) {
2930 // all keys that are not TileLayer options go to WMS params
30 if (!this.options.hasOwnProperty(i)) {
31 if (!this.options.hasOwnProperty(i) && i !== 'crs') {
3132 wmsParams[i] = options[i];
3233 }
3334 }
3940
4041 onAdd: function (map) {
4142
43 this._crs = this.options.crs || map.options.crs;
44
4245 var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
43 this.wmsParams[projectionKey] = map.options.crs.code;
46 this.wmsParams[projectionKey] = this._crs.code;
4447
4548 L.TileLayer.prototype.onAdd.call(this, map);
4649 },
4750
4851 getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
4952
50 this._adjustTilePoint(tilePoint);
51
5253 var map = this._map,
53 crs = map.options.crs,
5454 tileSize = this.options.tileSize,
5555
5656 nwPoint = tilePoint.multiplyBy(tileSize),
57 sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
57 sePoint = nwPoint.add([tileSize, tileSize]),
5858
59 nw = crs.project(map.unproject(nwPoint, zoom)),
60 se = crs.project(map.unproject(sePoint, zoom)),
59 nw = this._crs.project(map.unproject(nwPoint, zoom)),
60 se = this._crs.project(map.unproject(sePoint, zoom)),
6161
6262 bbox = [nw.x, se.y, se.x, nw.y].join(','),
6363
6464 url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
6565
66 return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox;
66 return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;
6767 },
6868
6969 setParams: function (params, noRedraw) {
2121 zoomReverse: false,
2222 detectRetina: false,
2323 reuseTiles: false,
24 bounds: false,
2425 */
2526 unloadInvisibleTiles: L.Browser.mobile,
2627 updateWhenIdle: L.Browser.mobile
4142 this.options.maxZoom--;
4243 }
4344
45 if (options.bounds) {
46 options.bounds = L.latLngBounds(options.bounds);
47 }
48
4449 this._url = url;
4550
4651 var subdomains = this.options.subdomains;
5257
5358 onAdd: function (map) {
5459 this._map = map;
60 this._animated = map._zoomAnimated;
5561
5662 // create a container div for tiles
5763 this._initContainer();
6167
6268 // set up events
6369 map.on({
64 'viewreset': this._resetCallback,
70 'viewreset': this._reset,
6571 'moveend': this._update
6672 }, this);
73
74 if (this._animated) {
75 map.on({
76 'zoomanim': this._animateZoom,
77 'zoomend': this._endZoomAnim
78 }, this);
79 }
6780
6881 if (!this.options.updateWhenIdle) {
6982 this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
8396 this._container.parentNode.removeChild(this._container);
8497
8598 map.off({
86 'viewreset': this._resetCallback,
99 'viewreset': this._reset,
87100 'moveend': this._update
88101 }, this);
102
103 if (this._animated) {
104 map.off({
105 'zoomanim': this._animateZoom,
106 'zoomend': this._endZoomAnim
107 }, this);
108 }
89109
90110 if (!this.options.updateWhenIdle) {
91111 map.off('move', this._limitedUpdate, this);
121141 return this.options.attribution;
122142 },
123143
144 getContainer: function () {
145 return this._container;
146 },
147
124148 setOpacity: function (opacity) {
125149 this.options.opacity = opacity;
126150
150174
151175 redraw: function () {
152176 if (this._map) {
153 this._map._panes.tilePane.empty = false;
154 this._reset(true);
177 this._reset({hard: true});
155178 this._update();
156179 }
157180 return this;
185208 },
186209
187210 _updateOpacity: function () {
188 L.DomUtil.setOpacity(this._container, this.options.opacity);
189
190 // stupid webkit hack to force redrawing of tiles
191211 var i,
192212 tiles = this._tiles;
193213
194 if (L.Browser.webkit) {
214 if (L.Browser.ielt9) {
195215 for (i in tiles) {
196 if (tiles.hasOwnProperty(i)) {
197 tiles[i].style.webkitTransform += ' translate(0,0)';
198 }
199 }
216 L.DomUtil.setOpacity(tiles[i], this.options.opacity);
217 }
218 } else {
219 L.DomUtil.setOpacity(this._container, this.options.opacity);
200220 }
201221 },
202222
203223 _initContainer: function () {
204224 var tilePane = this._map._panes.tilePane;
205225
206 if (!this._container || tilePane.empty) {
226 if (!this._container) {
207227 this._container = L.DomUtil.create('div', 'leaflet-layer');
208228
209229 this._updateZIndex();
230
231 if (this._animated) {
232 var className = 'leaflet-tile-container leaflet-zoom-animated';
233
234 this._bgBuffer = L.DomUtil.create('div', className, this._container);
235 this._bgBuffer.style.zIndex = 1;
236
237 this._tileContainer = L.DomUtil.create('div', className, this._container);
238 this._tileContainer.style.zIndex = 2;
239
240 } else {
241 this._tileContainer = this._container;
242 }
210243
211244 tilePane.appendChild(this._container);
212245
216249 }
217250 },
218251
219 _resetCallback: function (e) {
220 this._reset(e.hard);
221 },
222
223 _reset: function (clearOldContainer) {
224 var tiles = this._tiles;
225
226 for (var key in tiles) {
227 if (tiles.hasOwnProperty(key)) {
228 this.fire('tileunload', {tile: tiles[key]});
229 }
252 _reset: function (e) {
253 for (var key in this._tiles) {
254 this.fire('tileunload', {tile: this._tiles[key]});
230255 }
231256
232257 this._tiles = {};
236261 this._unusedTiles = [];
237262 }
238263
239 if (clearOldContainer && this._container) {
240 this._container.innerHTML = "";
264 this._tileContainer.innerHTML = '';
265
266 if (this._animated && e && e.hard) {
267 this._clearBgBuffer();
241268 }
242269
243270 this._initContainer();
255282 return;
256283 }
257284
258 var nwTilePoint = new L.Point(
259 Math.floor(bounds.min.x / tileSize),
260 Math.floor(bounds.min.y / tileSize)),
261
262 seTilePoint = new L.Point(
263 Math.floor(bounds.max.x / tileSize),
264 Math.floor(bounds.max.y / tileSize)),
265
266 tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
285 var tileBounds = L.bounds(
286 bounds.min.divideBy(tileSize)._floor(),
287 bounds.max.divideBy(tileSize)._floor());
267288
268289 this._addTilesFromCenterOut(tileBounds);
269290
310331 this._addTile(queue[i], fragment);
311332 }
312333
313 this._container.appendChild(fragment);
334 this._tileContainer.appendChild(fragment);
314335 },
315336
316337 _tileShouldBeLoaded: function (tilePoint) {
318339 return false; // already loaded
319340 }
320341
321 if (!this.options.continuousWorld) {
342 var options = this.options;
343
344 if (!options.continuousWorld) {
322345 var limit = this._getWrapTileNum();
323346
324 if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
325 tilePoint.y < 0 || tilePoint.y >= limit) {
326 return false; // exceeds world bounds
327 }
347 // don't load if exceeds world bounds
348 if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) ||
349 tilePoint.y < 0 || tilePoint.y >= limit) { return false; }
350 }
351
352 if (options.bounds) {
353 var tileSize = options.tileSize,
354 nwPoint = tilePoint.multiplyBy(tileSize),
355 sePoint = nwPoint.add([tileSize, tileSize]),
356 nw = this._map.unproject(nwPoint),
357 se = this._map.unproject(sePoint);
358
359 // TODO temporary hack, will be removed after refactoring projections
360 // https://github.com/Leaflet/Leaflet/issues/1618
361 if (!options.continuousWorld && !options.noWrap) {
362 nw = nw.wrap();
363 se = se.wrap();
364 }
365
366 if (!options.bounds.intersects([nw, se])) { return false; }
328367 }
329368
330369 return true;
334373 var kArr, x, y, key;
335374
336375 for (key in this._tiles) {
337 if (this._tiles.hasOwnProperty(key)) {
338 kArr = key.split(':');
339 x = parseInt(kArr[0], 10);
340 y = parseInt(kArr[1], 10);
341
342 // remove tile if it's out of bounds
343 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
344 this._removeTile(key);
345 }
376 kArr = key.split(':');
377 x = parseInt(kArr[0], 10);
378 y = parseInt(kArr[1], 10);
379
380 // remove tile if it's out of bounds
381 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
382 this._removeTile(key);
346383 }
347384 }
348385 },
350387 _removeTile: function (key) {
351388 var tile = this._tiles[key];
352389
353 this.fire("tileunload", {tile: tile, url: tile.src});
390 this.fire('tileunload', {tile: tile, url: tile.src});
354391
355392 if (this.options.reuseTiles) {
356393 L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
357394 this._unusedTiles.push(tile);
358395
359 } else if (tile.parentNode === this._container) {
360 this._container.removeChild(tile);
396 } else if (tile.parentNode === this._tileContainer) {
397 this._tileContainer.removeChild(tile);
361398 }
362399
363400 // for https://github.com/CloudMade/Leaflet/issues/137
364401 if (!L.Browser.android) {
402 tile.onload = null;
365403 tile.src = L.Util.emptyImageUrl;
366404 }
367405
377415 /*
378416 Chrome 20 layouts much faster with top/left (verify with timeline, frames)
379417 Android 4 browser has display issues with top/left and requires transform instead
380 Android 3 browser not tested
381418 Android 2 browser requires top/left or tiles disappear on load or first drag
382419 (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
383420 (other browsers don't currently care) - see debug/hacks/jitter.html for an example
388425
389426 this._loadTile(tile, tilePoint);
390427
391 if (tile.parentNode !== this._container) {
428 if (tile.parentNode !== this._tileContainer) {
392429 container.appendChild(tile);
393430 }
394431 },
415452 // image-specific code (override to implement e.g. Canvas or SVG tile layer)
416453
417454 getTileUrl: function (tilePoint) {
418 this._adjustTilePoint(tilePoint);
419
420455 return L.Util.template(this._url, L.extend({
421456 s: this._getSubdomain(tilePoint),
422 z: this._getZoomForUrl(),
457 z: tilePoint.z,
423458 x: tilePoint.x,
424459 y: tilePoint.y
425460 }, this.options));
442477 if (this.options.tms) {
443478 tilePoint.y = limit - tilePoint.y - 1;
444479 }
480
481 tilePoint.z = this._getZoomForUrl();
445482 },
446483
447484 _getSubdomain: function (tilePoint) {
448 var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
485 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
449486 return this.options.subdomains[index];
450487 },
451488
470507 _createTile: function () {
471508 var tile = this._tileImg.cloneNode(false);
472509 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
510
511 if (L.Browser.ielt9 && this.options.opacity !== undefined) {
512 L.DomUtil.setOpacity(tile, this.options.opacity);
513 }
473514 return tile;
474515 },
475516
478519 tile.onload = this._tileOnLoad;
479520 tile.onerror = this._tileOnError;
480521
522 this._adjustTilePoint(tilePoint);
481523 tile.src = this.getTileUrl(tilePoint);
482524 },
483525
484 _tileLoaded: function () {
485 this._tilesToLoad--;
486 if (!this._tilesToLoad) {
487 this.fire('load');
488 }
489 },
526 _tileLoaded: function () {
527 this._tilesToLoad--;
528 if (!this._tilesToLoad) {
529 this.fire('load');
530
531 if (this._animated) {
532 // clear scaled tiles after all new tiles are loaded (for performance)
533 clearTimeout(this._clearBgBufferTimer);
534 this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
535 }
536 }
537 },
490538
491539 _tileOnLoad: function () {
492540 var layer = this._layer;
517565 this.src = newUrl;
518566 }
519567
520 layer._tileLoaded();
521 }
568 layer._tileLoaded();
569 }
522570 });
523571
524572 L.tileLayer = function (url, options) {
2525
2626 projectLatlngs: function () {
2727 var lngRadius = this._getLngRadius(),
28 latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius),
29 point2 = this._map.latLngToLayerPoint(latlng2);
28 latlng = this._latlng,
29 pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
3030
31 this._point = this._map.latLngToLayerPoint(this._latlng);
32 this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
31 this._point = this._map.latLngToLayerPoint(latlng);
32 this._radius = Math.max(this._point.x - pointLeft.x, 1);
3333 },
3434
3535 getBounds: function () {
3636 var lngRadius = this._getLngRadius(),
3737 latRadius = (this._mRadius / 40075017) * 360,
38 latlng = this._latlng,
39 sw = new L.LatLng(latlng.lat - latRadius, latlng.lng - lngRadius),
40 ne = new L.LatLng(latlng.lat + latRadius, latlng.lng + lngRadius);
38 latlng = this._latlng;
4139
42 return new L.LatLngBounds(sw, ne);
40 return new L.LatLngBounds(
41 [latlng.lat - latRadius, latlng.lng - lngRadius],
42 [latlng.lat + latRadius, latlng.lng + lngRadius]);
4343 },
4444
4545 getLatLng: function () {
5555 }
5656
5757 if (L.Browser.svg) {
58 return "M" + p.x + "," + (p.y - r) +
59 "A" + r + "," + r + ",0,1,1," +
60 (p.x - 0.1) + "," + (p.y - r) + " z";
58 return 'M' + p.x + ',' + (p.y - r) +
59 'A' + r + ',' + r + ',0,1,1,' +
60 (p.x - 0.1) + ',' + (p.y - r) + ' z';
6161 } else {
6262 p._round();
6363 r = Math.round(r);
64 return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
64 return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
6565 }
6666 },
6767
1515 projectLatlngs: function () {
1616 this._point = this._map.latLngToLayerPoint(this._latlng);
1717 },
18
18
1919 _updateStyle : function () {
2020 L.Circle.prototype._updateStyle.call(this);
2121 this.setRadius(this.options.radius);
55
66 bindPopup: function (content, options) {
77
8 if (!this._popup || options) {
9 this._popup = new L.Popup(options, this);
8 if (content instanceof L.Popup) {
9 this._popup = content;
10 } else {
11 if (!this._popup || options) {
12 this._popup = new L.Popup(options, this);
13 }
14 this._popup.setContent(content);
1015 }
11
12 this._popup.setContent(content);
1316
1417 if (!this._popupHandlersAdded) {
1518 this
5959 }
6060 if (this.options.fill) {
6161 this._path.setAttribute('fill-rule', 'evenodd');
62 }
63 if (this.options.pointerEvents) {
64 this._path.setAttribute('pointer-events', this.options.pointerEvents);
65 }
66 if (!this.options.clickable && !this.options.pointerEvents) {
67 this._path.setAttribute('pointer-events', 'none');
6268 }
6369 this._updateStyle();
6470 },
7777 if (options.dashArray) {
7878 stroke.dashStyle = options.dashArray instanceof Array ?
7979 options.dashArray.join(' ') :
80 options.dashArray.replace(/ *, */g, ' ');
80 options.dashArray.replace(/( *, *)/g, ' ');
8181 } else {
8282 stroke.dashStyle = '';
8383 }
77 },
88
99 initialize: function (latlngs, options) {
10 var i, len, hole;
11
1012 L.Polyline.prototype.initialize.call(this, latlngs, options);
1113
12 if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
14 if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
1315 this._latlngs = this._convertLatLngs(latlngs[0]);
1416 this._holes = latlngs.slice(1);
17
18 for (i = 0, len = this._holes.length; i < len; i++) {
19 hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
20 if (hole[0].equals(hole[hole.length - 1])) {
21 hole.pop();
22 }
23 }
24 }
25
26 // filter out last point if its equal to the first one
27 latlngs = this._latlngs;
28
29 if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
30 latlngs.pop();
1531 }
1632 },
1733
+0
-249
src/layer/vector/Polyline.Edit.js less more
0 /*
1 * L.Handler.PolyEdit is an editing handler for polylines and polygons.
2 */
3
4 L.Handler.PolyEdit = L.Handler.extend({
5 options: {
6 icon: new L.DivIcon({
7 iconSize: new L.Point(8, 8),
8 className: 'leaflet-div-icon leaflet-editing-icon'
9 })
10 },
11
12 initialize: function (poly, options) {
13 this._poly = poly;
14 L.setOptions(this, options);
15 },
16
17 addHooks: function () {
18 if (this._poly._map) {
19 if (!this._markerGroup) {
20 this._initMarkers();
21 }
22 this._poly._map.addLayer(this._markerGroup);
23 }
24 },
25
26 removeHooks: function () {
27 if (this._poly._map) {
28 this._poly._map.removeLayer(this._markerGroup);
29 delete this._markerGroup;
30 delete this._markers;
31 }
32 },
33
34 updateMarkers: function () {
35 this._markerGroup.clearLayers();
36 this._initMarkers();
37 },
38
39 _initMarkers: function () {
40 if (!this._markerGroup) {
41 this._markerGroup = new L.LayerGroup();
42 }
43 this._markers = [];
44
45 var latlngs = this._poly._latlngs,
46 i, j, len, marker;
47
48 // TODO refactor holes implementation in Polygon to support it here
49
50 for (i = 0, len = latlngs.length; i < len; i++) {
51
52 marker = this._createMarker(latlngs[i], i);
53 marker.on('click', this._onMarkerClick, this);
54 this._markers.push(marker);
55 }
56
57 var markerLeft, markerRight;
58
59 for (i = 0, j = len - 1; i < len; j = i++) {
60 if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
61 continue;
62 }
63
64 markerLeft = this._markers[j];
65 markerRight = this._markers[i];
66
67 this._createMiddleMarker(markerLeft, markerRight);
68 this._updatePrevNext(markerLeft, markerRight);
69 }
70 },
71
72 _createMarker: function (latlng, index) {
73 var marker = new L.Marker(latlng, {
74 draggable: true,
75 icon: this.options.icon
76 });
77
78 marker._origLatLng = latlng;
79 marker._index = index;
80
81 marker.on('drag', this._onMarkerDrag, this);
82 marker.on('dragend', this._fireEdit, this);
83
84 this._markerGroup.addLayer(marker);
85
86 return marker;
87 },
88
89 _fireEdit: function () {
90 this._poly.fire('edit');
91 },
92
93 _onMarkerDrag: function (e) {
94 var marker = e.target;
95
96 L.extend(marker._origLatLng, marker._latlng);
97
98 if (marker._middleLeft) {
99 marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
100 }
101 if (marker._middleRight) {
102 marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
103 }
104
105 this._poly.redraw();
106 },
107
108 _onMarkerClick: function (e) {
109 // we want to remove the marker on click, but if latlng count < 3, polyline would be invalid
110 if (this._poly._latlngs.length < 3) { return; }
111
112 var marker = e.target,
113 i = marker._index;
114
115 // remove the marker
116 this._markerGroup.removeLayer(marker);
117 this._markers.splice(i, 1);
118 this._poly.spliceLatLngs(i, 1);
119 this._updateIndexes(i, -1);
120
121 // update prev/next links of adjacent markers
122 this._updatePrevNext(marker._prev, marker._next);
123
124 // remove ghost markers near the removed marker
125 if (marker._middleLeft) {
126 this._markerGroup.removeLayer(marker._middleLeft);
127 }
128 if (marker._middleRight) {
129 this._markerGroup.removeLayer(marker._middleRight);
130 }
131
132 // create a ghost marker in place of the removed one
133 if (marker._prev && marker._next) {
134 this._createMiddleMarker(marker._prev, marker._next);
135
136 } else if (!marker._prev) {
137 marker._next._middleLeft = null;
138
139 } else if (!marker._next) {
140 marker._prev._middleRight = null;
141 }
142
143 this._poly.fire('edit');
144 },
145
146 _updateIndexes: function (index, delta) {
147 this._markerGroup.eachLayer(function (marker) {
148 if (marker._index > index) {
149 marker._index += delta;
150 }
151 });
152 },
153
154 _createMiddleMarker: function (marker1, marker2) {
155 var latlng = this._getMiddleLatLng(marker1, marker2),
156 marker = this._createMarker(latlng),
157 onClick,
158 onDragStart,
159 onDragEnd;
160
161 marker.setOpacity(0.6);
162
163 marker1._middleRight = marker2._middleLeft = marker;
164
165 onDragStart = function () {
166 var i = marker2._index;
167
168 marker._index = i;
169
170 marker
171 .off('click', onClick)
172 .on('click', this._onMarkerClick, this);
173
174 latlng.lat = marker.getLatLng().lat;
175 latlng.lng = marker.getLatLng().lng;
176 this._poly.spliceLatLngs(i, 0, latlng);
177 this._markers.splice(i, 0, marker);
178
179 marker.setOpacity(1);
180
181 this._updateIndexes(i, 1);
182 marker2._index++;
183 this._updatePrevNext(marker1, marker);
184 this._updatePrevNext(marker, marker2);
185 };
186
187 onDragEnd = function () {
188 marker.off('dragstart', onDragStart, this);
189 marker.off('dragend', onDragEnd, this);
190
191 this._createMiddleMarker(marker1, marker);
192 this._createMiddleMarker(marker, marker2);
193 };
194
195 onClick = function () {
196 onDragStart.call(this);
197 onDragEnd.call(this);
198 this._poly.fire('edit');
199 };
200
201 marker
202 .on('click', onClick, this)
203 .on('dragstart', onDragStart, this)
204 .on('dragend', onDragEnd, this);
205
206 this._markerGroup.addLayer(marker);
207 },
208
209 _updatePrevNext: function (marker1, marker2) {
210 if (marker1) {
211 marker1._next = marker2;
212 }
213 if (marker2) {
214 marker2._prev = marker1;
215 }
216 },
217
218 _getMiddleLatLng: function (marker1, marker2) {
219 var map = this._poly._map,
220 p1 = map.latLngToLayerPoint(marker1.getLatLng()),
221 p2 = map.latLngToLayerPoint(marker2.getLatLng());
222
223 return map.layerPointToLatLng(p1._add(p2)._divideBy(2));
224 }
225 });
226
227 L.Polyline.addInitHook(function () {
228
229 if (L.Handler.PolyEdit) {
230 this.editing = new L.Handler.PolyEdit(this);
231
232 if (this.options.editable) {
233 this.editing.enable();
234 }
235 }
236
237 this.on('add', function () {
238 if (this.editing && this.editing.enabled()) {
239 this.editing.addHooks();
240 }
241 });
242
243 this.on('remove', function () {
244 if (this.editing && this.editing.enabled()) {
245 this.editing.removeHooks();
246 }
247 });
248 });
00 /*
1 * L.Polygon is used to display polylines on a map.
1 * L.Polyline is used to display polylines on a map.
22 */
33
44 L.Polyline = L.Path.extend({
4646
4747 spliceLatLngs: function () { // (Number index, Number howMany)
4848 var removed = [].splice.apply(this._latlngs, arguments);
49 this._convertLatLngs(this._latlngs);
49 this._convertLatLngs(this._latlngs, true);
5050 this.redraw();
5151 return removed;
5252 },
7373 },
7474
7575 getBounds: function () {
76 var bounds = new L.LatLngBounds(),
77 latLngs = this.getLatLngs(),
78 i, len;
79
80 for (i = 0, len = latLngs.length; i < len; i++) {
81 bounds.extend(latLngs[i]);
82 }
83
84 return bounds;
76 return new L.LatLngBounds(this.getLatLngs());
8577 },
8678
87 _convertLatLngs: function (latlngs) {
88 var i, len;
79 _convertLatLngs: function (latlngs, overwrite) {
80 var i, len, target = overwrite ? latlngs : [];
81
8982 for (i = 0, len = latlngs.length; i < len; i++) {
90 if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
83 if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
9184 return;
9285 }
93 latlngs[i] = L.latLng(latlngs[i]);
86 target[i] = L.latLng(latlngs[i]);
9487 }
95 return latlngs;
88 return target;
9689 },
9790
9891 _initEvents: function () {
0 /*
1 * CircleMarker canvas specific drawing parts.
2 */
3
4 L.CircleMarker.include(!L.Path.CANVAS ? {} : {
5 _updateStyle: function () {
6 L.Path.prototype._updateStyle.call(this);
7 }
8 });
3737
3838 if (this.options.clickable) {
3939 this._map.off('click', this._onClick, this);
40 this._map.off('mousemove', this._onMouseMove, this);
4041 }
4142
4243 this._requestUpdate();
122123
123124 _initEvents: function () {
124125 if (this.options.clickable) {
125 // TODO hand cursor
126 // TODO mouseover, mouseout, dblclick
126 // TODO dblclick
127 this._map.on('mousemove', this._onMouseMove, this);
127128 this._map.on('click', this._onClick, this);
128129 }
129130 },
130131
131132 _onClick: function (e) {
132133 if (this._containsPoint(e.layerPoint)) {
133 this.fire('click', {
134 latlng: e.latlng,
135 layerPoint: e.layerPoint,
136 containerPoint: e.containerPoint,
137 originalEvent: e
138 });
134 this.fire('click', e);
135 }
136 },
137
138 _onMouseMove: function (e) {
139 if (!this._map || this._map._animatingZoom) { return; }
140
141 // TODO don't do on each move
142 if (this._containsPoint(e.layerPoint)) {
143 this._ctx.canvas.style.cursor = 'pointer';
144 this._mouseInside = true;
145 this.fire('mouseover', e);
146
147 } else if (this._mouseInside) {
148 this._ctx.canvas.style.cursor = '';
149 this._mouseInside = false;
150 this.fire('mouseout', e);
139151 }
140152 }
141153 });
146158 ctx;
147159
148160 if (!root) {
149 root = this._pathRoot = document.createElement("canvas");
161 root = this._pathRoot = document.createElement('canvas');
150162 root.style.position = 'absolute';
151163 ctx = this._canvasCtx = root.getContext('2d');
152164
153 ctx.lineCap = "round";
154 ctx.lineJoin = "round";
165 ctx.lineCap = 'round';
166 ctx.lineJoin = 'round';
155167
156168 this._panes.overlayPane.appendChild(root);
157169
2424
2525 this._initContainer(id);
2626 this._initLayout();
27 this.callInitHooks();
2827 this._initEvents();
2928
3029 if (options.maxBounds) {
3231 }
3332
3433 if (options.center && options.zoom !== undefined) {
35 this.setView(L.latLng(options.center), options.zoom, true);
36 }
37
38 this._initLayers(options.layers);
34 this.setView(L.latLng(options.center), options.zoom, {reset: true});
35 }
36
37 this._handlers = [];
38
39 this._layers = {};
40 this._zoomBoundLayers = {};
41 this._tileLayersNum = 0;
42
43 this.callInitHooks();
44
45 this._addLayers(options.layers);
3946 },
4047
4148
4754 return this;
4855 },
4956
50 setZoom: function (zoom) { // (Number)
51 return this.setView(this.getCenter(), zoom);
52 },
53
54 zoomIn: function (delta) {
55 return this.setZoom(this._zoom + (delta || 1));
56 },
57
58 zoomOut: function (delta) {
59 return this.setZoom(this._zoom - (delta || 1));
60 },
61
62 fitBounds: function (bounds) { // (LatLngBounds)
63 var zoom = this.getBoundsZoom(bounds);
64 return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
65 },
66
67 fitWorld: function () {
68 var sw = new L.LatLng(-60, -170),
69 ne = new L.LatLng(85, 179);
70
71 return this.fitBounds(new L.LatLngBounds(sw, ne));
72 },
73
74 panTo: function (center) { // (LatLng)
75 return this.setView(center, this._zoom);
57 setZoom: function (zoom, options) {
58 return this.setView(this.getCenter(), zoom, {zoom: options});
59 },
60
61 zoomIn: function (delta, options) {
62 return this.setZoom(this._zoom + (delta || 1), options);
63 },
64
65 zoomOut: function (delta, options) {
66 return this.setZoom(this._zoom - (delta || 1), options);
67 },
68
69 setZoomAround: function (latlng, zoom, options) {
70 var scale = this.getZoomScale(zoom),
71 viewHalf = this.getSize().divideBy(2),
72 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
73
74 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
75 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
76
77 return this.setView(newCenter, zoom, {zoom: options});
78 },
79
80 fitBounds: function (bounds, options) {
81
82 options = options || {};
83 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
84
85 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
86 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
87
88 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
89 paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
90
91 swPoint = this.project(bounds.getSouthWest(), zoom),
92 nePoint = this.project(bounds.getNorthEast(), zoom),
93 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
94
95 return this.setView(center, zoom, options);
96 },
97
98 fitWorld: function (options) {
99 return this.fitBounds([[-90, -180], [90, 180]], options);
100 },
101
102 panTo: function (center, options) { // (LatLng)
103 return this.setView(center, this._zoom, {pan: options});
76104 },
77105
78106 panBy: function (offset) { // (Point)
92120
93121 if (!bounds) {
94122 this._boundsMinZoom = null;
123 this.off('moveend', this._panInsideMaxBounds, this);
95124 return this;
96125 }
97126
107136 }
108137 }
109138
139 this.on('moveend', this._panInsideMaxBounds, this);
140
110141 return this;
111142 },
112143
113144 panInsideBounds: function (bounds) {
114145 bounds = L.latLngBounds(bounds);
115146
116 var viewBounds = this.getBounds(),
117 viewSw = this.project(viewBounds.getSouthWest()),
118 viewNe = this.project(viewBounds.getNorthEast()),
147 var viewBounds = this.getPixelBounds(),
148 viewSw = viewBounds.getBottomLeft(),
149 viewNe = viewBounds.getTopRight(),
119150 sw = this.project(bounds.getSouthWest()),
120151 ne = this.project(bounds.getNorthEast()),
121152 dx = 0,
122153 dy = 0;
123154
124155 if (viewNe.y < ne.y) { // north
125 dy = ne.y - viewNe.y;
156 dy = Math.ceil(ne.y - viewNe.y);
126157 }
127158 if (viewNe.x > ne.x) { // east
128 dx = ne.x - viewNe.x;
159 dx = Math.floor(ne.x - viewNe.x);
129160 }
130161 if (viewSw.y > sw.y) { // south
131 dy = sw.y - viewSw.y;
162 dy = Math.floor(sw.y - viewSw.y);
132163 }
133164 if (viewSw.x < sw.x) { // west
134 dx = sw.x - viewSw.x;
135 }
136
137 return this.panBy(new L.Point(dx, dy, true));
165 dx = Math.ceil(sw.x - viewSw.x);
166 }
167
168 if (dx || dy) {
169 return this.panBy([dx, dy]);
170 }
171
172 return this;
138173 },
139174
140175 addLayer: function (layer) {
155190 // TODO looks ugly, refactor!!!
156191 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
157192 this._tileLayersNum++;
158 this._tileLayersToLoad++;
159 layer.on('load', this._onTileLayerLoad, this);
160 }
161
162 this.whenReady(function () {
163 layer.onAdd(this);
164 this.fire('layeradd', {layer: layer});
165 }, this);
193 this._tileLayersToLoad++;
194 layer.on('load', this._onTileLayerLoad, this);
195 }
196
197 if (this._loaded) {
198 this._layerAdd(layer);
199 }
166200
167201 return this;
168202 },
172206
173207 if (!this._layers[id]) { return; }
174208
175 layer.onRemove(this);
209 if (this._loaded) {
210 layer.onRemove(this);
211 this.fire('layerremove', {layer: layer});
212 }
176213
177214 delete this._layers[id];
178215 if (this._zoomBoundLayers[id]) {
183220 // TODO looks ugly, refactor
184221 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
185222 this._tileLayersNum--;
186 this._tileLayersToLoad--;
187 layer.off('load', this._onTileLayerLoad, this);
188 }
189
190 return this.fire('layerremove', {layer: layer});
223 this._tileLayersToLoad--;
224 layer.off('load', this._onTileLayerLoad, this);
225 }
226
227 return this;
191228 },
192229
193230 hasLayer: function (layer) {
194 var id = L.stamp(layer);
195 return this._layers.hasOwnProperty(id);
196 },
197
198 invalidateSize: function (animate) {
231 if (!layer) { return false; }
232
233 return (L.stamp(layer) in this._layers);
234 },
235
236 eachLayer: function (method, context) {
237 for (var i in this._layers) {
238 method.call(context, this._layers[i]);
239 }
240 return this;
241 },
242
243 invalidateSize: function (options) {
244 options = L.extend({
245 animate: false,
246 pan: true
247 }, options === true ? {animate: true} : options);
248
199249 var oldSize = this.getSize();
200
201250 this._sizeChanged = true;
202251
203252 if (this.options.maxBounds) {
206255
207256 if (!this._loaded) { return this; }
208257
209 var offset = oldSize._subtract(this.getSize())._divideBy(2)._round();
210
211 if (animate === true) {
258 var newSize = this.getSize(),
259 offset = oldSize.subtract(newSize).divideBy(2).round();
260
261 if (!offset.x && !offset.y) { return this; }
262
263 if (options.animate && options.pan) {
212264 this.panBy(offset);
265
213266 } else {
214 this._rawPanBy(offset);
267 if (options.pan) {
268 this._rawPanBy(offset);
269 }
215270
216271 this.fire('move');
217272
273 // make sure moveend is not fired too often on resize
218274 clearTimeout(this._sizeTimer);
219275 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
220276 }
221 return this;
277
278 return this.fire('resize', {
279 oldSize: oldSize,
280 newSize: newSize
281 });
222282 },
223283
224284 // TODO handler.addTo
225285 addHandler: function (name, HandlerClass) {
226286 if (!HandlerClass) { return; }
227287
228 this[name] = new HandlerClass(this);
288 var handler = this[name] = new HandlerClass(this);
289
290 this._handlers.push(handler);
229291
230292 if (this.options[name]) {
231 this[name].enable();
232 }
293 handler.enable();
294 }
295
296 return this;
297 },
298
299 remove: function () {
300 if (this._loaded) {
301 this.fire('unload');
302 }
303
304 this._initEvents('off');
305
306 delete this._container._leaflet;
307
308 this._clearPanes();
309 if (this._clearControlPos) {
310 this._clearControlPos();
311 }
312
313 this._clearHandlers();
233314
234315 return this;
235316 },
238319 // public methods for getting map state
239320
240321 getCenter: function () { // (Boolean) -> LatLng
322 this._checkIfLoaded();
323
324 if (!this._moved()) {
325 return this._initialCenter;
326 }
241327 return this.layerPointToLatLng(this._getCenterLayerPoint());
242328 },
243329
268354 return Math.min(z1, z2);
269355 },
270356
271 getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
357 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
272358 bounds = L.latLngBounds(bounds);
273359
274 var size = this.getSize(),
275 zoom = this.options.minZoom || 0,
360 var zoom = this.getMinZoom() - (inside ? 1 : 0),
276361 maxZoom = this.getMaxZoom(),
277 ne = bounds.getNorthEast(),
278 sw = bounds.getSouthWest(),
279 boundsSize,
280 nePoint,
281 swPoint,
282 zoomNotFound = true;
283
284 if (inside) {
285 zoom--;
286 }
362 size = this.getSize(),
363
364 nw = bounds.getNorthWest(),
365 se = bounds.getSouthEast(),
366
367 zoomNotFound = true,
368 boundsSize;
369
370 padding = L.point(padding || [0, 0]);
287371
288372 do {
289373 zoom++;
290 nePoint = this.project(ne, zoom);
291 swPoint = this.project(sw, zoom);
292
293 boundsSize = new L.Point(
294 Math.abs(nePoint.x - swPoint.x),
295 Math.abs(swPoint.y - nePoint.y));
296
297 if (!inside) {
298 zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
299 } else {
300 zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
301 }
374 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
375 zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
376
302377 } while (zoomNotFound && zoom <= maxZoom);
303378
304379 if (zoomNotFound && inside) {
325400 },
326401
327402 getPixelOrigin: function () {
403 this._checkIfLoaded();
328404 return this._initialTopLeftPoint;
329405 },
330406
362438 },
363439
364440 layerPointToLatLng: function (point) { // (Point)
365 var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
441 var projectedPoint = L.point(point).add(this.getPixelOrigin());
366442 return this.unproject(projectedPoint);
367443 },
368444
369445 latLngToLayerPoint: function (latlng) { // (LatLng)
370446 var projectedPoint = this.project(L.latLng(latlng))._round();
371 return projectedPoint._subtract(this._initialTopLeftPoint);
447 return projectedPoint._subtract(this.getPixelOrigin());
372448 },
373449
374450 containerPointToLayerPoint: function (point) { // (Point)
406482 _initContainer: function (id) {
407483 var container = this._container = L.DomUtil.get(id);
408484
409 if (container._leaflet) {
410 throw new Error("Map container is already initialized.");
485 if (!container) {
486 throw new Error('Map container not found.');
487 } else if (container._leaflet) {
488 throw new Error('Map container is already initialized.');
411489 }
412490
413491 container._leaflet = true;
416494 _initLayout: function () {
417495 var container = this._container;
418496
419 L.DomUtil.addClass(container, 'leaflet-container');
420
421 if (L.Browser.touch) {
422 L.DomUtil.addClass(container, 'leaflet-touch');
423 }
424
425 if (this.options.fadeAnimation) {
426 L.DomUtil.addClass(container, 'leaflet-fade-anim');
427 }
497 L.DomUtil.addClass(container, 'leaflet-container' +
498 (L.Browser.touch ? ' leaflet-touch' : '') +
499 (L.Browser.retina ? ' leaflet-retina' : '') +
500 (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
428501
429502 var position = L.DomUtil.getStyle(container, 'position');
430503
464537 return L.DomUtil.create('div', className, container || this._panes.objectsPane);
465538 },
466539
467 _initLayers: function (layers) {
468 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
469
470 this._layers = {};
471 this._zoomBoundLayers = {};
472 this._tileLayersNum = 0;
473
474 var i, len;
475
476 for (i = 0, len = layers.length; i < len; i++) {
540 _clearPanes: function () {
541 this._container.removeChild(this._mapPane);
542 },
543
544 _addLayers: function (layers) {
545 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
546
547 for (var i = 0, len = layers.length; i < len; i++) {
477548 this.addLayer(layers[i]);
478549 }
479550 },
494565 }
495566
496567 this._zoom = zoom;
568 this._initialCenter = center;
497569
498570 this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
499571
508580 var loading = !this._loaded;
509581 this._loaded = true;
510582
583 if (loading) {
584 this.fire('load');
585 this.eachLayer(this._layerAdd, this);
586 }
587
511588 this.fire('viewreset', {hard: !preserveMapOffset});
512589
513590 this.fire('move');
517594 }
518595
519596 this.fire('moveend', {hard: !preserveMapOffset});
520
521 if (loading) {
522 this.fire('load');
523 }
524597 },
525598
526599 _rawPanBy: function (offset) {
527600 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
601 },
602
603 _getZoomSpan: function () {
604 return this.getMaxZoom() - this.getMinZoom();
528605 },
529606
530607 _updateZoomLevels: function () {
531608 var i,
532609 minZoom = Infinity,
533 maxZoom = -Infinity;
610 maxZoom = -Infinity,
611 oldZoomSpan = this._getZoomSpan();
534612
535613 for (i in this._zoomBoundLayers) {
536 if (this._zoomBoundLayers.hasOwnProperty(i)) {
537 var layer = this._zoomBoundLayers[i];
538 if (!isNaN(layer.options.minZoom)) {
539 minZoom = Math.min(minZoom, layer.options.minZoom);
540 }
541 if (!isNaN(layer.options.maxZoom)) {
542 maxZoom = Math.max(maxZoom, layer.options.maxZoom);
543 }
614 var layer = this._zoomBoundLayers[i];
615 if (!isNaN(layer.options.minZoom)) {
616 minZoom = Math.min(minZoom, layer.options.minZoom);
617 }
618 if (!isNaN(layer.options.maxZoom)) {
619 maxZoom = Math.max(maxZoom, layer.options.maxZoom);
544620 }
545621 }
546622
550626 this._layersMaxZoom = maxZoom;
551627 this._layersMinZoom = minZoom;
552628 }
629
630 if (oldZoomSpan !== this._getZoomSpan()) {
631 this.fire('zoomlevelschange');
632 }
633 },
634
635 _panInsideMaxBounds: function () {
636 this.panInsideBounds(this.options.maxBounds);
637 },
638
639 _checkIfLoaded: function () {
640 if (!this._loaded) {
641 throw new Error('Set map center and zoom first.');
642 }
553643 },
554644
555645 // map events
556646
557 _initEvents: function () {
647 _initEvents: function (onOff) {
558648 if (!L.DomEvent) { return; }
559649
560 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
650 onOff = onOff || 'on';
651
652 L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
561653
562654 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
563655 'mouseleave', 'mousemove', 'contextmenu'],
564656 i, len;
565657
566658 for (i = 0, len = events.length; i < len; i++) {
567 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
659 L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
568660 }
569661
570662 if (this.options.trackResize) {
571 L.DomEvent.on(window, 'resize', this._onResize, this);
663 L.DomEvent[onOff](window, 'resize', this._onResize, this);
572664 }
573665 },
574666
579671 },
580672
581673 _onMouseClick: function (e) {
582 if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
674 // jshint camelcase: false
675 if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) || e._leaflet_stop) { return; }
583676
584677 this.fire('preclick');
585678 this._fireMouseEvent(e);
586679 },
587680
588681 _fireMouseEvent: function (e) {
589 if (!this._loaded) { return; }
682 // jshint camelcase: false
683 if (!this._loaded || e._leaflet_stop) { return; }
590684
591685 var type = e.type;
592686
611705 },
612706
613707 _onTileLayerLoad: function () {
614 // TODO super-ugly, refactor!!!
615 // clear scaled tiles after all new tiles are loaded (for performance)
616708 this._tileLayersToLoad--;
617 if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
618 clearTimeout(this._clearTileBgTimer);
619 this._clearTileBgTimer = setTimeout(L.bind(this._clearTileBg, this), 500);
709 if (this._tileLayersNum && !this._tileLayersToLoad) {
710 this.fire('tilelayersload');
711 }
712 },
713
714 _clearHandlers: function () {
715 for (var i = 0, len = this._handlers.length; i < len; i++) {
716 this._handlers[i].disable();
620717 }
621718 },
622719
629726 return this;
630727 },
631728
729 _layerAdd: function (layer) {
730 layer.onAdd(this);
731 this.fire('layeradd', {layer: layer});
732 },
733
632734
633735 // private methods for getting map state
634736
636738 return L.DomUtil.getPosition(this._mapPane);
637739 },
638740
741 _moved: function () {
742 var pos = this._getMapPanePos();
743 return pos && !pos.equals([0, 0]);
744 },
745
639746 _getTopLeftPoint: function () {
640 if (!this._loaded) {
641 throw new Error('Set map center and zoom first.');
642 }
643
644 return this._initialTopLeftPoint.subtract(this._getMapPanePos());
747 return this.getPixelOrigin().subtract(this._getMapPanePos());
645748 },
646749
647750 _getNewTopLeftPoint: function (center, zoom) {
655758 return this.project(latlng, newZoom)._subtract(topLeft);
656759 },
657760
761 // layer point of the current center
658762 _getCenterLayerPoint: function () {
659763 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
660764 },
661765
662 _getCenterOffset: function (center) {
663 return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
766 // offset of the specified place to the current center in pixels
767 _getCenterOffset: function (latlng) {
768 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
664769 },
665770
666771 _limitZoom: function (zoom) {
33
44 L.Map.include({
55
6 setView: function (center, zoom, forceReset) {
6 setView: function (center, zoom, options) {
7
78 zoom = this._limitZoom(zoom);
9 center = L.latLng(center);
10 options = options || {};
811
9 var zoomChanged = (this._zoom !== zoom);
12 if (this._panAnim) {
13 this._panAnim.stop();
14 }
1015
11 if (this._loaded && !forceReset && this._layers) {
16 if (this._loaded && !options.reset && options !== true) {
1217
13 if (this._panAnim) {
14 this._panAnim.stop();
18 if (options.animate !== undefined) {
19 options.zoom = L.extend({animate: options.animate}, options.zoom);
20 options.pan = L.extend({animate: options.animate}, options.pan);
1521 }
1622
17 var done = (zoomChanged ?
18 this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
19 this._panByIfClose(center));
23 // try animating pan or zoom
24 var animated = (this._zoom !== zoom) ?
25 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
26 this._tryAnimatedPan(center, options.pan);
2027
21 // exit if animated pan or zoom started
22 if (done) {
28 if (animated) {
29 // prevent resize handler call, the view will refresh after animation anyway
2330 clearTimeout(this._sizeTimer);
2431 return this;
2532 }
2633 }
2734
28 // reset the map view
35 // animation didn't start, just reset the map view
2936 this._resetView(center, zoom);
3037
3138 return this;
3239 },
3340
34 panBy: function (offset, duration, easeLinearity) {
35 offset = L.point(offset);
41 panBy: function (offset, options) {
42 offset = L.point(offset).round();
43 options = options || {};
3644
37 if (!(offset.x || offset.y)) {
45 if (!offset.x && !offset.y) {
3846 return this;
3947 }
4048
4755 }, this);
4856 }
4957
50 this.fire('movestart');
58 // don't fire movestart if animating inertia
59 if (!options.noMoveStart) {
60 this.fire('movestart');
61 }
5162
52 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
63 // animate pan unless animate: false specified
64 if (options.animate !== false) {
65 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
5366
54 var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round();
55 this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
67 var newPos = this._getMapPanePos().subtract(offset);
68 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
69 } else {
70 this._rawPanBy(offset);
71 this.fire('move').fire('moveend');
72 }
5673
5774 return this;
5875 },
6683 this.fire('moveend');
6784 },
6885
69 _panByIfClose: function (center) {
86 _tryAnimatedPan: function (center, options) {
7087 // difference between the new and current centers in pixels
7188 var offset = this._getCenterOffset(center)._floor();
7289
73 if (this._offsetIsWithinView(offset)) {
74 this.panBy(offset);
75 return true;
76 }
77 return false;
78 },
90 // don't animate too far unless animate: true specified in options
91 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
7992
80 _offsetIsWithinView: function (offset, multiplyFactor) {
81 var m = multiplyFactor || 1,
82 size = this.getSize();
93 this.panBy(offset, options);
8394
84 return (Math.abs(offset.x) <= size.x * m) &&
85 (Math.abs(offset.y) <= size.y * m);
95 return true;
8696 }
8797 });
22 */
33
44 L.Map.mergeOptions({
5 zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
5 zoomAnimation: true,
6 zoomAnimationThreshold: 4
67 });
78
89 if (L.DomUtil.TRANSITION) {
10
911 L.Map.addInitHook(function () {
10 L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
12 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
13 this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
14 L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
15
16 // zoom transitions run with the same duration for all layers, so if one of transitionend events
17 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
18 if (this._zoomAnimated) {
19 L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
20 }
1121 });
1222 }
1323
1424 L.Map.include(!L.DomUtil.TRANSITION ? {} : {
15
16 _zoomToIfClose: function (center, zoom) {
17
18 if (this._animatingZoom) { return true; }
19
20 if (!this.options.zoomAnimation) { return false; }
21
22 var scale = this.getZoomScale(zoom),
23 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
24
25 // if offset does not exceed half of the view
26 if (!this._offsetIsWithinView(offset, 1)) { return false; }
27
28 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
29
30 this
31 .fire('movestart')
32 .fire('zoomstart');
33
34 this.fire('zoomanim', {
35 center: center,
36 zoom: zoom
37 });
38
39 var origin = this._getCenterLayerPoint().add(offset);
40
41 this._prepareTileBg();
42 this._runAnimation(center, zoom, scale, origin);
43
44 return true;
45 },
4625
4726 _catchTransitionEnd: function () {
4827 if (this._animatingZoom) {
5029 }
5130 },
5231
53 _runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
32 _tryAnimatedZoom: function (center, zoom, options) {
33
34 if (this._animatingZoom) { return true; }
35
36 options = options || {};
37
38 // don't animate if disabled, not supported or zoom difference is too large
39 if (!this._zoomAnimated || options.animate === false ||
40 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
41
42 // offset is the pixel coords of the zoom origin relative to the current center
43 var scale = this.getZoomScale(zoom),
44 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
45 origin = this._getCenterLayerPoint()._add(offset);
46
47 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
48 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
49
50 this
51 .fire('movestart')
52 .fire('zoomstart');
53
54 this._animateZoom(center, zoom, origin, scale, null, true);
55
56 return true;
57 },
58
59 _animateZoom: function (center, zoom, origin, scale, delta, backwards) {
60
61 this._animatingZoom = true;
62
63 // put transform transition on all layers with leaflet-zoom-animated class
64 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
65
66 // remember what center/zoom to set after animation
5467 this._animateToCenter = center;
5568 this._animateToZoom = zoom;
56 this._animatingZoom = true;
5769
70 // disable any dragging during animation
5871 if (L.Draggable) {
5972 L.Draggable._disabled = true;
6073 }
6174
62 var transform = L.DomUtil.TRANSFORM,
63 tileBg = this._tileBg;
64
65 clearTimeout(this._clearTileBgTimer);
66
67 L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
68
69 var scaleStr = L.DomUtil.getScaleString(scale, origin),
70 oldTransform = tileBg.style[transform];
71
72 tileBg.style[transform] = backwardsTransform ?
73 oldTransform + ' ' + scaleStr :
74 scaleStr + ' ' + oldTransform;
75 },
76
77 _prepareTileBg: function () {
78 var tilePane = this._tilePane,
79 tileBg = this._tileBg;
80
81 // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
82 if (tileBg && this._getLoadedTilesPercentage(tileBg) > 0.5 &&
83 this._getLoadedTilesPercentage(tilePane) < 0.5) {
84
85 tilePane.style.visibility = 'hidden';
86 tilePane.empty = true;
87 this._stopLoadingImages(tilePane);
88 return;
89 }
90
91 if (!tileBg) {
92 tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
93 tileBg.style.zIndex = 1;
94 }
95
96 // prepare the background pane to become the main tile pane
97 tileBg.style[L.DomUtil.TRANSFORM] = '';
98 tileBg.style.visibility = 'hidden';
99
100 // tells tile layers to reinitialize their containers
101 tileBg.empty = true; //new FG
102 tilePane.empty = false; //new BG
103
104 //Switch out the current layer to be the new bg layer (And vice-versa)
105 this._tilePane = this._panes.tilePane = tileBg;
106 var newTileBg = this._tileBg = tilePane;
107
108 L.DomUtil.addClass(newTileBg, 'leaflet-zoom-animated');
109
110 this._stopLoadingImages(newTileBg);
111 },
112
113 _getLoadedTilesPercentage: function (container) {
114 var tiles = container.getElementsByTagName('img'),
115 i, len, count = 0;
116
117 for (i = 0, len = tiles.length; i < len; i++) {
118 if (tiles[i].complete) {
119 count++;
120 }
121 }
122 return count / len;
123 },
124
125 // stops loading all tiles in the background layer
126 _stopLoadingImages: function (container) {
127 var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
128 i, len, tile;
129
130 for (i = 0, len = tiles.length; i < len; i++) {
131 tile = tiles[i];
132
133 if (!tile.complete) {
134 tile.onload = L.Util.falseFn;
135 tile.onerror = L.Util.falseFn;
136 tile.src = L.Util.emptyImageUrl;
137
138 tile.parentNode.removeChild(tile);
139 }
140 }
75 this.fire('zoomanim', {
76 center: center,
77 zoom: zoom,
78 origin: origin,
79 scale: scale,
80 delta: delta,
81 backwards: backwards
82 });
14183 },
14284
14385 _onZoomTransitionEnd: function () {
144 this._restoreTileFront();
86
87 this._animatingZoom = false;
14588
14689 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
147 L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
148 this._animatingZoom = false;
90
14991 this._resetView(this._animateToCenter, this._animateToZoom, true, true);
15092
15193 if (L.Draggable) {
15294 L.Draggable._disabled = false;
15395 }
154 },
155
156 _restoreTileFront: function () {
157 this._tilePane.innerHTML = '';
158 this._tilePane.style.visibility = '';
159 this._tilePane.style.zIndex = 2;
160 this._tileBg.style.zIndex = 1;
161 },
162
163 _clearTileBg: function () {
164 if (!this._animatingZoom && !this.touchZoom._zooming) {
165 this._tileBg.innerHTML = '';
166 }
16796 }
16897 });
+0
-33
src/map/ext/Map.Control.js less more
0 /*
1 * Adds control-related methods to L.Map.
2 */
3
4 L.Map.include({
5 addControl: function (control) {
6 control.addTo(this);
7 return this;
8 },
9
10 removeControl: function (control) {
11 control.removeFrom(this);
12 return this;
13 },
14
15 _initControlPos: function () {
16 var corners = this._controlCorners = {},
17 l = 'leaflet-',
18 container = this._controlContainer =
19 L.DomUtil.create('div', l + 'control-container', this._container);
20
21 function createCorner(vSide, hSide) {
22 var className = l + vSide + ' ' + l + hSide;
23
24 corners[vSide + hSide] = L.DomUtil.create('div', className, container);
25 }
26
27 createCorner('top', 'left');
28 createCorner('top', 'right');
29 createCorner('bottom', 'left');
30 createCorner('bottom', 'right');
31 }
32 });
1313
1414 locate: function (/*Object*/ options) {
1515
16 options = this._locationOptions = L.extend(this._defaultLocateOptions, options);
16 options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
1717
1818 if (!navigator.geolocation) {
1919 this._handleGeolocationError({
2020 code: 0,
21 message: "Geolocation not supported."
21 message: 'Geolocation not supported.'
2222 });
2323 return this;
2424 }
3939 if (navigator.geolocation) {
4040 navigator.geolocation.clearWatch(this._locationWatchId);
4141 }
42 if (this._locateOptions) {
43 this._locateOptions.setView = false;
44 }
4245 return this;
4346 },
4447
4548 _handleGeolocationError: function (error) {
4649 var c = error.code,
4750 message = error.message ||
48 (c === 1 ? "permission denied" :
49 (c === 2 ? "position unavailable" : "timeout"));
51 (c === 1 ? 'permission denied' :
52 (c === 2 ? 'position unavailable' : 'timeout'));
5053
51 if (this._locationOptions.setView && !this._loaded) {
54 if (this._locateOptions.setView && !this._loaded) {
5255 this.fitWorld();
5356 }
5457
5558 this.fire('locationerror', {
5659 code: c,
57 message: "Geolocation error: " + message + "."
60 message: 'Geolocation error: ' + message + '.'
5861 });
5962 },
6063
6164 _handleGeolocationResponse: function (pos) {
62 var latAccuracy = 180 * pos.coords.accuracy / 4e7,
63 lngAccuracy = latAccuracy * 2,
64
65 lat = pos.coords.latitude,
65 var lat = pos.coords.latitude,
6666 lng = pos.coords.longitude,
6767 latlng = new L.LatLng(lat, lng),
6868
69 sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
70 ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
71 bounds = new L.LatLngBounds(sw, ne),
69 latAccuracy = 180 * pos.coords.accuracy / 40075017,
70 lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),
7271
73 options = this._locationOptions;
72 bounds = L.latLngBounds(
73 [lat - latAccuracy, lng - lngAccuracy],
74 [lat + latAccuracy, lng + lngAccuracy]),
75
76 options = this._locateOptions;
7477
7578 if (options.setView) {
7679 var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
7780 this.setView(latlng, zoom);
7881 }
7982
80 this.fire('locationfound', {
83 var data = {
8184 latlng: latlng,
8285 bounds: bounds,
83 accuracy: pos.coords.accuracy
84 });
86 };
87
88 for (var i in pos.coords) {
89 if (typeof pos.coords[i] === 'number') {
90 data[i] = pos.coords[i];
91 }
92 }
93
94 this.fire('locationfound', data);
8595 }
8696 });
+0
-22
src/map/ext/Map.Popup.js less more
0 /*
1 * Adds popup-related methods to L.Map.
2 */
3
4 L.Map.include({
5 openPopup: function (popup) {
6 this.closePopup();
7
8 this._popup = popup;
9
10 return this
11 .addLayer(popup)
12 .fire('popupopen', {popup: this._popup});
13 },
14
15 closePopup: function () {
16 if (this._popup) {
17 this._popup._close();
18 }
19 return this;
20 }
21 });
2525 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
2626
2727 L.DomUtil.disableTextSelection();
28 L.DomUtil.disableImageDrag();
2829
2930 this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
3031
3738 L.DomEvent
3839 .on(document, 'mousemove', this._onMouseMove, this)
3940 .on(document, 'mouseup', this._onMouseUp, this)
40 .preventDefault(e);
41 .on(document, 'keydown', this._onKeyDown, this);
4142
42 this._map.fire("boxzoomstart");
43 this._map.fire('boxzoomstart');
4344 },
4445
4546 _onMouseMove: function (e) {
6061 box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
6162 },
6263
63 _onMouseUp: function (e) {
64 _finish: function () {
6465 this._pane.removeChild(this._box);
6566 this._container.style.cursor = '';
6667
6768 L.DomUtil.enableTextSelection();
69 L.DomUtil.enableImageDrag();
6870
6971 L.DomEvent
7072 .off(document, 'mousemove', this._onMouseMove)
71 .off(document, 'mouseup', this._onMouseUp);
73 .off(document, 'mouseup', this._onMouseUp)
74 .off(document, 'keydown', this._onKeyDown);
75 },
76
77 _onMouseUp: function (e) {
78
79 this._finish();
7280
7381 var map = this._map,
7482 layerPoint = map.mouseEventToLayerPoint(e);
8189
8290 map.fitBounds(bounds);
8391
84 map.fire("boxzoomend", {
92 map.fire('boxzoomend', {
8593 boxZoomBounds: bounds
8694 });
95 },
96
97 _onKeyDown: function (e) {
98 if (e.keyCode === 27) {
99 this._finish();
100 }
87101 }
88102 });
89103
1515 },
1616
1717 _onDoubleClick: function (e) {
18 this.setView(e.latlng, this._zoom + 1);
18 this.setZoomAround(e.containerPoint, this._zoom + 1);
1919 }
2020 });
2121
1010 inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
1111 easeLinearity: 0.25,
1212
13 longPress: true,
14
1513 // TODO refactor, move to CRS
1614 worldCopyJump: false
1715 });
2119 if (!this._draggable) {
2220 var map = this._map;
2321
24 this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress);
22 this._draggable = new L.Draggable(map._mapPane, map._container);
2523
2624 this._draggable.on({
2725 'dragstart': this._onDragStart,
8482 _onViewReset: function () {
8583 // TODO fix hardcoded Earth values
8684 var pxCenter = this._map.getSize()._divideBy(2),
87 pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
85 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
8886
8987 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
90 this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
88 this._worldWidth = this._map.project([0, 180]).x;
9189 },
9290
9391 _onPreDrag: function () {
110108
111109 noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
112110
111 map.fire('dragend');
112
113113 if (noInertia) {
114114 map.fire('moveend');
115115
120120 ease = options.easeLinearity,
121121
122122 speedVector = direction.multiplyBy(ease / duration),
123 speed = speedVector.distanceTo(new L.Point(0, 0)),
123 speed = speedVector.distanceTo([0, 0]),
124124
125125 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
126126 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
128128 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
129129 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
130130
131 L.Util.requestAnimFrame(function () {
132 map.panBy(offset, decelerationDuration, ease);
133 });
131 if (!offset.x || !offset.y) {
132 map.fire('moveend');
133
134 } else {
135 L.Util.requestAnimFrame(function () {
136 map.panBy(offset, {
137 duration: decelerationDuration,
138 easeLinearity: ease,
139 noMoveStart: true
140 });
141 });
142 }
134143 }
135
136 map.fire('dragend');
137
138 if (options.maxBounds) {
139 // TODO predrag validation instead of animation
140 L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
141 }
142 },
143
144 _panInsideMaxBounds: function () {
145 this.panInsideBounds(this.options.maxBounds);
146144 }
147145 });
148146
3030
3131 // make the container focusable by tabbing
3232 if (container.tabIndex === -1) {
33 container.tabIndex = "0";
33 container.tabIndex = '0';
3434 }
3535
3636 L.DomEvent
5959 },
6060
6161 _onMouseDown: function () {
62 if (!this._focused) {
63 this._map._container.focus();
64 }
62 if (this._focused) { return; }
63
64 var body = document.body,
65 docEl = document.documentElement,
66 top = body.scrollTop || docEl.scrollTop,
67 left = body.scrollTop || docEl.scrollLeft;
68
69 this._map._container.focus();
70
71 window.scrollTo(left, top);
6572 },
6673
6774 _onFocus: function () {
118125 var key = e.keyCode,
119126 map = this._map;
120127
121 if (this._panKeys.hasOwnProperty(key)) {
128 if (key in this._panKeys) {
129
130 if (map._panAnim && map._panAnim._inProgress) { return; }
131
122132 map.panBy(this._panKeys[key]);
123133
124134 if (map.options.maxBounds) {
125135 map.panInsideBounds(map.options.maxBounds);
126136 }
127137
128 } else if (this._zoomKeys.hasOwnProperty(key)) {
138 } else if (key in this._zoomKeys) {
129139 map.setZoom(map.getZoom() + this._zoomKeys[key]);
130140
131141 } else {
77
88 L.Map.ScrollWheelZoom = L.Handler.extend({
99 addHooks: function () {
10 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
10 L.DomEvent.on(this._map._container, 'wheel', this._onWheelScroll, this);
1111 this._delta = 0;
1212 },
1313
1414 removeHooks: function () {
15 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
15 L.DomEvent.off(this._map._container, 'wheel', this._onWheelScroll);
1616 },
1717
1818 _onWheelScroll: function (e) {
3939 delta = this._delta,
4040 zoom = map.getZoom();
4141
42 delta = delta > 0 ? Math.ceil(delta) : Math.round(delta);
42 delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
4343 delta = Math.max(Math.min(delta, 4), -4);
4444 delta = map._limitZoom(zoom + delta) - zoom;
4545
4646 this._delta = 0;
47
4847 this._startTime = null;
4948
5049 if (!delta) { return; }
5150
52 var newZoom = zoom + delta,
53 newCenter = this._getCenterForScrollWheelZoom(newZoom);
54
55 map.setView(newCenter, newZoom);
56 },
57
58 _getCenterForScrollWheelZoom: function (newZoom) {
59 var map = this._map,
60 scale = map.getZoomScale(newZoom),
61 viewHalf = map.getSize()._divideBy(2),
62 centerOffset = this._lastMousePos._subtract(viewHalf)._multiplyBy(1 - 1 / scale),
63 newCenterPoint = map._getTopLeftPoint()._add(viewHalf)._add(centerOffset);
64
65 return map.unproject(newCenterPoint);
51 map.setZoomAround(this._lastMousePos, zoom + delta);
6652 }
6753 });
6854
0 /*
1 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
2 */
3
4 L.Map.mergeOptions({
5 tap: true,
6 tapTolerance: 15
7 });
8
9 L.Map.Tap = L.Handler.extend({
10 addHooks: function () {
11 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
12 },
13
14 removeHooks: function () {
15 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
16 },
17
18 _onDown: function (e) {
19 if (!e.touches) { return; }
20
21 L.DomEvent.preventDefault(e);
22
23 this._fireClick = true;
24
25 // don't simulate click or track longpress if more than 1 touch
26 if (e.touches.length > 1) {
27 this._fireClick = false;
28 clearTimeout(this._holdTimeout);
29 return;
30 }
31
32 var first = e.touches[0],
33 el = first.target;
34
35 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
36
37 // if touching a link, highlight it
38 if (el.tagName.toLowerCase() === 'a') {
39 L.DomUtil.addClass(el, 'leaflet-active');
40 }
41
42 // simulate long hold but setting a timeout
43 this._holdTimeout = setTimeout(L.bind(function () {
44 if (this._isTapValid()) {
45 this._fireClick = false;
46 this._onUp();
47 this._simulateEvent('contextmenu', first);
48 }
49 }, this), 1000);
50
51 L.DomEvent
52 .on(document, 'touchmove', this._onMove, this)
53 .on(document, 'touchend', this._onUp, this);
54 },
55
56 _onUp: function (e) {
57 clearTimeout(this._holdTimeout);
58
59 L.DomEvent
60 .off(document, 'touchmove', this._onMove, this)
61 .off(document, 'touchend', this._onUp, this);
62
63 if (this._fireClick && e && e.changedTouches) {
64
65 var first = e.changedTouches[0],
66 el = first.target;
67
68 if (el.tagName.toLowerCase() === 'a') {
69 L.DomUtil.removeClass(el, 'leaflet-active');
70 }
71
72 // simulate click if the touch didn't move too much
73 if (this._isTapValid()) {
74 this._simulateEvent('click', first);
75 }
76 }
77 },
78
79 _isTapValid: function () {
80 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
81 },
82
83 _onMove: function (e) {
84 var first = e.touches[0];
85 this._newPos = new L.Point(first.clientX, first.clientY);
86 },
87
88 _simulateEvent: function (type, e) {
89 var simulatedEvent = document.createEvent('MouseEvents');
90
91 simulatedEvent._simulated = true;
92 e.target._simulatedClick = true;
93
94 simulatedEvent.initMouseEvent(
95 type, true, true, window, 1,
96 e.screenX, e.screenY,
97 e.clientX, e.clientY,
98 false, false, false, false, 0, null);
99
100 e.target.dispatchEvent(simulatedEvent);
101 }
102 });
103
104 if (L.Browser.touch && !L.Browser.msTouch) {
105 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
106 }
4343 },
4444
4545 _onTouchMove: function (e) {
46 if (!e.touches || e.touches.length !== 2) { return; }
46 var map = this._map;
4747
48 var map = this._map;
48 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
4949
5050 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
5151 p2 = map.mouseEventToLayerPoint(e.touches[1]);
5656 if (this._scale === 1) { return; }
5757
5858 if (!this._moved) {
59 L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
59 L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
6060
6161 map
6262 .fire('movestart')
63 .fire('zoomstart')
64 ._prepareTileBg();
63 .fire('zoomstart');
6564
6665 this._moved = true;
6766 }
7675 _updateOnMove: function () {
7776 var map = this._map,
7877 origin = this._getScaleOrigin(),
79 center = map.layerPointToLatLng(origin);
78 center = map.layerPointToLatLng(origin),
79 zoom = map.getScaleZoom(this._scale);
8080
81 map.fire('zoomanim', {
82 center: center,
83 zoom: map.getScaleZoom(this._scale)
84 });
85
86 // Used 2 translates instead of transform-origin because of a very strange bug -
87 // it didn't count the origin on the first touch-zoom but worked correctly afterwards
88
89 map._tileBg.style[L.DomUtil.TRANSFORM] =
90 L.DomUtil.getTranslateString(this._delta) + ' ' +
91 L.DomUtil.getScaleString(this._scale, this._startCenter);
81 map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta);
9282 },
9383
9484 _onTouchEnd: function () {
95 if (!this._moved || !this._zooming) { return; }
85 if (!this._moved || !this._zooming) {
86 this._zooming = false;
87 return;
88 }
9689
9790 var map = this._map;
9891
9992 this._zooming = false;
10093 L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
94 L.Util.cancelAnimFrame(this._animRequest);
10195
10296 L.DomEvent
10397 .off(document, 'touchmove', this._onTouchMove)
111105 roundZoomDelta = (floatZoomDelta > 0 ?
112106 Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
113107
114 zoom = map._limitZoom(oldZoom + roundZoomDelta);
108 zoom = map._limitZoom(oldZoom + roundZoomDelta),
109 scale = map.getZoomScale(zoom) / this._scale;
115110
116 map.fire('zoomanim', {
117 center: center,
118 zoom: zoom
119 });
120
121 map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
111 map._animateZoom(center, zoom, origin, scale);
122112 },
123113
124114 _getScaleOrigin: function () {