Codebase list leaflet / b9a2860
Imported Upstream version 0.4 Jonas Smedegaard 11 years ago
115 changed file(s) with 15495 addition(s) and 1990 deletion(s). Raw diff Collapse all Expand all
0 node_modules
1 .DS_Store
2 tmp/**/*
3 .idea
4 .idea/**/*
5 *.iml
6 *.sublime-*
44
55 ## 0.4 (master)
66
7 An in-progress version being developed on the master branch.
8
9 ### API simplification
10
11 Leaflet 0.4 contains several API improvements that allow simpler, jQuery-like syntax ([example](https://gist.github.com/3038879)) while being backwards compatible with the previous approach (so you can use both styles):
12
13 * Improved most methods and options to accept `LatLng`, `LatLngBounds`, `Point` and `Bounds` values in an array form (e.g. `map.panTo([lat, lng])` will be the same as `map.panTo(new L.LatLng(lat, lng))`)
14 * Added `addTo` method to all layer classes, e.g. `marker.addTo(map)` is equivalent to `map.addLayer(marker)`
15 * Added factory methods to most classes to be able to write code without `new` keyword, named the same as classes but starting with a lowercase letter, e.g. `L.map('map')` is the same as `new L.Map('map')`
16
17 ### Notable new features
18
19 * Added configurable **panning inertia** - after a quick pan, the map slows down in the same direction.
20 * Added **keyboard navigation** for panning/zooming with keyboard arrows and +/- keys (by [@ericmmartinez](https://github.com/ericmmartinez)). [#663](https://github.com/CloudMade/Leaflet/pull/663) [#646](https://github.com/CloudMade/Leaflet/issues/646)
21 * Added smooth **zoom animation of markers, vector layers, image overlays and popups** (by [@danzel](https://github.com/danzel)). [#740](https://github.com/CloudMade/Leaflet/pull/740) [#758](https://github.com/CloudMade/Leaflet/issues/758)
22 * Added **Android 4+ pinch-zoom** support (by [@danzel](https://github.com/danzel)). [#774](https://github.com/CloudMade/Leaflet/pull/774)
23 * Added **polyline and polygon editing**. [#174](https://github.com/CloudMade/Leaflet/issues/174)
24 * Added an unobtrusive **scale control**.
25 * Added **DivIcon** class that easily allows you to create lightweight div-based markers.
26 * Added **Rectangle** vector layer (by [@JasonSanford](https://github.com/JasonSanford)). [#504](https://github.com/CloudMade/Leaflet/pull/504)
27
728 ### Improvements
829
30 #### Usability improvements
31
32 * Improved zooming so that you don't get a blank map when you zoom in or out twice quickly (by [@danzel](https://github.com/danzel)). [#7](https://github.com/CloudMade/Leaflet/issues/7) [#729](https://github.com/CloudMade/Leaflet/pull/729)
33 * Drag-panning now works even when there are markers in the starting point (helps on maps with lots of markers). [#506](https://github.com/CloudMade/Leaflet/issues/506)
34 * Improved panning performance even more (there are no wasted frames now).
35 * Improved pinch-zoom performance in mobile Chrome and Firefox.
36 * Improved map performance on window resize.
37 * Replaced box-shadow with border on controls for mobile devices to improve performance.
38 * Slightly improved default popup styling.
39 * Added `TileLayer` `detectRetina` option (`false` by default) that makes tiles show in a higher resolution on iOS retina displays (by [@Mithgol](https://github.com/Mithgol)). [#586](https://github.com/CloudMade/Leaflet/pull/586)
40
41 #### GeoJSON API changes
42
43 GeoJSON API was improved to be simpler and more flexible ([example](https://gist.github.com/3062900)). The changes are not backwards-compatible, so be sure to update your old code.
44
45 * Added `style` option for styling vector layers, passed either as an object or as a function (to style vector layers according to GeoJSON properties).
46 * Added `filter` option to leave out features that don't correspond to a certain criteria (e.g. based on properties).
47 * Added `onEachFeature` option to execute certain code on each feature layer based on its properties (binding popups, etc).
48 * Changed `pointToLayer` function signature to provide `geojson` in addition to `latlng` when creating point features for more flexibility.
49
50 #### Icon API changes
51
52 Icon API was improved to be more flexible, but one of the changes is backwards-incompatible: you now need to pass different icon properties (like `iconUrl`) inside an options object ([example](https://gist.github.com/3076084)).
53
54 * Converted `Icon` properties to options, changed constructor signature to `Icon(options)`.
55 * Moved default marker icon options to `L.Icon.Default` class (which extends from `L.Icon`).
56 * Added `Icon` `className` option to assign a custom class to an icon.
57 * Added `Icon` `shadowAnchor` option to set the anchor of the shadow.
58 * Made all `Icon` options except `iconUrl` optional (if not specified, they'll be chosen automatically or implemented using CSS). Anchor is centered by default (if size is specified), and otherwise can be set through CSS using negative margins.
59
60 #### Control API changes
61
962 * Added `setPosition` and `getPosition` to all controls, as well as ability to pass certain position as an option when creating a control.
10 * Replaced ugly control position constants (e.g. L.Control.Position.TOP_LEFT) with light strings ('topleft', 'bottomright', etc.)
1163 * Made controls implementation easier (now more magic happens under the hood).
64 * Replaced ugly control position constants (e.g. `L.Control.Position.TOP_LEFT`) with light strings (`'topleft'`, `'bottomright'`, etc.)
65
66 #### Other breaking API changes
67
68 * Improved `TileLayer` constructor to interpolate URL template values from options (removed third `urlParams` argument).
69 * Changed `TileLayer` `scheme: 'tms'` option to `tms: true`.
70 * Removed `Map` `locateAndSetView` method (use `locate` with `setView: true` option)
71 * Changed popup `minWidth` and `maxWidth` options to be applied to content element, not the whole popup.
72 * Moved `prefix` argument to `options` in `Control.Attribution` constructor.
73 * Renamed `L.VERSION` to `L.version`.
74
75 #### Other API improvements
76
77 * Improved `on` and `off` methods to also accept `(eventHash[, context])`, as well as multiple space-separated events (by [@Guiswa](https://github.com/Guiswa)). [#770](https://github.com/CloudMade/Leaflet/pull/770)
78 * Improved `off` to remove all listeners of the event if no function was specified (by [@Guiswa](https://github.com/Guiswa)). [#770](https://github.com/CloudMade/Leaflet/pull/770) [#691](https://github.com/CloudMade/Leaflet/issues/691)
79 * Added `TileLayer` `setZIndex` method for controlling the order of tile layers (thanks to [@mattcurrie](https://github.com/mattcurrie)). [#837](https://github.com/CloudMade/Leaflet/pull/837)
80 * Added `Control.Layers` `autoZIndex` option (on by default) to preserve the order of tile layers when switching.
81 * Added `TileLayer` `redraw` method for re-requesting tiles (by [@greeninfo](https://github.com/greeninfo)). [#719](https://github.com/CloudMade/Leaflet/issues/719)
82 * Added `TileLayer` `setUrl` method for dynamically changing the tile URL template.
83 * Added `bringToFront` and `bringToBack` methods to `TileLayer`, `ImageOverlay` and vector layers. [#185](https://github.com/CloudMade/Leaflet/issues/185) [#505](https://github.com/CloudMade/Leaflet/issues/505)
84 * Added `TileLayer` `loading` event that fires when its tiles start to load (thanks to [@lapinos03](https://github.com/lapinos03)). [#177](https://github.com/CloudMade/Leaflet/issues/177)
85 * Added `TileLayer.WMS` `setParams` method for setting WMS parameters at runtime (by [@greeninfo](https://github.com/greeninfo)). [#719](https://github.com/CloudMade/Leaflet/issues/719)
86 * Added `TileLayer.WMS` subdomain support (`{s}` in the url) (by [@greeninfo](https://github.com/greeninfo)). [#735](https://github.com/CloudMade/Leaflet/issues/735)
87 * Added `originalEvent` property to `MouseEvent` (by [@k4](https://github.com/k4)). [#521](https://github.com/CloudMade/Leaflet/pull/521)
88 * Added `containerPoint` property to `MouseEvent`. [#413](https://github.com/CloudMade/Leaflet/issues/413)
89 * Added `contextmenu` event to vector layers (by [@ErrorProne](https://github.com/ErrorProne)). [#500](https://github.com/CloudMade/Leaflet/pull/500)
90 * Added `LayerGroup` `eachLayer` method for iterating over its members.
91 * Added `FeatureGroup` `mousemove` and `contextmenu` events (by [@jacobtoye](https://github.com/jacobtoye)). [#816](https://github.com/CloudMade/Leaflet/pull/816)
92 * Added chaining to `DomEvent` methods.
93 * Added `on` and `off` aliases for `DomEvent` `addListener` and `removeListener` methods.
94 * Added `L_NO_TOUCH` global variable switch (set it before Leaflet inclusion) which disables touch detection, helpful for desktop apps built using QT. [#572](https://github.com/CloudMade/Leaflet/issues/572)
95 * Added `dashArray` option to vector layers for making dashed strokes (by [jacobtoye](https://github.com/jacobtoye)). [#821](https://github.com/CloudMade/Leaflet/pull/821) [#165](https://github.com/CloudMade/Leaflet/issues/165)
96 * Added `Circle` `getBounds` method. [#440](https://github.com/CloudMade/Leaflet/issues/440)
97 * Added `Circle` `getLatLng` and `getRadius` methods (by [@Guiswa](https://github.com/Guiswa)). [#655](https://github.com/CloudMade/Leaflet/pull/655)
98 * Added `openPopup` method to all vector layers. [#246](https://github.com/CloudMade/Leaflet/issues/246)
99 * Added public `redraw` method to vector layers (useful if you manipulate their `LatLng` points directly).
100 * Added `Marker` `opacity` option and `setOpacity` method.
101 * Added `Marker` `update` method. [#392](https://github.com/CloudMade/Leaflet/issues/392)
102 * Improved `Marker` `openPopup` not to raise an error if it doesn't have a popup. [#507](https://github.com/CloudMade/Leaflet/issues/507)
103 * Added `ImageOverlay` `opacity` option and `setOpacity` method. [#438](https://github.com/CloudMade/Leaflet/issues/438)
104 * Added `Popup` `maxHeight` option that makes content inside the popup scrolled if it doesn't fit the specified max height.
105 * Added `Popup` `openOn(map)` method (similar to `Map` `openPopup`).
106 * Added `Map` `getContainer` method (by [@Guiswa](https://github.com/Guiswa)). [#654](https://github.com/CloudMade/Leaflet/pull/654)
107 * Added `Map` `containerPointToLatLng` and `latLngToContainerPoint` methods. [#474](https://github.com/CloudMade/Leaflet/issues/474)
108 * Added `Map` `addHandler` method.
109 * Added `Map` `mouseup` and `autopanstart` events.
110 * Added `LatLngBounds` `pad` method that returns bounds extended by a percentage (by [@jacobtoye](https://github.com/jacobtoye)). [#492](https://github.com/CloudMade/Leaflet/pull/492)
111 * Moved dragging cursor styles from JS code to CSS.
12112
13113 ### Bug fixes
14114
15 * Fixed a bug where `TileLayer.WMS` wouldn't take `insertAtTheBottom` option into account (by [@bmcbride](https://github.com/bmcbride)). [#478](https://github.com/CloudMade/Leaflet/pull/478)
16
17 ## 0.3.1 (14.02.2012)
115 #### General bugfixes
116
117 * Fixed a bug where the map was zooming incorrectly inside a `position: fixed` container (by [@chx007](https://github.com/chx007)). [#602](https://github.com/CloudMade/Leaflet/pull/602)
118 * Fixed a bug where scaled tiles weren't cleared up after zoom in some cases (by [@cfis](https://github.com/cfis)) [#683](https://github.com/CloudMade/Leaflet/pull/683)
119 * Fixed a bug where map wouldn't drag over a polygon with a `mousedown` listener. [#834](https://github.com/CloudMade/Leaflet/issues/834)
120
121 #### API bugfixes
122
123 * Fixed a regression where removeLayer would not remove corresponding attribution. [#488](https://github.com/CloudMade/Leaflet/issues/488)
124 * Fixed a bug where popup close button wouldn't work on manually added popups. [#423](https://github.com/CloudMade/Leaflet/issues/423)
125 * Fixed a bug where marker click event would stop working if you dragged it and then disabled dragging. [#434](https://github.com/CloudMade/Leaflet/issues/434)
126 * Fixed a bug where `TileLayer` `setOpacity` wouldn't work when setting it back to 1.
127 * Fixed a bug where vector layer `setStyle({stroke: false})` wouldn't remove stroke and the same for fill. [#441](https://github.com/CloudMade/Leaflet/issues/441)
128 * Fixed a bug where `Marker` `bindPopup` method wouldn't take `offset` option into account.
129 * Fixed a bug where `TileLayer` `load` event wasn't fired if some tile didn't load (by [@lapinos03](https://github.com/lapinos03) and [@cfis](https://github.com/cfis)) [#682](https://github.com/CloudMade/Leaflet/pull/682)
130 * Fixed error when removing `GeoJSON` layer. [#685](https://github.com/CloudMade/Leaflet/issues/685)
131 * Fixed error when calling `GeoJSON` `clearLayer` (by [@runderwood](https://github.com/runderwood)). [#617](https://github.com/CloudMade/Leaflet/pull/617)
132 * Fixed a bug where `Control` `setPosition` wasn't always working correctly (by [@ericmmartinez](https://github.com/ericmmartinez)). [#657](https://github.com/CloudMade/Leaflet/pull/657)
133 * Fixed a bug with `Util.bind` sometimes losing arguments (by [@johtso](https://github.com/johtso)). [#588](https://github.com/CloudMade/Leaflet/pull/588)
134 * Fixed a bug where `drag` event was sometimes fired after `dragend`. [#555](https://github.com/CloudMade/Leaflet/issues/555)
135 * Fixed a bug where `TileLayer` `load` event was firing only once (by [@lapinos03](https://github.com/lapinos03) and [shintonik](https://github.com/shintonik)). [#742](https://github.com/CloudMade/Leaflet/pull/742) [#177](https://github.com/CloudMade/Leaflet/issues/177)
136 * Fixed a bug where `FeatureGroup` popup events where not cleaned up after removing a layer from it (by [@danzel](https://github.com/danzel)). [#775](https://github.com/CloudMade/Leaflet/pull/775)
137 * Fixed a bug where `DomUtil.removeClass` didn't remove trailing spaces (by [@jieter](https://github.com/jieter)). [#784](https://github.com/CloudMade/Leaflet/pull/784)
138 * Fixed a bug where marker popup didn't take popup offset into account.
139 * Fixed a bug that led to an error when polyline was removed inside a `moveend` listener.
140 * Fixed a bug where `LayerGroup` `addLayer` wouldn't check if a layer has already been added (by [@danzel](https://github.com/danzel)). [798](https://github.com/CloudMade/Leaflet/pull/798)
141
142 #### Browser bugfixes
143
144 * Fixed broken zooming on IE10 beta (by [@danzel](https://github.com/danzel)). [#650](https://github.com/CloudMade/Leaflet/issues/650) [#751](https://github.com/CloudMade/Leaflet/pull/751)
145 * Fixed a bug that broke Leaflet for websites that had XHTML content-type header set (by [lars-sh](https://github.com/lars-sh)). [#801](https://github.com/CloudMade/Leaflet/pull/801)
146 * Fixed a bug that caused popups to be empty in IE when passing a DOM node as the content (by [@nrenner](https://github.com/nrenner)). [#472](https://github.com/CloudMade/Leaflet/pull/472)
147 * Fixed inability to use scrolled content inside popup due to mouse wheel propagation.
148 * Fixed a bug that caused jumping/stuttering of panning animation in some cases.
149 * Fixed a bug where popup size was calculated incorrectly in IE.
150 * Fixed a bug where cursor would flicker when dragging a marker.
151 * Fixed a bug where clickable paths on IE9 didn't have a hand cursor (by [naehrstoff](https://github.com/naehrstoff)). [#671](https://github.com/CloudMade/Leaflet/pull/671)
152 * Fixed a bug in IE with disappearing icons when changing opacity (by [@tagliala](https://github.com/tagliala) and [DamonOehlman](https://github.com/DamonOehlman)). [#667](https://github.com/CloudMade/Leaflet/pull/667) [#600](https://github.com/CloudMade/Leaflet/pull/600)
153 * Fixed a bug with setting opacity on IE10 (by [@danzel](https://github.com/danzel)). [796](https://github.com/CloudMade/Leaflet/pull/796)
154 * Fixed a bug where `Control.Layers` didn't work on IE7. [#652](https://github.com/CloudMade/Leaflet/issues/652)
155 * Fixed a bug that could cause false `mousemove` events on click in Chrome (by [@stsydow](https://github.com/stsydow)). [#757](https://github.com/CloudMade/Leaflet/pull/757)
156 * Fixed a bug in IE6-8 where adding fill or stroke on vector layers after initialization with `setStyle` would break the map. [#641](https://github.com/CloudMade/Leaflet/issues/641)
157 * Fixed a bug with setOpacity in IE where it would not work correctly if used more than once on the same element (by [@ajbeaven](https://github.com/ajbeaven)). [#827](https://github.com/CloudMade/Leaflet/pull/827)
158 * Fixed a bug in Chrome where transparent control corners sometimes couldn't be clicked through (by [@danzel](https://github.com/danzel)). [#836](https://github.com/CloudMade/Leaflet/pull/836) [#575](https://github.com/CloudMade/Leaflet/issues/575)
159
160 #### Mobile browser bugfixes
161
162 * Fixed a bug that sometimes caused map to disappear completely after zoom on iOS (by [@fr1n63](https://github.com/fr1n63)). [#580](https://github.com/CloudMade/Leaflet/issues/580) [#777](https://github.com/CloudMade/Leaflet/pull/777)
163 * Fixed a bug that often caused vector layers to flicker on drag end on iOS (by [@krawaller](https://github.com/krawaller)). [#18](https://github.com/CloudMade/Leaflet/issues/18)
164 * Fixed a bug with false map click events on pinch-zoom and zoom/layers controls click. [#485](https://github.com/CloudMade/Leaflet/issues/485)
165 * Fixed a bug where touching the map with two or more fingers simultaneously would raise an error.
166 * Fixed a bug where zoom control wasn't always visible on Android 3. [#335](https://github.com/CloudMade/Leaflet/issues/335)
167 * Fixed a bug where opening the layers control would propagate a click to the map (by [@jacobtoye](https://github.com/jacobtoye)). [#638](https://github.com/CloudMade/Leaflet/pull/638)
168 * Fixed a bug where `ImageOverlay` wouldn't stretch properly on zoom on Android 2. [#651](https://github.com/CloudMade/Leaflet/issues/651)
169 * Fixed a bug where `clearLayers` for vector layers on a Canvas backend (e.g. on Android 2) would take unreasonable amount of time. [#785](https://github.com/CloudMade/Leaflet/issues/785)
170 * Fixed a bug where `setLatLngs` and similar methods on vector layers on a Canvas backend would not update the layers immediately. [#732](https://github.com/CloudMade/Leaflet/issues/732)
171
172 ## 0.3.1 (February 14, 2012)
18173
19174 * Fixed a regression where default marker icons wouldn't work if Leaflet include url contained a query string.
20175 * Fixed a regression where tiles sometimes flickered with black on panning in iOS.
21176
22 ## 0.3 (13.02.2012)
177 ## 0.3 (February 13, 2012)
23178
24179 ### Major features
25180
55210 * Added `Polyline` `closestLayerPoint` method that's can be useful for interaction features (by [@anru](https://github.com/anru)). [#186](https://github.com/CloudMade/Leaflet/pull/186)
56211 * Added `setLatLngs` method to `MultiPolyline` and `MultiPolygon` (by [@anru](https://github.com/anru)). [#194](https://github.com/CloudMade/Leaflet/pull/194)
57212 * Added `getBounds` method to `Polyline` and `Polygon` (by [@JasonSanford](https://github.com/JasonSanford)). [#253](https://github.com/CloudMade/Leaflet/pull/253)
213 * Added `getBounds` method to `FeatureGroup` (by [@JasonSanford](https://github.com/JasonSanford)). [#557](https://github.com/CloudMade/Leaflet/pull/557)
58214 * Added `FeatureGroup` `setStyle` method (also inherited by `MultiPolyline` and `MultiPolygon`). [#353](https://github.com/CloudMade/Leaflet/issues/353)
59215 * Added `FeatureGroup` `invoke` method to call a particular method on all layers of the group with the given arguments.
60216 * Added `ImageOverlay` `load` event. [#213](https://github.com/CloudMade/Leaflet/issues/213)
63219 * Added `LatLng` `distanceTo` method (great circle distance) (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#462](https://github.com/CloudMade/Leaflet/pull/462)
64220 * Added `LatLngBounds` `toBBoxString` method for convenience (by [@JasonSanford](https://github.com/JasonSanford)). [#263](https://github.com/CloudMade/Leaflet/pull/263)
65221 * Added `LatLngBounds` `intersects(otherBounds)` method (thanks to [@pagameba](https://github.com/pagameba)). [#350](https://github.com/CloudMade/Leaflet/pull/350)
222 * Made `LatLngBounds` `extend` method to accept other `LatLngBounds` in addition to `LatLng` (by [@JasonSanford](https://github.com/JasonSanford)). [#553](https://github.com/CloudMade/Leaflet/pull/553)
66223 * Added `Bounds` `intersects(otherBounds)` method. [#461](https://github.com/CloudMade/Leaflet/issues/461)
67224 * Added `L.Util.template` method for simple string template evaluation.
68225 * Added `DomUtil.removeClass` method (by [@anru](https://github.com/anru)).
69 * Added ability to pass empty imageUrl to icons for creating transparent clickable regions (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#460](https://github.com/CloudMade/Leaflet/pull/460)
70226 * Improved browser-specific code to rely more on feature detection rather than user agent string.
71227 * Improved superclass access mechanism to work with inheritance chains of 3 or more classes; now you should use `Klass.superclass` instead of `this.superclass` (by [@anru](https://github.com/anru)). [#179](https://github.com/CloudMade/Leaflet/pull/179)
228 * Added `Map` `boxzoomstart` and `boxzoomend` events (by [@zedd45](https://github.com/zedd45)). [#554](https://github.com/CloudMade/Leaflet/pull/554)
229 * Added `Popup` `contentupdate` event (by [@mehmeta](https://github.com/mehmeta)). [#548](https://github.com/CloudMade/Leaflet/pull/548)
230
231 #### Breaking API changes
232
233 * `shiftDragZoom` map option/property renamed to `boxZoom`.
234 * Removed `mouseEventToLatLng` method (bringed back in 0.4).
72235
73236 #### Development workflow improvements
74237
00 var build = require('./build/build.js'),
1 lint = require('./build/hint.js');
1 lint = require('./build/hint.js');
22
3 var crlf = '\r\n',
4 COPYRIGHT = '/*' + crlf + ' Copyright (c) 2010-2011, CloudMade, Vladimir Agafonkin' + crlf +
5 ' Leaflet is a modern open-source JavaScript library for interactive maps.' + crlf +
6 ' http://leaflet.cloudmade.com' + crlf + '*/' + crlf;
3 var COPYRIGHT = '/*\n Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin\n' +
4 ' Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.\n' +
5 ' http://leaflet.cloudmade.com\n*/\n';
76
87 desc('Check Leaflet source for errors with JSHint');
98 task('lint', function () {
9
1010 var files = build.getFiles();
11
11
1212 console.log('Checking for JS errors...');
13
13
1414 var errorsFound = lint.jshint(files);
15
15
1616 if (errorsFound > 0) {
1717 console.log(errorsFound + ' error(s) found.\n');
1818 fail();
2323
2424 desc('Combine and compress Leaflet source files');
2525 task('build', ['lint'], function (compsBase32, buildName) {
26 var pathPart = 'dist/leaflet' + (buildName ? '-' + buildName : ''),
27 srcPath = pathPart + '-src.js',
28 path = pathPart + '.js';
2926
3027 var files = build.getFiles(compsBase32);
3128
3229 console.log('Concatenating ' + files.length + ' files...');
33 var content = build.combineFiles(files);
34
35 var oldSrc = build.load(srcPath),
36 newSrc = COPYRIGHT + content,
37 srcDelta = build.getSizeDelta(newSrc, oldSrc);
38
30
31 var content = build.combineFiles(files),
32 newSrc = COPYRIGHT + content,
33
34 pathPart = 'dist/leaflet' + (buildName ? '-' + buildName : ''),
35 srcPath = pathPart + '-src.js',
36
37 oldSrc = build.load(srcPath),
38 srcDelta = build.getSizeDelta(newSrc, oldSrc);
39
3940 console.log('\tUncompressed size: ' + newSrc.length + ' bytes (' + srcDelta + ')');
40
41
4142 if (newSrc === oldSrc) {
4243 console.log('\tNo changes');
4344 } else {
4445 build.save(srcPath, newSrc);
4546 console.log('\tSaved to ' + srcPath);
4647 }
47
48
4849 console.log('Compressing...');
4950
50 var oldCompressed = build.load(path),
51 newCompressed = COPYRIGHT + build.uglify(content),
52 delta = build.getSizeDelta(newCompressed, oldCompressed);
53
51 var path = pathPart + '.js',
52 oldCompressed = build.load(path),
53 newCompressed = COPYRIGHT + build.uglify(content),
54 delta = build.getSizeDelta(newCompressed, oldCompressed);
55
5456 console.log('\tCompressed size: ' + newCompressed.length + ' bytes (' + delta + ')');
5557
5658 if (newCompressed === oldCompressed) {
6163 }
6264 });
6365
64 task('default', ['build']);
66 task('default', ['build']);
00 <img src="http://leaflet.cloudmade.com/docs/images/logo.png" alt="Leaflet" />
11
2 Leaflet is a modern, lightweight BSD-licensed JavaScript library for making tile-based interactive maps for both desktop and mobile web browsers, developed by [CloudMade](http://cloudmade.com) to form the core of its next generation JavaScript API.
2 Leaflet is a modern, lightweight open-source JavaScript library for mobile-friendly interactive maps, developed by [Vladimir Agafonkin](http://agafonkin.com/en) of [CloudMade](http://cloudmade.com) with a team of dedicated [contributors](https://github.com/CloudMade/Leaflet/graphs/contributors). Weighting just about 25kb of gzipped JS code, it still has all the [features](http://leaflet.cloudmade.com/features.html) most developers ever need for online maps, while providing a fast, pleasant user experience.
33
4 It is built from the ground up to work efficiently and smoothly on both platforms, utilizing cutting-edge technologies included in HTML5. Its top priorities are usability, performance and small size, [A-grade](http://developer.yahoo.com/yui/articles/gbs/) browser support, flexibility and easy to use API. The OOP-based code of the library is designed to be modular, extensible and very easy to understand.
4 It is built from the ground up to work efficiently and smoothly on both desktop and mobile platforms like iOS and Android, taking advantage of HTML5 and CSS3 on modern browsers. The focus is on usability, performance, small size, [A-grade](http://developer.yahoo.com/yui/articles/gbs/) browser support, flexibility and [easy to use API](http://leaflet.cloudmade.com/reference.html) with convention over configuration. The OOP-based code of the library is designed to be modular, extensible and very easy to understand.
55
66 Check out the website for more information: [leaflet.cloudmade.com](http://leaflet.cloudmade.com)
77
88 ## Contributing to Leaflet
9 Let's make the best open-source library for maps that can possibly exist!
9 Let's make the best open-source library for maps that can possibly exist!
1010
11 Contributing is simple: make the changes in your fork, make sure that Leaflet builds successfully (see below) and then create a pull request to [Vladimir Agafonkin](http://github.com/mourner) (Leaflet maintainer). Updates to Leaflet [documentation](http://leaflet.cloudmade.com/reference.html) and [examples](http://leaflet.cloudmade.com/examples.html) (located in the `gh-pages` branch) are really appreciated too.
11 Contributing is simple: make the changes in your fork (preferably in a separate branch), make sure that Leaflet builds successfully (see below) and then create a pull request (without the built files, just the source changes) to [Vladimir Agafonkin](http://github.com/mourner) (Leaflet maintainer). Updates to Leaflet [documentation](http://leaflet.cloudmade.com/reference.html) and [examples](http://leaflet.cloudmade.com/examples.html) (located in the `gh-pages` branch for current stable release and `gh-pages-master` for the in-progress version) are really appreciated too.
12
13 Note that bugfixes and small improvements are higher priority than new features or substantial API changes, and will be reviewed and merged much faster than pulls with lots of new code. If your new feature is not expected to be widely used, consider making a plugin instead. Lets keep Leaflet slim, fast and simple!
1214
1315 Here's [a list of the awesome people](http://github.com/CloudMade/Leaflet/contributors) that joined us already. Looking forward to _your_ contributions!
1416
1719
1820 1. [Download and install Node](http://nodejs.org)
1921 2. Run the following commands in the command line:
20
22
2123 ```
2224 npm install -g jake
23 npm install -g jshint
24 npm install -g uglify-js
25 npm install jshint
26 npm install uglify-js
2527 ```
2628
2729 Now that you have everything installed, run `jake` inside the Leaflet directory. This will check Leaflet source files for JavaScript errors and inconsistencies, and then combine and compress it to the `dist` folder.
2830
2931 To make a custom build of the library with only the things you need, use the build helper (`build/build.html`) to choose the components (it figures out dependencies for you) and then run the command generated with it.
3032
31 If you add any new files to the Leaflet source, make sure to also add them to `build/deps.js` so that the build system knows about them. Happy coding!
33 If you add any new files to the Leaflet source, make sure to also add them to `build/deps.js` so that the build system knows about them. Happy coding!
8585 <li><a href="http://nodejs.org/#download">Download and install Node</a></li>
8686 <li>Run this in the command line:<br />
8787 <pre><code>npm install -g jake
88 npm install -g jshint
89 npm install -g uglify-js</code></pre></li>
88 npm install jshint
89 npm install uglify-js</code></pre></li>
9090 <li>Run this command inside the Leaflet directory: <br /><input type="text" id="command2" />
9191 </ol>
9292 <h2>Building using Closure Compiler</h2>
9393 <ol>
94 <li><a href="http://closure-compiler.googlecode.com/files/compiler-latest.zip">Download Closure Compiler</a> and extract it into <code>lib/closure-compiler</code> directory</li>
94 <li><a href="http://closure-compiler.googlecode.com/files/compiler-latest.zip">Download Closure Compiler</a>, extract it into <code>closure-compiler</code> directory</li>
9595 <li>Run this command in the root Leaflet directory: <br /><input type="text" id="command" /></li>
9696 </ol>
9797 </div>
138138 }
139139 }
140140
141 var command = 'java -jar lib/closure-compiler/compiler.jar ';
141 var command = 'java -jar closure-compiler/compiler.jar ';
142142 for (var src in files) {
143143 command += '--js src/' + src + ' ';
144144 }
153153 this.focus();
154154 this.select();
155155 };
156
156
157157 commandInput.onclick = inputSelect;
158158 commandInput2.onclick = inputSelect;
159159
4242 var pro = uglifyjs.uglify;
4343
4444 var ast = uglifyjs.parser.parse(code);
45 ast = pro.ast_mangle(ast);
46 ast = pro.ast_squeeze(ast, {keep_comps: false});
45 ast = pro.ast_mangle(ast, {mangle: true});
46 ast = pro.ast_squeeze(ast);
4747 ast = pro.ast_squeeze_more(ast);
4848
4949 return pro.gen_code(ast) + ';';
5050 };
5151
5252 exports.combineFiles = function (files) {
53 var content = '';
53 var content = '(function (window, undefined) {\n\n';
5454 for (var i = 0, len = files.length; i < len; i++) {
55 content += fs.readFileSync(files[i], 'utf8') + '\r\n\r\n';
55 content += fs.readFileSync(files[i], 'utf8') + '\n\n';
5656 }
57 return content;
57 return content + '\n\n}(this));';
5858 };
5959
6060 exports.save = function (savePath, compressed) {
5252 },
5353
5454 Marker: {
55 src: ['layer/marker/Icon.js', 'layer/marker/Marker.js'],
55 src: ['layer/marker/Icon.js', 'layer/marker/Icon.Default.js', 'layer/marker/Marker.js'],
5656 desc: 'Markers to put on the map.'
57 },
58
59 DivIcon: {
60 src: ['layer/marker/DivIcon.js'],
61 deps: ['Marker'],
62 desc: 'Lightweight div-based icon for markers.'
5763 },
5864
5965 Popup: {
107113 src: ['layer/vector/MultiPoly.js'],
108114 deps: ['FeatureGroup', 'Polyline', 'Polygon'],
109115 desc: 'MultiPolygon and MultyPolyline layers.'
116 },
117
118 Rectangle: {
119 src: ['layer/vector/Rectangle.js'],
120 deps: ['Polygon'],
121 desc: ['Rectangle overlays.']
110122 },
111123
112124 Circle: {
167179 desc: 'Enables zooming to bounding box by shift-dragging the map.'
168180 },
169181
182 Keyboard: {
183 src: ['map/handler/Map.Keyboard.js'],
184 desc: 'Enables keyboard pan/zoom when the map is focused.'
185 },
186
170187 MarkerDrag: {
171188 src: ['layer/marker/Marker.Drag.js'],
189 deps: ['Marker'],
172190 desc: 'Makes markers draggable (by mouse or touch).'
191 },
192
193 PolyEdit: {
194 src: ['layer/vector/Polyline.Edit.js'],
195 deps: ['Polyline', 'DivIcon'],
196 desc: 'Polyline and polygon editing.'
173197 },
174198
175199
186210 'map/ext/Map.Control.js',
187211 'control/Control.Attribution.js'],
188212 desc: 'Attribution control.'
213 },
214
215 ControlScale: {
216 src: ['control/Control.js',
217 'map/ext/Map.Control.js',
218 'control/Control.Scale.js'],
219 desc: 'Scale control.'
189220 },
190221
191222 ControlLayers: {
00 exports.config = {
11 "browser": true,
2 "node": true,
23 "predef": ["L"],
34
45 "debug": false,
1617 "eqnull": false,
1718 "evil": false,
1819 "expr": false,
19 "forin": false,
20 "forin": true,
2021 "immed": true,
2122 "latedef": true,
2223 "loopfunc": false,
2728 "shadow": false,
2829 "supernew": false,
2930 "undef": true,
31 "funcscope": false,
3032
3133 "newcap": true,
3234 "noempty": true,
3941
4042 "eqeqeq": true,
4143 "trailing": true,
42 "white": true
43 };
44 "white": true,
45 "smarttabs": true
46 };
+0
-47
debug/control/control-layers.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
14 <div id="map"></div>
15
16 <script type="text/javascript">
17
18 function getCloudMadeUrl(styleId) {
19 return 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/' + styleId + '/256/{z}/{x}/{y}.png';
20 }
21
22 var cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
23 cloudmade = new L.TileLayer(getCloudMadeUrl(997), {attribution: cloudmadeAttribution}),
24 cloudmade2 = new L.TileLayer(getCloudMadeUrl(998), {attribution: cloudmadeAttribution});
25
26 var map = new L.Map('map').addLayer(cloudmade).setView(new L.LatLng(50.5, 30.51), 15);
27
28 var marker = new L.Marker(new L.LatLng(50.5, 30.505));
29 map.addLayer(marker);
30
31 var marker2 = new L.Marker(new L.LatLng(50.502, 30.515));
32 map.addLayer(marker2);
33
34 var layersControl = new L.Control.Layers({
35 'CloudMade Fresh': cloudmade,
36 'CloudMade Pale Dawn': cloudmade2
37 }, {
38 'Some marker': marker,
39 'Another marker': marker2
40 });
41
42 map.addControl(layersControl);
43
44 </script>
45 </body>
46 </html>
+0
-29
debug/control/map-control.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
14 <div id="map"></div>
15
16 <script type="text/javascript">
17
18 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
19 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18}),
20 latlng = new L.LatLng(50.5, 30.51);
21
22 var map = new L.Map('map').addLayer(cloudmade).setView(latlng, 15);
23
24 var zoomControl = new L.Control.Zoom();
25 map.addControl(zoomControl);
26 </script>
27 </body>
28 </html>
+0
-53
debug/geojson/geojson-sample.js less more
0 var geojsonSample = {
1 "type": "FeatureCollection",
2 "features": [
3 {
4 "type": "Feature",
5 "geometry": {
6 "type": "Point",
7 "coordinates": [102.0, 0.5]
8 },
9 "properties": {
10 "prop0": "value0",
11 "color": "blue"
12 }
13 },
14
15 {
16 "type": "Feature",
17 "geometry": {
18 "type": "LineString",
19 "coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]
20 },
21 "properties": {
22 "color": "red",
23 "prop1": 0.0
24 }
25 },
26
27 {
28 "type": "Feature",
29 "geometry": {
30 "type": "Polygon",
31 "coordinates": [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]
32 },
33 "properties": {
34 "color": "green",
35 "prop1": {
36 "this": "that"
37 }
38 }
39 },
40
41 {
42 "type": "Feature",
43 "geometry": {
44 "type": "MultiPolygon",
45 "coordinates": [[[[100.0, 1.5], [100.5, 1.5], [100.5, 2.0], [100.0, 2.0], [100.0, 1.5]]], [[[100.5, 2.0], [100.5, 2.5], [101.0, 2.5], [101.0, 2.0], [100.5, 2.0]]]]
46 },
47 "properties": {
48 "color": "purple"
49 }
50 }
51 ]
52 };
+0
-56
debug/geojson/geojson.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
14 <div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
15 <button id="populate">Populate with 10 markers</button>
16
17 <script type="text/javascript" src="geojson-sample.js"></script>
18 <script type="text/javascript">
19
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
21 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
22 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
23
24 var map = new L.Map('map', {
25 center: new L.LatLng(0.78, 102.37),
26 zoom: 7,
27 layers: [cloudmade]
28 });
29
30 var geojson = new L.GeoJSON();
31
32 /* points are rendered as markers by default, but you can change this:
33
34 var geojson = new L.GeoJSON(null, {
35 pointToLayer: function(latlng) { return new L.CircleMarker(latlng); }
36 });
37 */
38
39 geojson.on('featureparse', function(e) {
40 // you can style features depending on their properties, etc.
41 var popupText = 'geometry type: ' + e.geometryType;
42
43 if (e.layer.setStyle && e.properties && e.properties.color) {
44 e.layer.setStyle({color: e.properties.color});
45 popupText += '<br/>color: ' + e.properties.color;
46 }
47 e.layer.bindPopup(popupText);
48 });
49
50 geojson.addGeoJSON(geojsonSample);
51
52 map.addLayer(geojson);
53 </script>
54 </body>
55 </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 <meta name="viewport" content="width=device-width,initial-scale=1 maximum-scale=1.0 user-scalable=0">
9 <link rel="stylesheet" href="../css/screen.css" />
10
11 <script src="../leaflet-include.js"></script>
12 </head>
13 <body>
14
15 <div id="map"></div>
16 <div >
17 <form method="get">Click in field then scroll map (in up/left direction) to see shift of map tiles.
18 <fieldset><label for="textField">Name</label>:
19 <input id="textField" name="textField" type="text" value="">
20 </fieldset>
21 </form>
22 Bug tested to occur on: Safari on Mac (Tested in 5.1.7), iPad/iPhone 5.1.1., Android 4 Browser. Hack is in L.Browser.chrome and TileLayer._addTile
23
24 </div>
25 <script type="text/javascript">
26
27 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/997/256/{z}/{x}/{y}.png', {
28 maxZoom: 18,
29 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
30 key: 'BC9A493B41014CAABB98F0471D759707'
31 });
32
33 //Disable the hack fix
34 L.Browser.chrome = true;
35
36 var map = L.map('map')
37 .setView([50.5, 30.51], 15)
38 .addLayer(cloudmade);
39 </script>
40 </body>
41 </html>
5050 'map/handler/Map.DoubleClickZoom.js',
5151 'map/handler/Map.ScrollWheelZoom.js',
5252 'map/handler/Map.BoxZoom.js',
53 'map/handler/Map.Keyboard.js',
5354
5455 'layer/LayerGroup.js',
5556 'layer/FeatureGroup.js',
6162 'layer/Popup.js',
6263
6364 'layer/marker/Icon.js',
65 'layer/marker/Icon.Default.js',
66 'layer/marker/DivIcon.js',
6467 'layer/marker/Marker.js',
6568 'layer/marker/Marker.Popup.js',
6669 'layer/marker/Marker.Drag.js',
7174 'layer/vector/Path.VML.js',
7275 'layer/vector/canvas/Path.Canvas.js',
7376 'layer/vector/Polyline.js',
77 'layer/vector/Polyline.Edit.js',
7478 'layer/vector/canvas/Polyline.Canvas.js',
7579 'layer/vector/Polygon.js',
80 'layer/vector/Rectangle.js',
7681 'layer/vector/canvas/Polygon.Canvas.js',
7782 'layer/vector/MultiPoly.js',
7883 'layer/vector/Circle.js',
8590 'control/Control.Zoom.js',
8691 'control/Control.Attribution.js',
8792 'control/Control.Layers.js',
93 'control/Control.Scale.js'
8894 ];
8995
9096 function getSrcUrl() {
101107 }
102108
103109 var path = getSrcUrl();
104 for (var i = 0; i < scripts.length; i++) {
105 document.writeln("<script type='text/javascript' src='" + path + "../src/" + scripts[i] + "'></script>");
110 for (var i = 0; i < scripts.length; i++) {
111 document.writeln("<script src='" + path + scripts[i] + "'></script>");
106112 }
113 document.writeln('<script>L.Icon.Default.imagePath = "' + path + '../dist/images";</script>');
107114 })();
108115
109116 function getRandomLatLng(map) {
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
14 <div id="map"></div>
15
16 <script type="text/javascript">
17
18 function getCloudMadeUrl(styleId) {
19 return 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/' + styleId + '/256/{z}/{x}/{y}.png';
20 }
21
22 var cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
23 cloudmade = new L.TileLayer(getCloudMadeUrl(997), {attribution: cloudmadeAttribution}),
24 cloudmade2 = new L.TileLayer(getCloudMadeUrl(998), {attribution: 'Hello world'});
25
26 var map = new L.Map('map').addLayer(cloudmade).setView(new L.LatLng(50.5, 30.51), 15);
27
28 var marker = new L.CircleMarker(new L.LatLng(50.5, 30.505), {color: 'red'});
29 map.addLayer(marker);
30 marker.bindPopup("Hello World").openPopup();
31
32 var marker2 = new L.Marker(new L.LatLng(50.502, 30.515));
33 map.addLayer(marker2);
34
35 var layersControl = new L.Control.Layers({
36 'CloudMade Fresh': cloudmade,
37 'CloudMade Pale Dawn': cloudmade2
38 }, {
39 'Some marker': marker,
40 'Another marker': marker2
41 });
42
43 map.addControl(layersControl);
44 map.addControl(new L.Control.Scale());
45
46 </script>
47 </body>
48 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet geolocation 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 src="../leaflet-include.js"></script>
13 </head>
14 <body>
15
16 <div id="map"></div>
17
18 <script type="text/javascript">
19
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
21 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
22 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
23
24 var map = new L.Map('map', {zoom: 15, layers: [cloudmade]});
25
26 function logEvent(e) { console.log(e.type); }
27 map.on('locationerror', logEvent);
28 map.on('locationfound', logEvent);
29
30 map.locate({setView: true});
31
32 </script>
33 </body>
34 </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 <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
10 <link rel="stylesheet" href="../css/screen.css" />
11
12 <script src="../leaflet-include.js"></script>
13 </head>
14 <body>
15
16 <div id="map"></div>
17 <button id="populate">Populate with 10 markers</button>
18
19 <script type="text/javascript">
20
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
22 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
23 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
24 latlng = new L.LatLng(50.5, 30.51);
25
26 var map = new L.Map('map');
27 map.addLayer(cloudmade);
28
29 var bounds = new L.LatLngBounds(
30 new L.LatLng(40.71222,-74.22655),
31 new L.LatLng(40.77394,-74.12544));
32
33 map.fitBounds(bounds);
34
35 var overlay = new L.ImageOverlay("https://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg", bounds, {
36 opacity: 0.5
37 });
38 map.addLayer(overlay);
39
40 </script>
41 </body>
42 </html>
22 <head>
33 <title>Leaflet debug page</title>
44
5 <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
6
5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
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
1212 <script src="../leaflet-include.js"></script>
1313 </head>
1414 <body>
1919
2020 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
2121 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
22 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
23
24 var map = new L.Map('map').addLayer(cloudmade);
25
26 map.on('locationfound', function(e) {
27 var marker = new L.Marker(e.latlng);
28 map.addLayer(marker);
29
30 marker.bindPopup("<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede.</p><p>Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque felis.</p>");
31 });
32
33 map.on('locationerror', function(e) {
34 alert(e.message);
35 map.fitWorld();
36 });
37
38 map.locateAndSetView();
22 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
23 latlng = new L.LatLng(50.5, 30.51);
24
25 var map = new L.Map('map', {center: latlng, zoom: 15, layers: [cloudmade]});
26
27 var marker = new L.Marker(latlng);
28 map.addLayer(marker);
29
30 marker.bindPopup("<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede.</p><p>Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque felis.</p>");
3931 </script>
4032 </body>
41 </html>
33 </html>
55 <link rel="stylesheet" href="../../dist/leaflet.css" />
66 <!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
77
8 <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
810 <link rel="stylesheet" href="../css/screen.css" />
911
1012 <script src="../leaflet-include.js"></script>
1113 </head>
1214 <body>
1315
14 <div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
16 <div id="map"></div>
1517 <button id="populate">Populate with 10 markers</button>
1618
1719 <script type="text/javascript">
1820
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
20 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
21 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
22 latlng = new L.LatLng(50.5, 30.51);
21 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/997/256/{z}/{x}/{y}.png', {
22 maxZoom: 18,
23 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
24 key: 'BC9A493B41014CAABB98F0471D759707'
25 });
2326
24 var map = new L.Map('map', {center: latlng, zoom: 15, layers: [cloudmade]});
27 var map = L.map('map')
28 .setView([50.5, 30.51], 15)
29 .addLayer(cloudmade);
2530
2631 var markers = new L.FeatureGroup();
2732
2020 layers: 'bluemarble',
2121 attribution: "Data &copy; NASA Blue Marble, image service by OpenGeo",
2222 minZoom: 2,
23 maxZoom: 5,
23 maxZoom: 5
2424 });
2525
2626 map.addLayer(bluemarble).fitWorld();
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
1010 <script src="../leaflet-include.js"></script>
1111 </head>
1212 <body>
1515
1616 <script type="text/javascript">
1717 var map = new L.Map('map');
18
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
18
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/{styleId}/256/{z}/{x}/{y}.png',
2020 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
21 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
22
21 cloudmade = new L.TileLayer(cloudmadeUrl, {styleId: 997, attribution: cloudmadeAttribution}),
22 cloudmade2 = new L.TileLayer(cloudmadeUrl, {styleId: 998, attribution: cloudmadeAttribution});
23
2324 var nexrad = new L.TileLayer.WMS("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
2425 layers: 'nexrad-n0r-900913',
2526 format: 'image/png',
2930 });
3031
3132 var bounds = new L.LatLngBounds(new L.LatLng(32, -126), new L.LatLng(50, -64));
32
33
3334 map.addLayer(cloudmade).addLayer(nexrad).fitBounds(bounds);
35
36 L.control.layers({"CM": cloudmade, "CM2": cloudmade2}, {"NexRad": nexrad}).addTo(map);
3437 </script>
3538 </body>
36 </html>
39 </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 src="../leaflet-include.js"></script>
11 </head>
12 <body>
13 <div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
14 <button onclick="boundsExtendBounds();">Extend the bounds of the center rectangle with the upper right rectangle</button>
15 <button onclick="boundsExtendLatLng()">Extend the bounds of the center rectangle with the lower left marker</button>
16 <script src="route.js"></script>
17 <script>
18 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
19 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
20
21 var latLng = new L.LatLng(54.18815548107151, -7.657470703124999);
22
23 var bounds1 = new L.LatLngBounds(new L.LatLng(54.559322, -5.767822), new L.LatLng(56.1210604, -3.021240));
24 var bounds2 = new L.LatLngBounds(new L.LatLng(56.56023925701561, -2.076416015625), new L.LatLng(57.01158038001565, -0.9777832031250001));
25 var bounds3;
26
27 var map = new L.Map('map', {
28 layers: [cloudmade],
29 center: bounds1.getCenter(),
30 zoom: 7
31 });
32
33 var rectangle1 = new L.Rectangle(bounds1);
34 var rectangle2 = new L.Rectangle(bounds2);
35 var rectangle3;
36
37 var marker = new L.Marker(latLng);
38
39 map.addLayer(rectangle1).addLayer(rectangle2).addLayer(marker);
40
41
42
43
44
45 function boundsExtendBounds() {
46 if (rectangle3) {
47 map.removeLayer(rectangle3);
48 rectangle3 = null;
49 }
50 if (bounds3) {
51 bounds3 = null;
52 }
53 bounds3 = new L.LatLngBounds(bounds1.getSouthWest(), bounds1.getNorthEast());
54 bounds3.extend(bounds2);
55 rectangle3 = new L.Rectangle(bounds3, {
56 color: "#ff0000",
57 weight: 1,
58 opacity: 1,
59 fillOpacity: 0
60 });
61
62 map.addLayer(rectangle3);
63 }
64
65 function boundsExtendLatLng() {
66 if (rectangle3) {
67 map.removeLayer(rectangle3);
68 rectangle3 = null;
69 }
70 if (bounds3) {
71 bounds3 = null;
72 }
73 bounds3 = new L.LatLngBounds(bounds1.getSouthWest(), bounds1.getNorthEast());
74 bounds3.extend(marker.getLatLng());
75 rectangle3 = new L.Rectangle(bounds3, {
76 color: "#ff0000",
77 weight: 1,
78 opacity: 1,
79 fillOpacity: 0
80 });
81
82 map.addLayer(rectangle3);
83 }
84
85 </script>
86 </body>
87 </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
1010 <script src="../leaflet-include.js"></script>
1111 </head>
1212 <body>
1616 <script>
1717 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
1818 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18}),
19 map = new L.Map('map', {layers: [cloudmade], center: new L.LatLng(50.5, 30.5), zoom: 15});
20
21
22 var latlngs = [];
23 latlngs.push(getRandomLatLng(map));
24 latlngs.push(getRandomLatLng(map));
25 latlngs.push(getRandomLatLng(map));
26
27 var path = new L.Polygon(latlngs);
19 map = new L.Map('map', {layers: [cloudmade], center: new L.LatLng(51.505, -0.04), zoom: 13});
2820
29 console.log(latlngs);
30
31 var marker = new L.Marker(latlngs[0], {draggable: true});
32 map.addLayer(marker);
33
34 marker.on('drag', function() {
35 latlngs[0] = marker.getLatLng();
36 path.setLatLngs(latlngs);
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.49, -0.02],
34 [51.51, 0],
35 [51.52, -0.02]
36 ]);
37
38 polyline.editing.enable();
39
40 map.addLayer(polyline);
41
42 polygon.on('edit', function() {
43 console.log('Polygon was edited!');
3744 });
38
39 map.addLayer(path);
45 polyline.on('edit', function() {
46 console.log('Polyline was edited!');
47 });
4048 </script>
4149 </body>
42 </html>
50 </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 src="../leaflet-include.js"></script>
11 </head>
12 <body>
13
14 <div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
15 <button onclick="geojsonLayerBounds();">Show GeoJSON layer bounds</button>
16 <button onclick="featureGroupBounds();">Show feature group bounds</button>
17
18 <script type="text/javascript" src="geojson-sample.js"></script>
19 <script type="text/javascript">
20
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
22 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
23 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
24 rectangle,
25 featureGroup;
26
27 var map = new L.Map('map', {
28 center: new L.LatLng(0.78, 102.37),
29 zoom: 7,
30 layers: [cloudmade]
31 });
32
33 var geojson = L.geoJson(geojsonSample, {
34
35 style: function (feature) {
36 return {color: feature.properties.color};
37 },
38
39 onEachFeature: function (feature, layer) {
40 var popupText = 'geometry type: ' + feature.geometry.type;
41
42 if (feature.properties.color) {
43 popupText += '<br/>color: ' + feature.properties.color
44 }
45
46 layer.bindPopup(popupText);
47 }
48 });
49
50 geojson.addLayer(new L.Marker(new L.LatLng(2.745530718801952, 105.194091796875)))
51
52 var eye1 = new L.Marker(new L.LatLng(-0.7250783020332547, 101.8212890625));
53 var eye2 = new L.Marker(new L.LatLng(-0.7360637370492077, 103.2275390625));
54 var nose = new L.Marker(new L.LatLng(-1.3292264529974207, 102.5463867187));
55 var mouth = new L.Polyline([
56 new L.LatLng(-1.3841426927920029, 101.7333984375),
57 new L.LatLng(-1.6037944300589726, 101.964111328125),
58 new L.LatLng(-1.6806671337507222, 102.249755859375),
59 new L.LatLng(-1.7355743631421197, 102.67822265625),
60 new L.LatLng(-1.5928123762763, 103.0078125),
61 new L.LatLng(-1.3292264529974207, 103.3154296875)
62 ]);
63 map.addLayer(eye1).addLayer(eye2).addLayer(nose).addLayer(mouth);
64 featureGroup = new L.FeatureGroup([eye1, eye2, nose, mouth]);
65
66 map.addLayer(geojson);
67 map.addLayer(featureGroup);
68
69 function geojsonLayerBounds() {
70 if (rectangle) {
71 rectangle.setBounds(geojson.getBounds());
72 } else {
73 rectangle = new L.Rectangle(geojson.getBounds());
74 map.addLayer(rectangle);
75 }
76 }
77
78 function featureGroupBounds() {
79 if (rectangle) {
80 rectangle.setBounds(featureGroup.getBounds());
81 } else {
82 rectangle = new L.Rectangle(featureGroup.getBounds());
83 map.addLayer(rectangle);
84 }
85
86 }
87 </script>
88 </body>
89 </html>
0 var geojsonSample = {
1 "type": "FeatureCollection",
2 "features": [
3 {
4 "type": "Feature",
5 "geometry": {
6 "type": "Point",
7 "coordinates": [102.0, 0.5]
8 },
9 "properties": {
10 "prop0": "value0",
11 "color": "blue"
12 }
13 },
14
15 {
16 "type": "Feature",
17 "geometry": {
18 "type": "LineString",
19 "coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]
20 },
21 "properties": {
22 "color": "red",
23 "prop1": 0.0
24 }
25 },
26
27 {
28 "type": "Feature",
29 "geometry": {
30 "type": "Polygon",
31 "coordinates": [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]
32 },
33 "properties": {
34 "color": "green",
35 "prop1": {
36 "this": "that"
37 }
38 }
39 },
40
41 {
42 "type": "Feature",
43 "geometry": {
44 "type": "MultiPolygon",
45 "coordinates": [[[[100.0, 1.5], [100.5, 1.5], [100.5, 2.0], [100.0, 2.0], [100.0, 1.5]]], [[[100.5, 2.0], [100.5, 2.5], [101.0, 2.5], [101.0, 2.0], [100.5, 2.0]]]]
46 },
47 "properties": {
48 "color": "purple"
49 }
50 }
51 ]
52 };
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
14 <div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
15 <button id="populate">Populate with 10 markers</button>
16
17 <script type="text/javascript" src="geojson-sample.js"></script>
18 <script type="text/javascript">
19
20 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
21 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
22 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
23
24 var map = new L.Map('map', {
25 center: new L.LatLng(0.78, 102.37),
26 zoom: 7,
27 layers: [cloudmade]
28 });
29
30 var geojson = L.geoJson(geojsonSample, {
31
32 style: function (feature) {
33 return {color: feature.properties.color};
34 },
35
36 onEachFeature: function (feature, layer) {
37 var popupText = 'geometry type: ' + feature.geometry.type;
38
39 if (feature.properties.color) {
40 popupText += '<br/>color: ' + feature.properties.color
41 }
42
43 layer.bindPopup(popupText);
44 }
45 });
46
47 map.addLayer(geojson);
48 </script>
49 </body>
50 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Leaflet debug page</title>
4
5 <meta name="viewport" content="initial-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 <script src="../leaflet-include.js"></script>
11 </head>
12 <body>
13
14 <div id="map" style="width: 500px; height: 500px;"></div>
15 <input type="button" value="Set blue rectangle bounds as current map extent." onclick="resetBounds();" />
16
17 <script type="text/javascript">
18
19 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
20 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
21 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});;
22
23 var bounds = new L.LatLngBounds(new L.LatLng(54.559322, -5.767822), new L.LatLng(56.1210604, -3.021240));
24 var bounds2 = new L.LatLngBounds(new L.LatLng(56.2124322195806, -3.427734375), new L.LatLng(56.307776937156945, -3.2560729980468746));
25
26 var rectangle = new L.Rectangle(bounds);
27 var styledRectangle = new L.Rectangle(bounds2, {
28 fillColor: "#ff7800",
29 color: "#000000",
30 opacity: 1,
31 weight: 2
32 });
33
34 rectangle.on("click", function () {
35 alert("you clicked a rectangle.")
36 });
37
38 var map = new L.Map('map', {
39 center: bounds.getCenter(),
40 zoom: 7,
41 layers: [cloudmade]
42 });
43
44 map.addLayer(rectangle).addLayer(styledRectangle);
45
46 function resetBounds() {
47 rectangle.setBounds(map.getBounds());
48 }
49
50 </script>
51 </body>
52 </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 src="../leaflet-include.js"></script>
11 </head>
12 <body>
13 <div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
14 <div style="background-color:chartreuse; width: 100px; height:100px; position: absolute; left: 850px; top: 10px" onclick="Hack1()">Hack1Touch</div>
15 <div style="background-color:coral; width: 100px; height:100px; position: absolute; left: 850px; top: 120px" onclick="Hack2()">Hack2Touch</div>
16 <script src="route.js"></script>
17 <script>
18 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
19 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18}),
20 map = new L.Map('map', {layers: [cloudmade], center: new L.LatLng(51.505, -0.04), zoom: 13});
21
22
23 var polygon = new L.Polygon([
24 new L.LatLng(51.51, -0.1),
25 new L.LatLng(51.5, -0.06),
26 new L.LatLng(51.52, -0.03)
27 ]);
28
29 polygon.editing.enable();
30
31 map.addLayer(polygon);
32
33 var polyline = new L.Polyline([
34 new L.LatLng(51.49, -0.02),
35 new L.LatLng(51.51, 0),
36 new L.LatLng(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
50 var _timerAt, _timer;
51 function Hack1() {
52 _timerAt = 0;
53 clearInterval(_timer);
54 _timer = setInterval(Hack1Timer, 1000);
55 }
56 function Hack1Timer() {
57 switch (_timerAt) {
58 case 0:
59 map.touchZoom._onTouchStart({ touches: [{ pageX: 405, pageY: 312 }, { pageX: 233, pageY: 321 }] });
60 break;
61 case 1:
62 map.touchZoom._onTouchMove({ touches: [{ pageX: 412, pageY: 312 }, { pageX: 236, pageY: 322 }] });
63 break;
64 case 2:
65 map.touchZoom._onTouchMove({ touches: [{ pageX: 423, pageY: 313 }, { pageX: 243, pageY: 321 }] });
66 break;
67 case 3:
68 map.touchZoom._onTouchMove({ touches: [{ pageX: 476, pageY: 326 }, { pageX: 299, pageY: 321 }] });
69 break;
70 case 4:
71 map.touchZoom._onTouchEnd();
72 break;
73 case 5:
74 clearInterval(_timer);
75 break;
76 }
77 _timerAt++;
78 }
79 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);
85 }
86 function Hack2Timer() {
87 switch (_timerAt) {
88 case 0:
89 map.touchZoom._onTouchStart({ touches: [{ pageX: 100, pageY: 100 }, { pageX: 50, pageY: 100}] });
90 break;
91 case 1:
92 map.touchZoom._onTouchMove({ touches: [{ pageX: 100, pageY: 100 }, { pageX: 50, pageY: 100 }] });
93 break;
94 case 2:
95 map.touchZoom._onTouchMove({ touches: [{ pageX: 110, pageY: 100 }, { pageX: 50, pageY: 100 }] });
96 break;
97 case 3:
98 map.touchZoom._onTouchMove({ touches: [{ pageX: 120, pageY: 100 }, { pageX: 50, pageY: 100 }] });
99 break;
100 case 4:
101 map.touchZoom._onTouchMove({ touches: [{ pageX: 130, pageY: 100 }, { pageX: 50, pageY: 100 }] });
102 break;
103 case 5:
104 map.touchZoom._onTouchMove({ touches: [{ pageX: 140, pageY: 100 }, { pageX: 50, pageY: 100 }] });
105 break;
106 case 6:
107 map.touchZoom._onTouchMove({ touches: [{ pageX: 150, pageY: 100 }, { pageX: 50, pageY: 100 }] });
108 break;
109 case 7:
110 map.touchZoom._onTouchEnd();
111 break;
112 case 8:
113 clearInterval(_timer);
114 break;
115 }
116 _timerAt++;
117 }
118 </script>
119 </body>
120 </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
1010 <script>
1111 L_PREFER_CANVAS = true; // experimental
1212 </script>
2323 <script>
2424 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
2525 cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
26
26
2727 for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
2828 latlngs.push(new L.LatLng(route[i][0], route[i][1]));
2929 }
3030 var path = new L.Polyline(latlngs);
3131
3232 var map = new L.Map('map', {layers: [cloudmade]});
33
33
3434 var group = new L.LayerGroup();
3535
3636 map.fitBounds(new L.LatLngBounds(latlngs));
37
37
3838 var circleLocation = new L.LatLng(51.508, -0.11),
3939 circleOptions = {
40 color: 'red',
41 fillColor: 'yellow',
40 color: 'red',
41 fillColor: 'yellow',
4242 fillOpacity: 0.7
4343 };
44
44
4545 var circle = new L.Circle(circleLocation, 500000, circleOptions),
4646 circleMarker = new L.CircleMarker(circleLocation, {fillColor: 'blue', fillOpacity: 1, stroke: false});
47
47
4848 group.addLayer(circle).addLayer(circleMarker);
49
49
5050 circle.bindPopup('I am a circle');
5151 circleMarker.bindPopup('I am a circle marker');
52
53 group.addLayer(path);
52
53 group.addLayer(path);
5454 path.bindPopup('I am a polyline');
55
55
5656 var p1 = latlngs[0],
5757 p2 = latlngs[parseInt(len/4)],
5858 p3 = latlngs[parseInt(len/3)],
6565 h3 = new L.LatLng(p3.lat, p3.lng),
6666 h4 = new L.LatLng(p4.lat, p4.lng),
6767 h5 = new L.LatLng(p5.lat, p5.lng);
68
68
6969 h1.lng += 20;
7070 h2.lat -= 5;
7171 h3.lat -= 5;
7272 h4.lng -= 10;
7373 h5.lng -= 8;
7474 h5.lat += 10;
75
75
7676 var holePoints = [h5, h4, h3, h2, h1];
7777
7878 var polygon = new L.Polygon([polygonPoints, holePoints], {
8080 color: 'green'
8181 });
8282 group.addLayer(polygon);
83
83
8484 polygon.bindPopup('I am a polygon');
85
85
8686 map.addLayer(group);
8787
8888 </script>
1515 <div id="map"></div>
1616
1717 <script>
18 var map = L.map('map');
1819
19 var map = new L.Map('map', {fadeAnimation: false});
20 var marker = L.marker([51.5, -0.09])
21 .bindPopup("<b>Hello world!</b><br />I am a popup.")
22 .addTo(map);
23 //.openPopup();
2024
21 var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
22 cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
23 cloudmade = new L.TileLayer(cloudmadeUrl, {
24 maxZoom: 18,
25 attribution: cloudmadeAttribution
26 });
25 var circle = L.circle([51.508, -0.11], 500, {color: '#f03', opacity: 0.7})
26 .bindPopup("I am a circle.")
27 .addTo(map);
2728
28 map.setView(new L.LatLng(51.505, -0.09), 13).addLayer(cloudmade);
29 var polygon = L.polygon([
30 [51.509, -0.08],
31 [51.503, -0.06],
32 [51.51, -0.047]])
33 .bindPopup("I am a polygon.")
34 .addTo(map);
2935
36 map.setView([51.505, -0.09], 13);
3037
31 var markerLocation = new L.LatLng(51.5, -0.09),
32 marker = new L.Marker(markerLocation);
38 var cloudmade = L.tileLayer('http://{s}.tile.cloudmade.com/{key}/{styleId}/256/{z}/{x}/{y}.png', {
39 attribution: 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
40 maxZoom: 18,
41 key: 'BC9A493B41014CAABB98F0471D759707',
42 styleId: 997
43 }).addTo(map);
3344
34 map.addLayer(marker);
35
36 marker.bindPopup("<b>Hello world!</b><br />I am a popup.").openPopup();
37
38
39 var circleLocation = new L.LatLng(51.508, -0.11),
40 circleOptions = {
41 color: '#f03',
42 opacity: 0.7
43 },
44 circle = new L.Circle(circleLocation, 500, circleOptions);
45
46 circle.bindPopup("I am a circle.");
47
48 map.addLayer(circle);
49
50
51 var p1 = new L.LatLng(51.509, -0.08),
52 p2 = new L.LatLng(51.503, -0.06),
53 p3 = new L.LatLng(51.51, -0.047),
54 polygonPoints = [ p1, p2, p3 ],
55 polygon = new L.Polygon(polygonPoints);
56
57 polygon.bindPopup("I am a polygon.");
58
59 map.addLayer(polygon);
6045 </script>
6146 </body>
62 </html>
47 </html>
dist/images/marker.png less more
Binary diff not shown
dist/images/popup-close.png less more
Binary diff not shown
0 /*
1 Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
2 Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
3 http://leaflet.cloudmade.com
4 */
5 (function (window, undefined) {
6
7 var L, originalL;
8
9 if (typeof exports !== undefined + '') {
10 L = exports;
11 } else {
12 originalL = window.L;
13 L = {};
14
15 L.noConflict = function () {
16 window.L = originalL;
17 return this;
18 };
19
20 window.L = L;
21 }
22
23 L.version = '0.4';
24
25
26 /*
27 * L.Util is a namespace for various utility functions.
28 */
29
30 L.Util = {
31 extend: function (/*Object*/ dest) /*-> Object*/ { // merge src properties into dest
32 var sources = Array.prototype.slice.call(arguments, 1);
33 for (var j = 0, len = sources.length, src; j < len; j++) {
34 src = sources[j] || {};
35 for (var i in src) {
36 if (src.hasOwnProperty(i)) {
37 dest[i] = src[i];
38 }
39 }
40 }
41 return dest;
42 },
43
44 bind: function (fn, obj) { // (Function, Object) -> Function
45 var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
46 return function () {
47 return fn.apply(obj, args || arguments);
48 };
49 },
50
51 stamp: (function () {
52 var lastId = 0, key = '_leaflet_id';
53 return function (/*Object*/ obj) {
54 obj[key] = obj[key] || ++lastId;
55 return obj[key];
56 };
57 }()),
58
59 limitExecByInterval: function (fn, time, context) {
60 var lock, execOnUnlock;
61
62 return function wrapperFn() {
63 var args = arguments;
64
65 if (lock) {
66 execOnUnlock = true;
67 return;
68 }
69
70 lock = true;
71
72 setTimeout(function () {
73 lock = false;
74
75 if (execOnUnlock) {
76 wrapperFn.apply(context, args);
77 execOnUnlock = false;
78 }
79 }, time);
80
81 fn.apply(context, args);
82 };
83 },
84
85 falseFn: function () {
86 return false;
87 },
88
89 formatNum: function (num, digits) {
90 var pow = Math.pow(10, digits || 5);
91 return Math.round(num * pow) / pow;
92 },
93
94 splitWords: function (str) {
95 return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
96 },
97
98 setOptions: function (obj, options) {
99 obj.options = L.Util.extend({}, obj.options, options);
100 return obj.options;
101 },
102
103 getParamString: function (obj) {
104 var params = [];
105 for (var i in obj) {
106 if (obj.hasOwnProperty(i)) {
107 params.push(i + '=' + obj[i]);
108 }
109 }
110 return '?' + params.join('&');
111 },
112
113 template: function (str, data) {
114 return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
115 var value = data[key];
116 if (!data.hasOwnProperty(key)) {
117 throw new Error('No value provided for variable ' + str);
118 }
119 return value;
120 });
121 },
122
123 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
124 };
125
126 (function () {
127
128 function getPrefixed(name) {
129 var i, fn,
130 prefixes = ['webkit', 'moz', 'o', 'ms'];
131
132 for (i = 0; i < prefixes.length && !fn; i++) {
133 fn = window[prefixes[i] + name];
134 }
135
136 return fn;
137 }
138
139 function timeoutDefer(fn) {
140 return window.setTimeout(fn, 1000 / 60);
141 }
142
143 var requestFn = window.requestAnimationFrame ||
144 getPrefixed('RequestAnimationFrame') || timeoutDefer;
145
146 var cancelFn = window.cancelAnimationFrame ||
147 getPrefixed('CancelAnimationFrame') ||
148 getPrefixed('CancelRequestAnimationFrame') ||
149 function (id) {
150 window.clearTimeout(id);
151 };
152
153
154 L.Util.requestAnimFrame = function (fn, context, immediate, element) {
155 fn = L.Util.bind(fn, context);
156
157 if (immediate && requestFn === timeoutDefer) {
158 fn();
159 } else {
160 return requestFn.call(window, fn, element);
161 }
162 };
163
164 L.Util.cancelAnimFrame = function (id) {
165 if (id) {
166 cancelFn.call(window, id);
167 }
168 };
169
170 }());
171
172
173 /*
174 * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
175 */
176
177 L.Class = function () {};
178
179 L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
180
181 // extended class with the new prototype
182 var NewClass = function () {
183 if (this.initialize) {
184 this.initialize.apply(this, arguments);
185 }
186 };
187
188 // instantiate class without calling constructor
189 var F = function () {};
190 F.prototype = this.prototype;
191
192 var proto = new F();
193 proto.constructor = NewClass;
194
195 NewClass.prototype = proto;
196
197 //inherit parent's statics
198 for (var i in this) {
199 if (this.hasOwnProperty(i) && i !== 'prototype') {
200 NewClass[i] = this[i];
201 }
202 }
203
204 // mix static properties into the class
205 if (props.statics) {
206 L.Util.extend(NewClass, props.statics);
207 delete props.statics;
208 }
209
210 // mix includes into the prototype
211 if (props.includes) {
212 L.Util.extend.apply(null, [proto].concat(props.includes));
213 delete props.includes;
214 }
215
216 // merge options
217 if (props.options && proto.options) {
218 props.options = L.Util.extend({}, proto.options, props.options);
219 }
220
221 // mix given properties into the prototype
222 L.Util.extend(proto, props);
223
224 return NewClass;
225 };
226
227
228 // method for adding properties to prototype
229 L.Class.include = function (props) {
230 L.Util.extend(this.prototype, props);
231 };
232
233 L.Class.mergeOptions = function (options) {
234 L.Util.extend(this.prototype.options, options);
235 };
236
237 /*
238 * L.Mixin.Events adds custom events functionality to Leaflet classes
239 */
240
241 var key = '_leaflet_events';
242
243 L.Mixin = {};
244
245 L.Mixin.Events = {
246
247 addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
248 var events = this[key] = this[key] || {},
249 type, i, len;
250
251 // Types can be a map of types/handlers
252 if (typeof types === 'object') {
253 for (type in types) {
254 if (types.hasOwnProperty(type)) {
255 this.addEventListener(type, types[type], fn);
256 }
257 }
258
259 return this;
260 }
261
262 types = L.Util.splitWords(types);
263
264 for (i = 0, len = types.length; i < len; i++) {
265 events[types[i]] = events[types[i]] || [];
266 events[types[i]].push({
267 action: fn,
268 context: context || this
269 });
270 }
271
272 return this;
273 },
274
275 hasEventListeners: function (type) { // (String) -> Boolean
276 return (key in this) && (type in this[key]) && (this[key][type].length > 0);
277 },
278
279 removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
280 var events = this[key],
281 type, i, len, listeners, j;
282
283 if (typeof types === 'object') {
284 for (type in types) {
285 if (types.hasOwnProperty(type)) {
286 this.removeEventListener(type, types[type], fn);
287 }
288 }
289
290 return this;
291 }
292
293 types = L.Util.splitWords(types);
294
295 for (i = 0, len = types.length; i < len; i++) {
296
297 if (this.hasEventListeners(types[i])) {
298 listeners = events[types[i]];
299
300 for (j = listeners.length - 1; j >= 0; j--) {
301 if (
302 (!fn || listeners[j].action === fn) &&
303 (!context || (listeners[j].context === context))
304 ) {
305 listeners.splice(j, 1);
306 }
307 }
308 }
309 }
310
311 return this;
312 },
313
314 fireEvent: function (type, data) { // (String[, Object])
315 if (!this.hasEventListeners(type)) {
316 return this;
317 }
318
319 var event = L.Util.extend({
320 type: type,
321 target: this
322 }, data);
323
324 var listeners = this[key][type].slice();
325
326 for (var i = 0, len = listeners.length; i < len; i++) {
327 listeners[i].action.call(listeners[i].context || this, event);
328 }
329
330 return this;
331 }
332 };
333
334 L.Mixin.Events.on = L.Mixin.Events.addEventListener;
335 L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
336 L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
337
338
339 (function () {
340 var ua = navigator.userAgent.toLowerCase(),
341 ie = !!window.ActiveXObject,
342 ie6 = ie && !window.XMLHttpRequest,
343 webkit = ua.indexOf("webkit") !== -1,
344 gecko = ua.indexOf("gecko") !== -1,
345 //Terrible browser detection to work around a safari / iOS / android browser bug. See TileLayer._addTile and debug/hacks/jitter.html
346 chrome = ua.indexOf("chrome") !== -1,
347 opera = window.opera,
348 android = ua.indexOf("android") !== -1,
349 android23 = ua.search("android [23]") !== -1,
350 mobile = typeof orientation !== undefined + '' ? true : false,
351 doc = document.documentElement,
352 ie3d = ie && ('transition' in doc.style),
353 webkit3d = webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
354 gecko3d = gecko && ('MozPerspective' in doc.style),
355 opera3d = opera && ('OTransition' in doc.style);
356
357 var touch = !window.L_NO_TOUCH && (function () {
358 var startName = 'ontouchstart';
359
360 // WebKit, etc
361 if (startName in doc) {
362 return true;
363 }
364
365 // Firefox/Gecko
366 var div = document.createElement('div'),
367 supported = false;
368
369 if (!div.setAttribute) {
370 return false;
371 }
372 div.setAttribute(startName, 'return;');
373
374 if (typeof div[startName] === 'function') {
375 supported = true;
376 }
377
378 div.removeAttribute(startName);
379 div = null;
380
381 return supported;
382 }());
383
384 L.Browser = {
385 ua: ua,
386 ie: ie,
387 ie6: ie6,
388 webkit: webkit,
389 gecko: gecko,
390 opera: opera,
391 android: android,
392 android23: android23,
393
394 chrome: chrome,
395
396 ie3d: ie3d,
397 webkit3d: webkit3d,
398 gecko3d: gecko3d,
399 opera3d: opera3d,
400 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d),
401
402 mobile: mobile,
403 mobileWebkit: mobile && webkit,
404 mobileWebkit3d: mobile && webkit3d,
405 mobileOpera: mobile && opera,
406
407 touch: touch
408 };
409 }());
410
411
412 /*
413 * L.Point represents a point with x and y coordinates.
414 */
415
416 L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
417 this.x = (round ? Math.round(x) : x);
418 this.y = (round ? Math.round(y) : y);
419 };
420
421 L.Point.prototype = {
422 add: function (point) {
423 return this.clone()._add(L.point(point));
424 },
425
426 _add: function (point) {
427 this.x += point.x;
428 this.y += point.y;
429 return this;
430 },
431
432 subtract: function (point) {
433 return this.clone()._subtract(L.point(point));
434 },
435
436 // destructive subtract (faster)
437 _subtract: function (point) {
438 this.x -= point.x;
439 this.y -= point.y;
440 return this;
441 },
442
443 divideBy: function (num, round) {
444 return new L.Point(this.x / num, this.y / num, round);
445 },
446
447 multiplyBy: function (num, round) {
448 return new L.Point(this.x * num, this.y * num, round);
449 },
450
451 distanceTo: function (point) {
452 point = L.point(point);
453
454 var x = point.x - this.x,
455 y = point.y - this.y;
456
457 return Math.sqrt(x * x + y * y);
458 },
459
460 round: function () {
461 return this.clone()._round();
462 },
463
464 // destructive round
465 _round: function () {
466 this.x = Math.round(this.x);
467 this.y = Math.round(this.y);
468 return this;
469 },
470
471 floor: function () {
472 return this.clone()._floor();
473 },
474
475 _floor: function () {
476 this.x = Math.floor(this.x);
477 this.y = Math.floor(this.y);
478 return this;
479 },
480
481 clone: function () {
482 return new L.Point(this.x, this.y);
483 },
484
485 toString: function () {
486 return 'Point(' +
487 L.Util.formatNum(this.x) + ', ' +
488 L.Util.formatNum(this.y) + ')';
489 }
490 };
491
492 L.point = function (x, y, round) {
493 if (x instanceof L.Point) {
494 return x;
495 }
496 if (x instanceof Array) {
497 return new L.Point(x[0], x[1]);
498 }
499 if (isNaN(x)) {
500 return x;
501 }
502 return new L.Point(x, y, round);
503 };
504
505
506 /*
507 * L.Bounds represents a rectangular area on the screen in pixel coordinates.
508 */
509
510 L.Bounds = L.Class.extend({
511
512 initialize: function (a, b) { //(Point, Point) or Point[]
513 if (!a) { return; }
514
515 var points = b ? [a, b] : a;
516
517 for (var i = 0, len = points.length; i < len; i++) {
518 this.extend(points[i]);
519 }
520 },
521
522 // extend the bounds to contain the given point
523 extend: function (point) { // (Point)
524 point = L.point(point);
525
526 if (!this.min && !this.max) {
527 this.min = point.clone();
528 this.max = point.clone();
529 } else {
530 this.min.x = Math.min(point.x, this.min.x);
531 this.max.x = Math.max(point.x, this.max.x);
532 this.min.y = Math.min(point.y, this.min.y);
533 this.max.y = Math.max(point.y, this.max.y);
534 }
535 return this;
536 },
537
538 getCenter: function (round) { // (Boolean) -> Point
539 return new L.Point(
540 (this.min.x + this.max.x) / 2,
541 (this.min.y + this.max.y) / 2, round);
542 },
543
544 getBottomLeft: function () { // -> Point
545 return new L.Point(this.min.x, this.max.y);
546 },
547
548 getTopRight: function () { // -> Point
549 return new L.Point(this.max.x, this.min.y);
550 },
551
552 contains: function (obj) { // (Bounds) or (Point) -> Boolean
553 var min, max;
554
555 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
556 obj = L.point(obj);
557 } else {
558 obj = L.bounds(obj);
559 }
560
561 if (obj instanceof L.Bounds) {
562 min = obj.min;
563 max = obj.max;
564 } else {
565 min = max = obj;
566 }
567
568 return (min.x >= this.min.x) &&
569 (max.x <= this.max.x) &&
570 (min.y >= this.min.y) &&
571 (max.y <= this.max.y);
572 },
573
574 intersects: function (bounds) { // (Bounds) -> Boolean
575 bounds = L.bounds(bounds);
576
577 var min = this.min,
578 max = this.max,
579 min2 = bounds.min,
580 max2 = bounds.max;
581
582 var xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
583 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
584
585 return xIntersects && yIntersects;
586 }
587
588 });
589
590 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
591 if (!a || a instanceof L.Bounds) {
592 return a;
593 }
594 return new L.Bounds(a, b);
595 };
596
597
598 /*
599 * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
600 */
601
602 L.Transformation = L.Class.extend({
603 initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
604 this._a = a;
605 this._b = b;
606 this._c = c;
607 this._d = d;
608 },
609
610 transform: function (point, scale) {
611 return this._transform(point.clone(), scale);
612 },
613
614 // destructive transform (faster)
615 _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
616 scale = scale || 1;
617 point.x = scale * (this._a * point.x + this._b);
618 point.y = scale * (this._c * point.y + this._d);
619 return point;
620 },
621
622 untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
623 scale = scale || 1;
624 return new L.Point(
625 (point.x / scale - this._b) / this._a,
626 (point.y / scale - this._d) / this._c);
627 }
628 });
629
630
631 /*
632 * L.DomUtil contains various utility functions for working with DOM
633 */
634
635 L.DomUtil = {
636 get: function (id) {
637 return (typeof id === 'string' ? document.getElementById(id) : id);
638 },
639
640 getStyle: function (el, style) {
641 var value = el.style[style];
642 if (!value && el.currentStyle) {
643 value = el.currentStyle[style];
644 }
645 if (!value || value === 'auto') {
646 var css = document.defaultView.getComputedStyle(el, null);
647 value = css ? css[style] : null;
648 }
649 return (value === 'auto' ? null : value);
650 },
651
652 getViewportOffset: function (element) {
653 var top = 0,
654 left = 0,
655 el = element,
656 docBody = document.body;
657
658 do {
659 top += el.offsetTop || 0;
660 left += el.offsetLeft || 0;
661
662 if (el.offsetParent === docBody &&
663 L.DomUtil.getStyle(el, 'position') === 'absolute') {
664 break;
665 }
666 if (L.DomUtil.getStyle(el, 'position') === 'fixed') {
667 top += docBody.scrollTop || 0;
668 left += docBody.scrollLeft || 0;
669 break;
670 }
671
672 el = el.offsetParent;
673 } while (el);
674
675 el = element;
676
677 do {
678 if (el === docBody) {
679 break;
680 }
681
682 top -= el.scrollTop || 0;
683 left -= el.scrollLeft || 0;
684
685 el = el.parentNode;
686 } while (el);
687
688 return new L.Point(left, top);
689 },
690
691 create: function (tagName, className, container) {
692 var el = document.createElement(tagName);
693 el.className = className;
694 if (container) {
695 container.appendChild(el);
696 }
697 return el;
698 },
699
700 disableTextSelection: function () {
701 if (document.selection && document.selection.empty) {
702 document.selection.empty();
703 }
704 if (!this._onselectstart) {
705 this._onselectstart = document.onselectstart;
706 document.onselectstart = L.Util.falseFn;
707 }
708 },
709
710 enableTextSelection: function () {
711 document.onselectstart = this._onselectstart;
712 this._onselectstart = null;
713 },
714
715 hasClass: function (el, name) {
716 return (el.className.length > 0) &&
717 new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
718 },
719
720 addClass: function (el, name) {
721 if (!L.DomUtil.hasClass(el, name)) {
722 el.className += (el.className ? ' ' : '') + name;
723 }
724 },
725
726 removeClass: function (el, name) {
727 function replaceFn(w, match) {
728 if (match === name) {
729 return '';
730 }
731 return w;
732 }
733 el.className = el.className
734 .replace(/(\S+)\s*/g, replaceFn)
735 .replace(/(^\s+|\s+$)/, '');
736 },
737
738 setOpacity: function (el, value) {
739
740 if ('opacity' in el.style) {
741 el.style.opacity = value;
742
743 } else if (L.Browser.ie) {
744
745 var filter = false,
746 filterName = 'DXImageTransform.Microsoft.Alpha';
747
748 // filters collection throws an error if we try to retrieve a filter that doesn't exist
749 try { filter = el.filters.item(filterName); } catch (e) {}
750
751 value = Math.round(value * 100);
752
753 if (filter) {
754 filter.Enabled = (value === 100);
755 filter.Opacity = value;
756 } else {
757 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
758 }
759 }
760 },
761
762 testProp: function (props) {
763 var style = document.documentElement.style;
764
765 for (var i = 0; i < props.length; i++) {
766 if (props[i] in style) {
767 return props[i];
768 }
769 }
770 return false;
771 },
772
773 getTranslateString: function (point) {
774 // On webkit browsers (Chrome/Safari/MobileSafari/Android) using translate3d instead of translate
775 // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
776 // (same speed either way), Opera 12 doesn't support translate3d
777
778 var is3d = L.Browser.webkit3d,
779 open = 'translate' + (is3d ? '3d' : '') + '(',
780 close = (is3d ? ',0' : '') + ')';
781
782 return open + point.x + 'px,' + point.y + 'px' + close;
783 },
784
785 getScaleString: function (scale, origin) {
786 var preTranslateStr = L.DomUtil.getTranslateString(origin),
787 scaleStr = ' scale(' + scale + ') ',
788 postTranslateStr = L.DomUtil.getTranslateString(origin.multiplyBy(-1));
789
790 return preTranslateStr + scaleStr + postTranslateStr;
791 },
792
793 setPosition: function (el, point, disable3D) {
794 el._leaflet_pos = point;
795 if (!disable3D && L.Browser.any3d) {
796 el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
797
798 // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
799 if (L.Browser.mobileWebkit3d) {
800 el.style.WebkitBackfaceVisibility = 'hidden';
801 }
802 } else {
803 el.style.left = point.x + 'px';
804 el.style.top = point.y + 'px';
805 }
806 },
807
808 getPosition: function (el) {
809 return el._leaflet_pos;
810 }
811 };
812
813 L.Util.extend(L.DomUtil, {
814 TRANSITION: L.DomUtil.testProp(['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']),
815 TRANSFORM: L.DomUtil.testProp(['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform'])
816 });
817
818
819 /*
820 CM.LatLng represents a geographical point with latitude and longtitude coordinates.
821 */
822
823 L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
824 var lat = parseFloat(rawLat),
825 lng = parseFloat(rawLng);
826
827 if (isNaN(lat) || isNaN(lng)) {
828 throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
829 }
830
831 if (noWrap !== true) {
832 lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
833 lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
834 }
835
836 this.lat = lat;
837 this.lng = lng;
838 };
839
840 L.Util.extend(L.LatLng, {
841 DEG_TO_RAD: Math.PI / 180,
842 RAD_TO_DEG: 180 / Math.PI,
843 MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
844 });
845
846 L.LatLng.prototype = {
847 equals: function (obj) { // (LatLng) -> Boolean
848 if (!obj) { return false; }
849
850 obj = L.latLng(obj);
851
852 var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
853 return margin <= L.LatLng.MAX_MARGIN;
854 },
855
856 toString: function () { // -> String
857 return 'LatLng(' +
858 L.Util.formatNum(this.lat) + ', ' +
859 L.Util.formatNum(this.lng) + ')';
860 },
861
862 // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
863 distanceTo: function (other) { // (LatLng) -> Number
864 other = L.latLng(other);
865
866 var R = 6378137, // earth radius in meters
867 d2r = L.LatLng.DEG_TO_RAD,
868 dLat = (other.lat - this.lat) * d2r,
869 dLon = (other.lng - this.lng) * d2r,
870 lat1 = this.lat * d2r,
871 lat2 = other.lat * d2r,
872 sin1 = Math.sin(dLat / 2),
873 sin2 = Math.sin(dLon / 2);
874
875 var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
876
877 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
878 }
879 };
880
881 L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
882 if (a instanceof L.LatLng) {
883 return a;
884 }
885 if (a instanceof Array) {
886 return new L.LatLng(a[0], a[1]);
887 }
888 if (isNaN(a)) {
889 return a;
890 }
891 return new L.LatLng(a, b, c);
892 };
893
894
895 /*
896 * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
897 */
898
899 L.LatLngBounds = L.Class.extend({
900 initialize: function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
901 if (!southWest) { return; }
902
903 var latlngs = northEast ? [southWest, northEast] : southWest;
904
905 for (var i = 0, len = latlngs.length; i < len; i++) {
906 this.extend(latlngs[i]);
907 }
908 },
909
910 // extend the bounds to contain the given point or bounds
911 extend: function (obj) { // (LatLng) or (LatLngBounds)
912 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
913 obj = L.latLng(obj);
914 } else {
915 obj = L.latLngBounds(obj);
916 }
917
918 if (obj instanceof L.LatLng) {
919 if (!this._southWest && !this._northEast) {
920 this._southWest = new L.LatLng(obj.lat, obj.lng, true);
921 this._northEast = new L.LatLng(obj.lat, obj.lng, true);
922 } else {
923 this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
924 this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
925
926 this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
927 this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
928 }
929 } else if (obj instanceof L.LatLngBounds) {
930 this.extend(obj._southWest);
931 this.extend(obj._northEast);
932 }
933 return this;
934 },
935
936 // extend the bounds by a percentage
937 pad: function (bufferRatio) { // (Number) -> LatLngBounds
938 var sw = this._southWest,
939 ne = this._northEast,
940 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
941 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
942
943 return new L.LatLngBounds(
944 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
945 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
946 },
947
948 getCenter: function () { // -> LatLng
949 return new L.LatLng(
950 (this._southWest.lat + this._northEast.lat) / 2,
951 (this._southWest.lng + this._northEast.lng) / 2);
952 },
953
954 getSouthWest: function () {
955 return this._southWest;
956 },
957
958 getNorthEast: function () {
959 return this._northEast;
960 },
961
962 getNorthWest: function () {
963 return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
964 },
965
966 getSouthEast: function () {
967 return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
968 },
969
970 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
971 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
972 obj = L.latLng(obj);
973 } else {
974 obj = L.latLngBounds(obj);
975 }
976
977 var sw = this._southWest,
978 ne = this._northEast,
979 sw2, ne2;
980
981 if (obj instanceof L.LatLngBounds) {
982 sw2 = obj.getSouthWest();
983 ne2 = obj.getNorthEast();
984 } else {
985 sw2 = ne2 = obj;
986 }
987
988 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
989 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
990 },
991
992 intersects: function (bounds) { // (LatLngBounds)
993 bounds = L.latLngBounds(bounds);
994
995 var sw = this._southWest,
996 ne = this._northEast,
997 sw2 = bounds.getSouthWest(),
998 ne2 = bounds.getNorthEast();
999
1000 var latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1001 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1002
1003 return latIntersects && lngIntersects;
1004 },
1005
1006 toBBoxString: function () {
1007 var sw = this._southWest,
1008 ne = this._northEast;
1009 return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
1010 },
1011
1012 equals: function (bounds) { // (LatLngBounds)
1013 if (!bounds) { return false; }
1014
1015 bounds = L.latLngBounds(bounds);
1016
1017 return this._southWest.equals(bounds.getSouthWest()) &&
1018 this._northEast.equals(bounds.getNorthEast());
1019 }
1020 });
1021
1022 //TODO International date line?
1023
1024 L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
1025 if (!a || a instanceof L.LatLngBounds) {
1026 return a;
1027 }
1028 return new L.LatLngBounds(a, b);
1029 };
1030
1031
1032 /*
1033 * L.Projection contains various geographical projections used by CRS classes.
1034 */
1035
1036 L.Projection = {};
1037
1038
1039
1040 L.Projection.SphericalMercator = {
1041 MAX_LATITUDE: 85.0511287798,
1042
1043 project: function (latlng) { // (LatLng) -> Point
1044 var d = L.LatLng.DEG_TO_RAD,
1045 max = this.MAX_LATITUDE,
1046 lat = Math.max(Math.min(max, latlng.lat), -max),
1047 x = latlng.lng * d,
1048 y = lat * d;
1049 y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
1050
1051 return new L.Point(x, y);
1052 },
1053
1054 unproject: function (point) { // (Point, Boolean) -> LatLng
1055 var d = L.LatLng.RAD_TO_DEG,
1056 lng = point.x * d,
1057 lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
1058
1059 // TODO refactor LatLng wrapping
1060 return new L.LatLng(lat, lng, true);
1061 }
1062 };
1063
1064
1065
1066 L.Projection.LonLat = {
1067 project: function (latlng) {
1068 return new L.Point(latlng.lng, latlng.lat);
1069 },
1070
1071 unproject: function (point) {
1072 return new L.LatLng(point.y, point.x, true);
1073 }
1074 };
1075
1076
1077
1078 L.CRS = {
1079 latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
1080 var projectedPoint = this.projection.project(latlng),
1081 scale = this.scale(zoom);
1082
1083 return this.transformation._transform(projectedPoint, scale);
1084 },
1085
1086 pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
1087 var scale = this.scale(zoom),
1088 untransformedPoint = this.transformation.untransform(point, scale);
1089
1090 return this.projection.unproject(untransformedPoint);
1091 },
1092
1093 project: function (latlng) {
1094 return this.projection.project(latlng);
1095 },
1096
1097 scale: function (zoom) {
1098 return 256 * Math.pow(2, zoom);
1099 }
1100 };
1101
1102
1103
1104 L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, {
1105 code: 'EPSG:3857',
1106
1107 projection: L.Projection.SphericalMercator,
1108 transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
1109
1110 project: function (latlng) { // (LatLng) -> Point
1111 var projectedPoint = this.projection.project(latlng),
1112 earthRadius = 6378137;
1113 return projectedPoint.multiplyBy(earthRadius);
1114 }
1115 });
1116
1117 L.CRS.EPSG900913 = L.Util.extend({}, L.CRS.EPSG3857, {
1118 code: 'EPSG:900913'
1119 });
1120
1121
1122
1123 L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, {
1124 code: 'EPSG:4326',
1125
1126 projection: L.Projection.LonLat,
1127 transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
1128 });
1129
1130
1131 /*
1132 * L.Map is the central class of the API - it is used to create a map.
1133 */
1134
1135 L.Map = L.Class.extend({
1136
1137 includes: L.Mixin.Events,
1138
1139 options: {
1140 crs: L.CRS.EPSG3857,
1141
1142 /*
1143 center: LatLng,
1144 zoom: Number,
1145 layers: Array,
1146 */
1147
1148 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
1149 trackResize: true,
1150 markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
1151 },
1152
1153 initialize: function (id, options) { // (HTMLElement or String, Object)
1154 options = L.Util.setOptions(this, options);
1155
1156 this._initContainer(id);
1157 this._initLayout();
1158 this._initHooks();
1159 this._initEvents();
1160
1161 if (options.maxBounds) {
1162 this.setMaxBounds(options.maxBounds);
1163 }
1164
1165 if (options.center && options.zoom !== undefined) {
1166 this.setView(L.latLng(options.center), options.zoom, true);
1167 }
1168
1169 this._initLayers(options.layers);
1170 },
1171
1172
1173 // public methods that modify map state
1174
1175 // replaced by animation-powered implementation in Map.PanAnimation.js
1176 setView: function (center, zoom) {
1177 this._resetView(L.latLng(center), this._limitZoom(zoom));
1178 return this;
1179 },
1180
1181 setZoom: function (zoom) { // (Number)
1182 return this.setView(this.getCenter(), zoom);
1183 },
1184
1185 zoomIn: function () {
1186 return this.setZoom(this._zoom + 1);
1187 },
1188
1189 zoomOut: function () {
1190 return this.setZoom(this._zoom - 1);
1191 },
1192
1193 fitBounds: function (bounds) { // (LatLngBounds)
1194 var zoom = this.getBoundsZoom(bounds);
1195 return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
1196 },
1197
1198 fitWorld: function () {
1199 var sw = new L.LatLng(-60, -170),
1200 ne = new L.LatLng(85, 179);
1201
1202 return this.fitBounds(new L.LatLngBounds(sw, ne));
1203 },
1204
1205 panTo: function (center) { // (LatLng)
1206 return this.setView(center, this._zoom);
1207 },
1208
1209 panBy: function (offset) { // (Point)
1210 // replaced with animated panBy in Map.Animation.js
1211 this.fire('movestart');
1212
1213 this._rawPanBy(L.point(offset));
1214
1215 this.fire('move');
1216 return this.fire('moveend');
1217 },
1218
1219 setMaxBounds: function (bounds) {
1220 bounds = L.latLngBounds(bounds);
1221
1222 this.options.maxBounds = bounds;
1223
1224 if (!bounds) {
1225 this._boundsMinZoom = null;
1226 return this;
1227 }
1228
1229 var minZoom = this.getBoundsZoom(bounds, true);
1230
1231 this._boundsMinZoom = minZoom;
1232
1233 if (this._loaded) {
1234 if (this._zoom < minZoom) {
1235 this.setView(bounds.getCenter(), minZoom);
1236 } else {
1237 this.panInsideBounds(bounds);
1238 }
1239 }
1240
1241 return this;
1242 },
1243
1244 panInsideBounds: function (bounds) {
1245 bounds = L.latLngBounds(bounds);
1246
1247 var viewBounds = this.getBounds(),
1248 viewSw = this.project(viewBounds.getSouthWest()),
1249 viewNe = this.project(viewBounds.getNorthEast()),
1250 sw = this.project(bounds.getSouthWest()),
1251 ne = this.project(bounds.getNorthEast()),
1252 dx = 0,
1253 dy = 0;
1254
1255 if (viewNe.y < ne.y) { // north
1256 dy = ne.y - viewNe.y;
1257 }
1258 if (viewNe.x > ne.x) { // east
1259 dx = ne.x - viewNe.x;
1260 }
1261 if (viewSw.y > sw.y) { // south
1262 dy = sw.y - viewSw.y;
1263 }
1264 if (viewSw.x < sw.x) { // west
1265 dx = sw.x - viewSw.x;
1266 }
1267
1268 return this.panBy(new L.Point(dx, dy, true));
1269 },
1270
1271 addLayer: function (layer) {
1272 // TODO method is too big, refactor
1273
1274 var id = L.Util.stamp(layer);
1275
1276 if (this._layers[id]) { return this; }
1277
1278 this._layers[id] = layer;
1279
1280 // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
1281 if (layer.options && !isNaN(layer.options.maxZoom)) {
1282 this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
1283 }
1284 if (layer.options && !isNaN(layer.options.minZoom)) {
1285 this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
1286 }
1287
1288 // TODO looks ugly, refactor!!!
1289 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1290 this._tileLayersNum++;
1291 this._tileLayersToLoad++;
1292 layer.on('load', this._onTileLayerLoad, this);
1293 }
1294
1295 var onMapLoad = function () {
1296 layer.onAdd(this);
1297 this.fire('layeradd', {layer: layer});
1298 };
1299
1300 if (this._loaded) {
1301 onMapLoad.call(this);
1302 } else {
1303 this.on('load', onMapLoad, this);
1304 }
1305
1306 return this;
1307 },
1308
1309 removeLayer: function (layer) {
1310 var id = L.Util.stamp(layer);
1311
1312 if (!this._layers[id]) { return; }
1313
1314 layer.onRemove(this);
1315
1316 delete this._layers[id];
1317
1318 // TODO looks ugly, refactor
1319 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1320 this._tileLayersNum--;
1321 this._tileLayersToLoad--;
1322 layer.off('load', this._onTileLayerLoad, this);
1323 }
1324
1325 return this.fire('layerremove', {layer: layer});
1326 },
1327
1328 hasLayer: function (layer) {
1329 var id = L.Util.stamp(layer);
1330 return this._layers.hasOwnProperty(id);
1331 },
1332
1333 invalidateSize: function () {
1334 var oldSize = this.getSize();
1335
1336 this._sizeChanged = true;
1337
1338 if (this.options.maxBounds) {
1339 this.setMaxBounds(this.options.maxBounds);
1340 }
1341
1342 if (!this._loaded) { return this; }
1343
1344 var offset = oldSize.subtract(this.getSize()).divideBy(2, true);
1345 this._rawPanBy(offset);
1346
1347 this.fire('move');
1348
1349 clearTimeout(this._sizeTimer);
1350 this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);
1351
1352 return this;
1353 },
1354
1355 // TODO handler.addTo
1356 addHandler: function (name, HandlerClass) {
1357 if (!HandlerClass) { return; }
1358
1359 this[name] = new HandlerClass(this);
1360
1361 if (this.options[name]) {
1362 this[name].enable();
1363 }
1364
1365 return this;
1366 },
1367
1368
1369 // public methods for getting map state
1370
1371 getCenter: function () { // (Boolean) -> LatLng
1372 return this.layerPointToLatLng(this._getCenterLayerPoint());
1373 },
1374
1375 getZoom: function () {
1376 return this._zoom;
1377 },
1378
1379 getBounds: function () {
1380 var bounds = this.getPixelBounds(),
1381 sw = this.unproject(bounds.getBottomLeft()),
1382 ne = this.unproject(bounds.getTopRight());
1383
1384 return new L.LatLngBounds(sw, ne);
1385 },
1386
1387 getMinZoom: function () {
1388 var z1 = this.options.minZoom || 0,
1389 z2 = this._layersMinZoom || 0,
1390 z3 = this._boundsMinZoom || 0;
1391
1392 return Math.max(z1, z2, z3);
1393 },
1394
1395 getMaxZoom: function () {
1396 var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
1397 z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom;
1398
1399 return Math.min(z1, z2);
1400 },
1401
1402 getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
1403 bounds = L.latLngBounds(bounds);
1404
1405 var size = this.getSize(),
1406 zoom = this.options.minZoom || 0,
1407 maxZoom = this.getMaxZoom(),
1408 ne = bounds.getNorthEast(),
1409 sw = bounds.getSouthWest(),
1410 boundsSize,
1411 nePoint,
1412 swPoint,
1413 zoomNotFound = true;
1414
1415 if (inside) {
1416 zoom--;
1417 }
1418
1419 do {
1420 zoom++;
1421 nePoint = this.project(ne, zoom);
1422 swPoint = this.project(sw, zoom);
1423 boundsSize = new L.Point(Math.abs(nePoint.x - swPoint.x), Math.abs(swPoint.y - nePoint.y));
1424
1425 if (!inside) {
1426 zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
1427 } else {
1428 zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
1429 }
1430 } while (zoomNotFound && zoom <= maxZoom);
1431
1432 if (zoomNotFound && inside) {
1433 return null;
1434 }
1435
1436 return inside ? zoom : zoom - 1;
1437 },
1438
1439 getSize: function () {
1440 if (!this._size || this._sizeChanged) {
1441 this._size = new L.Point(
1442 this._container.clientWidth,
1443 this._container.clientHeight);
1444
1445 this._sizeChanged = false;
1446 }
1447 return this._size;
1448 },
1449
1450 getPixelBounds: function () {
1451 var topLeftPoint = this._getTopLeftPoint();
1452 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
1453 },
1454
1455 getPixelOrigin: function () {
1456 return this._initialTopLeftPoint;
1457 },
1458
1459 getPanes: function () {
1460 return this._panes;
1461 },
1462
1463 getContainer: function () {
1464 return this._container;
1465 },
1466
1467
1468 // TODO replace with universal implementation after refactoring projections
1469
1470 getZoomScale: function (toZoom) {
1471 var crs = this.options.crs;
1472 return crs.scale(toZoom) / crs.scale(this._zoom);
1473 },
1474
1475 getScaleZoom: function (scale) {
1476 return this._zoom + (Math.log(scale) / Math.LN2);
1477 },
1478
1479
1480 // conversion methods
1481
1482 project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
1483 zoom = zoom === undefined ? this._zoom : zoom;
1484 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
1485 },
1486
1487 unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
1488 zoom = zoom === undefined ? this._zoom : zoom;
1489 return this.options.crs.pointToLatLng(L.point(point), zoom);
1490 },
1491
1492 layerPointToLatLng: function (point) { // (Point)
1493 var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
1494 return this.unproject(projectedPoint);
1495 },
1496
1497 latLngToLayerPoint: function (latlng) { // (LatLng)
1498 var projectedPoint = this.project(L.latLng(latlng))._round();
1499 return projectedPoint._subtract(this._initialTopLeftPoint);
1500 },
1501
1502 containerPointToLayerPoint: function (point) { // (Point)
1503 return L.point(point).subtract(this._getMapPanePos());
1504 },
1505
1506 layerPointToContainerPoint: function (point) { // (Point)
1507 return L.point(point).add(this._getMapPanePos());
1508 },
1509
1510 containerPointToLatLng: function (point) {
1511 var layerPoint = this.containerPointToLayerPoint(L.point(point));
1512 return this.layerPointToLatLng(layerPoint);
1513 },
1514
1515 latLngToContainerPoint: function (latlng) {
1516 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
1517 },
1518
1519 mouseEventToContainerPoint: function (e) { // (MouseEvent)
1520 return L.DomEvent.getMousePosition(e, this._container);
1521 },
1522
1523 mouseEventToLayerPoint: function (e) { // (MouseEvent)
1524 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1525 },
1526
1527 mouseEventToLatLng: function (e) { // (MouseEvent)
1528 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1529 },
1530
1531
1532 // map initialization methods
1533
1534 _initContainer: function (id) {
1535 var container = this._container = L.DomUtil.get(id);
1536
1537 if (container._leaflet) {
1538 throw new Error("Map container is already initialized.");
1539 }
1540
1541 container._leaflet = true;
1542 },
1543
1544 _initLayout: function () {
1545 var container = this._container;
1546
1547 container.innerHTML = '';
1548 L.DomUtil.addClass(container, 'leaflet-container');
1549
1550 if (L.Browser.touch) {
1551 L.DomUtil.addClass(container, 'leaflet-touch');
1552 }
1553
1554 if (this.options.fadeAnimation) {
1555 L.DomUtil.addClass(container, 'leaflet-fade-anim');
1556 }
1557
1558 var position = L.DomUtil.getStyle(container, 'position');
1559
1560 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
1561 container.style.position = 'relative';
1562 }
1563
1564 this._initPanes();
1565
1566 if (this._initControlPos) {
1567 this._initControlPos();
1568 }
1569 },
1570
1571 _initPanes: function () {
1572 var panes = this._panes = {};
1573
1574 this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
1575
1576 this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
1577 this._objectsPane = panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
1578
1579 panes.shadowPane = this._createPane('leaflet-shadow-pane');
1580 panes.overlayPane = this._createPane('leaflet-overlay-pane');
1581 panes.markerPane = this._createPane('leaflet-marker-pane');
1582 panes.popupPane = this._createPane('leaflet-popup-pane');
1583
1584 var zoomHide = ' leaflet-zoom-hide';
1585
1586 if (!this.options.markerZoomAnimation) {
1587 L.DomUtil.addClass(panes.markerPane, zoomHide);
1588 L.DomUtil.addClass(panes.shadowPane, zoomHide);
1589 L.DomUtil.addClass(panes.popupPane, zoomHide);
1590 }
1591 },
1592
1593 _createPane: function (className, container) {
1594 return L.DomUtil.create('div', className, container || this._objectsPane);
1595 },
1596
1597 _initializers: [],
1598
1599 _initHooks: function () {
1600 var i, len;
1601 for (i = 0, len = this._initializers.length; i < len; i++) {
1602 this._initializers[i].call(this);
1603 }
1604 },
1605
1606 _initLayers: function (layers) {
1607 layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
1608
1609 this._layers = {};
1610 this._tileLayersNum = 0;
1611
1612 var i, len;
1613
1614 for (i = 0, len = layers.length; i < len; i++) {
1615 this.addLayer(layers[i]);
1616 }
1617 },
1618
1619
1620 // private methods that modify map state
1621
1622 _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
1623
1624 var zoomChanged = (this._zoom !== zoom);
1625
1626 if (!afterZoomAnim) {
1627 this.fire('movestart');
1628
1629 if (zoomChanged) {
1630 this.fire('zoomstart');
1631 }
1632 }
1633
1634 this._zoom = zoom;
1635
1636 this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
1637
1638 if (!preserveMapOffset) {
1639 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
1640 } else {
1641 this._initialTopLeftPoint._add(this._getMapPanePos());
1642 }
1643
1644 this._tileLayersToLoad = this._tileLayersNum;
1645
1646 this.fire('viewreset', {hard: !preserveMapOffset});
1647
1648 this.fire('move');
1649
1650 if (zoomChanged || afterZoomAnim) {
1651 this.fire('zoomend');
1652 }
1653
1654 this.fire('moveend', {hard: !preserveMapOffset});
1655
1656 if (!this._loaded) {
1657 this._loaded = true;
1658 this.fire('load');
1659 }
1660 },
1661
1662 _rawPanBy: function (offset) {
1663 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
1664 },
1665
1666
1667 // map events
1668
1669 _initEvents: function () {
1670 if (!L.DomEvent) { return; }
1671
1672 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
1673
1674 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'],
1675 i, len;
1676
1677 for (i = 0, len = events.length; i < len; i++) {
1678 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
1679 }
1680
1681 if (this.options.trackResize) {
1682 L.DomEvent.on(window, 'resize', this._onResize, this);
1683 }
1684 },
1685
1686 _onResize: function () {
1687 L.Util.cancelAnimFrame(this._resizeRequest);
1688 this._resizeRequest = L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
1689 },
1690
1691 _onMouseClick: function (e) {
1692 if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
1693
1694 this.fire('preclick');
1695 this._fireMouseEvent(e);
1696 },
1697
1698 _fireMouseEvent: function (e) {
1699 if (!this._loaded) { return; }
1700
1701 var type = e.type;
1702
1703 type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
1704
1705 if (!this.hasEventListeners(type)) { return; }
1706
1707 if (type === 'contextmenu') {
1708 L.DomEvent.preventDefault(e);
1709 }
1710
1711 var containerPoint = this.mouseEventToContainerPoint(e),
1712 layerPoint = this.containerPointToLayerPoint(containerPoint),
1713 latlng = this.layerPointToLatLng(layerPoint);
1714
1715 this.fire(type, {
1716 latlng: latlng,
1717 layerPoint: layerPoint,
1718 containerPoint: containerPoint,
1719 originalEvent: e
1720 });
1721 },
1722
1723 _onTileLayerLoad: function () {
1724 // TODO super-ugly, refactor!!!
1725 // clear scaled tiles after all new tiles are loaded (for performance)
1726 this._tileLayersToLoad--;
1727 if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
1728 clearTimeout(this._clearTileBgTimer);
1729 this._clearTileBgTimer = setTimeout(L.Util.bind(this._clearTileBg, this), 500);
1730 }
1731 },
1732
1733
1734 // private methods for getting map state
1735
1736 _getMapPanePos: function () {
1737 return L.DomUtil.getPosition(this._mapPane);
1738 },
1739
1740 _getTopLeftPoint: function () {
1741 if (!this._loaded) {
1742 throw new Error('Set map center and zoom first.');
1743 }
1744
1745 return this._initialTopLeftPoint.subtract(this._getMapPanePos());
1746 },
1747
1748 _getNewTopLeftPoint: function (center, zoom) {
1749 var viewHalf = this.getSize().divideBy(2);
1750 // TODO round on display, not calculation to increase precision?
1751 return this.project(center, zoom)._subtract(viewHalf)._round();
1752 },
1753
1754 _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
1755 var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
1756 return this.project(latlng, newZoom)._subtract(topLeft);
1757 },
1758
1759 _getCenterLayerPoint: function () {
1760 return this.containerPointToLayerPoint(this.getSize().divideBy(2));
1761 },
1762
1763 _getCenterOffset: function (center) {
1764 return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
1765 },
1766
1767 _limitZoom: function (zoom) {
1768 var min = this.getMinZoom(),
1769 max = this.getMaxZoom();
1770
1771 return Math.max(min, Math.min(max, zoom));
1772 }
1773 });
1774
1775 L.Map.addInitHook = function (fn) {
1776 var args = Array.prototype.slice.call(arguments, 1);
1777
1778 var init = typeof fn === 'function' ? fn : function () {
1779 this[fn].apply(this, args);
1780 };
1781
1782 this.prototype._initializers.push(init);
1783 };
1784
1785 L.map = function (id, options) {
1786 return new L.Map(id, options);
1787 };
1788
1789
1790
1791 L.Projection.Mercator = {
1792 MAX_LATITUDE: 85.0840591556,
1793
1794 R_MINOR: 6356752.3142,
1795 R_MAJOR: 6378137,
1796
1797 project: function (latlng) { // (LatLng) -> Point
1798 var d = L.LatLng.DEG_TO_RAD,
1799 max = this.MAX_LATITUDE,
1800 lat = Math.max(Math.min(max, latlng.lat), -max),
1801 r = this.R_MAJOR,
1802 r2 = this.R_MINOR,
1803 x = latlng.lng * d * r,
1804 y = lat * d,
1805 tmp = r2 / r,
1806 eccent = Math.sqrt(1.0 - tmp * tmp),
1807 con = eccent * Math.sin(y);
1808
1809 con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
1810
1811 var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
1812 y = -r2 * Math.log(ts);
1813
1814 return new L.Point(x, y);
1815 },
1816
1817 unproject: function (point) { // (Point, Boolean) -> LatLng
1818 var d = L.LatLng.RAD_TO_DEG,
1819 r = this.R_MAJOR,
1820 r2 = this.R_MINOR,
1821 lng = point.x * d / r,
1822 tmp = r2 / r,
1823 eccent = Math.sqrt(1 - (tmp * tmp)),
1824 ts = Math.exp(- point.y / r2),
1825 phi = (Math.PI / 2) - 2 * Math.atan(ts),
1826 numIter = 15,
1827 tol = 1e-7,
1828 i = numIter,
1829 dphi = 0.1,
1830 con;
1831
1832 while ((Math.abs(dphi) > tol) && (--i > 0)) {
1833 con = eccent * Math.sin(phi);
1834 dphi = (Math.PI / 2) - 2 * Math.atan(ts * Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
1835 phi += dphi;
1836 }
1837
1838 return new L.LatLng(phi * d, lng, true);
1839 }
1840 };
1841
1842
1843
1844 L.CRS.EPSG3395 = L.Util.extend({}, L.CRS, {
1845 code: 'EPSG:3395',
1846
1847 projection: L.Projection.Mercator,
1848
1849 transformation: (function () {
1850 var m = L.Projection.Mercator,
1851 r = m.R_MAJOR,
1852 r2 = m.R_MINOR;
1853
1854 return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
1855 }())
1856 });
1857
1858
1859 /*
1860 * L.TileLayer is used for standard xyz-numbered tile layers.
1861 */
1862
1863 L.TileLayer = L.Class.extend({
1864 includes: L.Mixin.Events,
1865
1866 options: {
1867 minZoom: 0,
1868 maxZoom: 18,
1869 tileSize: 256,
1870 subdomains: 'abc',
1871 errorTileUrl: '',
1872 attribution: '',
1873 zoomOffset: 0,
1874 opacity: 1,
1875 /* (undefined works too)
1876 zIndex: null,
1877 tms: false,
1878 continuousWorld: false,
1879 noWrap: false,
1880 zoomReverse: false,
1881 detectRetina: false,
1882 reuseTiles: false,
1883 */
1884 unloadInvisibleTiles: L.Browser.mobile,
1885 updateWhenIdle: L.Browser.mobile
1886 },
1887
1888 initialize: function (url, options) {
1889 options = L.Util.setOptions(this, options);
1890
1891 // detecting retina displays, adjusting tileSize and zoom levels
1892 if (options.detectRetina && window.devicePixelRatio > 1 && options.maxZoom > 0) {
1893
1894 options.tileSize = Math.floor(options.tileSize / 2);
1895 options.zoomOffset++;
1896
1897 if (options.minZoom > 0) {
1898 options.minZoom--;
1899 }
1900 this.options.maxZoom--;
1901 }
1902
1903 this._url = url;
1904
1905 var subdomains = this.options.subdomains;
1906
1907 if (typeof subdomains === 'string') {
1908 this.options.subdomains = subdomains.split('');
1909 }
1910 },
1911
1912 onAdd: function (map) {
1913 this._map = map;
1914
1915 // create a container div for tiles
1916 this._initContainer();
1917
1918 // create an image to clone for tiles
1919 this._createTileProto();
1920
1921 // set up events
1922 map.on({
1923 'viewreset': this._resetCallback,
1924 'moveend': this._update
1925 }, this);
1926
1927 if (!this.options.updateWhenIdle) {
1928 this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
1929 map.on('move', this._limitedUpdate, this);
1930 }
1931
1932 this._reset();
1933 this._update();
1934 },
1935
1936 addTo: function (map) {
1937 map.addLayer(this);
1938 return this;
1939 },
1940
1941 onRemove: function (map) {
1942 map._panes.tilePane.removeChild(this._container);
1943
1944 map.off({
1945 'viewreset': this._resetCallback,
1946 'moveend': this._update
1947 }, this);
1948
1949 if (!this.options.updateWhenIdle) {
1950 map.off('move', this._limitedUpdate, this);
1951 }
1952
1953 this._container = null;
1954 this._map = null;
1955 },
1956
1957 bringToFront: function () {
1958 var pane = this._map._panes.tilePane;
1959
1960 if (this._container) {
1961 pane.appendChild(this._container);
1962 this._setAutoZIndex(pane, Math.max);
1963 }
1964
1965 return this;
1966 },
1967
1968 bringToBack: function () {
1969 var pane = this._map._panes.tilePane;
1970
1971 if (this._container) {
1972 pane.insertBefore(this._container, pane.firstChild);
1973 this._setAutoZIndex(pane, Math.min);
1974 }
1975
1976 return this;
1977 },
1978
1979 getAttribution: function () {
1980 return this.options.attribution;
1981 },
1982
1983 setOpacity: function (opacity) {
1984 this.options.opacity = opacity;
1985
1986 if (this._map) {
1987 this._updateOpacity();
1988 }
1989
1990 return this;
1991 },
1992
1993 setZIndex: function (zIndex) {
1994 this.options.zIndex = zIndex;
1995 this._updateZIndex();
1996
1997 return this;
1998 },
1999
2000 setUrl: function (url, noRedraw) {
2001 this._url = url;
2002
2003 if (!noRedraw) {
2004 this.redraw();
2005 }
2006
2007 return this;
2008 },
2009
2010 redraw: function () {
2011 if (this._map) {
2012 this._map._panes.tilePane.empty = false;
2013 this._reset(true);
2014 this._update();
2015 }
2016 return this;
2017 },
2018
2019 _updateZIndex: function () {
2020 if (this._container && this.options.zIndex !== undefined) {
2021 this._container.style.zIndex = this.options.zIndex;
2022 }
2023 },
2024
2025 _setAutoZIndex: function (pane, compare) {
2026
2027 var layers = pane.getElementsByClassName('leaflet-layer'),
2028 edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
2029 zIndex;
2030
2031 for (var i = 0, len = layers.length; i < len; i++) {
2032
2033 if (layers[i] !== this._container) {
2034 zIndex = parseInt(layers[i].style.zIndex, 10);
2035
2036 if (!isNaN(zIndex)) {
2037 edgeZIndex = compare(edgeZIndex, zIndex);
2038 }
2039 }
2040 }
2041
2042 this._container.style.zIndex = isFinite(edgeZIndex) ? edgeZIndex + compare(1, -1) : '';
2043 },
2044
2045 _updateOpacity: function () {
2046 L.DomUtil.setOpacity(this._container, this.options.opacity);
2047
2048 // stupid webkit hack to force redrawing of tiles
2049 var i,
2050 tiles = this._tiles;
2051
2052 if (L.Browser.webkit) {
2053 for (i in tiles) {
2054 if (tiles.hasOwnProperty(i)) {
2055 tiles[i].style.webkitTransform += ' translate(0,0)';
2056 }
2057 }
2058 }
2059 },
2060
2061 _initContainer: function () {
2062 var tilePane = this._map._panes.tilePane;
2063
2064 if (!this._container || tilePane.empty) {
2065 this._container = L.DomUtil.create('div', 'leaflet-layer');
2066
2067 this._updateZIndex();
2068
2069 tilePane.appendChild(this._container);
2070
2071 if (this.options.opacity < 1) {
2072 this._updateOpacity();
2073 }
2074 }
2075 },
2076
2077 _resetCallback: function (e) {
2078 this._reset(e.hard);
2079 },
2080
2081 _reset: function (clearOldContainer) {
2082 var key,
2083 tiles = this._tiles;
2084
2085 for (key in tiles) {
2086 if (tiles.hasOwnProperty(key)) {
2087 this.fire('tileunload', {tile: tiles[key]});
2088 }
2089 }
2090
2091 this._tiles = {};
2092 this._tilesToLoad = 0;
2093
2094 if (this.options.reuseTiles) {
2095 this._unusedTiles = [];
2096 }
2097
2098 if (clearOldContainer && this._container) {
2099 this._container.innerHTML = "";
2100 }
2101
2102 this._initContainer();
2103 },
2104
2105 _update: function (e) {
2106 if (this._map._panTransition && this._map._panTransition._inProgress) { return; }
2107
2108 var bounds = this._map.getPixelBounds(),
2109 zoom = this._map.getZoom(),
2110 tileSize = this.options.tileSize;
2111
2112 if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
2113 return;
2114 }
2115
2116 var nwTilePoint = new L.Point(
2117 Math.floor(bounds.min.x / tileSize),
2118 Math.floor(bounds.min.y / tileSize)),
2119 seTilePoint = new L.Point(
2120 Math.floor(bounds.max.x / tileSize),
2121 Math.floor(bounds.max.y / tileSize)),
2122 tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
2123
2124 this._addTilesFromCenterOut(tileBounds);
2125
2126 if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
2127 this._removeOtherTiles(tileBounds);
2128 }
2129 },
2130
2131 _addTilesFromCenterOut: function (bounds) {
2132 var queue = [],
2133 center = bounds.getCenter();
2134
2135 var j, i, point;
2136
2137 for (j = bounds.min.y; j <= bounds.max.y; j++) {
2138 for (i = bounds.min.x; i <= bounds.max.x; i++) {
2139 point = new L.Point(i, j);
2140
2141 if (this._tileShouldBeLoaded(point)) {
2142 queue.push(point);
2143 }
2144 }
2145 }
2146
2147 var tilesToLoad = queue.length;
2148
2149 if (tilesToLoad === 0) { return; }
2150
2151 // load tiles in order of their distance to center
2152 queue.sort(function (a, b) {
2153 return a.distanceTo(center) - b.distanceTo(center);
2154 });
2155
2156 var fragment = document.createDocumentFragment();
2157
2158 // if its the first batch of tiles to load
2159 if (!this._tilesToLoad) {
2160 this.fire('loading');
2161 }
2162
2163 this._tilesToLoad += tilesToLoad;
2164
2165 for (i = 0; i < tilesToLoad; i++) {
2166 this._addTile(queue[i], fragment);
2167 }
2168
2169 this._container.appendChild(fragment);
2170 },
2171
2172 _tileShouldBeLoaded: function (tilePoint) {
2173 if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
2174 return false; // already loaded
2175 }
2176
2177 if (!this.options.continuousWorld) {
2178 var limit = this._getWrapTileNum();
2179
2180 if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
2181 tilePoint.y < 0 || tilePoint.y >= limit) {
2182 return false; // exceeds world bounds
2183 }
2184 }
2185
2186 return true;
2187 },
2188
2189 _removeOtherTiles: function (bounds) {
2190 var kArr, x, y, key;
2191
2192 for (key in this._tiles) {
2193 if (this._tiles.hasOwnProperty(key)) {
2194 kArr = key.split(':');
2195 x = parseInt(kArr[0], 10);
2196 y = parseInt(kArr[1], 10);
2197
2198 // remove tile if it's out of bounds
2199 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
2200 this._removeTile(key);
2201 }
2202 }
2203 }
2204 },
2205
2206 _removeTile: function (key) {
2207 var tile = this._tiles[key];
2208
2209 this.fire("tileunload", {tile: tile, url: tile.src});
2210
2211 if (this.options.reuseTiles) {
2212 L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
2213 this._unusedTiles.push(tile);
2214 } else if (tile.parentNode === this._container) {
2215 this._container.removeChild(tile);
2216 }
2217
2218 if (!L.Browser.android) { //For https://github.com/CloudMade/Leaflet/issues/137
2219 tile.src = L.Util.emptyImageUrl;
2220 }
2221
2222 delete this._tiles[key];
2223 },
2224
2225 _addTile: function (tilePoint, container) {
2226 var tilePos = this._getTilePos(tilePoint);
2227
2228 // get unused tile - or create a new tile
2229 var tile = this._getTile();
2230
2231 // Chrome 20 layouts much faster with top/left (Verify with timeline, frames), Safari 5.1.7, iOS 5.1.1,
2232 // android browser (4.0) have display issues with top/left and requires transform instead
2233 // (other browsers don't currently care) - see debug/hacks/jitter.html for an example
2234 L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
2235
2236 this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
2237
2238 this._loadTile(tile, tilePoint);
2239
2240 if (tile.parentNode !== this._container) {
2241 container.appendChild(tile);
2242 }
2243 },
2244
2245 _getZoomForUrl: function () {
2246
2247 var options = this.options,
2248 zoom = this._map.getZoom();
2249
2250 if (options.zoomReverse) {
2251 zoom = options.maxZoom - zoom;
2252 }
2253
2254 return zoom + options.zoomOffset;
2255 },
2256
2257 _getTilePos: function (tilePoint) {
2258 var origin = this._map.getPixelOrigin(),
2259 tileSize = this.options.tileSize;
2260
2261 return tilePoint.multiplyBy(tileSize).subtract(origin);
2262 },
2263
2264 // image-specific code (override to implement e.g. Canvas or SVG tile layer)
2265
2266 getTileUrl: function (tilePoint) {
2267 this._adjustTilePoint(tilePoint);
2268
2269 return L.Util.template(this._url, L.Util.extend({
2270 s: this._getSubdomain(tilePoint),
2271 z: this._getZoomForUrl(),
2272 x: tilePoint.x,
2273 y: tilePoint.y
2274 }, this.options));
2275 },
2276
2277 _getWrapTileNum: function () {
2278 // TODO refactor, limit is not valid for non-standard projections
2279 return Math.pow(2, this._getZoomForUrl());
2280 },
2281
2282 _adjustTilePoint: function (tilePoint) {
2283
2284 var limit = this._getWrapTileNum();
2285
2286 // wrap tile coordinates
2287 if (!this.options.continuousWorld && !this.options.noWrap) {
2288 tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
2289 }
2290
2291 if (this.options.tms) {
2292 tilePoint.y = limit - tilePoint.y - 1;
2293 }
2294 },
2295
2296 _getSubdomain: function (tilePoint) {
2297 var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
2298 return this.options.subdomains[index];
2299 },
2300
2301 _createTileProto: function () {
2302 var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
2303 img.galleryimg = 'no';
2304
2305 var tileSize = this.options.tileSize;
2306 img.style.width = tileSize + 'px';
2307 img.style.height = tileSize + 'px';
2308 },
2309
2310 _getTile: function () {
2311 if (this.options.reuseTiles && this._unusedTiles.length > 0) {
2312 var tile = this._unusedTiles.pop();
2313 this._resetTile(tile);
2314 return tile;
2315 }
2316 return this._createTile();
2317 },
2318
2319 _resetTile: function (tile) {
2320 // Override if data stored on a tile needs to be cleaned up before reuse
2321 },
2322
2323 _createTile: function () {
2324 var tile = this._tileImg.cloneNode(false);
2325 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2326 return tile;
2327 },
2328
2329 _loadTile: function (tile, tilePoint) {
2330 tile._layer = this;
2331 tile.onload = this._tileOnLoad;
2332 tile.onerror = this._tileOnError;
2333
2334 tile.src = this.getTileUrl(tilePoint);
2335 },
2336
2337 _tileLoaded: function () {
2338 this._tilesToLoad--;
2339 if (!this._tilesToLoad) {
2340 this.fire('load');
2341 }
2342 },
2343
2344 _tileOnLoad: function (e) {
2345 var layer = this._layer;
2346
2347 //Only if we are loading an actual image
2348 if (this.src !== L.Util.emptyImageUrl) {
2349 L.DomUtil.addClass(this, 'leaflet-tile-loaded');
2350
2351 layer.fire('tileload', {
2352 tile: this,
2353 url: this.src
2354 });
2355 }
2356
2357 layer._tileLoaded();
2358 },
2359
2360 _tileOnError: function (e) {
2361 var layer = this._layer;
2362
2363 layer.fire('tileerror', {
2364 tile: this,
2365 url: this.src
2366 });
2367
2368 var newUrl = layer.options.errorTileUrl;
2369 if (newUrl) {
2370 this.src = newUrl;
2371 }
2372
2373 layer._tileLoaded();
2374 }
2375 });
2376
2377 L.tileLayer = function (url, options) {
2378 return new L.TileLayer(url, options);
2379 };
2380
2381
2382 L.TileLayer.WMS = L.TileLayer.extend({
2383
2384 defaultWmsParams: {
2385 service: 'WMS',
2386 request: 'GetMap',
2387 version: '1.1.1',
2388 layers: '',
2389 styles: '',
2390 format: 'image/jpeg',
2391 transparent: false
2392 },
2393
2394 initialize: function (url, options) { // (String, Object)
2395
2396 this._url = url;
2397
2398 var wmsParams = L.Util.extend({}, this.defaultWmsParams);
2399
2400 if (options.detectRetina && window.devicePixelRatio > 1) {
2401 wmsParams.width = wmsParams.height = this.options.tileSize * 2;
2402 } else {
2403 wmsParams.width = wmsParams.height = this.options.tileSize;
2404 }
2405
2406 for (var i in options) {
2407 // all keys that are not TileLayer options go to WMS params
2408 if (!this.options.hasOwnProperty(i)) {
2409 wmsParams[i] = options[i];
2410 }
2411 }
2412
2413 this.wmsParams = wmsParams;
2414
2415 L.Util.setOptions(this, options);
2416 },
2417
2418 onAdd: function (map) {
2419
2420 var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
2421 this.wmsParams[projectionKey] = map.options.crs.code;
2422
2423 L.TileLayer.prototype.onAdd.call(this, map);
2424 },
2425
2426 getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
2427
2428 var map = this._map,
2429 crs = map.options.crs,
2430 tileSize = this.options.tileSize,
2431
2432 nwPoint = tilePoint.multiplyBy(tileSize),
2433 sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
2434
2435 nw = crs.project(map.unproject(nwPoint, zoom)),
2436 se = crs.project(map.unproject(sePoint, zoom)),
2437
2438 bbox = [nw.x, se.y, se.x, nw.y].join(','),
2439
2440 url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
2441
2442 return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
2443 },
2444
2445 setParams: function (params, noRedraw) {
2446
2447 L.Util.extend(this.wmsParams, params);
2448
2449 if (!noRedraw) {
2450 this.redraw();
2451 }
2452
2453 return this;
2454 }
2455 });
2456
2457 L.tileLayer.wms = function (url, options) {
2458 return new L.TileLayer(url, options);
2459 };
2460
2461
2462 L.TileLayer.Canvas = L.TileLayer.extend({
2463 options: {
2464 async: false
2465 },
2466
2467 initialize: function (options) {
2468 L.Util.setOptions(this, options);
2469 },
2470
2471 redraw: function () {
2472 var i,
2473 tiles = this._tiles;
2474
2475 for (i in tiles) {
2476 if (tiles.hasOwnProperty(i)) {
2477 this._redrawTile(tiles[i]);
2478 }
2479 }
2480 },
2481
2482 _redrawTile: function (tile) {
2483 this.drawTile(tile, tile._tilePoint, tile._zoom);
2484 },
2485
2486 _createTileProto: function () {
2487 var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
2488
2489 var tileSize = this.options.tileSize;
2490 proto.width = tileSize;
2491 proto.height = tileSize;
2492 },
2493
2494 _createTile: function () {
2495 var tile = this._canvasProto.cloneNode(false);
2496 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2497 return tile;
2498 },
2499
2500 _loadTile: function (tile, tilePoint, zoom) {
2501 tile._layer = this;
2502 tile._tilePoint = tilePoint;
2503 tile._zoom = zoom;
2504
2505 this.drawTile(tile, tilePoint, zoom);
2506
2507 if (!this.options.async) {
2508 this.tileDrawn(tile);
2509 }
2510 },
2511
2512 drawTile: function (tile, tilePoint, zoom) {
2513 // override with rendering code
2514 },
2515
2516 tileDrawn: function (tile) {
2517 this._tileOnLoad.call(tile);
2518 }
2519 });
2520
2521
2522 L.tileLayer.canvas = function (options) {
2523 return new L.TileLayer.Canvas(options);
2524 };
2525
2526 L.ImageOverlay = L.Class.extend({
2527 includes: L.Mixin.Events,
2528
2529 options: {
2530 opacity: 1
2531 },
2532
2533 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
2534 this._url = url;
2535 this._bounds = L.latLngBounds(bounds);
2536
2537 L.Util.setOptions(this, options);
2538 },
2539
2540 onAdd: function (map) {
2541 this._map = map;
2542
2543 if (!this._image) {
2544 this._initImage();
2545 }
2546
2547 map._panes.overlayPane.appendChild(this._image);
2548
2549 map.on('viewreset', this._reset, this);
2550
2551 if (map.options.zoomAnimation && L.Browser.any3d) {
2552 map.on('zoomanim', this._animateZoom, this);
2553 }
2554
2555 this._reset();
2556 },
2557
2558 onRemove: function (map) {
2559 map.getPanes().overlayPane.removeChild(this._image);
2560
2561 map.off('viewreset', this._reset, this);
2562
2563 if (map.options.zoomAnimation) {
2564 map.off('zoomanim', this._animateZoom, this);
2565 }
2566 },
2567
2568 addTo: function (map) {
2569 map.addLayer(this);
2570 return this;
2571 },
2572
2573 setOpacity: function (opacity) {
2574 this.options.opacity = opacity;
2575 this._updateOpacity();
2576 return this;
2577 },
2578
2579 // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
2580 bringToFront: function () {
2581 if (this._image) {
2582 this._map._panes.overlayPane.appendChild(this._image);
2583 }
2584 return this;
2585 },
2586
2587 bringToBack: function () {
2588 var pane = this._map._panes.overlayPane;
2589 if (this._image) {
2590 pane.insertBefore(this._image, pane.firstChild);
2591 }
2592 return this;
2593 },
2594
2595 _initImage: function () {
2596 this._image = L.DomUtil.create('img', 'leaflet-image-layer');
2597
2598 if (this._map.options.zoomAnimation && L.Browser.any3d) {
2599 L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
2600 } else {
2601 L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
2602 }
2603
2604 this._updateOpacity();
2605
2606 //TODO createImage util method to remove duplication
2607 L.Util.extend(this._image, {
2608 galleryimg: 'no',
2609 onselectstart: L.Util.falseFn,
2610 onmousemove: L.Util.falseFn,
2611 onload: L.Util.bind(this._onImageLoad, this),
2612 src: this._url
2613 });
2614 },
2615
2616 _animateZoom: function (e) {
2617 var map = this._map,
2618 image = this._image,
2619 scale = map.getZoomScale(e.zoom),
2620 nw = this._bounds.getNorthWest(),
2621 se = this._bounds.getSouthEast(),
2622 topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
2623 size = map._latLngToNewLayerPoint(se, e.zoom, e.center).subtract(topLeft),
2624 currentSize = map.latLngToLayerPoint(se).subtract(map.latLngToLayerPoint(nw)),
2625 origin = topLeft.add(size.subtract(currentSize).divideBy(2));
2626
2627 image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
2628 },
2629
2630 _reset: function () {
2631 var image = this._image,
2632 topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
2633 size = this._map.latLngToLayerPoint(this._bounds.getSouthEast()).subtract(topLeft);
2634
2635 L.DomUtil.setPosition(image, topLeft);
2636
2637 image.style.width = size.x + 'px';
2638 image.style.height = size.y + 'px';
2639 },
2640
2641 _onImageLoad: function () {
2642 this.fire('load');
2643 },
2644
2645 _updateOpacity: function () {
2646 L.DomUtil.setOpacity(this._image, this.options.opacity);
2647 }
2648 });
2649
2650 L.imageOverlay = function (url, bounds, options) {
2651 return new L.ImageOverlay(url, bounds, options);
2652 };
2653
2654
2655 L.Icon = L.Class.extend({
2656 options: {
2657 /*
2658 iconUrl: (String) (required)
2659 iconSize: (Point) (can be set through CSS)
2660 iconAnchor: (Point) (centered by default if size is specified, can be set in CSS with negative margins)
2661 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
2662 shadowUrl: (Point) (no shadow by default)
2663 shadowSize: (Point)
2664 shadowAnchor: (Point)
2665 */
2666 className: ''
2667 },
2668
2669 initialize: function (options) {
2670 L.Util.setOptions(this, options);
2671 },
2672
2673 createIcon: function () {
2674 return this._createIcon('icon');
2675 },
2676
2677 createShadow: function () {
2678 return this._createIcon('shadow');
2679 },
2680
2681 _createIcon: function (name) {
2682 var src = this._getIconUrl(name);
2683
2684 if (!src) {
2685 if (name === 'icon') {
2686 throw new Error("iconUrl not set in Icon options (see the docs).");
2687 }
2688 return null;
2689 }
2690
2691 var img = this._createImg(src);
2692 this._setIconStyles(img, name);
2693
2694 return img;
2695 },
2696
2697 _setIconStyles: function (img, name) {
2698 var options = this.options,
2699 size = L.point(options[name + 'Size']),
2700 anchor;
2701
2702 if (name === 'shadow') {
2703 anchor = L.point(options.shadowAnchor || options.iconAnchor);
2704 } else {
2705 anchor = L.point(options.iconAnchor);
2706 }
2707
2708 if (!anchor && size) {
2709 anchor = size.divideBy(2, true);
2710 }
2711
2712 img.className = 'leaflet-marker-' + name + ' ' + options.className;
2713
2714 if (anchor) {
2715 img.style.marginLeft = (-anchor.x) + 'px';
2716 img.style.marginTop = (-anchor.y) + 'px';
2717 }
2718
2719 if (size) {
2720 img.style.width = size.x + 'px';
2721 img.style.height = size.y + 'px';
2722 }
2723 },
2724
2725 _createImg: function (src) {
2726 var el;
2727
2728 if (!L.Browser.ie6) {
2729 el = document.createElement('img');
2730 el.src = src;
2731 } else {
2732 el = document.createElement('div');
2733 el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
2734 }
2735 return el;
2736 },
2737
2738 _getIconUrl: function (name) {
2739 return this.options[name + 'Url'];
2740 }
2741 });
2742
2743 L.icon = function (options) {
2744 return new L.Icon(options);
2745 };
2746
2747
2748
2749 L.Icon.Default = L.Icon.extend({
2750
2751 options: {
2752 iconSize: new L.Point(25, 41),
2753 iconAnchor: new L.Point(13, 41),
2754 popupAnchor: new L.Point(1, -34),
2755
2756 shadowSize: new L.Point(41, 41)
2757 },
2758
2759 _getIconUrl: function (name) {
2760 var key = name + 'Url';
2761
2762 if (this.options[key]) {
2763 return this.options[key];
2764 }
2765
2766 var path = L.Icon.Default.imagePath;
2767
2768 if (!path) {
2769 throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
2770 }
2771
2772 return path + '/marker-' + name + '.png';
2773 }
2774 });
2775
2776 L.Icon.Default.imagePath = (function () {
2777 var scripts = document.getElementsByTagName('script'),
2778 leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
2779
2780 var i, len, src, matches;
2781
2782 for (i = 0, len = scripts.length; i < len; i++) {
2783 src = scripts[i].src;
2784 matches = src.match(leafletRe);
2785
2786 if (matches) {
2787 return src.split(leafletRe)[0] + '/images';
2788 }
2789 }
2790 }());
2791
2792
2793 /*
2794 * L.Marker is used to display clickable/draggable icons on the map.
2795 */
2796
2797 L.Marker = L.Class.extend({
2798
2799 includes: L.Mixin.Events,
2800
2801 options: {
2802 icon: new L.Icon.Default(),
2803 title: '',
2804 clickable: true,
2805 draggable: false,
2806 zIndexOffset: 0,
2807 opacity: 1
2808 },
2809
2810 initialize: function (latlng, options) {
2811 L.Util.setOptions(this, options);
2812 this._latlng = L.latLng(latlng);
2813 },
2814
2815 onAdd: function (map) {
2816 this._map = map;
2817
2818 map.on('viewreset', this.update, this);
2819
2820 this._initIcon();
2821 this.update();
2822
2823 if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
2824 map.on('zoomanim', this._animateZoom, this);
2825 }
2826 },
2827
2828 addTo: function (map) {
2829 map.addLayer(this);
2830 return this;
2831 },
2832
2833 onRemove: function (map) {
2834 this._removeIcon();
2835
2836 // TODO move to Marker.Popup.js
2837 if (this.closePopup) {
2838 this.closePopup();
2839 }
2840
2841 map.off({
2842 'viewreset': this.update,
2843 'zoomanim': this._animateZoom
2844 }, this);
2845
2846 this._map = null;
2847 },
2848
2849 getLatLng: function () {
2850 return this._latlng;
2851 },
2852
2853 setLatLng: function (latlng) {
2854 this._latlng = L.latLng(latlng);
2855
2856 this.update();
2857
2858 if (this._popup) {
2859 this._popup.setLatLng(latlng);
2860 }
2861 },
2862
2863 setZIndexOffset: function (offset) {
2864 this.options.zIndexOffset = offset;
2865 this.update();
2866 },
2867
2868 setIcon: function (icon) {
2869 if (this._map) {
2870 this._removeIcon();
2871 }
2872
2873 this.options.icon = icon;
2874
2875 if (this._map) {
2876 this._initIcon();
2877 this.update();
2878 }
2879 },
2880
2881 update: function () {
2882 if (!this._icon) { return; }
2883
2884 var pos = this._map.latLngToLayerPoint(this._latlng).round();
2885 this._setPos(pos);
2886 },
2887
2888 _initIcon: function () {
2889 var options = this.options,
2890 map = this._map,
2891 animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
2892 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
2893 needOpacityUpdate = false;
2894
2895 if (!this._icon) {
2896 this._icon = options.icon.createIcon();
2897
2898 if (options.title) {
2899 this._icon.title = options.title;
2900 }
2901
2902 this._initInteraction();
2903 needOpacityUpdate = true;
2904
2905 L.DomUtil.addClass(this._icon, classToAdd);
2906 }
2907 if (!this._shadow) {
2908 this._shadow = options.icon.createShadow();
2909
2910 if (this._shadow) {
2911 L.DomUtil.addClass(this._shadow, classToAdd);
2912 needOpacityUpdate = true;
2913 }
2914 }
2915
2916 if (needOpacityUpdate) {
2917 this._updateOpacity();
2918 }
2919
2920 var panes = this._map._panes;
2921
2922 panes.markerPane.appendChild(this._icon);
2923
2924 if (this._shadow) {
2925 panes.shadowPane.appendChild(this._shadow);
2926 }
2927 },
2928
2929 _removeIcon: function () {
2930 var panes = this._map._panes;
2931
2932 panes.markerPane.removeChild(this._icon);
2933
2934 if (this._shadow) {
2935 panes.shadowPane.removeChild(this._shadow);
2936 }
2937
2938 this._icon = this._shadow = null;
2939 },
2940
2941 _setPos: function (pos) {
2942 L.DomUtil.setPosition(this._icon, pos);
2943
2944 if (this._shadow) {
2945 L.DomUtil.setPosition(this._shadow, pos);
2946 }
2947
2948 this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
2949 },
2950
2951 _animateZoom: function (opt) {
2952 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
2953
2954 this._setPos(pos);
2955 },
2956
2957 _initInteraction: function () {
2958 if (!this.options.clickable) {
2959 return;
2960 }
2961
2962 var icon = this._icon,
2963 events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
2964
2965 L.DomUtil.addClass(icon, 'leaflet-clickable');
2966 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
2967
2968 for (var i = 0; i < events.length; i++) {
2969 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
2970 }
2971
2972 if (L.Handler.MarkerDrag) {
2973 this.dragging = new L.Handler.MarkerDrag(this);
2974
2975 if (this.options.draggable) {
2976 this.dragging.enable();
2977 }
2978 }
2979 },
2980
2981 _onMouseClick: function (e) {
2982 L.DomEvent.stopPropagation(e);
2983 if (this.dragging && this.dragging.moved()) { return; }
2984 if (this._map.dragging && this._map.dragging.moved()) { return; }
2985 this.fire(e.type, {
2986 originalEvent: e
2987 });
2988 },
2989
2990 _fireMouseEvent: function (e) {
2991 this.fire(e.type, {
2992 originalEvent: e
2993 });
2994 if (e.type !== 'mousedown') {
2995 L.DomEvent.stopPropagation(e);
2996 }
2997 },
2998
2999 setOpacity: function (opacity) {
3000 this.options.opacity = opacity;
3001 if (this._map) {
3002 this._updateOpacity();
3003 }
3004 },
3005
3006 _updateOpacity: function () {
3007 L.DomUtil.setOpacity(this._icon, this.options.opacity);
3008 if (this._shadow) {
3009 L.DomUtil.setOpacity(this._shadow, this.options.opacity);
3010 }
3011 }
3012 });
3013
3014 L.marker = function (latlng, options) {
3015 return new L.Marker(latlng, options);
3016 };
3017
3018
3019 L.DivIcon = L.Icon.extend({
3020 options: {
3021 iconSize: new L.Point(12, 12), // also can be set through CSS
3022 /*
3023 iconAnchor: (Point)
3024 popupAnchor: (Point)
3025 html: (String)
3026 bgPos: (Point)
3027 */
3028 className: 'leaflet-div-icon'
3029 },
3030
3031 createIcon: function () {
3032 var div = document.createElement('div'),
3033 options = this.options;
3034
3035 if (options.html) {
3036 div.innerHTML = options.html;
3037 }
3038
3039 if (options.bgPos) {
3040 div.style.backgroundPosition =
3041 (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
3042 }
3043
3044 this._setIconStyles(div, 'icon');
3045 return div;
3046 },
3047
3048 createShadow: function () {
3049 return null;
3050 }
3051 });
3052
3053 L.divIcon = function (options) {
3054 return new L.DivIcon(options);
3055 };
3056
3057
3058
3059 L.Map.mergeOptions({
3060 closePopupOnClick: true
3061 });
3062
3063 L.Popup = L.Class.extend({
3064 includes: L.Mixin.Events,
3065
3066 options: {
3067 minWidth: 50,
3068 maxWidth: 300,
3069 maxHeight: null,
3070 autoPan: true,
3071 closeButton: true,
3072 offset: new L.Point(0, 6),
3073 autoPanPadding: new L.Point(5, 5),
3074 className: ''
3075 },
3076
3077 initialize: function (options, source) {
3078 L.Util.setOptions(this, options);
3079
3080 this._source = source;
3081 },
3082
3083 onAdd: function (map) {
3084 this._map = map;
3085
3086 if (!this._container) {
3087 this._initLayout();
3088 }
3089 this._updateContent();
3090
3091 var animFade = map.options.fadeAnimation;
3092
3093 if (animFade) {
3094 L.DomUtil.setOpacity(this._container, 0);
3095 }
3096 map._panes.popupPane.appendChild(this._container);
3097
3098 map.on('viewreset', this._updatePosition, this);
3099
3100 if (L.Browser.any3d) {
3101 map.on('zoomanim', this._zoomAnimation, this);
3102 }
3103
3104 if (map.options.closePopupOnClick) {
3105 map.on('preclick', this._close, this);
3106 }
3107
3108 this._update();
3109
3110 if (animFade) {
3111 L.DomUtil.setOpacity(this._container, 1);
3112 }
3113 },
3114
3115 addTo: function (map) {
3116 map.addLayer(this);
3117 return this;
3118 },
3119
3120 openOn: function (map) {
3121 map.openPopup(this);
3122 return this;
3123 },
3124
3125 onRemove: function (map) {
3126 map._panes.popupPane.removeChild(this._container);
3127
3128 L.Util.falseFn(this._container.offsetWidth); // force reflow
3129
3130 map.off({
3131 viewreset: this._updatePosition,
3132 preclick: this._close,
3133 zoomanim: this._zoomAnimation
3134 }, this);
3135
3136 if (map.options.fadeAnimation) {
3137 L.DomUtil.setOpacity(this._container, 0);
3138 }
3139
3140 this._map = null;
3141 },
3142
3143 setLatLng: function (latlng) {
3144 this._latlng = L.latLng(latlng);
3145 this._update();
3146 return this;
3147 },
3148
3149 setContent: function (content) {
3150 this._content = content;
3151 this._update();
3152 return this;
3153 },
3154
3155 _close: function () {
3156 var map = this._map;
3157
3158 if (map) {
3159 map._popup = null;
3160
3161 map
3162 .removeLayer(this)
3163 .fire('popupclose', {popup: this});
3164 }
3165 },
3166
3167 _initLayout: function () {
3168 var prefix = 'leaflet-popup',
3169 container = this._container = L.DomUtil.create('div', prefix + ' ' + this.options.className + ' leaflet-zoom-animated'),
3170 closeButton;
3171
3172 if (this.options.closeButton) {
3173 closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
3174 closeButton.href = '#close';
3175 closeButton.innerHTML = '&#215;';
3176
3177 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
3178 }
3179
3180 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
3181 L.DomEvent.disableClickPropagation(wrapper);
3182
3183 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
3184 L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
3185
3186 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
3187 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
3188 },
3189
3190 _update: function () {
3191 if (!this._map) { return; }
3192
3193 this._container.style.visibility = 'hidden';
3194
3195 this._updateContent();
3196 this._updateLayout();
3197 this._updatePosition();
3198
3199 this._container.style.visibility = '';
3200
3201 this._adjustPan();
3202 },
3203
3204 _updateContent: function () {
3205 if (!this._content) { return; }
3206
3207 if (typeof this._content === 'string') {
3208 this._contentNode.innerHTML = this._content;
3209 } else {
3210 while (this._contentNode.hasChildNodes()) {
3211 this._contentNode.removeChild(this._contentNode.firstChild);
3212 }
3213 this._contentNode.appendChild(this._content);
3214 }
3215 this.fire('contentupdate');
3216 },
3217
3218 _updateLayout: function () {
3219 var container = this._contentNode,
3220 style = container.style;
3221
3222 style.width = '';
3223 style.whiteSpace = 'nowrap';
3224
3225 var width = container.offsetWidth;
3226 width = Math.min(width, this.options.maxWidth);
3227 width = Math.max(width, this.options.minWidth);
3228
3229 style.width = (width + 1) + 'px';
3230 style.whiteSpace = '';
3231
3232 style.height = '';
3233
3234 var height = container.offsetHeight,
3235 maxHeight = this.options.maxHeight,
3236 scrolledClass = 'leaflet-popup-scrolled';
3237
3238 if (maxHeight && height > maxHeight) {
3239 style.height = maxHeight + 'px';
3240 L.DomUtil.addClass(container, scrolledClass);
3241 } else {
3242 L.DomUtil.removeClass(container, scrolledClass);
3243 }
3244
3245 this._containerWidth = this._container.offsetWidth;
3246 },
3247
3248 _updatePosition: function () {
3249 var pos = this._map.latLngToLayerPoint(this._latlng),
3250 is3d = L.Browser.any3d,
3251 offset = this.options.offset;
3252
3253 if (is3d) {
3254 L.DomUtil.setPosition(this._container, pos);
3255 }
3256
3257 this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
3258 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
3259
3260 //Bottom position the popup in case the height of the popup changes (images loading etc)
3261 this._container.style.bottom = this._containerBottom + 'px';
3262 this._container.style.left = this._containerLeft + 'px';
3263 },
3264
3265 _zoomAnimation: function (opt) {
3266 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3267
3268 L.DomUtil.setPosition(this._container, pos);
3269 },
3270
3271 _adjustPan: function () {
3272 if (!this.options.autoPan) { return; }
3273
3274 var map = this._map,
3275 containerHeight = this._container.offsetHeight,
3276 containerWidth = this._containerWidth,
3277
3278 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
3279
3280 if (L.Browser.any3d) {
3281 layerPos._add(L.DomUtil.getPosition(this._container));
3282 }
3283
3284 var containerPos = map.layerPointToContainerPoint(layerPos),
3285 padding = this.options.autoPanPadding,
3286 size = map.getSize(),
3287 dx = 0,
3288 dy = 0;
3289
3290 if (containerPos.x < 0) {
3291 dx = containerPos.x - padding.x;
3292 }
3293 if (containerPos.x + containerWidth > size.x) {
3294 dx = containerPos.x + containerWidth - size.x + padding.x;
3295 }
3296 if (containerPos.y < 0) {
3297 dy = containerPos.y - padding.y;
3298 }
3299 if (containerPos.y + containerHeight > size.y) {
3300 dy = containerPos.y + containerHeight - size.y + padding.y;
3301 }
3302
3303 if (dx || dy) {
3304 map.panBy(new L.Point(dx, dy));
3305 }
3306 },
3307
3308 _onCloseButtonClick: function (e) {
3309 this._close();
3310 L.DomEvent.stop(e);
3311 }
3312 });
3313
3314 L.popup = function (options, source) {
3315 return new L.Popup(options, source);
3316 };
3317
3318
3319 /*
3320 * Popup extension to L.Marker, adding openPopup & bindPopup methods.
3321 */
3322
3323 L.Marker.include({
3324 openPopup: function () {
3325 if (this._popup && this._map) {
3326 this._popup.setLatLng(this._latlng);
3327 this._map.openPopup(this._popup);
3328 }
3329
3330 return this;
3331 },
3332
3333 closePopup: function () {
3334 if (this._popup) {
3335 this._popup._close();
3336 }
3337 return this;
3338 },
3339
3340 bindPopup: function (content, options) {
3341 var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
3342
3343 anchor = anchor.add(L.Popup.prototype.options.offset);
3344
3345 if (options && options.offset) {
3346 anchor = anchor.add(options.offset);
3347 }
3348
3349 options = L.Util.extend({offset: anchor}, options);
3350
3351 if (!this._popup) {
3352 this.on('click', this.openPopup, this);
3353 }
3354
3355 this._popup = new L.Popup(options, this)
3356 .setContent(content);
3357
3358 return this;
3359 },
3360
3361 unbindPopup: function () {
3362 if (this._popup) {
3363 this._popup = null;
3364 this.off('click', this.openPopup);
3365 }
3366 return this;
3367 }
3368 });
3369
3370
3371
3372 L.Map.include({
3373 openPopup: function (popup) {
3374 this.closePopup();
3375
3376 this._popup = popup;
3377
3378 return this
3379 .addLayer(popup)
3380 .fire('popupopen', {popup: this._popup});
3381 },
3382
3383 closePopup: function () {
3384 if (this._popup) {
3385 this._popup._close();
3386 }
3387 return this;
3388 }
3389 });
3390
3391 /*
3392 * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
3393 */
3394
3395 L.LayerGroup = L.Class.extend({
3396 initialize: function (layers) {
3397 this._layers = {};
3398
3399 var i, len;
3400
3401 if (layers) {
3402 for (i = 0, len = layers.length; i < len; i++) {
3403 this.addLayer(layers[i]);
3404 }
3405 }
3406 },
3407
3408 addLayer: function (layer) {
3409 var id = L.Util.stamp(layer);
3410
3411 this._layers[id] = layer;
3412
3413 if (this._map) {
3414 this._map.addLayer(layer);
3415 }
3416
3417 return this;
3418 },
3419
3420 removeLayer: function (layer) {
3421 var id = L.Util.stamp(layer);
3422
3423 delete this._layers[id];
3424
3425 if (this._map) {
3426 this._map.removeLayer(layer);
3427 }
3428
3429 return this;
3430 },
3431
3432 clearLayers: function () {
3433 this.eachLayer(this.removeLayer, this);
3434 return this;
3435 },
3436
3437 invoke: function (methodName) {
3438 var args = Array.prototype.slice.call(arguments, 1),
3439 i, layer;
3440
3441 for (i in this._layers) {
3442 if (this._layers.hasOwnProperty(i)) {
3443 layer = this._layers[i];
3444
3445 if (layer[methodName]) {
3446 layer[methodName].apply(layer, args);
3447 }
3448 }
3449 }
3450
3451 return this;
3452 },
3453
3454 onAdd: function (map) {
3455 this._map = map;
3456 this.eachLayer(map.addLayer, map);
3457 },
3458
3459 onRemove: function (map) {
3460 this.eachLayer(map.removeLayer, map);
3461 this._map = null;
3462 },
3463
3464 addTo: function (map) {
3465 map.addLayer(this);
3466 return this;
3467 },
3468
3469 eachLayer: function (method, context) {
3470 for (var i in this._layers) {
3471 if (this._layers.hasOwnProperty(i)) {
3472 method.call(context, this._layers[i]);
3473 }
3474 }
3475 }
3476 });
3477
3478 L.layerGroup = function (layers) {
3479 return new L.LayerGroup(layers);
3480 };
3481
3482
3483 /*
3484 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
3485 */
3486
3487 L.FeatureGroup = L.LayerGroup.extend({
3488 includes: L.Mixin.Events,
3489
3490 addLayer: function (layer) {
3491 if (this._layers[L.Util.stamp(layer)]) {
3492 return this;
3493 }
3494
3495 layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
3496
3497 L.LayerGroup.prototype.addLayer.call(this, layer);
3498
3499 if (this._popupContent && layer.bindPopup) {
3500 layer.bindPopup(this._popupContent);
3501 }
3502
3503 return this;
3504 },
3505
3506 removeLayer: function (layer) {
3507 layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
3508
3509 L.LayerGroup.prototype.removeLayer.call(this, layer);
3510
3511 return this.invoke('unbindPopup');
3512 },
3513
3514 bindPopup: function (content) {
3515 this._popupContent = content;
3516 return this.invoke('bindPopup', content);
3517 },
3518
3519 setStyle: function (style) {
3520 return this.invoke('setStyle', style);
3521 },
3522
3523 getBounds: function () {
3524 var bounds = new L.LatLngBounds();
3525 this.eachLayer(function (layer) {
3526 bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
3527 }, this);
3528 return bounds;
3529 },
3530
3531 _propagateEvent: function (e) {
3532 e.layer = e.target;
3533 e.target = this;
3534
3535 this.fire(e.type, e);
3536 }
3537 });
3538
3539 L.featureGroup = function (layers) {
3540 return new L.FeatureGroup(layers);
3541 };
3542
3543
3544 /*
3545 * L.Path is a base class for rendering vector paths on a map. It's inherited by Polyline, Circle, etc.
3546 */
3547
3548 L.Path = L.Class.extend({
3549 includes: [L.Mixin.Events],
3550
3551 statics: {
3552 // how much to extend the clip area around the map view
3553 // (relative to its size, e.g. 0.5 is half the screen in each direction)
3554 // set in such way that SVG element doesn't exceed 1280px (vector layers flicker on dragend if it is)
3555 CLIP_PADDING: L.Browser.mobile ?
3556 Math.max(0, Math.min(0.5,
3557 (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2))
3558 : 0.5
3559 },
3560
3561 options: {
3562 stroke: true,
3563 color: '#0033ff',
3564 dashArray: null,
3565 weight: 5,
3566 opacity: 0.5,
3567
3568 fill: false,
3569 fillColor: null, //same as color by default
3570 fillOpacity: 0.2,
3571
3572 clickable: true
3573 },
3574
3575 initialize: function (options) {
3576 L.Util.setOptions(this, options);
3577 },
3578
3579 onAdd: function (map) {
3580 this._map = map;
3581
3582 if (!this._container) {
3583 this._initElements();
3584 this._initEvents();
3585 }
3586
3587 this.projectLatlngs();
3588 this._updatePath();
3589
3590 if (this._container) {
3591 this._map._pathRoot.appendChild(this._container);
3592 }
3593
3594 map.on({
3595 'viewreset': this.projectLatlngs,
3596 'moveend': this._updatePath
3597 }, this);
3598 },
3599
3600 addTo: function (map) {
3601 map.addLayer(this);
3602 return this;
3603 },
3604
3605 onRemove: function (map) {
3606 map._pathRoot.removeChild(this._container);
3607
3608 this._map = null;
3609
3610 if (L.Browser.vml) {
3611 this._container = null;
3612 this._stroke = null;
3613 this._fill = null;
3614 }
3615
3616 map.off({
3617 'viewreset': this.projectLatlngs,
3618 'moveend': this._updatePath
3619 }, this);
3620 },
3621
3622 projectLatlngs: function () {
3623 // do all projection stuff here
3624 },
3625
3626 setStyle: function (style) {
3627 L.Util.setOptions(this, style);
3628
3629 if (this._container) {
3630 this._updateStyle();
3631 }
3632
3633 return this;
3634 },
3635
3636 redraw: function () {
3637 if (this._map) {
3638 this.projectLatlngs();
3639 this._updatePath();
3640 }
3641 return this;
3642 }
3643 });
3644
3645 L.Map.include({
3646 _updatePathViewport: function () {
3647 var p = L.Path.CLIP_PADDING,
3648 size = this.getSize(),
3649 panePos = L.DomUtil.getPosition(this._mapPane),
3650 min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)),
3651 max = min.add(size.multiplyBy(1 + p * 2));
3652
3653 this._pathViewport = new L.Bounds(min, max);
3654 }
3655 });
3656
3657
3658 L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
3659
3660 L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
3661
3662 L.Path = L.Path.extend({
3663 statics: {
3664 SVG: L.Browser.svg
3665 },
3666
3667 bringToFront: function () {
3668 if (this._container) {
3669 this._map._pathRoot.appendChild(this._container);
3670 }
3671 },
3672
3673 bringToBack: function () {
3674 if (this._container) {
3675 var root = this._map._pathRoot;
3676 root.insertBefore(this._container, root.firstChild);
3677 }
3678 },
3679
3680 getPathString: function () {
3681 // form path string here
3682 },
3683
3684 _createElement: function (name) {
3685 return document.createElementNS(L.Path.SVG_NS, name);
3686 },
3687
3688 _initElements: function () {
3689 this._map._initPathRoot();
3690 this._initPath();
3691 this._initStyle();
3692 },
3693
3694 _initPath: function () {
3695 this._container = this._createElement('g');
3696
3697 this._path = this._createElement('path');
3698 this._container.appendChild(this._path);
3699 },
3700
3701 _initStyle: function () {
3702 if (this.options.stroke) {
3703 this._path.setAttribute('stroke-linejoin', 'round');
3704 this._path.setAttribute('stroke-linecap', 'round');
3705 }
3706 if (this.options.fill) {
3707 this._path.setAttribute('fill-rule', 'evenodd');
3708 }
3709 this._updateStyle();
3710 },
3711
3712 _updateStyle: function () {
3713 if (this.options.stroke) {
3714 this._path.setAttribute('stroke', this.options.color);
3715 this._path.setAttribute('stroke-opacity', this.options.opacity);
3716 this._path.setAttribute('stroke-width', this.options.weight);
3717 if (this.options.dashArray) {
3718 this._path.setAttribute('stroke-dasharray', this.options.dashArray);
3719 } else {
3720 this._path.removeAttribute('stroke-dasharray');
3721 }
3722 } else {
3723 this._path.setAttribute('stroke', 'none');
3724 }
3725 if (this.options.fill) {
3726 this._path.setAttribute('fill', this.options.fillColor || this.options.color);
3727 this._path.setAttribute('fill-opacity', this.options.fillOpacity);
3728 } else {
3729 this._path.setAttribute('fill', 'none');
3730 }
3731 },
3732
3733 _updatePath: function () {
3734 var str = this.getPathString();
3735 if (!str) {
3736 // fix webkit empty string parsing bug
3737 str = 'M0 0';
3738 }
3739 this._path.setAttribute('d', str);
3740 },
3741
3742 // TODO remove duplication with L.Map
3743 _initEvents: function () {
3744 if (this.options.clickable) {
3745 if (L.Browser.svg || !L.Browser.vml) {
3746 this._path.setAttribute('class', 'leaflet-clickable');
3747 }
3748
3749 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
3750
3751 var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'mousemove', 'contextmenu'];
3752 for (var i = 0; i < events.length; i++) {
3753 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
3754 }
3755 }
3756 },
3757
3758 _onMouseClick: function (e) {
3759 if (this._map.dragging && this._map.dragging.moved()) {
3760 return;
3761 }
3762
3763 this._fireMouseEvent(e);
3764
3765 L.DomEvent.stopPropagation(e);
3766 },
3767
3768 _fireMouseEvent: function (e) {
3769 if (!this.hasEventListeners(e.type)) {
3770 return;
3771 }
3772
3773 if (e.type === 'contextmenu') {
3774 L.DomEvent.preventDefault(e);
3775 }
3776
3777 var map = this._map,
3778 containerPoint = map.mouseEventToContainerPoint(e),
3779 layerPoint = map.containerPointToLayerPoint(containerPoint),
3780 latlng = map.layerPointToLatLng(layerPoint);
3781
3782 this.fire(e.type, {
3783 latlng: latlng,
3784 layerPoint: layerPoint,
3785 containerPoint: containerPoint,
3786 originalEvent: e
3787 });
3788 }
3789 });
3790
3791 L.Map.include({
3792 _initPathRoot: function () {
3793 if (!this._pathRoot) {
3794 this._pathRoot = L.Path.prototype._createElement('svg');
3795 this._panes.overlayPane.appendChild(this._pathRoot);
3796
3797 if (this.options.zoomAnimation && L.Browser.any3d) {
3798 this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
3799
3800 this.on({
3801 'zoomanim': this._animatePathZoom,
3802 'zoomend': this._endPathZoom
3803 });
3804 } else {
3805 this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
3806 }
3807
3808 this.on('moveend', this._updateSvgViewport);
3809 this._updateSvgViewport();
3810 }
3811 },
3812
3813 _animatePathZoom: function (opt) {
3814 var scale = this.getZoomScale(opt.zoom),
3815 offset = this._getCenterOffset(opt.center).divideBy(1 - 1 / scale),
3816 viewportPos = this.containerPointToLayerPoint(this.getSize().multiplyBy(-L.Path.CLIP_PADDING)),
3817 origin = viewportPos.add(offset).round();
3818
3819 this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString((origin.multiplyBy(-1).add(L.DomUtil.getPosition(this._pathRoot)).multiplyBy(scale).add(origin))) + ' scale(' + scale + ') ';
3820
3821 this._pathZooming = true;
3822 },
3823
3824 _endPathZoom: function () {
3825 this._pathZooming = false;
3826 },
3827
3828 _updateSvgViewport: function () {
3829 if (this._pathZooming) {
3830 // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
3831 // When the zoom animation ends we will be updated again anyway
3832 // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
3833 return;
3834 }
3835
3836 this._updatePathViewport();
3837
3838 var vp = this._pathViewport,
3839 min = vp.min,
3840 max = vp.max,
3841 width = max.x - min.x,
3842 height = max.y - min.y,
3843 root = this._pathRoot,
3844 pane = this._panes.overlayPane;
3845
3846 // Hack to make flicker on drag end on mobile webkit less irritating
3847 if (L.Browser.mobileWebkit) {
3848 pane.removeChild(root);
3849 }
3850
3851 L.DomUtil.setPosition(root, min);
3852 root.setAttribute('width', width);
3853 root.setAttribute('height', height);
3854 root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
3855
3856 if (L.Browser.mobileWebkit) {
3857 pane.appendChild(root);
3858 }
3859 }
3860 });
3861
3862
3863 /*
3864 * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
3865 */
3866
3867 L.Path.include({
3868
3869 bindPopup: function (content, options) {
3870
3871 if (!this._popup || this._popup.options !== options) {
3872 this._popup = new L.Popup(options, this);
3873 }
3874
3875 this._popup.setContent(content);
3876
3877 if (!this._openPopupAdded) {
3878 this.on('click', this._openPopup, this);
3879 this._openPopupAdded = true;
3880 }
3881
3882 return this;
3883 },
3884
3885 openPopup: function (latlng) {
3886
3887 if (this._popup) {
3888 latlng = latlng || this._latlng ||
3889 this._latlngs[Math.floor(this._latlngs.length / 2)];
3890
3891 this._openPopup({latlng: latlng});
3892 }
3893
3894 return this;
3895 },
3896
3897 _openPopup: function (e) {
3898 this._popup.setLatLng(e.latlng);
3899 this._map.openPopup(this._popup);
3900 }
3901 });
3902
3903
3904 /*
3905 * Vector rendering for IE6-8 through VML.
3906 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
3907 */
3908
3909 L.Browser.vml = (function () {
3910 try {
3911 var div = document.createElement('div');
3912 div.innerHTML = '<v:shape adj="1"/>';
3913
3914 var shape = div.firstChild;
3915 shape.style.behavior = 'url(#default#VML)';
3916
3917 return shape && (typeof shape.adj === 'object');
3918 } catch (e) {
3919 return false;
3920 }
3921 }());
3922
3923 L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
3924 statics: {
3925 VML: true,
3926 CLIP_PADDING: 0.02
3927 },
3928
3929 _createElement: (function () {
3930 try {
3931 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
3932 return function (name) {
3933 return document.createElement('<lvml:' + name + ' class="lvml">');
3934 };
3935 } catch (e) {
3936 return function (name) {
3937 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
3938 };
3939 }
3940 }()),
3941
3942 _initPath: function () {
3943 var container = this._container = this._createElement('shape');
3944 L.DomUtil.addClass(container, 'leaflet-vml-shape');
3945 if (this.options.clickable) {
3946 L.DomUtil.addClass(container, 'leaflet-clickable');
3947 }
3948 container.coordsize = '1 1';
3949
3950 this._path = this._createElement('path');
3951 container.appendChild(this._path);
3952
3953 this._map._pathRoot.appendChild(container);
3954 },
3955
3956 _initStyle: function () {
3957 this._updateStyle();
3958 },
3959
3960 _updateStyle: function () {
3961 var stroke = this._stroke,
3962 fill = this._fill,
3963 options = this.options,
3964 container = this._container;
3965
3966 container.stroked = options.stroke;
3967 container.filled = options.fill;
3968
3969 if (options.stroke) {
3970 if (!stroke) {
3971 stroke = this._stroke = this._createElement('stroke');
3972 stroke.endcap = 'round';
3973 container.appendChild(stroke);
3974 }
3975 stroke.weight = options.weight + 'px';
3976 stroke.color = options.color;
3977 stroke.opacity = options.opacity;
3978 if (options.dashArray) {
3979 stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
3980 } else {
3981 stroke.dashStyle = '';
3982 }
3983 } else if (stroke) {
3984 container.removeChild(stroke);
3985 this._stroke = null;
3986 }
3987
3988 if (options.fill) {
3989 if (!fill) {
3990 fill = this._fill = this._createElement('fill');
3991 container.appendChild(fill);
3992 }
3993 fill.color = options.fillColor || options.color;
3994 fill.opacity = options.fillOpacity;
3995 } else if (fill) {
3996 container.removeChild(fill);
3997 this._fill = null;
3998 }
3999 },
4000
4001 _updatePath: function () {
4002 var style = this._container.style;
4003
4004 style.display = 'none';
4005 this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
4006 style.display = '';
4007 }
4008 });
4009
4010 L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
4011 _initPathRoot: function () {
4012 if (this._pathRoot) { return; }
4013
4014 var root = this._pathRoot = document.createElement('div');
4015 root.className = 'leaflet-vml-container';
4016 this._panes.overlayPane.appendChild(root);
4017
4018 this.on('moveend', this._updatePathViewport);
4019 this._updatePathViewport();
4020 }
4021 });
4022
4023
4024 /*
4025 * Vector rendering for all browsers that support canvas.
4026 */
4027
4028 L.Browser.canvas = (function () {
4029 return !!document.createElement('canvas').getContext;
4030 }());
4031
4032 L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
4033 statics: {
4034 //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
4035 CANVAS: true,
4036 SVG: false
4037 },
4038
4039 redraw: function () {
4040 if (this._map) {
4041 this.projectLatlngs();
4042 this._requestUpdate();
4043 }
4044 return this;
4045 },
4046
4047 setStyle: function (style) {
4048 L.Util.setOptions(this, style);
4049
4050 if (this._map) {
4051 this._updateStyle();
4052 this._requestUpdate();
4053 }
4054 return this;
4055 },
4056
4057 onRemove: function (map) {
4058 map
4059 .off('viewreset', this.projectLatlngs, this)
4060 .off('moveend', this._updatePath, this);
4061
4062 this._requestUpdate();
4063
4064 this._map = null;
4065 },
4066
4067 _requestUpdate: function () {
4068 if (this._map) {
4069 L.Util.cancelAnimFrame(this._fireMapMoveEnd);
4070 this._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
4071 }
4072 },
4073
4074 _fireMapMoveEnd: function () {
4075 this.fire('moveend');
4076 },
4077
4078 _initElements: function () {
4079 this._map._initPathRoot();
4080 this._ctx = this._map._canvasCtx;
4081 },
4082
4083 _updateStyle: function () {
4084 var options = this.options;
4085
4086 if (options.stroke) {
4087 this._ctx.lineWidth = options.weight;
4088 this._ctx.strokeStyle = options.color;
4089 }
4090 if (options.fill) {
4091 this._ctx.fillStyle = options.fillColor || options.color;
4092 }
4093 },
4094
4095 _drawPath: function () {
4096 var i, j, len, len2, point, drawMethod;
4097
4098 this._ctx.beginPath();
4099
4100 for (i = 0, len = this._parts.length; i < len; i++) {
4101 for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
4102 point = this._parts[i][j];
4103 drawMethod = (j === 0 ? 'move' : 'line') + 'To';
4104
4105 this._ctx[drawMethod](point.x, point.y);
4106 }
4107 // TODO refactor ugly hack
4108 if (this instanceof L.Polygon) {
4109 this._ctx.closePath();
4110 }
4111 }
4112 },
4113
4114 _checkIfEmpty: function () {
4115 return !this._parts.length;
4116 },
4117
4118 _updatePath: function () {
4119 if (this._checkIfEmpty()) { return; }
4120
4121 var ctx = this._ctx,
4122 options = this.options;
4123
4124 this._drawPath();
4125 ctx.save();
4126 this._updateStyle();
4127
4128 if (options.fill) {
4129 if (options.fillOpacity < 1) {
4130 ctx.globalAlpha = options.fillOpacity;
4131 }
4132 ctx.fill();
4133 }
4134
4135 if (options.stroke) {
4136 if (options.opacity < 1) {
4137 ctx.globalAlpha = options.opacity;
4138 }
4139 ctx.stroke();
4140 }
4141
4142 ctx.restore();
4143
4144 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
4145 },
4146
4147 _initEvents: function () {
4148 if (this.options.clickable) {
4149 // TODO hand cursor
4150 // TODO mouseover, mouseout, dblclick
4151 this._map.on('click', this._onClick, this);
4152 }
4153 },
4154
4155 _onClick: function (e) {
4156 if (this._containsPoint(e.layerPoint)) {
4157 this.fire('click', e);
4158 }
4159 }
4160 });
4161
4162 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
4163 _initPathRoot: function () {
4164 var root = this._pathRoot,
4165 ctx;
4166
4167 if (!root) {
4168 root = this._pathRoot = document.createElement("canvas");
4169 root.style.position = 'absolute';
4170 ctx = this._canvasCtx = root.getContext('2d');
4171
4172 ctx.lineCap = "round";
4173 ctx.lineJoin = "round";
4174
4175 this._panes.overlayPane.appendChild(root);
4176
4177 if (this.options.zoomAnimation) {
4178 this._pathRoot.className = 'leaflet-zoom-animated';
4179 this.on('zoomanim', this._animatePathZoom);
4180 this.on('zoomend', this._endPathZoom);
4181 }
4182 this.on('moveend', this._updateCanvasViewport);
4183 this._updateCanvasViewport();
4184 }
4185 },
4186
4187 _updateCanvasViewport: function () {
4188 if (this._pathZooming) {
4189 //Don't redraw while zooming. See _updateSvgViewport for more details
4190 return;
4191 }
4192 this._updatePathViewport();
4193
4194 var vp = this._pathViewport,
4195 min = vp.min,
4196 size = vp.max.subtract(min),
4197 root = this._pathRoot;
4198
4199 //TODO check if this works properly on mobile webkit
4200 L.DomUtil.setPosition(root, min);
4201 root.width = size.x;
4202 root.height = size.y;
4203 root.getContext('2d').translate(-min.x, -min.y);
4204 }
4205 });
4206
4207
4208 /*
4209 * L.LineUtil contains different utility functions for line segments
4210 * and polylines (clipping, simplification, distances, etc.)
4211 */
4212
4213 L.LineUtil = {
4214
4215 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
4216 // Improves rendering performance dramatically by lessening the number of points to draw.
4217
4218 simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
4219 if (!tolerance || !points.length) {
4220 return points.slice();
4221 }
4222
4223 var sqTolerance = tolerance * tolerance;
4224
4225 // stage 1: vertex reduction
4226 points = this._reducePoints(points, sqTolerance);
4227
4228 // stage 2: Douglas-Peucker simplification
4229 points = this._simplifyDP(points, sqTolerance);
4230
4231 return points;
4232 },
4233
4234 // distance from a point to a segment between two points
4235 pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
4236 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
4237 },
4238
4239 closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
4240 return this._sqClosestPointOnSegment(p, p1, p2);
4241 },
4242
4243 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
4244 _simplifyDP: function (points, sqTolerance) {
4245
4246 var len = points.length,
4247 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
4248 markers = new ArrayConstructor(len);
4249
4250 markers[0] = markers[len - 1] = 1;
4251
4252 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
4253
4254 var i,
4255 newPoints = [];
4256
4257 for (i = 0; i < len; i++) {
4258 if (markers[i]) {
4259 newPoints.push(points[i]);
4260 }
4261 }
4262
4263 return newPoints;
4264 },
4265
4266 _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
4267
4268 var maxSqDist = 0,
4269 index, i, sqDist;
4270
4271 for (i = first + 1; i <= last - 1; i++) {
4272 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
4273
4274 if (sqDist > maxSqDist) {
4275 index = i;
4276 maxSqDist = sqDist;
4277 }
4278 }
4279
4280 if (maxSqDist > sqTolerance) {
4281 markers[index] = 1;
4282
4283 this._simplifyDPStep(points, markers, sqTolerance, first, index);
4284 this._simplifyDPStep(points, markers, sqTolerance, index, last);
4285 }
4286 },
4287
4288 // reduce points that are too close to each other to a single point
4289 _reducePoints: function (points, sqTolerance) {
4290 var reducedPoints = [points[0]];
4291
4292 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
4293 if (this._sqDist(points[i], points[prev]) > sqTolerance) {
4294 reducedPoints.push(points[i]);
4295 prev = i;
4296 }
4297 }
4298 if (prev < len - 1) {
4299 reducedPoints.push(points[len - 1]);
4300 }
4301 return reducedPoints;
4302 },
4303
4304 /*jshint bitwise:false */ // temporarily allow bitwise oprations
4305
4306 // Cohen-Sutherland line clipping algorithm.
4307 // Used to avoid rendering parts of a polyline that are not currently visible.
4308
4309 clipSegment: function (a, b, bounds, useLastCode) {
4310 var min = bounds.min,
4311 max = bounds.max;
4312
4313 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
4314 codeB = this._getBitCode(b, bounds);
4315
4316 // save 2nd code to avoid calculating it on the next segment
4317 this._lastCode = codeB;
4318
4319 while (true) {
4320 // if a,b is inside the clip window (trivial accept)
4321 if (!(codeA | codeB)) {
4322 return [a, b];
4323 // if a,b is outside the clip window (trivial reject)
4324 } else if (codeA & codeB) {
4325 return false;
4326 // other cases
4327 } else {
4328 var codeOut = codeA || codeB,
4329 p = this._getEdgeIntersection(a, b, codeOut, bounds),
4330 newCode = this._getBitCode(p, bounds);
4331
4332 if (codeOut === codeA) {
4333 a = p;
4334 codeA = newCode;
4335 } else {
4336 b = p;
4337 codeB = newCode;
4338 }
4339 }
4340 }
4341 },
4342
4343 _getEdgeIntersection: function (a, b, code, bounds) {
4344 var dx = b.x - a.x,
4345 dy = b.y - a.y,
4346 min = bounds.min,
4347 max = bounds.max;
4348
4349 if (code & 8) { // top
4350 return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
4351 } else if (code & 4) { // bottom
4352 return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
4353 } else if (code & 2) { // right
4354 return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
4355 } else if (code & 1) { // left
4356 return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
4357 }
4358 },
4359
4360 _getBitCode: function (/*Point*/ p, bounds) {
4361 var code = 0;
4362
4363 if (p.x < bounds.min.x) { // left
4364 code |= 1;
4365 } else if (p.x > bounds.max.x) { // right
4366 code |= 2;
4367 }
4368 if (p.y < bounds.min.y) { // bottom
4369 code |= 4;
4370 } else if (p.y > bounds.max.y) { // top
4371 code |= 8;
4372 }
4373
4374 return code;
4375 },
4376
4377 /*jshint bitwise:true */
4378
4379 // square distance (to avoid unnecessary Math.sqrt calls)
4380 _sqDist: function (p1, p2) {
4381 var dx = p2.x - p1.x,
4382 dy = p2.y - p1.y;
4383 return dx * dx + dy * dy;
4384 },
4385
4386 // return closest point on segment or distance to that point
4387 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
4388 var x = p1.x,
4389 y = p1.y,
4390 dx = p2.x - x,
4391 dy = p2.y - y,
4392 dot = dx * dx + dy * dy,
4393 t;
4394
4395 if (dot > 0) {
4396 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
4397
4398 if (t > 1) {
4399 x = p2.x;
4400 y = p2.y;
4401 } else if (t > 0) {
4402 x += dx * t;
4403 y += dy * t;
4404 }
4405 }
4406
4407 dx = p.x - x;
4408 dy = p.y - y;
4409
4410 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
4411 }
4412 };
4413
4414
4415 L.Polyline = L.Path.extend({
4416 initialize: function (latlngs, options) {
4417 L.Path.prototype.initialize.call(this, options);
4418
4419 this._latlngs = this._convertLatLngs(latlngs);
4420
4421 // TODO refactor: move to Polyline.Edit.js
4422 if (L.Handler.PolyEdit) {
4423 this.editing = new L.Handler.PolyEdit(this);
4424
4425 if (this.options.editable) {
4426 this.editing.enable();
4427 }
4428 }
4429 },
4430
4431 options: {
4432 // how much to simplify the polyline on each zoom level
4433 // more = better performance and smoother look, less = more accurate
4434 smoothFactor: 1.0,
4435 noClip: false
4436 },
4437
4438 projectLatlngs: function () {
4439 this._originalPoints = [];
4440
4441 for (var i = 0, len = this._latlngs.length; i < len; i++) {
4442 this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
4443 }
4444 },
4445
4446 getPathString: function () {
4447 for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
4448 str += this._getPathPartStr(this._parts[i]);
4449 }
4450 return str;
4451 },
4452
4453 getLatLngs: function () {
4454 return this._latlngs;
4455 },
4456
4457 setLatLngs: function (latlngs) {
4458 this._latlngs = this._convertLatLngs(latlngs);
4459 return this.redraw();
4460 },
4461
4462 addLatLng: function (latlng) {
4463 this._latlngs.push(L.latLng(latlng));
4464 return this.redraw();
4465 },
4466
4467 spliceLatLngs: function (index, howMany) {
4468 var removed = [].splice.apply(this._latlngs, arguments);
4469 this._convertLatLngs(this._latlngs);
4470 this.redraw();
4471 return removed;
4472 },
4473
4474 closestLayerPoint: function (p) {
4475 var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
4476
4477 for (var j = 0, jLen = parts.length; j < jLen; j++) {
4478 var points = parts[j];
4479 for (var i = 1, len = points.length; i < len; i++) {
4480 p1 = points[i - 1];
4481 p2 = points[i];
4482 var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
4483 if (sqDist < minDistance) {
4484 minDistance = sqDist;
4485 minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
4486 }
4487 }
4488 }
4489 if (minPoint) {
4490 minPoint.distance = Math.sqrt(minDistance);
4491 }
4492 return minPoint;
4493 },
4494
4495 getBounds: function () {
4496 var b = new L.LatLngBounds();
4497 var latLngs = this.getLatLngs();
4498 for (var i = 0, len = latLngs.length; i < len; i++) {
4499 b.extend(latLngs[i]);
4500 }
4501 return b;
4502 },
4503
4504 // TODO refactor: move to Polyline.Edit.js
4505 onAdd: function (map) {
4506 L.Path.prototype.onAdd.call(this, map);
4507
4508 if (this.editing && this.editing.enabled()) {
4509 this.editing.addHooks();
4510 }
4511 },
4512
4513 onRemove: function (map) {
4514 if (this.editing && this.editing.enabled()) {
4515 this.editing.removeHooks();
4516 }
4517
4518 L.Path.prototype.onRemove.call(this, map);
4519 },
4520
4521 _convertLatLngs: function (latlngs) {
4522 var i, len;
4523 for (i = 0, len = latlngs.length; i < len; i++) {
4524 if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
4525 return;
4526 }
4527 latlngs[i] = L.latLng(latlngs[i]);
4528 }
4529 return latlngs;
4530 },
4531
4532 _initEvents: function () {
4533 L.Path.prototype._initEvents.call(this);
4534 },
4535
4536 _getPathPartStr: function (points) {
4537 var round = L.Path.VML;
4538
4539 for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
4540 p = points[j];
4541 if (round) {
4542 p._round();
4543 }
4544 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
4545 }
4546 return str;
4547 },
4548
4549 _clipPoints: function () {
4550 var points = this._originalPoints,
4551 len = points.length,
4552 i, k, segment;
4553
4554 if (this.options.noClip) {
4555 this._parts = [points];
4556 return;
4557 }
4558
4559 this._parts = [];
4560
4561 var parts = this._parts,
4562 vp = this._map._pathViewport,
4563 lu = L.LineUtil;
4564
4565 for (i = 0, k = 0; i < len - 1; i++) {
4566 segment = lu.clipSegment(points[i], points[i + 1], vp, i);
4567 if (!segment) {
4568 continue;
4569 }
4570
4571 parts[k] = parts[k] || [];
4572 parts[k].push(segment[0]);
4573
4574 // if segment goes out of screen, or it's the last one, it's the end of the line part
4575 if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
4576 parts[k].push(segment[1]);
4577 k++;
4578 }
4579 }
4580 },
4581
4582 // simplify each clipped part of the polyline
4583 _simplifyPoints: function () {
4584 var parts = this._parts,
4585 lu = L.LineUtil;
4586
4587 for (var i = 0, len = parts.length; i < len; i++) {
4588 parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
4589 }
4590 },
4591
4592 _updatePath: function () {
4593 if (!this._map) { return; }
4594
4595 this._clipPoints();
4596 this._simplifyPoints();
4597
4598 L.Path.prototype._updatePath.call(this);
4599 }
4600 });
4601
4602 L.polyline = function (latlngs, options) {
4603 return new L.Polyline(latlngs, options);
4604 };
4605
4606
4607 /*
4608 * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
4609 */
4610
4611 /*jshint bitwise:false */ // allow bitwise oprations here
4612
4613 L.PolyUtil = {};
4614
4615 /*
4616 * Sutherland-Hodgeman polygon clipping algorithm.
4617 * Used to avoid rendering parts of a polygon that are not currently visible.
4618 */
4619 L.PolyUtil.clipPolygon = function (points, bounds) {
4620 var min = bounds.min,
4621 max = bounds.max,
4622 clippedPoints,
4623 edges = [1, 4, 2, 8],
4624 i, j, k,
4625 a, b,
4626 len, edge, p,
4627 lu = L.LineUtil;
4628
4629 for (i = 0, len = points.length; i < len; i++) {
4630 points[i]._code = lu._getBitCode(points[i], bounds);
4631 }
4632
4633 // for each edge (left, bottom, right, top)
4634 for (k = 0; k < 4; k++) {
4635 edge = edges[k];
4636 clippedPoints = [];
4637
4638 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
4639 a = points[i];
4640 b = points[j];
4641
4642 // if a is inside the clip window
4643 if (!(a._code & edge)) {
4644 // if b is outside the clip window (a->b goes out of screen)
4645 if (b._code & edge) {
4646 p = lu._getEdgeIntersection(b, a, edge, bounds);
4647 p._code = lu._getBitCode(p, bounds);
4648 clippedPoints.push(p);
4649 }
4650 clippedPoints.push(a);
4651
4652 // else if b is inside the clip window (a->b enters the screen)
4653 } else if (!(b._code & edge)) {
4654 p = lu._getEdgeIntersection(b, a, edge, bounds);
4655 p._code = lu._getBitCode(p, bounds);
4656 clippedPoints.push(p);
4657 }
4658 }
4659 points = clippedPoints;
4660 }
4661
4662 return points;
4663 };
4664
4665 /*jshint bitwise:true */
4666
4667
4668 /*
4669 * L.Polygon is used to display polygons on a map.
4670 */
4671
4672 L.Polygon = L.Polyline.extend({
4673 options: {
4674 fill: true
4675 },
4676
4677 initialize: function (latlngs, options) {
4678 L.Polyline.prototype.initialize.call(this, latlngs, options);
4679
4680 if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
4681 this._latlngs = this._convertLatLngs(latlngs[0]);
4682 this._holes = latlngs.slice(1);
4683 }
4684 },
4685
4686 projectLatlngs: function () {
4687 L.Polyline.prototype.projectLatlngs.call(this);
4688
4689 // project polygon holes points
4690 // TODO move this logic to Polyline to get rid of duplication
4691 this._holePoints = [];
4692
4693 if (!this._holes) {
4694 return;
4695 }
4696
4697 for (var i = 0, len = this._holes.length, hole; i < len; i++) {
4698 this._holePoints[i] = [];
4699
4700 for (var j = 0, len2 = this._holes[i].length; j < len2; j++) {
4701 this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
4702 }
4703 }
4704 },
4705
4706 _clipPoints: function () {
4707 var points = this._originalPoints,
4708 newParts = [];
4709
4710 this._parts = [points].concat(this._holePoints);
4711
4712 if (this.options.noClip) {
4713 return;
4714 }
4715
4716 for (var i = 0, len = this._parts.length; i < len; i++) {
4717 var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
4718 if (!clipped.length) {
4719 continue;
4720 }
4721 newParts.push(clipped);
4722 }
4723
4724 this._parts = newParts;
4725 },
4726
4727 _getPathPartStr: function (points) {
4728 var str = L.Polyline.prototype._getPathPartStr.call(this, points);
4729 return str + (L.Browser.svg ? 'z' : 'x');
4730 }
4731 });
4732
4733 L.polygon = function (latlngs, options) {
4734 return new L.Polygon(latlngs, options);
4735 };
4736
4737
4738 /*
4739 * Contains L.MultiPolyline and L.MultiPolygon layers.
4740 */
4741
4742 (function () {
4743 function createMulti(Klass) {
4744 return L.FeatureGroup.extend({
4745 initialize: function (latlngs, options) {
4746 this._layers = {};
4747 this._options = options;
4748 this.setLatLngs(latlngs);
4749 },
4750
4751 setLatLngs: function (latlngs) {
4752 var i = 0, len = latlngs.length;
4753
4754 this.eachLayer(function (layer) {
4755 if (i < len) {
4756 layer.setLatLngs(latlngs[i++]);
4757 } else {
4758 this.removeLayer(layer);
4759 }
4760 }, this);
4761
4762 while (i < len) {
4763 this.addLayer(new Klass(latlngs[i++], this._options));
4764 }
4765
4766 return this;
4767 }
4768 });
4769 }
4770
4771 L.MultiPolyline = createMulti(L.Polyline);
4772 L.MultiPolygon = createMulti(L.Polygon);
4773
4774 L.multiPolyline = function (latlngs, options) {
4775 return new L.MultiPolyline(latlngs, options);
4776 };
4777
4778 L.multiPolygon = function (latlngs, options) {
4779 return new L.MultiPolygon(latlngs, options);
4780 };
4781 }());
4782
4783
4784 /*
4785 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
4786 */
4787
4788 L.Rectangle = L.Polygon.extend({
4789 initialize: function (latLngBounds, options) {
4790 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
4791 },
4792
4793 setBounds: function (latLngBounds) {
4794 this.setLatLngs(this._boundsToLatLngs(latLngBounds));
4795 },
4796
4797 _boundsToLatLngs: function (latLngBounds) {
4798 latLngBounds = L.latLngBounds(latLngBounds);
4799 return [
4800 latLngBounds.getSouthWest(),
4801 latLngBounds.getNorthWest(),
4802 latLngBounds.getNorthEast(),
4803 latLngBounds.getSouthEast(),
4804 latLngBounds.getSouthWest()
4805 ];
4806 }
4807 });
4808
4809 L.rectangle = function (latLngBounds, options) {
4810 return new L.Rectangle(latLngBounds, options);
4811 };
4812
4813
4814 /*
4815 * L.Circle is a circle overlay (with a certain radius in meters).
4816 */
4817
4818 L.Circle = L.Path.extend({
4819 initialize: function (latlng, radius, options) {
4820 L.Path.prototype.initialize.call(this, options);
4821
4822 this._latlng = L.latLng(latlng);
4823 this._mRadius = radius;
4824 },
4825
4826 options: {
4827 fill: true
4828 },
4829
4830 setLatLng: function (latlng) {
4831 this._latlng = L.latLng(latlng);
4832 return this.redraw();
4833 },
4834
4835 setRadius: function (radius) {
4836 this._mRadius = radius;
4837 return this.redraw();
4838 },
4839
4840 projectLatlngs: function () {
4841 var lngRadius = this._getLngRadius(),
4842 latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
4843 point2 = this._map.latLngToLayerPoint(latlng2);
4844
4845 this._point = this._map.latLngToLayerPoint(this._latlng);
4846 this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
4847 },
4848
4849 getBounds: function () {
4850 var map = this._map,
4851 delta = this._radius * Math.cos(Math.PI / 4),
4852 point = map.project(this._latlng),
4853 swPoint = new L.Point(point.x - delta, point.y + delta),
4854 nePoint = new L.Point(point.x + delta, point.y - delta),
4855 sw = map.unproject(swPoint),
4856 ne = map.unproject(nePoint);
4857
4858 return new L.LatLngBounds(sw, ne);
4859 },
4860
4861 getLatLng: function () {
4862 return this._latlng;
4863 },
4864
4865 getPathString: function () {
4866 var p = this._point,
4867 r = this._radius;
4868
4869 if (this._checkIfEmpty()) {
4870 return '';
4871 }
4872
4873 if (L.Browser.svg) {
4874 return "M" + p.x + "," + (p.y - r) +
4875 "A" + r + "," + r + ",0,1,1," +
4876 (p.x - 0.1) + "," + (p.y - r) + " z";
4877 } else {
4878 p._round();
4879 r = Math.round(r);
4880 return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
4881 }
4882 },
4883
4884 getRadius: function () {
4885 return this._mRadius;
4886 },
4887
4888 _getLngRadius: function () {
4889 var equatorLength = 40075017,
4890 hLength = equatorLength * Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
4891
4892 return (this._mRadius / hLength) * 360;
4893 },
4894
4895 _checkIfEmpty: function () {
4896 if (!this._map) {
4897 return false;
4898 }
4899 var vp = this._map._pathViewport,
4900 r = this._radius,
4901 p = this._point;
4902
4903 return p.x - r > vp.max.x || p.y - r > vp.max.y ||
4904 p.x + r < vp.min.x || p.y + r < vp.min.y;
4905 }
4906 });
4907
4908 L.circle = function (latlng, radius, options) {
4909 return new L.Circle(latlng, radius, options);
4910 };
4911
4912
4913 /*
4914 * L.CircleMarker is a circle overlay with a permanent pixel radius.
4915 */
4916
4917 L.CircleMarker = L.Circle.extend({
4918 options: {
4919 radius: 10,
4920 weight: 2
4921 },
4922
4923 initialize: function (latlng, options) {
4924 L.Circle.prototype.initialize.call(this, latlng, null, options);
4925 this._radius = this.options.radius;
4926 },
4927
4928 projectLatlngs: function () {
4929 this._point = this._map.latLngToLayerPoint(this._latlng);
4930 },
4931
4932 setRadius: function (radius) {
4933 this._radius = radius;
4934 return this.redraw();
4935 }
4936 });
4937
4938 L.circleMarker = function (latlng, options) {
4939 return new L.CircleMarker(latlng, options);
4940 };
4941
4942
4943
4944 L.Polyline.include(!L.Path.CANVAS ? {} : {
4945 _containsPoint: function (p, closed) {
4946 var i, j, k, len, len2, dist, part,
4947 w = this.options.weight / 2;
4948
4949 if (L.Browser.touch) {
4950 w += 10; // polyline click tolerance on touch devices
4951 }
4952
4953 for (i = 0, len = this._parts.length; i < len; i++) {
4954 part = this._parts[i];
4955 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
4956 if (!closed && (j === 0)) {
4957 continue;
4958 }
4959
4960 dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
4961
4962 if (dist <= w) {
4963 return true;
4964 }
4965 }
4966 }
4967 return false;
4968 }
4969 });
4970
4971
4972
4973 L.Polygon.include(!L.Path.CANVAS ? {} : {
4974 _containsPoint: function (p) {
4975 var inside = false,
4976 part, p1, p2,
4977 i, j, k,
4978 len, len2;
4979
4980 // TODO optimization: check if within bounds first
4981
4982 if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
4983 // click on polygon border
4984 return true;
4985 }
4986
4987 // ray casting algorithm for detecting if point is in polygon
4988
4989 for (i = 0, len = this._parts.length; i < len; i++) {
4990 part = this._parts[i];
4991
4992 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
4993 p1 = part[j];
4994 p2 = part[k];
4995
4996 if (((p1.y > p.y) !== (p2.y > p.y)) &&
4997 (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
4998 inside = !inside;
4999 }
5000 }
5001 }
5002
5003 return inside;
5004 }
5005 });
5006
5007
5008 /*
5009 * Circle canvas specific drawing parts.
5010 */
5011
5012 L.Circle.include(!L.Path.CANVAS ? {} : {
5013 _drawPath: function () {
5014 var p = this._point;
5015 this._ctx.beginPath();
5016 this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
5017 },
5018
5019 _containsPoint: function (p) {
5020 var center = this._point,
5021 w2 = this.options.stroke ? this.options.weight / 2 : 0;
5022
5023 return (p.distanceTo(center) <= this._radius + w2);
5024 }
5025 });
5026
5027
5028 L.GeoJSON = L.FeatureGroup.extend({
5029 initialize: function (geojson, options) {
5030 L.Util.setOptions(this, options);
5031
5032 this._layers = {};
5033
5034 if (geojson) {
5035 this.addData(geojson);
5036 }
5037 },
5038
5039 addData: function (geojson) {
5040 var features = geojson instanceof Array ? geojson : geojson.features,
5041 i, len;
5042
5043 if (features) {
5044 for (i = 0, len = features.length; i < len; i++) {
5045 this.addData(features[i]);
5046 }
5047 return this;
5048 }
5049
5050 var options = this.options,
5051 style = options.style;
5052
5053 if (options.filter && !options.filter(geojson)) { return; }
5054
5055 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
5056
5057 if (style) {
5058 if (typeof style === 'function') {
5059 style = style(geojson);
5060 }
5061 if (layer.setStyle) {
5062 layer.setStyle(style);
5063 }
5064 }
5065
5066 if (options.onEachFeature) {
5067 options.onEachFeature(geojson, layer);
5068 }
5069
5070 return this.addLayer(layer);
5071 }
5072 });
5073
5074 L.Util.extend(L.GeoJSON, {
5075 geometryToLayer: function (geojson, pointToLayer) {
5076 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
5077 coords = geometry.coordinates,
5078 layers = [],
5079 latlng, latlngs, i, len, layer;
5080
5081 switch (geometry.type) {
5082 case 'Point':
5083 latlng = this.coordsToLatLng(coords);
5084 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
5085
5086 case 'MultiPoint':
5087 for (i = 0, len = coords.length; i < len; i++) {
5088 latlng = this.coordsToLatLng(coords[i]);
5089 layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
5090 layers.push(layer);
5091 }
5092 return new L.FeatureGroup(layers);
5093
5094 case 'LineString':
5095 latlngs = this.coordsToLatLngs(coords);
5096 return new L.Polyline(latlngs);
5097
5098 case 'Polygon':
5099 latlngs = this.coordsToLatLngs(coords, 1);
5100 return new L.Polygon(latlngs);
5101
5102 case 'MultiLineString':
5103 latlngs = this.coordsToLatLngs(coords, 1);
5104 return new L.MultiPolyline(latlngs);
5105
5106 case "MultiPolygon":
5107 latlngs = this.coordsToLatLngs(coords, 2);
5108 return new L.MultiPolygon(latlngs);
5109
5110 case "GeometryCollection":
5111 for (i = 0, len = geometry.geometries.length; i < len; i++) {
5112 layer = this.geometryToLayer(geometry.geometries[i], pointToLayer);
5113 layers.push(layer);
5114 }
5115 return new L.FeatureGroup(layers);
5116
5117 default:
5118 throw new Error('Invalid GeoJSON object.');
5119 }
5120 },
5121
5122 coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
5123 var lat = parseFloat(coords[reverse ? 0 : 1]),
5124 lng = parseFloat(coords[reverse ? 1 : 0]);
5125
5126 return new L.LatLng(lat, lng, true);
5127 },
5128
5129 coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
5130 var latlng,
5131 latlngs = [],
5132 i, len;
5133
5134 for (i = 0, len = coords.length; i < len; i++) {
5135 latlng = levelsDeep ?
5136 this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
5137 this.coordsToLatLng(coords[i], reverse);
5138
5139 latlngs.push(latlng);
5140 }
5141
5142 return latlngs;
5143 }
5144 });
5145
5146 L.geoJson = function (geojson, options) {
5147 return new L.GeoJSON(geojson, options);
5148 };
5149
5150 /*
5151 * L.DomEvent contains functions for working with DOM events.
5152 */
5153
5154 L.DomEvent = {
5155 /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
5156 addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
5157
5158 var id = L.Util.stamp(fn),
5159 key = '_leaflet_' + type + id,
5160 handler, originalHandler, newType;
5161
5162 if (obj[key]) { return this; }
5163
5164 handler = function (e) {
5165 return fn.call(context || obj, e || L.DomEvent._getEvent());
5166 };
5167
5168 if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
5169 return this.addDoubleTapListener(obj, handler, id);
5170
5171 } else if ('addEventListener' in obj) {
5172
5173 if (type === 'mousewheel') {
5174 obj.addEventListener('DOMMouseScroll', handler, false);
5175 obj.addEventListener(type, handler, false);
5176
5177 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
5178
5179 originalHandler = handler;
5180 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
5181
5182 handler = function (e) {
5183 if (!L.DomEvent._checkMouse(obj, e)) { return; }
5184 return originalHandler(e);
5185 };
5186
5187 obj.addEventListener(newType, handler, false);
5188
5189 } else {
5190 obj.addEventListener(type, handler, false);
5191 }
5192
5193 } else if ('attachEvent' in obj) {
5194 obj.attachEvent("on" + type, handler);
5195 }
5196
5197 obj[key] = handler;
5198
5199 return this;
5200 },
5201
5202 removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
5203
5204 var id = L.Util.stamp(fn),
5205 key = '_leaflet_' + type + id,
5206 handler = obj[key];
5207
5208 if (!handler) { return; }
5209
5210 if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
5211 this.removeDoubleTapListener(obj, id);
5212
5213 } else if ('removeEventListener' in obj) {
5214
5215 if (type === 'mousewheel') {
5216 obj.removeEventListener('DOMMouseScroll', handler, false);
5217 obj.removeEventListener(type, handler, false);
5218
5219 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
5220 obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
5221 } else {
5222 obj.removeEventListener(type, handler, false);
5223 }
5224 } else if ('detachEvent' in obj) {
5225 obj.detachEvent("on" + type, handler);
5226 }
5227
5228 obj[key] = null;
5229
5230 return this;
5231 },
5232
5233 stopPropagation: function (e) {
5234
5235 if (e.stopPropagation) {
5236 e.stopPropagation();
5237 } else {
5238 e.cancelBubble = true;
5239 }
5240 return this;
5241 },
5242
5243 disableClickPropagation: function (el) {
5244
5245 var stop = L.DomEvent.stopPropagation;
5246
5247 return L.DomEvent
5248 .addListener(el, L.Draggable.START, stop)
5249 .addListener(el, 'click', stop)
5250 .addListener(el, 'dblclick', stop);
5251 },
5252
5253 preventDefault: function (e) {
5254
5255 if (e.preventDefault) {
5256 e.preventDefault();
5257 } else {
5258 e.returnValue = false;
5259 }
5260 return this;
5261 },
5262
5263 stop: function (e) {
5264 return L.DomEvent.preventDefault(e).stopPropagation(e);
5265 },
5266
5267 getMousePosition: function (e, container) {
5268
5269 var body = document.body,
5270 docEl = document.documentElement,
5271 x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
5272 y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
5273 pos = new L.Point(x, y);
5274
5275 return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
5276 },
5277
5278 getWheelDelta: function (e) {
5279
5280 var delta = 0;
5281
5282 if (e.wheelDelta) {
5283 delta = e.wheelDelta / 120;
5284 }
5285 if (e.detail) {
5286 delta = -e.detail / 3;
5287 }
5288 return delta;
5289 },
5290
5291 // check if element really left/entered the event target (for mouseenter/mouseleave)
5292 _checkMouse: function (el, e) {
5293
5294 var related = e.relatedTarget;
5295
5296 if (!related) { return true; }
5297
5298 try {
5299 while (related && (related !== el)) {
5300 related = related.parentNode;
5301 }
5302 } catch (err) {
5303 return false;
5304 }
5305 return (related !== el);
5306 },
5307
5308 /*jshint noarg:false */
5309 _getEvent: function () { // evil magic for IE
5310
5311 var e = window.event;
5312 if (!e) {
5313 var caller = arguments.callee.caller;
5314 while (caller) {
5315 e = caller['arguments'][0];
5316 if (e && window.Event === e.constructor) {
5317 break;
5318 }
5319 caller = caller.caller;
5320 }
5321 }
5322 return e;
5323 }
5324 /*jshint noarg:false */
5325 };
5326
5327 L.DomEvent.on = L.DomEvent.addListener;
5328 L.DomEvent.off = L.DomEvent.removeListener;
5329
5330 /*
5331 * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
5332 */
5333
5334 L.Draggable = L.Class.extend({
5335 includes: L.Mixin.Events,
5336
5337 statics: {
5338 START: L.Browser.touch ? 'touchstart' : 'mousedown',
5339 END: L.Browser.touch ? 'touchend' : 'mouseup',
5340 MOVE: L.Browser.touch ? 'touchmove' : 'mousemove',
5341 TAP_TOLERANCE: 15
5342 },
5343
5344 initialize: function (element, dragStartTarget) {
5345 this._element = element;
5346 this._dragStartTarget = dragStartTarget || element;
5347 },
5348
5349 enable: function () {
5350 if (this._enabled) {
5351 return;
5352 }
5353 L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
5354 this._enabled = true;
5355 },
5356
5357 disable: function () {
5358 if (!this._enabled) {
5359 return;
5360 }
5361 L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
5362 this._enabled = false;
5363 this._moved = false;
5364 },
5365
5366 _onDown: function (e) {
5367 if ((!L.Browser.touch && e.shiftKey) || ((e.which !== 1) && (e.button !== 1) && !e.touches)) {
5368 return;
5369 }
5370
5371 this._simulateClick = true;
5372
5373 if (e.touches && e.touches.length > 1) {
5374 this._simulateClick = false;
5375 return;
5376 }
5377
5378 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5379 el = first.target;
5380
5381 L.DomEvent.preventDefault(e);
5382
5383 if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
5384 L.DomUtil.addClass(el, 'leaflet-active');
5385 }
5386
5387 this._moved = false;
5388 if (this._moving) {
5389 return;
5390 }
5391
5392 if (!L.Browser.touch) {
5393 L.DomUtil.disableTextSelection();
5394 this._setMovingCursor();
5395 }
5396
5397 this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
5398 this._startPoint = new L.Point(first.clientX, first.clientY);
5399
5400 L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
5401 L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
5402 },
5403
5404 _onMove: function (e) {
5405 if (e.touches && e.touches.length > 1) { return; }
5406
5407 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5408 newPoint = new L.Point(first.clientX, first.clientY),
5409 diffVec = newPoint.subtract(this._startPoint);
5410
5411 if (!diffVec.x && !diffVec.y) { return; }
5412
5413 L.DomEvent.preventDefault(e);
5414
5415 if (!this._moved) {
5416 this.fire('dragstart');
5417 this._moved = true;
5418 }
5419
5420 this._newPos = this._startPos.add(diffVec);
5421 this._moving = true;
5422
5423 L.Util.cancelAnimFrame(this._animRequest);
5424 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
5425 },
5426
5427 _updatePosition: function () {
5428 this.fire('predrag');
5429 L.DomUtil.setPosition(this._element, this._newPos);
5430 this.fire('drag');
5431 },
5432
5433 _onUp: function (e) {
5434 if (this._simulateClick && e.changedTouches) {
5435 var first = e.changedTouches[0],
5436 el = first.target,
5437 dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
5438
5439 if (el.tagName.toLowerCase() === 'a') {
5440 L.DomUtil.removeClass(el, 'leaflet-active');
5441 }
5442
5443 if (dist < L.Draggable.TAP_TOLERANCE) {
5444 this._simulateEvent('click', first);
5445 }
5446 }
5447
5448 if (!L.Browser.touch) {
5449 L.DomUtil.enableTextSelection();
5450 this._restoreCursor();
5451 }
5452
5453 L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
5454 L.DomEvent.off(document, L.Draggable.END, this._onUp);
5455
5456 if (this._moved) {
5457 // ensure drag is not fired after dragend
5458 L.Util.cancelAnimFrame(this._animRequest);
5459
5460 this.fire('dragend');
5461 }
5462 this._moving = false;
5463 },
5464
5465 _setMovingCursor: function () {
5466 L.DomUtil.addClass(document.body, 'leaflet-dragging');
5467 },
5468
5469 _restoreCursor: function () {
5470 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
5471 },
5472
5473 _simulateEvent: function (type, e) {
5474 var simulatedEvent = document.createEvent('MouseEvents');
5475
5476 simulatedEvent.initMouseEvent(
5477 type, true, true, window, 1,
5478 e.screenX, e.screenY,
5479 e.clientX, e.clientY,
5480 false, false, false, false, 0, null);
5481
5482 e.target.dispatchEvent(simulatedEvent);
5483 }
5484 });
5485
5486
5487 /*
5488 * L.Handler classes are used internally to inject interaction features to classes like Map and Marker.
5489 */
5490
5491 L.Handler = L.Class.extend({
5492 initialize: function (map) {
5493 this._map = map;
5494 },
5495
5496 enable: function () {
5497 if (this._enabled) { return; }
5498
5499 this._enabled = true;
5500 this.addHooks();
5501 },
5502
5503 disable: function () {
5504 if (!this._enabled) { return; }
5505
5506 this._enabled = false;
5507 this.removeHooks();
5508 },
5509
5510 enabled: function () {
5511 return !!this._enabled;
5512 }
5513 });
5514
5515
5516 /*
5517 * L.Handler.MapDrag is used internally by L.Map to make the map draggable.
5518 */
5519
5520 L.Map.mergeOptions({
5521 dragging: true,
5522
5523 inertia: !L.Browser.android23,
5524 inertiaDeceleration: 3000, // px/s^2
5525 inertiaMaxSpeed: 1500, // px/s
5526 inertiaThreshold: L.Browser.touch ? 32 : 14, // ms
5527
5528 // TODO refactor, move to CRS
5529 worldCopyJump: true
5530 });
5531
5532 L.Map.Drag = L.Handler.extend({
5533 addHooks: function () {
5534 if (!this._draggable) {
5535 this._draggable = new L.Draggable(this._map._mapPane, this._map._container);
5536
5537 this._draggable.on({
5538 'dragstart': this._onDragStart,
5539 'drag': this._onDrag,
5540 'dragend': this._onDragEnd
5541 }, this);
5542
5543 var options = this._map.options;
5544
5545 if (options.worldCopyJump) {
5546 this._draggable.on('predrag', this._onPreDrag, this);
5547 this._map.on('viewreset', this._onViewReset, this);
5548 }
5549 }
5550 this._draggable.enable();
5551 },
5552
5553 removeHooks: function () {
5554 this._draggable.disable();
5555 },
5556
5557 moved: function () {
5558 return this._draggable && this._draggable._moved;
5559 },
5560
5561 _onDragStart: function () {
5562 var map = this._map;
5563
5564 map
5565 .fire('movestart')
5566 .fire('dragstart');
5567
5568 if (map._panTransition) {
5569 map._panTransition._onTransitionEnd(true);
5570 }
5571
5572 if (map.options.inertia) {
5573 this._positions = [];
5574 this._times = [];
5575 }
5576 },
5577
5578 _onDrag: function () {
5579 if (this._map.options.inertia) {
5580 var time = this._lastTime = +new Date(),
5581 pos = this._lastPos = this._draggable._newPos;
5582
5583 this._positions.push(pos);
5584 this._times.push(time);
5585
5586 if (time - this._times[0] > 200) {
5587 this._positions.shift();
5588 this._times.shift();
5589 }
5590 }
5591
5592 this._map
5593 .fire('move')
5594 .fire('drag');
5595 },
5596
5597 _onViewReset: function () {
5598 var pxCenter = this._map.getSize().divideBy(2),
5599 pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
5600
5601 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
5602 this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
5603 },
5604
5605 _onPreDrag: function () {
5606 // TODO refactor to be able to adjust map pane position after zoom
5607 var map = this._map,
5608 worldWidth = this._worldWidth,
5609 halfWidth = Math.round(worldWidth / 2),
5610 dx = this._initialWorldOffset,
5611 x = this._draggable._newPos.x,
5612 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
5613 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
5614 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
5615
5616 this._draggable._newPos.x = newX;
5617 },
5618
5619 _onDragEnd: function () {
5620 var map = this._map,
5621 options = map.options,
5622 delay = +new Date() - this._lastTime,
5623
5624 noInertia = !options.inertia ||
5625 delay > options.inertiaThreshold ||
5626 this._positions[0] === undefined;
5627
5628 if (noInertia) {
5629 map.fire('moveend');
5630
5631 } else {
5632
5633 var direction = this._lastPos.subtract(this._positions[0]),
5634 duration = (this._lastTime + delay - this._times[0]) / 1000,
5635
5636 speedVector = direction.multiplyBy(0.58 / duration),
5637 speed = speedVector.distanceTo(new L.Point(0, 0)),
5638
5639 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
5640 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
5641
5642 decelerationDuration = limitedSpeed / options.inertiaDeceleration,
5643 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
5644
5645 var panOptions = {
5646 duration: decelerationDuration,
5647 easing: 'ease-out'
5648 };
5649
5650 L.Util.requestAnimFrame(L.Util.bind(function () {
5651 this._map.panBy(offset, panOptions);
5652 }, this));
5653 }
5654
5655 map.fire('dragend');
5656
5657 if (options.maxBounds) {
5658 // TODO predrag validation instead of animation
5659 L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
5660 }
5661 },
5662
5663 _panInsideMaxBounds: function () {
5664 this.panInsideBounds(this.options.maxBounds);
5665 }
5666 });
5667
5668 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
5669
5670
5671 /*
5672 * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
5673 */
5674
5675 L.Map.mergeOptions({
5676 doubleClickZoom: true
5677 });
5678
5679 L.Map.DoubleClickZoom = L.Handler.extend({
5680 addHooks: function () {
5681 this._map.on('dblclick', this._onDoubleClick);
5682 },
5683
5684 removeHooks: function () {
5685 this._map.off('dblclick', this._onDoubleClick);
5686 },
5687
5688 _onDoubleClick: function (e) {
5689 this.setView(e.latlng, this._zoom + 1);
5690 }
5691 });
5692
5693 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
5694
5695 /*
5696 * L.Handler.ScrollWheelZoom is used internally by L.Map to enable mouse scroll wheel zooming on the map.
5697 */
5698
5699 L.Map.mergeOptions({
5700 scrollWheelZoom: !L.Browser.touch
5701 });
5702
5703 L.Map.ScrollWheelZoom = L.Handler.extend({
5704 addHooks: function () {
5705 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
5706 this._delta = 0;
5707 },
5708
5709 removeHooks: function () {
5710 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
5711 },
5712
5713 _onWheelScroll: function (e) {
5714 var delta = L.DomEvent.getWheelDelta(e);
5715
5716 this._delta += delta;
5717 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
5718
5719 clearTimeout(this._timer);
5720 this._timer = setTimeout(L.Util.bind(this._performZoom, this), 40);
5721
5722 L.DomEvent.preventDefault(e);
5723 },
5724
5725 _performZoom: function () {
5726 var map = this._map,
5727 delta = Math.round(this._delta),
5728 zoom = map.getZoom();
5729
5730 delta = Math.max(Math.min(delta, 4), -4);
5731 delta = map._limitZoom(zoom + delta) - zoom;
5732
5733 this._delta = 0;
5734
5735 if (!delta) { return; }
5736
5737 var newZoom = zoom + delta,
5738 newCenter = this._getCenterForScrollWheelZoom(this._lastMousePos, newZoom);
5739
5740 map.setView(newCenter, newZoom);
5741 },
5742
5743 _getCenterForScrollWheelZoom: function (mousePos, newZoom) {
5744 var map = this._map,
5745 scale = map.getZoomScale(newZoom),
5746 viewHalf = map.getSize().divideBy(2),
5747 centerOffset = mousePos.subtract(viewHalf).multiplyBy(1 - 1 / scale),
5748 newCenterPoint = map._getTopLeftPoint().add(viewHalf).add(centerOffset);
5749
5750 return map.unproject(newCenterPoint);
5751 }
5752 });
5753
5754 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
5755
5756
5757 L.Util.extend(L.DomEvent, {
5758 // inspired by Zepto touch code by Thomas Fuchs
5759 addDoubleTapListener: function (obj, handler, id) {
5760 var last,
5761 doubleTap = false,
5762 delay = 250,
5763 touch,
5764 pre = '_leaflet_',
5765 touchstart = 'touchstart',
5766 touchend = 'touchend';
5767
5768 function onTouchStart(e) {
5769 if (e.touches.length !== 1) {
5770 return;
5771 }
5772
5773 var now = Date.now(),
5774 delta = now - (last || now);
5775
5776 touch = e.touches[0];
5777 doubleTap = (delta > 0 && delta <= delay);
5778 last = now;
5779 }
5780 function onTouchEnd(e) {
5781 if (doubleTap) {
5782 touch.type = 'dblclick';
5783 handler(touch);
5784 last = null;
5785 }
5786 }
5787 obj[pre + touchstart + id] = onTouchStart;
5788 obj[pre + touchend + id] = onTouchEnd;
5789
5790 obj.addEventListener(touchstart, onTouchStart, false);
5791 obj.addEventListener(touchend, onTouchEnd, false);
5792 return this;
5793 },
5794
5795 removeDoubleTapListener: function (obj, id) {
5796 var pre = '_leaflet_';
5797 obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false);
5798 obj.removeEventListener(obj, obj[pre + 'touchend' + id], false);
5799 return this;
5800 }
5801 });
5802
5803
5804 /*
5805 * L.Handler.TouchZoom is used internally by L.Map to add touch-zooming on Webkit-powered mobile browsers.
5806 */
5807
5808 L.Map.mergeOptions({
5809 touchZoom: L.Browser.touch && !L.Browser.android23
5810 });
5811
5812 L.Map.TouchZoom = L.Handler.extend({
5813 addHooks: function () {
5814 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
5815 },
5816
5817 removeHooks: function () {
5818 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
5819 },
5820
5821 _onTouchStart: function (e) {
5822 var map = this._map;
5823
5824 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
5825
5826 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
5827 p2 = map.mouseEventToLayerPoint(e.touches[1]),
5828 viewCenter = map._getCenterLayerPoint();
5829
5830 this._startCenter = p1.add(p2).divideBy(2, true);
5831 this._startDist = p1.distanceTo(p2);
5832
5833 this._moved = false;
5834 this._zooming = true;
5835
5836 this._centerOffset = viewCenter.subtract(this._startCenter);
5837
5838 L.DomEvent
5839 .on(document, 'touchmove', this._onTouchMove, this)
5840 .on(document, 'touchend', this._onTouchEnd, this);
5841
5842 L.DomEvent.preventDefault(e);
5843 },
5844
5845 _onTouchMove: function (e) {
5846 if (!e.touches || e.touches.length !== 2) { return; }
5847
5848 var map = this._map;
5849
5850 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
5851 p2 = map.mouseEventToLayerPoint(e.touches[1]);
5852
5853 this._scale = p1.distanceTo(p2) / this._startDist;
5854 this._delta = p1.add(p2).divideBy(2, true).subtract(this._startCenter);
5855
5856 if (this._scale === 1) { return; }
5857
5858 if (!this._moved) {
5859 L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
5860
5861 map
5862 .fire('movestart')
5863 .fire('zoomstart')
5864 ._prepareTileBg();
5865
5866 this._moved = true;
5867 }
5868
5869 L.Util.cancelAnimFrame(this._animRequest);
5870 this._animRequest = L.Util.requestAnimFrame(this._updateOnMove, this, true, this._map._container);
5871
5872 L.DomEvent.preventDefault(e);
5873 },
5874
5875 _updateOnMove: function () {
5876 var map = this._map,
5877 origin = this._getScaleOrigin(),
5878 center = map.layerPointToLatLng(origin);
5879
5880 map.fire('zoomanim', {
5881 center: center,
5882 zoom: map.getScaleZoom(this._scale)
5883 });
5884
5885 // Used 2 translates instead of transform-origin because of a very strange bug -
5886 // it didn't count the origin on the first touch-zoom but worked correctly afterwards
5887
5888 map._tileBg.style[L.DomUtil.TRANSFORM] =
5889 L.DomUtil.getTranslateString(this._delta) + ' ' +
5890 L.DomUtil.getScaleString(this._scale, this._startCenter);
5891 },
5892
5893 _onTouchEnd: function (e) {
5894 if (!this._moved || !this._zooming) { return; }
5895
5896 var map = this._map;
5897
5898 this._zooming = false;
5899 L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
5900
5901 L.DomEvent
5902 .off(document, 'touchmove', this._onTouchMove)
5903 .off(document, 'touchend', this._onTouchEnd);
5904
5905 var origin = this._getScaleOrigin(),
5906 center = map.layerPointToLatLng(origin),
5907
5908 oldZoom = map.getZoom(),
5909 floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
5910 roundZoomDelta = (floatZoomDelta > 0 ? Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
5911 zoom = map._limitZoom(oldZoom + roundZoomDelta);
5912
5913 map.fire('zoomanim', {
5914 center: center,
5915 zoom: zoom
5916 });
5917
5918 map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
5919 },
5920
5921 _getScaleOrigin: function () {
5922 var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
5923 return this._startCenter.add(centerOffset);
5924 }
5925 });
5926
5927 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
5928
5929
5930 /*
5931 * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
5932 */
5933
5934 L.Map.mergeOptions({
5935 boxZoom: true
5936 });
5937
5938 L.Map.BoxZoom = L.Handler.extend({
5939 initialize: function (map) {
5940 this._map = map;
5941 this._container = map._container;
5942 this._pane = map._panes.overlayPane;
5943 },
5944
5945 addHooks: function () {
5946 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
5947 },
5948
5949 removeHooks: function () {
5950 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
5951 },
5952
5953 _onMouseDown: function (e) {
5954 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
5955
5956 L.DomUtil.disableTextSelection();
5957
5958 this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
5959
5960 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
5961 L.DomUtil.setPosition(this._box, this._startLayerPoint);
5962
5963 //TODO refactor: move cursor to styles
5964 this._container.style.cursor = 'crosshair';
5965
5966 L.DomEvent
5967 .on(document, 'mousemove', this._onMouseMove, this)
5968 .on(document, 'mouseup', this._onMouseUp, this)
5969 .preventDefault(e);
5970
5971 this._map.fire("boxzoomstart");
5972 },
5973
5974 _onMouseMove: function (e) {
5975 var startPoint = this._startLayerPoint,
5976 box = this._box,
5977
5978 layerPoint = this._map.mouseEventToLayerPoint(e),
5979 offset = layerPoint.subtract(startPoint),
5980
5981 newPos = new L.Point(
5982 Math.min(layerPoint.x, startPoint.x),
5983 Math.min(layerPoint.y, startPoint.y));
5984
5985 L.DomUtil.setPosition(box, newPos);
5986
5987 // TODO refactor: remove hardcoded 4 pixels
5988 box.style.width = (Math.abs(offset.x) - 4) + 'px';
5989 box.style.height = (Math.abs(offset.y) - 4) + 'px';
5990 },
5991
5992 _onMouseUp: function (e) {
5993 this._pane.removeChild(this._box);
5994 this._container.style.cursor = '';
5995
5996 L.DomUtil.enableTextSelection();
5997
5998 L.DomEvent
5999 .off(document, 'mousemove', this._onMouseMove)
6000 .off(document, 'mouseup', this._onMouseUp);
6001
6002 var map = this._map,
6003 layerPoint = map.mouseEventToLayerPoint(e);
6004
6005 var bounds = new L.LatLngBounds(
6006 map.layerPointToLatLng(this._startLayerPoint),
6007 map.layerPointToLatLng(layerPoint));
6008
6009 map.fitBounds(bounds);
6010
6011 map.fire("boxzoomend", {
6012 boxZoomBounds: bounds
6013 });
6014 }
6015 });
6016
6017 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
6018
6019
6020 L.Map.mergeOptions({
6021 keyboard: true,
6022 keyboardPanOffset: 80,
6023 keyboardZoomOffset: 1
6024 });
6025
6026 L.Map.Keyboard = L.Handler.extend({
6027
6028 // list of e.keyCode values for particular actions
6029 keyCodes: {
6030 left: [37],
6031 right: [39],
6032 down: [40],
6033 up: [38],
6034 zoomIn: [187, 61, 107],
6035 zoomOut: [189, 109, 0]
6036 },
6037
6038 initialize: function (map) {
6039 this._map = map;
6040
6041 this._setPanOffset(map.options.keyboardPanOffset);
6042 this._setZoomOffset(map.options.keyboardZoomOffset);
6043 },
6044
6045 addHooks: function () {
6046 var container = this._map._container;
6047
6048 // make the container focusable by tabbing
6049 if (container.tabIndex === -1) {
6050 container.tabIndex = "0";
6051 }
6052
6053 L.DomEvent
6054 .addListener(container, 'focus', this._onFocus, this)
6055 .addListener(container, 'blur', this._onBlur, this)
6056 .addListener(container, 'mousedown', this._onMouseDown, this);
6057
6058 this._map
6059 .on('focus', this._addHooks, this)
6060 .on('blur', this._removeHooks, this);
6061 },
6062
6063 removeHooks: function () {
6064 this._removeHooks();
6065
6066 var container = this._map._container;
6067 L.DomEvent
6068 .removeListener(container, 'focus', this._onFocus, this)
6069 .removeListener(container, 'blur', this._onBlur, this)
6070 .removeListener(container, 'mousedown', this._onMouseDown, this);
6071
6072 this._map
6073 .off('focus', this._addHooks, this)
6074 .off('blur', this._removeHooks, this);
6075 },
6076
6077 _onMouseDown: function () {
6078 if (!this._focused) {
6079 this._map._container.focus();
6080 }
6081 },
6082
6083 _onFocus: function () {
6084 this._focused = true;
6085 this._map.fire('focus');
6086 },
6087
6088 _onBlur: function () {
6089 this._focused = false;
6090 this._map.fire('blur');
6091 },
6092
6093 _setPanOffset: function (pan) {
6094 var keys = this._panKeys = {},
6095 codes = this.keyCodes,
6096 i, len;
6097
6098 for (i = 0, len = codes.left.length; i < len; i++) {
6099 keys[codes.left[i]] = [-1 * pan, 0];
6100 }
6101 for (i = 0, len = codes.right.length; i < len; i++) {
6102 keys[codes.right[i]] = [pan, 0];
6103 }
6104 for (i = 0, len = codes.down.length; i < len; i++) {
6105 keys[codes.down[i]] = [0, pan];
6106 }
6107 for (i = 0, len = codes.up.length; i < len; i++) {
6108 keys[codes.up[i]] = [0, -1 * pan];
6109 }
6110 },
6111
6112 _setZoomOffset: function (zoom) {
6113 var keys = this._zoomKeys = {},
6114 codes = this.keyCodes,
6115 i, len;
6116
6117 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
6118 keys[codes.zoomIn[i]] = zoom;
6119 }
6120 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
6121 keys[codes.zoomOut[i]] = -zoom;
6122 }
6123 },
6124
6125 _addHooks: function () {
6126 L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
6127 },
6128
6129 _removeHooks: function () {
6130 L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
6131 },
6132
6133 _onKeyDown: function (e) {
6134 var key = e.keyCode;
6135
6136 if (this._panKeys.hasOwnProperty(key)) {
6137 this._map.panBy(this._panKeys[key]);
6138
6139 } else if (this._zoomKeys.hasOwnProperty(key)) {
6140 this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
6141
6142 } else {
6143 return;
6144 }
6145
6146 L.DomEvent.stop(e);
6147 }
6148 });
6149
6150 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
6151
6152
6153 /*
6154 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
6155 */
6156
6157 L.Handler.MarkerDrag = L.Handler.extend({
6158 initialize: function (marker) {
6159 this._marker = marker;
6160 },
6161
6162 addHooks: function () {
6163 var icon = this._marker._icon;
6164 if (!this._draggable) {
6165 this._draggable = new L.Draggable(icon, icon)
6166 .on('dragstart', this._onDragStart, this)
6167 .on('drag', this._onDrag, this)
6168 .on('dragend', this._onDragEnd, this);
6169 }
6170 this._draggable.enable();
6171 },
6172
6173 removeHooks: function () {
6174 this._draggable.disable();
6175 },
6176
6177 moved: function () {
6178 return this._draggable && this._draggable._moved;
6179 },
6180
6181 _onDragStart: function (e) {
6182 this._marker
6183 .closePopup()
6184 .fire('movestart')
6185 .fire('dragstart');
6186 },
6187
6188 _onDrag: function (e) {
6189 // update shadow position
6190 var iconPos = L.DomUtil.getPosition(this._marker._icon);
6191 if (this._marker._shadow) {
6192 L.DomUtil.setPosition(this._marker._shadow, iconPos);
6193 }
6194
6195 this._marker._latlng = this._marker._map.layerPointToLatLng(iconPos);
6196
6197 this._marker
6198 .fire('move')
6199 .fire('drag');
6200 },
6201
6202 _onDragEnd: function () {
6203 this._marker
6204 .fire('moveend')
6205 .fire('dragend');
6206 }
6207 });
6208
6209
6210 L.Handler.PolyEdit = L.Handler.extend({
6211 options: {
6212 icon: new L.DivIcon({
6213 iconSize: new L.Point(8, 8),
6214 className: 'leaflet-div-icon leaflet-editing-icon'
6215 })
6216 },
6217
6218 initialize: function (poly, options) {
6219 this._poly = poly;
6220 L.Util.setOptions(this, options);
6221 },
6222
6223 addHooks: function () {
6224 if (this._poly._map) {
6225 if (!this._markerGroup) {
6226 this._initMarkers();
6227 }
6228 this._poly._map.addLayer(this._markerGroup);
6229 }
6230 },
6231
6232 removeHooks: function () {
6233 if (this._poly._map) {
6234 this._poly._map.removeLayer(this._markerGroup);
6235 delete this._markerGroup;
6236 delete this._markers;
6237 }
6238 },
6239
6240 updateMarkers: function () {
6241 this._markerGroup.clearLayers();
6242 this._initMarkers();
6243 },
6244
6245 _initMarkers: function () {
6246 if (!this._markerGroup) {
6247 this._markerGroup = new L.LayerGroup();
6248 }
6249 this._markers = [];
6250
6251 var latlngs = this._poly._latlngs,
6252 i, j, len, marker;
6253
6254 // TODO refactor holes implementation in Polygon to support it here
6255
6256 for (i = 0, len = latlngs.length; i < len; i++) {
6257
6258 marker = this._createMarker(latlngs[i], i);
6259 marker.on('click', this._onMarkerClick, this);
6260 this._markers.push(marker);
6261 }
6262
6263 var markerLeft, markerRight;
6264
6265 for (i = 0, j = len - 1; i < len; j = i++) {
6266 if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
6267 continue;
6268 }
6269
6270 markerLeft = this._markers[j];
6271 markerRight = this._markers[i];
6272
6273 this._createMiddleMarker(markerLeft, markerRight);
6274 this._updatePrevNext(markerLeft, markerRight);
6275 }
6276 },
6277
6278 _createMarker: function (latlng, index) {
6279 var marker = new L.Marker(latlng, {
6280 draggable: true,
6281 icon: this.options.icon
6282 });
6283
6284 marker._origLatLng = latlng;
6285 marker._index = index;
6286
6287 marker.on('drag', this._onMarkerDrag, this);
6288 marker.on('dragend', this._fireEdit, this);
6289
6290 this._markerGroup.addLayer(marker);
6291
6292 return marker;
6293 },
6294
6295 _fireEdit: function () {
6296 this._poly.fire('edit');
6297 },
6298
6299 _onMarkerDrag: function (e) {
6300 var marker = e.target;
6301
6302 L.Util.extend(marker._origLatLng, marker._latlng);
6303
6304 if (marker._middleLeft) {
6305 marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
6306 }
6307 if (marker._middleRight) {
6308 marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
6309 }
6310
6311 this._poly.redraw();
6312 },
6313
6314 _onMarkerClick: function (e) {
6315 // Default action on marker click is to remove that marker, but if we remove the marker when latlng count < 3, we don't have a valid polyline anymore
6316 if (this._poly._latlngs.length < 3) {
6317 return;
6318 }
6319
6320 var marker = e.target,
6321 i = marker._index;
6322
6323 // Check existence of previous and next markers since they wouldn't exist for edge points on the polyline
6324 if (marker._prev && marker._next) {
6325 this._createMiddleMarker(marker._prev, marker._next);
6326 this._updatePrevNext(marker._prev, marker._next);
6327 }
6328
6329 // The marker itself is guaranteed to exist and present in the layer, since we managed to click on it
6330 this._markerGroup.removeLayer(marker);
6331 // Check for the existence of middle left or middle right
6332 if (marker._middleLeft) {
6333 this._markerGroup.removeLayer(marker._middleLeft);
6334 }
6335 if (marker._middleRight) {
6336 this._markerGroup.removeLayer(marker._middleRight);
6337 }
6338 this._markers.splice(i, 1);
6339 this._poly.spliceLatLngs(i, 1);
6340 this._updateIndexes(i, -1);
6341 this._poly.fire('edit');
6342 },
6343
6344 _updateIndexes: function (index, delta) {
6345 this._markerGroup.eachLayer(function (marker) {
6346 if (marker._index > index) {
6347 marker._index += delta;
6348 }
6349 });
6350 },
6351
6352 _createMiddleMarker: function (marker1, marker2) {
6353 var latlng = this._getMiddleLatLng(marker1, marker2),
6354 marker = this._createMarker(latlng),
6355 onClick,
6356 onDragStart,
6357 onDragEnd;
6358
6359 marker.setOpacity(0.6);
6360
6361 marker1._middleRight = marker2._middleLeft = marker;
6362
6363 onDragStart = function () {
6364 var i = marker2._index;
6365
6366 marker._index = i;
6367
6368 marker
6369 .off('click', onClick)
6370 .on('click', this._onMarkerClick, this);
6371
6372 latlng.lat = marker.getLatLng().lat;
6373 latlng.lng = marker.getLatLng().lng;
6374 this._poly.spliceLatLngs(i, 0, latlng);
6375 this._markers.splice(i, 0, marker);
6376
6377 marker.setOpacity(1);
6378
6379 this._updateIndexes(i, 1);
6380 marker2._index++;
6381 this._updatePrevNext(marker1, marker);
6382 this._updatePrevNext(marker, marker2);
6383 };
6384
6385 onDragEnd = function () {
6386 marker.off('dragstart', onDragStart, this);
6387 marker.off('dragend', onDragEnd, this);
6388
6389 this._createMiddleMarker(marker1, marker);
6390 this._createMiddleMarker(marker, marker2);
6391 };
6392
6393 onClick = function () {
6394 onDragStart.call(this);
6395 onDragEnd.call(this);
6396 this._poly.fire('edit');
6397 };
6398
6399 marker
6400 .on('click', onClick, this)
6401 .on('dragstart', onDragStart, this)
6402 .on('dragend', onDragEnd, this);
6403
6404 this._markerGroup.addLayer(marker);
6405 },
6406
6407 _updatePrevNext: function (marker1, marker2) {
6408 marker1._next = marker2;
6409 marker2._prev = marker1;
6410 },
6411
6412 _getMiddleLatLng: function (marker1, marker2) {
6413 var map = this._poly._map,
6414 p1 = map.latLngToLayerPoint(marker1.getLatLng()),
6415 p2 = map.latLngToLayerPoint(marker2.getLatLng());
6416
6417 return map.layerPointToLatLng(p1._add(p2).divideBy(2));
6418 }
6419 });
6420
6421
6422
6423 L.Control = L.Class.extend({
6424 options: {
6425 position: 'topright'
6426 },
6427
6428 initialize: function (options) {
6429 L.Util.setOptions(this, options);
6430 },
6431
6432 getPosition: function () {
6433 return this.options.position;
6434 },
6435
6436 setPosition: function (position) {
6437 var map = this._map;
6438
6439 if (map) {
6440 map.removeControl(this);
6441 }
6442
6443 this.options.position = position;
6444
6445 if (map) {
6446 map.addControl(this);
6447 }
6448
6449 return this;
6450 },
6451
6452 addTo: function (map) {
6453 this._map = map;
6454
6455 var container = this._container = this.onAdd(map),
6456 pos = this.getPosition(),
6457 corner = map._controlCorners[pos];
6458
6459 L.DomUtil.addClass(container, 'leaflet-control');
6460
6461 if (pos.indexOf('bottom') !== -1) {
6462 corner.insertBefore(container, corner.firstChild);
6463 } else {
6464 corner.appendChild(container);
6465 }
6466
6467 return this;
6468 },
6469
6470 removeFrom: function (map) {
6471 var pos = this.getPosition(),
6472 corner = map._controlCorners[pos];
6473
6474 corner.removeChild(this._container);
6475 this._map = null;
6476
6477 if (this.onRemove) {
6478 this.onRemove(map);
6479 }
6480
6481 return this;
6482 }
6483 });
6484
6485 L.control = function (options) {
6486 return new L.Control(options);
6487 };
6488
6489
6490 L.Map.include({
6491 addControl: function (control) {
6492 control.addTo(this);
6493 return this;
6494 },
6495
6496 removeControl: function (control) {
6497 control.removeFrom(this);
6498 return this;
6499 },
6500
6501 _initControlPos: function () {
6502 var corners = this._controlCorners = {},
6503 l = 'leaflet-',
6504 container = this._controlContainer =
6505 L.DomUtil.create('div', l + 'control-container', this._container);
6506
6507 function createCorner(vSide, hSide) {
6508 var className = l + vSide + ' ' + l + hSide;
6509
6510 corners[vSide + hSide] =
6511 L.DomUtil.create('div', className, container);
6512 }
6513
6514 createCorner('top', 'left');
6515 createCorner('top', 'right');
6516 createCorner('bottom', 'left');
6517 createCorner('bottom', 'right');
6518 }
6519 });
6520
6521
6522 L.Control.Zoom = L.Control.extend({
6523 options: {
6524 position: 'topleft'
6525 },
6526
6527 onAdd: function (map) {
6528 var className = 'leaflet-control-zoom',
6529 container = L.DomUtil.create('div', className);
6530
6531 this._createButton('Zoom in', className + '-in', container, map.zoomIn, map);
6532 this._createButton('Zoom out', className + '-out', container, map.zoomOut, map);
6533
6534 return container;
6535 },
6536
6537 _createButton: function (title, className, container, fn, context) {
6538 var link = L.DomUtil.create('a', className, container);
6539 link.href = '#';
6540 link.title = title;
6541
6542 L.DomEvent
6543 .on(link, 'click', L.DomEvent.stopPropagation)
6544 .on(link, 'click', L.DomEvent.preventDefault)
6545 .on(link, 'click', fn, context)
6546 .on(link, 'dblclick', L.DomEvent.stopPropagation);
6547
6548 return link;
6549 }
6550 });
6551
6552 L.Map.mergeOptions({
6553 zoomControl: true
6554 });
6555
6556 L.Map.addInitHook(function () {
6557 if (this.options.zoomControl) {
6558 this.zoomControl = new L.Control.Zoom();
6559 this.addControl(this.zoomControl);
6560 }
6561 });
6562
6563 L.control.zoom = function (options) {
6564 return new L.Control.Zoom(options);
6565 };
6566
6567 L.Control.Attribution = L.Control.extend({
6568 options: {
6569 position: 'bottomright',
6570 prefix: 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>'
6571 },
6572
6573 initialize: function (options) {
6574 L.Util.setOptions(this, options);
6575
6576 this._attributions = {};
6577 },
6578
6579 onAdd: function (map) {
6580 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
6581 L.DomEvent.disableClickPropagation(this._container);
6582
6583 map
6584 .on('layeradd', this._onLayerAdd, this)
6585 .on('layerremove', this._onLayerRemove, this);
6586
6587 this._update();
6588
6589 return this._container;
6590 },
6591
6592 onRemove: function (map) {
6593 map
6594 .off('layeradd', this._onLayerAdd)
6595 .off('layerremove', this._onLayerRemove);
6596
6597 },
6598
6599 setPrefix: function (prefix) {
6600 this.options.prefix = prefix;
6601 this._update();
6602 return this;
6603 },
6604
6605 addAttribution: function (text) {
6606 if (!text) { return; }
6607
6608 if (!this._attributions[text]) {
6609 this._attributions[text] = 0;
6610 }
6611 this._attributions[text]++;
6612
6613 this._update();
6614
6615 return this;
6616 },
6617
6618 removeAttribution: function (text) {
6619 if (!text) { return; }
6620
6621 this._attributions[text]--;
6622 this._update();
6623
6624 return this;
6625 },
6626
6627 _update: function () {
6628 if (!this._map) { return; }
6629
6630 var attribs = [];
6631
6632 for (var i in this._attributions) {
6633 if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
6634 attribs.push(i);
6635 }
6636 }
6637
6638 var prefixAndAttribs = [];
6639
6640 if (this.options.prefix) {
6641 prefixAndAttribs.push(this.options.prefix);
6642 }
6643 if (attribs.length) {
6644 prefixAndAttribs.push(attribs.join(', '));
6645 }
6646
6647 this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
6648 },
6649
6650 _onLayerAdd: function (e) {
6651 if (e.layer.getAttribution) {
6652 this.addAttribution(e.layer.getAttribution());
6653 }
6654 },
6655
6656 _onLayerRemove: function (e) {
6657 if (e.layer.getAttribution) {
6658 this.removeAttribution(e.layer.getAttribution());
6659 }
6660 }
6661 });
6662
6663 L.Map.mergeOptions({
6664 attributionControl: true
6665 });
6666
6667 L.Map.addInitHook(function () {
6668 if (this.options.attributionControl) {
6669 this.attributionControl = (new L.Control.Attribution()).addTo(this);
6670 }
6671 });
6672
6673 L.control.attribution = function (options) {
6674 return new L.Control.Attribution(options);
6675 };
6676
6677
6678 L.Control.Scale = L.Control.extend({
6679 options: {
6680 position: 'bottomleft',
6681 maxWidth: 100,
6682 metric: true,
6683 imperial: true,
6684 updateWhenIdle: false
6685 },
6686
6687 onAdd: function (map) {
6688 this._map = map;
6689
6690 var className = 'leaflet-control-scale',
6691 container = L.DomUtil.create('div', className),
6692 options = this.options;
6693
6694 this._addScales(options, className, container);
6695
6696 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
6697 this._update();
6698
6699 return container;
6700 },
6701
6702 onRemove: function (map) {
6703 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
6704 },
6705
6706 _addScales: function (options, className, container) {
6707 if (options.metric) {
6708 this._mScale = L.DomUtil.create('div', className + '-line', container);
6709 }
6710 if (options.imperial) {
6711 this._iScale = L.DomUtil.create('div', className + '-line', container);
6712 }
6713 },
6714
6715 _update: function () {
6716 var bounds = this._map.getBounds(),
6717 centerLat = bounds.getCenter().lat,
6718 halfWorldMeters = new L.LatLng(centerLat, 0).distanceTo(new L.LatLng(centerLat, 180)),
6719 dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
6720
6721 size = this._map.getSize(),
6722 options = this.options,
6723 maxMeters = 0;
6724
6725 if (size.x > 0) {
6726 maxMeters = dist * (options.maxWidth / size.x);
6727 }
6728
6729 this._updateScales(options, maxMeters);
6730 },
6731
6732 _updateScales: function (options, maxMeters) {
6733 if (options.metric && maxMeters) {
6734 this._updateMetric(maxMeters);
6735 }
6736
6737 if (options.imperial && maxMeters) {
6738 this._updateImperial(maxMeters);
6739 }
6740 },
6741
6742 _updateMetric: function (maxMeters) {
6743 var meters = this._getRoundNum(maxMeters);
6744
6745 this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
6746 this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
6747 },
6748
6749 _updateImperial: function (maxMeters) {
6750 var maxFeet = maxMeters * 3.2808399,
6751 scale = this._iScale,
6752 maxMiles, miles, feet;
6753
6754 if (maxFeet > 5280) {
6755 maxMiles = maxFeet / 5280;
6756 miles = this._getRoundNum(maxMiles);
6757
6758 scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
6759 scale.innerHTML = miles + ' mi';
6760
6761 } else {
6762 feet = this._getRoundNum(maxFeet);
6763
6764 scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
6765 scale.innerHTML = feet + ' ft';
6766 }
6767 },
6768
6769 _getScaleWidth: function (ratio) {
6770 return Math.round(this.options.maxWidth * ratio) - 10;
6771 },
6772
6773 _getRoundNum: function (num) {
6774 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
6775 d = num / pow10;
6776
6777 d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
6778
6779 return pow10 * d;
6780 }
6781 });
6782
6783 L.control.scale = function (options) {
6784 return new L.Control.Scale(options);
6785 };
6786
6787
6788
6789 L.Control.Layers = L.Control.extend({
6790 options: {
6791 collapsed: true,
6792 position: 'topright',
6793 autoZIndex: true
6794 },
6795
6796 initialize: function (baseLayers, overlays, options) {
6797 L.Util.setOptions(this, options);
6798
6799 this._layers = {};
6800 this._lastZIndex = 0;
6801
6802 for (var i in baseLayers) {
6803 if (baseLayers.hasOwnProperty(i)) {
6804 this._addLayer(baseLayers[i], i);
6805 }
6806 }
6807
6808 for (i in overlays) {
6809 if (overlays.hasOwnProperty(i)) {
6810 this._addLayer(overlays[i], i, true);
6811 }
6812 }
6813 },
6814
6815 onAdd: function (map) {
6816 this._initLayout();
6817 this._update();
6818
6819 return this._container;
6820 },
6821
6822 addBaseLayer: function (layer, name) {
6823 this._addLayer(layer, name);
6824 this._update();
6825 return this;
6826 },
6827
6828 addOverlay: function (layer, name) {
6829 this._addLayer(layer, name, true);
6830 this._update();
6831 return this;
6832 },
6833
6834 removeLayer: function (layer) {
6835 var id = L.Util.stamp(layer);
6836 delete this._layers[id];
6837 this._update();
6838 return this;
6839 },
6840
6841 _initLayout: function () {
6842 var className = 'leaflet-control-layers',
6843 container = this._container = L.DomUtil.create('div', className);
6844
6845 if (!L.Browser.touch) {
6846 L.DomEvent.disableClickPropagation(container);
6847 } else {
6848 L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
6849 }
6850
6851 var form = this._form = L.DomUtil.create('form', className + '-list');
6852
6853 if (this.options.collapsed) {
6854 L.DomEvent
6855 .on(container, 'mouseover', this._expand, this)
6856 .on(container, 'mouseout', this._collapse, this);
6857
6858 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
6859 link.href = '#';
6860 link.title = 'Layers';
6861
6862 if (L.Browser.touch) {
6863 L.DomEvent
6864 .on(link, 'click', L.DomEvent.stopPropagation)
6865 .on(link, 'click', L.DomEvent.preventDefault)
6866 .on(link, 'click', this._expand, this);
6867 }
6868 else {
6869 L.DomEvent.on(link, 'focus', this._expand, this);
6870 }
6871
6872 this._map.on('movestart', this._collapse, this);
6873 // TODO keyboard accessibility
6874 } else {
6875 this._expand();
6876 }
6877
6878 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
6879 this._separator = L.DomUtil.create('div', className + '-separator', form);
6880 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
6881
6882 container.appendChild(form);
6883 },
6884
6885 _addLayer: function (layer, name, overlay) {
6886 var id = L.Util.stamp(layer);
6887
6888 this._layers[id] = {
6889 layer: layer,
6890 name: name,
6891 overlay: overlay
6892 };
6893
6894 if (this.options.autoZIndex && layer.setZIndex) {
6895 this._lastZIndex++;
6896 layer.setZIndex(this._lastZIndex);
6897 }
6898 },
6899
6900 _update: function () {
6901 if (!this._container) {
6902 return;
6903 }
6904
6905 this._baseLayersList.innerHTML = '';
6906 this._overlaysList.innerHTML = '';
6907
6908 var baseLayersPresent = false,
6909 overlaysPresent = false;
6910
6911 for (var i in this._layers) {
6912 if (this._layers.hasOwnProperty(i)) {
6913 var obj = this._layers[i];
6914 this._addItem(obj);
6915 overlaysPresent = overlaysPresent || obj.overlay;
6916 baseLayersPresent = baseLayersPresent || !obj.overlay;
6917 }
6918 }
6919
6920 this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
6921 },
6922
6923 _addItem: function (obj, onclick) {
6924 var label = document.createElement('label');
6925
6926 var input = document.createElement('input');
6927 if (!obj.overlay) {
6928 input.name = 'leaflet-base-layers';
6929 }
6930 input.type = obj.overlay ? 'checkbox' : 'radio';
6931 input.layerId = L.Util.stamp(obj.layer);
6932 input.defaultChecked = this._map.hasLayer(obj.layer);
6933
6934 L.DomEvent.on(input, 'click', this._onInputClick, this);
6935
6936 var name = document.createTextNode(' ' + obj.name);
6937
6938 label.appendChild(input);
6939 label.appendChild(name);
6940
6941 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
6942 container.appendChild(label);
6943 },
6944
6945 _onInputClick: function () {
6946 var i, input, obj,
6947 inputs = this._form.getElementsByTagName('input'),
6948 inputsLen = inputs.length;
6949
6950 for (i = 0; i < inputsLen; i++) {
6951 input = inputs[i];
6952 obj = this._layers[input.layerId];
6953
6954 if (input.checked) {
6955 this._map.addLayer(obj.layer, !obj.overlay);
6956 } else {
6957 this._map.removeLayer(obj.layer);
6958 }
6959 }
6960 },
6961
6962 _expand: function () {
6963 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
6964 },
6965
6966 _collapse: function () {
6967 this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
6968 }
6969 });
6970
6971 L.control.layers = function (baseLayers, overlays, options) {
6972 return new L.Control.Layers(baseLayers, overlays, options);
6973 };
6974
6975
6976 L.Transition = L.Class.extend({
6977 includes: L.Mixin.Events,
6978
6979 statics: {
6980 CUSTOM_PROPS_SETTERS: {
6981 position: L.DomUtil.setPosition
6982 //TODO transform custom attr
6983 },
6984
6985 implemented: function () {
6986 return L.Transition.NATIVE || L.Transition.TIMER;
6987 }
6988 },
6989
6990 options: {
6991 easing: 'ease',
6992 duration: 0.5
6993 },
6994
6995 _setProperty: function (prop, value) {
6996 var setters = L.Transition.CUSTOM_PROPS_SETTERS;
6997 if (prop in setters) {
6998 setters[prop](this._el, value);
6999 } else {
7000 this._el.style[prop] = value;
7001 }
7002 }
7003 });
7004
7005
7006 /*
7007 * L.Transition native implementation that powers Leaflet animation
7008 * in browsers that support CSS3 Transitions
7009 */
7010
7011 L.Transition = L.Transition.extend({
7012 statics: (function () {
7013 var transition = L.DomUtil.TRANSITION,
7014 transitionEnd = (transition === 'webkitTransition' || transition === 'OTransition' ?
7015 transition + 'End' : 'transitionend');
7016
7017 return {
7018 NATIVE: !!transition,
7019
7020 TRANSITION: transition,
7021 PROPERTY: transition + 'Property',
7022 DURATION: transition + 'Duration',
7023 EASING: transition + 'TimingFunction',
7024 END: transitionEnd,
7025
7026 // transition-property value to use with each particular custom property
7027 CUSTOM_PROPS_PROPERTIES: {
7028 position: L.Browser.any3d ? L.DomUtil.TRANSFORM : 'top, left'
7029 }
7030 };
7031 }()),
7032
7033 options: {
7034 fakeStepInterval: 100
7035 },
7036
7037 initialize: function (/*HTMLElement*/ el, /*Object*/ options) {
7038 this._el = el;
7039 L.Util.setOptions(this, options);
7040
7041 L.DomEvent.on(el, L.Transition.END, this._onTransitionEnd, this);
7042 this._onFakeStep = L.Util.bind(this._onFakeStep, this);
7043 },
7044
7045 run: function (/*Object*/ props) {
7046 var prop,
7047 propsList = [],
7048 customProp = L.Transition.CUSTOM_PROPS_PROPERTIES;
7049
7050 for (prop in props) {
7051 if (props.hasOwnProperty(prop)) {
7052 prop = customProp[prop] ? customProp[prop] : prop;
7053 prop = this._dasherize(prop);
7054 propsList.push(prop);
7055 }
7056 }
7057
7058 this._el.style[L.Transition.DURATION] = this.options.duration + 's';
7059 this._el.style[L.Transition.EASING] = this.options.easing;
7060 this._el.style[L.Transition.PROPERTY] = propsList.join(', ');
7061
7062 for (prop in props) {
7063 if (props.hasOwnProperty(prop)) {
7064 this._setProperty(prop, props[prop]);
7065 }
7066 }
7067
7068 this._inProgress = true;
7069
7070 this.fire('start');
7071
7072 if (L.Browser.mobileWebkit) {
7073 // Set up a slightly delayed call to a backup event if webkitTransitionEnd doesn't fire properly
7074 this.backupEventFire = setTimeout(L.Util.bind(this._onBackupFireEnd, this), this.options.duration * 1.2 * 1000);
7075 }
7076
7077 if (L.Transition.NATIVE) {
7078 clearInterval(this._timer);
7079 this._timer = setInterval(this._onFakeStep, this.options.fakeStepInterval);
7080 } else {
7081 this._onTransitionEnd();
7082 }
7083 },
7084
7085 _dasherize: (function () {
7086 var re = /([A-Z])/g;
7087
7088 function replaceFn(w) {
7089 return '-' + w.toLowerCase();
7090 }
7091
7092 return function (str) {
7093 return str.replace(re, replaceFn);
7094 };
7095 }()),
7096
7097 _onFakeStep: function () {
7098 this.fire('step');
7099 },
7100
7101 _onTransitionEnd: function (e) {
7102 if (this._inProgress) {
7103 this._inProgress = false;
7104 clearInterval(this._timer);
7105
7106 this._el.style[L.Transition.TRANSITION] = '';
7107
7108 // Clear the delayed call to the backup event, we have recieved some form of webkitTransitionEnd
7109 clearTimeout(this.backupEventFire);
7110 delete this.backupEventFire;
7111
7112 this.fire('step');
7113
7114 if (e && e.type) {
7115 this.fire('end');
7116 }
7117 }
7118 },
7119
7120 _onBackupFireEnd: function () {
7121 // Create and fire a transitionEnd event on the element.
7122
7123 var event = document.createEvent("Event");
7124 event.initEvent(L.Transition.END, true, false);
7125 this._el.dispatchEvent(event);
7126 }
7127 });
7128
7129
7130 /*
7131 * L.Transition fallback implementation that powers Leaflet animation
7132 * in browsers that don't support CSS3 Transitions
7133 */
7134
7135 L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({
7136 statics: {
7137 getTime: Date.now || function () {
7138 return +new Date();
7139 },
7140
7141 TIMER: true,
7142
7143 EASINGS: {
7144 'linear': function (t) { return t; },
7145 'ease-out': function (t) { return t * (2 - t); }
7146 },
7147
7148 CUSTOM_PROPS_GETTERS: {
7149 position: L.DomUtil.getPosition
7150 },
7151
7152 //used to get units from strings like "10.5px" (->px)
7153 UNIT_RE: /^[\d\.]+(\D*)$/
7154 },
7155
7156 options: {
7157 fps: 50
7158 },
7159
7160 initialize: function (el, options) {
7161 this._el = el;
7162 L.Util.extend(this.options, options);
7163
7164 this._easing = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS['ease-out'];
7165
7166 this._step = L.Util.bind(this._step, this);
7167 this._interval = Math.round(1000 / this.options.fps);
7168 },
7169
7170 run: function (props) {
7171 this._props = {};
7172
7173 var getters = L.Transition.CUSTOM_PROPS_GETTERS,
7174 re = L.Transition.UNIT_RE;
7175
7176 this.fire('start');
7177
7178 for (var prop in props) {
7179 if (props.hasOwnProperty(prop)) {
7180 var p = {};
7181 if (prop in getters) {
7182 p.from = getters[prop](this._el);
7183 } else {
7184 var matches = this._el.style[prop].match(re);
7185 p.from = parseFloat(matches[0]);
7186 p.unit = matches[1];
7187 }
7188 p.to = props[prop];
7189 this._props[prop] = p;
7190 }
7191 }
7192
7193 clearInterval(this._timer);
7194 this._timer = setInterval(this._step, this._interval);
7195 this._startTime = L.Transition.getTime();
7196 },
7197
7198 _step: function () {
7199 var time = L.Transition.getTime(),
7200 elapsed = time - this._startTime,
7201 duration = this.options.duration * 1000;
7202
7203 if (elapsed < duration) {
7204 this._runFrame(this._easing(elapsed / duration));
7205 } else {
7206 this._runFrame(1);
7207 this._complete();
7208 }
7209 },
7210
7211 _runFrame: function (percentComplete) {
7212 var setters = L.Transition.CUSTOM_PROPS_SETTERS,
7213 prop, p, value;
7214
7215 for (prop in this._props) {
7216 if (this._props.hasOwnProperty(prop)) {
7217 p = this._props[prop];
7218 if (prop in setters) {
7219 value = p.to.subtract(p.from).multiplyBy(percentComplete).add(p.from);
7220 setters[prop](this._el, value);
7221 } else {
7222 this._el.style[prop] =
7223 ((p.to - p.from) * percentComplete + p.from) + p.unit;
7224 }
7225 }
7226 }
7227 this.fire('step');
7228 },
7229
7230 _complete: function () {
7231 clearInterval(this._timer);
7232 this.fire('end');
7233 }
7234 });
7235
7236
7237
7238 L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
7239
7240 setView: function (center, zoom, forceReset) {
7241 zoom = this._limitZoom(zoom);
7242
7243 var zoomChanged = (this._zoom !== zoom);
7244
7245 if (this._loaded && !forceReset && this._layers) {
7246 var done = (zoomChanged ?
7247 this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
7248 this._panByIfClose(center));
7249
7250 // exit if animated pan or zoom started
7251 if (done) {
7252 clearTimeout(this._sizeTimer);
7253 return this;
7254 }
7255 }
7256
7257 // reset the map view
7258 this._resetView(center, zoom);
7259
7260 return this;
7261 },
7262
7263 panBy: function (offset, options) {
7264 offset = L.point(offset);
7265
7266 if (!(offset.x || offset.y)) {
7267 return this;
7268 }
7269
7270 if (!this._panTransition) {
7271 this._panTransition = new L.Transition(this._mapPane);
7272
7273 this._panTransition.on({
7274 'step': this._onPanTransitionStep,
7275 'end': this._onPanTransitionEnd
7276 }, this);
7277 }
7278
7279 L.Util.setOptions(this._panTransition, L.Util.extend({duration: 0.25}, options));
7280
7281 this.fire('movestart');
7282
7283 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
7284
7285 this._panTransition.run({
7286 position: L.DomUtil.getPosition(this._mapPane).subtract(offset)
7287 });
7288
7289 return this;
7290 },
7291
7292 _onPanTransitionStep: function () {
7293 this.fire('move');
7294 },
7295
7296 _onPanTransitionEnd: function () {
7297 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
7298 this.fire('moveend');
7299 },
7300
7301 _panByIfClose: function (center) {
7302 // difference between the new and current centers in pixels
7303 var offset = this._getCenterOffset(center)._floor();
7304
7305 if (this._offsetIsWithinView(offset)) {
7306 this.panBy(offset);
7307 return true;
7308 }
7309 return false;
7310 },
7311
7312 _offsetIsWithinView: function (offset, multiplyFactor) {
7313 var m = multiplyFactor || 1,
7314 size = this.getSize();
7315
7316 return (Math.abs(offset.x) <= size.x * m) &&
7317 (Math.abs(offset.y) <= size.y * m);
7318 }
7319 });
7320
7321
7322 L.Map.mergeOptions({
7323 zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
7324 });
7325
7326 L.Map.include(!L.DomUtil.TRANSITION ? {} : {
7327
7328 _zoomToIfClose: function (center, zoom) {
7329
7330 if (this._animatingZoom) { return true; }
7331 if (!this.options.zoomAnimation) { return false; }
7332
7333 var scale = this.getZoomScale(zoom),
7334 offset = this._getCenterOffset(center).divideBy(1 - 1 / scale);
7335
7336 // if offset does not exceed half of the view
7337 if (!this._offsetIsWithinView(offset, 1)) { return false; }
7338
7339 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
7340
7341 this
7342 .fire('movestart')
7343 .fire('zoomstart');
7344
7345 this._prepareTileBg();
7346
7347 this.fire('zoomanim', {
7348 center: center,
7349 zoom: zoom
7350 });
7351
7352 var origin = this._getCenterLayerPoint().add(offset);
7353
7354 this._runAnimation(center, zoom, scale, origin);
7355
7356 return true;
7357 },
7358
7359
7360 _runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
7361 this._animatingZoom = true;
7362
7363 this._animateToCenter = center;
7364 this._animateToZoom = zoom;
7365
7366 var transform = L.DomUtil.TRANSFORM,
7367 tileBg = this._tileBg;
7368
7369 clearTimeout(this._clearTileBgTimer);
7370
7371 //dumb FireFox hack, I have no idea why this magic zero translate fixes the scale transition problem
7372 if (L.Browser.gecko || window.opera) {
7373 tileBg.style[transform] += ' translate(0,0)';
7374 }
7375
7376 var scaleStr;
7377
7378 // Android 2.* doesn't like translate/scale chains, transformOrigin + scale works better but
7379 // it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack
7380
7381 // TODO work around this prettier
7382 if (L.Browser.android23) {
7383 tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
7384 scaleStr = 'scale(' + scale + ')';
7385 } else {
7386 scaleStr = L.DomUtil.getScaleString(scale, origin);
7387 }
7388
7389 L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
7390
7391 var options = {};
7392 if (backwardsTransform) {
7393 options[transform] = tileBg.style[transform] + ' ' + scaleStr;
7394 } else {
7395 options[transform] = scaleStr + ' ' + tileBg.style[transform];
7396 }
7397
7398 tileBg.transition.run(options);
7399 },
7400
7401 _prepareTileBg: function () {
7402 var tilePane = this._tilePane,
7403 tileBg = this._tileBg;
7404
7405 // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
7406 // (disable this for Android due to it not supporting double translate)
7407 if (!L.Browser.android23 && tileBg &&
7408 this._getLoadedTilesPercentage(tileBg) > 0.5 &&
7409 this._getLoadedTilesPercentage(tilePane) < 0.5) {
7410
7411 tilePane.style.visibility = 'hidden';
7412 tilePane.empty = true;
7413 this._stopLoadingImages(tilePane);
7414 return;
7415 }
7416
7417 if (!tileBg) {
7418 tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
7419 tileBg.style.zIndex = 1;
7420 }
7421
7422 // prepare the background pane to become the main tile pane
7423 tileBg.style[L.DomUtil.TRANSFORM] = '';
7424 tileBg.style.visibility = 'hidden';
7425
7426 // tells tile layers to reinitialize their containers
7427 tileBg.empty = true; //new FG
7428 tilePane.empty = false; //new BG
7429
7430 //Switch out the current layer to be the new bg layer (And vice-versa)
7431 this._tilePane = this._panes.tilePane = tileBg;
7432 var newTileBg = this._tileBg = tilePane;
7433
7434 if (!newTileBg.transition) {
7435 // TODO move to Map options
7436 newTileBg.transition = new L.Transition(newTileBg, {
7437 duration: 0.25,
7438 easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'
7439 });
7440 newTileBg.transition.on('end', this._onZoomTransitionEnd, this);
7441 }
7442
7443 this._stopLoadingImages(newTileBg);
7444 },
7445
7446 _getLoadedTilesPercentage: function (container) {
7447 var tiles = container.getElementsByTagName('img'),
7448 i, len, count = 0;
7449
7450 for (i = 0, len = tiles.length; i < len; i++) {
7451 if (tiles[i].complete) {
7452 count++;
7453 }
7454 }
7455 return count / len;
7456 },
7457
7458 // stops loading all tiles in the background layer
7459 _stopLoadingImages: function (container) {
7460 var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
7461 i, len, tile;
7462
7463 for (i = 0, len = tiles.length; i < len; i++) {
7464 tile = tiles[i];
7465
7466 if (!tile.complete) {
7467 tile.onload = L.Util.falseFn;
7468 tile.onerror = L.Util.falseFn;
7469 tile.src = L.Util.emptyImageUrl;
7470
7471 tile.parentNode.removeChild(tile);
7472 }
7473 }
7474 },
7475
7476 _onZoomTransitionEnd: function () {
7477 this._restoreTileFront();
7478
7479 L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
7480 this._resetView(this._animateToCenter, this._animateToZoom, true, true);
7481
7482 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
7483 this._animatingZoom = false;
7484 },
7485
7486 _restoreTileFront: function () {
7487 this._tilePane.innerHTML = '';
7488 this._tilePane.style.visibility = '';
7489 this._tilePane.style.zIndex = 2;
7490 this._tileBg.style.zIndex = 1;
7491 },
7492
7493 _clearTileBg: function () {
7494 if (!this._animatingZoom && !this.touchZoom._zooming) {
7495 this._tileBg.innerHTML = '';
7496 }
7497 }
7498 });
7499
7500
7501 /*
7502 * Provides L.Map with convenient shortcuts for W3C geolocation.
7503 */
7504
7505 L.Map.include({
7506 _defaultLocateOptions: {
7507 watch: false,
7508 setView: false,
7509 maxZoom: Infinity,
7510 timeout: 10000,
7511 maximumAge: 0,
7512 enableHighAccuracy: false
7513 },
7514
7515 locate: function (/*Object*/ options) {
7516
7517 options = this._locationOptions = L.Util.extend(this._defaultLocateOptions, options);
7518
7519 if (!navigator.geolocation) {
7520 this._handleGeolocationError({
7521 code: 0,
7522 message: "Geolocation not supported."
7523 });
7524 return this;
7525 }
7526
7527 var onResponse = L.Util.bind(this._handleGeolocationResponse, this),
7528 onError = L.Util.bind(this._handleGeolocationError, this);
7529
7530 if (options.watch) {
7531 this._locationWatchId = navigator.geolocation.watchPosition(onResponse, onError, options);
7532 } else {
7533 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
7534 }
7535 return this;
7536 },
7537
7538 stopLocate: function () {
7539 if (navigator.geolocation) {
7540 navigator.geolocation.clearWatch(this._locationWatchId);
7541 }
7542 return this;
7543 },
7544
7545 _handleGeolocationError: function (error) {
7546 var c = error.code,
7547 message = error.message ||
7548 (c === 1 ? "permission denied" :
7549 (c === 2 ? "position unavailable" : "timeout"));
7550
7551 if (this._locationOptions.setView && !this._loaded) {
7552 this.fitWorld();
7553 }
7554
7555 this.fire('locationerror', {
7556 code: c,
7557 message: "Geolocation error: " + message + "."
7558 });
7559 },
7560
7561 _handleGeolocationResponse: function (pos) {
7562 var latAccuracy = 180 * pos.coords.accuracy / 4e7,
7563 lngAccuracy = latAccuracy * 2,
7564
7565 lat = pos.coords.latitude,
7566 lng = pos.coords.longitude,
7567 latlng = new L.LatLng(lat, lng),
7568
7569 sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
7570 ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
7571 bounds = new L.LatLngBounds(sw, ne),
7572
7573 options = this._locationOptions;
7574
7575 if (options.setView) {
7576 var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
7577 this.setView(latlng, zoom);
7578 }
7579
7580 this.fire('locationfound', {
7581 latlng: latlng,
7582 bounds: bounds,
7583 accuracy: pos.coords.accuracy
7584 });
7585 }
7586 });
7587
7588
7589
7590
7591 }(this));
1010 .leaflet-popup-pane,
1111 .leaflet-overlay-pane svg,
1212 .leaflet-zoom-box,
13 .leaflet-image-layer { /* TODO optimize classes */
13 .leaflet-image-layer,
14 .leaflet-layer { /* TODO optimize classes */
1415 position: absolute;
1516 }
1617 .leaflet-container {
1718 overflow: hidden;
18 }
19 .leaflet-tile-pane, .leaflet-container {
20 -webkit-transform: translate3d(0,0,0);
19 outline: 0;
2120 }
2221 .leaflet-tile,
2322 .leaflet-marker-icon,
3332 .leaflet-clickable {
3433 cursor: pointer;
3534 }
35 .leaflet-dragging, .leaflet-dragging .leaflet-clickable {
36 cursor: move;
37 }
3638 .leaflet-container img {
39 /* map is broken in FF if you have max-width: 100% on tiles */
3740 max-width: none !important;
3841 }
42 .leaflet-container img.leaflet-image-layer {
43 /* stupid Android 2 doesn't understand "max-width: none" properly */
44 max-width: 15000px !important;
45 }
3946
4047 .leaflet-tile-pane { z-index: 2; }
41
4248 .leaflet-objects-pane { z-index: 3; }
4349 .leaflet-overlay-pane { z-index: 4; }
4450 .leaflet-shadow-pane { z-index: 5; }
4551 .leaflet-marker-pane { z-index: 6; }
4652 .leaflet-popup-pane { z-index: 7; }
4753
48 .leaflet-zoom-box {
49 width: 0;
50 height: 0;
51 }
52
5354 .leaflet-tile {
54 visibility: hidden;
55 filter: inherit;
56 visibility: hidden;
5557 }
5658 .leaflet-tile-loaded {
5759 visibility: inherit;
5860 }
5961
60 a.leaflet-active {
61 outline: 2px solid orange;
62 }
63
62 .leaflet-zoom-box {
63 width: 0;
64 height: 0;
65 }
6466
6567 /* Leaflet controls */
6668
6769 .leaflet-control {
6870 position: relative;
6971 z-index: 7;
72 pointer-events: auto;
7073 }
7174 .leaflet-top,
7275 .leaflet-bottom {
7376 position: absolute;
77 z-index: 1000;
78 pointer-events: none;
7479 }
7580 .leaflet-top {
7681 top: 0;
104109 margin-right: 10px;
105110 }
106111
107 .leaflet-control-zoom, .leaflet-control-layers {
112 .leaflet-control-zoom {
108113 -moz-border-radius: 7px;
109114 -webkit-border-radius: 7px;
110115 border-radius: 7px;
131136 .leaflet-control-zoom a:hover {
132137 background-color: #fff;
133138 }
134 .leaflet-big-buttons .leaflet-control-zoom a {
139 .leaflet-touch .leaflet-control-zoom a {
135140 width: 27px;
136141 height: 27px;
137142 }
144149 }
145150
146151 .leaflet-control-layers {
147 -moz-box-shadow: 0 0 7px #999;
148 -webkit-box-shadow: 0 0 7px #999;
149 box-shadow: 0 0 7px #999;
150
152 box-shadow: 0 1px 7px #999;
151153 background: #f8f8f9;
154 -moz-border-radius: 8px;
155 -webkit-border-radius: 8px;
156 border-radius: 8px;
152157 }
153158 .leaflet-control-layers a {
154159 background-image: url(images/layers.png);
155160 width: 36px;
156161 height: 36px;
157162 }
158 .leaflet-big-buttons .leaflet-control-layers a {
163 .leaflet-touch .leaflet-control-layers a {
159164 width: 44px;
160165 height: 44px;
161166 }
188193 }
189194
190195 .leaflet-container .leaflet-control-attribution {
196 background-color: rgba(255, 255, 255, 0.7);
197 box-shadow: 0 0 5px #bbb;
191198 margin: 0;
199 }
200
201 .leaflet-control-attribution,
202 .leaflet-control-scale-line {
192203 padding: 0 5px;
193
204 color: #333;
205 }
206
207 .leaflet-container .leaflet-control-attribution,
208 .leaflet-container .leaflet-control-scale {
194209 font: 11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
195 color: #333;
196
197 background-color: rgba(255, 255, 255, 0.7);
198
199 -moz-box-shadow: 0 0 7px #ccc;
200 -webkit-box-shadow: 0 0 7px #ccc;
201 box-shadow: 0 0 7px #ccc;
202 }
203
204
205 /* Fade animations */
206
207 .leaflet-fade-anim .leaflet-tile {
210 }
211
212 .leaflet-left .leaflet-control-scale {
213 margin-left: 5px;
214 }
215 .leaflet-bottom .leaflet-control-scale {
216 margin-bottom: 5px;
217 }
218
219 .leaflet-control-scale-line {
220 border: 2px solid #777;
221 border-top: none;
222 color: black;
223 line-height: 1;
224 font-size: 10px;
225 padding-bottom: 2px;
226 text-shadow: 1px 1px 1px #fff;
227 background-color: rgba(255, 255, 255, 0.5);
228 }
229 .leaflet-control-scale-line:not(:first-child) {
230 border-top: 2px solid #777;
231 padding-top: 1px;
232 border-bottom: none;
233 margin-top: -2px;
234 }
235 .leaflet-control-scale-line:not(:first-child):not(:last-child) {
236 border-bottom: 2px solid #777;
237 }
238
239 .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers {
240 box-shadow: none;
241 }
242 .leaflet-touch .leaflet-control-layers {
243 border: 5px solid #bbb;
244 }
245
246
247 /* Zoom and fade animations */
248
249 .leaflet-fade-anim .leaflet-tile, .leaflet-fade-anim .leaflet-popup {
208250 opacity: 0;
209251
210252 -webkit-transition: opacity 0.2s linear;
212254 -o-transition: opacity 0.2s linear;
213255 transition: opacity 0.2s linear;
214256 }
215 .leaflet-fade-anim .leaflet-tile-loaded {
257 .leaflet-fade-anim .leaflet-tile-loaded, .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
216258 opacity: 1;
217259 }
218260
219 .leaflet-fade-anim .leaflet-popup {
220 opacity: 0;
221
222 -webkit-transition: opacity 0.2s linear;
223 -moz-transition: opacity 0.2s linear;
224 -o-transition: opacity 0.2s linear;
225 transition: opacity 0.2s linear;
226 }
227 .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
228 opacity: 1;
229 }
230
231 .leaflet-zoom-anim .leaflet-tile {
232 -webkit-transition: none;
233 -moz-transition: none;
234 -o-transition: none;
235 transition: none;
236 }
237
238 .leaflet-zoom-anim .leaflet-objects-pane {
261 .leaflet-zoom-anim .leaflet-zoom-animated {
262 -webkit-transition: -webkit-transform 0.25s cubic-bezier(0.25,0.1,0.25,0.75);
263 -moz-transition: -moz-transform 0.25s cubic-bezier(0.25,0.1,0.25,0.75);
264 -o-transition: -o-transform 0.25s cubic-bezier(0.25,0.1,0.25,0.75);
265 transition: transform 0.25s cubic-bezier(0.25,0.1,0.25,0.75);
266 }
267
268 .leaflet-zoom-anim .leaflet-tile,
269 .leaflet-pan-anim .leaflet-tile,
270 .leaflet-touching .leaflet-zoom-animated {
271 -webkit-transition: none;
272 -moz-transition: none;
273 -o-transition: none;
274 transition: none;
275 }
276
277 .leaflet-zoom-anim .leaflet-zoom-hide {
239278 visibility: hidden;
240279 }
241280
245284 .leaflet-popup {
246285 position: absolute;
247286 text-align: center;
248 -webkit-transform: translate3d(0,0,0);
249287 }
250288 .leaflet-popup-content-wrapper {
251289 padding: 1px;
252290 text-align: left;
253291 }
254292 .leaflet-popup-content {
255 margin: 19px;
293 margin: 14px 20px;
256294 }
257295 .leaflet-popup-tip-container {
258296 margin: 0 auto;
259297 width: 40px;
260 height: 16px;
298 height: 20px;
261299 position: relative;
262300 overflow: hidden;
263301 }
274312 -o-transform: rotate(45deg);
275313 transform: rotate(45deg);
276314 }
277 .leaflet-popup-close-button {
315 .leaflet-container a.leaflet-popup-close-button {
278316 position: absolute;
279 top: 9px;
280 right: 9px;
281
282 width: 10px;
283 height: 10px;
284
285 overflow: hidden;
317 top: 0;
318 right: 0;
319 padding: 4px 5px 0 0;
320 text-align: center;
321 width: 18px;
322 height: 14px;
323 font: 16px/14px Tahoma, Verdana, sans-serif;
324 color: #c3c3c3;
325 text-decoration: none;
326 font-weight: bold;
327 }
328 .leaflet-container a.leaflet-popup-close-button:hover {
329 color: #999;
286330 }
287331 .leaflet-popup-content p {
288332 margin: 18px 0;
289333 }
334 .leaflet-popup-scrolled {
335 overflow: auto;
336 border-bottom: 1px solid #ddd;
337 border-top: 1px solid #ddd;
338 }
290339
291340
292341 /* Visual appearance */
297346 .leaflet-container a {
298347 color: #0078A8;
299348 }
349 .leaflet-container a.leaflet-active {
350 outline: 2px solid orange;
351 }
300352 .leaflet-zoom-box {
301353 border: 2px dotted #05f;
302354 background: white;
303355 opacity: 0.5;
304356 }
357 .leaflet-div-icon {
358 background: #fff;
359 border: 1px solid #666;
360 }
361 .leaflet-editing-icon {
362 border-radius: 2px;
363 }
305364 .leaflet-popup-content-wrapper, .leaflet-popup-tip {
306365 background: white;
307366
308 box-shadow: 0 1px 10px #888;
309 -moz-box-shadow: 0 1px 10px #888;
310 -webkit-box-shadow: 0 1px 14px #999;
367 box-shadow: 0 3px 10px #888;
368 -moz-box-shadow: 0 3px 10px #888;
369 -webkit-box-shadow: 0 3px 14px #999;
311370 }
312371 .leaflet-popup-content-wrapper {
313372 -moz-border-radius: 20px;
317376 .leaflet-popup-content {
318377 font: 12px/1.4 "Helvetica Neue", Arial, Helvetica, sans-serif;
319378 }
320 .leaflet-popup-close-button {
321 background: white url(images/popup-close.png);
322 }
0 .leaflet-tile {
1 filter: inherit;
2 }
3
40 .leaflet-vml-shape {
51 width: 1px;
62 height: 1px;
0 /*
1 Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
2 Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
3 http://leaflet.cloudmade.com
4 */
5 (function(e,t){var n,r;typeof exports!=t+""?n=exports:(r=e.L,n={},n.noConflict=function(){return e.L=r,this},e.L=n),n.version="0.4",n.Util={extend:function(e){var t=Array.prototype.slice.call(arguments,1);for(var n=0,r=t.length,i;n<r;n++){i=t[n]||{};for(var s in i)i.hasOwnProperty(s)&&(e[s]=i[s])}return e},bind:function(e,t){var n=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return e.apply(t,n||arguments)}},stamp:function(){var e=0,t="_leaflet_id";return function(n){return n[t]=n[t]||++e,n[t]}}(),limitExecByInterval:function(e,t,n){var r,i;return function s(){var o=arguments;if(r){i=!0;return}r=!0,setTimeout(function(){r=!1,i&&(s.apply(n,o),i=!1)},t),e.apply(n,o)}},falseFn:function(){return!1},formatNum:function(e,t){var n=Math.pow(10,t||5);return Math.round(e*n)/n},splitWords:function(e){return e.replace(/^\s+|\s+$/g,"").split(/\s+/)},setOptions:function(e,t){return e.options=n.Util.extend({},e.options,t),e.options},getParamString:function(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n+"="+e[n]);return"?"+t.join("&")},template:function(e,t){return e.replace(/\{ *([\w_]+) *\}/g,function(e,n){var r=t[n];if(!t.hasOwnProperty(n))throw Error("No value provided for variable "+e);return r})},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function t(t){var n,r,i=["webkit","moz","o","ms"];for(n=0;n<i.length&&!r;n++)r=e[i[n]+t];return r}function r(t){return e.setTimeout(t,1e3/60)}var i=e.requestAnimationFrame||t("RequestAnimationFrame")||r,s=e.cancelAnimationFrame||t("CancelAnimationFrame")||t("CancelRequestAnimationFrame")||function(t){e.clearTimeout(t)};n.Util.requestAnimFrame=function(t,s,o,u){t=n.Util.bind(t,s);if(!o||i!==r)return i.call(e,t,u);t()},n.Util.cancelAnimFrame=function(t){t&&s.call(e,t)}}(),n.Class=function(){},n.Class.extend=function(e){var t=function(){this.initialize&&this.initialize.apply(this,arguments)},r=function(){};r.prototype=this.prototype;var i=new r;i.constructor=t,t.prototype=i;for(var s in this)this.hasOwnProperty(s)&&s!=="prototype"&&(t[s]=this[s]);return e.statics&&(n.Util.extend(t,e.statics),delete e.statics),e.includes&&(n.Util.extend.apply(null,[i].concat(e.includes)),delete e.includes),e.options&&i.options&&(e.options=n.Util.extend({},i.options,e.options)),n.Util.extend(i,e),t},n.Class.include=function(e){n.Util.extend(this.prototype,e)},n.Class.mergeOptions=function(e){n.Util.extend(this.prototype.options,e)};var i="_leaflet_events";n.Mixin={},n.Mixin.Events={addEventListener:function(e,t,r){var s=this[i]=this[i]||{},o,u,a;if(typeof e=="object"){for(o in e)e.hasOwnProperty(o)&&this.addEventListener(o,e[o],t);return this}e=n.Util.splitWords(e);for(u=0,a=e.length;u<a;u++)s[e[u]]=s[e[u]]||[],s[e[u]].push({action:t,context:r||this});return this},hasEventListeners:function(e){return i in this&&e in this[i]&&this[i][e].length>0},removeEventListener:function(e,t,r){var s=this[i],o,u,a,f,l;if(typeof e=="object"){for(o in e)e.hasOwnProperty(o)&&this.removeEventListener(o,e[o],t);return this}e=n.Util.splitWords(e);for(u=0,a=e.length;u<a;u++)if(this.hasEventListeners(e[u])){f=s[e[u]];for(l=f.length-1;l>=0;l--)(!t||f[l].action===t)&&(!r||f[l].context===r)&&f.splice(l,1)}return this},fireEvent:function(e,t){if(!this.hasEventListeners(e))return this;var r=n.Util.extend({type:e,target:this},t),s=this[i][e].slice();for(var o=0,u=s.length;o<u;o++)s[o].action.call(s[o].context||this,r);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 r=navigator.userAgent.toLowerCase(),i=!!e.ActiveXObject,s=i&&!e.XMLHttpRequest,o=r.indexOf("webkit")!==-1,u=r.indexOf("gecko")!==-1,a=r.indexOf("chrome")!==-1,f=e.opera,l=r.indexOf("android")!==-1,c=r.search("android [23]")!==-1,h=typeof orientation!=t+""?!0:!1,p=document.documentElement,d=i&&"transition"in p.style,v=o&&"WebKitCSSMatrix"in e&&"m11"in new e.WebKitCSSMatrix,m=u&&"MozPerspective"in p.style,g=f&&"OTransition"in p.style,y=!e.L_NO_TOUCH&&function(){var e="ontouchstart";if(e in p)return!0;var t=document.createElement("div"),n=!1;return t.setAttribute?(t.setAttribute(e,"return;"),typeof t[e]=="function"&&(n=!0),t.removeAttribute(e),t=null,n):!1}();n.Browser={ua:r,ie:i,ie6:s,webkit:o,gecko:u,opera:f,android:l,android23:c,chrome:a,ie3d:d,webkit3d:v,gecko3d:m,opera3d:g,any3d:!e.L_DISABLE_3D&&(d||v||m||g),mobile:h,mobileWebkit:h&&o,mobileWebkit3d:h&&v,mobileOpera:h&&f,touch:y}}(),n.Point=function(e,t,n){this.x=n?Math.round(e):e,this.y=n?Math.round(t):t},n.Point.prototype={add:function(e){return this.clone()._add(n.point(e))},_add:function(e){return this.x+=e.x,this.y+=e.y,this},subtract:function(e){return this.clone()._subtract(n.point(e))},_subtract:function(e){return this.x-=e.x,this.y-=e.y,this},divideBy:function(e,t){return new n.Point(this.x/e,this.y/e,t)},multiplyBy:function(e,t){return new n.Point(this.x*e,this.y*e,t)},distanceTo:function(e){e=n.point(e);var t=e.x-this.x,r=e.y-this.y;return Math.sqrt(t*t+r*r)},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},clone:function(){return new n.Point(this.x,this.y)},toString:function(){return"Point("+n.Util.formatNum(this.x)+", "+n.Util.formatNum(this.y)+")"}},n.point=function(e,t,r){return e instanceof n.Point?e:e instanceof Array?new n.Point(e[0],e[1]):isNaN(e)?e:new n.Point(e,t,r)},n.Bounds=n.Class.extend({initialize:function(e,t){if(!e)return;var n=t?[e,t]:e;for(var r=0,i=n.length;r<i;r++)this.extend(n[r])},extend:function(e){return e=n.point(e),!this.min&&!this.max?(this.min=e.clone(),this.max=e.clone()):(this.min.x=Math.min(e.x,this.min.x),this.max.x=Math.max(e.x,this.max.x),this.min.y=Math.min(e.y,this.min.y),this.max.y=Math.max(e.y,this.max.y)),this},getCenter:function(e){return new n.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,e)},getBottomLeft:function(){return new n.Point(this.min.x,this.max.y)},getTopRight:function(){return new n.Point(this.max.x,this.min.y)},contains:function(e){var t,r;return typeof e[0]=="number"||e instanceof n.Point?e=n.point(e):e=n.bounds(e),e instanceof n.Bounds?(t=e.min,r=e.max):t=r=e,t.x>=this.min.x&&r.x<=this.max.x&&t.y>=this.min.y&&r.y<=this.max.y},intersects:function(e){e=n.bounds(e);var t=this.min,r=this.max,i=e.min,s=e.max,o=s.x>=t.x&&i.x<=r.x,u=s.y>=t.y&&i.y<=r.y;return o&&u}}),n.bounds=function(e,t){return!e||e instanceof n.Bounds?e:new n.Bounds(e,t)},n.Transformation=n.Class.extend({initialize:function(e,t,n,r){this._a=e,this._b=t,this._c=n,this._d=r},transform:function(e,t){return this._transform(e.clone(),t)},_transform:function(e,t){return t=t||1,e.x=t*(this._a*e.x+this._b),e.y=t*(this._c*e.y+this._d),e},untransform:function(e,t){return t=t||1,new n.Point((e.x/t-this._b)/this._a,(e.y/t-this._d)/this._c)}}),n.DomUtil={get:function(e){return typeof e=="string"?document.getElementById(e):e},getStyle:function(e,t){var n=e.style[t];!n&&e.currentStyle&&(n=e.currentStyle[t]);if(!n||n==="auto"){var r=document.defaultView.getComputedStyle(e,null);n=r?r[t]:null}return n==="auto"?null:n},getViewportOffset:function(e){var t=0,r=0,i=e,s=document.body;do{t+=i.offsetTop||0,r+=i.offsetLeft||0;if(i.offsetParent===s&&n.DomUtil.getStyle(i,"position")==="absolute")break;if(n.DomUtil.getStyle(i,"position")==="fixed"){t+=s.scrollTop||0,r+=s.scrollLeft||0;break}i=i.offsetParent}while(i);i=e;do{if(i===s)break;t-=i.scrollTop||0,r-=i.scrollLeft||0,i=i.parentNode}while(i);return new n.Point(r,t)},create:function(e,t,n){var r=document.createElement(e);return r.className=t,n&&n.appendChild(r),r},disableTextSelection:function(){document.selection&&document.selection.empty&&document.selection.empty(),this._onselectstart||(this._onselectstart=document.onselectstart,document.onselectstart=n.Util.falseFn)},enableTextSelection:function(){document.onselectstart=this._onselectstart,this._onselectstart=null},hasClass:function(e,t){return e.className.length>0&&RegExp("(^|\\s)"+t+"(\\s|$)").test(e.className)},addClass:function(e,t){n.DomUtil.hasClass(e,t)||(e.className+=(e.className?" ":"")+t)},removeClass:function(e,t){function n(e,n){return n===t?"":e}e.className=e.className.replace(/(\S+)\s*/g,n).replace(/(^\s+|\s+$)/,"")},setOpacity:function(e,t){if("opacity"in e.style)e.style.opacity=t;else if(n.Browser.ie){var r=!1,i="DXImageTransform.Microsoft.Alpha";try{r=e.filters.item(i)}catch(s){}t=Math.round(t*100),r?(r.Enabled=t===100,r.Opacity=t):e.style.filter+=" progid:"+i+"(opacity="+t+")"}},testProp:function(e){var t=document.documentElement.style;for(var n=0;n<e.length;n++)if(e[n]in t)return e[n];return!1},getTranslateString:function(e){var t=n.Browser.webkit3d,r="translate"+(t?"3d":"")+"(",i=(t?",0":"")+")";return r+e.x+"px,"+e.y+"px"+i},getScaleString:function(e,t){var r=n.DomUtil.getTranslateString(t),i=" scale("+e+") ",s=n.DomUtil.getTranslateString(t.multiplyBy(-1));return r+i+s},setPosition:function(e,t,r){e._leaflet_pos=t,!r&&n.Browser.any3d?(e.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(t),n.Browser.mobileWebkit3d&&(e.style.WebkitBackfaceVisibility="hidden")):(e.style.left=t.x+"px",e.style.top=t.y+"px")},getPosition:function(e){return e._leaflet_pos}},n.Util.extend(n.DomUtil,{TRANSITION:n.DomUtil.testProp(["transition","webkitTransition","OTransition","MozTransition","msTransition"]),TRANSFORM:n.DomUtil.testProp(["transform","WebkitTransform","OTransform","MozTransform","msTransform"])}),n.LatLng=function(e,t,n){var r=parseFloat(e),i=parseFloat(t);if(isNaN(r)||isNaN(i))throw Error("Invalid LatLng object: ("+e+", "+t+")");n!==!0&&(r=Math.max(Math.min(r,90),-90),i=(i+180)%360+(i<-180||i===180?180:-180)),this.lat=r,this.lng=i},n.Util.extend(n.LatLng,{DEG_TO_RAD:Math.PI/180,RAD_TO_DEG:180/Math.PI,MAX_MARGIN:1e-9}),n.LatLng.prototype={equals:function(e){if(!e)return!1;e=n.latLng(e);var t=Math.max(Math.abs(this.lat-e.lat),Math.abs(this.lng-e.lng));return t<=n.LatLng.MAX_MARGIN},toString:function(){return"LatLng("+n.Util.formatNum(this.lat)+", "+n.Util.formatNum(this.lng)+")"},distanceTo:function(e){e=n.latLng(e);var t=6378137,r=n.LatLng.DEG_TO_RAD,i=(e.lat-this.lat)*r,s=(e.lng-this.lng)*r,o=this.lat*r,u=e.lat*r,a=Math.sin(i/2),f=Math.sin(s/2),l=a*a+f*f*Math.cos(o)*Math.cos(u);return t*2*Math.atan2(Math.sqrt(l),Math.sqrt(1-l))}},n.latLng=function(e,t,r){return e instanceof n.LatLng?e:e instanceof Array?new n.LatLng(e[0],e[1]):isNaN(e)?e:new n.LatLng(e,t,r)},n.LatLngBounds=n.Class.extend({initialize:function(e,t){if(!e)return;var n=t?[e,t]:e;for(var r=0,i=n.length;r<i;r++)this.extend(n[r])},extend:function(e){return typeof e[0]=="number"||e instanceof n.LatLng?e=n.latLng(e):e=n.latLngBounds(e),e instanceof n.LatLng?!this._southWest&&!this._northEast?(this._southWest=new n.LatLng(e.lat,e.lng,!0),this._northEast=new n.LatLng(e.lat,e.lng,!0)):(this._southWest.lat=Math.min(e.lat,this._southWest.lat),this._southWest.lng=Math.min(e.lng,this._southWest.lng),this._northEast.lat=Math.max(e.lat,this._northEast.lat),this._northEast.lng=Math.max(e.lng,this._northEast.lng)):e instanceof n.LatLngBounds&&(this.extend(e._southWest),this.extend(e._northEast)),this},pad:function(e){var t=this._southWest,r=this._northEast,i=Math.abs(t.lat-r.lat)*e,s=Math.abs(t.lng-r.lng)*e;return new n.LatLngBounds(new n.LatLng(t.lat-i,t.lng-s),new n.LatLng(r.lat+i,r.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,!0)},getSouthEast:function(){return new n.LatLng(this._southWest.lat,this._northEast.lng,!0)},contains:function(e){typeof e[0]=="number"||e instanceof n.LatLng?e=n.latLng(e):e=n.latLngBounds(e);var t=this._southWest,r=this._northEast,i,s;return e instanceof n.LatLngBounds?(i=e.getSouthWest(),s=e.getNorthEast()):i=s=e,i.lat>=t.lat&&s.lat<=r.lat&&i.lng>=t.lng&&s.lng<=r.lng},intersects:function(e){e=n.latLngBounds(e);var t=this._southWest,r=this._northEast,i=e.getSouthWest(),s=e.getNorthEast(),o=s.lat>=t.lat&&i.lat<=r.lat,u=s.lng>=t.lng&&i.lng<=r.lng;return o&&u},toBBoxString:function(){var e=this._southWest,t=this._northEast;return[e.lng,e.lat,t.lng,t.lat].join(",")},equals:function(e){return e?(e=n.latLngBounds(e),this._southWest.equals(e.getSouthWest())&&this._northEast.equals(e.getNorthEast())):!1}}),n.latLngBounds=function(e,t){return!e||e instanceof n.LatLngBounds?e:new n.LatLngBounds(e,t)},n.Projection={},n.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(e){var t=n.LatLng.DEG_TO_RAD,r=this.MAX_LATITUDE,i=Math.max(Math.min(r,e.lat),-r),s=e.lng*t,o=i*t;return o=Math.log(Math.tan(Math.PI/4+o/2)),new n.Point(s,o)},unproject:function(e){var t=n.LatLng.RAD_TO_DEG,r=e.x*t,i=(2*Math.atan(Math.exp(e.y))-Math.PI/2)*t;return new n.LatLng(i,r,!0)}},n.Projection.LonLat={project:function(e){return new n.Point(e.lng,e.lat)},unproject:function(e){return new n.LatLng(e.y,e.x,!0)}},n.CRS={latLngToPoint:function(e,t){var n=this.projection.project(e),r=this.scale(t);return this.transformation._transform(n,r)},pointToLatLng:function(e,t){var n=this.scale(t),r=this.transformation.untransform(e,n);return this.projection.unproject(r)},project:function(e){return this.projection.project(e)},scale:function(e){return 256*Math.pow(2,e)}},n.CRS.EPSG3857=n.Util.extend({},n.CRS,{code:"EPSG:3857",projection:n.Projection.SphericalMercator,transformation:new n.Transformation(.5/Math.PI,.5,-0.5/Math.PI,.5),project:function(e){var t=this.projection.project(e),n=6378137;return t.multiplyBy(n)}}),n.CRS.EPSG900913=n.Util.extend({},n.CRS.EPSG3857,{code:"EPSG:900913"}),n.CRS.EPSG4326=n.Util.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(e,r){r=n.Util.setOptions(this,r),this._initContainer(e),this._initLayout(),this._initHooks(),this._initEvents(),r.maxBounds&&this.setMaxBounds(r.maxBounds),r.center&&r.zoom!==t&&this.setView(n.latLng(r.center),r.zoom,!0),this._initLayers(r.layers)},setView:function(e,t){return this._resetView(n.latLng(e),this._limitZoom(t)),this},setZoom:function(e){return this.setView(this.getCenter(),e)},zoomIn:function(){return this.setZoom(this._zoom+1)},zoomOut:function(){return this.setZoom(this._zoom-1)},fitBounds:function(e){var t=this.getBoundsZoom(e);return this.setView(n.latLngBounds(e).getCenter(),t)},fitWorld:function(){var e=new n.LatLng(-60,-170),t=new n.LatLng(85,179);return this.fitBounds(new n.LatLngBounds(e,t))},panTo:function(e){return this.setView(e,this._zoom)},panBy:function(e){return this.fire("movestart"),this._rawPanBy(n.point(e)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(e){e=n.latLngBounds(e),this.options.maxBounds=e;if(!e)return this._boundsMinZoom=null,this;var t=this.getBoundsZoom(e,!0);return this._boundsMinZoom=t,this._loaded&&(this._zoom<t?this.setView(e.getCenter(),t):this.panInsideBounds(e)),this},panInsideBounds:function(e){e=n.latLngBounds(e);var t=this.getBounds(),r=this.project(t.getSouthWest()),i=this.project(t.getNorthEast()),s=this.project(e.getSouthWest()),o=this.project(e.getNorthEast()),u=0,a=0;return i.y<o.y&&(a=o.y-i.y),i.x>o.x&&(u=o.x-i.x),r.y>s.y&&(a=s.y-r.y),r.x<s.x&&(u=s.x-r.x),this.panBy(new n.Point(u,a,!0))},addLayer:function(e){var t=n.Util.stamp(e);if(this._layers[t])return this;this._layers[t]=e,e.options&&!isNaN(e.options.maxZoom)&&(this._layersMaxZoom=Math.max(this._layersMaxZoom||0,e.options.maxZoom)),e.options&&!isNaN(e.options.minZoom)&&(this._layersMinZoom=Math.min(this._layersMinZoom||Infinity,e.options.minZoom)),this.options.zoomAnimation&&n.TileLayer&&e instanceof n.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,e.on("load",this._onTileLayerLoad,this));var r=function(){e.onAdd(this),this.fire("layeradd",{layer:e})};return this._loaded?r.call(this):this.on("load",r,this),this},removeLayer:function(e){var t=n.Util.stamp(e);if(!this._layers[t])return;return e.onRemove(this),delete this._layers[t],this.options.zoomAnimation&&n.TileLayer&&e instanceof n.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,e.off("load",this._onTileLayerLoad,this)),this.fire("layerremove",{layer:e})},hasLayer:function(e){var t=n.Util.stamp(e);return this._layers.hasOwnProperty(t)},invalidateSize:function(){var e=this.getSize();this._sizeChanged=!0,this.options.maxBounds&&this.setMaxBounds(this.options.maxBounds);if(!this._loaded)return this;var t=e.subtract(this.getSize()).divideBy(2,!0);return this._rawPanBy(t),this.fire("move"),clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(n.Util.bind(this.fire,this,"moveend"),200),this},addHandler:function(e,t){if(!t)return;return this[e]=new t(this),this.options[e]&&this[e].enable(),this},getCenter:function(){return this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var e=this.getPixelBounds(),t=this.unproject(e.getBottomLeft()),r=this.unproject(e.getTopRight());return new n.LatLngBounds(t,r)},getMinZoom:function(){var e=this.options.minZoom||0,t=this._layersMinZoom||0,n=this._boundsMinZoom||0;return Math.max(e,t,n)},getMaxZoom:function(){var e=this.options.maxZoom===t?Infinity:this.options.maxZoom,n=this._layersMaxZoom===t?Infinity:this._layersMaxZoom;return Math.min(e,n)},getBoundsZoom:function(e,t){e=n.latLngBounds(e);var r=this.getSize(),i=this.options.minZoom||0,s=this.getMaxZoom(),o=e.getNorthEast(),u=e.getSouthWest(),a,f,l,c=!0;t&&i--;do i++,f=this.project(o,i),l=this.project(u,i),a=new n.Point(Math.abs(f.x-l.x),Math.abs(l.y-f.y)),t?c=a.x<r.x||a.y<r.y:c=a.x<=r.x&&a.y<=r.y;while(c&&i<=s);return c&&t?null:t?i:i-1},getSize:function(){if(!this._size||this._sizeChanged)this._size=new n.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1;return this._size},getPixelBounds:function(){var e=this._getTopLeftPoint();return new n.Bounds(e,e.add(this.getSize()))},getPixelOrigin:function(){return this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(e){var t=this.options.crs;return t.scale(e)/t.scale(this._zoom)},getScaleZoom:function(e){return this._zoom+Math.log(e)/Math.LN2},project:function(e,r){return r=r===t?this._zoom:r,this.options.crs.latLngToPoint(n.latLng(e),r)},unproject:function(e,r){return r=r===t?this._zoom:r,this.options.crs.pointToLatLng(n.point(e),r)},layerPointToLatLng:function(e){var t=n.point(e).add(this._initialTopLeftPoint);return this.unproject(t)},latLngToLayerPoint:function(e){var t=this.project(n.latLng(e))._round();return t._subtract(this._initialTopLeftPoint)},containerPointToLayerPoint:function(e){return n.point(e).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(e){return n.point(e).add(this._getMapPanePos())},containerPointToLatLng:function(e){var t=this.containerPointToLayerPoint(n.point(e));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(e){return this.layerPointToContainerPoint(this.latLngToLayerPoint(n.latLng(e)))},mouseEventToContainerPoint:function(e){return n.DomEvent.getMousePosition(e,this._container)},mouseEventToLayerPoint:function(e){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e))},mouseEventToLatLng:function(e){return this.layerPointToLatLng(this.mouseEventToLayerPoint(e))},_initContainer:function(e){var t=this._container=n.DomUtil.get(e);if(t._leaflet)throw Error("Map container is already initialized.");t._leaflet=!0},_initLayout:function(){var e=this._container;e.innerHTML="",n.DomUtil.addClass(e,"leaflet-container"),n.Browser.touch&&n.DomUtil.addClass(e,"leaflet-touch"),this.options.fadeAnimation&&n.DomUtil.addClass(e,"leaflet-fade-anim");var t=n.DomUtil.getStyle(e,"position");t!=="absolute"&&t!=="relative"&&t!=="fixed"&&(e.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var e=this._panes={};this._mapPane=e.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=e.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),this._objectsPane=e.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),e.shadowPane=this._createPane("leaflet-shadow-pane"),e.overlayPane=this._createPane("leaflet-overlay-pane"),e.markerPane=this._createPane("leaflet-marker-pane"),e.popupPane=this._createPane("leaflet-popup-pane");var t=" leaflet-zoom-hide";this.options.markerZoomAnimation||(n.DomUtil.addClass(e.markerPane,t),n.DomUtil.addClass(e.shadowPane,t),n.DomUtil.addClass(e.popupPane,t))},_createPane:function(e,t){return n.DomUtil.create("div",e,t||this._objectsPane)},_initializers:[],_initHooks:function(){var e,t;for(e=0,t=this._initializers.length;e<t;e++)this._initializers[e].call(this)},_initLayers:function(e){e=e?e instanceof Array?e:[e]:[],this._layers={},this._tileLayersNum=0;var t,n;for(t=0,n=e.length;t<n;t++)this.addLayer(e[t])},_resetView:function(e,t,r,i){var s=this._zoom!==t;i||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=t,this._initialTopLeftPoint=this._getNewTopLeftPoint(e),r?this._initialTopLeftPoint._add(this._getMapPanePos()):n.DomUtil.setPosition(this._mapPane,new n.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum,this.fire("viewreset",{hard:!r}),this.fire("move"),(s||i)&&this.fire("zoomend"),this.fire("moveend",{hard:!r}),this._loaded||(this._loaded=!0,this.fire("load"))},_rawPanBy:function(e){n.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(e))},_initEvents:function(){if(!n.DomEvent)return;n.DomEvent.on(this._container,"click",this._onMouseClick,this);var t=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"],r,i;for(r=0,i=t.length;r<i;r++)n.DomEvent.on(this._container,t[r],this._fireMouseEvent,this);this.options.trackResize&&n.DomEvent.on(e,"resize",this._onResize,this)},_onResize:function(){n.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=n.Util.requestAnimFrame(this.invalidateSize,this,!1,this._container)},_onMouseClick:function(e){if(!this._loaded||this.dragging&&this.dragging.moved())return;this.fire("preclick"),this._fireMouseEvent(e)},_fireMouseEvent:function(e){if(!this._loaded)return;var t=e.type;t=t==="mouseenter"?"mouseover":t==="mouseleave"?"mouseout":t;if(!this.hasEventListeners(t))return;t==="contextmenu"&&n.DomEvent.preventDefault(e);var r=this.mouseEventToContainerPoint(e),i=this.containerPointToLayerPoint(r),s=this.layerPointToLatLng(i);this.fire(t,{latlng:s,layerPoint:i,containerPoint:r,originalEvent:e})},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this._tileBg&&(clearTimeout(this._clearTileBgTimer),this._clearTileBgTimer=setTimeout(n.Util.bind(this._clearTileBg,this),500))},_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(e,t){var n=this.getSize().divideBy(2);return this.project(e,t)._subtract(n)._round()},_latLngToNewLayerPoint:function(e,t,n){var r=this._getNewTopLeftPoint(n,t).add(this._getMapPanePos());return this.project(e,t)._subtract(r)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize().divideBy(2))},_getCenterOffset:function(e){return this.latLngToLayerPoint(e).subtract(this._getCenterLayerPoint())},_limitZoom:function(e){var t=this.getMinZoom(),n=this.getMaxZoom();return Math.max(t,Math.min(n,e))}}),n.Map.addInitHook=function(e){var t=Array.prototype.slice.call(arguments,1),n=typeof e=="function"?e:function(){this[e].apply(this,t)};this.prototype._initializers.push(n)},n.map=function(e,t){return new n.Map(e,t)},n.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.3142,R_MAJOR:6378137,project:function(e){var t=n.LatLng.DEG_TO_RAD,r=this.MAX_LATITUDE,i=Math.max(Math.min(r,e.lat),-r),s=this.R_MAJOR,o=this.R_MINOR,u=e.lng*t*s,a=i*t,f=o/s,l=Math.sqrt(1-f*f),c=l*Math.sin(a);c=Math.pow((1-c)/(1+c),l*.5);var h=Math.tan(.5*(Math.PI*.5-a))/c;return a=-o*Math.log(h),new n.Point(u,a)},unproject:function(e){var t=n.LatLng.RAD_TO_DEG,r=this.R_MAJOR,i=this.R_MINOR,s=e.x*t/r,o=i/r,u=Math.sqrt(1-o*o),a=Math.exp(-e.y/i),f=Math.PI/2-2*Math.atan(a),l=15,c=1e-7,h=l,p=.1,d;while(Math.abs(p)>c&&--h>0)d=u*Math.sin(f),p=Math.PI/2-2*Math.atan(a*Math.pow((1-d)/(1+d),.5*u))-f,f+=p;return new n.LatLng(f*t,s,!0)}},n.CRS.EPSG3395=n.Util.extend({},n.CRS,{code:"EPSG:3395",projection:n.Projection.Mercator,transformation:function(){var e=n.Projection.Mercator,t=e.R_MAJOR,r=e.R_MINOR;return new n.Transformation(.5/(Math.PI*t),.5,-0.5/(Math.PI*r),.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,r){r=n.Util.setOptions(this,r),r.detectRetina&&e.devicePixelRatio>1&&r.maxZoom>0&&(r.tileSize=Math.floor(r.tileSize/2),r.zoomOffset++,r.minZoom>0&&r.minZoom--,this.options.maxZoom--),this._url=t;var i=this.options.subdomains;typeof i=="string"&&(this.options.subdomains=i.split(""))},onAdd:function(e){this._map=e,this._initContainer(),this._createTileProto(),e.on({viewreset:this._resetCallback,moveend:this._update},this),this.options.updateWhenIdle||(this._limitedUpdate=n.Util.limitExecByInterval(this._update,150,this),e.on("move",this._limitedUpdate,this)),this._reset(),this._update()},addTo:function(e){return e.addLayer(this),this},onRemove:function(e){e._panes.tilePane.removeChild(this._container),e.off({viewreset:this._resetCallback,moveend:this._update},this),this.options.updateWhenIdle||e.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var e=this._map._panes.tilePane;return this._container&&(e.appendChild(this._container),this._setAutoZIndex(e,Math.max)),this},bringToBack:function(){var e=this._map._panes.tilePane;return this._container&&(e.insertBefore(this._container,e.firstChild),this._setAutoZIndex(e,Math.min)),this},getAttribution:function(){return this.options.attribution},setOpacity:function(e){return this.options.opacity=e,this._map&&this._updateOpacity(),this},setZIndex:function(e){return this.options.zIndex=e,this._updateZIndex(),this},setUrl:function(e,t){return this._url=e,t||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!==t&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(e,t){var n=e.getElementsByClassName("leaflet-layer"),r=-t(Infinity,-Infinity),i;for(var s=0,o=n.length;s<o;s++)n[s]!==this._container&&(i=parseInt(n[s].style.zIndex,10),isNaN(i)||(r=t(r,i)));this._container.style.zIndex=isFinite(r)?r+t(1,-1):""},_updateOpacity:function(){n.DomUtil.setOpacity(this._container,this.options.opacity);var e,t=this._tiles;if(n.Browser.webkit)for(e in t)t.hasOwnProperty(e)&&(t[e].style.webkitTransform+=" translate(0,0)")},_initContainer:function(){var e=this._map._panes.tilePane;if(!this._container||e.empty)this._container=n.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),e.appendChild(this._container),this.options.opacity<1&&this._updateOpacity()},_resetCallback:function(e){this._reset(e.hard)},_reset:function(e){var t,n=this._tiles;for(t in n)n.hasOwnProperty(t)&&this.fire("tileunload",{tile:n[t]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),e&&this._container&&(this._container.innerHTML=""),this._initContainer()},_update:function(e){if(this._map._panTransition&&this._map._panTransition._inProgress)return;var t=this._map.getPixelBounds(),r=this._map.getZoom(),i=this.options.tileSize;if(r>this.options.maxZoom||r<this.options.minZoom)return;var s=new n.Point(Math.floor(t.min.x/i),Math.floor(t.min.y/i)),o=new n.Point(Math.floor(t.max.x/i),Math.floor(t.max.y/i)),u=new n.Bounds(s,o);this._addTilesFromCenterOut(u),(this.options.unloadInvisibleTiles||this.options.reuseTiles)&&this._removeOtherTiles(u)},_addTilesFromCenterOut:function(e){var t=[],r=e.getCenter(),i,s,o;for(i=e.min.y;i<=e.max.y;i++)for(s=e.min.x;s<=e.max.x;s++)o=new n.Point(s,i),this._tileShouldBeLoaded(o)&&t.push(o);var u=t.length;if(u===0)return;t.sort(function(e,t){return e.distanceTo(r)-t.distanceTo(r)});var a=document.createDocumentFragment();this._tilesToLoad||this.fire("loading"),this._tilesToLoad+=u;for(s=0;s<u;s++)this._addTile(t[s],a);this._container.appendChild(a)},_tileShouldBeLoaded:function(e){if(e.x+":"+e.y in this._tiles)return!1;if(!this.options.continuousWorld){var t=this._getWrapTileNum();if(this.options.noWrap&&(e.x<0||e.x>=t)||e.y<0||e.y>=t)return!1}return!0},_removeOtherTiles:function(e){var t,n,r,i;for(i in this._tiles)this._tiles.hasOwnProperty(i)&&(t=i.split(":"),n=parseInt(t[0],10),r=parseInt(t[1],10),(n<e.min.x||n>e.max.x||r<e.min.y||r>e.max.y)&&this._removeTile(i))},_removeTile:function(e){var t=this._tiles[e];this.fire("tileunload",{tile:t,url:t.src}),this.options.reuseTiles?(n.DomUtil.removeClass(t,"leaflet-tile-loaded"),this._unusedTiles.push(t)):t.parentNode===this._container&&this._container.removeChild(t),n.Browser.android||(t.src=n.Util.emptyImageUrl),delete this._tiles[e]},_addTile:function(e,t){var r=this._getTilePos(e),i=this._getTile();n.DomUtil.setPosition(i,r,n.Browser.chrome),this._tiles[e.x+":"+e.y]=i,this._loadTile(i,e),i.parentNode!==this._container&&t.appendChild(i)},_getZoomForUrl:function(){var e=this.options,t=this._map.getZoom();return e.zoomReverse&&(t=e.maxZoom-t),t+e.zoomOffset},_getTilePos:function(e){var t=this._map.getPixelOrigin(),n=this.options.tileSize;return e.multiplyBy(n).subtract(t)},getTileUrl:function(e){return this._adjustTilePoint(e),n.Util.template(this._url,n.Util.extend({s:this._getSubdomain(e),z:this._getZoomForUrl(),x:e.x,y:e.y},this.options))},_getWrapTileNum:function(){return Math.pow(2,this._getZoomForUrl())},_adjustTilePoint:function(e){var t=this._getWrapTileNum();!this.options.continuousWorld&&!this.options.noWrap&&(e.x=(e.x%t+t)%t),this.options.tms&&(e.y=t-e.y-1)},_getSubdomain:function(e){var t=(e.x+e.y)%this.options.subdomains.length;return this.options.subdomains[t]},_createTileProto:function(){var e=this._tileImg=n.DomUtil.create("img","leaflet-tile");e.galleryimg="no";var t=this.options.tileSize;e.style.width=t+"px",e.style.height=t+"px"},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var e=this._unusedTiles.pop();return this._resetTile(e),e}return this._createTile()},_resetTile:function(e){},_createTile:function(){var e=this._tileImg.cloneNode(!1);return e.onselectstart=e.onmousemove=n.Util.falseFn,e},_loadTile:function(e,t){e._layer=this,e.onload=this._tileOnLoad,e.onerror=this._tileOnError,e.src=this.getTileUrl(t)},_tileLoaded:function(){this._tilesToLoad--,this._tilesToLoad||this.fire("load")},_tileOnLoad:function(e){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(e){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var n=t.options.errorTileUrl;n&&(this.src=n),t._tileLoaded()}}),n.tileLayer=function(e,t){return new n.TileLayer(e,t)},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,r){this._url=t;var i=n.Util.extend({},this.defaultWmsParams);r.detectRetina&&e.devicePixelRatio>1?i.width=i.height=this.options.tileSize*2:i.width=i.height=this.options.tileSize;for(var s in r)this.options.hasOwnProperty(s)||(i[s]=r[s]);this.wmsParams=i,n.Util.setOptions(this,r)},onAdd:function(e){var t=parseFloat(this.wmsParams.version)>=1.3?"crs":"srs";this.wmsParams[t]=e.options.crs.code,n.TileLayer.prototype.onAdd.call(this,e)},getTileUrl:function(e,t){var r=this._map,i=r.options.crs,s=this.options.tileSize,o=e.multiplyBy(s),u=o.add(new n.Point(s,s)),a=i.project(r.unproject(o,t)),f=i.project(r.unproject(u,t)),l=[a.x,f.y,f.x,a.y].join(","),c=n.Util.template(this._url,{s:this._getSubdomain(e)});return c+n.Util.getParamString(this.wmsParams)+"&bbox="+l},setParams:function(e,t){return n.Util.extend(this.wmsParams,e),t||this.redraw(),this}}),n.tileLayer.wms=function(e,t){return new n.TileLayer(e,t)},n.TileLayer.Canvas=n.TileLayer.extend({options:{async:!1},initialize:function(e){n.Util.setOptions(this,e)},redraw:function(){var e,t=this._tiles;for(e in t)t.hasOwnProperty(e)&&this._redrawTile(t[e])},_redrawTile:function(e){this.drawTile(e,e._tilePoint,e._zoom)},_createTileProto:function(){var e=this._canvasProto=n.DomUtil.create("canvas","leaflet-tile"),t=this.options.tileSize;e.width=t,e.height=t},_createTile:function(){var e=this._canvasProto.cloneNode(!1);return e.onselectstart=e.onmousemove=n.Util.falseFn,e},_loadTile:function(e,t,n){e._layer=this,e._tilePoint=t,e._zoom=n,this.drawTile(e,t,n),this.options.async||this.tileDrawn(e)},drawTile:function(e,t,n){},tileDrawn:function(e){this._tileOnLoad.call(e)}}),n.tileLayer.canvas=function(e){return new n.TileLayer.Canvas(e)},n.ImageOverlay=n.Class.extend({includes:n.Mixin.Events,options:{opacity:1},initialize:function(e,t,r){this._url=e,this._bounds=n.latLngBounds(t),n.Util.setOptions(this,r)},onAdd:function(e){this._map=e,this._image||this._initImage(),e._panes.overlayPane.appendChild(this._image),e.on("viewreset",this._reset,this),e.options.zoomAnimation&&n.Browser.any3d&&e.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(e){e.getPanes().overlayPane.removeChild(this._image),e.off("viewreset",this._reset,this),e.options.zoomAnimation&&e.off("zoomanim",this._animateZoom,this)},addTo:function(e){return e.addLayer(this),this},setOpacity:function(e){return this.options.opacity=e,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var e=this._map._panes.overlayPane;return this._image&&e.insertBefore(this._image,e.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.Util.extend(this._image,{galleryimg:"no",onselectstart:n.Util.falseFn,onmousemove:n.Util.falseFn,onload:n.Util.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(e){var t=this._map,r=this._image,i=t.getZoomScale(e.zoom),s=this._bounds.getNorthWest(),o=this._bounds.getSouthEast(),u=t._latLngToNewLayerPoint(s,e.zoom,e.center),a=t._latLngToNewLayerPoint(o,e.zoom,e.center).subtract(u),f=t.latLngToLayerPoint(o).subtract(t.latLngToLayerPoint(s)),l=u.add(a.subtract(f).divideBy(2));r.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(l)+" scale("+i+") "},_reset:function(){var e=this._image,t=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),r=this._map.latLngToLayerPoint(this._bounds.getSouthEast()).subtract(t);n.DomUtil.setPosition(e,t),e.style.width=r.x+"px",e.style.height=r.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){n.DomUtil.setOpacity(this._image,this.options.opacity)}}),n.imageOverlay=function(e,t,r){return new n.ImageOverlay(e,t,r)},n.Icon=n.Class.extend({options:{className:""},initialize:function(e){n.Util.setOptions(this,e)},createIcon:function(){return this._createIcon("icon")},createShadow:function(){return this._createIcon("shadow")},_createIcon:function(e){var t=this._getIconUrl(e);if(!t){if(e==="icon")throw Error("iconUrl not set in Icon options (see the docs).");return null}var n=this._createImg(t);return this._setIconStyles(n,e),n},_setIconStyles:function(e,t){var r=this.options,i=n.point(r[t+"Size"]),s;t==="shadow"?s=n.point(r.shadowAnchor||r.iconAnchor):s=n.point(r.iconAnchor),!s&&i&&(s=i.divideBy(2,!0)),e.className="leaflet-marker-"+t+" "+r.className,s&&(e.style.marginLeft=-s.x+"px",e.style.marginTop=-s.y+"px"),i&&(e.style.width=i.x+"px",e.style.height=i.y+"px")},_createImg:function(e){var t;return n.Browser.ie6?(t=document.createElement("div"),t.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+e+'")'):(t=document.createElement("img"),t.src=e),t},_getIconUrl:function(e){return this.options[e+"Url"]}}),n.icon=function(e){return new n.Icon(e)},n.Icon.Default=n.Icon.extend({options:{iconSize:new n.Point(25,41),iconAnchor:new n.Point(13,41),popupAnchor:new n.Point(1,-34),shadowSize:new n.Point(41,41)},_getIconUrl:function(e){var t=e+"Url";if(this.options[t])return this.options[t];var r=n.Icon.Default.imagePath;if(!r)throw Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return r+"/marker-"+e+".png"}}),n.Icon.Default.imagePath=function(){var e=document.getElementsByTagName("script"),t=/\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/,n,r,i,s;for(n=0,r=e.length;n<r;n++){i=e[n].src,s=i.match(t);if(s)return i.split(t)[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},initialize:function(e,t){n.Util.setOptions(this,t),this._latlng=n.latLng(e)},onAdd:function(e){this._map=e,e.on("viewreset",this.update,this),this._initIcon(),this.update(),e.options.zoomAnimation&&e.options.markerZoomAnimation&&e.on("zoomanim",this._animateZoom,this)},addTo:function(e){return e.addLayer(this),this},onRemove:function(e){this._removeIcon(),this.closePopup&&this.closePopup(),e.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(e){this._latlng=n.latLng(e),this.update(),this._popup&&this._popup.setLatLng(e)},setZIndexOffset:function(e){this.options.zIndexOffset=e,this.update()},setIcon:function(e){this._map&&this._removeIcon(),this.options.icon=e,this._map&&(this._initIcon(),this.update())},update:function(){if(!this._icon)return;var e=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(e)},_initIcon:function(){var e=this.options,t=this._map,r=t.options.zoomAnimation&&t.options.markerZoomAnimation,i=r?"leaflet-zoom-animated":"leaflet-zoom-hide",s=!1;this._icon||(this._icon=e.icon.createIcon(),e.title&&(this._icon.title=e.title),this._initInteraction(),s=!0,n.DomUtil.addClass(this._icon,i)),this._shadow||(this._shadow=e.icon.createShadow(),this._shadow&&(n.DomUtil.addClass(this._shadow,i),s=!0)),s&&this._updateOpacity();var o=this._map._panes;o.markerPane.appendChild(this._icon),this._shadow&&o.shadowPane.appendChild(this._shadow)},_removeIcon:function(){var e=this._map._panes;e.markerPane.removeChild(this._icon),this._shadow&&e.shadowPane.removeChild(this._shadow),this._icon=this._shadow=null},_setPos:function(e){n.DomUtil.setPosition(this._icon,e),this._shadow&&n.DomUtil.setPosition(this._shadow,e),this._icon.style.zIndex=e.y+this.options.zIndexOffset},_animateZoom:function(e){var t=this._map._latLngToNewLayerPoint(this._latlng,e.zoom,e.center);this._setPos(t)},_initInteraction:function(){if(!this.options.clickable)return;var e=this._icon,t=["dblclick","mousedown","mouseover","mouseout"];n.DomUtil.addClass(e,"leaflet-clickable"),n.DomEvent.on(e,"click",this._onMouseClick,this);for(var r=0;r<t.length;r++)n.DomEvent.on(e,t[r],this._fireMouseEvent,this);n.Handler.MarkerDrag&&(this.dragging=new n.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())},_onMouseClick:function(e){n.DomEvent.stopPropagation(e);if(this.dragging&&this.dragging.moved())return;if(this._map.dragging&&this._map.dragging.moved())return;this.fire(e.type,{originalEvent:e})},_fireMouseEvent:function(e){this.fire(e.type,{originalEvent:e}),e.type!=="mousedown"&&n.DomEvent.stopPropagation(e)},setOpacity:function(e){this.options.opacity=e,this._map&&this._updateOpacity()},_updateOpacity:function(){n.DomUtil.setOpacity(this._icon,this.options.opacity),this._shadow&&n.DomUtil.setOpacity(this._shadow,this.options.opacity)}}),n.marker=function(e,t){return new n.Marker(e,t)},n.DivIcon=n.Icon.extend({options:{iconSize:new n.Point(12,12),className:"leaflet-div-icon"},createIcon:function(){var e=document.createElement("div"),t=this.options;return t.html&&(e.innerHTML=t.html),t.bgPos&&(e.style.backgroundPosition=-t.bgPos.x+"px "+ -t.bgPos.y+"px"),this._setIconStyles(e,"icon"),e},createShadow:function(){return null}}),n.divIcon=function(e){return new n.DivIcon(e)},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:""},initialize:function(e,t){n.Util.setOptions(this,e),this._source=t},onAdd:function(e){this._map=e,this._container||this._initLayout(),this._updateContent();var t=e.options.fadeAnimation;t&&n.DomUtil.setOpacity(this._container,0),e._panes.popupPane.appendChild(this._container),e.on("viewreset",this._updatePosition,this),n.Browser.any3d&&e.on("zoomanim",this._zoomAnimation,this),e.options.closePopupOnClick&&e.on("preclick",this._close,this),this._update(),t&&n.DomUtil.setOpacity(this._container,1)},addTo:function(e){return e.addLayer(this),this},openOn:function(e){return e.openPopup(this),this},onRemove:function(e){e._panes.popupPane.removeChild(this._container),n.Util.falseFn(this._container.offsetWidth),e.off({viewreset:this._updatePosition,preclick:this._close,zoomanim:this._zoomAnimation},this),e.options.fadeAnimation&&n.DomUtil.setOpacity(this._container,0),this._map=null},setLatLng:function(e){return this._latlng=n.latLng(e),this._update(),this},setContent:function(e){return this._content=e,this._update(),this},_close:function(){var e=this._map;e&&(e._popup=null,e.removeLayer(this).fire("popupclose",{popup:this}))},_initLayout:function(){var e="leaflet-popup",t=this._container=n.DomUtil.create("div",e+" "+this.options.className+" leaflet-zoom-animated"),r;this.options.closeButton&&(r=this._closeButton=n.DomUtil.create("a",e+"-close-button",t),r.href="#close",r.innerHTML="&#215;",n.DomEvent.on(r,"click",this._onCloseButtonClick,this));var i=this._wrapper=n.DomUtil.create("div",e+"-content-wrapper",t);n.DomEvent.disableClickPropagation(i),this._contentNode=n.DomUtil.create("div",e+"-content",i),n.DomEvent.on(this._contentNode,"mousewheel",n.DomEvent.stopPropagation),this._tipContainer=n.DomUtil.create("div",e+"-tip-container",t),this._tip=n.DomUtil.create("div",e+"-tip",this._tipContainer)},_update:function(){if(!this._map)return;this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan()},_updateContent:function(){if(!this._content)return;if(typeof this._content=="string")this._contentNode.innerHTML=this._content;else{while(this._contentNode.hasChildNodes())this._contentNode.removeChild(this._contentNode.firstChild);this._contentNode.appendChild(this._content)}this.fire("contentupdate")},_updateLayout:function(){var e=this._contentNode,t=e.style;t.width="",t.whiteSpace="nowrap";var r=e.offsetWidth;r=Math.min(r,this.options.maxWidth),r=Math.max(r,this.options.minWidth),t.width=r+1+"px",t.whiteSpace="",t.height="";var i=e.offsetHeight,s=this.options.maxHeight,o="leaflet-popup-scrolled";s&&i>s?(t.height=s+"px",n.DomUtil.addClass(e,o)):n.DomUtil.removeClass(e,o),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){var e=this._map.latLngToLayerPoint(this._latlng),t=n.Browser.any3d,r=this.options.offset;t&&n.DomUtil.setPosition(this._container,e),this._containerBottom=-r.y-(t?0:e.y),this._containerLeft=-Math.round(this._containerWidth/2)+r.x+(t?0:e.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"},_zoomAnimation:function(e){var t=this._map._latLngToNewLayerPoint(this._latlng,e.zoom,e.center);n.DomUtil.setPosition(this._container,t)},_adjustPan:function(){if(!this.options.autoPan)return;var e=this._map,t=this._container.offsetHeight,r=this._containerWidth,i=new n.Point(this._containerLeft,-t-this._containerBottom);n.Browser.any3d&&i._add(n.DomUtil.getPosition(this._container));var s=e.layerPointToContainerPoint(i),o=this.options.autoPanPadding,u=e.getSize(),a=0,f=0;s.x<0&&(a=s.x-o.x),s.x+r>u.x&&(a=s.x+r-u.x+o.x),s.y<0&&(f=s.y-o.y),s.y+t>u.y&&(f=s.y+t-u.y+o.y),(a||f)&&e.panBy(new n.Point(a,f))},_onCloseButtonClick:function(e){this._close(),n.DomEvent.stop(e)}}),n.popup=function(e,t){return new n.Popup(e,t)},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(e,t){var r=n.point(this.options.icon.options.popupAnchor)||new n.Point(0,0);return r=r.add(n.Popup.prototype.options.offset),t&&t.offset&&(r=r.add(t.offset)),t=n.Util.extend({offset:r},t),this._popup||this.on("click",this.openPopup,this),this._popup=(new n.Popup(t,this)).setContent(e),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.openPopup)),this}}),n.Map.include({openPopup:function(e){return this.closePopup(),this._popup=e,this.addLayer(e).fire("popupopen",{popup:this._popup})},closePopup:function(){return this._popup&&this._popup._close(),this}}),n.LayerGroup=n.Class.extend({initialize:function(e){this._layers={};var t,n;if(e)for(t=0,n=e.length;t<n;t++)this.addLayer(e[t])},addLayer:function(e){var t=n.Util.stamp(e);return this._layers[t]=e,this._map&&this._map.addLayer(e),this},removeLayer:function(e){var t=n.Util.stamp(e);return delete this._layers[t],this._map&&this._map.removeLayer(e),this},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(e){var t=Array.prototype.slice.call(arguments,1),n,r;for(n in this._layers)this._layers.hasOwnProperty(n)&&(r=this._layers[n],r[e]&&r[e].apply(r,t));return this},onAdd:function(e){this._map=e,this.eachLayer(e.addLayer,e)},onRemove:function(e){this.eachLayer(e.removeLayer,e),this._map=null},addTo:function(e){return e.addLayer(this),this},eachLayer:function(e,t){for(var n in this._layers)this._layers.hasOwnProperty(n)&&e.call(t,this._layers[n])}}),n.layerGroup=function(e){return new n.LayerGroup(e)},n.FeatureGroup=n.LayerGroup.extend({includes:n.Mixin.Events,addLayer:function(e){return this._layers[n.Util.stamp(e)]?this:(e.on("click dblclick mouseover mouseout mousemove contextmenu",this._propagateEvent,this),n.LayerGroup.prototype.addLayer.call(this,e),this._popupContent&&e.bindPopup&&e.bindPopup(this._popupContent),this)},removeLayer:function(e){return e.off("click dblclick mouseover mouseout mousemove contextmenu",this._propagateEvent,this),n.LayerGroup.prototype.removeLayer.call(this,e),this.invoke("unbindPopup")},bindPopup:function(e){return this._popupContent=e,this.invoke("bindPopup",e)},setStyle:function(e){return this.invoke("setStyle",e)},getBounds:function(){var e=new n.LatLngBounds;return this.eachLayer(function(t){e.extend(t instanceof n.Marker?t.getLatLng():t.getBounds())},this),e},_propagateEvent:function(e){e.layer=e.target,e.target=this,this.fire(e.type,e)}}),n.featureGroup=function(e){return new n.FeatureGroup(e)},n.Path=n.Class.extend({includes:[n.Mixin.Events],statics:{CLIP_PADDING:n.Browser.mobile?Math.max(0,Math.min(.5,(1280/Math.max(e.innerWidth,e.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(e){n.Util.setOptions(this,e)},onAdd:function(e){this._map=e,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),e.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(e){return e.addLayer(this),this},onRemove:function(e){e._pathRoot.removeChild(this._container),this._map=null,n.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),e.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(e){return n.Util.setOptions(this,e),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),n.Map.include({_updatePathViewport:function(){var e=n.Path.CLIP_PADDING,t=this.getSize(),r=n.DomUtil.getPosition(this._mapPane),i=r.multiplyBy(-1)._subtract(t.multiplyBy(e)),s=i.add(t.multiplyBy(1+e*2));this._pathViewport=new n.Bounds(i,s)}}),n.Path.SVG_NS="http://www.w3.org/2000/svg",n.Browser.svg=!!document.createElementNS&&!!document.createElementNS(n.Path.SVG_NS,"svg").createSVGRect,n.Path=n.Path.extend({statics:{SVG:n.Browser.svg},bringToFront:function(){this._container&&this._map._pathRoot.appendChild(this._container)},bringToBack:function(){if(this._container){var e=this._map._pathRoot;e.insertBefore(this._container,e.firstChild)}},getPathString:function(){},_createElement:function(e){return document.createElementNS(n.Path.SVG_NS,e)},_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 e=this.getPathString();e||(e="M0 0"),this._path.setAttribute("d",e)},_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);var e=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"];for(var t=0;t<e.length;t++)n.DomEvent.on(this._container,e[t],this._fireMouseEvent,this)}},_onMouseClick:function(e){if(this._map.dragging&&this._map.dragging.moved())return;this._fireMouseEvent(e),n.DomEvent.stopPropagation(e)},_fireMouseEvent:function(e){if(!this.hasEventListeners(e.type))return;e.type==="contextmenu"&&n.DomEvent.preventDefault(e);var t=this._map,r=t.mouseEventToContainerPoint(e),i=t.containerPointToLayerPoint(r),s=t.layerPointToLatLng(i);this.fire(e.type,{latlng:s,layerPoint:i,containerPoint:r,originalEvent:e})}}),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(e){var t=this.getZoomScale(e.zoom),r=this._getCenterOffset(e.center).divideBy(1-1/t),i=this.containerPointToLayerPoint(this.getSize().multiplyBy(-n.Path.CLIP_PADDING)),s=i.add(r).round();this._pathRoot.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(s.multiplyBy(-1).add(n.DomUtil.getPosition(this._pathRoot)).multiplyBy(t).add(s))+" scale("+t+") ",this._pathZooming=!0},_endPathZoom:function(){this._pathZooming=!1},_updateSvgViewport:function(){if(this._pathZooming)return;this._updatePathViewport();var e=this._pathViewport,t=e.min,r=e.max,i=r.x-t.x,s=r.y-t.y,o=this._pathRoot,u=this._panes.overlayPane;n.Browser.mobileWebkit&&u.removeChild(o),n.DomUtil.setPosition(o,t),o.setAttribute("width",i),o.setAttribute("height",s),o.setAttribute("viewBox",[t.x,t.y,i,s].join(" ")),n.Browser.mobileWebkit&&u.appendChild(o)}}),n.Path.include({bindPopup:function(e,t){if(!this._popup||this._popup.options!==t)this._popup=new n.Popup(t,this);return this._popup.setContent(e),this._openPopupAdded||(this.on("click",this._openPopup,this),this._openPopupAdded=!0),this},openPopup:function(e){return this._popup&&(e=e||this._latlng||this._latlngs[Math.floor(this._latlngs.length/2)],this._openPopup({latlng:e})),this},_openPopup:function(e){this._popup.setLatLng(e.latlng),this._map.openPopup(this._popup)}}),n.Browser.vml=function(){try{var e=document.createElement("div");e.innerHTML='<v:shape adj="1"/>';var t=e.firstChild;return t.style.behavior="url(#default#VML)",t&&typeof t.adj=="object"}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 document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(e){return document.createElement("<lvml:"+e+' class="lvml">')}}catch(e){return function(e){return document.createElement("<"+e+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var e=this._container=this._createElement("shape");n.DomUtil.addClass(e,"leaflet-vml-shape"),this.options.clickable&&n.DomUtil.addClass(e,"leaflet-clickable"),e.coordsize="1 1",this._path=this._createElement("path"),e.appendChild(this._path),this._map._pathRoot.appendChild(e)},_initStyle:function(){this._updateStyle()},_updateStyle:function(){var e=this._stroke,t=this._fill,n=this.options,r=this._container;r.stroked=n.stroke,r.filled=n.fill,n.stroke?(e||(e=this._stroke=this._createElement("stroke"),e.endcap="round",r.appendChild(e)),e.weight=n.weight+"px",e.color=n.color,e.opacity=n.opacity,n.dashArray?e.dashStyle=n.dashArray.replace(/ *, */g," "):e.dashStyle=""):e&&(r.removeChild(e),this._stroke=null),n.fill?(t||(t=this._fill=this._createElement("fill"),r.appendChild(t)),t.color=n.fillColor||n.color,t.opacity=n.fillOpacity):t&&(r.removeChild(t),this._fill=null)},_updatePath:function(){var e=this._container.style;e.display="none",this._path.v=this.getPathString()+" ",e.display=""}}),n.Map.include(n.Browser.svg||!n.Browser.vml?{}:{_initPathRoot:function(){if(this._pathRoot)return;var e=this._pathRoot=document.createElement("div");e.className="leaflet-vml-container",this._panes.overlayPane.appendChild(e),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}),n.Browser.canvas=function(){return!!document.createElement("canvas").getContext}(),n.Path=n.Path.SVG&&!e.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(e){return n.Util.setOptions(this,e),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(e){e.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this._requestUpdate(),this._map=null},_requestUpdate:function(){this._map&&(n.Util.cancelAnimFrame(this._fireMapMoveEnd),this._updateRequest=n.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var e=this.options;e.stroke&&(this._ctx.lineWidth=e.weight,this._ctx.strokeStyle=e.color),e.fill&&(this._ctx.fillStyle=e.fillColor||e.color)},_drawPath:function(){var e,t,r,i,s,o;this._ctx.beginPath();for(e=0,r=this._parts.length;e<r;e++){for(t=0,i=this._parts[e].length;t<i;t++)s=this._parts[e][t],o=(t===0?"move":"line")+"To",this._ctx[o](s.x,s.y);this instanceof n.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(this._checkIfEmpty())return;var e=this._ctx,t=this.options;this._drawPath(),e.save(),this._updateStyle(),t.fill&&(t.fillOpacity<1&&(e.globalAlpha=t.fillOpacity),e.fill()),t.stroke&&(t.opacity<1&&(e.globalAlpha=t.opacity),e.stroke()),e.restore()},_initEvents:function(){this.options.clickable&&this._map.on("click",this._onClick,this)},_onClick:function(e){this._containsPoint(e.layerPoint)&&this.fire("click",e)}}),n.Map.include(n.Path.SVG&&!e.L_PREFER_CANVAS||!n.Browser.canvas?{}:{_initPathRoot:function(){var e=this._pathRoot,t;e||(e=this._pathRoot=document.createElement("canvas"),e.style.position="absolute",t=this._canvasCtx=e.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(e),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)return;this._updatePathViewport();var e=this._pathViewport,t=e.min,r=e.max.subtract(t),i=this._pathRoot;n.DomUtil.setPosition(i,t),i.width=r.x,i.height=r.y,i.getContext("2d").translate(-t.x,-t.y)}}),n.LineUtil={simplify:function(e,t){if(!t||!e.length)return e.slice();var n=t*t;return e=this._reducePoints(e,n),e=this._simplifyDP(e,n),e},pointToSegmentDistance:function(e,t,n){return Math.sqrt(this._sqClosestPointOnSegment(e,t,n,!0))},closestPointOnSegment:function(e,t,n){return this._sqClosestPointOnSegment(e,t,n)},_simplifyDP:function(e,n){var r=e.length,i=typeof Uint8Array!=t+""?Uint8Array:Array,s=new i(r);s[0]=s[r-1]=1,this._simplifyDPStep(e,s,n,0,r-1);var o,u=[];for(o=0;o<r;o++)s[o]&&u.push(e[o]);return u},_simplifyDPStep:function(e,t,n,r,i){var s=0,o,u,a;for(u=r+1;u<=i-1;u++)a=this._sqClosestPointOnSegment(e[u],e[r],e[i],!0),a>s&&(o=u,s=a);s>n&&(t[o]=1,this._simplifyDPStep(e,t,n,r,o),this._simplifyDPStep(e,t,n,o,i))},_reducePoints:function(e,t){var n=[e[0]];for(var r=1,i=0,s=e.length;r<s;r++)this._sqDist(e[r],e[i])>t&&(n.push(e[r]),i=r);return i<s-1&&n.push(e[s-1]),n},clipSegment:function(e,t,n,r){var i=n.min,s=n.max,o=r?this._lastCode:this._getBitCode(e,n),u=this._getBitCode(t,n);this._lastCode=u;for(;;){if(!(o|u))return[e,t];if(o&u)return!1;var a=o||u,f=this._getEdgeIntersection(e,t,a,n),l=this._getBitCode(f,n);a===o?(e=f,o=l):(t=f,u=l)}},_getEdgeIntersection:function(e,t,r,i){var s=t.x-e.x,o=t.y-e.y,u=i.min,a=i.max;if(r&8)return new n.Point(e.x+s*(a.y-e.y)/o,a.y);if(r&4)return new n.Point(e.x+s*(u.y-e.y)/o,u.y);if(r&2)return new n.Point(a.x,e.y+o*(a.x-e.x)/s);if(r&1)return new n.Point(u.x,e.y+o*(u.x-e.x)/s)},_getBitCode:function(e,t){var n=0;return e.x<t.min.x?n|=1:e.x>t.max.x&&(n|=2),e.y<t.min.y?n|=4:e.y>t.max.y&&(n|=8),n},_sqDist:function(e,t){var n=t.x-e.x,r=t.y-e.y;return n*n+r*r},_sqClosestPointOnSegment:function(e,t,r,i){var s=t.x,o=t.y,u=r.x-s,a=r.y-o,f=u*u+a*a,l;return f>0&&(l=((e.x-s)*u+(e.y-o)*a)/f,l>1?(s=r.x,o=r.y):l>0&&(s+=u*l,o+=a*l)),u=e.x-s,a=e.y-o,i?u*u+a*a:new n.Point(s,o)}},n.Polyline=n.Path.extend({initialize:function(e,t){n.Path.prototype.initialize.call(this,t),this._latlngs=this._convertLatLngs(e),n.Handler.PolyEdit&&(this.editing=new n.Handler.PolyEdit(this),this.options.editable&&this.editing.enable())},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var e=0,t=this._latlngs.length;e<t;e++)this._originalPoints[e]=this._map.latLngToLayerPoint(this._latlngs[e])},getPathString:function(){for(var e=0,t=this._parts.length,n="";e<t;e++)n+=this._getPathPartStr(this._parts[e]);return n},getLatLngs:function(){return this._latlngs},setLatLngs:function(e){return this._latlngs=this._convertLatLngs(e),this.redraw()},addLatLng:function(e){return this._latlngs.push(n.latLng(e)),this.redraw()},spliceLatLngs:function(e,t){var n=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs),this.redraw(),n},closestLayerPoint:function(e){var t=Infinity,r=this._parts,i,s,o=null;for(var u=0,a=r.length;u<a;u++){var f=r[u];for(var l=1,c=f.length;l<c;l++){i=f[l-1],s=f[l];var h=n.LineUtil._sqClosestPointOnSegment(e,i,s,!0);h<t&&(t=h,o=n.LineUtil._sqClosestPointOnSegment(e,i,s))}}return o&&(o.distance=Math.sqrt(t)),o},getBounds:function(){var e=new n.LatLngBounds,t=this.getLatLngs();for(var r=0,i=t.length;r<i;r++)e.extend(t[r]);return e},onAdd:function(e){n.Path.prototype.onAdd.call(this,e),this.editing&&this.editing.enabled()&&this.editing.addHooks()},onRemove:function(e){this.editing&&this.editing.enabled()&&this.editing.removeHooks(),n.Path.prototype.onRemove.call(this,e)},_convertLatLngs:function(e){var t,r;for(t=0,r=e.length;t<r;t++){if(e[t]instanceof Array&&typeof e[t][0]!="number")return;e[t]=n.latLng(e[t])}return e},_initEvents:function(){n.Path.prototype._initEvents.call(this)},_getPathPartStr:function(e){var t=n.Path.VML;for(var r=0,i=e.length,s="",o;r<i;r++)o=e[r],t&&o._round(),s+=(r?"L":"M")+o.x+" "+o.y;return s},_clipPoints:function(){var e=this._originalPoints,t=e.length,r,i,s;if(this.options.noClip){this._parts=[e];return}this._parts=[];var o=this._parts,u=this._map._pathViewport,a=n.LineUtil;for(r=0,i=0;r<t-1;r++){s=a.clipSegment(e[r],e[r+1],u,r);if(!s)continue;o[i]=o[i]||[],o[i].push(s[0]);if(s[1]!==e[r+1]||r===t-2)o[i].push(s[1]),i++}},_simplifyPoints:function(){var e=this._parts,t=n.LineUtil;for(var r=0,i=e.length;r<i;r++)e[r]=t.simplify(e[r],this.options.smoothFactor)},_updatePath:function(){if(!this._map)return;this._clipPoints(),this._simplifyPoints(),n.Path.prototype._updatePath.call(this)}}),n.polyline=function(e,t){return new n.Polyline(e,t)},n.PolyUtil={},n.PolyUtil.clipPolygon=function(e,t){var r=t.min,i=t.max,s,o=[1,4,2,8],u,a,f,l,c,h,p,d,v=n.LineUtil;for(u=0,h=e.length;u<h;u++)e[u]._code=v._getBitCode(e[u],t);for(f=0;f<4;f++){p=o[f],s=[];for(u=0,h=e.length,a=h-1;u<h;a=u++)l=e[u],c=e[a],l._code&p?c._code&p||(d=v._getEdgeIntersection(c,l,p,t),d._code=v._getBitCode(d,t),s.push(d)):(c._code&p&&(d=v._getEdgeIntersection(c,l,p,t),d._code=v._getBitCode(d,t),s.push(d)),s.push(l));e=s}return e},n.Polygon=n.Polyline.extend({options:{fill:!0},initialize:function(e,t){n.Polyline.prototype.initialize.call(this,e,t),e&&e[0]instanceof Array&&typeof e[0][0]!="number"&&(this._latlngs=this._convertLatLngs(e[0]),this._holes=e.slice(1))},projectLatlngs:function(){n.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[];if(!this._holes)return;for(var e=0,t=this._holes.length,r;e<t;e++){this._holePoints[e]=[];for(var i=0,s=this._holes[e].length;i<s;i++)this._holePoints[e][i]=this._map.latLngToLayerPoint(this._holes[e][i])}},_clipPoints:function(){var e=this._originalPoints,t=[];this._parts=[e].concat(this._holePoints);if(this.options.noClip)return;for(var r=0,i=this._parts.length;r<i;r++){var s=n.PolyUtil.clipPolygon(this._parts[r],this._map._pathViewport);if(!s.length)continue;t.push(s)}this._parts=t},_getPathPartStr:function(e){var t=n.Polyline.prototype._getPathPartStr.call(this,e);return t+(n.Browser.svg?"z":"x")}}),n.polygon=function(e,t){return new n.Polygon(e,t)},function(){function e(e){return n.FeatureGroup.extend({initialize:function(e,t){this._layers={},this._options=t,this.setLatLngs(e)},setLatLngs:function(t){var n=0,r=t.length;this.eachLayer(function(e){n<r?e.setLatLngs(t[n++]):this.removeLayer(e)},this);while(n<r)this.addLayer(new e(t[n++],this._options));return this}})}n.MultiPolyline=e(n.Polyline),n.MultiPolygon=e(n.Polygon),n.multiPolyline=function(e,t){return new n.MultiPolyline(e,t)},n.multiPolygon=function(e,t){return new n.MultiPolygon(e,t)}}(),n.Rectangle=n.Polygon.extend({initialize:function(e,t){n.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(e),t)},setBounds:function(e){this.setLatLngs(this._boundsToLatLngs(e))},_boundsToLatLngs:function(e){return e=n.latLngBounds(e),[e.getSouthWest(),e.getNorthWest(),e.getNorthEast(),e.getSouthEast(),e.getSouthWest()]}}),n.rectangle=function(e,t){return new n.Rectangle(e,t)},n.Circle=n.Path.extend({initialize:function(e,t,r){n.Path.prototype.initialize.call(this,r),this._latlng=n.latLng(e),this._mRadius=t},options:{fill:!0},setLatLng:function(e){return this._latlng=n.latLng(e),this.redraw()},setRadius:function(e){return this._mRadius=e,this.redraw()},projectLatlngs:function(){var e=this._getLngRadius(),t=new n.LatLng(this._latlng.lat,this._latlng.lng-e,!0),r=this._map.latLngToLayerPoint(t);this._point=this._map.latLngToLayerPoint(this._latlng),this._radius=Math.max(Math.round(this._point.x-r.x),1)},getBounds:function(){var e=this._map,t=this._radius*Math.cos(Math.PI/4),r=e.project(this._latlng),i=new n.Point(r.x-t,r.y+t),s=new n.Point(r.x+t,r.y-t),o=e.unproject(i),u=e.unproject(s);return new n.LatLngBounds(o,u)},getLatLng:function(){return this._latlng},getPathString:function(){var e=this._point,t=this._radius;return this._checkIfEmpty()?"":n.Browser.svg?"M"+e.x+","+(e.y-t)+"A"+t+","+t+",0,1,1,"+(e.x-.1)+","+(e.y-t)+" z":(e._round(),t=Math.round(t),"AL "+e.x+","+e.y+" "+t+","+t+" 0,"+23592600)},getRadius:function(){return this._mRadius},_getLngRadius:function(){var e=40075017,t=e*Math.cos(n.LatLng.DEG_TO_RAD*this._latlng.lat);return this._mRadius/t*360},_checkIfEmpty:function(){if(!this._map)return!1;var e=this._map._pathViewport,t=this._radius,n=this._point;return n.x-t>e.max.x||n.y-t>e.max.y||n.x+t<e.min.x||n.y+t<e.min.y}}),n.circle=function(e,t,r){return new n.Circle(e,t,r)},n.CircleMarker=n.Circle.extend({options:{radius:10,weight:2},initialize:function(e,t){n.Circle.prototype.initialize.call(this,e,null,t),this._radius=this.options.radius},projectLatlngs:function(){this._point=this._map.latLngToLayerPoint(this._latlng)},setRadius:function(e){return this._radius=e,this.redraw()}}),n.circleMarker=function(e,t){return new n.CircleMarker(e,t)},n.Polyline.include(n.Path.CANVAS?{_containsPoint:function(e,t){var r,i,s,o,u,a,f,l=this.options.weight/2;n.Browser.touch&&(l+=10);for(r=0,o=this._parts.length;r<o;r++){f=this._parts[r];for(i=0,u=f.length,s=u-1;i<u;s=i++){if(!t&&i===0)continue;a=n.LineUtil.pointToSegmentDistance(e,f[s],f[i]);if(a<=l)return!0}}return!1}}:{}),n.Polygon.include(n.Path.CANVAS?{_containsPoint:function(e){var t=!1,r,i,s,o,u,a,f,l;if(n.Polyline.prototype._containsPoint.call(this,e,!0))return!0;for(o=0,f=this._parts.length;o<f;o++){r=this._parts[o];for(u=0,l=r.length,a=l-1;u<l;a=u++)i=r[u],s=r[a],i.y>e.y!=s.y>e.y&&e.x<(s.x-i.x)*(e.y-i.y)/(s.y-i.y)+i.x&&(t=!t)}return t}}:{}),n.Circle.include(n.Path.CANVAS?{_drawPath:function(){var e=this._point;this._ctx.beginPath(),this._ctx.arc(e.x,e.y,this._radius,0,Math.PI*2,!1)},_containsPoint:function(e){var t=this._point,n=this.options.stroke?this.options.weight/2:0;return e.distanceTo(t)<=this._radius+n}}:{}),n.GeoJSON=n.FeatureGroup.extend({initialize:function(e,t){n.Util.setOptions(this,t),this._layers={},e&&this.addData(e)},addData:function(e){var t=e instanceof Array?e:e.features,r,i;if(t){for(r=0,i=t.length;r<i;r++)this.addData(t[r]);return this}var s=this.options,o=s.style;if(s.filter&&!s.filter(e))return;var u=n.GeoJSON.geometryToLayer(e,s.pointToLayer);return o&&(typeof o=="function"&&(o=o(e)),u.setStyle&&u.setStyle(o)),s.onEachFeature&&s.onEachFeature(e,u),this.addLayer(u)}}),n.Util.extend(n.GeoJSON,{geometryToLayer:function(e,t){var r=e.type==="Feature"?e.geometry:e,i=r.coordinates,s=[],o,u,a,f,l;switch(r.type){case"Point":return o=this.coordsToLatLng(i),t?t(e,o):new n.Marker(o);case"MultiPoint":for(a=0,f=i.length;a<f;a++)o=this.coordsToLatLng(i[a]),l=t?t(e,o):new n.Marker(o),s.push(l);return new n.FeatureGroup(s);case"LineString":return u=this.coordsToLatLngs(i),new n.Polyline(u);case"Polygon":return u=this.coordsToLatLngs(i,1),new n.Polygon(u);case"MultiLineString":return u=this.coordsToLatLngs(i,1),new n.MultiPolyline(u);case"MultiPolygon":return u=this.coordsToLatLngs(i,2),new n.MultiPolygon(u);case"GeometryCollection":for(a=0,f=r.geometries.length;a<f;a++)l=this.geometryToLayer(r.geometries[a],t),s.push(l);return new n.FeatureGroup(s);default:throw Error("Invalid GeoJSON object.")}},coordsToLatLng:function(e,t){var r=parseFloat(e[t?0:1]),i=parseFloat(e[t?1:0]);return new n.LatLng(r,i,!0)},coordsToLatLngs:function(e,t,n){var r,i=[],s,o;for(s=0,o=e.length;s<o;s++)r=t?this.coordsToLatLngs(e[s],t-1,n):this.coordsToLatLng(e[s],n),i.push(r);return i}}),n.geoJson=function(e,t){return new n.GeoJSON(e,t)},n.DomEvent={addListener:function(e,t,r,i){var s=n.Util.stamp(r),o="_leaflet_"+t+s,u,a,f;return e[o]?this:(u=function(t){return r.call(i||e,t||n.DomEvent._getEvent())},n.Browser.touch&&t==="dblclick"&&this.addDoubleTapListener?this.addDoubleTapListener(e,u,s):("addEventListener"in e?t==="mousewheel"?(e.addEventListener("DOMMouseScroll",u,!1),e.addEventListener(t,u,!1)):t==="mouseenter"||t==="mouseleave"?(a=u,f=t==="mouseenter"?"mouseover":"mouseout",u=function(t){if(!n.DomEvent._checkMouse(e,t))return;return a(t)},e.addEventListener(f,u,!1)):e.addEventListener(t,u,!1):"attachEvent"in e&&e.attachEvent("on"+t,u),e[o]=u,this))},removeListener:function(e,t,r){var i=n.Util.stamp(r),s="_leaflet_"+t+i,o=e[s];if(!o)return;return n.Browser.touch&&t==="dblclick"&&this.removeDoubleTapListener?this.removeDoubleTapListener(e,i):"removeEventListener"in e?t==="mousewheel"?(e.removeEventListener("DOMMouseScroll",o,!1),e.removeEventListener(t,o,!1)):t==="mouseenter"||t==="mouseleave"?e.removeEventListener(t==="mouseenter"?"mouseover":"mouseout",o,!1):e.removeEventListener(t,o,!1):"detachEvent"in e&&e.detachEvent("on"+t,o),e[s]=null,this},stopPropagation:function(e){return e.stopPropagation?e.stopPropagation():e.cancelBubble=!0,this},disableClickPropagation:function(e){var t=n.DomEvent.stopPropagation;return n.DomEvent.addListener(e,n.Draggable.START,t).addListener(e,"click",t).addListener(e,"dblclick",t)},preventDefault:function(e){return e.preventDefault?e.preventDefault():e.returnValue=!1,this},stop:function(e){return n.DomEvent.preventDefault(e).stopPropagation(e)},getMousePosition:function(e,t){var r=document.body,i=document.documentElement,s=e.pageX?e.pageX:e.clientX+r.scrollLeft+i.scrollLeft,o=e.pageY?e.pageY:e.clientY+r.scrollTop+i.scrollTop,u=new n.Point(s,o);return t?u._subtract(n.DomUtil.getViewportOffset(t)):u},getWheelDelta:function(e){var t=0;return e.wheelDelta&&(t=e.wheelDelta/120),e.detail&&(t=-e.detail/3),t},_checkMouse:function(e,t){var n=t.relatedTarget;if(!n)return!0;try{while(n&&n!==e)n=n.parentNode}catch(r){return!1}return n!==e},_getEvent:function(){var t=e.event;if(!t){var n=arguments.callee.caller;while(n){t=n.arguments[0];if(t&&e.Event===t.constructor)break;n=n.caller}}return t}},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",END:n.Browser.touch?"touchend":"mouseup",MOVE:n.Browser.touch?"touchmove":"mousemove",TAP_TOLERANCE:15},initialize:function(e,t){this._element=e,this._dragStartTarget=t||e},enable:function(){if(this._enabled)return;n.DomEvent.on(this._dragStartTarget,n.Draggable.START,this._onDown,this),this._enabled=!0},disable:function(){if(!this._enabled)return;n.DomEvent.off(this._dragStartTarget,n.Draggable.START,this._onDown),this._enabled=!1,this._moved=!1},_onDown:function(e){if(!n.Browser.touch&&e.shiftKey||e.which!==1&&e.button!==1&&!e.touches)return;this._simulateClick=!0;if(e.touches&&e.touches.length>1){this._simulateClick=!1;return}var t=e.touches&&e.touches.length===1?e.touches[0]:e,r=t.target;n.DomEvent.preventDefault(e),n.Browser.touch&&r.tagName.toLowerCase()==="a"&&n.DomUtil.addClass(r,"leaflet-active"),this._moved=!1;if(this._moving)return;n.Browser.touch||(n.DomUtil.disableTextSelection(),this._setMovingCursor()),this._startPos=this._newPos=n.DomUtil.getPosition(this._element),this._startPoint=new n.Point(t.clientX,t.clientY),n.DomEvent.on(document,n.Draggable.MOVE,this._onMove,this),n.DomEvent.on(document,n.Draggable.END,this._onUp,this)},_onMove:function(e){if(e.touches&&e.touches.length>1)return;var t=e.touches&&e.touches.length===1?e.touches[0]:e,r=new n.Point(t.clientX,t.clientY),i=r.subtract(this._startPoint);if(!i.x&&!i.y)return;n.DomEvent.preventDefault(e),this._moved||(this.fire("dragstart"),this._moved=!0),this._newPos=this._startPos.add(i),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(e){if(this._simulateClick&&e.changedTouches){var t=e.changedTouches[0],r=t.target,i=this._newPos&&this._newPos.distanceTo(this._startPos)||0;r.tagName.toLowerCase()==="a"&&n.DomUtil.removeClass(r,"leaflet-active"),i<n.Draggable.TAP_TOLERANCE&&this._simulateEvent("click",t)}n.Browser.touch||(n.DomUtil.enableTextSelection(),this._restoreCursor()),n.DomEvent.off(document,n.Draggable.MOVE,this._onMove),n.DomEvent.off(document,n.Draggable.END,this._onUp),this._moved&&(n.Util.cancelAnimFrame(this._animRequest),this.fire("dragend")),this._moving=!1},_setMovingCursor:function(){n.DomUtil.addClass(document.body,"leaflet-dragging")},_restoreCursor:function(){n.DomUtil.removeClass(document.body,"leaflet-dragging")},_simulateEvent:function(t,n){var r=document.createEvent("MouseEvents");r.initMouseEvent(t,!0,!0,e,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(r)}}),n.Handler=n.Class.extend({initialize:function(e){this._map=e},enable:function(){if(this._enabled)return;this._enabled=!0,this.addHooks()},disable:function(){if(!this._enabled)return;this._enabled=!1,this.removeHooks()},enabled:function(){return!!this._enabled}}),n.Map.mergeOptions({dragging:!0,inertia:!n.Browser.android23,inertiaDeceleration:3e3,inertiaMaxSpeed:1500,inertiaThreshold:n.Browser.touch?32:14,worldCopyJump:!0}),n.Map.Drag=n.Handler.extend({addHooks:function(){if(!this._draggable){this._draggable=new n.Draggable(this._map._mapPane,this._map._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this);var e=this._map.options;e.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),this._map.on("viewreset",this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var e=this._map;e.fire("movestart").fire("dragstart"),e._panTransition&&e._panTransition._onTransitionEnd(!0),e.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var e=this._lastTime=+(new Date),t=this._lastPos=this._draggable._newPos;this._positions.push(t),this._times.push(e),e-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var e=this._map.getSize().divideBy(2),t=this._map.latLngToLayerPoint(new n.LatLng(0,0));this._initialWorldOffset=t.subtract(e).x,this._worldWidth=this._map.project(new n.LatLng(0,180)).x},_onPreDrag:function(){var e=this._map,t=this._worldWidth,n=Math.round(t/2),r=this._initialWorldOffset,i=this._draggable._newPos.x,s=(i-n+r)%t+n-r,o=(i+n+r)%t-n-r,u=Math.abs(s+r)<Math.abs(o+r)?s:o;this._draggable._newPos.x=u},_onDragEnd:function(){var e=this._map,r=e.options,i=+(new Date)-this._lastTime,s=!r.inertia||i>r.inertiaThreshold||this._positions[0]===t;if(s)e.fire("moveend");else{var o=this._lastPos.subtract(this._positions[0]),u=(this._lastTime+i-this._times[0])/1e3,a=o.multiplyBy(.58/u),f=a.distanceTo(new n.Point(0,0)),l=Math.min(r.inertiaMaxSpeed,f),c=a.multiplyBy(l/f),h=l/r.inertiaDeceleration,p=c.multiplyBy(-h/2).round(),d={duration:h,easing:"ease-out"};n.Util.requestAnimFrame(n.Util.bind(function(){this._map.panBy(p,d)},this))}e.fire("dragend"),r.maxBounds&&n.Util.requestAnimFrame(this._panInsideMaxBounds,e,!0,e._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(e){this.setView(e.latlng,this._zoom+1)}}),n.Map.addInitHook("addHandler","doubleClickZoom",n.Map.DoubleClickZoom),n.Map.mergeOptions({scrollWheelZoom:!n.Browser.touch}),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(e){var t=n.DomEvent.getWheelDelta(e);this._delta+=t,this._lastMousePos=this._map.mouseEventToContainerPoint(e),clearTimeout(this._timer),this._timer=setTimeout(n.Util.bind(this._performZoom,this),40),n.DomEvent.preventDefault(e)},_performZoom:function(){var e=this._map,t=Math.round(this._delta),n=e.getZoom();t=Math.max(Math.min(t,4),-4),t=e._limitZoom(n+t)-n,this._delta=0;if(!t)return;var r=n+t,i=this._getCenterForScrollWheelZoom(this._lastMousePos,r);e.setView(i,r)},_getCenterForScrollWheelZoom:function(e,t){var n=this._map,r=n.getZoomScale(t),i=n.getSize().divideBy(2),s=e.subtract(i).multiplyBy(1-1/r),o=n._getTopLeftPoint().add(i).add(s);return n.unproject(o)}}),n.Map.addInitHook("addHandler","scrollWheelZoom",n.Map.ScrollWheelZoom),n.Util.extend(n.DomEvent,{addDoubleTapListener:function(e,t,n){function l(e){if(e.touches.length!==1)return;var t=Date.now(),n=t-(r||t);o=e.touches[0],i=n>0&&n<=s,r=t}function c(e){i&&(o.type="dblclick",t(o),r=null)}var r,i=!1,s=250,o,u="_leaflet_",a="touchstart",f="touchend";return e[u+a+n]=l,e[u+f+n]=c,e.addEventListener(a,l,!1),e.addEventListener(f,c,!1),this},removeDoubleTapListener:function(e,t){var n="_leaflet_";return e.removeEventListener(e,e[n+"touchstart"+t],!1),e.removeEventListener(e,e[n+"touchend"+t],!1),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(e){var t=this._map;if(!e.touches||e.touches.length!==2||t._animatingZoom||this._zooming)return;var r=t.mouseEventToLayerPoint(e.touches[0]),i=t.mouseEventToLayerPoint(e.touches[1]),s=t._getCenterLayerPoint();this._startCenter=r.add(i).divideBy(2,!0),this._startDist=r.distanceTo(i),this._moved=!1,this._zooming=!0,this._centerOffset=s.subtract(this._startCenter),n.DomEvent.on(document,"touchmove",this._onTouchMove,this).on(document,"touchend",this._onTouchEnd,this),n.DomEvent.preventDefault(e)},_onTouchMove:function(e){if(!e.touches||e.touches.length!==2)return;var t=this._map,r=t.mouseEventToLayerPoint(e.touches[0]),i=t.mouseEventToLayerPoint(e.touches[1]);this._scale=r.distanceTo(i)/this._startDist,this._delta=r.add(i).divideBy(2,!0).subtract(this._startCenter);if(this._scale===1)return;this._moved||(n.DomUtil.addClass(t._mapPane,"leaflet-zoom-anim leaflet-touching"),t.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(e)},_updateOnMove:function(){var e=this._map,t=this._getScaleOrigin(),r=e.layerPointToLatLng(t);e.fire("zoomanim",{center:r,zoom:e.getScaleZoom(this._scale)}),e._tileBg.style[n.DomUtil.TRANSFORM]=n.DomUtil.getTranslateString(this._delta)+" "+n.DomUtil.getScaleString(this._scale,this._startCenter)},_onTouchEnd:function(e){if(!this._moved||!this._zooming)return;var t=this._map;this._zooming=!1,n.DomUtil.removeClass(t._mapPane,"leaflet-touching"),n.DomEvent.off(document,"touchmove",this._onTouchMove).off(document,"touchend",this._onTouchEnd);var r=this._getScaleOrigin(),i=t.layerPointToLatLng(r),s=t.getZoom(),o=t.getScaleZoom(this._scale)-s,u=o>0?Math.ceil(o):Math.floor(o),a=t._limitZoom(s+u);t.fire("zoomanim",{center:i,zoom:a}),t._runAnimation(i,a,t.getZoomScale(a)/this._scale,r,!0)},_getScaleOrigin:function(){var e=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(e)}}),n.Map.addInitHook("addHandler","touchZoom",n.Map.TouchZoom),n.Map.mergeOptions({boxZoom:!0}),n.Map.BoxZoom=n.Handler.extend({initialize:function(e){this._map=e,this._container=e._container,this._pane=e._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(e){if(!e.shiftKey||e.which!==1&&e.button!==1)return!1;n.DomUtil.disableTextSelection(),this._startLayerPoint=this._map.mouseEventToLayerPoint(e),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(document,"mousemove",this._onMouseMove,this).on(document,"mouseup",this._onMouseUp,this).preventDefault(e),this._map.fire("boxzoomstart")},_onMouseMove:function(e){var t=this._startLayerPoint,r=this._box,i=this._map.mouseEventToLayerPoint(e),s=i.subtract(t),o=new n.Point(Math.min(i.x,t.x),Math.min(i.y,t.y));n.DomUtil.setPosition(r,o),r.style.width=Math.abs(s.x)-4+"px",r.style.height=Math.abs(s.y)-4+"px"},_onMouseUp:function(e){this._pane.removeChild(this._box),this._container.style.cursor="",n.DomUtil.enableTextSelection(),n.DomEvent.off(document,"mousemove",this._onMouseMove).off(document,"mouseup",this._onMouseUp);var t=this._map,r=t.mouseEventToLayerPoint(e),i=new n.LatLngBounds(t.layerPointToLatLng(this._startLayerPoint),t.layerPointToLatLng(r));t.fitBounds(i),t.fire("boxzoomend",{boxZoomBounds:i})}}),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,61,107],zoomOut:[189,109,0]},initialize:function(e){this._map=e,this._setPanOffset(e.options.keyboardPanOffset),this._setZoomOffset(e.options.keyboardZoomOffset)},addHooks:function(){var e=this._map._container;e.tabIndex===-1&&(e.tabIndex="0"),n.DomEvent.addListener(e,"focus",this._onFocus,this).addListener(e,"blur",this._onBlur,this).addListener(e,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var e=this._map._container;n.DomEvent.removeListener(e,"focus",this._onFocus,this).removeListener(e,"blur",this._onBlur,this).removeListener(e,"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(e){var t=this._panKeys={},n=this.keyCodes,r,i;for(r=0,i=n.left.length;r<i;r++)t[n.left[r]]=[-1*e,0];for(r=0,i=n.right.length;r<i;r++)t[n.right[r]]=[e,0];for(r=0,i=n.down.length;r<i;r++)t[n.down[r]]=[0,e];for(r=0,i=n.up.length;r<i;r++)t[n.up[r]]=[0,-1*e]},_setZoomOffset:function(e){var t=this._zoomKeys={},n=this.keyCodes,r,i;for(r=0,i=n.zoomIn.length;r<i;r++)t[n.zoomIn[r]]=e;for(r=0,i=n.zoomOut.length;r<i;r++)t[n.zoomOut[r]]=-e},_addHooks:function(){n.DomEvent.addListener(document,"keydown",this._onKeyDown,this)},_removeHooks:function(){n.DomEvent.removeListener(document,"keydown",this._onKeyDown,this)},_onKeyDown:function(e){var t=e.keyCode;if(this._panKeys.hasOwnProperty(t))this._map.panBy(this._panKeys[t]);else{if(!this._zoomKeys.hasOwnProperty(t))return;this._map.setZoom(this._map.getZoom()+this._zoomKeys[t])}n.DomEvent.stop(e)}}),n.Map.addInitHook("addHandler","keyboard",n.Map.Keyboard),n.Handler.MarkerDrag=n.Handler.extend({initialize:function(e){this._marker=e},addHooks:function(){var e=this._marker._icon;this._draggable||(this._draggable=(new n.Draggable(e,e)).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(e){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(e){var t=n.DomUtil.getPosition(this._marker._icon);this._marker._shadow&&n.DomUtil.setPosition(this._marker._shadow,t),this._marker._latlng=this._marker._map.layerPointToLatLng(t),this._marker.fire("move").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(e,t){this._poly=e,n.Util.setOptions(this,t)},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 e=this._poly._latlngs,t,r,i,s;for(t=0,i=e.length;t<i;t++)s=this._createMarker(e[t],t),s.on("click",this._onMarkerClick,this),this._markers.push(s);var o,u;for(t=0,r=i-1;t<i;r=t++){if(t===0&&!(n.Polygon&&this._poly instanceof n.Polygon))continue;o=this._markers[r],u=this._markers[t],this._createMiddleMarker(o,u),this._updatePrevNext(o,u)}},_createMarker:function(e,t){var r=new n.Marker(e,{draggable:!0,icon:this.options.icon});return r._origLatLng=e,r._index=t,r.on("drag",this._onMarkerDrag,this),r.on("dragend",this._fireEdit,this),this._markerGroup.addLayer(r),r},_fireEdit:function(){this._poly.fire("edit")},_onMarkerDrag:function(e){var t=e.target;n.Util.extend(t._origLatLng,t._latlng),t._middleLeft&&t._middleLeft.setLatLng(this._getMiddleLatLng(t._prev,t)),t._middleRight&&t._middleRight.setLatLng(this._getMiddleLatLng(t,t._next)),this._poly.redraw()},_onMarkerClick:function(e){if(this._poly._latlngs.length<3)return;var t=e.target,n=t._index;t._prev&&t._next&&(this._createMiddleMarker(t._prev,t._next),this._updatePrevNext(t._prev,t._next)),this._markerGroup.removeLayer(t),t._middleLeft&&this._markerGroup.removeLayer(t._middleLeft),t._middleRight&&this._markerGroup.removeLayer(t._middleRight),this._markers.splice(n,1),this._poly.spliceLatLngs(n,1),this._updateIndexes(n,-1),this._poly.fire("edit")},_updateIndexes:function(e,t){this._markerGroup.eachLayer(function(n){n._index>e&&(n._index+=t)})},_createMiddleMarker:function(e,t){var n=this._getMiddleLatLng(e,t),r=this._createMarker(n),i,s,o;r.setOpacity(.6),e._middleRight=t._middleLeft=r,s=function(){var s=t._index;r._index=s,r.off("click",i).on("click",this._onMarkerClick,this),n.lat=r.getLatLng().lat,n.lng=r.getLatLng().lng,this._poly.spliceLatLngs(s,0,n),this._markers.splice(s,0,r),r.setOpacity(1),this._updateIndexes(s,1),t._index++,this._updatePrevNext(e,r),this._updatePrevNext(r,t)},o=function(){r.off("dragstart",s,this),r.off("dragend",o,this),this._createMiddleMarker(e,r),this._createMiddleMarker(r,t)},i=function(){s.call(this),o.call(this),this._poly.fire("edit")},r.on("click",i,this).on("dragstart",s,this).on("dragend",o,this),this._markerGroup.addLayer(r)},_updatePrevNext:function(e,t){e._next=t,t._prev=e},_getMiddleLatLng:function(e,t){var n=this._poly._map,r=n.latLngToLayerPoint(e.getLatLng()),i=n.latLngToLayerPoint(t.getLatLng());return n.layerPointToLatLng(r._add(i).divideBy(2))}}),n.Control=n.Class.extend({options:{position:"topright"},initialize:function(e){n.Util.setOptions(this,e)},getPosition:function(){return this.options.position},setPosition:function(e){var t=this._map;return t&&t.removeControl(this),this.options.position=e,t&&t.addControl(this),this},addTo:function(e){this._map=e;var t=this._container=this.onAdd(e),r=this.getPosition(),i=e._controlCorners[r];return n.DomUtil.addClass(t,"leaflet-control"),r.indexOf("bottom")!==-1?i.insertBefore(t,i.firstChild):i.appendChild(t),this},removeFrom:function(e){var t=this.getPosition(),n=e._controlCorners[t];return n.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(e),this}}),n.control=function(e){return new n.Control(e)},n.Map.include({addControl:function(e){return e.addTo(this),this},removeControl:function(e){return e.removeFrom(this),this},_initControlPos:function(){function i(i,s){var o=t+i+" "+t+s;e[i+s]=n.DomUtil.create("div",o,r)}var e=this._controlCorners={},t="leaflet-",r=this._controlContainer=n.DomUtil.create("div",t+"control-container",this._container);i("top","left"),i("top","right"),i("bottom","left"),i("bottom","right")}}),n.Control.Zoom=n.Control.extend({options:{position:"topleft"},onAdd:function(e){var t="leaflet-control-zoom",r=n.DomUtil.create("div",t);return this._createButton("Zoom in",t+"-in",r,e.zoomIn,e),this._createButton("Zoom out",t+"-out",r,e.zoomOut,e),r},_createButton:function(e,t,r,i,s){var o=n.DomUtil.create("a",t,r);return o.href="#",o.title=e,n.DomEvent.on(o,"click",n.DomEvent.stopPropagation).on(o,"click",n.DomEvent.preventDefault).on(o,"click",i,s).on(o,"dblclick",n.DomEvent.stopPropagation),o}}),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(e){return new n.Control.Zoom(e)},n.Control.Attribution=n.Control.extend({options:{position:"bottomright",prefix:'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>'},initialize:function(e){n.Util.setOptions(this,e),this._attributions={}},onAdd:function(e){return this._container=n.DomUtil.create("div","leaflet-control-attribution"),n.DomEvent.disableClickPropagation(this._container),e.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(e){e.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(e){return this.options.prefix=e,this._update(),this},addAttribution:function(e){if(!e)return;return this._attributions[e]||(this._attributions[e]=0),this._attributions[e]++,this._update(),this},removeAttribution:function(e){if(!e)return;return this._attributions[e]--,this._update(),this},_update:function(){if(!this._map)return;var e=[];for(var t in this._attributions)this._attributions.hasOwnProperty(t)&&this._attributions[t]&&e.push(t);var n=[];this.options.prefix&&n.push(this.options.prefix),e.length&&n.push(e.join(", ")),this._container.innerHTML=n.join(" &#8212; ")},_onLayerAdd:function(e){e.layer.getAttribution&&this.addAttribution(e.layer.getAttribution())},_onLayerRemove:function(e){e.layer.getAttribution&&this.removeAttribution(e.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(e){return new n.Control.Attribution(e)},n.Control.Scale=n.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(e){this._map=e;var t="leaflet-control-scale",r=n.DomUtil.create("div",t),i=this.options;return this._addScales(i,t,r),e.on(i.updateWhenIdle?"moveend":"move",this._update,this),this._update(),r},onRemove:function(e){e.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(e,t,r){e.metric&&(this._mScale=n.DomUtil.create("div",t+"-line",r)),e.imperial&&(this._iScale=n.DomUtil.create("div",t+"-line",r))},_update:function(){var e=this._map.getBounds(),t=e.getCenter().lat,r=(new n.LatLng(t,0)).distanceTo(new n.LatLng(t,180)),i=r*(e.getNorthEast().lng-e.getSouthWest().lng)/180,s=this._map.getSize(),o=this.options,u=0;s.x>0&&(u=i*(o.maxWidth/s.x)),this._updateScales(o,u)},_updateScales:function(e,t){e.metric&&t&&this._updateMetric(t),e.imperial&&t&&this._updateImperial(t)},_updateMetric:function(e){var t=this._getRoundNum(e);this._mScale.style.width=this._getScaleWidth(t/e)+"px",this._mScale.innerHTML=t<1e3?t+" m":t/1e3+" km"},_updateImperial:function(e){var t=e*3.2808399,n=this._iScale,r,i,s;t>5280?(r=t/5280,i=this._getRoundNum(r),n.style.width=this._getScaleWidth(i/r)+"px",n.innerHTML=i+" mi"):(s=this._getRoundNum(t),n.style.width=this._getScaleWidth(s/t)+"px",n.innerHTML=s+" ft")},_getScaleWidth:function(e){return Math.round(this.options.maxWidth*e)-10},_getRoundNum:function(e){var t=Math.pow(10,(Math.floor(e)+"").length-1),n=e/t;return n=n>=10?10:n>=5?5:n>=3?3:n>=2?2:1,t*n}}),n.control.scale=function(e){return new n.Control.Scale(e)},n.Control.Layers=n.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(e,t,r){n.Util.setOptions(this,r),this._layers={},this._lastZIndex=0;for(var i in e)e.hasOwnProperty(i)&&this._addLayer(e[i],i);for(i in t)t.hasOwnProperty(i)&&this._addLayer(t[i],i,!0)},onAdd:function(e){return this._initLayout(),this._update(),this._container},addBaseLayer:function(e,t){return this._addLayer(e,t),this._update(),this},addOverlay:function(e,t){return this._addLayer(e,t,!0),this._update(),this},removeLayer:function(e){var t=n.Util.stamp(e);return delete this._layers[t],this._update(),this},_initLayout:function(){var e="leaflet-control-layers",t=this._container=n.DomUtil.create("div",e);n.Browser.touch?n.DomEvent.on(t,"click",n.DomEvent.stopPropagation):n.DomEvent.disableClickPropagation(t);var r=this._form=n.DomUtil.create("form",e+"-list");if(this.options.collapsed){n.DomEvent.on(t,"mouseover",this._expand,this).on(t,"mouseout",this._collapse,this);var i=this._layersLink=n.DomUtil.create("a",e+"-toggle",t);i.href="#",i.title="Layers",n.Browser.touch?n.DomEvent.on(i,"click",n.DomEvent.stopPropagation).on(i,"click",n.DomEvent.preventDefault).on(i,"click",this._expand,this):n.DomEvent.on(i,"focus",this._expand,this),this._map.on("movestart",this._collapse,this)}else this._expand();this._baseLayersList=n.DomUtil.create("div",e+"-base",r),this._separator=n.DomUtil.create("div",e+"-separator",r),this._overlaysList=n.DomUtil.create("div",e+"-overlays",r),t.appendChild(r)},_addLayer:function(e,t,r){var i=n.Util.stamp(e);this._layers[i]={layer:e,name:t,overlay:r},this.options.autoZIndex&&e.setZIndex&&(this._lastZIndex++,e.setZIndex(this._lastZIndex))},_update:function(){if(!this._container)return;this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var e=!1,t=!1;for(var n in this._layers)if(this._layers.hasOwnProperty(n)){var r=this._layers[n];this._addItem(r),t=t||r.overlay,e=e||!r.overlay}this._separator.style.display=t&&e?"":"none"},_addItem:function(e,t){var r=document.createElement("label"),i=document.createElement("input");e.overlay||(i.name="leaflet-base-layers"),i.type=e.overlay?"checkbox":"radio",i.layerId=n.Util.stamp(e.layer),i.defaultChecked=this._map.hasLayer(e.layer),n.DomEvent.on(i,"click",this._onInputClick,this);var s=document.createTextNode(" "+e.name);r.appendChild(i),r.appendChild(s);var o=e.overlay?this._overlaysList:this._baseLayersList;o.appendChild(r)},_onInputClick:function(){var e,t,n,r=this._form.getElementsByTagName("input"),i=r.length;for(e=0;e<i;e++)t=r[e],n=this._layers[t.layerId],t.checked?this._map.addLayer(n.layer,!n.overlay):this._map.removeLayer(n.layer)},_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(e,t,r){return new n.Control.Layers(e,t,r)},n.Transition=n.Class.extend({includes:n.Mixin.Events,statics:{CUSTOM_PROPS_SETTERS:{position:n.DomUtil.setPosition},implemented:function(){return n.Transition.NATIVE||n.Transition.TIMER}},options:{easing:"ease",duration:.5},_setProperty:function(e,t){var r=n.Transition.CUSTOM_PROPS_SETTERS;e in r?r[e](this._el,t):this._el.style[e]=t}}),n.Transition=n.Transition.extend({statics:function(){var e=n.DomUtil.TRANSITION,t=e==="webkitTransition"||e==="OTransition"?e+"End":"transitionend";return{NATIVE:!!e,TRANSITION:e,PROPERTY:e+"Property",DURATION:e+"Duration",EASING:e+"TimingFunction",END:t,CUSTOM_PROPS_PROPERTIES:{position:n.Browser.any3d?n.DomUtil.TRANSFORM:"top, left"}}}(),options:{fakeStepInterval:100},initialize:function(e,t){this._el=e,n.Util.setOptions(this,t),n.DomEvent.on(e,n.Transition.END,this._onTransitionEnd,this),this._onFakeStep=n.Util.bind(this._onFakeStep,this)},run:function(e){var t,r=[],i=n.Transition.CUSTOM_PROPS_PROPERTIES;for(t in e)e.hasOwnProperty(t)&&(t=i[t]?i[t]:t,t=this._dasherize(t),r.push(t));this._el.style[n.Transition.DURATION]=this.options.duration+"s",this._el.style[n.Transition.EASING]=this.options.easing,this._el.style[n.Transition.PROPERTY]=r.join(", ");for(t in e)e.hasOwnProperty(t)&&this._setProperty(t,e[t]);this._inProgress=!0,this.fire("start"),n.Browser.mobileWebkit&&(this.backupEventFire=setTimeout(n.Util.bind(this._onBackupFireEnd,this),this.options.duration*1.2*1e3)),n.Transition.NATIVE?(clearInterval(this._timer),this._timer=setInterval(this._onFakeStep,this.options.fakeStepInterval)):this._onTransitionEnd()},_dasherize:function(){function t(e){return"-"+e.toLowerCase()}var e=/([A-Z])/g;return function(n){return n.replace(e,t)}}(),_onFakeStep:function(){this.fire("step")},_onTransitionEnd:function(e){this._inProgress&&(this._inProgress=!1,clearInterval(this._timer),this._el.style[n.Transition.TRANSITION]="",clearTimeout(this.backupEventFire),delete this.backupEventFire,this.fire("step"),e&&e.type&&this.fire("end"))},_onBackupFireEnd:function(){var e=document.createEvent("Event");e.initEvent(n.Transition.END,!0,!1),this._el.dispatchEvent(e)}}),n.Transition=n.Transition.NATIVE?n.Transition:n.Transition.extend({statics:{getTime:Date.now||function(){return+(new Date)},TIMER:!0,EASINGS:{linear:function(e){return e},"ease-out":function(e){return e*(2-e)}},CUSTOM_PROPS_GETTERS:{position:n.DomUtil.getPosition},UNIT_RE:/^[\d\.]+(\D*)$/},options:{fps:50},initialize:function(e,t){this._el=e,n.Util.extend(this.options,t),this._easing=n.Transition.EASINGS[this.options.easing]||n.Transition.EASINGS["ease-out"],this._step=n.Util.bind(this._step,this),this._interval=Math.round(1e3/this.options.fps)},run:function(e){this._props={};var t=n.Transition.CUSTOM_PROPS_GETTERS,r=n.Transition.UNIT_RE;this.fire("start");for(var i in e)if(e.hasOwnProperty(i)){var s={};if(i in t)s.from=t[i](this._el);else{var o=this._el.style[i].match(r);s.from=parseFloat(o[0]),s.unit=o[1]}s.to=e[i],this._props[i]=s}clearInterval(this._timer),this._timer=setInterval(this._step,this._interval),this._startTime=n.Transition.getTime()},_step:function(){var e=n.Transition.getTime(),t=e-this._startTime,r=this.options.duration*1e3;t<r?this._runFrame(this._easing(t/r)):(this._runFrame(1),this._complete())},_runFrame:function(e){var t=n.Transition.CUSTOM_PROPS_SETTERS,r,i,s;for(r in this._props)this._props.hasOwnProperty(r)&&(i=this._props[r],r in t?(s=i.to.subtract(i.from).multiplyBy(e).add(i.from),t[r](this._el,s)):this._el.style[r]=(i.to-i.from)*e+i.from+i.unit);this.fire("step")},_complete:function(){clearInterval(this._timer),this.fire("end")}}),n.Map.include(!n.Transition||!n.Transition.implemented()?{}:{setView:function(e,t,n){t=this._limitZoom(t);var r=this._zoom!==t;if(this._loaded&&!n&&this._layers){var i=r?this._zoomToIfClose&&this._zoomToIfClose(e,t):this._panByIfClose(e);if(i)return clearTimeout(this._sizeTimer),this}return this._resetView(e,t),this},panBy:function(e,t){return e=n.point(e),!e.x&&!e.y?this:(this._panTransition||(this._panTransition=new n.Transition(this._mapPane),this._panTransition.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),n.Util.setOptions(this._panTransition,n.Util.extend({duration:.25},t)),this.fire("movestart"),n.DomUtil.addClass(this._mapPane,"leaflet-pan-anim"),this._panTransition.run({position:n.DomUtil.getPosition(this._mapPane).subtract(e)}),this)},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){n.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_panByIfClose:function(e){var t=this._getCenterOffset(e)._floor();return this._offsetIsWithinView(t)?(this.panBy(t),!0):!1},_offsetIsWithinView:function(e,t){var n=t||1,r=this.getSize();return Math.abs(e.x)<=r.x*n&&Math.abs(e.y)<=r.y*n}}),n.Map.mergeOptions({zoomAnimation:n.DomUtil.TRANSITION&&!n.Browser.android23&&!n.Browser.mobileOpera}),n.Map.include(n.DomUtil.TRANSITION?{_zoomToIfClose:function(e,t){if(this._animatingZoom)return!0;if(!this.options.zoomAnimation)return!1;var r=this.getZoomScale(t),i=this._getCenterOffset(e).divideBy(1-1/r);if(!this._offsetIsWithinView(i,1))return!1;n.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this.fire("movestart").fire("zoomstart"),this._prepareTileBg(),this.fire("zoomanim",{center:e,zoom:t});var s=this._getCenterLayerPoint().add(i);return this._runAnimation(e,t,r,s),!0},_runAnimation:function(t,r,i,s,o){this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=r;var u=n.DomUtil.TRANSFORM,a=this._tileBg;clearTimeout(this._clearTileBgTimer);if(n.Browser.gecko||e.opera)a.style[u]+=" translate(0,0)";var f;n.Browser.android23?(a.style[u+"Origin"]=s.x+"px "+s.y+"px",f="scale("+i+")"):f=n.DomUtil.getScaleString(i,s),n.Util.falseFn(a.offsetWidth);var l={};o?l[u]=a.style[u]+" "+f:l[u]=f+" "+a.style[u],a.transition.run(l)},_prepareTileBg:function(){var e=this._tilePane,t=this._tileBg;if(!n.Browser.android23&&t&&this._getLoadedTilesPercentage(t)>.5&&this._getLoadedTilesPercentage(e)<.5){e.style.visibility="hidden",e.empty=!0,this._stopLoadingImages(e);return}t||(t=this._tileBg=this._createPane("leaflet-tile-pane",this._mapPane),t.style.zIndex=1),t.style[n.DomUtil.TRANSFORM]="",t.style.visibility="hidden",t.empty=!0,e.empty=!1,this._tilePane=this._panes.tilePane=t;var r=this._tileBg=e;r.transition||(r.transition=new n.Transition(r,{duration:.25,easing:"cubic-bezier(0.25,0.1,0.25,0.75)"}),r.transition.on("end",this._onZoomTransitionEnd,this)),this._stopLoadingImages(r)},_getLoadedTilesPercentage:function(e){var t=e.getElementsByTagName("img"),n,r,i=0;for(n=0,r=t.length;n<r;n++)t[n].complete&&i++;return i/r},_stopLoadingImages:function(e){var t=Array.prototype.slice.call(e.getElementsByTagName("img")),r,i,s;for(r=0,i=t.length;r<i;r++)s=t[r],s.complete||(s.onload=n.Util.falseFn,s.onerror=n.Util.falseFn,s.src=n.Util.emptyImageUrl,s.parentNode.removeChild(s))},_onZoomTransitionEnd:function(){this._restoreTileFront(),n.Util.falseFn(this._tileBg.offsetWidth),this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),n.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!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:Infinity,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(e){e=this._locationOptions=n.Util.extend(this._defaultLocateOptions,e);if(!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var t=n.Util.bind(this._handleGeolocationResponse,this),r=n.Util.bind(this._handleGeolocationError,this);return e.watch?this._locationWatchId=navigator.geolocation.watchPosition(t,r,e):navigator.geolocation.getCurrentPosition(t,r,e),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this},_handleGeolocationError:function(e){var t=e.code,n=e.message||(t===1?"permission denied":t===2?"position unavailable":"timeout");this._locationOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:t,message:"Geolocation error: "+n+"."})},_handleGeolocationResponse:function(e){var t=180*e.coords.accuracy/4e7,r=t*2,i=e.coords.latitude,s=e.coords.longitude,o=new n.LatLng(i,s),u=new n.LatLng(i-t,s-r),a=new n.LatLng(i+t,s+r),f=new n.LatLngBounds(u,a),l=this._locationOptions;if(l.setView){var c=Math.min(this.getBoundsZoom(f),l.maxZoom);this.setView(o,c)}this.fire("locationfound",{latlng:o,bounds:f,accuracy:e.coords.accuracy})}})})(this);
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 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 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 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 };
00 <!DOCTYPE html>
11 <html>
22 <head>
3 <title>Jasmine Test Runner</title>
4 <link rel="stylesheet" type="text/css" href="../lib/jasmine/jasmine.css">
5 <script type="text/javascript" src="../lib/jasmine/jasmine.js"></script>
6 <script type="text/javascript" src="../lib/jasmine/jasmine-html.js"></script>
7
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
88 <!-- source files -->
9
10 <script type="text/javascript">
11 L = 'test'; //to test L#noConflict later
12 </script>
13
14 <script type="text/javascript" src="../debug/leaflet-include.js"></script>
15
9
10 <script type="text/javascript">
11 L = 'test'; //to test L#noConflict later
12 </script>
13
14 <script type="text/javascript" src="../debug/leaflet-include.js"></script>
15
1616
1717 <!-- spec files -->
18
19 <script type="text/javascript" src="suites/SpecHelper.js"></script>
20 <script type="text/javascript" src="suites/LeafletSpec.js"></script>
21
22 <!-- /core -->
23 <script type="text/javascript" src="suites/core/UtilSpec.js"></script>
24 <script type="text/javascript" src="suites/core/ClassSpec.js"></script>
25 <script type="text/javascript" src="suites/core/EventsSpec.js"></script>
2618
27 <!-- /geometry -->
28 <script type="text/javascript" src="suites/geometry/PointSpec.js"></script>
29 <script type="text/javascript" src="suites/geometry/BoundsSpec.js"></script>
30 <script type="text/javascript" src="suites/geometry/TransformationSpec.js"></script>
19 <script type="text/javascript" src="suites/SpecHelper.js"></script>
20 <script type="text/javascript" src="suites/LeafletSpec.js"></script>
3121
32 <!-- /geo -->
33 <script type="text/javascript" src="suites/geo/LatLngSpec.js"></script>
34 <script type="text/javascript" src="suites/geo/LatLngBoundsSpec.js"></script>
35 <script type="text/javascript" src="suites/geo/ProjectionSpec.js"></script>
22 <!-- /core -->
23 <script type="text/javascript" src="suites/core/UtilSpec.js"></script>
24 <script type="text/javascript" src="suites/core/ClassSpec.js"></script>
25 <script type="text/javascript" src="suites/core/EventsSpec.js"></script>
3626
37 <!-- /dom -->
38 <script type="text/javascript" src="suites/dom/DomEventSpec.js"></script>
39 <script type="text/javascript" src="suites/dom/DomUtilSpec.js"></script>
27 <!-- /geometry -->
28 <script type="text/javascript" src="suites/geometry/PointSpec.js"></script>
29 <script type="text/javascript" src="suites/geometry/BoundsSpec.js"></script>
30 <script type="text/javascript" src="suites/geometry/TransformationSpec.js"></script>
4031
41 <!-- /layer -->
42 <script type="text/javascript" src="suites/layer/TileLayerSpec.js"></script>
43 <script type="text/javascript" src="suites/layer/vector/PolylineGeometrySpec.js"></script>
44
45 <!-- /map -->
46 <script type="text/javascript" src="suites/map/MapSpec.js"></script>
32 <!-- /geo -->
33 <script type="text/javascript" src="suites/geo/LatLngSpec.js"></script>
34 <script type="text/javascript" src="suites/geo/LatLngBoundsSpec.js"></script>
35 <script type="text/javascript" src="suites/geo/ProjectionSpec.js"></script>
36
37 <!-- /dom -->
38 <script type="text/javascript" src="suites/dom/DomEventSpec.js"></script>
39 <script type="text/javascript" src="suites/dom/DomUtilSpec.js"></script>
40
41 <!-- /layer -->
42 <script type="text/javascript" src="suites/layer/TileLayerSpec.js"></script>
43 <script type="text/javascript" src="suites/layer/vector/PolylineGeometrySpec.js"></script>
44
45 <!-- /map -->
46 <script type="text/javascript" src="suites/map/MapSpec.js"></script>
4747 </head>
4848 <body>
49 <script type="text/javascript">
50 (function() {
51 var jasmineEnv = jasmine.getEnv();
52 jasmineEnv.updateInterval = 1000;
4953
50 <script type="text/javascript">
51 jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
52 jasmine.getEnv().execute();
53 </script>
54 var htmlReporter = new jasmine.HtmlReporter();
55 jasmineEnv.addReporter(htmlReporter);
5456
57 jasmineEnv.specFilter = function(spec) {
58 return htmlReporter.specFilter(spec);
59 };
60
61 jasmineEnv.execute();
62 })();
63 </script>
5564 </body>
5665 </html>
00 describe('L#noConflict', function() {
11 it('should restore the previous L value and return Leaflet namespace', function(){
22
3 expect(L.VERSION).toBeDefined();
3 expect(L.version).toBeDefined();
44
55 var L2 = L.noConflict();
6
7 expect(L).toEqual('test');
8 expect(L2.VERSION).toBeDefined();
9
10 this.after(function() {
6 this.after(function () {
117 window.L = L2;
128 });
9
10 expect(L).toEqual('test');
11 expect(L2.version).toBeDefined();
1312 });
1413 });
00 function noSpecs() {
1 xit('should have specs', function() {
2 expect('specs').toBe();
3 });
1 xit('should have specs');
42 }
53
64 if (!Array.prototype.map) {
4242
4343 b.bar();
4444
45 expect(method).toHaveBeenCalled();
46 });
47
48 it("should grant the ability to call parent methods, including constructor", function() {
49 var Klass2 = Klass.extend({
50 initialize: function() {},
51 bar: function() {}
52 });
53
54 var b = new Klass2();
55
56 expect(constructor).not.toHaveBeenCalled();
57 b.constructor.superclass.initialize.call(this);
58 expect(constructor).toHaveBeenCalled();
59
60 b.constructor.superclass.bar.call(this);
6145 expect(method).toHaveBeenCalled();
6246 });
6347
121105 foo3: 4
122106 });
123107 });
124
125 it("should have working superclass access with inheritance level > 2", function() {
126 var constructor2 = jasmine.createSpy("Klass2 constructor"),
127 constructor3 = jasmine.createSpy("Klass3 constructor");
128
129 var Klass2 = Klass.extend({
130 initialize: function() {
131 constructor2();
132 expect(Klass2.superclass).toBe(Klass.prototype);
133 Klass2.superclass.initialize.apply(this, arguments);
134 }
135 });
136
137 var Klass3 = Klass2.extend({
138 initialize: function() {
139 constructor3();
140 expect(Klass3.superclass).toBe(Klass2.prototype);
141 Klass3.superclass.initialize.apply(this, arguments);
142 }
143 });
144
145 var a = new Klass3();
146
147 expect(constructor3).toHaveBeenCalled();
148 expect(constructor2).toHaveBeenCalled();
149 expect(constructor).toHaveBeenCalled();
150 });
151108 });
109
110 // TODO Class.include
111
112 // TODO Class.mergeOptions
152113 });
1212 var obj = new Klass(),
1313 spy = jasmine.createSpy(),
1414 spy2 = jasmine.createSpy(),
15 spy3 = jasmine.createSpy();
15 spy3 = jasmine.createSpy(),
16 spy4 = jasmine.createSpy(),
17 spy5 = jasmine.createSpy();
18 spy6 = jasmine.createSpy();
1619
1720 obj.addEventListener('test', spy);
1821 obj.addEventListener('test', spy2);
1922 obj.addEventListener('other', spy3);
23 obj.addEventListener({ test: spy4, other: spy5 });
24 obj.addEventListener({'test other': spy6 })
2025
2126 expect(spy).not.toHaveBeenCalled();
2227 expect(spy2).not.toHaveBeenCalled();
2328 expect(spy3).not.toHaveBeenCalled();
29 expect(spy4).not.toHaveBeenCalled();
30 expect(spy5).not.toHaveBeenCalled();
31 expect(spy6).not.toHaveBeenCalled();
2432
2533 obj.fireEvent('test');
2634
2735 expect(spy).toHaveBeenCalled();
2836 expect(spy2).toHaveBeenCalled();
2937 expect(spy3).not.toHaveBeenCalled();
38 expect(spy4).toHaveBeenCalled();
39 expect(spy5).not.toHaveBeenCalled();
40 expect(spy6).toHaveBeenCalled();
41 expect(spy6.calls.length).toEqual(1);
3042 });
43
3144
3245 it('should provide event object to listeners and execute them in the right context', function() {
3346 var obj = new Klass(),
3447 obj2 = new Klass(),
48 obj3 = new Klass(),
49 obj4 = new Klass(),
3550 foo = {};
3651
3752 function listener1(e) {
3853 expect(e.type).toEqual('test');
3954 expect(e.target).toEqual(obj);
4055 expect(this).toEqual(obj);
41 expect(e.bar).toEqual(3);
42 };
56 expect(e.baz).toEqual(1);
57 }
4358
4459 function listener2(e) {
60 expect(e.type).toEqual('test');
4561 expect(e.target).toEqual(obj2);
4662 expect(this).toEqual(foo);
47 };
63 expect(e.baz).toEqual(2);
64 }
65
66 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);
71 }
72
73 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);
78 }
4879
4980 obj.addEventListener('test', listener1);
5081 obj2.addEventListener('test', listener2, foo);
82 obj3.addEventListener({ test: listener3 });
83 obj4.addEventListener({ test: listener4 }, foo);
5184
52 obj.fireEvent('test', {bar: 3});
85 obj.fireEvent('test', {baz: 1});
86 obj2.fireEvent('test', {baz: 2});
87 obj3.fireEvent('test', {baz: 3});
88 obj4.fireEvent('test', {baz: 4});
5389 });
5490
5591 it('should not call listeners removed through #removeEventListener', function() {
5692 var obj = new Klass(),
57 spy = jasmine.createSpy();
93 spy = jasmine.createSpy(),
94 spy2 = jasmine.createSpy(),
95 spy3 = jasmine.createSpy(),
96 spy4 = jasmine.createSpy(),
97 spy5 = jasmine.createSpy();
5898
5999 obj.addEventListener('test', spy);
60100 obj.removeEventListener('test', spy);
62102 obj.fireEvent('test');
63103
64104 expect(spy).not.toHaveBeenCalled();
105
106 obj.addEventListener('test2', spy2);
107 obj.addEventListener('test2', spy3);
108 obj.removeEventListener('test2');
109
110 obj.fireEvent('test2');
111
112 expect(spy2).not.toHaveBeenCalled();
113 expect(spy3).not.toHaveBeenCalled();
114
115 obj.addEventListener('test3', spy4);
116 obj.addEventListener('test4', spy5);
117 obj.removeEventListener({
118 test3: spy4,
119 test4: spy5
120 });
121
122 obj.fireEvent('test3');
123 obj.fireEvent('test4');
124
125 expect(spy4).not.toHaveBeenCalled();
126 expect(spy5).not.toHaveBeenCalled();
65127 });
66128 });
67129
5959 expect(id2).not.toEqual(id);
6060 });
6161 });
62
63 // TODO cancel/requestAnimFrame?
64
65 // TODO limitExecByInterval
66
67 // TODO formatNum
68
69 // TODO splitWords
70
71 // TODO setOptions
72
73 // TODO getParamString
74
75 // TODO template
6276 });
0 var L, originalL;
01
1 (function (root) {
2 root.L = {
3 VERSION: '0.3',
2 if (typeof exports !== undefined + '') {
3 L = exports;
4 } else {
5 originalL = window.L;
6 L = {};
47
5 ROOT_URL: root.L_ROOT_URL || (function () {
6 var scripts = document.getElementsByTagName('script'),
7 leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
8 L.noConflict = function () {
9 window.L = originalL;
10 return this;
11 };
812
9 var i, len, src, matches;
13 window.L = L;
14 }
1015
11 for (i = 0, len = scripts.length; i < len; i++) {
12 src = scripts[i].src;
13 matches = src.match(leafletRe);
14
15 if (matches) {
16 if (matches[1] === 'include') {
17 return '../../dist/';
18 }
19 return src.split(leafletRe)[0] + '/';
20 }
21 }
22 return '';
23 }()),
24
25 noConflict: function () {
26 root.L = this._originalL;
27 return this;
28 },
29
30 _originalL: root.L
31 };
32 }(this));
16 L.version = '0.4';
0 L.Control.Attribution = L.Class.extend({
1 initialize: function (prefix) {
2 this._prefix = prefix || 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>';
0 L.Control.Attribution = L.Control.extend({
1 options: {
2 position: 'bottomright',
3 prefix: 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>'
4 },
5
6 initialize: function (options) {
7 L.Util.setOptions(this, options);
8
39 this._attributions = {};
410 },
511
612 onAdd: function (map) {
713 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
814 L.DomEvent.disableClickPropagation(this._container);
9 this._map = map;
15
16 map
17 .on('layeradd', this._onLayerAdd, this)
18 .on('layerremove', this._onLayerRemove, this);
19
1020 this._update();
11 },
1221
13 getPosition: function () {
14 return L.Control.Position.BOTTOM_RIGHT;
15 },
16
17 getContainer: function () {
1822 return this._container;
1923 },
2024
25 onRemove: function (map) {
26 map
27 .off('layeradd', this._onLayerAdd)
28 .off('layerremove', this._onLayerRemove);
29
30 },
31
2132 setPrefix: function (prefix) {
22 this._prefix = prefix;
33 this.options.prefix = prefix;
2334 this._update();
35 return this;
2436 },
2537
2638 addAttribution: function (text) {
27 if (!text) {
28 return;
29 }
39 if (!text) { return; }
40
3041 if (!this._attributions[text]) {
3142 this._attributions[text] = 0;
3243 }
3344 this._attributions[text]++;
45
3446 this._update();
47
48 return this;
3549 },
3650
3751 removeAttribution: function (text) {
38 if (!text) {
39 return;
40 }
52 if (!text) { return; }
53
4154 this._attributions[text]--;
4255 this._update();
56
57 return this;
4358 },
4459
4560 _update: function () {
46 if (!this._map) {
47 return;
48 }
61 if (!this._map) { return; }
4962
5063 var attribs = [];
5164
5265 for (var i in this._attributions) {
53 if (this._attributions.hasOwnProperty(i)) {
66 if (this._attributions.hasOwnProperty(i) && this._attributions[i]) {
5467 attribs.push(i);
5568 }
5669 }
5770
5871 var prefixAndAttribs = [];
59 if (this._prefix) {
60 prefixAndAttribs.push(this._prefix);
72
73 if (this.options.prefix) {
74 prefixAndAttribs.push(this.options.prefix);
6175 }
6276 if (attribs.length) {
6377 prefixAndAttribs.push(attribs.join(', '));
6478 }
6579
66 this._container.innerHTML = prefixAndAttribs.join(' &mdash; ');
80 this._container.innerHTML = prefixAndAttribs.join(' &#8212; ');
81 },
82
83 _onLayerAdd: function (e) {
84 if (e.layer.getAttribution) {
85 this.addAttribution(e.layer.getAttribution());
86 }
87 },
88
89 _onLayerRemove: function (e) {
90 if (e.layer.getAttribution) {
91 this.removeAttribution(e.layer.getAttribution());
92 }
6793 }
6894 });
95
96 L.Map.mergeOptions({
97 attributionControl: true
98 });
99
100 L.Map.addInitHook(function () {
101 if (this.options.attributionControl) {
102 this.attributionControl = (new L.Control.Attribution()).addTo(this);
103 }
104 });
105
106 L.control.attribution = function (options) {
107 return new L.Control.Attribution(options);
108 };
00
1 L.Control.Layers = L.Class.extend({
1 L.Control.Layers = L.Control.extend({
22 options: {
3 collapsed: true
3 collapsed: true,
4 position: 'topright',
5 autoZIndex: true
46 },
57
68 initialize: function (baseLayers, overlays, options) {
79 L.Util.setOptions(this, options);
810
911 this._layers = {};
12 this._lastZIndex = 0;
1013
1114 for (var i in baseLayers) {
1215 if (baseLayers.hasOwnProperty(i)) {
2225 },
2326
2427 onAdd: function (map) {
25 this._map = map;
26
2728 this._initLayout();
2829 this._update();
29 },
3030
31 getContainer: function () {
3231 return this._container;
33 },
34
35 getPosition: function () {
36 return L.Control.Position.TOP_RIGHT;
3732 },
3833
3934 addBaseLayer: function (layer, name) {
5651 },
5752
5853 _initLayout: function () {
59 this._container = L.DomUtil.create('div', 'leaflet-control-layers');
54 var className = 'leaflet-control-layers',
55 container = this._container = L.DomUtil.create('div', className);
56
6057 if (!L.Browser.touch) {
61 L.DomEvent.disableClickPropagation(this._container);
58 L.DomEvent.disableClickPropagation(container);
59 } else {
60 L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
6261 }
6362
64 this._form = L.DomUtil.create('form', 'leaflet-control-layers-list');
63 var form = this._form = L.DomUtil.create('form', className + '-list');
6564
6665 if (this.options.collapsed) {
67 L.DomEvent.addListener(this._container, 'mouseover', this._expand, this);
68 L.DomEvent.addListener(this._container, 'mouseout', this._collapse, this);
66 L.DomEvent
67 .on(container, 'mouseover', this._expand, this)
68 .on(container, 'mouseout', this._collapse, this);
6969
70 var link = this._layersLink = L.DomUtil.create('a', 'leaflet-control-layers-toggle');
70 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
7171 link.href = '#';
7272 link.title = 'Layers';
7373
7474 if (L.Browser.touch) {
75 L.DomEvent.addListener(link, 'click', this._expand, this);
76 //L.DomEvent.disableClickPropagation(link);
77 } else {
78 L.DomEvent.addListener(link, 'focus', this._expand, this);
75 L.DomEvent
76 .on(link, 'click', L.DomEvent.stopPropagation)
77 .on(link, 'click', L.DomEvent.preventDefault)
78 .on(link, 'click', this._expand, this);
7979 }
80 else {
81 L.DomEvent.on(link, 'focus', this._expand, this);
82 }
83
8084 this._map.on('movestart', this._collapse, this);
8185 // TODO keyboard accessibility
82
83 this._container.appendChild(link);
8486 } else {
8587 this._expand();
8688 }
8789
88 this._baseLayersList = L.DomUtil.create('div', 'leaflet-control-layers-base', this._form);
89 this._separator = L.DomUtil.create('div', 'leaflet-control-layers-separator', this._form);
90 this._overlaysList = L.DomUtil.create('div', 'leaflet-control-layers-overlays', this._form);
90 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
91 this._separator = L.DomUtil.create('div', className + '-separator', form);
92 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
9193
92 this._container.appendChild(this._form);
94 container.appendChild(form);
9395 },
9496
9597 _addLayer: function (layer, name, overlay) {
9698 var id = L.Util.stamp(layer);
99
97100 this._layers[id] = {
98101 layer: layer,
99102 name: name,
100103 overlay: overlay
101104 };
105
106 if (this.options.autoZIndex && layer.setZIndex) {
107 this._lastZIndex++;
108 layer.setZIndex(this._lastZIndex);
109 }
102110 },
103111
104112 _update: function () {
132140 input.name = 'leaflet-base-layers';
133141 }
134142 input.type = obj.overlay ? 'checkbox' : 'radio';
135 input.checked = this._map.hasLayer(obj.layer);
136143 input.layerId = L.Util.stamp(obj.layer);
144 input.defaultChecked = this._map.hasLayer(obj.layer);
137145
138 L.DomEvent.addListener(input, 'click', this._onInputClick, this);
146 L.DomEvent.on(input, 'click', this._onInputClick, this);
139147
140148 var name = document.createTextNode(' ' + obj.name);
141149
171179 this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
172180 }
173181 });
182
183 L.control.layers = function (baseLayers, overlays, options) {
184 return new L.Control.Layers(baseLayers, overlays, options);
185 };
0 L.Control.Scale = L.Control.extend({
1 options: {
2 position: 'bottomleft',
3 maxWidth: 100,
4 metric: true,
5 imperial: true,
6 updateWhenIdle: false
7 },
8
9 onAdd: function (map) {
10 this._map = map;
11
12 var className = 'leaflet-control-scale',
13 container = L.DomUtil.create('div', className),
14 options = this.options;
15
16 this._addScales(options, className, container);
17
18 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
19 this._update();
20
21 return container;
22 },
23
24 onRemove: function (map) {
25 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
26 },
27
28 _addScales: function (options, className, container) {
29 if (options.metric) {
30 this._mScale = L.DomUtil.create('div', className + '-line', container);
31 }
32 if (options.imperial) {
33 this._iScale = L.DomUtil.create('div', className + '-line', container);
34 }
35 },
36
37 _update: function () {
38 var bounds = this._map.getBounds(),
39 centerLat = bounds.getCenter().lat,
40 halfWorldMeters = new L.LatLng(centerLat, 0).distanceTo(new L.LatLng(centerLat, 180)),
41 dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
42
43 size = this._map.getSize(),
44 options = this.options,
45 maxMeters = 0;
46
47 if (size.x > 0) {
48 maxMeters = dist * (options.maxWidth / size.x);
49 }
50
51 this._updateScales(options, maxMeters);
52 },
53
54 _updateScales: function (options, maxMeters) {
55 if (options.metric && maxMeters) {
56 this._updateMetric(maxMeters);
57 }
58
59 if (options.imperial && maxMeters) {
60 this._updateImperial(maxMeters);
61 }
62 },
63
64 _updateMetric: function (maxMeters) {
65 var meters = this._getRoundNum(maxMeters);
66
67 this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
68 this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
69 },
70
71 _updateImperial: function (maxMeters) {
72 var maxFeet = maxMeters * 3.2808399,
73 scale = this._iScale,
74 maxMiles, miles, feet;
75
76 if (maxFeet > 5280) {
77 maxMiles = maxFeet / 5280;
78 miles = this._getRoundNum(maxMiles);
79
80 scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
81 scale.innerHTML = miles + ' mi';
82
83 } else {
84 feet = this._getRoundNum(maxFeet);
85
86 scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
87 scale.innerHTML = feet + ' ft';
88 }
89 },
90
91 _getScaleWidth: function (ratio) {
92 return Math.round(this.options.maxWidth * ratio) - 10;
93 },
94
95 _getRoundNum: function (num) {
96 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
97 d = num / pow10;
98
99 d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
100
101 return pow10 * d;
102 }
103 });
104
105 L.control.scale = function (options) {
106 return new L.Control.Scale(options);
107 };
0
1 L.Control.Zoom = L.Class.extend({
2 onAdd: function (map) {
3 this._map = map;
4 this._container = L.DomUtil.create('div', 'leaflet-control-zoom');
5
6 this._zoomInButton = this._createButton(
7 'Zoom in', 'leaflet-control-zoom-in', this._map.zoomIn, this._map);
8 this._zoomOutButton = this._createButton(
9 'Zoom out', 'leaflet-control-zoom-out', this._map.zoomOut, this._map);
10
11 this._container.appendChild(this._zoomInButton);
12 this._container.appendChild(this._zoomOutButton);
0 L.Control.Zoom = L.Control.extend({
1 options: {
2 position: 'topleft'
133 },
144
15 getContainer: function () {
16 return this._container;
5 onAdd: function (map) {
6 var className = 'leaflet-control-zoom',
7 container = L.DomUtil.create('div', className);
8
9 this._createButton('Zoom in', className + '-in', container, map.zoomIn, map);
10 this._createButton('Zoom out', className + '-out', container, map.zoomOut, map);
11
12 return container;
1713 },
1814
19 getPosition: function () {
20 return L.Control.Position.TOP_LEFT;
21 },
22
23 _createButton: function (title, className, fn, context) {
24 var link = document.createElement('a');
15 _createButton: function (title, className, container, fn, context) {
16 var link = L.DomUtil.create('a', className, container);
2517 link.href = '#';
2618 link.title = title;
27 link.className = className;
2819
29 if (!L.Browser.touch) {
30 L.DomEvent.disableClickPropagation(link);
31 }
32 L.DomEvent.addListener(link, 'click', L.DomEvent.preventDefault);
33 L.DomEvent.addListener(link, 'click', fn, context);
20 L.DomEvent
21 .on(link, 'click', L.DomEvent.stopPropagation)
22 .on(link, 'click', L.DomEvent.preventDefault)
23 .on(link, 'click', fn, context)
24 .on(link, 'dblclick', L.DomEvent.stopPropagation);
3425
3526 return link;
3627 }
3728 });
29
30 L.Map.mergeOptions({
31 zoomControl: true
32 });
33
34 L.Map.addInitHook(function () {
35 if (this.options.zoomControl) {
36 this.zoomControl = new L.Control.Zoom();
37 this.addControl(this.zoomControl);
38 }
39 });
40
41 L.control.zoom = function (options) {
42 return new L.Control.Zoom(options);
43 };
00
1 L.Control = {};
1 L.Control = L.Class.extend({
2 options: {
3 position: 'topright'
4 },
25
3 L.Control.Position = {
4 TOP_LEFT: 'topLeft',
5 TOP_RIGHT: 'topRight',
6 BOTTOM_LEFT: 'bottomLeft',
7 BOTTOM_RIGHT: 'bottomRight'
6 initialize: function (options) {
7 L.Util.setOptions(this, options);
8 },
9
10 getPosition: function () {
11 return this.options.position;
12 },
13
14 setPosition: function (position) {
15 var map = this._map;
16
17 if (map) {
18 map.removeControl(this);
19 }
20
21 this.options.position = position;
22
23 if (map) {
24 map.addControl(this);
25 }
26
27 return this;
28 },
29
30 addTo: function (map) {
31 this._map = map;
32
33 var container = this._container = this.onAdd(map),
34 pos = this.getPosition(),
35 corner = map._controlCorners[pos];
36
37 L.DomUtil.addClass(container, 'leaflet-control');
38
39 if (pos.indexOf('bottom') !== -1) {
40 corner.insertBefore(container, corner.firstChild);
41 } else {
42 corner.appendChild(container);
43 }
44
45 return this;
46 },
47
48 removeFrom: function (map) {
49 var pos = this.getPosition(),
50 corner = map._controlCorners[pos];
51
52 corner.removeChild(this._container);
53 this._map = null;
54
55 if (this.onRemove) {
56 this.onRemove(map);
57 }
58
59 return this;
60 }
61 });
62
63 L.control = function (options) {
64 return new L.Control(options);
865 };
00 (function () {
11 var ua = navigator.userAgent.toLowerCase(),
22 ie = !!window.ActiveXObject,
3 ie6 = ie && !window.XMLHttpRequest,
34 webkit = ua.indexOf("webkit") !== -1,
4 mobile = typeof orientation !== 'undefined' ? true : false,
5 gecko = ua.indexOf("gecko") !== -1,
6 //Terrible browser detection to work around a safari / iOS / android browser bug. See TileLayer._addTile and debug/hacks/jitter.html
7 chrome = ua.indexOf("chrome") !== -1,
8 opera = window.opera,
59 android = ua.indexOf("android") !== -1,
6 opera = window.opera;
10 android23 = ua.search("android [23]") !== -1,
11 mobile = typeof orientation !== undefined + '' ? true : false,
12 doc = document.documentElement,
13 ie3d = ie && ('transition' in doc.style),
14 webkit3d = webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
15 gecko3d = gecko && ('MozPerspective' in doc.style),
16 opera3d = opera && ('OTransition' in doc.style);
17
18 var touch = !window.L_NO_TOUCH && (function () {
19 var startName = 'ontouchstart';
20
21 // WebKit, etc
22 if (startName in doc) {
23 return true;
24 }
25
26 // Firefox/Gecko
27 var div = document.createElement('div'),
28 supported = false;
29
30 if (!div.setAttribute) {
31 return false;
32 }
33 div.setAttribute(startName, 'return;');
34
35 if (typeof div[startName] === 'function') {
36 supported = true;
37 }
38
39 div.removeAttribute(startName);
40 div = null;
41
42 return supported;
43 }());
744
845 L.Browser = {
46 ua: ua,
947 ie: ie,
10 ie6: ie && !window.XMLHttpRequest,
48 ie6: ie6,
49 webkit: webkit,
50 gecko: gecko,
51 opera: opera,
52 android: android,
53 android23: android23,
1154
12 webkit: webkit,
13 webkit3d: webkit && ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()),
55 chrome: chrome,
1456
15 gecko: ua.indexOf("gecko") !== -1,
57 ie3d: ie3d,
58 webkit3d: webkit3d,
59 gecko3d: gecko3d,
60 opera3d: opera3d,
61 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d),
1662
17 opera: opera,
18
19 android: android,
63 mobile: mobile,
2064 mobileWebkit: mobile && webkit,
65 mobileWebkit3d: mobile && webkit3d,
2166 mobileOpera: mobile && opera,
2267
23 mobile: mobile,
24 touch: (function () {
25 var touchSupported = false,
26 startName = 'ontouchstart';
27
28 // WebKit, etc
29 if (startName in document.documentElement) {
30 return true;
31 }
32
33 // Firefox/Gecko
34 var e = document.createElement('div');
35
36 // If no support for basic event stuff, unlikely to have touch support
37 if (!e.setAttribute || !e.removeAttribute) {
38 return false;
39 }
40
41 e.setAttribute(startName, 'return;');
42 if (typeof e[startName] === 'function') {
43 touchSupported = true;
44 }
45
46 e.removeAttribute(startName);
47 e = null;
48
49 return touchSupported;
50 }())
68 touch: touch
5169 };
5270 }());
1515 // instantiate class without calling constructor
1616 var F = function () {};
1717 F.prototype = this.prototype;
18
1819 var proto = new F();
20 proto.constructor = NewClass;
1921
20 proto.constructor = NewClass;
2122 NewClass.prototype = proto;
22
23 // add superclass access
24 NewClass.superclass = this.prototype;
25
26 // add class name
27 //proto.className = props;
2823
2924 //inherit parent's statics
3025 for (var i in this) {
31 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== 'superclass') {
26 if (this.hasOwnProperty(i) && i !== 'prototype') {
3227 NewClass[i] = this[i];
3328 }
3429 }
5348 // mix given properties into the prototype
5449 L.Util.extend(proto, props);
5550
56 // allow inheriting further
57 NewClass.extend = L.Class.extend;
58
59 // method for adding properties to prototype
60 NewClass.include = function (props) {
61 L.Util.extend(this.prototype, props);
62 };
63
6451 return NewClass;
6552 };
53
54
55 // method for adding properties to prototype
56 L.Class.include = function (props) {
57 L.Util.extend(this.prototype, props);
58 };
59
60 L.Class.mergeOptions = function (options) {
61 L.Util.extend(this.prototype.options, options);
62 };
11 * L.Mixin.Events adds custom events functionality to Leaflet classes
22 */
33
4 var key = '_leaflet_events';
5
46 L.Mixin = {};
57
68 L.Mixin.Events = {
7 addEventListener: function (/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
8 var events = this._leaflet_events = this._leaflet_events || {};
9 events[type] = events[type] || [];
10 events[type].push({
11 action: fn,
12 context: context || this
13 });
9
10 addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
11 var events = this[key] = this[key] || {},
12 type, i, len;
13
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 }
21
22 return this;
23 }
24
25 types = L.Util.splitWords(types);
26
27 for (i = 0, len = types.length; i < len; i++) {
28 events[types[i]] = events[types[i]] || [];
29 events[types[i]].push({
30 action: fn,
31 context: context || this
32 });
33 }
34
1435 return this;
1536 },
1637
17 hasEventListeners: function (/*String*/ type) /*-> Boolean*/ {
18 var k = '_leaflet_events';
19 return (k in this) && (type in this[k]) && (this[k][type].length > 0);
38 hasEventListeners: function (type) { // (String) -> Boolean
39 return (key in this) && (type in this[key]) && (this[key][type].length > 0);
2040 },
2141
22 removeEventListener: function (/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
23 if (!this.hasEventListeners(type)) {
42 removeEventListener: function (types, fn, context) { // (String[, Function, Object]) or (Object[, Object])
43 var events = this[key],
44 type, i, len, listeners, j;
45
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
2453 return this;
2554 }
55
56 types = L.Util.splitWords(types);
2657
27 for (var i = 0, events = this._leaflet_events, len = events[type].length; i < len; i++) {
28 if (
29 (events[type][i].action === fn) &&
30 (!context || (events[type][i].context === context))
31 ) {
32 events[type].splice(i, 1);
33 return this;
58 for (i = 0, len = types.length; i < len; i++) {
59
60 if (this.hasEventListeners(types[i])) {
61 listeners = events[types[i]];
62
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);
69 }
70 }
3471 }
3572 }
73
3674 return this;
3775 },
3876
39 fireEvent: function (/*String*/ type, /*(optional) Object*/ data) {
77 fireEvent: function (type, data) { // (String[, Object])
4078 if (!this.hasEventListeners(type)) {
4179 return this;
4280 }
4684 target: this
4785 }, data);
4886
49 var listeners = this._leaflet_events[type].slice();
87 var listeners = this[key][type].slice();
5088
5189 for (var i = 0, len = listeners.length; i < len; i++) {
5290 listeners[i].action.call(listeners[i].context || this, event);
77 },
88
99 enable: function () {
10 if (this._enabled) {
11 return;
12 }
10 if (this._enabled) { return; }
11
1312 this._enabled = true;
1413 this.addHooks();
1514 },
1615
1716 disable: function () {
18 if (!this._enabled) {
19 return;
20 }
17 if (!this._enabled) { return; }
18
2119 this._enabled = false;
2220 this.removeHooks();
2321 },
1515 return dest;
1616 },
1717
18 bind: function (/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
18 bind: function (fn, obj) { // (Function, Object) -> Function
19 var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
1920 return function () {
20 return fn.apply(obj, arguments);
21 return fn.apply(obj, args || arguments);
2122 };
2223 },
2324
2930 };
3031 }()),
3132
32 requestAnimFrame: (function () {
33 function timeoutDefer(callback) {
34 window.setTimeout(callback, 1000 / 60);
35 }
33 limitExecByInterval: function (fn, time, context) {
34 var lock, execOnUnlock;
3635
37 var requestFn = window.requestAnimationFrame ||
38 window.webkitRequestAnimationFrame ||
39 window.mozRequestAnimationFrame ||
40 window.oRequestAnimationFrame ||
41 window.msRequestAnimationFrame ||
42 timeoutDefer;
36 return function wrapperFn() {
37 var args = arguments;
4338
44 return function (callback, context, immediate, contextEl) {
45 callback = context ? L.Util.bind(callback, context) : callback;
46 if (immediate && requestFn === timeoutDefer) {
47 callback();
48 } else {
49 requestFn(callback, contextEl);
39 if (lock) {
40 execOnUnlock = true;
41 return;
5042 }
51 };
52 }()),
5343
54 limitExecByInterval: function (fn, time, context) {
55 var lock, execOnUnlock, args;
56 function exec() {
57 lock = false;
58 if (execOnUnlock) {
59 args.callee.apply(context, args);
60 execOnUnlock = false;
61 }
62 }
63 return function () {
64 args = arguments;
65 if (!lock) {
66 lock = true;
67 setTimeout(exec, time);
68 fn.apply(context, args);
69 } else {
70 execOnUnlock = true;
71 }
44 lock = true;
45
46 setTimeout(function () {
47 lock = false;
48
49 if (execOnUnlock) {
50 wrapperFn.apply(context, args);
51 execOnUnlock = false;
52 }
53 }, time);
54
55 fn.apply(context, args);
7256 };
7357 },
7458
8165 return Math.round(num * pow) / pow;
8266 },
8367
68 splitWords: function (str) {
69 return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
70 },
71
8472 setOptions: function (obj, options) {
8573 obj.options = L.Util.extend({}, obj.options, options);
74 return obj.options;
8675 },
8776
8877 getParamString: function (obj) {
10392 }
10493 return value;
10594 });
95 },
96
97 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
98 };
99
100 (function () {
101
102 function getPrefixed(name) {
103 var i, fn,
104 prefixes = ['webkit', 'moz', 'o', 'ms'];
105
106 for (i = 0; i < prefixes.length && !fn; i++) {
107 fn = window[prefixes[i] + name];
108 }
109
110 return fn;
106111 }
107 };
112
113 function timeoutDefer(fn) {
114 return window.setTimeout(fn, 1000 / 60);
115 }
116
117 var requestFn = window.requestAnimationFrame ||
118 getPrefixed('RequestAnimationFrame') || timeoutDefer;
119
120 var cancelFn = window.cancelAnimationFrame ||
121 getPrefixed('CancelAnimationFrame') ||
122 getPrefixed('CancelRequestAnimationFrame') ||
123 function (id) {
124 window.clearTimeout(id);
125 };
126
127
128 L.Util.requestAnimFrame = function (fn, context, immediate, element) {
129 fn = L.Util.bind(fn, context);
130
131 if (immediate && requestFn === timeoutDefer) {
132 fn();
133 } else {
134 return requestFn.call(window, fn, element);
135 }
136 };
137
138 L.Util.cancelAnimFrame = function (id) {
139 if (id) {
140 cancelFn.call(window, id);
141 }
142 };
143
144 }());
3232
3333 obj.addEventListener(touchstart, onTouchStart, false);
3434 obj.addEventListener(touchend, onTouchEnd, false);
35 return this;
3536 },
3637
3738 removeDoubleTapListener: function (obj, id) {
3839 var pre = '_leaflet_';
3940 obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false);
4041 obj.removeEventListener(obj, obj[pre + 'touchend' + id], false);
42 return this;
4143 }
4244 });
33
44 L.DomEvent = {
55 /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
6 addListener: function (/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn, /*Object*/ context) {
6 addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
7
78 var id = L.Util.stamp(fn),
8 key = '_leaflet_' + type + id;
9 key = '_leaflet_' + type + id,
10 handler, originalHandler, newType;
911
10 if (obj[key]) {
11 return;
12 }
12 if (obj[key]) { return this; }
1313
14 var handler = function (e) {
14 handler = function (e) {
1515 return fn.call(context || obj, e || L.DomEvent._getEvent());
1616 };
1717
1818 if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
19 this.addDoubleTapListener(obj, handler, id);
19 return this.addDoubleTapListener(obj, handler, id);
20
2021 } else if ('addEventListener' in obj) {
22
2123 if (type === 'mousewheel') {
2224 obj.addEventListener('DOMMouseScroll', handler, false);
2325 obj.addEventListener(type, handler, false);
26
2427 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
25 var originalHandler = handler,
26 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
28
29 originalHandler = handler;
30 newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
31
2732 handler = function (e) {
28 if (!L.DomEvent._checkMouse(obj, e)) {
29 return;
30 }
33 if (!L.DomEvent._checkMouse(obj, e)) { return; }
3134 return originalHandler(e);
3235 };
36
3337 obj.addEventListener(newType, handler, false);
38
3439 } else {
3540 obj.addEventListener(type, handler, false);
3641 }
42
3743 } else if ('attachEvent' in obj) {
3844 obj.attachEvent("on" + type, handler);
3945 }
4046
4147 obj[key] = handler;
48
49 return this;
4250 },
4351
44 removeListener: function (/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn) {
52 removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
53
4554 var id = L.Util.stamp(fn),
4655 key = '_leaflet_' + type + id,
4756 handler = obj[key];
4857
49 if (!handler) {
50 return;
51 }
58 if (!handler) { return; }
5259
5360 if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
5461 this.removeDoubleTapListener(obj, id);
62
5563 } else if ('removeEventListener' in obj) {
64
5665 if (type === 'mousewheel') {
5766 obj.removeEventListener('DOMMouseScroll', handler, false);
5867 obj.removeEventListener(type, handler, false);
68
5969 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
6070 obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
6171 } else {
6474 } else if ('detachEvent' in obj) {
6575 obj.detachEvent("on" + type, handler);
6676 }
77
6778 obj[key] = null;
79
80 return this;
6881 },
6982
83 stopPropagation: function (e) {
84
85 if (e.stopPropagation) {
86 e.stopPropagation();
87 } else {
88 e.cancelBubble = true;
89 }
90 return this;
91 },
92
93 disableClickPropagation: function (el) {
94
95 var stop = L.DomEvent.stopPropagation;
96
97 return L.DomEvent
98 .addListener(el, L.Draggable.START, stop)
99 .addListener(el, 'click', stop)
100 .addListener(el, 'dblclick', stop);
101 },
102
103 preventDefault: function (e) {
104
105 if (e.preventDefault) {
106 e.preventDefault();
107 } else {
108 e.returnValue = false;
109 }
110 return this;
111 },
112
113 stop: function (e) {
114 return L.DomEvent.preventDefault(e).stopPropagation(e);
115 },
116
117 getMousePosition: function (e, container) {
118
119 var body = document.body,
120 docEl = document.documentElement,
121 x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft,
122 y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop,
123 pos = new L.Point(x, y);
124
125 return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos);
126 },
127
128 getWheelDelta: function (e) {
129
130 var delta = 0;
131
132 if (e.wheelDelta) {
133 delta = e.wheelDelta / 120;
134 }
135 if (e.detail) {
136 delta = -e.detail / 3;
137 }
138 return delta;
139 },
140
141 // check if element really left/entered the event target (for mouseenter/mouseleave)
70142 _checkMouse: function (el, e) {
143
71144 var related = e.relatedTarget;
72145
73 if (!related) {
74 return true;
75 }
146 if (!related) { return true; }
76147
77148 try {
78149 while (related && (related !== el)) {
81152 } catch (err) {
82153 return false;
83154 }
84
85155 return (related !== el);
86156 },
87157
88 /*jshint noarg:false */ // evil magic for IE
89 _getEvent: function () {
158 /*jshint noarg:false */
159 _getEvent: function () { // evil magic for IE
160
90161 var e = window.event;
91162 if (!e) {
92163 var caller = arguments.callee.caller;
99170 }
100171 }
101172 return e;
102 },
173 }
103174 /*jshint noarg:false */
104
105 stopPropagation: function (/*Event*/ e) {
106 if (e.stopPropagation) {
107 e.stopPropagation();
108 } else {
109 e.cancelBubble = true;
110 }
111 },
112
113 disableClickPropagation: function (/*HTMLElement*/ el) {
114 L.DomEvent.addListener(el, L.Draggable.START, L.DomEvent.stopPropagation);
115 L.DomEvent.addListener(el, 'click', L.DomEvent.stopPropagation);
116 L.DomEvent.addListener(el, 'dblclick', L.DomEvent.stopPropagation);
117 },
118
119 preventDefault: function (/*Event*/ e) {
120 if (e.preventDefault) {
121 e.preventDefault();
122 } else {
123 e.returnValue = false;
124 }
125 },
126
127 stop: function (e) {
128 L.DomEvent.preventDefault(e);
129 L.DomEvent.stopPropagation(e);
130 },
131
132 getMousePosition: function (e, container) {
133 var x = e.pageX ? e.pageX : e.clientX +
134 document.body.scrollLeft + document.documentElement.scrollLeft,
135 y = e.pageY ? e.pageY : e.clientY +
136 document.body.scrollTop + document.documentElement.scrollTop,
137 pos = new L.Point(x, y);
138 return (container ?
139 pos.subtract(L.DomUtil.getViewportOffset(container)) : pos);
140 },
141
142 getWheelDelta: function (e) {
143 var delta = 0;
144 if (e.wheelDelta) {
145 delta = e.wheelDelta / 120;
146 }
147 if (e.detail) {
148 delta = -e.detail / 3;
149 }
150 return delta;
151 }
152175 };
153176
177 L.DomEvent.on = L.DomEvent.addListener;
178 L.DomEvent.off = L.DomEvent.removeListener;
3232 L.DomUtil.getStyle(el, 'position') === 'absolute') {
3333 break;
3434 }
35 if (L.DomUtil.getStyle(el, 'position') === 'fixed') {
36 top += docBody.scrollTop || 0;
37 left += docBody.scrollLeft || 0;
38 break;
39 }
40
3541 el = el.offsetParent;
3642 } while (el);
3743
8793 },
8894
8995 removeClass: function (el, name) {
90 el.className = el.className.replace(/(\S+)\s*/g, function (w, match) {
96 function replaceFn(w, match) {
9197 if (match === name) {
9298 return '';
9399 }
94100 return w;
95 }).replace(/^\s+/, '');
101 }
102 el.className = el.className
103 .replace(/(\S+)\s*/g, replaceFn)
104 .replace(/(^\s+|\s+$)/, '');
96105 },
97106
98107 setOpacity: function (el, value) {
99 if (L.Browser.ie) {
100 el.style.filter = 'alpha(opacity=' + Math.round(value * 100) + ')';
101 } else {
108
109 if ('opacity' in el.style) {
102110 el.style.opacity = value;
111
112 } else if (L.Browser.ie) {
113
114 var filter = false,
115 filterName = 'DXImageTransform.Microsoft.Alpha';
116
117 // filters collection throws an error if we try to retrieve a filter that doesn't exist
118 try { filter = el.filters.item(filterName); } catch (e) {}
119
120 value = Math.round(value * 100);
121
122 if (filter) {
123 filter.Enabled = (value === 100);
124 filter.Opacity = value;
125 } else {
126 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
127 }
103128 }
104129 },
105
106 //TODO refactor away this ugly translate/position mess
107130
108131 testProp: function (props) {
109132 var style = document.documentElement.style;
117140 },
118141
119142 getTranslateString: function (point) {
120 return L.DomUtil.TRANSLATE_OPEN +
121 point.x + 'px,' + point.y + 'px' +
122 L.DomUtil.TRANSLATE_CLOSE;
143 // On webkit browsers (Chrome/Safari/MobileSafari/Android) using translate3d instead of translate
144 // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
145 // (same speed either way), Opera 12 doesn't support translate3d
146
147 var is3d = L.Browser.webkit3d,
148 open = 'translate' + (is3d ? '3d' : '') + '(',
149 close = (is3d ? ',0' : '') + ')';
150
151 return open + point.x + 'px,' + point.y + 'px' + close;
123152 },
124153
125154 getScaleString: function (scale, origin) {
130159 return preTranslateStr + scaleStr + postTranslateStr;
131160 },
132161
133 setPosition: function (el, point) {
162 setPosition: function (el, point, disable3D) {
134163 el._leaflet_pos = point;
135 if (L.Browser.webkit3d) {
164 if (!disable3D && L.Browser.any3d) {
136165 el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
137166
138 if (L.Browser.android) {
139 el.style['-webkit-perspective'] = '1000';
140 el.style['-webkit-backface-visibility'] = 'hidden';
167 // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69)
168 if (L.Browser.mobileWebkit3d) {
169 el.style.WebkitBackfaceVisibility = 'hidden';
141170 }
142171 } else {
143172 el.style.left = point.x + 'px';
152181
153182 L.Util.extend(L.DomUtil, {
154183 TRANSITION: L.DomUtil.testProp(['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']),
155 TRANSFORM: L.DomUtil.testProp(['transformProperty', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']),
156
157 TRANSLATE_OPEN: 'translate' + (L.Browser.webkit3d ? '3d(' : '('),
158 TRANSLATE_CLOSE: L.Browser.webkit3d ? ',0)' : ')'
184 TRANSFORM: L.DomUtil.testProp(['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform'])
159185 });
2020 if (this._enabled) {
2121 return;
2222 }
23 L.DomEvent.addListener(this._dragStartTarget, L.Draggable.START, this._onDown, this);
23 L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
2424 this._enabled = true;
2525 },
2626
2828 if (!this._enabled) {
2929 return;
3030 }
31 L.DomEvent.removeListener(this._dragStartTarget, L.Draggable.START, this._onDown);
31 L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
3232 this._enabled = false;
33 this._moved = false;
3334 },
3435
3536 _onDown: function (e) {
3738 return;
3839 }
3940
41 this._simulateClick = true;
42
4043 if (e.touches && e.touches.length > 1) {
44 this._simulateClick = false;
4145 return;
4246 }
4347
4751 L.DomEvent.preventDefault(e);
4852
4953 if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
50 el.className += ' leaflet-active';
54 L.DomUtil.addClass(el, 'leaflet-active');
5155 }
5256
5357 this._moved = false;
6367 this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
6468 this._startPoint = new L.Point(first.clientX, first.clientY);
6569
66 L.DomEvent.addListener(document, L.Draggable.MOVE, this._onMove, this);
67 L.DomEvent.addListener(document, L.Draggable.END, this._onUp, this);
70 L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
71 L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
6872 },
6973
7074 _onMove: function (e) {
71 if (e.touches && e.touches.length > 1) {
72 return;
73 }
75 if (e.touches && e.touches.length > 1) { return; }
76
77 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
78 newPoint = new L.Point(first.clientX, first.clientY),
79 diffVec = newPoint.subtract(this._startPoint);
80
81 if (!diffVec.x && !diffVec.y) { return; }
7482
7583 L.DomEvent.preventDefault(e);
76
77 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e);
7884
7985 if (!this._moved) {
8086 this.fire('dragstart');
8187 this._moved = true;
8288 }
89
90 this._newPos = this._startPos.add(diffVec);
8391 this._moving = true;
8492
85 var newPoint = new L.Point(first.clientX, first.clientY);
86 this._newPos = this._startPos.add(newPoint).subtract(this._startPoint);
87
88 L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
93 L.Util.cancelAnimFrame(this._animRequest);
94 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
8995 },
9096
9197 _updatePosition: function () {
95101 },
96102
97103 _onUp: function (e) {
98 if (e.changedTouches) {
104 if (this._simulateClick && e.changedTouches) {
99105 var first = e.changedTouches[0],
100106 el = first.target,
101107 dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
102108
103109 if (el.tagName.toLowerCase() === 'a') {
104 el.className = el.className.replace(' leaflet-active', '');
110 L.DomUtil.removeClass(el, 'leaflet-active');
105111 }
106112
107113 if (dist < L.Draggable.TAP_TOLERANCE) {
114120 this._restoreCursor();
115121 }
116122
117 L.DomEvent.removeListener(document, L.Draggable.MOVE, this._onMove);
118 L.DomEvent.removeListener(document, L.Draggable.END, this._onUp);
123 L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
124 L.DomEvent.off(document, L.Draggable.END, this._onUp);
119125
120126 if (this._moved) {
127 // ensure drag is not fired after dragend
128 L.Util.cancelAnimFrame(this._animRequest);
129
121130 this.fire('dragend');
122131 }
123132 this._moving = false;
124133 },
125134
126135 _setMovingCursor: function () {
127 this._bodyCursor = document.body.style.cursor;
128 document.body.style.cursor = 'move';
136 L.DomUtil.addClass(document.body, 'leaflet-dragging');
129137 },
130138
131139 _restoreCursor: function () {
132 document.body.style.cursor = this._bodyCursor;
140 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
133141 },
134142
135143 _simulateEvent: function (type, e) {
1919
2020 // transition-property value to use with each particular custom property
2121 CUSTOM_PROPS_PROPERTIES: {
22 position: L.Browser.webkit ? L.DomUtil.TRANSFORM : 'top, left'
22 position: L.Browser.any3d ? L.DomUtil.TRANSFORM : 'top, left'
2323 }
2424 };
2525 }()),
3232 this._el = el;
3333 L.Util.setOptions(this, options);
3434
35 L.DomEvent.addListener(el, L.Transition.END, this._onTransitionEnd, this);
35 L.DomEvent.on(el, L.Transition.END, this._onTransitionEnd, this);
3636 this._onFakeStep = L.Util.bind(this._onFakeStep, this);
3737 },
3838
6363
6464 this.fire('start');
6565
66 if (L.Browser.mobileWebkit) {
67 // Set up a slightly delayed call to a backup event if webkitTransitionEnd doesn't fire properly
68 this.backupEventFire = setTimeout(L.Util.bind(this._onBackupFireEnd, this), this.options.duration * 1.2 * 1000);
69 }
70
6671 if (L.Transition.NATIVE) {
6772 clearInterval(this._timer);
6873 this._timer = setInterval(this._onFakeStep, this.options.fakeStepInterval);
8792 this.fire('step');
8893 },
8994
90 _onTransitionEnd: function () {
95 _onTransitionEnd: function (e) {
9196 if (this._inProgress) {
9297 this._inProgress = false;
9398 clearInterval(this._timer);
9499
95 this._el.style[L.Transition.PROPERTY] = 'none';
100 this._el.style[L.Transition.TRANSITION] = '';
101
102 // Clear the delayed call to the backup event, we have recieved some form of webkitTransitionEnd
103 clearTimeout(this.backupEventFire);
104 delete this.backupEventFire;
96105
97106 this.fire('step');
98 this.fire('end');
107
108 if (e && e.type) {
109 this.fire('end');
110 }
99111 }
112 },
113
114 _onBackupFireEnd: function () {
115 // Create and fire a transitionEnd event on the element.
116
117 var event = document.createEvent("Event");
118 event.initEvent(L.Transition.END, true, false);
119 this._el.dispatchEvent(event);
100120 }
101121 });
1111 TIMER: true,
1212
1313 EASINGS: {
14 'ease': [0.25, 0.1, 0.25, 1.0],
15 'linear': [0.0, 0.0, 1.0, 1.0],
16 'ease-in': [0.42, 0, 1.0, 1.0],
17 'ease-out': [0, 0, 0.58, 1.0],
18 'ease-in-out': [0.42, 0, 0.58, 1.0]
14 'linear': function (t) { return t; },
15 'ease-out': function (t) { return t * (2 - t); }
1916 },
2017
2118 CUSTOM_PROPS_GETTERS: {
3431 this._el = el;
3532 L.Util.extend(this.options, options);
3633
37 var easings = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS.ease;
38
39 this._p1 = new L.Point(0, 0);
40 this._p2 = new L.Point(easings[0], easings[1]);
41 this._p3 = new L.Point(easings[2], easings[3]);
42 this._p4 = new L.Point(1, 1);
34 this._easing = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS['ease-out'];
4335
4436 this._step = L.Util.bind(this._step, this);
4537 this._interval = Math.round(1000 / this.options.fps);
7971 duration = this.options.duration * 1000;
8072
8173 if (elapsed < duration) {
82 this._runFrame(this._cubicBezier(elapsed / duration));
74 this._runFrame(this._easing(elapsed / duration));
8375 } else {
8476 this._runFrame(1);
8577 this._complete();
108100 _complete: function () {
109101 clearInterval(this._timer);
110102 this.fire('end');
111 },
112
113 _cubicBezier: function (t) {
114 var a = Math.pow(1 - t, 3),
115 b = 3 * Math.pow(1 - t, 2) * t,
116 c = 3 * (1 - t) * Math.pow(t, 2),
117 d = Math.pow(t, 3),
118 p1 = this._p1.multiplyBy(a),
119 p2 = this._p2.multiplyBy(b),
120 p3 = this._p3.multiplyBy(c),
121 p4 = this._p4.multiplyBy(d);
122
123 return p1.add(p2).add(p3).add(p4).y;
124103 }
125104 });
11 CM.LatLng represents a geographical point with latitude and longtitude coordinates.
22 */
33
4 L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
4 L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
55 var lat = parseFloat(rawLat),
66 lng = parseFloat(rawLng);
77
1414 lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
1515 }
1616
17 //TODO change to lat() & lng()
1817 this.lat = lat;
1918 this.lng = lng;
2019 };
2625 });
2726
2827 L.LatLng.prototype = {
29 equals: function (/*LatLng*/ obj) {
30 if (!(obj instanceof L.LatLng)) {
31 return false;
32 }
28 equals: function (obj) { // (LatLng) -> Boolean
29 if (!obj) { return false; }
30
31 obj = L.latLng(obj);
3332
3433 var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
3534 return margin <= L.LatLng.MAX_MARGIN;
3635 },
3736
38 toString: function () {
37 toString: function () { // -> String
3938 return 'LatLng(' +
4039 L.Util.formatNum(this.lat) + ', ' +
4140 L.Util.formatNum(this.lng) + ')';
4241 },
4342
4443 // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
45 distanceTo: function (/*LatLng*/ other)/*->Double*/ {
44 distanceTo: function (other) { // (LatLng) -> Number
45 other = L.latLng(other);
46
4647 var R = 6378137, // earth radius in meters
4748 d2r = L.LatLng.DEG_TO_RAD,
4849 dLat = (other.lat - this.lat) * d2r,
5758 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
5859 }
5960 };
61
62 L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
63 if (a instanceof L.LatLng) {
64 return a;
65 }
66 if (a instanceof Array) {
67 return new L.LatLng(a[0], a[1]);
68 }
69 if (isNaN(a)) {
70 return a;
71 }
72 return new L.LatLng(a, b, c);
73 };
74
33
44 L.LatLngBounds = L.Class.extend({
55 initialize: function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
6 if (!southWest) {
7 return;
8 }
9 var latlngs = (southWest instanceof Array ? southWest : [southWest, northEast]);
6 if (!southWest) { return; }
7
8 var latlngs = northEast ? [southWest, northEast] : southWest;
9
1010 for (var i = 0, len = latlngs.length; i < len; i++) {
1111 this.extend(latlngs[i]);
1212 }
1313 },
1414
15 // extend the bounds to contain the given point
16 extend: function (/*LatLng*/ latlng) {
17 if (!this._southWest && !this._northEast) {
18 this._southWest = new L.LatLng(latlng.lat, latlng.lng, true);
19 this._northEast = new L.LatLng(latlng.lat, latlng.lng, true);
15 // extend the bounds to contain the given point or bounds
16 extend: function (obj) { // (LatLng) or (LatLngBounds)
17 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
18 obj = L.latLng(obj);
2019 } else {
21 this._southWest.lat = Math.min(latlng.lat, this._southWest.lat);
22 this._southWest.lng = Math.min(latlng.lng, this._southWest.lng);
23 this._northEast.lat = Math.max(latlng.lat, this._northEast.lat);
24 this._northEast.lng = Math.max(latlng.lng, this._northEast.lng);
20 obj = L.latLngBounds(obj);
2521 }
22
23 if (obj instanceof L.LatLng) {
24 if (!this._southWest && !this._northEast) {
25 this._southWest = new L.LatLng(obj.lat, obj.lng, true);
26 this._northEast = new L.LatLng(obj.lat, obj.lng, true);
27 } else {
28 this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
29 this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
30
31 this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
32 this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
33 }
34 } else if (obj instanceof L.LatLngBounds) {
35 this.extend(obj._southWest);
36 this.extend(obj._northEast);
37 }
38 return this;
2639 },
2740
28 getCenter: function () /*-> LatLng*/ {
41 // extend the bounds by a percentage
42 pad: function (bufferRatio) { // (Number) -> LatLngBounds
43 var sw = this._southWest,
44 ne = this._northEast,
45 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
46 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
47
48 return new L.LatLngBounds(
49 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
50 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
51 },
52
53 getCenter: function () { // -> LatLng
2954 return new L.LatLng(
3055 (this._southWest.lat + this._northEast.lat) / 2,
3156 (this._southWest.lng + this._northEast.lng) / 2);
4772 return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
4873 },
4974
50 contains: function (/*LatLngBounds or LatLng*/ obj) /*-> Boolean*/ {
75 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
76 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
77 obj = L.latLng(obj);
78 } else {
79 obj = L.latLngBounds(obj);
80 }
81
5182 var sw = this._southWest,
5283 ne = this._northEast,
5384 sw2, ne2;
6394 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
6495 },
6596
66 intersects: function (/*LatLngBounds*/ bounds) {
97 intersects: function (bounds) { // (LatLngBounds)
98 bounds = L.latLngBounds(bounds);
99
67100 var sw = this._southWest,
68101 ne = this._northEast,
69102 sw2 = bounds.getSouthWest(),
79112 var sw = this._southWest,
80113 ne = this._northEast;
81114 return [sw.lng, sw.lat, ne.lng, ne.lat].join(',');
115 },
116
117 equals: function (bounds) { // (LatLngBounds)
118 if (!bounds) { return false; }
119
120 bounds = L.latLngBounds(bounds);
121
122 return this._southWest.equals(bounds.getSouthWest()) &&
123 this._northEast.equals(bounds.getNorthEast());
82124 }
83125 });
84126
85127 //TODO International date line?
128
129 L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
130 if (!a || a instanceof L.LatLngBounds) {
131 return a;
132 }
133 return new L.LatLngBounds(a, b);
134 };
22 code: 'EPSG:3395',
33
44 projection: L.Projection.Mercator,
5
56 transformation: (function () {
67 var m = L.Projection.Mercator,
78 r = m.R_MAJOR,
44 projection: L.Projection.SphericalMercator,
55 transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
66
7 project: function (/*LatLng*/ latlng)/*-> Point*/ {
7 project: function (latlng) { // (LatLng) -> Point
88 var projectedPoint = this.projection.project(latlng),
99 earthRadius = 6378137;
1010 return projectedPoint.multiplyBy(earthRadius);
0
1 L.CRS.Simple = L.Util.extend({}, L.CRS, {
2 projection: L.Projection.LonLat,
3 transformation: new L.Transformation(1, 0, 1, 0)
4 });
00
11 L.CRS = {
2 latLngToPoint: function (/*LatLng*/ latlng, /*Number*/ scale)/*-> Point*/ {
3 var projectedPoint = this.projection.project(latlng);
2 latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
3 var projectedPoint = this.projection.project(latlng),
4 scale = this.scale(zoom);
5
46 return this.transformation._transform(projectedPoint, scale);
57 },
68
7 pointToLatLng: function (/*Point*/ point, /*Number*/ scale, /*(optional) Boolean*/ unbounded)/*-> LatLng*/ {
8 var untransformedPoint = this.transformation.untransform(point, scale);
9 return this.projection.unproject(untransformedPoint, unbounded);
10 //TODO get rid of 'unbounded' everywhere
9 pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
10 var scale = this.scale(zoom),
11 untransformedPoint = this.transformation.untransform(point, scale);
12
13 return this.projection.unproject(untransformedPoint);
1114 },
1215
1316 project: function (latlng) {
1417 return this.projection.project(latlng);
18 },
19
20 scale: function (zoom) {
21 return 256 * Math.pow(2, zoom);
1522 }
1623 };
33 return new L.Point(latlng.lng, latlng.lat);
44 },
55
6 unproject: function (point, unbounded) {
7 return new L.LatLng(point.y, point.x, unbounded);
6 unproject: function (point) {
7 return new L.LatLng(point.y, point.x, true);
88 }
99 };
44 R_MINOR: 6356752.3142,
55 R_MAJOR: 6378137,
66
7 project: function (/*LatLng*/ latlng) /*-> Point*/ {
7 project: function (latlng) { // (LatLng) -> Point
88 var d = L.LatLng.DEG_TO_RAD,
99 max = this.MAX_LATITUDE,
1010 lat = Math.max(Math.min(max, latlng.lat), -max),
2424 return new L.Point(x, y);
2525 },
2626
27 unproject: function (/*Point*/ point, /*Boolean*/ unbounded) /*-> LatLng*/ {
27 unproject: function (point) { // (Point, Boolean) -> LatLng
2828 var d = L.LatLng.RAD_TO_DEG,
2929 r = this.R_MAJOR,
3030 r2 = this.R_MINOR,
4545 phi += dphi;
4646 }
4747
48 return new L.LatLng(phi * d, lng, unbounded);
48 return new L.LatLng(phi * d, lng, true);
4949 }
5050 };
11 L.Projection.SphericalMercator = {
22 MAX_LATITUDE: 85.0511287798,
33
4 project: function (/*LatLng*/ latlng) /*-> Point*/ {
4 project: function (latlng) { // (LatLng) -> Point
55 var d = L.LatLng.DEG_TO_RAD,
66 max = this.MAX_LATITUDE,
77 lat = Math.max(Math.min(max, latlng.lat), -max),
1212 return new L.Point(x, y);
1313 },
1414
15 unproject: function (/*Point*/ point, /*Boolean*/ unbounded) /*-> LatLng*/ {
15 unproject: function (point) { // (Point, Boolean) -> LatLng
1616 var d = L.LatLng.RAD_TO_DEG,
1717 lng = point.x * d,
1818 lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
1919
20 return new L.LatLng(lat, lng, unbounded);
20 // TODO refactor LatLng wrapping
21 return new L.LatLng(lat, lng, true);
2122 }
2223 };
22 */
33
44 L.Bounds = L.Class.extend({
5 initialize: function (min, max) { //(Point, Point) or Point[]
6 if (!min) {
7 return;
8 }
9 var points = (min instanceof Array ? min : [min, max]);
5
6 initialize: function (a, b) { //(Point, Point) or Point[]
7 if (!a) { return; }
8
9 var points = b ? [a, b] : a;
10
1011 for (var i = 0, len = points.length; i < len; i++) {
1112 this.extend(points[i]);
1213 }
1314 },
1415
1516 // extend the bounds to contain the given point
16 extend: function (/*Point*/ point) {
17 extend: function (point) { // (Point)
18 point = L.point(point);
19
1720 if (!this.min && !this.max) {
18 this.min = new L.Point(point.x, point.y);
19 this.max = new L.Point(point.x, point.y);
21 this.min = point.clone();
22 this.max = point.clone();
2023 } else {
2124 this.min.x = Math.min(point.x, this.min.x);
2225 this.max.x = Math.max(point.x, this.max.x);
2326 this.min.y = Math.min(point.y, this.min.y);
2427 this.max.y = Math.max(point.y, this.max.y);
2528 }
29 return this;
2630 },
2731
28 getCenter: function (round)/*->Point*/ {
32 getCenter: function (round) { // (Boolean) -> Point
2933 return new L.Point(
3034 (this.min.x + this.max.x) / 2,
3135 (this.min.y + this.max.y) / 2, round);
3236 },
3337
34 contains: function (/*Bounds or Point*/ obj)/*->Boolean*/ {
38 getBottomLeft: function () { // -> Point
39 return new L.Point(this.min.x, this.max.y);
40 },
41
42 getTopRight: function () { // -> Point
43 return new L.Point(this.max.x, this.min.y);
44 },
45
46 contains: function (obj) { // (Bounds) or (Point) -> Boolean
3547 var min, max;
48
49 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
50 obj = L.point(obj);
51 } else {
52 obj = L.bounds(obj);
53 }
3654
3755 if (obj instanceof L.Bounds) {
3856 min = obj.min;
4765 (max.y <= this.max.y);
4866 },
4967
50 intersects: function (/*Bounds*/ bounds) {
68 intersects: function (bounds) { // (Bounds) -> Boolean
69 bounds = L.bounds(bounds);
70
5171 var min = this.min,
5272 max = this.max,
5373 min2 = bounds.min,
6080 }
6181
6282 });
83
84 L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
85 if (!a || a instanceof L.Bounds) {
86 return a;
87 }
88 return new L.Bounds(a, b);
89 };
3636 _simplifyDP: function (points, sqTolerance) {
3737
3838 var len = points.length,
39 ArrayConstructor = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
39 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
4040 markers = new ArrayConstructor(len);
4141
4242 markers[0] = markers[len - 1] = 1;
88
99 L.Point.prototype = {
1010 add: function (point) {
11 return this.clone()._add(point);
11 return this.clone()._add(L.point(point));
1212 },
1313
1414 _add: function (point) {
1818 },
1919
2020 subtract: function (point) {
21 return this.clone()._subtract(point);
21 return this.clone()._subtract(L.point(point));
2222 },
2323
2424 // destructive subtract (faster)
3232 return new L.Point(this.x / num, this.y / num, round);
3333 },
3434
35 multiplyBy: function (num) {
36 return new L.Point(this.x * num, this.y * num);
35 multiplyBy: function (num, round) {
36 return new L.Point(this.x * num, this.y * num, round);
3737 },
3838
3939 distanceTo: function (point) {
40 point = L.point(point);
41
4042 var x = point.x - this.x,
4143 y = point.y - this.y;
44
4245 return Math.sqrt(x * x + y * y);
4346 },
4447
5356 return this;
5457 },
5558
59 floor: function () {
60 return this.clone()._floor();
61 },
62
63 _floor: function () {
64 this.x = Math.floor(this.x);
65 this.y = Math.floor(this.y);
66 return this;
67 },
68
5669 clone: function () {
5770 return new L.Point(this.x, this.y);
5871 },
6376 L.Util.formatNum(this.y) + ')';
6477 }
6578 };
79
80 L.point = function (x, y, round) {
81 if (x instanceof L.Point) {
82 return x;
83 }
84 if (x instanceof Array) {
85 return new L.Point(x[0], x[1]);
86 }
87 if (isNaN(x)) {
88 return x;
89 }
90 return new L.Point(x, y, round);
91 };
55 includes: L.Mixin.Events,
66
77 addLayer: function (layer) {
8 this._initEvents(layer);
8 if (this._layers[L.Util.stamp(layer)]) {
9 return this;
10 }
11
12 layer.on('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
13
914 L.LayerGroup.prototype.addLayer.call(this, layer);
1015
1116 if (this._popupContent && layer.bindPopup) {
1217 layer.bindPopup(this._popupContent);
1318 }
19
20 return this;
21 },
22
23 removeLayer: function (layer) {
24 layer.off('click dblclick mouseover mouseout mousemove contextmenu', this._propagateEvent, this);
25
26 L.LayerGroup.prototype.removeLayer.call(this, layer);
27
28 return this.invoke('unbindPopup');
1429 },
1530
1631 bindPopup: function (content) {
1732 this._popupContent = content;
18
1933 return this.invoke('bindPopup', content);
2034 },
2135
2337 return this.invoke('setStyle', style);
2438 },
2539
26 _events: ['click', 'dblclick', 'mouseover', 'mouseout'],
27
28 _initEvents: function (layer) {
29 for (var i = 0, len = this._events.length; i < len; i++) {
30 layer.on(this._events[i], this._propagateEvent, this);
31 }
40 getBounds: function () {
41 var bounds = new L.LatLngBounds();
42 this.eachLayer(function (layer) {
43 bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
44 }, this);
45 return bounds;
3246 },
3347
3448 _propagateEvent: function (e) {
35 e.layer = e.target;
49 e.layer = e.target;
3650 e.target = this;
51
3752 this.fire(e.type, e);
3853 }
3954 });
55
56 L.featureGroup = function (layers) {
57 return new L.FeatureGroup(layers);
58 };
0
10 L.GeoJSON = L.FeatureGroup.extend({
21 initialize: function (geojson, options) {
32 L.Util.setOptions(this, options);
4 this._geojson = geojson;
3
54 this._layers = {};
65
76 if (geojson) {
8 this.addGeoJSON(geojson);
7 this.addData(geojson);
98 }
109 },
1110
12 addGeoJSON: function (geojson) {
13 if (geojson.features) {
14 for (var i = 0, len = geojson.features.length; i < len; i++) {
15 this.addGeoJSON(geojson.features[i]);
11 addData: function (geojson) {
12 var features = geojson instanceof Array ? geojson : geojson.features,
13 i, len;
14
15 if (features) {
16 for (i = 0, len = features.length; i < len; i++) {
17 this.addData(features[i]);
1618 }
17 return;
19 return this;
1820 }
1921
20 var isFeature = (geojson.type === 'Feature'),
21 geometry = (isFeature ? geojson.geometry : geojson),
22 layer = L.GeoJSON.geometryToLayer(geometry, this.options.pointToLayer);
22 var options = this.options,
23 style = options.style;
2324
24 this.fire('featureparse', {
25 layer: layer,
26 properties: geojson.properties,
27 geometryType: geometry.type,
28 bbox: geojson.bbox,
29 id: geojson.id
30 });
25 if (options.filter && !options.filter(geojson)) { return; }
3126
32 this.addLayer(layer);
27 var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
28
29 if (style) {
30 if (typeof style === 'function') {
31 style = style(geojson);
32 }
33 if (layer.setStyle) {
34 layer.setStyle(style);
35 }
36 }
37
38 if (options.onEachFeature) {
39 options.onEachFeature(geojson, layer);
40 }
41
42 return this.addLayer(layer);
3343 }
3444 });
3545
3646 L.Util.extend(L.GeoJSON, {
37 geometryToLayer: function (geometry, pointToLayer) {
38 var coords = geometry.coordinates,
39 latlng, latlngs,
40 i, len,
41 layer,
42 layers = [];
47 geometryToLayer: function (geojson, pointToLayer) {
48 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
49 coords = geometry.coordinates,
50 layers = [],
51 latlng, latlngs, i, len, layer;
4352
4453 switch (geometry.type) {
4554 case 'Point':
4655 latlng = this.coordsToLatLng(coords);
47 return pointToLayer ? pointToLayer(latlng) : new L.Marker(latlng);
56 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
4857
4958 case 'MultiPoint':
5059 for (i = 0, len = coords.length; i < len; i++) {
5160 latlng = this.coordsToLatLng(coords[i]);
52 layer = pointToLayer ? pointToLayer(latlng) : new L.Marker(latlng);
61 layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
5362 layers.push(layer);
5463 }
5564 return new L.FeatureGroup(layers);
8291 }
8392 },
8493
85 coordsToLatLng: function (/*Array*/ coords, /*Boolean*/ reverse)/*: LatLng*/ {
94 coordsToLatLng: function (coords, reverse) { // (Array, Boolean) -> LatLng
8695 var lat = parseFloat(coords[reverse ? 0 : 1]),
87 lng = parseFloat(coords[reverse ? 1 : 0]);
96 lng = parseFloat(coords[reverse ? 1 : 0]);
97
8898 return new L.LatLng(lat, lng, true);
8999 },
90100
91 coordsToLatLngs: function (/*Array*/ coords, /*Number*/ levelsDeep, /*Boolean*/ reverse)/*: Array*/ {
92 var latlng, latlngs = [],
93 i, len = coords.length;
101 coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
102 var latlng,
103 latlngs = [],
104 i, len;
94105
95 for (i = 0; i < len; i++) {
106 for (i = 0, len = coords.length; i < len; i++) {
96107 latlng = levelsDeep ?
97108 this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
98109 this.coordsToLatLng(coords[i], reverse);
110
99111 latlngs.push(latlng);
100112 }
113
101114 return latlngs;
102115 }
103116 });
117
118 L.geoJson = function (geojson, options) {
119 return new L.GeoJSON(geojson, options);
120 };
00 L.ImageOverlay = L.Class.extend({
11 includes: L.Mixin.Events,
22
3 initialize: function (/*String*/ url, /*LatLngBounds*/ bounds) {
3 options: {
4 opacity: 1
5 },
6
7 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
48 this._url = url;
5 this._bounds = bounds;
9 this._bounds = L.latLngBounds(bounds);
10
11 L.Util.setOptions(this, options);
612 },
713
814 onAdd: function (map) {
1218 this._initImage();
1319 }
1420
15 map.getPanes().overlayPane.appendChild(this._image);
21 map._panes.overlayPane.appendChild(this._image);
1622
1723 map.on('viewreset', this._reset, this);
24
25 if (map.options.zoomAnimation && L.Browser.any3d) {
26 map.on('zoomanim', this._animateZoom, this);
27 }
28
1829 this._reset();
1930 },
2031
2132 onRemove: function (map) {
2233 map.getPanes().overlayPane.removeChild(this._image);
34
2335 map.off('viewreset', this._reset, this);
36
37 if (map.options.zoomAnimation) {
38 map.off('zoomanim', this._animateZoom, this);
39 }
40 },
41
42 addTo: function (map) {
43 map.addLayer(this);
44 return this;
45 },
46
47 setOpacity: function (opacity) {
48 this.options.opacity = opacity;
49 this._updateOpacity();
50 return this;
51 },
52
53 // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
54 bringToFront: function () {
55 if (this._image) {
56 this._map._panes.overlayPane.appendChild(this._image);
57 }
58 return this;
59 },
60
61 bringToBack: function () {
62 var pane = this._map._panes.overlayPane;
63 if (this._image) {
64 pane.insertBefore(this._image, pane.firstChild);
65 }
66 return this;
2467 },
2568
2669 _initImage: function () {
2770 this._image = L.DomUtil.create('img', 'leaflet-image-layer');
2871
29 this._image.style.visibility = 'hidden';
30 //TODO opacity option
72 if (this._map.options.zoomAnimation && L.Browser.any3d) {
73 L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
74 } else {
75 L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
76 }
77
78 this._updateOpacity();
3179
3280 //TODO createImage util method to remove duplication
3381 L.Util.extend(this._image, {
3987 });
4088 },
4189
90 _animateZoom: function (e) {
91 var map = this._map,
92 image = this._image,
93 scale = map.getZoomScale(e.zoom),
94 nw = this._bounds.getNorthWest(),
95 se = this._bounds.getSouthEast(),
96 topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
97 size = map._latLngToNewLayerPoint(se, e.zoom, e.center).subtract(topLeft),
98 currentSize = map.latLngToLayerPoint(se).subtract(map.latLngToLayerPoint(nw)),
99 origin = topLeft.add(size.subtract(currentSize).divideBy(2));
100
101 image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
102 },
103
42104 _reset: function () {
43 var topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
44 bottomRight = this._map.latLngToLayerPoint(this._bounds.getSouthEast()),
45 size = bottomRight.subtract(topLeft);
105 var image = this._image,
106 topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
107 size = this._map.latLngToLayerPoint(this._bounds.getSouthEast()).subtract(topLeft);
46108
47 L.DomUtil.setPosition(this._image, topLeft);
109 L.DomUtil.setPosition(image, topLeft);
48110
49 this._image.style.width = size.x + 'px';
50 this._image.style.height = size.y + 'px';
111 image.style.width = size.x + 'px';
112 image.style.height = size.y + 'px';
51113 },
52114
53115 _onImageLoad: function () {
54 this._image.style.visibility = '';
55116 this.fire('load');
117 },
118
119 _updateOpacity: function () {
120 L.DomUtil.setOpacity(this._image, this.options.opacity);
56121 }
57122 });
123
124 L.imageOverlay = function (url, bounds, options) {
125 return new L.ImageOverlay(url, bounds, options);
126 };
55 initialize: function (layers) {
66 this._layers = {};
77
8 var i, len;
9
810 if (layers) {
9 for (var i = 0, len = layers.length; i < len; i++) {
11 for (i = 0, len = layers.length; i < len; i++) {
1012 this.addLayer(layers[i]);
1113 }
1214 }
1416
1517 addLayer: function (layer) {
1618 var id = L.Util.stamp(layer);
19
1720 this._layers[id] = layer;
1821
1922 if (this._map) {
2023 this._map.addLayer(layer);
2124 }
25
2226 return this;
2327 },
2428
2529 removeLayer: function (layer) {
2630 var id = L.Util.stamp(layer);
31
2732 delete this._layers[id];
2833
2934 if (this._map) {
3035 this._map.removeLayer(layer);
3136 }
37
3238 return this;
3339 },
3440
3541 clearLayers: function () {
36 this._iterateLayers(this.removeLayer, this);
42 this.eachLayer(this.removeLayer, this);
3743 return this;
3844 },
3945
5056 }
5157 }
5258 }
59
5360 return this;
5461 },
5562
5663 onAdd: function (map) {
5764 this._map = map;
58 this._iterateLayers(map.addLayer, map);
65 this.eachLayer(map.addLayer, map);
5966 },
6067
6168 onRemove: function (map) {
62 this._iterateLayers(map.removeLayer, map);
63 delete this._map;
69 this.eachLayer(map.removeLayer, map);
70 this._map = null;
6471 },
6572
66 _iterateLayers: function (method, context) {
73 addTo: function (map) {
74 map.addLayer(this);
75 return this;
76 },
77
78 eachLayer: function (method, context) {
6779 for (var i in this._layers) {
6880 if (this._layers.hasOwnProperty(i)) {
6981 method.call(context, this._layers[i]);
7183 }
7284 }
7385 });
86
87 L.layerGroup = function (layers) {
88 return new L.LayerGroup(layers);
89 };
0
1 L.Map.mergeOptions({
2 closePopupOnClick: true
3 });
04
15 L.Popup = L.Class.extend({
26 includes: L.Mixin.Events,
48 options: {
59 minWidth: 50,
610 maxWidth: 300,
11 maxHeight: null,
712 autoPan: true,
813 closeButton: true,
9 offset: new L.Point(0, 2),
14 offset: new L.Point(0, 6),
1015 autoPanPadding: new L.Point(5, 5),
1116 className: ''
1217 },
1924
2025 onAdd: function (map) {
2126 this._map = map;
27
2228 if (!this._container) {
2329 this._initLayout();
2430 }
2531 this._updateContent();
2632
27 this._container.style.opacity = '0';
28
29 this._map._panes.popupPane.appendChild(this._container);
30 this._map.on('viewreset', this._updatePosition, this);
31
32 if (this._map.options.closePopupOnClick) {
33 this._map.on('preclick', this._close, this);
33 var animFade = map.options.fadeAnimation;
34
35 if (animFade) {
36 L.DomUtil.setOpacity(this._container, 0);
37 }
38 map._panes.popupPane.appendChild(this._container);
39
40 map.on('viewreset', this._updatePosition, this);
41
42 if (L.Browser.any3d) {
43 map.on('zoomanim', this._zoomAnimation, this);
44 }
45
46 if (map.options.closePopupOnClick) {
47 map.on('preclick', this._close, this);
3448 }
3549
3650 this._update();
3751
38 this._container.style.opacity = '1'; //TODO fix ugly opacity hack
39
40 this._opened = true;
52 if (animFade) {
53 L.DomUtil.setOpacity(this._container, 1);
54 }
55 },
56
57 addTo: function (map) {
58 map.addLayer(this);
59 return this;
60 },
61
62 openOn: function (map) {
63 map.openPopup(this);
64 return this;
4165 },
4266
4367 onRemove: function (map) {
4468 map._panes.popupPane.removeChild(this._container);
45 L.Util.falseFn(this._container.offsetWidth);
46
47 map.off('viewreset', this._updatePosition, this);
48 map.off('click', this._close, this);
49
50 this._container.style.opacity = '0';
51
52 this._opened = false;
69
70 L.Util.falseFn(this._container.offsetWidth); // force reflow
71
72 map.off({
73 viewreset: this._updatePosition,
74 preclick: this._close,
75 zoomanim: this._zoomAnimation
76 }, this);
77
78 if (map.options.fadeAnimation) {
79 L.DomUtil.setOpacity(this._container, 0);
80 }
81
82 this._map = null;
5383 },
5484
5585 setLatLng: function (latlng) {
56 this._latlng = latlng;
57 if (this._opened) {
58 this._update();
59 }
86 this._latlng = L.latLng(latlng);
87 this._update();
6088 return this;
6189 },
6290
6391 setContent: function (content) {
6492 this._content = content;
65 if (this._opened) {
66 this._update();
67 }
93 this._update();
6894 return this;
6995 },
7096
7197 _close: function () {
72 if (this._opened) {
73 this._map.closePopup();
98 var map = this._map;
99
100 if (map) {
101 map._popup = null;
102
103 map
104 .removeLayer(this)
105 .fire('popupclose', {popup: this});
74106 }
75107 },
76108
77109 _initLayout: function () {
78 this._container = L.DomUtil.create('div', 'leaflet-popup ' + this.options.className);
110 var prefix = 'leaflet-popup',
111 container = this._container = L.DomUtil.create('div', prefix + ' ' + this.options.className + ' leaflet-zoom-animated'),
112 closeButton;
79113
80114 if (this.options.closeButton) {
81 this._closeButton = L.DomUtil.create('a', 'leaflet-popup-close-button', this._container);
82 this._closeButton.href = '#close';
83 L.DomEvent.addListener(this._closeButton, 'click', this._onCloseButtonClick, this);
84 }
85
86 this._wrapper = L.DomUtil.create('div', 'leaflet-popup-content-wrapper', this._container);
87 L.DomEvent.disableClickPropagation(this._wrapper);
88 this._contentNode = L.DomUtil.create('div', 'leaflet-popup-content', this._wrapper);
89
90 this._tipContainer = L.DomUtil.create('div', 'leaflet-popup-tip-container', this._container);
91 this._tip = L.DomUtil.create('div', 'leaflet-popup-tip', this._tipContainer);
115 closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
116 closeButton.href = '#close';
117 closeButton.innerHTML = '&#215;';
118
119 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
120 }
121
122 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
123 L.DomEvent.disableClickPropagation(wrapper);
124
125 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
126 L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
127
128 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
129 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
92130 },
93131
94132 _update: function () {
133 if (!this._map) { return; }
134
95135 this._container.style.visibility = 'hidden';
96136
97137 this._updateContent();
104144 },
105145
106146 _updateContent: function () {
107 if (!this._content) {
108 return;
109 }
147 if (!this._content) { return; }
110148
111149 if (typeof this._content === 'string') {
112150 this._contentNode.innerHTML = this._content;
113151 } else {
114 this._contentNode.innerHTML = '';
152 while (this._contentNode.hasChildNodes()) {
153 this._contentNode.removeChild(this._contentNode.firstChild);
154 }
115155 this._contentNode.appendChild(this._content);
116156 }
157 this.fire('contentupdate');
117158 },
118159
119160 _updateLayout: function () {
120 this._container.style.width = '';
121 this._container.style.whiteSpace = 'nowrap';
122
123 var width = this._container.offsetWidth;
124
125 this._container.style.width = (width > this.options.maxWidth ?
126 this.options.maxWidth : (width < this.options.minWidth ? this.options.minWidth : width)) + 'px';
127 this._container.style.whiteSpace = '';
161 var container = this._contentNode,
162 style = container.style;
163
164 style.width = '';
165 style.whiteSpace = 'nowrap';
166
167 var width = container.offsetWidth;
168 width = Math.min(width, this.options.maxWidth);
169 width = Math.max(width, this.options.minWidth);
170
171 style.width = (width + 1) + 'px';
172 style.whiteSpace = '';
173
174 style.height = '';
175
176 var height = container.offsetHeight,
177 maxHeight = this.options.maxHeight,
178 scrolledClass = 'leaflet-popup-scrolled';
179
180 if (maxHeight && height > maxHeight) {
181 style.height = maxHeight + 'px';
182 L.DomUtil.addClass(container, scrolledClass);
183 } else {
184 L.DomUtil.removeClass(container, scrolledClass);
185 }
128186
129187 this._containerWidth = this._container.offsetWidth;
130188 },
131189
132190 _updatePosition: function () {
133 var pos = this._map.latLngToLayerPoint(this._latlng);
134
135 this._containerBottom = -pos.y - this.options.offset.y;
136 this._containerLeft = pos.x - Math.round(this._containerWidth / 2) + this.options.offset.x;
137
191 var pos = this._map.latLngToLayerPoint(this._latlng),
192 is3d = L.Browser.any3d,
193 offset = this.options.offset;
194
195 if (is3d) {
196 L.DomUtil.setPosition(this._container, pos);
197 }
198
199 this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
200 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
201
202 //Bottom position the popup in case the height of the popup changes (images loading etc)
138203 this._container.style.bottom = this._containerBottom + 'px';
139204 this._container.style.left = this._containerLeft + 'px';
140205 },
141206
207 _zoomAnimation: function (opt) {
208 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
209
210 L.DomUtil.setPosition(this._container, pos);
211 },
212
142213 _adjustPan: function () {
143 if (!this.options.autoPan) {
144 return;
145 }
146
147 var containerHeight = this._container.offsetHeight,
148 layerPos = new L.Point(
149 this._containerLeft,
150 -containerHeight - this._containerBottom),
151 containerPos = this._map.layerPointToContainerPoint(layerPos),
152 adjustOffset = new L.Point(0, 0),
214 if (!this.options.autoPan) { return; }
215
216 var map = this._map,
217 containerHeight = this._container.offsetHeight,
218 containerWidth = this._containerWidth,
219
220 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
221
222 if (L.Browser.any3d) {
223 layerPos._add(L.DomUtil.getPosition(this._container));
224 }
225
226 var containerPos = map.layerPointToContainerPoint(layerPos),
153227 padding = this.options.autoPanPadding,
154 size = this._map.getSize();
228 size = map.getSize(),
229 dx = 0,
230 dy = 0;
155231
156232 if (containerPos.x < 0) {
157 adjustOffset.x = containerPos.x - padding.x;
158 }
159 if (containerPos.x + this._containerWidth > size.x) {
160 adjustOffset.x = containerPos.x + this._containerWidth - size.x + padding.x;
233 dx = containerPos.x - padding.x;
234 }
235 if (containerPos.x + containerWidth > size.x) {
236 dx = containerPos.x + containerWidth - size.x + padding.x;
161237 }
162238 if (containerPos.y < 0) {
163 adjustOffset.y = containerPos.y - padding.y;
239 dy = containerPos.y - padding.y;
164240 }
165241 if (containerPos.y + containerHeight > size.y) {
166 adjustOffset.y = containerPos.y + containerHeight - size.y + padding.y;
167 }
168
169 if (adjustOffset.x || adjustOffset.y) {
170 this._map.panBy(adjustOffset);
242 dy = containerPos.y + containerHeight - size.y + padding.y;
243 }
244
245 if (dx || dy) {
246 map.panBy(new L.Point(dx, dy));
171247 }
172248 },
173249
176252 L.DomEvent.stop(e);
177253 }
178254 });
255
256 L.popup = function (options, source) {
257 return new L.Popup(options, source);
258 };
0 L.DivIcon = L.Icon.extend({
1 options: {
2 iconSize: new L.Point(12, 12), // also can be set through CSS
3 /*
4 iconAnchor: (Point)
5 popupAnchor: (Point)
6 html: (String)
7 bgPos: (Point)
8 */
9 className: 'leaflet-div-icon'
10 },
11
12 createIcon: function () {
13 var div = document.createElement('div'),
14 options = this.options;
15
16 if (options.html) {
17 div.innerHTML = options.html;
18 }
19
20 if (options.bgPos) {
21 div.style.backgroundPosition =
22 (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
23 }
24
25 this._setIconStyles(div, 'icon');
26 return div;
27 },
28
29 createShadow: function () {
30 return null;
31 }
32 });
33
34 L.divIcon = function (options) {
35 return new L.DivIcon(options);
36 };
0
1 L.Icon.Default = L.Icon.extend({
2
3 options: {
4 iconSize: new L.Point(25, 41),
5 iconAnchor: new L.Point(13, 41),
6 popupAnchor: new L.Point(1, -34),
7
8 shadowSize: new L.Point(41, 41)
9 },
10
11 _getIconUrl: function (name) {
12 var key = name + 'Url';
13
14 if (this.options[key]) {
15 return this.options[key];
16 }
17
18 var path = L.Icon.Default.imagePath;
19
20 if (!path) {
21 throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");
22 }
23
24 return path + '/marker-' + name + '.png';
25 }
26 });
27
28 L.Icon.Default.imagePath = (function () {
29 var scripts = document.getElementsByTagName('script'),
30 leafletRe = /\/?leaflet[\-\._]?([\w\-\._]*)\.js\??/;
31
32 var i, len, src, matches;
33
34 for (i = 0, len = scripts.length; i < len; i++) {
35 src = scripts[i].src;
36 matches = src.match(leafletRe);
37
38 if (matches) {
39 return src.split(leafletRe)[0] + '/images';
40 }
41 }
42 }());
00 L.Icon = L.Class.extend({
1 iconUrl: L.ROOT_URL + 'images/marker.png',
2 shadowUrl: L.ROOT_URL + 'images/marker-shadow.png',
1 options: {
2 /*
3 iconUrl: (String) (required)
4 iconSize: (Point) (can be set through CSS)
5 iconAnchor: (Point) (centered by default if size is specified, can be set in CSS with negative margins)
6 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
7 shadowUrl: (Point) (no shadow by default)
8 shadowSize: (Point)
9 shadowAnchor: (Point)
10 */
11 className: ''
12 },
313
4 iconSize: new L.Point(25, 41),
5 shadowSize: new L.Point(41, 41),
6
7 iconAnchor: new L.Point(13, 41),
8 popupAnchor: new L.Point(0, -33),
9
10 initialize: function (iconUrl) {
11 if (iconUrl) {
12 this.iconUrl = iconUrl;
13 }
14 initialize: function (options) {
15 L.Util.setOptions(this, options);
1416 },
1517
1618 createIcon: function () {
2224 },
2325
2426 _createIcon: function (name) {
25 var size = this[name + 'Size'],
26 src = this[name + 'Url'];
27 if (!src && name === 'shadow') {
27 var src = this._getIconUrl(name);
28
29 if (!src) {
30 if (name === 'icon') {
31 throw new Error("iconUrl not set in Icon options (see the docs).");
32 }
2833 return null;
2934 }
3035
31 var img;
32 if (!src) {
33 img = this._createDiv();
34 }
35 else {
36 img = this._createImg(src);
37 }
38
39 img.className = 'leaflet-marker-' + name;
40
41 img.style.marginLeft = (-this.iconAnchor.x) + 'px';
42 img.style.marginTop = (-this.iconAnchor.y) + 'px';
43
44 if (size) {
45 img.style.width = size.x + 'px';
46 img.style.height = size.y + 'px';
47 }
36 var img = this._createImg(src);
37 this._setIconStyles(img, name);
4838
4939 return img;
5040 },
5141
42 _setIconStyles: function (img, name) {
43 var options = this.options,
44 size = L.point(options[name + 'Size']),
45 anchor;
46
47 if (name === 'shadow') {
48 anchor = L.point(options.shadowAnchor || options.iconAnchor);
49 } else {
50 anchor = L.point(options.iconAnchor);
51 }
52
53 if (!anchor && size) {
54 anchor = size.divideBy(2, true);
55 }
56
57 img.className = 'leaflet-marker-' + name + ' ' + options.className;
58
59 if (anchor) {
60 img.style.marginLeft = (-anchor.x) + 'px';
61 img.style.marginTop = (-anchor.y) + 'px';
62 }
63
64 if (size) {
65 img.style.width = size.x + 'px';
66 img.style.height = size.y + 'px';
67 }
68 },
69
5270 _createImg: function (src) {
5371 var el;
72
5473 if (!L.Browser.ie6) {
5574 el = document.createElement('img');
5675 el.src = src;
6180 return el;
6281 },
6382
64 _createDiv: function () {
65 return document.createElement('div');
83 _getIconUrl: function (name) {
84 return this.options[name + 'Url'];
6685 }
6786 });
87
88 L.icon = function (options) {
89 return new L.Icon(options);
90 };
99 addHooks: function () {
1010 var icon = this._marker._icon;
1111 if (!this._draggable) {
12 this._draggable = new L.Draggable(icon, icon);
13
14 this._draggable
12 this._draggable = new L.Draggable(icon, icon)
1513 .on('dragstart', this._onDragStart, this)
1614 .on('drag', this._onDrag, this)
1715 .on('dragend', this._onDragEnd, this);
33
44 L.Marker.include({
55 openPopup: function () {
6 this._popup.setLatLng(this._latlng);
7 if (this._map) {
6 if (this._popup && this._map) {
7 this._popup.setLatLng(this._latlng);
88 this._map.openPopup(this._popup);
99 }
1010
1919 },
2020
2121 bindPopup: function (content, options) {
22 options = L.Util.extend({offset: this.options.icon.popupAnchor}, options);
22 var anchor = L.point(this.options.icon.options.popupAnchor) || new L.Point(0, 0);
23
24 anchor = anchor.add(L.Popup.prototype.options.offset);
25
26 if (options && options.offset) {
27 anchor = anchor.add(options.offset);
28 }
29
30 options = L.Util.extend({offset: anchor}, options);
2331
2432 if (!this._popup) {
2533 this.on('click', this.openPopup, this);
2634 }
2735
28 this._popup = new L.Popup(options, this);
29 this._popup.setContent(content);
36 this._popup = new L.Popup(options, this)
37 .setContent(content);
3038
3139 return this;
3240 },
66 includes: L.Mixin.Events,
77
88 options: {
9 icon: new L.Icon(),
9 icon: new L.Icon.Default(),
1010 title: '',
1111 clickable: true,
1212 draggable: false,
13 zIndexOffset: 0
13 zIndexOffset: 0,
14 opacity: 1
1415 },
1516
1617 initialize: function (latlng, options) {
1718 L.Util.setOptions(this, options);
18 this._latlng = latlng;
19 this._latlng = L.latLng(latlng);
1920 },
2021
2122 onAdd: function (map) {
2223 this._map = map;
2324
25 map.on('viewreset', this.update, this);
26
2427 this._initIcon();
25
26 map.on('viewreset', this._reset, this);
27 this._reset();
28 this.update();
29
30 if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
31 map.on('zoomanim', this._animateZoom, this);
32 }
33 },
34
35 addTo: function (map) {
36 map.addLayer(this);
37 return this;
2838 },
2939
3040 onRemove: function (map) {
3545 this.closePopup();
3646 }
3747
48 map.off({
49 'viewreset': this.update,
50 'zoomanim': this._animateZoom
51 }, this);
52
3853 this._map = null;
39
40 map.off('viewreset', this._reset, this);
4154 },
4255
4356 getLatLng: function () {
4558 },
4659
4760 setLatLng: function (latlng) {
48 this._latlng = latlng;
49 if (this._icon) {
50 this._reset();
51
52 if (this._popup) {
53 this._popup.setLatLng(this._latlng);
54 }
61 this._latlng = L.latLng(latlng);
62
63 this.update();
64
65 if (this._popup) {
66 this._popup.setLatLng(latlng);
5567 }
5668 },
5769
5870 setZIndexOffset: function (offset) {
5971 this.options.zIndexOffset = offset;
60 if (this._icon) {
61 this._reset();
62 }
72 this.update();
6373 },
6474
6575 setIcon: function (icon) {
7181
7282 if (this._map) {
7383 this._initIcon();
74 this._reset();
75 }
84 this.update();
85 }
86 },
87
88 update: function () {
89 if (!this._icon) { return; }
90
91 var pos = this._map.latLngToLayerPoint(this._latlng).round();
92 this._setPos(pos);
7693 },
7794
7895 _initIcon: function () {
96 var options = this.options,
97 map = this._map,
98 animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
99 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide',
100 needOpacityUpdate = false;
101
79102 if (!this._icon) {
80 this._icon = this.options.icon.createIcon();
81
82 if (this.options.title) {
83 this._icon.title = this.options.title;
103 this._icon = options.icon.createIcon();
104
105 if (options.title) {
106 this._icon.title = options.title;
84107 }
85108
86109 this._initInteraction();
110 needOpacityUpdate = true;
111
112 L.DomUtil.addClass(this._icon, classToAdd);
87113 }
88114 if (!this._shadow) {
89 this._shadow = this.options.icon.createShadow();
90 }
91
92 this._map._panes.markerPane.appendChild(this._icon);
93 if (this._shadow) {
94 this._map._panes.shadowPane.appendChild(this._shadow);
115 this._shadow = options.icon.createShadow();
116
117 if (this._shadow) {
118 L.DomUtil.addClass(this._shadow, classToAdd);
119 needOpacityUpdate = true;
120 }
121 }
122
123 if (needOpacityUpdate) {
124 this._updateOpacity();
125 }
126
127 var panes = this._map._panes;
128
129 panes.markerPane.appendChild(this._icon);
130
131 if (this._shadow) {
132 panes.shadowPane.appendChild(this._shadow);
95133 }
96134 },
97135
98136 _removeIcon: function () {
99 this._map._panes.markerPane.removeChild(this._icon);
100 if (this._shadow) {
101 this._map._panes.shadowPane.removeChild(this._shadow);
102 }
137 var panes = this._map._panes;
138
139 panes.markerPane.removeChild(this._icon);
140
141 if (this._shadow) {
142 panes.shadowPane.removeChild(this._shadow);
143 }
144
103145 this._icon = this._shadow = null;
104146 },
105147
106 _reset: function () {
107 var pos = this._map.latLngToLayerPoint(this._latlng).round();
108
148 _setPos: function (pos) {
109149 L.DomUtil.setPosition(this._icon, pos);
150
110151 if (this._shadow) {
111152 L.DomUtil.setPosition(this._shadow, pos);
112153 }
114155 this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
115156 },
116157
158 _animateZoom: function (opt) {
159 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
160
161 this._setPos(pos);
162 },
163
117164 _initInteraction: function () {
118 if (this.options.clickable) {
119 this._icon.className += ' leaflet-clickable';
120
121 L.DomEvent.addListener(this._icon, 'click', this._onMouseClick, this);
122
123 var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
124 for (var i = 0; i < events.length; i++) {
125 L.DomEvent.addListener(this._icon, events[i], this._fireMouseEvent, this);
126 }
165 if (!this.options.clickable) {
166 return;
167 }
168
169 var icon = this._icon,
170 events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
171
172 L.DomUtil.addClass(icon, 'leaflet-clickable');
173 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
174
175 for (var i = 0; i < events.length; i++) {
176 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
127177 }
128178
129179 if (L.Handler.MarkerDrag) {
138188 _onMouseClick: function (e) {
139189 L.DomEvent.stopPropagation(e);
140190 if (this.dragging && this.dragging.moved()) { return; }
141 this.fire(e.type);
191 if (this._map.dragging && this._map.dragging.moved()) { return; }
192 this.fire(e.type, {
193 originalEvent: e
194 });
142195 },
143196
144197 _fireMouseEvent: function (e) {
145 this.fire(e.type);
146 L.DomEvent.stopPropagation(e);
198 this.fire(e.type, {
199 originalEvent: e
200 });
201 if (e.type !== 'mousedown') {
202 L.DomEvent.stopPropagation(e);
203 }
204 },
205
206 setOpacity: function (opacity) {
207 this.options.opacity = opacity;
208 if (this._map) {
209 this._updateOpacity();
210 }
211 },
212
213 _updateOpacity: function () {
214 L.DomUtil.setOpacity(this._icon, this.options.opacity);
215 if (this._shadow) {
216 L.DomUtil.setOpacity(this._shadow, this.options.opacity);
217 }
147218 }
148219 });
220
221 L.marker = function (latlng, options) {
222 return new L.Marker(latlng, options);
223 };
77 },
88
99 redraw: function () {
10 for (var i in this._tiles) {
11 var tile = this._tiles[i];
12 this._redrawTile(tile);
10 var i,
11 tiles = this._tiles;
12
13 for (i in tiles) {
14 if (tiles.hasOwnProperty(i)) {
15 this._redrawTile(tiles[i]);
16 }
1317 }
1418 },
1519
1822 },
1923
2024 _createTileProto: function () {
21 this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
25 var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
2226
2327 var tileSize = this.options.tileSize;
24 this._canvasProto.width = tileSize;
25 this._canvasProto.height = tileSize;
28 proto.width = tileSize;
29 proto.height = tileSize;
2630 },
2731
2832 _createTile: function () {
5155 this._tileOnLoad.call(tile);
5256 }
5357 });
58
59
60 L.tileLayer.canvas = function (options) {
61 return new L.TileLayer.Canvas(options);
62 };
00 L.TileLayer.WMS = L.TileLayer.extend({
1
12 defaultWmsParams: {
23 service: 'WMS',
34 request: 'GetMap',
89 transparent: false
910 },
1011
11 initialize: function (/*String*/ url, /*Object*/ options) {
12 initialize: function (url, options) { // (String, Object)
13
1214 this._url = url;
1315
14 this.wmsParams = L.Util.extend({}, this.defaultWmsParams);
15 this.wmsParams.width = this.wmsParams.height = this.options.tileSize;
16 var wmsParams = L.Util.extend({}, this.defaultWmsParams);
17
18 if (options.detectRetina && window.devicePixelRatio > 1) {
19 wmsParams.width = wmsParams.height = this.options.tileSize * 2;
20 } else {
21 wmsParams.width = wmsParams.height = this.options.tileSize;
22 }
1623
1724 for (var i in options) {
1825 // all keys that are not TileLayer options go to WMS params
1926 if (!this.options.hasOwnProperty(i)) {
20 this.wmsParams[i] = options[i];
27 wmsParams[i] = options[i];
2128 }
2229 }
30
31 this.wmsParams = wmsParams;
2332
2433 L.Util.setOptions(this, options);
2534 },
2635
2736 onAdd: function (map) {
28 var projectionKey = (parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs');
37
38 var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
2939 this.wmsParams[projectionKey] = map.options.crs.code;
3040
3141 L.TileLayer.prototype.onAdd.call(this, map);
3242 },
3343
34 getTileUrl: function (/*Point*/ tilePoint, /*Number*/ zoom)/*-> String*/ {
35 var tileSize = this.options.tileSize,
44 getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
45
46 var map = this._map,
47 crs = map.options.crs,
48 tileSize = this.options.tileSize,
49
3650 nwPoint = tilePoint.multiplyBy(tileSize),
3751 sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
38 nwMap = this._map.unproject(nwPoint, this._zoom, true),
39 seMap = this._map.unproject(sePoint, this._zoom, true),
40 nw = this._map.options.crs.project(nwMap),
41 se = this._map.options.crs.project(seMap),
42 bbox = [nw.x, se.y, se.x, nw.y].join(',');
4352
44 return this._url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
53 nw = crs.project(map.unproject(nwPoint, zoom)),
54 se = crs.project(map.unproject(sePoint, zoom)),
55
56 bbox = [nw.x, se.y, se.x, nw.y].join(','),
57
58 url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
59
60 return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
61 },
62
63 setParams: function (params, noRedraw) {
64
65 L.Util.extend(this.wmsParams, params);
66
67 if (!noRedraw) {
68 this.redraw();
69 }
70
71 return this;
4572 }
4673 });
74
75 L.tileLayer.wms = function (url, options) {
76 return new L.TileLayer(url, options);
77 };
1111 subdomains: 'abc',
1212 errorTileUrl: '',
1313 attribution: '',
14 zoomOffset: 0,
1415 opacity: 1,
15 scheme: 'xyz',
16 /* (undefined works too)
17 zIndex: null,
18 tms: false,
1619 continuousWorld: false,
1720 noWrap: false,
18 zoomOffset: 0,
1921 zoomReverse: false,
20
22 detectRetina: false,
23 reuseTiles: false,
24 */
2125 unloadInvisibleTiles: L.Browser.mobile,
22 updateWhenIdle: L.Browser.mobile,
23 reuseTiles: false
24 },
25
26 initialize: function (url, options, urlParams) {
27 L.Util.setOptions(this, options);
26 updateWhenIdle: L.Browser.mobile
27 },
28
29 initialize: function (url, options) {
30 options = L.Util.setOptions(this, options);
31
32 // detecting retina displays, adjusting tileSize and zoom levels
33 if (options.detectRetina && window.devicePixelRatio > 1 && options.maxZoom > 0) {
34
35 options.tileSize = Math.floor(options.tileSize / 2);
36 options.zoomOffset++;
37
38 if (options.minZoom > 0) {
39 options.minZoom--;
40 }
41 this.options.maxZoom--;
42 }
2843
2944 this._url = url;
30 this._urlParams = urlParams;
31
32 if (typeof this.options.subdomains === 'string') {
33 this.options.subdomains = this.options.subdomains.split('');
34 }
35 },
36
37 onAdd: function (map, insertAtTheBottom) {
45
46 var subdomains = this.options.subdomains;
47
48 if (typeof subdomains === 'string') {
49 this.options.subdomains = subdomains.split('');
50 }
51 },
52
53 onAdd: function (map) {
3854 this._map = map;
39 this._insertAtTheBottom = insertAtTheBottom;
4055
4156 // create a container div for tiles
4257 this._initContainer();
4560 this._createTileProto();
4661
4762 // set up events
48 map.on('viewreset', this._resetCallback, this);
49
50 if (this.options.updateWhenIdle) {
51 map.on('moveend', this._update, this);
52 } else {
63 map.on({
64 'viewreset': this._resetCallback,
65 'moveend': this._update
66 }, this);
67
68 if (!this.options.updateWhenIdle) {
5369 this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
5470 map.on('move', this._limitedUpdate, this);
5571 }
5874 this._update();
5975 },
6076
77 addTo: function (map) {
78 map.addLayer(this);
79 return this;
80 },
81
6182 onRemove: function (map) {
62 this._map.getPanes().tilePane.removeChild(this._container);
83 map._panes.tilePane.removeChild(this._container);
84
85 map.off({
86 'viewreset': this._resetCallback,
87 'moveend': this._update
88 }, this);
89
90 if (!this.options.updateWhenIdle) {
91 map.off('move', this._limitedUpdate, this);
92 }
93
6394 this._container = null;
64
65 this._map.off('viewreset', this._resetCallback, this);
66
67 if (this.options.updateWhenIdle) {
68 this._map.off('moveend', this._update, this);
69 } else {
70 this._map.off('move', this._limitedUpdate, this);
71 }
95 this._map = null;
96 },
97
98 bringToFront: function () {
99 var pane = this._map._panes.tilePane;
100
101 if (this._container) {
102 pane.appendChild(this._container);
103 this._setAutoZIndex(pane, Math.max);
104 }
105
106 return this;
107 },
108
109 bringToBack: function () {
110 var pane = this._map._panes.tilePane;
111
112 if (this._container) {
113 pane.insertBefore(this._container, pane.firstChild);
114 this._setAutoZIndex(pane, Math.min);
115 }
116
117 return this;
72118 },
73119
74120 getAttribution: function () {
78124 setOpacity: function (opacity) {
79125 this.options.opacity = opacity;
80126
81 this._setOpacity(opacity);
127 if (this._map) {
128 this._updateOpacity();
129 }
130
131 return this;
132 },
133
134 setZIndex: function (zIndex) {
135 this.options.zIndex = zIndex;
136 this._updateZIndex();
137
138 return this;
139 },
140
141 setUrl: function (url, noRedraw) {
142 this._url = url;
143
144 if (!noRedraw) {
145 this.redraw();
146 }
147
148 return this;
149 },
150
151 redraw: function () {
152 if (this._map) {
153 this._map._panes.tilePane.empty = false;
154 this._reset(true);
155 this._update();
156 }
157 return this;
158 },
159
160 _updateZIndex: function () {
161 if (this._container && this.options.zIndex !== undefined) {
162 this._container.style.zIndex = this.options.zIndex;
163 }
164 },
165
166 _setAutoZIndex: function (pane, compare) {
167
168 var layers = pane.getElementsByClassName('leaflet-layer'),
169 edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
170 zIndex;
171
172 for (var i = 0, len = layers.length; i < len; i++) {
173
174 if (layers[i] !== this._container) {
175 zIndex = parseInt(layers[i].style.zIndex, 10);
176
177 if (!isNaN(zIndex)) {
178 edgeZIndex = compare(edgeZIndex, zIndex);
179 }
180 }
181 }
182
183 this._container.style.zIndex = isFinite(edgeZIndex) ? edgeZIndex + compare(1, -1) : '';
184 },
185
186 _updateOpacity: function () {
187 L.DomUtil.setOpacity(this._container, this.options.opacity);
82188
83189 // stupid webkit hack to force redrawing of tiles
190 var i,
191 tiles = this._tiles;
192
84193 if (L.Browser.webkit) {
85 for (var i in this._tiles) {
86 if (this._tiles.hasOwnProperty(i)) {
87 this._tiles[i].style.webkitTransform += ' translate(0,0)';
194 for (i in tiles) {
195 if (tiles.hasOwnProperty(i)) {
196 tiles[i].style.webkitTransform += ' translate(0,0)';
88197 }
89198 }
90199 }
91200 },
92201
93 _setOpacity: function (opacity) {
94 if (opacity < 1) {
95 L.DomUtil.setOpacity(this._container, opacity);
96 }
97 },
98
99202 _initContainer: function () {
100 var tilePane = this._map.getPanes().tilePane,
101 first = tilePane.firstChild;
203 var tilePane = this._map._panes.tilePane;
102204
103205 if (!this._container || tilePane.empty) {
104206 this._container = L.DomUtil.create('div', 'leaflet-layer');
105207
106 if (this._insertAtTheBottom && first) {
107 tilePane.insertBefore(this._container, first);
108 } else {
109 tilePane.appendChild(this._container);
110 }
111
112 this._setOpacity(this.options.opacity);
208 this._updateZIndex();
209
210 tilePane.appendChild(this._container);
211
212 if (this.options.opacity < 1) {
213 this._updateOpacity();
214 }
113215 }
114216 },
115217
118220 },
119221
120222 _reset: function (clearOldContainer) {
121 var key;
122 for (key in this._tiles) {
123 if (this._tiles.hasOwnProperty(key)) {
124 this.fire("tileunload", {tile: this._tiles[key]});
125 }
126 }
223 var key,
224 tiles = this._tiles;
225
226 for (key in tiles) {
227 if (tiles.hasOwnProperty(key)) {
228 this.fire('tileunload', {tile: tiles[key]});
229 }
230 }
231
127232 this._tiles = {};
233 this._tilesToLoad = 0;
128234
129235 if (this.options.reuseTiles) {
130236 this._unusedTiles = [];
133239 if (clearOldContainer && this._container) {
134240 this._container.innerHTML = "";
135241 }
242
136243 this._initContainer();
137244 },
138245
139 _update: function () {
140 var bounds = this._map.getPixelBounds(),
141 zoom = this._map.getZoom(),
142 tileSize = this.options.tileSize;
246 _update: function (e) {
247 if (this._map._panTransition && this._map._panTransition._inProgress) { return; }
248
249 var bounds = this._map.getPixelBounds(),
250 zoom = this._map.getZoom(),
251 tileSize = this.options.tileSize;
143252
144253 if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
145254 return;
164273 var queue = [],
165274 center = bounds.getCenter();
166275
167 for (var j = bounds.min.y; j <= bounds.max.y; j++) {
168 for (var i = bounds.min.x; i <= bounds.max.x; i++) {
169 if ((i + ':' + j) in this._tiles) {
170 continue;
276 var j, i, point;
277
278 for (j = bounds.min.y; j <= bounds.max.y; j++) {
279 for (i = bounds.min.x; i <= bounds.max.x; i++) {
280 point = new L.Point(i, j);
281
282 if (this._tileShouldBeLoaded(point)) {
283 queue.push(point);
171284 }
172 queue.push(new L.Point(i, j));
173 }
174 }
285 }
286 }
287
288 var tilesToLoad = queue.length;
289
290 if (tilesToLoad === 0) { return; }
175291
176292 // load tiles in order of their distance to center
177293 queue.sort(function (a, b) {
180296
181297 var fragment = document.createDocumentFragment();
182298
183 this._tilesToLoad = queue.length;
184 for (var k = 0, len = this._tilesToLoad; k < len; k++) {
185 this._addTile(queue[k], fragment);
299 // if its the first batch of tiles to load
300 if (!this._tilesToLoad) {
301 this.fire('loading');
302 }
303
304 this._tilesToLoad += tilesToLoad;
305
306 for (i = 0; i < tilesToLoad; i++) {
307 this._addTile(queue[i], fragment);
186308 }
187309
188310 this._container.appendChild(fragment);
189311 },
190312
313 _tileShouldBeLoaded: function (tilePoint) {
314 if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
315 return false; // already loaded
316 }
317
318 if (!this.options.continuousWorld) {
319 var limit = this._getWrapTileNum();
320
321 if (this.options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit) ||
322 tilePoint.y < 0 || tilePoint.y >= limit) {
323 return false; // exceeds world bounds
324 }
325 }
326
327 return true;
328 },
329
191330 _removeOtherTiles: function (bounds) {
192 var kArr, x, y, key, tile;
331 var kArr, x, y, key;
193332
194333 for (key in this._tiles) {
195334 if (this._tiles.hasOwnProperty(key)) {
199338
200339 // remove tile if it's out of bounds
201340 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
202
203 tile = this._tiles[key];
204 this.fire("tileunload", {tile: tile, url: tile.src});
205
206 if (tile.parentNode === this._container) {
207 this._container.removeChild(tile);
208 }
209 if (this.options.reuseTiles) {
210 this._unusedTiles.push(this._tiles[key]);
211 }
212 tile.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
213
214 delete this._tiles[key];
341 this._removeTile(key);
215342 }
216343 }
217344 }
218345 },
219346
347 _removeTile: function (key) {
348 var tile = this._tiles[key];
349
350 this.fire("tileunload", {tile: tile, url: tile.src});
351
352 if (this.options.reuseTiles) {
353 L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
354 this._unusedTiles.push(tile);
355 } else if (tile.parentNode === this._container) {
356 this._container.removeChild(tile);
357 }
358
359 if (!L.Browser.android) { //For https://github.com/CloudMade/Leaflet/issues/137
360 tile.src = L.Util.emptyImageUrl;
361 }
362
363 delete this._tiles[key];
364 },
365
220366 _addTile: function (tilePoint, container) {
221 var tilePos = this._getTilePos(tilePoint),
222 zoom = this._map.getZoom(),
223 key = tilePoint.x + ':' + tilePoint.y,
224 tileLimit = Math.pow(2, this._getOffsetZoom(zoom));
225
226 // wrap tile coordinates
227 if (!this.options.continuousWorld) {
228 if (!this.options.noWrap) {
229 tilePoint.x = ((tilePoint.x % tileLimit) + tileLimit) % tileLimit;
230 } else if (tilePoint.x < 0 || tilePoint.x >= tileLimit) {
231 this._tilesToLoad--;
232 return;
233 }
234
235 if (tilePoint.y < 0 || tilePoint.y >= tileLimit) {
236 this._tilesToLoad--;
237 return;
238 }
239 }
367 var tilePos = this._getTilePos(tilePoint);
240368
241369 // get unused tile - or create a new tile
242370 var tile = this._getTile();
243 L.DomUtil.setPosition(tile, tilePos);
244
245 this._tiles[key] = tile;
246
247 if (this.options.scheme === 'tms') {
248 tilePoint.y = tileLimit - tilePoint.y - 1;
249 }
250
251 this._loadTile(tile, tilePoint, zoom);
252
253 container.appendChild(tile);
254 },
255
256 _getOffsetZoom: function (zoom) {
257 zoom = this.options.zoomReverse ? this.options.maxZoom - zoom : zoom;
258 return zoom + this.options.zoomOffset;
371
372 // Chrome 20 layouts much faster with top/left (Verify with timeline, frames), Safari 5.1.7, iOS 5.1.1,
373 // android browser (4.0) have display issues with top/left and requires transform instead
374 // (other browsers don't currently care) - see debug/hacks/jitter.html for an example
375 L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
376
377 this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
378
379 this._loadTile(tile, tilePoint);
380
381 if (tile.parentNode !== this._container) {
382 container.appendChild(tile);
383 }
384 },
385
386 _getZoomForUrl: function () {
387
388 var options = this.options,
389 zoom = this._map.getZoom();
390
391 if (options.zoomReverse) {
392 zoom = options.maxZoom - zoom;
393 }
394
395 return zoom + options.zoomOffset;
259396 },
260397
261398 _getTilePos: function (tilePoint) {
267404
268405 // image-specific code (override to implement e.g. Canvas or SVG tile layer)
269406
270 getTileUrl: function (tilePoint, zoom) {
271 var subdomains = this.options.subdomains,
272 s = this.options.subdomains[(tilePoint.x + tilePoint.y) % subdomains.length];
407 getTileUrl: function (tilePoint) {
408 this._adjustTilePoint(tilePoint);
273409
274410 return L.Util.template(this._url, L.Util.extend({
275 s: s,
276 z: this._getOffsetZoom(zoom),
411 s: this._getSubdomain(tilePoint),
412 z: this._getZoomForUrl(),
277413 x: tilePoint.x,
278414 y: tilePoint.y
279 }, this._urlParams));
415 }, this.options));
416 },
417
418 _getWrapTileNum: function () {
419 // TODO refactor, limit is not valid for non-standard projections
420 return Math.pow(2, this._getZoomForUrl());
421 },
422
423 _adjustTilePoint: function (tilePoint) {
424
425 var limit = this._getWrapTileNum();
426
427 // wrap tile coordinates
428 if (!this.options.continuousWorld && !this.options.noWrap) {
429 tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
430 }
431
432 if (this.options.tms) {
433 tilePoint.y = limit - tilePoint.y - 1;
434 }
435 },
436
437 _getSubdomain: function (tilePoint) {
438 var index = (tilePoint.x + tilePoint.y) % this.options.subdomains.length;
439 return this.options.subdomains[index];
280440 },
281441
282442 _createTileProto: function () {
283 this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
284 this._tileImg.galleryimg = 'no';
443 var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
444 img.galleryimg = 'no';
285445
286446 var tileSize = this.options.tileSize;
287 this._tileImg.style.width = tileSize + 'px';
288 this._tileImg.style.height = tileSize + 'px';
447 img.style.width = tileSize + 'px';
448 img.style.height = tileSize + 'px';
289449 },
290450
291451 _getTile: function () {
307467 return tile;
308468 },
309469
310 _loadTile: function (tile, tilePoint, zoom) {
311 tile._layer = this;
312 tile.onload = this._tileOnLoad;
470 _loadTile: function (tile, tilePoint) {
471 tile._layer = this;
472 tile.onload = this._tileOnLoad;
313473 tile.onerror = this._tileOnError;
314 tile.src = this.getTileUrl(tilePoint, zoom);
315 },
474
475 tile.src = this.getTileUrl(tilePoint);
476 },
477
478 _tileLoaded: function () {
479 this._tilesToLoad--;
480 if (!this._tilesToLoad) {
481 this.fire('load');
482 }
483 },
316484
317485 _tileOnLoad: function (e) {
318486 var layer = this._layer;
319487
320 this.className += ' leaflet-tile-loaded';
321
322 layer.fire('tileload', {tile: this, url: this.src});
323
324 layer._tilesToLoad--;
325 if (!layer._tilesToLoad) {
326 layer.fire('load');
327 }
488 //Only if we are loading an actual image
489 if (this.src !== L.Util.emptyImageUrl) {
490 L.DomUtil.addClass(this, 'leaflet-tile-loaded');
491
492 layer.fire('tileload', {
493 tile: this,
494 url: this.src
495 });
496 }
497
498 layer._tileLoaded();
328499 },
329500
330501 _tileOnError: function (e) {
331502 var layer = this._layer;
332503
333 layer.fire('tileerror', {tile: this, url: this.src});
504 layer.fire('tileerror', {
505 tile: this,
506 url: this.src
507 });
334508
335509 var newUrl = layer.options.errorTileUrl;
336510 if (newUrl) {
337511 this.src = newUrl;
338512 }
339 }
513
514 layer._tileLoaded();
515 }
340516 });
517
518 L.tileLayer = function (url, options) {
519 return new L.TileLayer(url, options);
520 };
55 initialize: function (latlng, radius, options) {
66 L.Path.prototype.initialize.call(this, options);
77
8 this._latlng = latlng;
8 this._latlng = L.latLng(latlng);
99 this._mRadius = radius;
1010 },
1111
1414 },
1515
1616 setLatLng: function (latlng) {
17 this._latlng = latlng;
18 this._redraw();
19 return this;
17 this._latlng = L.latLng(latlng);
18 return this.redraw();
2019 },
2120
2221 setRadius: function (radius) {
2322 this._mRadius = radius;
24 this._redraw();
25 return this;
23 return this.redraw();
2624 },
2725
2826 projectLatlngs: function () {
29 var equatorLength = 40075017,
30 hLength = equatorLength * Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
31
32 var lngSpan = (this._mRadius / hLength) * 360,
33 latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngSpan, true),
27 var lngRadius = this._getLngRadius(),
28 latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
3429 point2 = this._map.latLngToLayerPoint(latlng2);
3530
3631 this._point = this._map.latLngToLayerPoint(this._latlng);
37 this._radius = Math.round(this._point.x - point2.x);
32 this._radius = Math.max(Math.round(this._point.x - point2.x), 1);
33 },
34
35 getBounds: function () {
36 var map = this._map,
37 delta = this._radius * Math.cos(Math.PI / 4),
38 point = map.project(this._latlng),
39 swPoint = new L.Point(point.x - delta, point.y + delta),
40 nePoint = new L.Point(point.x + delta, point.y - delta),
41 sw = map.unproject(swPoint),
42 ne = map.unproject(nePoint);
43
44 return new L.LatLngBounds(sw, ne);
45 },
46
47 getLatLng: function () {
48 return this._latlng;
3849 },
3950
4051 getPathString: function () {
5667 }
5768 },
5869
70 getRadius: function () {
71 return this._mRadius;
72 },
73
74 _getLngRadius: function () {
75 var equatorLength = 40075017,
76 hLength = equatorLength * Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
77
78 return (this._mRadius / hLength) * 360;
79 },
80
5981 _checkIfEmpty: function () {
82 if (!this._map) {
83 return false;
84 }
6085 var vp = this._map._pathViewport,
6186 r = this._radius,
6287 p = this._point;
6590 p.x + r < vp.min.x || p.y + r < vp.min.y;
6691 }
6792 });
93
94 L.circle = function (latlng, radius, options) {
95 return new L.Circle(latlng, radius, options);
96 };
1818
1919 setRadius: function (radius) {
2020 this._radius = radius;
21 this._redraw();
22 return this;
21 return this.redraw();
2322 }
2423 });
24
25 L.circleMarker = function (latlng, options) {
26 return new L.CircleMarker(latlng, options);
27 };
1313 setLatLngs: function (latlngs) {
1414 var i = 0, len = latlngs.length;
1515
16 this._iterateLayers(function (layer) {
16 this.eachLayer(function (layer) {
1717 if (i < len) {
1818 layer.setLatLngs(latlngs[i++]);
1919 } else {
2424 while (i < len) {
2525 this.addLayer(new Klass(latlngs[i++], this._options));
2626 }
27
28 return this;
2729 }
2830 });
2931 }
3032
3133 L.MultiPolyline = createMulti(L.Polyline);
3234 L.MultiPolygon = createMulti(L.Polygon);
35
36 L.multiPolyline = function (latlngs, options) {
37 return new L.MultiPolyline(latlngs, options);
38 };
39
40 L.multiPolygon = function (latlngs, options) {
41 return new L.MultiPolygon(latlngs, options);
42 };
3343 }());
22 */
33
44 L.Path.include({
5
56 bindPopup: function (content, options) {
7
68 if (!this._popup || this._popup.options !== options) {
79 this._popup = new L.Popup(options, this);
810 }
11
912 this._popup.setContent(content);
1013
1114 if (!this._openPopupAdded) {
1619 return this;
1720 },
1821
22 openPopup: function (latlng) {
23
24 if (this._popup) {
25 latlng = latlng || this._latlng ||
26 this._latlngs[Math.floor(this._latlngs.length / 2)];
27
28 this._openPopup({latlng: latlng});
29 }
30
31 return this;
32 },
33
1934 _openPopup: function (e) {
2035 this._popup.setLatLng(e.latlng);
2136 this._map.openPopup(this._popup);
33
44 L.Path = L.Path.extend({
55 statics: {
6 SVG: L.Browser.svg,
7 _createElement: function (name) {
8 return document.createElementNS(L.Path.SVG_NS, name);
6 SVG: L.Browser.svg
7 },
8
9 bringToFront: function () {
10 if (this._container) {
11 this._map._pathRoot.appendChild(this._container);
12 }
13 },
14
15 bringToBack: function () {
16 if (this._container) {
17 var root = this._map._pathRoot;
18 root.insertBefore(this._container, root.firstChild);
919 }
1020 },
1121
1222 getPathString: function () {
1323 // form path string here
24 },
25
26 _createElement: function (name) {
27 return document.createElementNS(L.Path.SVG_NS, name);
1428 },
1529
1630 _initElements: function () {
2034 },
2135
2236 _initPath: function () {
23 this._container = L.Path._createElement('g');
24
25 this._path = L.Path._createElement('path');
37 this._container = this._createElement('g');
38
39 this._path = this._createElement('path');
2640 this._container.appendChild(this._path);
27
28 this._map._pathRoot.appendChild(this._container);
2941 },
3042
3143 _initStyle: function () {
3547 }
3648 if (this.options.fill) {
3749 this._path.setAttribute('fill-rule', 'evenodd');
38 } else {
39 this._path.setAttribute('fill', 'none');
4050 }
4151 this._updateStyle();
4252 },
4656 this._path.setAttribute('stroke', this.options.color);
4757 this._path.setAttribute('stroke-opacity', this.options.opacity);
4858 this._path.setAttribute('stroke-width', this.options.weight);
59 if (this.options.dashArray) {
60 this._path.setAttribute('stroke-dasharray', this.options.dashArray);
61 } else {
62 this._path.removeAttribute('stroke-dasharray');
63 }
64 } else {
65 this._path.setAttribute('stroke', 'none');
4966 }
5067 if (this.options.fill) {
5168 this._path.setAttribute('fill', this.options.fillColor || this.options.color);
5269 this._path.setAttribute('fill-opacity', this.options.fillOpacity);
70 } else {
71 this._path.setAttribute('fill', 'none');
5372 }
5473 },
5574
6584 // TODO remove duplication with L.Map
6685 _initEvents: function () {
6786 if (this.options.clickable) {
68 if (!L.Browser.vml) {
87 if (L.Browser.svg || !L.Browser.vml) {
6988 this._path.setAttribute('class', 'leaflet-clickable');
7089 }
7190
72 L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
73
74 var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'mousemove'];
91 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
92
93 var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'mousemove', 'contextmenu'];
7594 for (var i = 0; i < events.length; i++) {
76 L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this);
95 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
7796 }
7897 }
7998 },
82101 if (this._map.dragging && this._map.dragging.moved()) {
83102 return;
84103 }
104
85105 this._fireMouseEvent(e);
106
107 L.DomEvent.stopPropagation(e);
86108 },
87109
88110 _fireMouseEvent: function (e) {
89111 if (!this.hasEventListeners(e.type)) {
90112 return;
91113 }
114
115 if (e.type === 'contextmenu') {
116 L.DomEvent.preventDefault(e);
117 }
118
119 var map = this._map,
120 containerPoint = map.mouseEventToContainerPoint(e),
121 layerPoint = map.containerPointToLayerPoint(containerPoint),
122 latlng = map.layerPointToLatLng(layerPoint);
123
92124 this.fire(e.type, {
93 latlng: this._map.mouseEventToLatLng(e),
94 layerPoint: this._map.mouseEventToLayerPoint(e)
125 latlng: latlng,
126 layerPoint: layerPoint,
127 containerPoint: containerPoint,
128 originalEvent: e
95129 });
96 L.DomEvent.stopPropagation(e);
97130 }
98131 });
99132
100133 L.Map.include({
101134 _initPathRoot: function () {
102135 if (!this._pathRoot) {
103 this._pathRoot = L.Path._createElement('svg');
136 this._pathRoot = L.Path.prototype._createElement('svg');
104137 this._panes.overlayPane.appendChild(this._pathRoot);
138
139 if (this.options.zoomAnimation && L.Browser.any3d) {
140 this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
141
142 this.on({
143 'zoomanim': this._animatePathZoom,
144 'zoomend': this._endPathZoom
145 });
146 } else {
147 this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
148 }
105149
106150 this.on('moveend', this._updateSvgViewport);
107151 this._updateSvgViewport();
108152 }
109153 },
110154
155 _animatePathZoom: function (opt) {
156 var scale = this.getZoomScale(opt.zoom),
157 offset = this._getCenterOffset(opt.center).divideBy(1 - 1 / scale),
158 viewportPos = this.containerPointToLayerPoint(this.getSize().multiplyBy(-L.Path.CLIP_PADDING)),
159 origin = viewportPos.add(offset).round();
160
161 this._pathRoot.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString((origin.multiplyBy(-1).add(L.DomUtil.getPosition(this._pathRoot)).multiplyBy(scale).add(origin))) + ' scale(' + scale + ') ';
162
163 this._pathZooming = true;
164 },
165
166 _endPathZoom: function () {
167 this._pathZooming = false;
168 },
169
111170 _updateSvgViewport: function () {
171 if (this._pathZooming) {
172 // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
173 // When the zoom animation ends we will be updated again anyway
174 // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
175 return;
176 }
177
112178 this._updatePathViewport();
113179
114180 var vp = this._pathViewport,
120186 pane = this._panes.overlayPane;
121187
122188 // Hack to make flicker on drag end on mobile webkit less irritating
123 // Unfortunately I haven't found a good workaround for this yet
124 if (L.Browser.webkit) {
189 if (L.Browser.mobileWebkit) {
125190 pane.removeChild(root);
126191 }
127192
130195 root.setAttribute('height', height);
131196 root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
132197
133 if (L.Browser.webkit) {
198 if (L.Browser.mobileWebkit) {
134199 pane.appendChild(root);
135200 }
136201 }
33 */
44
55 L.Browser.vml = (function () {
6 var d = document.createElement('div'), s;
7 d.innerHTML = '<v:shape adj="1"/>';
8 s = d.firstChild;
9 s.style.behavior = 'url(#default#VML)';
6 try {
7 var div = document.createElement('div');
8 div.innerHTML = '<v:shape adj="1"/>';
109
11 return (s && (typeof s.adj === 'object'));
10 var shape = div.firstChild;
11 shape.style.behavior = 'url(#default#VML)';
12
13 return shape && (typeof shape.adj === 'object');
14 } catch (e) {
15 return false;
16 }
1217 }());
1318
1419 L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
1520 statics: {
1621 VML: true,
17 CLIP_PADDING: 0.02,
18 _createElement: (function () {
19 try {
20 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
21 return function (name) {
22 return document.createElement('<lvml:' + name + ' class="lvml">');
23 };
24 } catch (e) {
25 return function (name) {
26 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
27 };
28 }
29 }())
22 CLIP_PADDING: 0.02
3023 },
3124
25 _createElement: (function () {
26 try {
27 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
28 return function (name) {
29 return document.createElement('<lvml:' + name + ' class="lvml">');
30 };
31 } catch (e) {
32 return function (name) {
33 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
34 };
35 }
36 }()),
37
3238 _initPath: function () {
33 this._container = L.Path._createElement('shape');
34 this._container.className += ' leaflet-vml-shape' +
35 (this.options.clickable ? ' leaflet-clickable' : '');
36 this._container.coordsize = '1 1';
39 var container = this._container = this._createElement('shape');
40 L.DomUtil.addClass(container, 'leaflet-vml-shape');
41 if (this.options.clickable) {
42 L.DomUtil.addClass(container, 'leaflet-clickable');
43 }
44 container.coordsize = '1 1';
3745
38 this._path = L.Path._createElement('path');
39 this._container.appendChild(this._path);
46 this._path = this._createElement('path');
47 container.appendChild(this._path);
4048
41 this._map._pathRoot.appendChild(this._container);
49 this._map._pathRoot.appendChild(container);
4250 },
4351
4452 _initStyle: function () {
45 if (this.options.stroke) {
46 this._stroke = L.Path._createElement('stroke');
47 this._stroke.endcap = 'round';
48 this._container.appendChild(this._stroke);
49 } else {
50 this._container.stroked = false;
51 }
52 if (this.options.fill) {
53 this._container.filled = true;
54 this._fill = L.Path._createElement('fill');
55 this._container.appendChild(this._fill);
56 } else {
57 this._container.filled = false;
58 }
5953 this._updateStyle();
6054 },
6155
6256 _updateStyle: function () {
63 if (this.options.stroke) {
64 this._stroke.weight = this.options.weight + 'px';
65 this._stroke.color = this.options.color;
66 this._stroke.opacity = this.options.opacity;
57 var stroke = this._stroke,
58 fill = this._fill,
59 options = this.options,
60 container = this._container;
61
62 container.stroked = options.stroke;
63 container.filled = options.fill;
64
65 if (options.stroke) {
66 if (!stroke) {
67 stroke = this._stroke = this._createElement('stroke');
68 stroke.endcap = 'round';
69 container.appendChild(stroke);
70 }
71 stroke.weight = options.weight + 'px';
72 stroke.color = options.color;
73 stroke.opacity = options.opacity;
74 if (options.dashArray) {
75 stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
76 } else {
77 stroke.dashStyle = '';
78 }
79 } else if (stroke) {
80 container.removeChild(stroke);
81 this._stroke = null;
6782 }
68 if (this.options.fill) {
69 this._fill.color = this.options.fillColor || this.options.color;
70 this._fill.opacity = this.options.fillOpacity;
83
84 if (options.fill) {
85 if (!fill) {
86 fill = this._fill = this._createElement('fill');
87 container.appendChild(fill);
88 }
89 fill.color = options.fillColor || options.color;
90 fill.opacity = options.fillOpacity;
91 } else if (fill) {
92 container.removeChild(fill);
93 this._fill = null;
7194 }
7295 },
7396
7497 _updatePath: function () {
75 this._container.style.display = 'none';
98 var style = this._container.style;
99
100 style.display = 'none';
76101 this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
77 this._container.style.display = '';
102 style.display = '';
78103 }
79104 });
80105
81106 L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
82107 _initPathRoot: function () {
83 if (!this._pathRoot) {
84 this._pathRoot = document.createElement('div');
85 this._pathRoot.className = 'leaflet-vml-container';
86 this._panes.overlayPane.appendChild(this._pathRoot);
108 if (this._pathRoot) { return; }
87109
88 this.on('moveend', this._updatePathViewport);
89 this._updatePathViewport();
90 }
110 var root = this._pathRoot = document.createElement('div');
111 root.className = 'leaflet-vml-container';
112 this._panes.overlayPane.appendChild(root);
113
114 this.on('moveend', this._updatePathViewport);
115 this._updatePathViewport();
91116 }
92117 });
77 statics: {
88 // how much to extend the clip area around the map view
99 // (relative to its size, e.g. 0.5 is half the screen in each direction)
10 CLIP_PADDING: 0.5
10 // set in such way that SVG element doesn't exceed 1280px (vector layers flicker on dragend if it is)
11 CLIP_PADDING: L.Browser.mobile ?
12 Math.max(0, Math.min(0.5,
13 (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2))
14 : 0.5
1115 },
1216
1317 options: {
1418 stroke: true,
1519 color: '#0033ff',
20 dashArray: null,
1621 weight: 5,
1722 opacity: 0.5,
1823
2025 fillColor: null, //same as color by default
2126 fillOpacity: 0.2,
2227
23 clickable: true,
24
25 // TODO remove this, as all paths now update on moveend
26 updateOnMoveEnd: true
28 clickable: true
2729 },
2830
2931 initialize: function (options) {
3335 onAdd: function (map) {
3436 this._map = map;
3537
36 this._initElements();
37 this._initEvents();
38 if (!this._container) {
39 this._initElements();
40 this._initEvents();
41 }
42
3843 this.projectLatlngs();
3944 this._updatePath();
4045
41 map.on('viewreset', this.projectLatlngs, this);
46 if (this._container) {
47 this._map._pathRoot.appendChild(this._container);
48 }
4249
43 this._updateTrigger = this.options.updateOnMoveEnd ? 'moveend' : 'viewreset';
44 map.on(this._updateTrigger, this._updatePath, this);
50 map.on({
51 'viewreset': this.projectLatlngs,
52 'moveend': this._updatePath
53 }, this);
54 },
55
56 addTo: function (map) {
57 map.addLayer(this);
58 return this;
4559 },
4660
4761 onRemove: function (map) {
62 map._pathRoot.removeChild(this._container);
63
4864 this._map = null;
4965
50 map._pathRoot.removeChild(this._container);
66 if (L.Browser.vml) {
67 this._container = null;
68 this._stroke = null;
69 this._fill = null;
70 }
5171
52 map.off('viewreset', this.projectLatlngs, this);
53 map.off(this._updateTrigger, this._updatePath, this);
72 map.off({
73 'viewreset': this.projectLatlngs,
74 'moveend': this._updatePath
75 }, this);
5476 },
5577
5678 projectLatlngs: function () {
5981
6082 setStyle: function (style) {
6183 L.Util.setOptions(this, style);
84
6285 if (this._container) {
6386 this._updateStyle();
6487 }
88
6589 return this;
6690 },
6791
68 _redraw: function () {
92 redraw: function () {
6993 if (this._map) {
7094 this.projectLatlngs();
7195 this._updatePath();
7296 }
97 return this;
7398 }
7499 });
75100
77102 _updatePathViewport: function () {
78103 var p = L.Path.CLIP_PADDING,
79104 size = this.getSize(),
80 //TODO this._map._getMapPanePos()
81105 panePos = L.DomUtil.getPosition(this._mapPane),
82 min = panePos.multiplyBy(-1).subtract(size.multiplyBy(p)),
106 min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)),
83107 max = min.add(size.multiplyBy(1 + p * 2));
84108
85109 this._pathViewport = new L.Bounds(min, max);
99 initialize: function (latlngs, options) {
1010 L.Polyline.prototype.initialize.call(this, latlngs, options);
1111
12 if (latlngs && (latlngs[0] instanceof Array)) {
13 this._latlngs = latlngs[0];
12 if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
13 this._latlngs = this._convertLatLngs(latlngs[0]);
1414 this._holes = latlngs.slice(1);
1515 }
1616 },
6161 return str + (L.Browser.svg ? 'z' : 'x');
6262 }
6363 });
64
65 L.polygon = function (latlngs, options) {
66 return new L.Polygon(latlngs, options);
67 };
0 L.Handler.PolyEdit = L.Handler.extend({
1 options: {
2 icon: new L.DivIcon({
3 iconSize: new L.Point(8, 8),
4 className: 'leaflet-div-icon leaflet-editing-icon'
5 })
6 },
7
8 initialize: function (poly, options) {
9 this._poly = poly;
10 L.Util.setOptions(this, options);
11 },
12
13 addHooks: function () {
14 if (this._poly._map) {
15 if (!this._markerGroup) {
16 this._initMarkers();
17 }
18 this._poly._map.addLayer(this._markerGroup);
19 }
20 },
21
22 removeHooks: function () {
23 if (this._poly._map) {
24 this._poly._map.removeLayer(this._markerGroup);
25 delete this._markerGroup;
26 delete this._markers;
27 }
28 },
29
30 updateMarkers: function () {
31 this._markerGroup.clearLayers();
32 this._initMarkers();
33 },
34
35 _initMarkers: function () {
36 if (!this._markerGroup) {
37 this._markerGroup = new L.LayerGroup();
38 }
39 this._markers = [];
40
41 var latlngs = this._poly._latlngs,
42 i, j, len, marker;
43
44 // TODO refactor holes implementation in Polygon to support it here
45
46 for (i = 0, len = latlngs.length; i < len; i++) {
47
48 marker = this._createMarker(latlngs[i], i);
49 marker.on('click', this._onMarkerClick, this);
50 this._markers.push(marker);
51 }
52
53 var markerLeft, markerRight;
54
55 for (i = 0, j = len - 1; i < len; j = i++) {
56 if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
57 continue;
58 }
59
60 markerLeft = this._markers[j];
61 markerRight = this._markers[i];
62
63 this._createMiddleMarker(markerLeft, markerRight);
64 this._updatePrevNext(markerLeft, markerRight);
65 }
66 },
67
68 _createMarker: function (latlng, index) {
69 var marker = new L.Marker(latlng, {
70 draggable: true,
71 icon: this.options.icon
72 });
73
74 marker._origLatLng = latlng;
75 marker._index = index;
76
77 marker.on('drag', this._onMarkerDrag, this);
78 marker.on('dragend', this._fireEdit, this);
79
80 this._markerGroup.addLayer(marker);
81
82 return marker;
83 },
84
85 _fireEdit: function () {
86 this._poly.fire('edit');
87 },
88
89 _onMarkerDrag: function (e) {
90 var marker = e.target;
91
92 L.Util.extend(marker._origLatLng, marker._latlng);
93
94 if (marker._middleLeft) {
95 marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
96 }
97 if (marker._middleRight) {
98 marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
99 }
100
101 this._poly.redraw();
102 },
103
104 _onMarkerClick: function (e) {
105 // Default action on marker click is to remove that marker, but if we remove the marker when latlng count < 3, we don't have a valid polyline anymore
106 if (this._poly._latlngs.length < 3) {
107 return;
108 }
109
110 var marker = e.target,
111 i = marker._index;
112
113 // Check existence of previous and next markers since they wouldn't exist for edge points on the polyline
114 if (marker._prev && marker._next) {
115 this._createMiddleMarker(marker._prev, marker._next);
116 this._updatePrevNext(marker._prev, marker._next);
117 }
118
119 // The marker itself is guaranteed to exist and present in the layer, since we managed to click on it
120 this._markerGroup.removeLayer(marker);
121 // Check for the existence of middle left or middle right
122 if (marker._middleLeft) {
123 this._markerGroup.removeLayer(marker._middleLeft);
124 }
125 if (marker._middleRight) {
126 this._markerGroup.removeLayer(marker._middleRight);
127 }
128 this._markers.splice(i, 1);
129 this._poly.spliceLatLngs(i, 1);
130 this._updateIndexes(i, -1);
131 this._poly.fire('edit');
132 },
133
134 _updateIndexes: function (index, delta) {
135 this._markerGroup.eachLayer(function (marker) {
136 if (marker._index > index) {
137 marker._index += delta;
138 }
139 });
140 },
141
142 _createMiddleMarker: function (marker1, marker2) {
143 var latlng = this._getMiddleLatLng(marker1, marker2),
144 marker = this._createMarker(latlng),
145 onClick,
146 onDragStart,
147 onDragEnd;
148
149 marker.setOpacity(0.6);
150
151 marker1._middleRight = marker2._middleLeft = marker;
152
153 onDragStart = function () {
154 var i = marker2._index;
155
156 marker._index = i;
157
158 marker
159 .off('click', onClick)
160 .on('click', this._onMarkerClick, this);
161
162 latlng.lat = marker.getLatLng().lat;
163 latlng.lng = marker.getLatLng().lng;
164 this._poly.spliceLatLngs(i, 0, latlng);
165 this._markers.splice(i, 0, marker);
166
167 marker.setOpacity(1);
168
169 this._updateIndexes(i, 1);
170 marker2._index++;
171 this._updatePrevNext(marker1, marker);
172 this._updatePrevNext(marker, marker2);
173 };
174
175 onDragEnd = function () {
176 marker.off('dragstart', onDragStart, this);
177 marker.off('dragend', onDragEnd, this);
178
179 this._createMiddleMarker(marker1, marker);
180 this._createMiddleMarker(marker, marker2);
181 };
182
183 onClick = function () {
184 onDragStart.call(this);
185 onDragEnd.call(this);
186 this._poly.fire('edit');
187 };
188
189 marker
190 .on('click', onClick, this)
191 .on('dragstart', onDragStart, this)
192 .on('dragend', onDragEnd, this);
193
194 this._markerGroup.addLayer(marker);
195 },
196
197 _updatePrevNext: function (marker1, marker2) {
198 marker1._next = marker2;
199 marker2._prev = marker1;
200 },
201
202 _getMiddleLatLng: function (marker1, marker2) {
203 var map = this._poly._map,
204 p1 = map.latLngToLayerPoint(marker1.getLatLng()),
205 p2 = map.latLngToLayerPoint(marker2.getLatLng());
206
207 return map.layerPointToLatLng(p1._add(p2).divideBy(2));
208 }
209 });
0
10 L.Polyline = L.Path.extend({
21 initialize: function (latlngs, options) {
32 L.Path.prototype.initialize.call(this, options);
4 this._latlngs = latlngs;
3
4 this._latlngs = this._convertLatLngs(latlngs);
5
6 // TODO refactor: move to Polyline.Edit.js
7 if (L.Handler.PolyEdit) {
8 this.editing = new L.Handler.PolyEdit(this);
9
10 if (this.options.editable) {
11 this.editing.enable();
12 }
13 }
514 },
615
716 options: {
817 // how much to simplify the polyline on each zoom level
918 // more = better performance and smoother look, less = more accurate
1019 smoothFactor: 1.0,
11 noClip: false,
12
13 updateOnMoveEnd: true
20 noClip: false
1421 },
1522
1623 projectLatlngs: function () {
3340 },
3441
3542 setLatLngs: function (latlngs) {
36 this._latlngs = latlngs;
37 this._redraw();
38 return this;
43 this._latlngs = this._convertLatLngs(latlngs);
44 return this.redraw();
3945 },
4046
4147 addLatLng: function (latlng) {
42 this._latlngs.push(latlng);
43 this._redraw();
44 return this;
48 this._latlngs.push(L.latLng(latlng));
49 return this.redraw();
4550 },
4651
4752 spliceLatLngs: function (index, howMany) {
4853 var removed = [].splice.apply(this._latlngs, arguments);
49 this._redraw();
54 this._convertLatLngs(this._latlngs);
55 this.redraw();
5056 return removed;
5157 },
5258
5864 for (var i = 1, len = points.length; i < len; i++) {
5965 p1 = points[i - 1];
6066 p2 = points[i];
61 var point = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
62 if (point._sqDist < minDistance) {
63 minDistance = point._sqDist;
64 minPoint = point;
67 var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
68 if (sqDist < minDistance) {
69 minDistance = sqDist;
70 minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
6571 }
6672 }
6773 }
7884 b.extend(latLngs[i]);
7985 }
8086 return b;
87 },
88
89 // TODO refactor: move to Polyline.Edit.js
90 onAdd: function (map) {
91 L.Path.prototype.onAdd.call(this, map);
92
93 if (this.editing && this.editing.enabled()) {
94 this.editing.addHooks();
95 }
96 },
97
98 onRemove: function (map) {
99 if (this.editing && this.editing.enabled()) {
100 this.editing.removeHooks();
101 }
102
103 L.Path.prototype.onRemove.call(this, map);
104 },
105
106 _convertLatLngs: function (latlngs) {
107 var i, len;
108 for (i = 0, len = latlngs.length; i < len; i++) {
109 if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
110 return;
111 }
112 latlngs[i] = L.latLng(latlngs[i]);
113 }
114 return latlngs;
115 },
116
117 _initEvents: function () {
118 L.Path.prototype._initEvents.call(this);
81119 },
82120
83121 _getPathPartStr: function (points) {
137175 },
138176
139177 _updatePath: function () {
178 if (!this._map) { return; }
179
140180 this._clipPoints();
141181 this._simplifyPoints();
142182
143183 L.Path.prototype._updatePath.call(this);
144184 }
145185 });
186
187 L.polyline = function (latlngs, options) {
188 return new L.Polyline(latlngs, options);
189 };
0 /*
1 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
2 */
3
4 L.Rectangle = L.Polygon.extend({
5 initialize: function (latLngBounds, options) {
6 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
7 },
8
9 setBounds: function (latLngBounds) {
10 this.setLatLngs(this._boundsToLatLngs(latLngBounds));
11 },
12
13 _boundsToLatLngs: function (latLngBounds) {
14 latLngBounds = L.latLngBounds(latLngBounds);
15 return [
16 latLngBounds.getSouthWest(),
17 latLngBounds.getNorthWest(),
18 latLngBounds.getNorthEast(),
19 latLngBounds.getSouthEast(),
20 latLngBounds.getSouthWest()
21 ];
22 }
23 });
24
25 L.rectangle = function (latLngBounds, options) {
26 return new L.Rectangle(latLngBounds, options);
27 };
55 _drawPath: function () {
66 var p = this._point;
77 this._ctx.beginPath();
8 this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2);
8 this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
99 },
1010
1111 _containsPoint: function (p) {
1212 SVG: false
1313 },
1414
15 options: {
16 updateOnMoveEnd: true
15 redraw: function () {
16 if (this._map) {
17 this.projectLatlngs();
18 this._requestUpdate();
19 }
20 return this;
21 },
22
23 setStyle: function (style) {
24 L.Util.setOptions(this, style);
25
26 if (this._map) {
27 this._updateStyle();
28 this._requestUpdate();
29 }
30 return this;
31 },
32
33 onRemove: function (map) {
34 map
35 .off('viewreset', this.projectLatlngs, this)
36 .off('moveend', this._updatePath, this);
37
38 this._requestUpdate();
39
40 this._map = null;
41 },
42
43 _requestUpdate: function () {
44 if (this._map) {
45 L.Util.cancelAnimFrame(this._fireMapMoveEnd);
46 this._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
47 }
48 },
49
50 _fireMapMoveEnd: function () {
51 this.fire('moveend');
1752 },
1853
1954 _initElements: function () {
2257 },
2358
2459 _updateStyle: function () {
25 if (this.options.stroke) {
26 this._ctx.lineWidth = this.options.weight;
27 this._ctx.strokeStyle = this.options.color;
60 var options = this.options;
61
62 if (options.stroke) {
63 this._ctx.lineWidth = options.weight;
64 this._ctx.strokeStyle = options.color;
2865 }
29 if (this.options.fill) {
30 this._ctx.fillStyle = this.options.fillColor || this.options.color;
66 if (options.fill) {
67 this._ctx.fillStyle = options.fillColor || options.color;
3168 }
3269 },
3370
5592 },
5693
5794 _updatePath: function () {
58 if (this._checkIfEmpty()) {
59 return;
95 if (this._checkIfEmpty()) { return; }
96
97 var ctx = this._ctx,
98 options = this.options;
99
100 this._drawPath();
101 ctx.save();
102 this._updateStyle();
103
104 if (options.fill) {
105 if (options.fillOpacity < 1) {
106 ctx.globalAlpha = options.fillOpacity;
107 }
108 ctx.fill();
60109 }
61110
62 this._drawPath();
63
64 this._ctx.save();
65
66 this._updateStyle();
67
68 var opacity = this.options.opacity,
69 fillOpacity = this.options.fillOpacity;
70
71 if (this.options.fill) {
72 if (fillOpacity < 1) {
73 this._ctx.globalAlpha = fillOpacity;
111 if (options.stroke) {
112 if (options.opacity < 1) {
113 ctx.globalAlpha = options.opacity;
74114 }
75 this._ctx.fill();
115 ctx.stroke();
76116 }
77117
78 if (this.options.stroke) {
79 if (opacity < 1) {
80 this._ctx.globalAlpha = opacity;
81 }
82 this._ctx.stroke();
83 }
84
85 this._ctx.restore();
118 ctx.restore();
86119
87120 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
88121 },
99132 if (this._containsPoint(e.layerPoint)) {
100133 this.fire('click', e);
101134 }
102 },
103
104 onRemove: function (map) {
105 map.off('viewreset', this._projectLatlngs, this);
106 map.off(this._updateTrigger, this._updatePath, this);
107 map.fire(this._updateTrigger);
108 }
135 }
109136 });
110137
111138 L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
123150
124151 this._panes.overlayPane.appendChild(root);
125152
153 if (this.options.zoomAnimation) {
154 this._pathRoot.className = 'leaflet-zoom-animated';
155 this.on('zoomanim', this._animatePathZoom);
156 this.on('zoomend', this._endPathZoom);
157 }
126158 this.on('moveend', this._updateCanvasViewport);
127159 this._updateCanvasViewport();
128160 }
129161 },
130162
131163 _updateCanvasViewport: function () {
164 if (this._pathZooming) {
165 //Don't redraw while zooming. See _updateSvgViewport for more details
166 return;
167 }
132168 this._updatePathViewport();
133169
134170 var vp = this._pathViewport,
136172 size = vp.max.subtract(min),
137173 root = this._pathRoot;
138174
139 //TODO check if it's works properly on mobile webkit
175 //TODO check if this works properly on mobile webkit
140176 L.DomUtil.setPosition(root, min);
141177 root.width = size.x;
142178 root.height = size.y;
22 */
33
44 L.Map = L.Class.extend({
5
56 includes: L.Mixin.Events,
67
78 options: {
8 // projection
9 crs: L.CRS.EPSG3857 || L.CRS.EPSG4326,
10 scale: function (zoom) {
11 return 256 * Math.pow(2, zoom);
12 },
13
14 // state
15 center: null,
16 zoom: null,
17 layers: [],
18
19 // interaction
20 dragging: true,
21 touchZoom: L.Browser.touch && !L.Browser.android,
22 scrollWheelZoom: !L.Browser.touch,
23 doubleClickZoom: true,
24 boxZoom: true,
25
26 // controls
27 zoomControl: true,
28 attributionControl: true,
29
30 // animation
31 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android,
32 zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android && !L.Browser.mobileOpera,
33
34 // misc
9 crs: L.CRS.EPSG3857,
10
11 /*
12 center: LatLng,
13 zoom: Number,
14 layers: Array,
15 */
16
17 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
3518 trackResize: true,
36 closePopupOnClick: true,
37 worldCopyJump: true
38 },
39
40
41 // constructor
19 markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
20 },
4221
4322 initialize: function (id, options) { // (HTMLElement or String, Object)
44 L.Util.setOptions(this, options);
45
46 this._container = L.DomUtil.get(id);
47
48 if (this._container._leaflet) {
49 throw new Error("Map container is already initialized.");
50 }
51 this._container._leaflet = true;
52
23 options = L.Util.setOptions(this, options);
24
25 this._initContainer(id);
5326 this._initLayout();
54
55 if (L.DomEvent) {
56 this._initEvents();
57 if (L.Handler) {
58 this._initInteraction();
59 }
60 if (L.Control) {
61 this._initControls();
62 }
63 }
64
65 if (this.options.maxBounds) {
66 this.setMaxBounds(this.options.maxBounds);
67 }
68
69 var center = this.options.center,
70 zoom = this.options.zoom;
71
72 if (center !== null && zoom !== null) {
73 this.setView(center, zoom, true);
74 }
75
76 var layers = this.options.layers;
77 layers = (layers instanceof Array ? layers : [layers]);
78 this._tileLayersNum = 0;
79 this._initLayers(layers);
27 this._initHooks();
28 this._initEvents();
29
30 if (options.maxBounds) {
31 this.setMaxBounds(options.maxBounds);
32 }
33
34 if (options.center && options.zoom !== undefined) {
35 this.setView(L.latLng(options.center), options.zoom, true);
36 }
37
38 this._initLayers(options.layers);
8039 },
8140
8241
8443
8544 // replaced by animation-powered implementation in Map.PanAnimation.js
8645 setView: function (center, zoom) {
87 // reset the map view
88 this._resetView(center, this._limitZoom(zoom));
46 this._resetView(L.latLng(center), this._limitZoom(zoom));
8947 return this;
9048 },
9149
10361
10462 fitBounds: function (bounds) { // (LatLngBounds)
10563 var zoom = this.getBoundsZoom(bounds);
106 return this.setView(bounds.getCenter(), zoom);
64 return this.setView(L.latLngBounds(bounds).getCenter(), zoom);
10765 },
10866
10967 fitWorld: function () {
11068 var sw = new L.LatLng(-60, -170),
111 ne = new L.LatLng(85, 179);
69 ne = new L.LatLng(85, 179);
70
11271 return this.fitBounds(new L.LatLngBounds(sw, ne));
11372 },
11473
12079 // replaced with animated panBy in Map.Animation.js
12180 this.fire('movestart');
12281
123 this._rawPanBy(offset);
82 this._rawPanBy(L.point(offset));
12483
12584 this.fire('move');
126 this.fire('moveend');
127
128 return this;
85 return this.fire('moveend');
12986 },
13087
13188 setMaxBounds: function (bounds) {
89 bounds = L.latLngBounds(bounds);
90
13291 this.options.maxBounds = bounds;
13392
13493 if (!bounds) {
147106 this.panInsideBounds(bounds);
148107 }
149108 }
109
150110 return this;
151111 },
152112
153113 panInsideBounds: function (bounds) {
114 bounds = L.latLngBounds(bounds);
115
154116 var viewBounds = this.getBounds(),
155 viewSw = this.project(viewBounds.getSouthWest()),
156 viewNe = this.project(viewBounds.getNorthEast()),
157 sw = this.project(bounds.getSouthWest()),
158 ne = this.project(bounds.getNorthEast()),
159 dx = 0,
160 dy = 0;
117 viewSw = this.project(viewBounds.getSouthWest()),
118 viewNe = this.project(viewBounds.getNorthEast()),
119 sw = this.project(bounds.getSouthWest()),
120 ne = this.project(bounds.getNorthEast()),
121 dx = 0,
122 dy = 0;
161123
162124 if (viewNe.y < ne.y) { // north
163125 dy = ne.y - viewNe.y;
175137 return this.panBy(new L.Point(dx, dy, true));
176138 },
177139
178 addLayer: function (layer, insertAtTheTop) {
140 addLayer: function (layer) {
141 // TODO method is too big, refactor
142
179143 var id = L.Util.stamp(layer);
180144
181 if (this._layers[id]) {
182 return this;
183 }
145 if (this._layers[id]) { return this; }
184146
185147 this._layers[id] = layer;
186148
149 // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
187150 if (layer.options && !isNaN(layer.options.maxZoom)) {
188151 this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
189152 }
190153 if (layer.options && !isNaN(layer.options.minZoom)) {
191154 this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
192155 }
193 //TODO getMaxZoom, getMinZoom in ILayer (instead of options)
194
156
157 // TODO looks ugly, refactor!!!
195158 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
196159 this._tileLayersNum++;
197 layer.on('load', this._onTileLayerLoad, this);
198 }
199 if (this.attributionControl && layer.getAttribution) {
200 this.attributionControl.addAttribution(layer.getAttribution());
160 this._tileLayersToLoad++;
161 layer.on('load', this._onTileLayerLoad, this);
201162 }
202163
203164 var onMapLoad = function () {
204 layer.onAdd(this, insertAtTheTop);
165 layer.onAdd(this);
205166 this.fire('layeradd', {layer: layer});
206167 };
207168
217178 removeLayer: function (layer) {
218179 var id = L.Util.stamp(layer);
219180
220 if (this._layers[id]) {
221 layer.onRemove(this);
222 delete this._layers[id];
223
224 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
225 this._tileLayersNum--;
226 layer.off('load', this._onTileLayerLoad, this);
227 }
228 if (this.attributionControl && layer.getAttribution) {
229 this.attributionControl.removeAttribution(layer.getAttribution());
230 }
231
232 this.fire('layerremove', {layer: layer});
233 }
234 return this;
181 if (!this._layers[id]) { return; }
182
183 layer.onRemove(this);
184
185 delete this._layers[id];
186
187 // TODO looks ugly, refactor
188 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
189 this._tileLayersNum--;
190 this._tileLayersToLoad--;
191 layer.off('load', this._onTileLayerLoad, this);
192 }
193
194 return this.fire('layerremove', {layer: layer});
235195 },
236196
237197 hasLayer: function (layer) {
248208 this.setMaxBounds(this.options.maxBounds);
249209 }
250210
251 if (!this._loaded) {
252 return this;
253 }
254
255 this._rawPanBy(oldSize.subtract(this.getSize()).divideBy(2, true));
211 if (!this._loaded) { return this; }
212
213 var offset = oldSize.subtract(this.getSize()).divideBy(2, true);
214 this._rawPanBy(offset);
256215
257216 this.fire('move');
258217
259218 clearTimeout(this._sizeTimer);
260 this._sizeTimer = setTimeout(L.Util.bind(function () {
261 this.fire('moveend');
262 }, this), 200);
219 this._sizeTimer = setTimeout(L.Util.bind(this.fire, this, 'moveend'), 200);
263220
264221 return this;
265222 },
266223
224 // TODO handler.addTo
225 addHandler: function (name, HandlerClass) {
226 if (!HandlerClass) { return; }
227
228 this[name] = new HandlerClass(this);
229
230 if (this.options[name]) {
231 this[name].enable();
232 }
233
234 return this;
235 },
236
267237
268238 // public methods for getting map state
269239
270 getCenter: function (unbounded) { // (Boolean)
271 var viewHalf = this.getSize().divideBy(2),
272 centerPoint = this._getTopLeftPoint().add(viewHalf);
273 return this.unproject(centerPoint, this._zoom, unbounded);
240 getCenter: function () { // (Boolean) -> LatLng
241 return this.layerPointToLatLng(this._getCenterLayerPoint());
274242 },
275243
276244 getZoom: function () {
279247
280248 getBounds: function () {
281249 var bounds = this.getPixelBounds(),
282 sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y), this._zoom, true),
283 ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y), this._zoom, true);
250 sw = this.unproject(bounds.getBottomLeft()),
251 ne = this.unproject(bounds.getTopRight());
252
284253 return new L.LatLngBounds(sw, ne);
285254 },
286255
287256 getMinZoom: function () {
288257 var z1 = this.options.minZoom || 0,
289 z2 = this._layersMinZoom || 0,
290 z3 = this._boundsMinZoom || 0;
258 z2 = this._layersMinZoom || 0,
259 z3 = this._boundsMinZoom || 0;
291260
292261 return Math.max(z1, z2, z3);
293262 },
294263
295264 getMaxZoom: function () {
296 var z1 = isNaN(this.options.maxZoom) ? Infinity : this.options.maxZoom,
297 z2 = this._layersMaxZoom || Infinity;
265 var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom,
266 z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom;
298267
299268 return Math.min(z1, z2);
300269 },
301270
302 getBoundsZoom: function (bounds, inside) { // (LatLngBounds)
271 getBoundsZoom: function (bounds, inside) { // (LatLngBounds, Boolean) -> Number
272 bounds = L.latLngBounds(bounds);
273
303274 var size = this.getSize(),
304 zoom = this.options.minZoom || 0,
305 maxZoom = this.getMaxZoom(),
306 ne = bounds.getNorthEast(),
307 sw = bounds.getSouthWest(),
308 boundsSize,
309 nePoint,
310 swPoint,
311 zoomNotFound = true;
275 zoom = this.options.minZoom || 0,
276 maxZoom = this.getMaxZoom(),
277 ne = bounds.getNorthEast(),
278 sw = bounds.getSouthWest(),
279 boundsSize,
280 nePoint,
281 swPoint,
282 zoomNotFound = true;
312283
313284 if (inside) {
314285 zoom--;
318289 zoom++;
319290 nePoint = this.project(ne, zoom);
320291 swPoint = this.project(sw, zoom);
321 boundsSize = new L.Point(nePoint.x - swPoint.x, swPoint.y - nePoint.y);
292 boundsSize = new L.Point(Math.abs(nePoint.x - swPoint.x), Math.abs(swPoint.y - nePoint.y));
322293
323294 if (!inside) {
324 zoomNotFound = (boundsSize.x <= size.x) && (boundsSize.y <= size.y);
295 zoomNotFound = boundsSize.x <= size.x && boundsSize.y <= size.y;
325296 } else {
326 zoomNotFound = (boundsSize.x < size.x) || (boundsSize.y < size.y);
297 zoomNotFound = boundsSize.x < size.x || boundsSize.y < size.y;
327298 }
328 } while (zoomNotFound && (zoom <= maxZoom));
299 } while (zoomNotFound && zoom <= maxZoom);
329300
330301 if (zoomNotFound && inside) {
331302 return null;
336307
337308 getSize: function () {
338309 if (!this._size || this._sizeChanged) {
339 this._size = new L.Point(this._container.clientWidth, this._container.clientHeight);
310 this._size = new L.Point(
311 this._container.clientWidth,
312 this._container.clientHeight);
313
340314 this._sizeChanged = false;
341315 }
342316 return this._size;
343317 },
344318
345319 getPixelBounds: function () {
346 var topLeftPoint = this._getTopLeftPoint(),
347 size = this.getSize();
348 return new L.Bounds(topLeftPoint, topLeftPoint.add(size));
320 var topLeftPoint = this._getTopLeftPoint();
321 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
349322 },
350323
351324 getPixelOrigin: function () {
356329 return this._panes;
357330 },
358331
332 getContainer: function () {
333 return this._container;
334 },
335
336
337 // TODO replace with universal implementation after refactoring projections
338
339 getZoomScale: function (toZoom) {
340 var crs = this.options.crs;
341 return crs.scale(toZoom) / crs.scale(this._zoom);
342 },
343
344 getScaleZoom: function (scale) {
345 return this._zoom + (Math.log(scale) / Math.LN2);
346 },
347
359348
360349 // conversion methods
350
351 project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
352 zoom = zoom === undefined ? this._zoom : zoom;
353 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
354 },
355
356 unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
357 zoom = zoom === undefined ? this._zoom : zoom;
358 return this.options.crs.pointToLatLng(L.point(point), zoom);
359 },
360
361 layerPointToLatLng: function (point) { // (Point)
362 var projectedPoint = L.point(point).add(this._initialTopLeftPoint);
363 return this.unproject(projectedPoint);
364 },
365
366 latLngToLayerPoint: function (latlng) { // (LatLng)
367 var projectedPoint = this.project(L.latLng(latlng))._round();
368 return projectedPoint._subtract(this._initialTopLeftPoint);
369 },
370
371 containerPointToLayerPoint: function (point) { // (Point)
372 return L.point(point).subtract(this._getMapPanePos());
373 },
374
375 layerPointToContainerPoint: function (point) { // (Point)
376 return L.point(point).add(this._getMapPanePos());
377 },
378
379 containerPointToLatLng: function (point) {
380 var layerPoint = this.containerPointToLayerPoint(L.point(point));
381 return this.layerPointToLatLng(layerPoint);
382 },
383
384 latLngToContainerPoint: function (latlng) {
385 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
386 },
361387
362388 mouseEventToContainerPoint: function (e) { // (MouseEvent)
363389 return L.DomEvent.getMousePosition(e, this._container);
371397 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
372398 },
373399
374 containerPointToLayerPoint: function (point) { // (Point)
375 return point.subtract(L.DomUtil.getPosition(this._mapPane));
376 },
377
378 layerPointToContainerPoint: function (point) { // (Point)
379 return point.add(L.DomUtil.getPosition(this._mapPane));
380 },
381
382 layerPointToLatLng: function (point) { // (Point)
383 return this.unproject(point.add(this._initialTopLeftPoint));
384 },
385
386 latLngToLayerPoint: function (latlng) { // (LatLng)
387 return this.project(latlng)._round()._subtract(this._initialTopLeftPoint);
388 },
389
390 project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
391 zoom = (typeof zoom === 'undefined' ? this._zoom : zoom);
392 return this.options.crs.latLngToPoint(latlng, this.options.scale(zoom));
393 },
394
395 unproject: function (point, zoom, unbounded) { // (Point[, Number, Boolean]) -> LatLng
396 zoom = (typeof zoom === 'undefined' ? this._zoom : zoom);
397 return this.options.crs.pointToLatLng(point, this.options.scale(zoom), unbounded);
398 },
399
400
401 // private methods that modify map state
400
401 // map initialization methods
402
403 _initContainer: function (id) {
404 var container = this._container = L.DomUtil.get(id);
405
406 if (container._leaflet) {
407 throw new Error("Map container is already initialized.");
408 }
409
410 container._leaflet = true;
411 },
402412
403413 _initLayout: function () {
404414 var container = this._container;
405415
406416 container.innerHTML = '';
407
408 container.className += ' leaflet-container';
417 L.DomUtil.addClass(container, 'leaflet-container');
418
419 if (L.Browser.touch) {
420 L.DomUtil.addClass(container, 'leaflet-touch');
421 }
409422
410423 if (this.options.fadeAnimation) {
411 container.className += ' leaflet-fade-anim';
424 L.DomUtil.addClass(container, 'leaflet-fade-anim');
412425 }
413426
414427 var position = L.DomUtil.getStyle(container, 'position');
415 if (position !== 'absolute' && position !== 'relative') {
428
429 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
416430 container.style.position = 'relative';
417431 }
418432
435449 panes.overlayPane = this._createPane('leaflet-overlay-pane');
436450 panes.markerPane = this._createPane('leaflet-marker-pane');
437451 panes.popupPane = this._createPane('leaflet-popup-pane');
452
453 var zoomHide = ' leaflet-zoom-hide';
454
455 if (!this.options.markerZoomAnimation) {
456 L.DomUtil.addClass(panes.markerPane, zoomHide);
457 L.DomUtil.addClass(panes.shadowPane, zoomHide);
458 L.DomUtil.addClass(panes.popupPane, zoomHide);
459 }
438460 },
439461
440462 _createPane: function (className, container) {
441463 return L.DomUtil.create('div', className, container || this._objectsPane);
442464 },
443465
466 _initializers: [],
467
468 _initHooks: function () {
469 var i, len;
470 for (i = 0, len = this._initializers.length; i < len; i++) {
471 this._initializers[i].call(this);
472 }
473 },
474
475 _initLayers: function (layers) {
476 layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
477
478 this._layers = {};
479 this._tileLayersNum = 0;
480
481 var i, len;
482
483 for (i = 0, len = layers.length; i < len; i++) {
484 this.addLayer(layers[i]);
485 }
486 },
487
488
489 // private methods that modify map state
490
444491 _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
492
445493 var zoomChanged = (this._zoom !== zoom);
446494
447495 if (!afterZoomAnim) {
459507 if (!preserveMapOffset) {
460508 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
461509 } else {
462 var offset = L.DomUtil.getPosition(this._mapPane);
463 this._initialTopLeftPoint._add(offset);
510 this._initialTopLeftPoint._add(this._getMapPanePos());
464511 }
465512
466513 this._tileLayersToLoad = this._tileLayersNum;
514
467515 this.fire('viewreset', {hard: !preserveMapOffset});
468516
469517 this.fire('move');
518
470519 if (zoomChanged || afterZoomAnim) {
471520 this.fire('zoomend');
472521 }
473 this.fire('moveend');
522
523 this.fire('moveend', {hard: !preserveMapOffset});
474524
475525 if (!this._loaded) {
476526 this._loaded = true;
478528 }
479529 },
480530
481 _initLayers: function (layers) {
482 this._layers = {};
483
484 var i, len;
485
486 for (i = 0, len = layers.length; i < len; i++) {
487 this.addLayer(layers[i]);
488 }
489 },
490
491 _initControls: function () {
492 if (this.options.zoomControl) {
493 this.addControl(new L.Control.Zoom());
494 }
495 if (this.options.attributionControl) {
496 this.attributionControl = new L.Control.Attribution();
497 this.addControl(this.attributionControl);
498 }
499 },
500
501531 _rawPanBy: function (offset) {
502 var mapPaneOffset = L.DomUtil.getPosition(this._mapPane);
503 L.DomUtil.setPosition(this._mapPane, mapPaneOffset.subtract(offset));
532 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
504533 },
505534
506535
507536 // map events
508537
509538 _initEvents: function () {
510 L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
511
512 var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'];
513
514 var i, len;
539 if (!L.DomEvent) { return; }
540
541 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
542
543 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'],
544 i, len;
515545
516546 for (i = 0, len = events.length; i < len; i++) {
517 L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this);
547 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
518548 }
519549
520550 if (this.options.trackResize) {
521 L.DomEvent.addListener(window, 'resize', this._onResize, this);
551 L.DomEvent.on(window, 'resize', this._onResize, this);
522552 }
523553 },
524554
525555 _onResize: function () {
526 L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
556 L.Util.cancelAnimFrame(this._resizeRequest);
557 this._resizeRequest = L.Util.requestAnimFrame(this.invalidateSize, this, false, this._container);
527558 },
528559
529560 _onMouseClick: function (e) {
530 if (!this._loaded || (this.dragging && this.dragging.moved())) {
531 return;
532 }
533
534 this.fire('pre' + e.type);
561 if (!this._loaded || (this.dragging && this.dragging.moved())) { return; }
562
563 this.fire('preclick');
535564 this._fireMouseEvent(e);
536565 },
537566
538567 _fireMouseEvent: function (e) {
539 if (!this._loaded) {
540 return;
541 }
568 if (!this._loaded) { return; }
542569
543570 var type = e.type;
571
544572 type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
545573
546 if (!this.hasEventListeners(type)) {
547 return;
548 }
574 if (!this.hasEventListeners(type)) { return; }
549575
550576 if (type === 'contextmenu') {
551577 L.DomEvent.preventDefault(e);
552578 }
553
579
580 var containerPoint = this.mouseEventToContainerPoint(e),
581 layerPoint = this.containerPointToLayerPoint(containerPoint),
582 latlng = this.layerPointToLatLng(layerPoint);
583
554584 this.fire(type, {
555 latlng: this.mouseEventToLatLng(e),
556 layerPoint: this.mouseEventToLayerPoint(e)
585 latlng: latlng,
586 layerPoint: layerPoint,
587 containerPoint: containerPoint,
588 originalEvent: e
557589 });
558590 },
559591
560 _initInteraction: function () {
561 var handlers = {
562 dragging: L.Map.Drag,
563 touchZoom: L.Map.TouchZoom,
564 doubleClickZoom: L.Map.DoubleClickZoom,
565 scrollWheelZoom: L.Map.ScrollWheelZoom,
566 boxZoom: L.Map.BoxZoom
567 };
568
569 var i;
570 for (i in handlers) {
571 if (handlers.hasOwnProperty(i) && handlers[i]) {
572 this[i] = new handlers[i](this);
573 if (this.options[i]) {
574 this[i].enable();
575 }
576 // TODO move enabling to handler contructor
577 }
578 }
579 },
580
581592 _onTileLayerLoad: function () {
593 // TODO super-ugly, refactor!!!
582594 // clear scaled tiles after all new tiles are loaded (for performance)
583595 this._tileLayersToLoad--;
584596 if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
590602
591603 // private methods for getting map state
592604
605 _getMapPanePos: function () {
606 return L.DomUtil.getPosition(this._mapPane);
607 },
608
593609 _getTopLeftPoint: function () {
594610 if (!this._loaded) {
595611 throw new Error('Set map center and zoom first.');
596612 }
597613
598 var offset = L.DomUtil.getPosition(this._mapPane);
599 return this._initialTopLeftPoint.subtract(offset);
600 },
601
602 _getNewTopLeftPoint: function (center) {
614 return this._initialTopLeftPoint.subtract(this._getMapPanePos());
615 },
616
617 _getNewTopLeftPoint: function (center, zoom) {
603618 var viewHalf = this.getSize().divideBy(2);
604 return this.project(center).subtract(viewHalf).round();
619 // TODO round on display, not calculation to increase precision?
620 return this.project(center, zoom)._subtract(viewHalf)._round();
621 },
622
623 _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
624 var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
625 return this.project(latlng, newZoom)._subtract(topLeft);
626 },
627
628 _getCenterLayerPoint: function () {
629 return this.containerPointToLayerPoint(this.getSize().divideBy(2));
630 },
631
632 _getCenterOffset: function (center) {
633 return this.latLngToLayerPoint(center).subtract(this._getCenterLayerPoint());
605634 },
606635
607636 _limitZoom: function (zoom) {
608 var min = this.getMinZoom();
609 var max = this.getMaxZoom();
637 var min = this.getMinZoom(),
638 max = this.getMaxZoom();
639
610640 return Math.max(min, Math.min(max, zoom));
611641 }
612642 });
643
644 L.Map.addInitHook = function (fn) {
645 var args = Array.prototype.slice.call(arguments, 1);
646
647 var init = typeof fn === 'function' ? fn : function () {
648 this[fn].apply(this, args);
649 };
650
651 this.prototype._initializers.push(init);
652 };
653
654 L.map = function (id, options) {
655 return new L.Map(id, options);
656 };
0
01 L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
2
13 setView: function (center, zoom, forceReset) {
24 zoom = this._limitZoom(zoom);
5
36 var zoomChanged = (this._zoom !== zoom);
47
58 if (this._loaded && !forceReset && this._layers) {
6 // difference between the new and current centers in pixels
7 var offset = this._getNewTopLeftPoint(center).subtract(this._getTopLeftPoint());
8
9 center = new L.LatLng(center.lat, center.lng);
10
119 var done = (zoomChanged ?
12 !!this._zoomToIfCenterInView && this._zoomToIfCenterInView(center, zoom, offset) :
13 this._panByIfClose(offset));
10 this._zoomToIfClose && this._zoomToIfClose(center, zoom) :
11 this._panByIfClose(center));
1412
1513 // exit if animated pan or zoom started
1614 if (done) {
15 clearTimeout(this._sizeTimer);
1716 return this;
1817 }
1918 }
2423 return this;
2524 },
2625
27 panBy: function (offset) {
26 panBy: function (offset, options) {
27 offset = L.point(offset);
28
2829 if (!(offset.x || offset.y)) {
2930 return this;
3031 }
3132
3233 if (!this._panTransition) {
33 this._panTransition = new L.Transition(this._mapPane, {duration: 0.3});
34 this._panTransition = new L.Transition(this._mapPane);
3435
35 this._panTransition.on('step', this._onPanTransitionStep, this);
36 this._panTransition.on('end', this._onPanTransitionEnd, this);
36 this._panTransition.on({
37 'step': this._onPanTransitionStep,
38 'end': this._onPanTransitionEnd
39 }, this);
3740 }
41
42 L.Util.setOptions(this._panTransition, L.Util.extend({duration: 0.25}, options));
43
3844 this.fire('movestart');
45
46 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
3947
4048 this._panTransition.run({
4149 position: L.DomUtil.getPosition(this._mapPane).subtract(offset)
4957 },
5058
5159 _onPanTransitionEnd: function () {
60 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
5261 this.fire('moveend');
5362 },
5463
55 _panByIfClose: function (offset) {
64 _panByIfClose: function (center) {
65 // difference between the new and current centers in pixels
66 var offset = this._getCenterOffset(center)._floor();
67
5668 if (this._offsetIsWithinView(offset)) {
5769 this.panBy(offset);
5870 return true;
6375 _offsetIsWithinView: function (offset, multiplyFactor) {
6476 var m = multiplyFactor || 1,
6577 size = this.getSize();
78
6679 return (Math.abs(offset.x) <= size.x * m) &&
6780 (Math.abs(offset.y) <= size.y * m);
6881 }
0 L.Map.mergeOptions({
1 zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
2 });
3
04 L.Map.include(!L.DomUtil.TRANSITION ? {} : {
1 _zoomToIfCenterInView: function (center, zoom, centerOffset) {
25
3 if (this._animatingZoom) {
4 return true;
5 }
6 if (!this.options.zoomAnimation) {
7 return false;
8 }
6 _zoomToIfClose: function (center, zoom) {
97
10 var zoomDelta = zoom - this._zoom,
11 scale = Math.pow(2, zoomDelta),
12 offset = centerOffset.divideBy(1 - 1 / scale);
8 if (this._animatingZoom) { return true; }
9 if (!this.options.zoomAnimation) { return false; }
1310
14 //if offset does not exceed half of the view
15 if (!this._offsetIsWithinView(offset, 1)) {
16 return false;
17 }
11 var scale = this.getZoomScale(zoom),
12 offset = this._getCenterOffset(center).divideBy(1 - 1 / scale);
1813
19 this._mapPane.className += ' leaflet-zoom-anim';
14 // if offset does not exceed half of the view
15 if (!this._offsetIsWithinView(offset, 1)) { return false; }
2016
21 this
17 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
18
19 this
2220 .fire('movestart')
2321 .fire('zoomstart');
2422
25 var centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(2)),
26 origin = centerPoint.add(offset);
23 this._prepareTileBg();
2724
28 this._prepareTileBg();
25 this.fire('zoomanim', {
26 center: center,
27 zoom: zoom
28 });
29
30 var origin = this._getCenterLayerPoint().add(offset);
2931
3032 this._runAnimation(center, zoom, scale, origin);
3133
3335 },
3436
3537
36 _runAnimation: function (center, zoom, scale, origin) {
38 _runAnimation: function (center, zoom, scale, origin, backwardsTransform) {
3739 this._animatingZoom = true;
3840
3941 this._animateToCenter = center;
4042 this._animateToZoom = zoom;
4143
42 var transform = L.DomUtil.TRANSFORM;
44 var transform = L.DomUtil.TRANSFORM,
45 tileBg = this._tileBg;
4346
4447 clearTimeout(this._clearTileBgTimer);
4548
4649 //dumb FireFox hack, I have no idea why this magic zero translate fixes the scale transition problem
4750 if (L.Browser.gecko || window.opera) {
48 this._tileBg.style[transform] += ' translate(0,0)';
51 tileBg.style[transform] += ' translate(0,0)';
4952 }
5053
5154 var scaleStr;
5255
53 // Android doesn't like translate/scale chains, transformOrigin + scale works better but
56 // Android 2.* doesn't like translate/scale chains, transformOrigin + scale works better but
5457 // it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack
58
5559 // TODO work around this prettier
56 if (L.Browser.android) {
57 this._tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
60 if (L.Browser.android23) {
61 tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
5862 scaleStr = 'scale(' + scale + ')';
5963 } else {
6064 scaleStr = L.DomUtil.getScaleString(scale, origin);
6165 }
6266
63 L.Util.falseFn(this._tileBg.offsetWidth); //hack to make sure transform is updated before running animation
67 L.Util.falseFn(tileBg.offsetWidth); //hack to make sure transform is updated before running animation
6468
6569 var options = {};
66 options[transform] = this._tileBg.style[transform] + ' ' + scaleStr;
67 this._tileBg.transition.run(options);
70 if (backwardsTransform) {
71 options[transform] = tileBg.style[transform] + ' ' + scaleStr;
72 } else {
73 options[transform] = scaleStr + ' ' + tileBg.style[transform];
74 }
75
76 tileBg.transition.run(options);
6877 },
6978
7079 _prepareTileBg: function () {
71 if (!this._tileBg) {
72 this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
73 this._tileBg.style.zIndex = 1;
74 }
75
7680 var tilePane = this._tilePane,
7781 tileBg = this._tileBg;
7882
83 // If foreground layer doesn't have many tiles but bg layer does, keep the existing bg layer and just zoom it some more
84 // (disable this for Android due to it not supporting double translate)
85 if (!L.Browser.android23 && tileBg &&
86 this._getLoadedTilesPercentage(tileBg) > 0.5 &&
87 this._getLoadedTilesPercentage(tilePane) < 0.5) {
88
89 tilePane.style.visibility = 'hidden';
90 tilePane.empty = true;
91 this._stopLoadingImages(tilePane);
92 return;
93 }
94
95 if (!tileBg) {
96 tileBg = this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
97 tileBg.style.zIndex = 1;
98 }
99
79100 // prepare the background pane to become the main tile pane
80 //tileBg.innerHTML = '';
81101 tileBg.style[L.DomUtil.TRANSFORM] = '';
82102 tileBg.style.visibility = 'hidden';
83103
84104 // tells tile layers to reinitialize their containers
85 tileBg.empty = true;
86 tilePane.empty = false;
105 tileBg.empty = true; //new FG
106 tilePane.empty = false; //new BG
87107
108 //Switch out the current layer to be the new bg layer (And vice-versa)
88109 this._tilePane = this._panes.tilePane = tileBg;
89 this._tileBg = tilePane;
110 var newTileBg = this._tileBg = tilePane;
90111
91 if (!this._tileBg.transition) {
92 this._tileBg.transition = new L.Transition(this._tileBg, {duration: 0.3, easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'});
93 this._tileBg.transition.on('end', this._onZoomTransitionEnd, this);
112 if (!newTileBg.transition) {
113 // TODO move to Map options
114 newTileBg.transition = new L.Transition(newTileBg, {
115 duration: 0.25,
116 easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'
117 });
118 newTileBg.transition.on('end', this._onZoomTransitionEnd, this);
94119 }
95120
96 this._stopLoadingBgTiles();
121 this._stopLoadingImages(newTileBg);
122 },
123
124 _getLoadedTilesPercentage: function (container) {
125 var tiles = container.getElementsByTagName('img'),
126 i, len, count = 0;
127
128 for (i = 0, len = tiles.length; i < len; i++) {
129 if (tiles[i].complete) {
130 count++;
131 }
132 }
133 return count / len;
97134 },
98135
99136 // stops loading all tiles in the background layer
100 _stopLoadingBgTiles: function () {
101 var tiles = [].slice.call(this._tileBg.getElementsByTagName('img'));
137 _stopLoadingImages: function (container) {
138 var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
139 i, len, tile;
102140
103 for (var i = 0, len = tiles.length; i < len; i++) {
104 if (!tiles[i].complete) {
105 tiles[i].onload = L.Util.falseFn;
106 tiles[i].onerror = L.Util.falseFn;
107 tiles[i].src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
141 for (i = 0, len = tiles.length; i < len; i++) {
142 tile = tiles[i];
108143
109 tiles[i].parentNode.removeChild(tiles[i]);
110 tiles[i] = null;
144 if (!tile.complete) {
145 tile.onload = L.Util.falseFn;
146 tile.onerror = L.Util.falseFn;
147 tile.src = L.Util.emptyImageUrl;
148
149 tile.parentNode.removeChild(tile);
111150 }
112151 }
113152 },
115154 _onZoomTransitionEnd: function () {
116155 this._restoreTileFront();
117156
118 L.Util.falseFn(this._tileBg.offsetWidth);
157 L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
119158 this._resetView(this._animateToCenter, this._animateToZoom, true, true);
120159
121 this._mapPane.className = this._mapPane.className.replace(' leaflet-zoom-anim', ''); //TODO toggleClass util
160 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
122161 this._animatingZoom = false;
123162 },
124163
00 L.Map.include({
11 addControl: function (control) {
2 control.onAdd(this);
3
4 var pos = control.getPosition(),
5 corner = this._controlCorners[pos],
6 container = control.getContainer();
7
8 L.DomUtil.addClass(container, 'leaflet-control');
9
10 if (pos.indexOf('bottom') !== -1) {
11 corner.insertBefore(container, corner.firstChild);
12 } else {
13 corner.appendChild(container);
14 }
2 control.addTo(this);
153 return this;
164 },
175
186 removeControl: function (control) {
19 var pos = control.getPosition(),
20 corner = this._controlCorners[pos],
21 container = control.getContainer();
22
23 corner.removeChild(container);
24
25 if (control.onRemove) {
26 control.onRemove(this);
27 }
7 control.removeFrom(this);
288 return this;
299 },
3010
3111 _initControlPos: function () {
3212 var corners = this._controlCorners = {},
33 classPart = 'leaflet-',
34 top = classPart + 'top',
35 bottom = classPart + 'bottom',
36 left = classPart + 'left',
37 right = classPart + 'right',
38 controlContainer = L.DomUtil.create('div', classPart + 'control-container', this._container);
13 l = 'leaflet-',
14 container = this._controlContainer =
15 L.DomUtil.create('div', l + 'control-container', this._container);
3916
40 if (L.Browser.touch) {
41 controlContainer.className += ' ' + classPart + 'big-buttons';
17 function createCorner(vSide, hSide) {
18 var className = l + vSide + ' ' + l + hSide;
19
20 corners[vSide + hSide] =
21 L.DomUtil.create('div', className, container);
4222 }
4323
44 corners.topLeft = L.DomUtil.create('div', top + ' ' + left, controlContainer);
45 corners.topRight = L.DomUtil.create('div', top + ' ' + right, controlContainer);
46 corners.bottomLeft = L.DomUtil.create('div', bottom + ' ' + left, controlContainer);
47 corners.bottomRight = L.DomUtil.create('div', bottom + ' ' + right, controlContainer);
24 createCorner('top', 'left');
25 createCorner('top', 'right');
26 createCorner('bottom', 'left');
27 createCorner('bottom', 'right');
4828 }
4929 });
22 */
33
44 L.Map.include({
5 _defaultLocateOptions: {
6 watch: false,
7 setView: false,
8 maxZoom: Infinity,
9 timeout: 10000,
10 maximumAge: 0,
11 enableHighAccuracy: false
12 },
13
514 locate: function (/*Object*/ options) {
615
7 this._locationOptions = options = L.Util.extend({
8 watch: false,
9 setView: false,
10 maxZoom: Infinity,
11 timeout: 10000,
12 maximumAge: 0,
13 enableHighAccuracy: false
14 }, options);
16 options = this._locationOptions = L.Util.extend(this._defaultLocateOptions, options);
1517
1618 if (!navigator.geolocation) {
17 return this.fire('locationerror', {
19 this._handleGeolocationError({
1820 code: 0,
1921 message: "Geolocation not supported."
2022 });
23 return this;
2124 }
2225
2326 var onResponse = L.Util.bind(this._handleGeolocationResponse, this),
3538 if (navigator.geolocation) {
3639 navigator.geolocation.clearWatch(this._locationWatchId);
3740 }
38 },
39
40 locateAndSetView: function (maxZoom, options) {
41 options = L.Util.extend({
42 maxZoom: maxZoom || Infinity,
43 setView: true
44 }, options);
45 return this.locate(options);
41 return this;
4642 },
4743
4844 _handleGeolocationError: function (error) {
4945 var c = error.code,
50 message = (c === 1 ? "permission denied" :
46 message = error.message ||
47 (c === 1 ? "permission denied" :
5148 (c === 2 ? "position unavailable" : "timeout"));
5249
5350 if (this._locationOptions.setView && !this._loaded) {
6360 _handleGeolocationResponse: function (pos) {
6461 var latAccuracy = 180 * pos.coords.accuracy / 4e7,
6562 lngAccuracy = latAccuracy * 2,
63
6664 lat = pos.coords.latitude,
6765 lng = pos.coords.longitude,
68 latlng = new L.LatLng(lat, lng);
66 latlng = new L.LatLng(lat, lng),
6967
70 var sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
68 sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
7169 ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
72 bounds = new L.LatLngBounds(sw, ne);
70 bounds = new L.LatLngBounds(sw, ne),
7371
74 if (this._locationOptions.setView) {
75 var zoom = Math.min(this.getBoundsZoom(bounds), this._locationOptions.maxZoom);
72 options = this._locationOptions;
73
74 if (options.setView) {
75 var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
7676 this.setView(latlng, zoom);
7777 }
7878
11 L.Map.include({
22 openPopup: function (popup) {
33 this.closePopup();
4
45 this._popup = popup;
5 this.addLayer(popup);
6 this.fire('popupopen', { popup: this._popup });
7
8 return this;
6
7 return this
8 .addLayer(popup)
9 .fire('popupopen', {popup: this._popup});
910 },
1011
1112 closePopup: function () {
1213 if (this._popup) {
13 this.removeLayer(this._popup);
14 this.fire('popupclose', { popup: this._popup });
15 this._popup = null;
14 this._popup._close();
1615 }
1716 return this;
1817 }
19 });
18 });
00 /*
11 * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
22 */
3
4 L.Map.mergeOptions({
5 boxZoom: true
6 });
37
48 L.Map.BoxZoom = L.Handler.extend({
59 initialize: function (map) {
913 },
1014
1115 addHooks: function () {
12 L.DomEvent.addListener(this._container, 'mousedown', this._onMouseDown, this);
16 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
1317 },
1418
1519 removeHooks: function () {
16 L.DomEvent.removeListener(this._container, 'mousedown', this._onMouseDown);
20 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
1721 },
1822
1923 _onMouseDown: function (e) {
20 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) {
21 return false;
22 }
24 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
2325
2426 L.DomUtil.disableTextSelection();
2527
2830 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
2931 L.DomUtil.setPosition(this._box, this._startLayerPoint);
3032
31 //TODO move cursor to styles
33 //TODO refactor: move cursor to styles
3234 this._container.style.cursor = 'crosshair';
3335
34 L.DomEvent.addListener(document, 'mousemove', this._onMouseMove, this);
35 L.DomEvent.addListener(document, 'mouseup', this._onMouseUp, this);
36
37 L.DomEvent.preventDefault(e);
36 L.DomEvent
37 .on(document, 'mousemove', this._onMouseMove, this)
38 .on(document, 'mouseup', this._onMouseUp, this)
39 .preventDefault(e);
40
41 this._map.fire("boxzoomstart");
3842 },
3943
4044 _onMouseMove: function (e) {
41 var layerPoint = this._map.mouseEventToLayerPoint(e),
42 dx = layerPoint.x - this._startLayerPoint.x,
43 dy = layerPoint.y - this._startLayerPoint.y;
45 var startPoint = this._startLayerPoint,
46 box = this._box,
4447
45 var newX = Math.min(layerPoint.x, this._startLayerPoint.x),
46 newY = Math.min(layerPoint.y, this._startLayerPoint.y),
47 newPos = new L.Point(newX, newY);
48 layerPoint = this._map.mouseEventToLayerPoint(e),
49 offset = layerPoint.subtract(startPoint),
4850
49 L.DomUtil.setPosition(this._box, newPos);
51 newPos = new L.Point(
52 Math.min(layerPoint.x, startPoint.x),
53 Math.min(layerPoint.y, startPoint.y));
5054
51 this._box.style.width = (Math.abs(dx) - 4) + 'px';
52 this._box.style.height = (Math.abs(dy) - 4) + 'px';
55 L.DomUtil.setPosition(box, newPos);
56
57 // TODO refactor: remove hardcoded 4 pixels
58 box.style.width = (Math.abs(offset.x) - 4) + 'px';
59 box.style.height = (Math.abs(offset.y) - 4) + 'px';
5360 },
5461
5562 _onMouseUp: function (e) {
5865
5966 L.DomUtil.enableTextSelection();
6067
61 L.DomEvent.removeListener(document, 'mousemove', this._onMouseMove);
62 L.DomEvent.removeListener(document, 'mouseup', this._onMouseUp);
68 L.DomEvent
69 .off(document, 'mousemove', this._onMouseMove)
70 .off(document, 'mouseup', this._onMouseUp);
6371
64 var layerPoint = this._map.mouseEventToLayerPoint(e);
72 var map = this._map,
73 layerPoint = map.mouseEventToLayerPoint(e);
6574
6675 var bounds = new L.LatLngBounds(
67 this._map.layerPointToLatLng(this._startLayerPoint),
68 this._map.layerPointToLatLng(layerPoint));
76 map.layerPointToLatLng(this._startLayerPoint),
77 map.layerPointToLatLng(layerPoint));
6978
70 this._map.fitBounds(bounds);
79 map.fitBounds(bounds);
80
81 map.fire("boxzoomend", {
82 boxZoomBounds: bounds
83 });
7184 }
7285 });
86
87 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11 * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
22 */
33
4 L.Map.mergeOptions({
5 doubleClickZoom: true
6 });
7
48 L.Map.DoubleClickZoom = L.Handler.extend({
59 addHooks: function () {
610 this._map.on('dblclick', this._onDoubleClick);
7 // TODO remove 3d argument?
811 },
912
1013 removeHooks: function () {
1518 this.setView(e.latlng, this._zoom + 1);
1619 }
1720 });
21
22 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
00 /*
11 * L.Handler.MapDrag is used internally by L.Map to make the map draggable.
22 */
3
4 L.Map.mergeOptions({
5 dragging: true,
6
7 inertia: !L.Browser.android23,
8 inertiaDeceleration: 3000, // px/s^2
9 inertiaMaxSpeed: 1500, // px/s
10 inertiaThreshold: L.Browser.touch ? 32 : 14, // ms
11
12 // TODO refactor, move to CRS
13 worldCopyJump: true
14 });
315
416 L.Map.Drag = L.Handler.extend({
517 addHooks: function () {
618 if (!this._draggable) {
719 this._draggable = new L.Draggable(this._map._mapPane, this._map._container);
820
9 this._draggable
10 .on('dragstart', this._onDragStart, this)
11 .on('drag', this._onDrag, this)
12 .on('dragend', this._onDragEnd, this);
21 this._draggable.on({
22 'dragstart': this._onDragStart,
23 'drag': this._onDrag,
24 'dragend': this._onDragEnd
25 }, this);
1326
1427 var options = this._map.options;
1528
16 if (options.worldCopyJump && !options.continuousWorld) {
29 if (options.worldCopyJump) {
1730 this._draggable.on('predrag', this._onPreDrag, this);
1831 this._map.on('viewreset', this._onViewReset, this);
1932 }
3043 },
3144
3245 _onDragStart: function () {
33 this._map
46 var map = this._map;
47
48 map
3449 .fire('movestart')
3550 .fire('dragstart');
51
52 if (map._panTransition) {
53 map._panTransition._onTransitionEnd(true);
54 }
55
56 if (map.options.inertia) {
57 this._positions = [];
58 this._times = [];
59 }
3660 },
3761
3862 _onDrag: function () {
63 if (this._map.options.inertia) {
64 var time = this._lastTime = +new Date(),
65 pos = this._lastPos = this._draggable._newPos;
66
67 this._positions.push(pos);
68 this._times.push(time);
69
70 if (time - this._times[0] > 200) {
71 this._positions.shift();
72 this._times.shift();
73 }
74 }
75
3976 this._map
4077 .fire('move')
4178 .fire('drag');
4582 var pxCenter = this._map.getSize().divideBy(2),
4683 pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
4784
48 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter);
85 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
86 this._worldWidth = this._map.project(new L.LatLng(0, 180)).x;
4987 },
5088
5189 _onPreDrag: function () {
90 // TODO refactor to be able to adjust map pane position after zoom
5291 var map = this._map,
53 worldWidth = map.options.scale(map.getZoom()),
92 worldWidth = this._worldWidth,
5493 halfWidth = Math.round(worldWidth / 2),
55 dx = this._initialWorldOffset.x,
94 dx = this._initialWorldOffset,
5695 x = this._draggable._newPos.x,
5796 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
5897 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
62101 },
63102
64103 _onDragEnd: function () {
65 var map = this._map;
104 var map = this._map,
105 options = map.options,
106 delay = +new Date() - this._lastTime,
66107
67 map
68 .fire('moveend')
69 .fire('dragend');
108 noInertia = !options.inertia ||
109 delay > options.inertiaThreshold ||
110 this._positions[0] === undefined;
70111
71 if (map.options.maxBounds) {
112 if (noInertia) {
113 map.fire('moveend');
114
115 } else {
116
117 var direction = this._lastPos.subtract(this._positions[0]),
118 duration = (this._lastTime + delay - this._times[0]) / 1000,
119
120 speedVector = direction.multiplyBy(0.58 / duration),
121 speed = speedVector.distanceTo(new L.Point(0, 0)),
122
123 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
124 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
125
126 decelerationDuration = limitedSpeed / options.inertiaDeceleration,
127 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
128
129 var panOptions = {
130 duration: decelerationDuration,
131 easing: 'ease-out'
132 };
133
134 L.Util.requestAnimFrame(L.Util.bind(function () {
135 this._map.panBy(offset, panOptions);
136 }, this));
137 }
138
139 map.fire('dragend');
140
141 if (options.maxBounds) {
72142 // TODO predrag validation instead of animation
73143 L.Util.requestAnimFrame(this._panInsideMaxBounds, map, true, map._container);
74144 }
78148 this.panInsideBounds(this.options.maxBounds);
79149 }
80150 });
151
152 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
0 L.Map.mergeOptions({
1 keyboard: true,
2 keyboardPanOffset: 80,
3 keyboardZoomOffset: 1
4 });
5
6 L.Map.Keyboard = L.Handler.extend({
7
8 // list of e.keyCode values for particular actions
9 keyCodes: {
10 left: [37],
11 right: [39],
12 down: [40],
13 up: [38],
14 zoomIn: [187, 61, 107],
15 zoomOut: [189, 109, 0]
16 },
17
18 initialize: function (map) {
19 this._map = map;
20
21 this._setPanOffset(map.options.keyboardPanOffset);
22 this._setZoomOffset(map.options.keyboardZoomOffset);
23 },
24
25 addHooks: function () {
26 var container = this._map._container;
27
28 // make the container focusable by tabbing
29 if (container.tabIndex === -1) {
30 container.tabIndex = "0";
31 }
32
33 L.DomEvent
34 .addListener(container, 'focus', this._onFocus, this)
35 .addListener(container, 'blur', this._onBlur, this)
36 .addListener(container, 'mousedown', this._onMouseDown, this);
37
38 this._map
39 .on('focus', this._addHooks, this)
40 .on('blur', this._removeHooks, this);
41 },
42
43 removeHooks: function () {
44 this._removeHooks();
45
46 var container = this._map._container;
47 L.DomEvent
48 .removeListener(container, 'focus', this._onFocus, this)
49 .removeListener(container, 'blur', this._onBlur, this)
50 .removeListener(container, 'mousedown', this._onMouseDown, this);
51
52 this._map
53 .off('focus', this._addHooks, this)
54 .off('blur', this._removeHooks, this);
55 },
56
57 _onMouseDown: function () {
58 if (!this._focused) {
59 this._map._container.focus();
60 }
61 },
62
63 _onFocus: function () {
64 this._focused = true;
65 this._map.fire('focus');
66 },
67
68 _onBlur: function () {
69 this._focused = false;
70 this._map.fire('blur');
71 },
72
73 _setPanOffset: function (pan) {
74 var keys = this._panKeys = {},
75 codes = this.keyCodes,
76 i, len;
77
78 for (i = 0, len = codes.left.length; i < len; i++) {
79 keys[codes.left[i]] = [-1 * pan, 0];
80 }
81 for (i = 0, len = codes.right.length; i < len; i++) {
82 keys[codes.right[i]] = [pan, 0];
83 }
84 for (i = 0, len = codes.down.length; i < len; i++) {
85 keys[codes.down[i]] = [0, pan];
86 }
87 for (i = 0, len = codes.up.length; i < len; i++) {
88 keys[codes.up[i]] = [0, -1 * pan];
89 }
90 },
91
92 _setZoomOffset: function (zoom) {
93 var keys = this._zoomKeys = {},
94 codes = this.keyCodes,
95 i, len;
96
97 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
98 keys[codes.zoomIn[i]] = zoom;
99 }
100 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
101 keys[codes.zoomOut[i]] = -zoom;
102 }
103 },
104
105 _addHooks: function () {
106 L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
107 },
108
109 _removeHooks: function () {
110 L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
111 },
112
113 _onKeyDown: function (e) {
114 var key = e.keyCode;
115
116 if (this._panKeys.hasOwnProperty(key)) {
117 this._map.panBy(this._panKeys[key]);
118
119 } else if (this._zoomKeys.hasOwnProperty(key)) {
120 this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
121
122 } else {
123 return;
124 }
125
126 L.DomEvent.stop(e);
127 }
128 });
129
130 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
11 * L.Handler.ScrollWheelZoom is used internally by L.Map to enable mouse scroll wheel zooming on the map.
22 */
33
4 L.Map.mergeOptions({
5 scrollWheelZoom: !L.Browser.touch
6 });
7
48 L.Map.ScrollWheelZoom = L.Handler.extend({
59 addHooks: function () {
6 L.DomEvent.addListener(this._map._container, 'mousewheel', this._onWheelScroll, this);
10 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
711 this._delta = 0;
812 },
913
1014 removeHooks: function () {
11 L.DomEvent.removeListener(this._map._container, 'mousewheel', this._onWheelScroll);
15 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
1216 },
1317
1418 _onWheelScroll: function (e) {
1519 var delta = L.DomEvent.getWheelDelta(e);
20
1621 this._delta += delta;
1722 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
1823
1924 clearTimeout(this._timer);
20 this._timer = setTimeout(L.Util.bind(this._performZoom, this), 50);
25 this._timer = setTimeout(L.Util.bind(this._performZoom, this), 40);
2126
2227 L.DomEvent.preventDefault(e);
2328 },
3237
3338 this._delta = 0;
3439
35 if (!delta) {
36 return;
37 }
40 if (!delta) { return; }
3841
39 var newCenter = this._getCenterForScrollWheelZoom(this._lastMousePos, delta),
40 newZoom = zoom + delta;
42 var newZoom = zoom + delta,
43 newCenter = this._getCenterForScrollWheelZoom(this._lastMousePos, newZoom);
4144
4245 map.setView(newCenter, newZoom);
4346 },
4447
45 _getCenterForScrollWheelZoom: function (mousePos, delta) {
48 _getCenterForScrollWheelZoom: function (mousePos, newZoom) {
4649 var map = this._map,
47 centerPoint = map.getPixelBounds().getCenter(),
50 scale = map.getZoomScale(newZoom),
4851 viewHalf = map.getSize().divideBy(2),
49 centerOffset = mousePos.subtract(viewHalf).multiplyBy(1 - Math.pow(2, -delta)),
50 newCenterPoint = centerPoint.add(centerOffset);
52 centerOffset = mousePos.subtract(viewHalf).multiplyBy(1 - 1 / scale),
53 newCenterPoint = map._getTopLeftPoint().add(viewHalf).add(centerOffset);
5154
52 return map.unproject(newCenterPoint, map._zoom, true);
55 return map.unproject(newCenterPoint);
5356 }
5457 });
58
59 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
11 * L.Handler.TouchZoom is used internally by L.Map to add touch-zooming on Webkit-powered mobile browsers.
22 */
33
4 L.Map.mergeOptions({
5 touchZoom: L.Browser.touch && !L.Browser.android23
6 });
7
48 L.Map.TouchZoom = L.Handler.extend({
59 addHooks: function () {
6 L.DomEvent.addListener(this._map._container, 'touchstart', this._onTouchStart, this);
10 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
711 },
812
913 removeHooks: function () {
10 L.DomEvent.removeListener(this._map._container, 'touchstart', this._onTouchStart, this);
14 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
1115 },
1216
1317 _onTouchStart: function (e) {
14 if (!e.touches || e.touches.length !== 2 || this._map._animatingZoom) {
15 return;
16 }
18 var map = this._map;
1719
18 var p1 = this._map.mouseEventToLayerPoint(e.touches[0]),
19 p2 = this._map.mouseEventToLayerPoint(e.touches[1]),
20 viewCenter = this._map.containerPointToLayerPoint(this._map.getSize().divideBy(2));
20 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
21
22 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
23 p2 = map.mouseEventToLayerPoint(e.touches[1]),
24 viewCenter = map._getCenterLayerPoint();
2125
2226 this._startCenter = p1.add(p2).divideBy(2, true);
2327 this._startDist = p1.distanceTo(p2);
24 //this._startTransform = this._map._mapPane.style.webkitTransform;
2528
2629 this._moved = false;
2730 this._zooming = true;
2831
2932 this._centerOffset = viewCenter.subtract(this._startCenter);
3033
31 L.DomEvent.addListener(document, 'touchmove', this._onTouchMove, this);
32 L.DomEvent.addListener(document, 'touchend', this._onTouchEnd, this);
34 L.DomEvent
35 .on(document, 'touchmove', this._onTouchMove, this)
36 .on(document, 'touchend', this._onTouchEnd, this);
3337
3438 L.DomEvent.preventDefault(e);
3539 },
3640
3741 _onTouchMove: function (e) {
38 if (!e.touches || e.touches.length !== 2) {
39 return;
40 }
42 if (!e.touches || e.touches.length !== 2) { return; }
43
44 var map = this._map;
45
46 var p1 = map.mouseEventToLayerPoint(e.touches[0]),
47 p2 = map.mouseEventToLayerPoint(e.touches[1]);
48
49 this._scale = p1.distanceTo(p2) / this._startDist;
50 this._delta = p1.add(p2).divideBy(2, true).subtract(this._startCenter);
51
52 if (this._scale === 1) { return; }
4153
4254 if (!this._moved) {
43 this._map._mapPane.className += ' leaflet-zoom-anim';
55 L.DomUtil.addClass(map._mapPane, 'leaflet-zoom-anim leaflet-touching');
4456
45 this._map
57 map
58 .fire('movestart')
4659 .fire('zoomstart')
47 .fire('movestart')
4860 ._prepareTileBg();
4961
5062 this._moved = true;
5163 }
5264
53 var p1 = this._map.mouseEventToLayerPoint(e.touches[0]),
54 p2 = this._map.mouseEventToLayerPoint(e.touches[1]);
65 L.Util.cancelAnimFrame(this._animRequest);
66 this._animRequest = L.Util.requestAnimFrame(this._updateOnMove, this, true, this._map._container);
5567
56 this._scale = p1.distanceTo(p2) / this._startDist;
57 this._delta = p1.add(p2).divideBy(2, true).subtract(this._startCenter);
68 L.DomEvent.preventDefault(e);
69 },
70
71 _updateOnMove: function () {
72 var map = this._map,
73 origin = this._getScaleOrigin(),
74 center = map.layerPointToLatLng(origin);
75
76 map.fire('zoomanim', {
77 center: center,
78 zoom: map.getScaleZoom(this._scale)
79 });
5880
5981 // Used 2 translates instead of transform-origin because of a very strange bug -
6082 // it didn't count the origin on the first touch-zoom but worked correctly afterwards
6183
62 this._map._tileBg.style.webkitTransform = [
63 L.DomUtil.getTranslateString(this._delta),
64 L.DomUtil.getScaleString(this._scale, this._startCenter)
65 ].join(" ");
66
67 L.DomEvent.preventDefault(e);
84 map._tileBg.style[L.DomUtil.TRANSFORM] =
85 L.DomUtil.getTranslateString(this._delta) + ' ' +
86 L.DomUtil.getScaleString(this._scale, this._startCenter);
6887 },
6988
7089 _onTouchEnd: function (e) {
71 if (!this._moved || !this._zooming) {
72 return;
73 }
90 if (!this._moved || !this._zooming) { return; }
91
92 var map = this._map;
93
7494 this._zooming = false;
95 L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
7596
76 var oldZoom = this._map.getZoom(),
77 floatZoomDelta = Math.log(this._scale) / Math.LN2,
97 L.DomEvent
98 .off(document, 'touchmove', this._onTouchMove)
99 .off(document, 'touchend', this._onTouchEnd);
100
101 var origin = this._getScaleOrigin(),
102 center = map.layerPointToLatLng(origin),
103
104 oldZoom = map.getZoom(),
105 floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
78106 roundZoomDelta = (floatZoomDelta > 0 ? Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
79 zoom = this._map._limitZoom(oldZoom + roundZoomDelta),
80 zoomDelta = zoom - oldZoom,
81 centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale),
82 centerPoint = this._map.getPixelOrigin().add(this._startCenter).add(centerOffset),
83 center = this._map.unproject(centerPoint);
107 zoom = map._limitZoom(oldZoom + roundZoomDelta);
84108
85 L.DomEvent.removeListener(document, 'touchmove', this._onTouchMove);
86 L.DomEvent.removeListener(document, 'touchend', this._onTouchEnd);
109 map.fire('zoomanim', {
110 center: center,
111 zoom: zoom
112 });
87113
88 var finalScale = Math.pow(2, zoomDelta);
114 map._runAnimation(center, zoom, map.getZoomScale(zoom) / this._scale, origin, true);
115 },
89116
90 this._map._runAnimation(center, zoom, finalScale / this._scale, this._startCenter.add(centerOffset));
117 _getScaleOrigin: function () {
118 var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
119 return this._startCenter.add(centerOffset);
91120 }
92121 });
122
123 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);