Commit 899e9758d62c235671549131f0e803e5385ccc07
1 parent
fa7d515e
geojson <3
Showing
16 changed files
with
9923 additions
and
5 deletions
controllets/data-table-controllet/data-table-controllet.html
... | ... | @@ -335,13 +335,14 @@ |
335 | 335 | |
336 | 336 | this._resize(); |
337 | 337 | |
338 | - this.$.tooltip_wornings.show(); | |
338 | + this.async(function() { | |
339 | + this.$.tooltip_wornings.show(); | |
340 | + }, 100); | |
339 | 341 | }, |
340 | 342 | |
341 | 343 | reset : function(){ |
342 | 344 | this.setData([]); |
343 | 345 | $(this.$.tbody).animate({ scrollTop: 0}, 0); |
344 | -// this.filter = ""; | |
345 | 346 | }, |
346 | 347 | |
347 | 348 | _onPrevClick : function(){ |
... | ... | @@ -478,7 +479,7 @@ |
478 | 479 | var w = $(this.$.data_table_container).width(); |
479 | 480 | $(this.$.tbody).width(w).height(h); |
480 | 481 | $(this.$.tbody).perfectScrollbar('update'); |
481 | - }, 1); | |
482 | + }, 0); | |
482 | 483 | } |
483 | 484 | |
484 | 485 | }); | ... | ... |
controllets/select-visualization-controllet/select-datalet-inputs.html
... | ... | @@ -276,7 +276,11 @@ |
276 | 276 | |
277 | 277 | setInputs : function(inputs) { |
278 | 278 | var temp = this._copy(inputs); |
279 | - if (inputs.constructor != Array) {//table --> 1 input | |
279 | + if (inputs.constructor != Array && inputs.name == "GEOJSON") { | |
280 | + temp = []; | |
281 | + temp.push(inputs); | |
282 | + } | |
283 | + else if (inputs.constructor != Array) {//table --> 1 input | |
280 | 284 | var name = inputs.name; |
281 | 285 | var description = inputs.description; |
282 | 286 | var selection = inputs.selection; | ... | ... |
datalets/base-ajax-json-alasql-datalet/base-ajax-json-alasql-datalet.html
... | ... | @@ -34,7 +34,7 @@ |
34 | 34 | <script type="text/javascript" src="../../alasql-utility/alasql.min.js"></script> |
35 | 35 | <script type="text/javascript" src="../../alasql-utility/alasql-utility.js"></script> |
36 | 36 | |
37 | -<script type="text/javascript" src="../../bower_components/JSDataChecker/jsdatachecker.min.js"></script> | |
37 | +<script type="text/javascript" src="../../bower_components/jsdatachecker/jsdatachecker.min.js"></script> | |
38 | 38 | |
39 | 39 | <!-- |
40 | 40 | The `base-ajax-json-jsonpath-datalet` extend the base component and define the mechanism to access and select data. | ... | ... |
datalets/leafletjs-geojson-datalet/demo/index.html
0 โ 100755
1 | +<!DOCTYPE html> | |
2 | +<html lang="en"> | |
3 | +<head> | |
4 | + <meta charset="UTF-8"> | |
5 | + <title></title> | |
6 | + | |
7 | + <script> | |
8 | + </script> | |
9 | + | |
10 | +</head> | |
11 | +<body> | |
12 | + | |
13 | +<script src="https://code.jquery.com/jquery-2.1.4.min.js" type="text/javascript"></script> | |
14 | +<link rel="import" href="../leafletjs-datalet.html" /> | |
15 | + | |
16 | +<leafletjs-datalet data-url="http://ckan.routetopa.eu/api/action/datastore_search?resource_id=73e02092-85a1-434e-85fe-0c9a43aa9a52&limit=10000" | |
17 | + fields='["result,records,Lat","result,records,Lng", "result,records,Link"]'></leafletjs-datalet> | |
18 | + | |
19 | + | |
20 | +</body> | |
21 | +</html> | |
0 | 22 | \ No newline at end of file | ... | ... |
datalets/leafletjs-geojson-datalet/docs.html
0 โ 100755
1 | +<!DOCTYPE html> | |
2 | +<html lang="en"> | |
3 | +<head> | |
4 | + <link rel="import" href="../../bower_components/iron-component-page/iron-component-page.html"> | |
5 | + <meta charset="UTF-8"> | |
6 | +</head> | |
7 | +<body> | |
8 | + | |
9 | +<iron-component-page src="leafletjs-datalet.html"></iron-component-page> | |
10 | + | |
11 | +</body> | |
12 | +</html> | |
0 | 13 | \ No newline at end of file | ... | ... |
datalets/leafletjs-geojson-datalet/leafletjs-geojson-datalet.html
0 โ 100755
1 | +<!-- | |
2 | +@license | |
3 | + The MIT License (MIT) | |
4 | + | |
5 | + Copyright (c) 2015 Dipartimento di Informatica - Universitร di Salerno - Italy | |
6 | + | |
7 | + Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | + of this software and associated documentation files (the "Software"), to deal | |
9 | + in the Software without restriction, including without limitation the rights | |
10 | + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | + copies of the Software, and to permit persons to whom the Software is | |
12 | + furnished to do so, subject to the following conditions: | |
13 | + | |
14 | + The above copyright notice and this permission notice shall be included in | |
15 | + all copies or substantial portions of the Software. | |
16 | + | |
17 | + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 | + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
23 | + THE SOFTWARE. | |
24 | +--> | |
25 | + | |
26 | +<!-- | |
27 | +* Developed by : | |
28 | +* ROUTE-TO-PA Project - grant No 645860. - www.routetopa.eu | |
29 | +* | |
30 | +--> | |
31 | + | |
32 | +<link rel="import" href="../base-ajax-json-alasql-datalet/base-ajax-json-alasql-datalet.html"> | |
33 | + | |
34 | +<!-- | |
35 | + | |
36 | +`leafletjs-datalet` is a map datalet based on open source project leafletjs <http://leafletjs.com/> | |
37 | + | |
38 | +Example: | |
39 | + | |
40 | + <leafletjs-datalet | |
41 | + data-url="http://ckan.routetopa.eu/api/action/datastore_search?resource_id=#" | |
42 | + fields='["field1","field2"]'> | |
43 | + </leafletjs-datalet> | |
44 | + | |
45 | +@element leafletjs-datalet | |
46 | +@status v0.1 | |
47 | +@demo demo/index.html | |
48 | +@group datalets | |
49 | +--> | |
50 | + | |
51 | +<dom-module name="leafletjs-geojson-datalet"> | |
52 | + <template> | |
53 | + <link rel="stylesheet" href="leafletsjs/leaflet.css" /> | |
54 | + <style> | |
55 | + #leafletjs {height: 600px;} | |
56 | + </style> | |
57 | + | |
58 | + <div id="leafletjs"></div> | |
59 | + <base-ajax-json-alasql-datalet data-url="{{dataUrl}}" fields="{{fields}}" data="{{data}}" title="{{title}}" description="{{description}}" export_menu="{{export_menu}}"></base-ajax-json-alasql-datalet> | |
60 | + | |
61 | + </template> | |
62 | + | |
63 | + <script src="leafletsjs/leaflet.js"></script> | |
64 | + | |
65 | + <script> | |
66 | + | |
67 | + var leafletjsBehavior = { | |
68 | + | |
69 | + /** | |
70 | + * Read markers coordinates from the data object and add the marker to the map. | |
71 | + * Call a method for the map redraw and set the viewport in order to fit all the markers in the viewport.. | |
72 | + * | |
73 | + * @method transformData | |
74 | + */ | |
75 | + presentData: function(){ | |
76 | + | |
77 | + var t = this; | |
78 | + try{ | |
79 | + this._component.map = L.map(this._component.$.leafletjs).setView([0, 0], 13, {reset:true}); | |
80 | + }catch(e){} | |
81 | + | |
82 | + | |
83 | + L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
84 | + maxZoom: 18, | |
85 | + attribution: 'Map data ยฉ <a href="http://openstreetmap.org">OpenStreetMap</a> contributors' | |
86 | + }).addTo(this._component.map); | |
87 | + | |
88 | + | |
89 | + L.Icon.Default.imagePath = 'http://services.routetopa.eu/DEEalerProvider/COMPONENTS/datalets/leafletjs-datalet/leafletsjs/images'; | |
90 | + | |
91 | + var coordinates = []; | |
92 | + var coordinates_index = 0; | |
93 | + var isArray = t.data[0].data[0].constructor === Array; | |
94 | + | |
95 | + for(var i=0; i<t.data[0].data.length; i++) | |
96 | + { | |
97 | + if(isArray) | |
98 | + { | |
99 | + if(!isNaN(t.data[0].data[i][0]) && !isNaN(t.data[0].data[i][1])) | |
100 | + coordinates.push([parseFloat(t.data[0].data[i][0]), parseFloat(t.data[0].data[i][1])]); | |
101 | + else | |
102 | + continue; | |
103 | + } | |
104 | + else | |
105 | + { | |
106 | + if(!isNaN(t.data[0].data[i]) && !isNaN(t.data[1].data[i])) | |
107 | + coordinates.push([parseFloat(t.data[0].data[i]), parseFloat(t.data[1].data[i])]); | |
108 | + else | |
109 | + continue; | |
110 | + } | |
111 | + | |
112 | + var marker = L.marker([coordinates[coordinates_index][0], coordinates[coordinates_index][1]]).addTo(t._component.map); | |
113 | + coordinates_index++; | |
114 | + | |
115 | + if(t.data.length > 2) | |
116 | + { | |
117 | + var popupText = ""; | |
118 | + for(var j=2; j<t.data.length; j++) | |
119 | + { | |
120 | + if(typeof t.data[j] != 'undefined' && typeof t.data[j].data[i] != 'undefined') | |
121 | + { | |
122 | + if (t.data[j].data[i].toString().match(new RegExp("^(http[s]?:\\/\\/(www\\.)?|ftp:\\/\\/(www\\.)?|www\\.){1}([0-9A-Za-z-\\.@:%_\+~#=]+)+((\\.[a-zA-Z]{2,3})+)(/(.)*)?(\\?(.)*)?"))) | |
123 | + popupText += '<image height="100" width="100" src="' + t.data[j].data[i] + '" /><br/>'; | |
124 | + else | |
125 | + popupText += '<span>' + t.data[j].name + ' : ' + t.data[j].data[i] + '</span><br/>' | |
126 | + } | |
127 | + } | |
128 | + | |
129 | + var popup = L.popup().setContent(popupText); | |
130 | + marker.bindPopup(popup); | |
131 | + } | |
132 | + } | |
133 | + | |
134 | + t._component.map._onResize(); | |
135 | + t._component.map.invalidateSize(false); | |
136 | + t._component.map.fitBounds(coordinates); | |
137 | + | |
138 | + } | |
139 | + }; | |
140 | + | |
141 | + | |
142 | + Polymer({ | |
143 | + | |
144 | + is : 'leafletjs-geojson-datalet', | |
145 | + | |
146 | + properties : { | |
147 | + /** | |
148 | + * Store a reference to the leafletjs map object created in 'ready' callback | |
149 | + * @attribute map | |
150 | + * @type Object | |
151 | + */ | |
152 | + map : | |
153 | + { | |
154 | + type: Object, | |
155 | + value:null | |
156 | + }, | |
157 | + /** | |
158 | + * An Array with all the markers extracted from the dataset | |
159 | + * @attribute markers | |
160 | + * @type Array | |
161 | + */ | |
162 | + markers : | |
163 | + { | |
164 | + type : Array, | |
165 | + value : [] | |
166 | + }, | |
167 | + /** | |
168 | + * It's the component behavior | |
169 | + * | |
170 | + * @attribute behavior | |
171 | + * @type Object | |
172 | + * @default {} | |
173 | + */ | |
174 | + behavior : { | |
175 | + type : Object, | |
176 | + value : {} | |
177 | + }, | |
178 | + /** | |
179 | + * Control the export menu | |
180 | + * xxxx BITMASK. FROM RIGHT : HTML, PNG, RTF, MY SPACE (eg. 1111 show all, 0000 hide all) | |
181 | + * | |
182 | + * @attribute export_menu | |
183 | + * @type Number | |
184 | + * @default 15 | |
185 | + */ | |
186 | + export_menu : { | |
187 | + type : Number, | |
188 | + value : 9 // xxxx BITMASK. FROM RIGHT : HTML, PNG, RTF, MY SPACE (eg. 1111 show all, 0000 hide all) | |
189 | + } | |
190 | + }, | |
191 | + | |
192 | + /** | |
193 | + * 'ready' callback create the map object, set the tileLayer to openstreetmap and the default icon image path. | |
194 | + * Moreover extend the leafletjsComponentBehavior with BaseDataletBehavior, WorkcycleBehavior and leafletjsBehavior | |
195 | + * and run the Datalet workcycle. | |
196 | + * | |
197 | + * @method ready | |
198 | + */ | |
199 | + ready: function(){ | |
200 | + this.behavior = $.extend(true, {}, BaseDataletBehavior, WorkcycleBehavior, AjaxJsonAlasqlBehavior, leafletjsBehavior); | |
201 | + this.async(function(){this.behavior.init(this)},100); | |
202 | + } | |
203 | + }); | |
204 | + </script> | |
205 | +</dom-module> | |
206 | + | ... | ... |
datalets/leafletjs-geojson-datalet/leafletjs-geojson-datalet.png
0 โ 100644
5.32 KB
datalets/leafletjs-geojson-datalet/leafletsjs/images/layers-2x.png
0 โ 100755
2.83 KB
datalets/leafletjs-geojson-datalet/leafletsjs/images/layers.png
0 โ 100755
1.47 KB
datalets/leafletjs-geojson-datalet/leafletsjs/images/marker-icon-2x.png
0 โ 100755
3.94 KB
datalets/leafletjs-geojson-datalet/leafletsjs/images/marker-icon.png
0 โ 100755
1.71 KB
datalets/leafletjs-geojson-datalet/leafletsjs/images/marker-shadow.png
0 โ 100755
797 Bytes
datalets/leafletjs-geojson-datalet/leafletsjs/leaflet-src.js
0 โ 100755
Changes suppressed. Click to show
1 | +/* | |
2 | + Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com | |
3 | + (c) 2010-2013, Vladimir Agafonkin | |
4 | + (c) 2010-2011, CloudMade | |
5 | +*/ | |
6 | +(function (window, document, undefined) { | |
7 | +var oldL = window.L, | |
8 | + L = {}; | |
9 | + | |
10 | +L.version = '0.7.3'; | |
11 | + | |
12 | +// define Leaflet for Node module pattern loaders, including Browserify | |
13 | +if (typeof module === 'object' && typeof module.exports === 'object') { | |
14 | + module.exports = L; | |
15 | + | |
16 | +// define Leaflet as an AMD module | |
17 | +} else if (typeof define === 'function' && define.amd) { | |
18 | + define(L); | |
19 | +} | |
20 | + | |
21 | +// define Leaflet as a global L variable, saving the original L to restore later if needed | |
22 | + | |
23 | +L.noConflict = function () { | |
24 | + window.L = oldL; | |
25 | + return this; | |
26 | +}; | |
27 | + | |
28 | +window.L = L; | |
29 | + | |
30 | + | |
31 | +/* | |
32 | + * L.Util contains various utility functions used throughout Leaflet code. | |
33 | + */ | |
34 | + | |
35 | +L.Util = { | |
36 | + extend: function (dest) { // (Object[, Object, ...]) -> | |
37 | + var sources = Array.prototype.slice.call(arguments, 1), | |
38 | + i, j, len, src; | |
39 | + | |
40 | + for (j = 0, len = sources.length; j < len; j++) { | |
41 | + src = sources[j] || {}; | |
42 | + for (i in src) { | |
43 | + if (src.hasOwnProperty(i)) { | |
44 | + dest[i] = src[i]; | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + return dest; | |
49 | + }, | |
50 | + | |
51 | + bind: function (fn, obj) { // (Function, Object) -> Function | |
52 | + var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null; | |
53 | + return function () { | |
54 | + return fn.apply(obj, args || arguments); | |
55 | + }; | |
56 | + }, | |
57 | + | |
58 | + stamp: (function () { | |
59 | + var lastId = 0, | |
60 | + key = '_leaflet_id'; | |
61 | + return function (obj) { | |
62 | + obj[key] = obj[key] || ++lastId; | |
63 | + return obj[key]; | |
64 | + }; | |
65 | + }()), | |
66 | + | |
67 | + invokeEach: function (obj, method, context) { | |
68 | + var i, args; | |
69 | + | |
70 | + if (typeof obj === 'object') { | |
71 | + args = Array.prototype.slice.call(arguments, 3); | |
72 | + | |
73 | + for (i in obj) { | |
74 | + method.apply(context, [i, obj[i]].concat(args)); | |
75 | + } | |
76 | + return true; | |
77 | + } | |
78 | + | |
79 | + return false; | |
80 | + }, | |
81 | + | |
82 | + limitExecByInterval: function (fn, time, context) { | |
83 | + var lock, execOnUnlock; | |
84 | + | |
85 | + return function wrapperFn() { | |
86 | + var args = arguments; | |
87 | + | |
88 | + if (lock) { | |
89 | + execOnUnlock = true; | |
90 | + return; | |
91 | + } | |
92 | + | |
93 | + lock = true; | |
94 | + | |
95 | + setTimeout(function () { | |
96 | + lock = false; | |
97 | + | |
98 | + if (execOnUnlock) { | |
99 | + wrapperFn.apply(context, args); | |
100 | + execOnUnlock = false; | |
101 | + } | |
102 | + }, time); | |
103 | + | |
104 | + fn.apply(context, args); | |
105 | + }; | |
106 | + }, | |
107 | + | |
108 | + falseFn: function () { | |
109 | + return false; | |
110 | + }, | |
111 | + | |
112 | + formatNum: function (num, digits) { | |
113 | + var pow = Math.pow(10, digits || 5); | |
114 | + return Math.round(num * pow) / pow; | |
115 | + }, | |
116 | + | |
117 | + trim: function (str) { | |
118 | + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); | |
119 | + }, | |
120 | + | |
121 | + splitWords: function (str) { | |
122 | + return L.Util.trim(str).split(/\s+/); | |
123 | + }, | |
124 | + | |
125 | + setOptions: function (obj, options) { | |
126 | + obj.options = L.extend({}, obj.options, options); | |
127 | + return obj.options; | |
128 | + }, | |
129 | + | |
130 | + getParamString: function (obj, existingUrl, uppercase) { | |
131 | + var params = []; | |
132 | + for (var i in obj) { | |
133 | + params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); | |
134 | + } | |
135 | + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); | |
136 | + }, | |
137 | + template: function (str, data) { | |
138 | + return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { | |
139 | + var value = data[key]; | |
140 | + if (value === undefined) { | |
141 | + throw new Error('No value provided for variable ' + str); | |
142 | + } else if (typeof value === 'function') { | |
143 | + value = value(data); | |
144 | + } | |
145 | + return value; | |
146 | + }); | |
147 | + }, | |
148 | + | |
149 | + isArray: Array.isArray || function (obj) { | |
150 | + return (Object.prototype.toString.call(obj) === '[object Array]'); | |
151 | + }, | |
152 | + | |
153 | + emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' | |
154 | +}; | |
155 | + | |
156 | +(function () { | |
157 | + | |
158 | + // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ | |
159 | + | |
160 | + function getPrefixed(name) { | |
161 | + var i, fn, | |
162 | + prefixes = ['webkit', 'moz', 'o', 'ms']; | |
163 | + | |
164 | + for (i = 0; i < prefixes.length && !fn; i++) { | |
165 | + fn = window[prefixes[i] + name]; | |
166 | + } | |
167 | + | |
168 | + return fn; | |
169 | + } | |
170 | + | |
171 | + var lastTime = 0; | |
172 | + | |
173 | + function timeoutDefer(fn) { | |
174 | + var time = +new Date(), | |
175 | + timeToCall = Math.max(0, 16 - (time - lastTime)); | |
176 | + | |
177 | + lastTime = time + timeToCall; | |
178 | + return window.setTimeout(fn, timeToCall); | |
179 | + } | |
180 | + | |
181 | + var requestFn = window.requestAnimationFrame || | |
182 | + getPrefixed('RequestAnimationFrame') || timeoutDefer; | |
183 | + | |
184 | + var cancelFn = window.cancelAnimationFrame || | |
185 | + getPrefixed('CancelAnimationFrame') || | |
186 | + getPrefixed('CancelRequestAnimationFrame') || | |
187 | + function (id) { window.clearTimeout(id); }; | |
188 | + | |
189 | + | |
190 | + L.Util.requestAnimFrame = function (fn, context, immediate, element) { | |
191 | + fn = L.bind(fn, context); | |
192 | + | |
193 | + if (immediate && requestFn === timeoutDefer) { | |
194 | + fn(); | |
195 | + } else { | |
196 | + return requestFn.call(window, fn, element); | |
197 | + } | |
198 | + }; | |
199 | + | |
200 | + L.Util.cancelAnimFrame = function (id) { | |
201 | + if (id) { | |
202 | + cancelFn.call(window, id); | |
203 | + } | |
204 | + }; | |
205 | + | |
206 | +}()); | |
207 | + | |
208 | +// shortcuts for most used utility functions | |
209 | +L.extend = L.Util.extend; | |
210 | +L.bind = L.Util.bind; | |
211 | +L.stamp = L.Util.stamp; | |
212 | +L.setOptions = L.Util.setOptions; | |
213 | + | |
214 | + | |
215 | +/* | |
216 | + * L.Class powers the OOP facilities of the library. | |
217 | + * Thanks to John Resig and Dean Edwards for inspiration! | |
218 | + */ | |
219 | + | |
220 | +L.Class = function () {}; | |
221 | + | |
222 | +L.Class.extend = function (props) { | |
223 | + | |
224 | + // extended class with the new prototype | |
225 | + var NewClass = function () { | |
226 | + | |
227 | + // call the constructor | |
228 | + if (this.initialize) { | |
229 | + this.initialize.apply(this, arguments); | |
230 | + } | |
231 | + | |
232 | + // call all constructor hooks | |
233 | + if (this._initHooks) { | |
234 | + this.callInitHooks(); | |
235 | + } | |
236 | + }; | |
237 | + | |
238 | + // instantiate class without calling constructor | |
239 | + var F = function () {}; | |
240 | + F.prototype = this.prototype; | |
241 | + | |
242 | + var proto = new F(); | |
243 | + proto.constructor = NewClass; | |
244 | + | |
245 | + NewClass.prototype = proto; | |
246 | + | |
247 | + //inherit parent's statics | |
248 | + for (var i in this) { | |
249 | + if (this.hasOwnProperty(i) && i !== 'prototype') { | |
250 | + NewClass[i] = this[i]; | |
251 | + } | |
252 | + } | |
253 | + | |
254 | + // mix static properties into the class | |
255 | + if (props.statics) { | |
256 | + L.extend(NewClass, props.statics); | |
257 | + delete props.statics; | |
258 | + } | |
259 | + | |
260 | + // mix includes into the prototype | |
261 | + if (props.includes) { | |
262 | + L.Util.extend.apply(null, [proto].concat(props.includes)); | |
263 | + delete props.includes; | |
264 | + } | |
265 | + | |
266 | + // merge options | |
267 | + if (props.options && proto.options) { | |
268 | + props.options = L.extend({}, proto.options, props.options); | |
269 | + } | |
270 | + | |
271 | + // mix given properties into the prototype | |
272 | + L.extend(proto, props); | |
273 | + | |
274 | + proto._initHooks = []; | |
275 | + | |
276 | + var parent = this; | |
277 | + // jshint camelcase: false | |
278 | + NewClass.__super__ = parent.prototype; | |
279 | + | |
280 | + // add method for calling all hooks | |
281 | + proto.callInitHooks = function () { | |
282 | + | |
283 | + if (this._initHooksCalled) { return; } | |
284 | + | |
285 | + if (parent.prototype.callInitHooks) { | |
286 | + parent.prototype.callInitHooks.call(this); | |
287 | + } | |
288 | + | |
289 | + this._initHooksCalled = true; | |
290 | + | |
291 | + for (var i = 0, len = proto._initHooks.length; i < len; i++) { | |
292 | + proto._initHooks[i].call(this); | |
293 | + } | |
294 | + }; | |
295 | + | |
296 | + return NewClass; | |
297 | +}; | |
298 | + | |
299 | + | |
300 | +// method for adding properties to prototype | |
301 | +L.Class.include = function (props) { | |
302 | + L.extend(this.prototype, props); | |
303 | +}; | |
304 | + | |
305 | +// merge new default options to the Class | |
306 | +L.Class.mergeOptions = function (options) { | |
307 | + L.extend(this.prototype.options, options); | |
308 | +}; | |
309 | + | |
310 | +// add a constructor hook | |
311 | +L.Class.addInitHook = function (fn) { // (Function) || (String, args...) | |
312 | + var args = Array.prototype.slice.call(arguments, 1); | |
313 | + | |
314 | + var init = typeof fn === 'function' ? fn : function () { | |
315 | + this[fn].apply(this, args); | |
316 | + }; | |
317 | + | |
318 | + this.prototype._initHooks = this.prototype._initHooks || []; | |
319 | + this.prototype._initHooks.push(init); | |
320 | +}; | |
321 | + | |
322 | + | |
323 | +/* | |
324 | + * L.Mixin.Events is used to add custom events functionality to Leaflet classes. | |
325 | + */ | |
326 | + | |
327 | +var eventsKey = '_leaflet_events'; | |
328 | + | |
329 | +L.Mixin = {}; | |
330 | + | |
331 | +L.Mixin.Events = { | |
332 | + | |
333 | + addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object]) | |
334 | + | |
335 | + // types can be a map of types/handlers | |
336 | + if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; } | |
337 | + | |
338 | + var events = this[eventsKey] = this[eventsKey] || {}, | |
339 | + contextId = context && context !== this && L.stamp(context), | |
340 | + i, len, event, type, indexKey, indexLenKey, typeIndex; | |
341 | + | |
342 | + // types can be a string of space-separated words | |
343 | + types = L.Util.splitWords(types); | |
344 | + | |
345 | + for (i = 0, len = types.length; i < len; i++) { | |
346 | + event = { | |
347 | + action: fn, | |
348 | + context: context || this | |
349 | + }; | |
350 | + type = types[i]; | |
351 | + | |
352 | + if (contextId) { | |
353 | + // store listeners of a particular context in a separate hash (if it has an id) | |
354 | + // gives a major performance boost when removing thousands of map layers | |
355 | + | |
356 | + indexKey = type + '_idx'; | |
357 | + indexLenKey = indexKey + '_len'; | |
358 | + | |
359 | + typeIndex = events[indexKey] = events[indexKey] || {}; | |
360 | + | |
361 | + if (!typeIndex[contextId]) { | |
362 | + typeIndex[contextId] = []; | |
363 | + | |
364 | + // keep track of the number of keys in the index to quickly check if it's empty | |
365 | + events[indexLenKey] = (events[indexLenKey] || 0) + 1; | |
366 | + } | |
367 | + | |
368 | + typeIndex[contextId].push(event); | |
369 | + | |
370 | + | |
371 | + } else { | |
372 | + events[type] = events[type] || []; | |
373 | + events[type].push(event); | |
374 | + } | |
375 | + } | |
376 | + | |
377 | + return this; | |
378 | + }, | |
379 | + | |
380 | + hasEventListeners: function (type) { // (String) -> Boolean | |
381 | + var events = this[eventsKey]; | |
382 | + return !!events && ((type in events && events[type].length > 0) || | |
383 | + (type + '_idx' in events && events[type + '_idx_len'] > 0)); | |
384 | + }, | |
385 | + | |
386 | + removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object]) | |
387 | + | |
388 | + if (!this[eventsKey]) { | |
389 | + return this; | |
390 | + } | |
391 | + | |
392 | + if (!types) { | |
393 | + return this.clearAllEventListeners(); | |
394 | + } | |
395 | + | |
396 | + if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; } | |
397 | + | |
398 | + var events = this[eventsKey], | |
399 | + contextId = context && context !== this && L.stamp(context), | |
400 | + i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed; | |
401 | + | |
402 | + types = L.Util.splitWords(types); | |
403 | + | |
404 | + for (i = 0, len = types.length; i < len; i++) { | |
405 | + type = types[i]; | |
406 | + indexKey = type + '_idx'; | |
407 | + indexLenKey = indexKey + '_len'; | |
408 | + | |
409 | + typeIndex = events[indexKey]; | |
410 | + | |
411 | + if (!fn) { | |
412 | + // clear all listeners for a type if function isn't specified | |
413 | + delete events[type]; | |
414 | + delete events[indexKey]; | |
415 | + delete events[indexLenKey]; | |
416 | + | |
417 | + } else { | |
418 | + listeners = contextId && typeIndex ? typeIndex[contextId] : events[type]; | |
419 | + | |
420 | + if (listeners) { | |
421 | + for (j = listeners.length - 1; j >= 0; j--) { | |
422 | + if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) { | |
423 | + removed = listeners.splice(j, 1); | |
424 | + // set the old action to a no-op, because it is possible | |
425 | + // that the listener is being iterated over as part of a dispatch | |
426 | + removed[0].action = L.Util.falseFn; | |
427 | + } | |
428 | + } | |
429 | + | |
430 | + if (context && typeIndex && (listeners.length === 0)) { | |
431 | + delete typeIndex[contextId]; | |
432 | + events[indexLenKey]--; | |
433 | + } | |
434 | + } | |
435 | + } | |
436 | + } | |
437 | + | |
438 | + return this; | |
439 | + }, | |
440 | + | |
441 | + clearAllEventListeners: function () { | |
442 | + delete this[eventsKey]; | |
443 | + return this; | |
444 | + }, | |
445 | + | |
446 | + fireEvent: function (type, data) { // (String[, Object]) | |
447 | + if (!this.hasEventListeners(type)) { | |
448 | + return this; | |
449 | + } | |
450 | + | |
451 | + var event = L.Util.extend({}, data, { type: type, target: this }); | |
452 | + | |
453 | + var events = this[eventsKey], | |
454 | + listeners, i, len, typeIndex, contextId; | |
455 | + | |
456 | + if (events[type]) { | |
457 | + // make sure adding/removing listeners inside other listeners won't cause infinite loop | |
458 | + listeners = events[type].slice(); | |
459 | + | |
460 | + for (i = 0, len = listeners.length; i < len; i++) { | |
461 | + listeners[i].action.call(listeners[i].context, event); | |
462 | + } | |
463 | + } | |
464 | + | |
465 | + // fire event for the context-indexed listeners as well | |
466 | + typeIndex = events[type + '_idx']; | |
467 | + | |
468 | + for (contextId in typeIndex) { | |
469 | + listeners = typeIndex[contextId].slice(); | |
470 | + | |
471 | + if (listeners) { | |
472 | + for (i = 0, len = listeners.length; i < len; i++) { | |
473 | + listeners[i].action.call(listeners[i].context, event); | |
474 | + } | |
475 | + } | |
476 | + } | |
477 | + | |
478 | + return this; | |
479 | + }, | |
480 | + | |
481 | + addOneTimeEventListener: function (types, fn, context) { | |
482 | + | |
483 | + if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; } | |
484 | + | |
485 | + var handler = L.bind(function () { | |
486 | + this | |
487 | + .removeEventListener(types, fn, context) | |
488 | + .removeEventListener(types, handler, context); | |
489 | + }, this); | |
490 | + | |
491 | + return this | |
492 | + .addEventListener(types, fn, context) | |
493 | + .addEventListener(types, handler, context); | |
494 | + } | |
495 | +}; | |
496 | + | |
497 | +L.Mixin.Events.on = L.Mixin.Events.addEventListener; | |
498 | +L.Mixin.Events.off = L.Mixin.Events.removeEventListener; | |
499 | +L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener; | |
500 | +L.Mixin.Events.fire = L.Mixin.Events.fireEvent; | |
501 | + | |
502 | + | |
503 | +/* | |
504 | + * L.Browser handles different browser and feature detections for internal Leaflet use. | |
505 | + */ | |
506 | + | |
507 | +(function () { | |
508 | + | |
509 | + var ie = 'ActiveXObject' in window, | |
510 | + ielt9 = ie && !document.addEventListener, | |
511 | + | |
512 | + // terrible browser detection to work around Safari / iOS / Android browser bugs | |
513 | + ua = navigator.userAgent.toLowerCase(), | |
514 | + webkit = ua.indexOf('webkit') !== -1, | |
515 | + chrome = ua.indexOf('chrome') !== -1, | |
516 | + phantomjs = ua.indexOf('phantom') !== -1, | |
517 | + android = ua.indexOf('android') !== -1, | |
518 | + android23 = ua.search('android [23]') !== -1, | |
519 | + gecko = ua.indexOf('gecko') !== -1, | |
520 | + | |
521 | + mobile = typeof orientation !== undefined + '', | |
522 | + msPointer = window.navigator && window.navigator.msPointerEnabled && | |
523 | + window.navigator.msMaxTouchPoints && !window.PointerEvent, | |
524 | + pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) || | |
525 | + msPointer, | |
526 | + retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || | |
527 | + ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && | |
528 | + window.matchMedia('(min-resolution:144dpi)').matches), | |
529 | + | |
530 | + doc = document.documentElement, | |
531 | + ie3d = ie && ('transition' in doc.style), | |
532 | + webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, | |
533 | + gecko3d = 'MozPerspective' in doc.style, | |
534 | + opera3d = 'OTransition' in doc.style, | |
535 | + any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs; | |
536 | + | |
537 | + | |
538 | + // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch. | |
539 | + // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151 | |
540 | + | |
541 | + var touch = !window.L_NO_TOUCH && !phantomjs && (function () { | |
542 | + | |
543 | + var startName = 'ontouchstart'; | |
544 | + | |
545 | + // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc. | |
546 | + if (pointer || (startName in doc)) { | |
547 | + return true; | |
548 | + } | |
549 | + | |
550 | + // Firefox/Gecko | |
551 | + var div = document.createElement('div'), | |
552 | + supported = false; | |
553 | + | |
554 | + if (!div.setAttribute) { | |
555 | + return false; | |
556 | + } | |
557 | + div.setAttribute(startName, 'return;'); | |
558 | + | |
559 | + if (typeof div[startName] === 'function') { | |
560 | + supported = true; | |
561 | + } | |
562 | + | |
563 | + div.removeAttribute(startName); | |
564 | + div = null; | |
565 | + | |
566 | + return supported; | |
567 | + }()); | |
568 | + | |
569 | + | |
570 | + L.Browser = { | |
571 | + ie: ie, | |
572 | + ielt9: ielt9, | |
573 | + webkit: webkit, | |
574 | + gecko: gecko && !webkit && !window.opera && !ie, | |
575 | + | |
576 | + android: android, | |
577 | + android23: android23, | |
578 | + | |
579 | + chrome: chrome, | |
580 | + | |
581 | + ie3d: ie3d, | |
582 | + webkit3d: webkit3d, | |
583 | + gecko3d: gecko3d, | |
584 | + opera3d: opera3d, | |
585 | + any3d: any3d, | |
586 | + | |
587 | + mobile: mobile, | |
588 | + mobileWebkit: mobile && webkit, | |
589 | + mobileWebkit3d: mobile && webkit3d, | |
590 | + mobileOpera: mobile && window.opera, | |
591 | + | |
592 | + touch: touch, | |
593 | + msPointer: msPointer, | |
594 | + pointer: pointer, | |
595 | + | |
596 | + retina: retina | |
597 | + }; | |
598 | + | |
599 | +}()); | |
600 | + | |
601 | + | |
602 | +/* | |
603 | + * L.Point represents a point with x and y coordinates. | |
604 | + */ | |
605 | + | |
606 | +L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) { | |
607 | + this.x = (round ? Math.round(x) : x); | |
608 | + this.y = (round ? Math.round(y) : y); | |
609 | +}; | |
610 | + | |
611 | +L.Point.prototype = { | |
612 | + | |
613 | + clone: function () { | |
614 | + return new L.Point(this.x, this.y); | |
615 | + }, | |
616 | + | |
617 | + // non-destructive, returns a new point | |
618 | + add: function (point) { | |
619 | + return this.clone()._add(L.point(point)); | |
620 | + }, | |
621 | + | |
622 | + // destructive, used directly for performance in situations where it's safe to modify existing point | |
623 | + _add: function (point) { | |
624 | + this.x += point.x; | |
625 | + this.y += point.y; | |
626 | + return this; | |
627 | + }, | |
628 | + | |
629 | + subtract: function (point) { | |
630 | + return this.clone()._subtract(L.point(point)); | |
631 | + }, | |
632 | + | |
633 | + _subtract: function (point) { | |
634 | + this.x -= point.x; | |
635 | + this.y -= point.y; | |
636 | + return this; | |
637 | + }, | |
638 | + | |
639 | + divideBy: function (num) { | |
640 | + return this.clone()._divideBy(num); | |
641 | + }, | |
642 | + | |
643 | + _divideBy: function (num) { | |
644 | + this.x /= num; | |
645 | + this.y /= num; | |
646 | + return this; | |
647 | + }, | |
648 | + | |
649 | + multiplyBy: function (num) { | |
650 | + return this.clone()._multiplyBy(num); | |
651 | + }, | |
652 | + | |
653 | + _multiplyBy: function (num) { | |
654 | + this.x *= num; | |
655 | + this.y *= num; | |
656 | + return this; | |
657 | + }, | |
658 | + | |
659 | + round: function () { | |
660 | + return this.clone()._round(); | |
661 | + }, | |
662 | + | |
663 | + _round: function () { | |
664 | + this.x = Math.round(this.x); | |
665 | + this.y = Math.round(this.y); | |
666 | + return this; | |
667 | + }, | |
668 | + | |
669 | + floor: function () { | |
670 | + return this.clone()._floor(); | |
671 | + }, | |
672 | + | |
673 | + _floor: function () { | |
674 | + this.x = Math.floor(this.x); | |
675 | + this.y = Math.floor(this.y); | |
676 | + return this; | |
677 | + }, | |
678 | + | |
679 | + distanceTo: function (point) { | |
680 | + point = L.point(point); | |
681 | + | |
682 | + var x = point.x - this.x, | |
683 | + y = point.y - this.y; | |
684 | + | |
685 | + return Math.sqrt(x * x + y * y); | |
686 | + }, | |
687 | + | |
688 | + equals: function (point) { | |
689 | + point = L.point(point); | |
690 | + | |
691 | + return point.x === this.x && | |
692 | + point.y === this.y; | |
693 | + }, | |
694 | + | |
695 | + contains: function (point) { | |
696 | + point = L.point(point); | |
697 | + | |
698 | + return Math.abs(point.x) <= Math.abs(this.x) && | |
699 | + Math.abs(point.y) <= Math.abs(this.y); | |
700 | + }, | |
701 | + | |
702 | + toString: function () { | |
703 | + return 'Point(' + | |
704 | + L.Util.formatNum(this.x) + ', ' + | |
705 | + L.Util.formatNum(this.y) + ')'; | |
706 | + } | |
707 | +}; | |
708 | + | |
709 | +L.point = function (x, y, round) { | |
710 | + if (x instanceof L.Point) { | |
711 | + return x; | |
712 | + } | |
713 | + if (L.Util.isArray(x)) { | |
714 | + return new L.Point(x[0], x[1]); | |
715 | + } | |
716 | + if (x === undefined || x === null) { | |
717 | + return x; | |
718 | + } | |
719 | + return new L.Point(x, y, round); | |
720 | +}; | |
721 | + | |
722 | + | |
723 | +/* | |
724 | + * L.Bounds represents a rectangular area on the screen in pixel coordinates. | |
725 | + */ | |
726 | + | |
727 | +L.Bounds = function (a, b) { //(Point, Point) or Point[] | |
728 | + if (!a) { return; } | |
729 | + | |
730 | + var points = b ? [a, b] : a; | |
731 | + | |
732 | + for (var i = 0, len = points.length; i < len; i++) { | |
733 | + this.extend(points[i]); | |
734 | + } | |
735 | +}; | |
736 | + | |
737 | +L.Bounds.prototype = { | |
738 | + // extend the bounds to contain the given point | |
739 | + extend: function (point) { // (Point) | |
740 | + point = L.point(point); | |
741 | + | |
742 | + if (!this.min && !this.max) { | |
743 | + this.min = point.clone(); | |
744 | + this.max = point.clone(); | |
745 | + } else { | |
746 | + this.min.x = Math.min(point.x, this.min.x); | |
747 | + this.max.x = Math.max(point.x, this.max.x); | |
748 | + this.min.y = Math.min(point.y, this.min.y); | |
749 | + this.max.y = Math.max(point.y, this.max.y); | |
750 | + } | |
751 | + return this; | |
752 | + }, | |
753 | + | |
754 | + getCenter: function (round) { // (Boolean) -> Point | |
755 | + return new L.Point( | |
756 | + (this.min.x + this.max.x) / 2, | |
757 | + (this.min.y + this.max.y) / 2, round); | |
758 | + }, | |
759 | + | |
760 | + getBottomLeft: function () { // -> Point | |
761 | + return new L.Point(this.min.x, this.max.y); | |
762 | + }, | |
763 | + | |
764 | + getTopRight: function () { // -> Point | |
765 | + return new L.Point(this.max.x, this.min.y); | |
766 | + }, | |
767 | + | |
768 | + getSize: function () { | |
769 | + return this.max.subtract(this.min); | |
770 | + }, | |
771 | + | |
772 | + contains: function (obj) { // (Bounds) or (Point) -> Boolean | |
773 | + var min, max; | |
774 | + | |
775 | + if (typeof obj[0] === 'number' || obj instanceof L.Point) { | |
776 | + obj = L.point(obj); | |
777 | + } else { | |
778 | + obj = L.bounds(obj); | |
779 | + } | |
780 | + | |
781 | + if (obj instanceof L.Bounds) { | |
782 | + min = obj.min; | |
783 | + max = obj.max; | |
784 | + } else { | |
785 | + min = max = obj; | |
786 | + } | |
787 | + | |
788 | + return (min.x >= this.min.x) && | |
789 | + (max.x <= this.max.x) && | |
790 | + (min.y >= this.min.y) && | |
791 | + (max.y <= this.max.y); | |
792 | + }, | |
793 | + | |
794 | + intersects: function (bounds) { // (Bounds) -> Boolean | |
795 | + bounds = L.bounds(bounds); | |
796 | + | |
797 | + var min = this.min, | |
798 | + max = this.max, | |
799 | + min2 = bounds.min, | |
800 | + max2 = bounds.max, | |
801 | + xIntersects = (max2.x >= min.x) && (min2.x <= max.x), | |
802 | + yIntersects = (max2.y >= min.y) && (min2.y <= max.y); | |
803 | + | |
804 | + return xIntersects && yIntersects; | |
805 | + }, | |
806 | + | |
807 | + isValid: function () { | |
808 | + return !!(this.min && this.max); | |
809 | + } | |
810 | +}; | |
811 | + | |
812 | +L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) | |
813 | + if (!a || a instanceof L.Bounds) { | |
814 | + return a; | |
815 | + } | |
816 | + return new L.Bounds(a, b); | |
817 | +}; | |
818 | + | |
819 | + | |
820 | +/* | |
821 | + * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix. | |
822 | + */ | |
823 | + | |
824 | +L.Transformation = function (a, b, c, d) { | |
825 | + this._a = a; | |
826 | + this._b = b; | |
827 | + this._c = c; | |
828 | + this._d = d; | |
829 | +}; | |
830 | + | |
831 | +L.Transformation.prototype = { | |
832 | + transform: function (point, scale) { // (Point, Number) -> Point | |
833 | + return this._transform(point.clone(), scale); | |
834 | + }, | |
835 | + | |
836 | + // destructive transform (faster) | |
837 | + _transform: function (point, scale) { | |
838 | + scale = scale || 1; | |
839 | + point.x = scale * (this._a * point.x + this._b); | |
840 | + point.y = scale * (this._c * point.y + this._d); | |
841 | + return point; | |
842 | + }, | |
843 | + | |
844 | + untransform: function (point, scale) { | |
845 | + scale = scale || 1; | |
846 | + return new L.Point( | |
847 | + (point.x / scale - this._b) / this._a, | |
848 | + (point.y / scale - this._d) / this._c); | |
849 | + } | |
850 | +}; | |
851 | + | |
852 | + | |
853 | +/* | |
854 | + * L.DomUtil contains various utility functions for working with DOM. | |
855 | + */ | |
856 | + | |
857 | +L.DomUtil = { | |
858 | + get: function (id) { | |
859 | + return (typeof id === 'string' ? document.getElementById(id) : id); | |
860 | + }, | |
861 | + | |
862 | + getStyle: function (el, style) { | |
863 | + | |
864 | + var value = el.style[style]; | |
865 | + | |
866 | + if (!value && el.currentStyle) { | |
867 | + value = el.currentStyle[style]; | |
868 | + } | |
869 | + | |
870 | + if ((!value || value === 'auto') && document.defaultView) { | |
871 | + var css = document.defaultView.getComputedStyle(el, null); | |
872 | + value = css ? css[style] : null; | |
873 | + } | |
874 | + | |
875 | + return value === 'auto' ? null : value; | |
876 | + }, | |
877 | + | |
878 | + getViewportOffset: function (element) { | |
879 | + | |
880 | + var top = 0, | |
881 | + left = 0, | |
882 | + el = element, | |
883 | + docBody = document.body, | |
884 | + docEl = document.documentElement, | |
885 | + pos; | |
886 | + | |
887 | + do { | |
888 | + top += el.offsetTop || 0; | |
889 | + left += el.offsetLeft || 0; | |
890 | + | |
891 | + //add borders | |
892 | + top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0; | |
893 | + left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0; | |
894 | + | |
895 | + pos = L.DomUtil.getStyle(el, 'position'); | |
896 | + | |
897 | + if (el.offsetParent === docBody && pos === 'absolute') { break; } | |
898 | + | |
899 | + if (pos === 'fixed') { | |
900 | + top += docBody.scrollTop || docEl.scrollTop || 0; | |
901 | + left += docBody.scrollLeft || docEl.scrollLeft || 0; | |
902 | + break; | |
903 | + } | |
904 | + | |
905 | + if (pos === 'relative' && !el.offsetLeft) { | |
906 | + var width = L.DomUtil.getStyle(el, 'width'), | |
907 | + maxWidth = L.DomUtil.getStyle(el, 'max-width'), | |
908 | + r = el.getBoundingClientRect(); | |
909 | + | |
910 | + if (width !== 'none' || maxWidth !== 'none') { | |
911 | + left += r.left + el.clientLeft; | |
912 | + } | |
913 | + | |
914 | + //calculate full y offset since we're breaking out of the loop | |
915 | + top += r.top + (docBody.scrollTop || docEl.scrollTop || 0); | |
916 | + | |
917 | + break; | |
918 | + } | |
919 | + | |
920 | + el = el.offsetParent; | |
921 | + | |
922 | + } while (el); | |
923 | + | |
924 | + el = element; | |
925 | + | |
926 | + do { | |
927 | + if (el === docBody) { break; } | |
928 | + | |
929 | + top -= el.scrollTop || 0; | |
930 | + left -= el.scrollLeft || 0; | |
931 | + | |
932 | + el = el.parentNode; | |
933 | + } while (el); | |
934 | + | |
935 | + return new L.Point(left, top); | |
936 | + }, | |
937 | + | |
938 | + documentIsLtr: function () { | |
939 | + if (!L.DomUtil._docIsLtrCached) { | |
940 | + L.DomUtil._docIsLtrCached = true; | |
941 | + L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr'; | |
942 | + } | |
943 | + return L.DomUtil._docIsLtr; | |
944 | + }, | |
945 | + | |
946 | + create: function (tagName, className, container) { | |
947 | + | |
948 | + var el = document.createElement(tagName); | |
949 | + el.className = className; | |
950 | + | |
951 | + if (container) { | |
952 | + container.appendChild(el); | |
953 | + } | |
954 | + | |
955 | + return el; | |
956 | + }, | |
957 | + | |
958 | + hasClass: function (el, name) { | |
959 | + if (el.classList !== undefined) { | |
960 | + return el.classList.contains(name); | |
961 | + } | |
962 | + var className = L.DomUtil._getClass(el); | |
963 | + return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); | |
964 | + }, | |
965 | + | |
966 | + addClass: function (el, name) { | |
967 | + if (el.classList !== undefined) { | |
968 | + var classes = L.Util.splitWords(name); | |
969 | + for (var i = 0, len = classes.length; i < len; i++) { | |
970 | + el.classList.add(classes[i]); | |
971 | + } | |
972 | + } else if (!L.DomUtil.hasClass(el, name)) { | |
973 | + var className = L.DomUtil._getClass(el); | |
974 | + L.DomUtil._setClass(el, (className ? className + ' ' : '') + name); | |
975 | + } | |
976 | + }, | |
977 | + | |
978 | + removeClass: function (el, name) { | |
979 | + if (el.classList !== undefined) { | |
980 | + el.classList.remove(name); | |
981 | + } else { | |
982 | + L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' '))); | |
983 | + } | |
984 | + }, | |
985 | + | |
986 | + _setClass: function (el, name) { | |
987 | + if (el.className.baseVal === undefined) { | |
988 | + el.className = name; | |
989 | + } else { | |
990 | + // in case of SVG element | |
991 | + el.className.baseVal = name; | |
992 | + } | |
993 | + }, | |
994 | + | |
995 | + _getClass: function (el) { | |
996 | + return el.className.baseVal === undefined ? el.className : el.className.baseVal; | |
997 | + }, | |
998 | + | |
999 | + setOpacity: function (el, value) { | |
1000 | + | |
1001 | + if ('opacity' in el.style) { | |
1002 | + el.style.opacity = value; | |
1003 | + | |
1004 | + } else if ('filter' in el.style) { | |
1005 | + | |
1006 | + var filter = false, | |
1007 | + filterName = 'DXImageTransform.Microsoft.Alpha'; | |
1008 | + | |
1009 | + // filters collection throws an error if we try to retrieve a filter that doesn't exist | |
1010 | + try { | |
1011 | + filter = el.filters.item(filterName); | |
1012 | + } catch (e) { | |
1013 | + // don't set opacity to 1 if we haven't already set an opacity, | |
1014 | + // it isn't needed and breaks transparent pngs. | |
1015 | + if (value === 1) { return; } | |
1016 | + } | |
1017 | + | |
1018 | + value = Math.round(value * 100); | |
1019 | + | |
1020 | + if (filter) { | |
1021 | + filter.Enabled = (value !== 100); | |
1022 | + filter.Opacity = value; | |
1023 | + } else { | |
1024 | + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; | |
1025 | + } | |
1026 | + } | |
1027 | + }, | |
1028 | + | |
1029 | + testProp: function (props) { | |
1030 | + | |
1031 | + var style = document.documentElement.style; | |
1032 | + | |
1033 | + for (var i = 0; i < props.length; i++) { | |
1034 | + if (props[i] in style) { | |
1035 | + return props[i]; | |
1036 | + } | |
1037 | + } | |
1038 | + return false; | |
1039 | + }, | |
1040 | + | |
1041 | + getTranslateString: function (point) { | |
1042 | + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate | |
1043 | + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care | |
1044 | + // (same speed either way), Opera 12 doesn't support translate3d | |
1045 | + | |
1046 | + var is3d = L.Browser.webkit3d, | |
1047 | + open = 'translate' + (is3d ? '3d' : '') + '(', | |
1048 | + close = (is3d ? ',0' : '') + ')'; | |
1049 | + | |
1050 | + return open + point.x + 'px,' + point.y + 'px' + close; | |
1051 | + }, | |
1052 | + | |
1053 | + getScaleString: function (scale, origin) { | |
1054 | + | |
1055 | + var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))), | |
1056 | + scaleStr = ' scale(' + scale + ') '; | |
1057 | + | |
1058 | + return preTranslateStr + scaleStr; | |
1059 | + }, | |
1060 | + | |
1061 | + setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean]) | |
1062 | + | |
1063 | + // jshint camelcase: false | |
1064 | + el._leaflet_pos = point; | |
1065 | + | |
1066 | + if (!disable3D && L.Browser.any3d) { | |
1067 | + el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point); | |
1068 | + } else { | |
1069 | + el.style.left = point.x + 'px'; | |
1070 | + el.style.top = point.y + 'px'; | |
1071 | + } | |
1072 | + }, | |
1073 | + | |
1074 | + getPosition: function (el) { | |
1075 | + // this method is only used for elements previously positioned using setPosition, | |
1076 | + // so it's safe to cache the position for performance | |
1077 | + | |
1078 | + // jshint camelcase: false | |
1079 | + return el._leaflet_pos; | |
1080 | + } | |
1081 | +}; | |
1082 | + | |
1083 | + | |
1084 | +// prefix style property names | |
1085 | + | |
1086 | +L.DomUtil.TRANSFORM = L.DomUtil.testProp( | |
1087 | + ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); | |
1088 | + | |
1089 | +// webkitTransition comes first because some browser versions that drop vendor prefix don't do | |
1090 | +// the same for the transitionend event, in particular the Android 4.1 stock browser | |
1091 | + | |
1092 | +L.DomUtil.TRANSITION = L.DomUtil.testProp( | |
1093 | + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); | |
1094 | + | |
1095 | +L.DomUtil.TRANSITION_END = | |
1096 | + L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? | |
1097 | + L.DomUtil.TRANSITION + 'End' : 'transitionend'; | |
1098 | + | |
1099 | +(function () { | |
1100 | + if ('onselectstart' in document) { | |
1101 | + L.extend(L.DomUtil, { | |
1102 | + disableTextSelection: function () { | |
1103 | + L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); | |
1104 | + }, | |
1105 | + | |
1106 | + enableTextSelection: function () { | |
1107 | + L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); | |
1108 | + } | |
1109 | + }); | |
1110 | + } else { | |
1111 | + var userSelectProperty = L.DomUtil.testProp( | |
1112 | + ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); | |
1113 | + | |
1114 | + L.extend(L.DomUtil, { | |
1115 | + disableTextSelection: function () { | |
1116 | + if (userSelectProperty) { | |
1117 | + var style = document.documentElement.style; | |
1118 | + this._userSelect = style[userSelectProperty]; | |
1119 | + style[userSelectProperty] = 'none'; | |
1120 | + } | |
1121 | + }, | |
1122 | + | |
1123 | + enableTextSelection: function () { | |
1124 | + if (userSelectProperty) { | |
1125 | + document.documentElement.style[userSelectProperty] = this._userSelect; | |
1126 | + delete this._userSelect; | |
1127 | + } | |
1128 | + } | |
1129 | + }); | |
1130 | + } | |
1131 | + | |
1132 | + L.extend(L.DomUtil, { | |
1133 | + disableImageDrag: function () { | |
1134 | + L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); | |
1135 | + }, | |
1136 | + | |
1137 | + enableImageDrag: function () { | |
1138 | + L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); | |
1139 | + } | |
1140 | + }); | |
1141 | +})(); | |
1142 | + | |
1143 | + | |
1144 | +/* | |
1145 | + * L.LatLng represents a geographical point with latitude and longitude coordinates. | |
1146 | + */ | |
1147 | + | |
1148 | +L.LatLng = function (lat, lng, alt) { // (Number, Number, Number) | |
1149 | + lat = parseFloat(lat); | |
1150 | + lng = parseFloat(lng); | |
1151 | + | |
1152 | + if (isNaN(lat) || isNaN(lng)) { | |
1153 | + throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); | |
1154 | + } | |
1155 | + | |
1156 | + this.lat = lat; | |
1157 | + this.lng = lng; | |
1158 | + | |
1159 | + if (alt !== undefined) { | |
1160 | + this.alt = parseFloat(alt); | |
1161 | + } | |
1162 | +}; | |
1163 | + | |
1164 | +L.extend(L.LatLng, { | |
1165 | + DEG_TO_RAD: Math.PI / 180, | |
1166 | + RAD_TO_DEG: 180 / Math.PI, | |
1167 | + MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check | |
1168 | +}); | |
1169 | + | |
1170 | +L.LatLng.prototype = { | |
1171 | + equals: function (obj) { // (LatLng) -> Boolean | |
1172 | + if (!obj) { return false; } | |
1173 | + | |
1174 | + obj = L.latLng(obj); | |
1175 | + | |
1176 | + var margin = Math.max( | |
1177 | + Math.abs(this.lat - obj.lat), | |
1178 | + Math.abs(this.lng - obj.lng)); | |
1179 | + | |
1180 | + return margin <= L.LatLng.MAX_MARGIN; | |
1181 | + }, | |
1182 | + | |
1183 | + toString: function (precision) { // (Number) -> String | |
1184 | + return 'LatLng(' + | |
1185 | + L.Util.formatNum(this.lat, precision) + ', ' + | |
1186 | + L.Util.formatNum(this.lng, precision) + ')'; | |
1187 | + }, | |
1188 | + | |
1189 | + // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula | |
1190 | + // TODO move to projection code, LatLng shouldn't know about Earth | |
1191 | + distanceTo: function (other) { // (LatLng) -> Number | |
1192 | + other = L.latLng(other); | |
1193 | + | |
1194 | + var R = 6378137, // earth radius in meters | |
1195 | + d2r = L.LatLng.DEG_TO_RAD, | |
1196 | + dLat = (other.lat - this.lat) * d2r, | |
1197 | + dLon = (other.lng - this.lng) * d2r, | |
1198 | + lat1 = this.lat * d2r, | |
1199 | + lat2 = other.lat * d2r, | |
1200 | + sin1 = Math.sin(dLat / 2), | |
1201 | + sin2 = Math.sin(dLon / 2); | |
1202 | + | |
1203 | + var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); | |
1204 | + | |
1205 | + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |
1206 | + }, | |
1207 | + | |
1208 | + wrap: function (a, b) { // (Number, Number) -> LatLng | |
1209 | + var lng = this.lng; | |
1210 | + | |
1211 | + a = a || -180; | |
1212 | + b = b || 180; | |
1213 | + | |
1214 | + lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a); | |
1215 | + | |
1216 | + return new L.LatLng(this.lat, lng); | |
1217 | + } | |
1218 | +}; | |
1219 | + | |
1220 | +L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number) | |
1221 | + if (a instanceof L.LatLng) { | |
1222 | + return a; | |
1223 | + } | |
1224 | + if (L.Util.isArray(a)) { | |
1225 | + if (typeof a[0] === 'number' || typeof a[0] === 'string') { | |
1226 | + return new L.LatLng(a[0], a[1], a[2]); | |
1227 | + } else { | |
1228 | + return null; | |
1229 | + } | |
1230 | + } | |
1231 | + if (a === undefined || a === null) { | |
1232 | + return a; | |
1233 | + } | |
1234 | + if (typeof a === 'object' && 'lat' in a) { | |
1235 | + return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon); | |
1236 | + } | |
1237 | + if (b === undefined) { | |
1238 | + return null; | |
1239 | + } | |
1240 | + return new L.LatLng(a, b); | |
1241 | +}; | |
1242 | + | |
1243 | + | |
1244 | + | |
1245 | +/* | |
1246 | + * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. | |
1247 | + */ | |
1248 | + | |
1249 | +L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) | |
1250 | + if (!southWest) { return; } | |
1251 | + | |
1252 | + var latlngs = northEast ? [southWest, northEast] : southWest; | |
1253 | + | |
1254 | + for (var i = 0, len = latlngs.length; i < len; i++) { | |
1255 | + this.extend(latlngs[i]); | |
1256 | + } | |
1257 | +}; | |
1258 | + | |
1259 | +L.LatLngBounds.prototype = { | |
1260 | + // extend the bounds to contain the given point or bounds | |
1261 | + extend: function (obj) { // (LatLng) or (LatLngBounds) | |
1262 | + if (!obj) { return this; } | |
1263 | + | |
1264 | + var latLng = L.latLng(obj); | |
1265 | + if (latLng !== null) { | |
1266 | + obj = latLng; | |
1267 | + } else { | |
1268 | + obj = L.latLngBounds(obj); | |
1269 | + } | |
1270 | + | |
1271 | + if (obj instanceof L.LatLng) { | |
1272 | + if (!this._southWest && !this._northEast) { | |
1273 | + this._southWest = new L.LatLng(obj.lat, obj.lng); | |
1274 | + this._northEast = new L.LatLng(obj.lat, obj.lng); | |
1275 | + } else { | |
1276 | + this._southWest.lat = Math.min(obj.lat, this._southWest.lat); | |
1277 | + this._southWest.lng = Math.min(obj.lng, this._southWest.lng); | |
1278 | + | |
1279 | + this._northEast.lat = Math.max(obj.lat, this._northEast.lat); | |
1280 | + this._northEast.lng = Math.max(obj.lng, this._northEast.lng); | |
1281 | + } | |
1282 | + } else if (obj instanceof L.LatLngBounds) { | |
1283 | + this.extend(obj._southWest); | |
1284 | + this.extend(obj._northEast); | |
1285 | + } | |
1286 | + return this; | |
1287 | + }, | |
1288 | + | |
1289 | + // extend the bounds by a percentage | |
1290 | + pad: function (bufferRatio) { // (Number) -> LatLngBounds | |
1291 | + var sw = this._southWest, | |
1292 | + ne = this._northEast, | |
1293 | + heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, | |
1294 | + widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; | |
1295 | + | |
1296 | + return new L.LatLngBounds( | |
1297 | + new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), | |
1298 | + new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); | |
1299 | + }, | |
1300 | + | |
1301 | + getCenter: function () { // -> LatLng | |
1302 | + return new L.LatLng( | |
1303 | + (this._southWest.lat + this._northEast.lat) / 2, | |
1304 | + (this._southWest.lng + this._northEast.lng) / 2); | |
1305 | + }, | |
1306 | + | |
1307 | + getSouthWest: function () { | |
1308 | + return this._southWest; | |
1309 | + }, | |
1310 | + | |
1311 | + getNorthEast: function () { | |
1312 | + return this._northEast; | |
1313 | + }, | |
1314 | + | |
1315 | + getNorthWest: function () { | |
1316 | + return new L.LatLng(this.getNorth(), this.getWest()); | |
1317 | + }, | |
1318 | + | |
1319 | + getSouthEast: function () { | |
1320 | + return new L.LatLng(this.getSouth(), this.getEast()); | |
1321 | + }, | |
1322 | + | |
1323 | + getWest: function () { | |
1324 | + return this._southWest.lng; | |
1325 | + }, | |
1326 | + | |
1327 | + getSouth: function () { | |
1328 | + return this._southWest.lat; | |
1329 | + }, | |
1330 | + | |
1331 | + getEast: function () { | |
1332 | + return this._northEast.lng; | |
1333 | + }, | |
1334 | + | |
1335 | + getNorth: function () { | |
1336 | + return this._northEast.lat; | |
1337 | + }, | |
1338 | + | |
1339 | + contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean | |
1340 | + if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { | |
1341 | + obj = L.latLng(obj); | |
1342 | + } else { | |
1343 | + obj = L.latLngBounds(obj); | |
1344 | + } | |
1345 | + | |
1346 | + var sw = this._southWest, | |
1347 | + ne = this._northEast, | |
1348 | + sw2, ne2; | |
1349 | + | |
1350 | + if (obj instanceof L.LatLngBounds) { | |
1351 | + sw2 = obj.getSouthWest(); | |
1352 | + ne2 = obj.getNorthEast(); | |
1353 | + } else { | |
1354 | + sw2 = ne2 = obj; | |
1355 | + } | |
1356 | + | |
1357 | + return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && | |
1358 | + (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); | |
1359 | + }, | |
1360 | + | |
1361 | + intersects: function (bounds) { // (LatLngBounds) | |
1362 | + bounds = L.latLngBounds(bounds); | |
1363 | + | |
1364 | + var sw = this._southWest, | |
1365 | + ne = this._northEast, | |
1366 | + sw2 = bounds.getSouthWest(), | |
1367 | + ne2 = bounds.getNorthEast(), | |
1368 | + | |
1369 | + latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), | |
1370 | + lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); | |
1371 | + | |
1372 | + return latIntersects && lngIntersects; | |
1373 | + }, | |
1374 | + | |
1375 | + toBBoxString: function () { | |
1376 | + return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); | |
1377 | + }, | |
1378 | + | |
1379 | + equals: function (bounds) { // (LatLngBounds) | |
1380 | + if (!bounds) { return false; } | |
1381 | + | |
1382 | + bounds = L.latLngBounds(bounds); | |
1383 | + | |
1384 | + return this._southWest.equals(bounds.getSouthWest()) && | |
1385 | + this._northEast.equals(bounds.getNorthEast()); | |
1386 | + }, | |
1387 | + | |
1388 | + isValid: function () { | |
1389 | + return !!(this._southWest && this._northEast); | |
1390 | + } | |
1391 | +}; | |
1392 | + | |
1393 | +//TODO International date line? | |
1394 | + | |
1395 | +L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) | |
1396 | + if (!a || a instanceof L.LatLngBounds) { | |
1397 | + return a; | |
1398 | + } | |
1399 | + return new L.LatLngBounds(a, b); | |
1400 | +}; | |
1401 | + | |
1402 | + | |
1403 | +/* | |
1404 | + * L.Projection contains various geographical projections used by CRS classes. | |
1405 | + */ | |
1406 | + | |
1407 | +L.Projection = {}; | |
1408 | + | |
1409 | + | |
1410 | +/* | |
1411 | + * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. | |
1412 | + */ | |
1413 | + | |
1414 | +L.Projection.SphericalMercator = { | |
1415 | + MAX_LATITUDE: 85.0511287798, | |
1416 | + | |
1417 | + project: function (latlng) { // (LatLng) -> Point | |
1418 | + var d = L.LatLng.DEG_TO_RAD, | |
1419 | + max = this.MAX_LATITUDE, | |
1420 | + lat = Math.max(Math.min(max, latlng.lat), -max), | |
1421 | + x = latlng.lng * d, | |
1422 | + y = lat * d; | |
1423 | + | |
1424 | + y = Math.log(Math.tan((Math.PI / 4) + (y / 2))); | |
1425 | + | |
1426 | + return new L.Point(x, y); | |
1427 | + }, | |
1428 | + | |
1429 | + unproject: function (point) { // (Point, Boolean) -> LatLng | |
1430 | + var d = L.LatLng.RAD_TO_DEG, | |
1431 | + lng = point.x * d, | |
1432 | + lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d; | |
1433 | + | |
1434 | + return new L.LatLng(lat, lng); | |
1435 | + } | |
1436 | +}; | |
1437 | + | |
1438 | + | |
1439 | +/* | |
1440 | + * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. | |
1441 | + */ | |
1442 | + | |
1443 | +L.Projection.LonLat = { | |
1444 | + project: function (latlng) { | |
1445 | + return new L.Point(latlng.lng, latlng.lat); | |
1446 | + }, | |
1447 | + | |
1448 | + unproject: function (point) { | |
1449 | + return new L.LatLng(point.y, point.x); | |
1450 | + } | |
1451 | +}; | |
1452 | + | |
1453 | + | |
1454 | +/* | |
1455 | + * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet. | |
1456 | + */ | |
1457 | + | |
1458 | +L.CRS = { | |
1459 | + latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point | |
1460 | + var projectedPoint = this.projection.project(latlng), | |
1461 | + scale = this.scale(zoom); | |
1462 | + | |
1463 | + return this.transformation._transform(projectedPoint, scale); | |
1464 | + }, | |
1465 | + | |
1466 | + pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng | |
1467 | + var scale = this.scale(zoom), | |
1468 | + untransformedPoint = this.transformation.untransform(point, scale); | |
1469 | + | |
1470 | + return this.projection.unproject(untransformedPoint); | |
1471 | + }, | |
1472 | + | |
1473 | + project: function (latlng) { | |
1474 | + return this.projection.project(latlng); | |
1475 | + }, | |
1476 | + | |
1477 | + scale: function (zoom) { | |
1478 | + return 256 * Math.pow(2, zoom); | |
1479 | + }, | |
1480 | + | |
1481 | + getSize: function (zoom) { | |
1482 | + var s = this.scale(zoom); | |
1483 | + return L.point(s, s); | |
1484 | + } | |
1485 | +}; | |
1486 | + | |
1487 | + | |
1488 | +/* | |
1489 | + * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps. | |
1490 | + */ | |
1491 | + | |
1492 | +L.CRS.Simple = L.extend({}, L.CRS, { | |
1493 | + projection: L.Projection.LonLat, | |
1494 | + transformation: new L.Transformation(1, 0, -1, 0), | |
1495 | + | |
1496 | + scale: function (zoom) { | |
1497 | + return Math.pow(2, zoom); | |
1498 | + } | |
1499 | +}); | |
1500 | + | |
1501 | + | |
1502 | +/* | |
1503 | + * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping | |
1504 | + * and is used by Leaflet by default. | |
1505 | + */ | |
1506 | + | |
1507 | +L.CRS.EPSG3857 = L.extend({}, L.CRS, { | |
1508 | + code: 'EPSG:3857', | |
1509 | + | |
1510 | + projection: L.Projection.SphericalMercator, | |
1511 | + transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5), | |
1512 | + | |
1513 | + project: function (latlng) { // (LatLng) -> Point | |
1514 | + var projectedPoint = this.projection.project(latlng), | |
1515 | + earthRadius = 6378137; | |
1516 | + return projectedPoint.multiplyBy(earthRadius); | |
1517 | + } | |
1518 | +}); | |
1519 | + | |
1520 | +L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { | |
1521 | + code: 'EPSG:900913' | |
1522 | +}); | |
1523 | + | |
1524 | + | |
1525 | +/* | |
1526 | + * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists. | |
1527 | + */ | |
1528 | + | |
1529 | +L.CRS.EPSG4326 = L.extend({}, L.CRS, { | |
1530 | + code: 'EPSG:4326', | |
1531 | + | |
1532 | + projection: L.Projection.LonLat, | |
1533 | + transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5) | |
1534 | +}); | |
1535 | + | |
1536 | + | |
1537 | +/* | |
1538 | + * L.Map is the central class of the API - it is used to create a map. | |
1539 | + */ | |
1540 | + | |
1541 | +L.Map = L.Class.extend({ | |
1542 | + | |
1543 | + includes: L.Mixin.Events, | |
1544 | + | |
1545 | + options: { | |
1546 | + crs: L.CRS.EPSG3857, | |
1547 | + | |
1548 | + /* | |
1549 | + center: LatLng, | |
1550 | + zoom: Number, | |
1551 | + layers: Array, | |
1552 | + */ | |
1553 | + | |
1554 | + fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, | |
1555 | + trackResize: true, | |
1556 | + markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d | |
1557 | + }, | |
1558 | + | |
1559 | + initialize: function (id, options) { // (HTMLElement or String, Object) | |
1560 | + options = L.setOptions(this, options); | |
1561 | + | |
1562 | + | |
1563 | + this._initContainer(id); | |
1564 | + this._initLayout(); | |
1565 | + | |
1566 | + // hack for https://github.com/Leaflet/Leaflet/issues/1980 | |
1567 | + this._onResize = L.bind(this._onResize, this); | |
1568 | + | |
1569 | + this._initEvents(); | |
1570 | + | |
1571 | + if (options.maxBounds) { | |
1572 | + this.setMaxBounds(options.maxBounds); | |
1573 | + } | |
1574 | + | |
1575 | + if (options.center && options.zoom !== undefined) { | |
1576 | + this.setView(L.latLng(options.center), options.zoom, {reset: true}); | |
1577 | + } | |
1578 | + | |
1579 | + this._handlers = []; | |
1580 | + | |
1581 | + this._layers = {}; | |
1582 | + this._zoomBoundLayers = {}; | |
1583 | + this._tileLayersNum = 0; | |
1584 | + | |
1585 | + this.callInitHooks(); | |
1586 | + | |
1587 | + this._addLayers(options.layers); | |
1588 | + }, | |
1589 | + | |
1590 | + | |
1591 | + // public methods that modify map state | |
1592 | + | |
1593 | + // replaced by animation-powered implementation in Map.PanAnimation.js | |
1594 | + setView: function (center, zoom) { | |
1595 | + zoom = zoom === undefined ? this.getZoom() : zoom; | |
1596 | + this._resetView(L.latLng(center), this._limitZoom(zoom)); | |
1597 | + return this; | |
1598 | + }, | |
1599 | + | |
1600 | + setZoom: function (zoom, options) { | |
1601 | + if (!this._loaded) { | |
1602 | + this._zoom = this._limitZoom(zoom); | |
1603 | + return this; | |
1604 | + } | |
1605 | + return this.setView(this.getCenter(), zoom, {zoom: options}); | |
1606 | + }, | |
1607 | + | |
1608 | + zoomIn: function (delta, options) { | |
1609 | + return this.setZoom(this._zoom + (delta || 1), options); | |
1610 | + }, | |
1611 | + | |
1612 | + zoomOut: function (delta, options) { | |
1613 | + return this.setZoom(this._zoom - (delta || 1), options); | |
1614 | + }, | |
1615 | + | |
1616 | + setZoomAround: function (latlng, zoom, options) { | |
1617 | + var scale = this.getZoomScale(zoom), | |
1618 | + viewHalf = this.getSize().divideBy(2), | |
1619 | + containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), | |
1620 | + | |
1621 | + centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), | |
1622 | + newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); | |
1623 | + | |
1624 | + return this.setView(newCenter, zoom, {zoom: options}); | |
1625 | + }, | |
1626 | + | |
1627 | + fitBounds: function (bounds, options) { | |
1628 | + | |
1629 | + options = options || {}; | |
1630 | + bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); | |
1631 | + | |
1632 | + var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), | |
1633 | + paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), | |
1634 | + | |
1635 | + zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)), | |
1636 | + paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), | |
1637 | + | |
1638 | + swPoint = this.project(bounds.getSouthWest(), zoom), | |
1639 | + nePoint = this.project(bounds.getNorthEast(), zoom), | |
1640 | + center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); | |
1641 | + | |
1642 | + zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom; | |
1643 | + | |
1644 | + return this.setView(center, zoom, options); | |
1645 | + }, | |
1646 | + | |
1647 | + fitWorld: function (options) { | |
1648 | + return this.fitBounds([[-90, -180], [90, 180]], options); | |
1649 | + }, | |
1650 | + | |
1651 | + panTo: function (center, options) { // (LatLng) | |
1652 | + return this.setView(center, this._zoom, {pan: options}); | |
1653 | + }, | |
1654 | + | |
1655 | + panBy: function (offset) { // (Point) | |
1656 | + // replaced with animated panBy in Map.PanAnimation.js | |
1657 | + this.fire('movestart'); | |
1658 | + | |
1659 | + this._rawPanBy(L.point(offset)); | |
1660 | + | |
1661 | + this.fire('move'); | |
1662 | + return this.fire('moveend'); | |
1663 | + }, | |
1664 | + | |
1665 | + setMaxBounds: function (bounds) { | |
1666 | + bounds = L.latLngBounds(bounds); | |
1667 | + | |
1668 | + this.options.maxBounds = bounds; | |
1669 | + | |
1670 | + if (!bounds) { | |
1671 | + return this.off('moveend', this._panInsideMaxBounds, this); | |
1672 | + } | |
1673 | + | |
1674 | + if (this._loaded) { | |
1675 | + this._panInsideMaxBounds(); | |
1676 | + } | |
1677 | + | |
1678 | + return this.on('moveend', this._panInsideMaxBounds, this); | |
1679 | + }, | |
1680 | + | |
1681 | + panInsideBounds: function (bounds, options) { | |
1682 | + var center = this.getCenter(), | |
1683 | + newCenter = this._limitCenter(center, this._zoom, bounds); | |
1684 | + | |
1685 | + if (center.equals(newCenter)) { return this; } | |
1686 | + | |
1687 | + return this.panTo(newCenter, options); | |
1688 | + }, | |
1689 | + | |
1690 | + addLayer: function (layer) { | |
1691 | + // TODO method is too big, refactor | |
1692 | + | |
1693 | + var id = L.stamp(layer); | |
1694 | + | |
1695 | + if (this._layers[id]) { return this; } | |
1696 | + | |
1697 | + this._layers[id] = layer; | |
1698 | + | |
1699 | + // TODO getMaxZoom, getMinZoom in ILayer (instead of options) | |
1700 | + if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { | |
1701 | + this._zoomBoundLayers[id] = layer; | |
1702 | + this._updateZoomLevels(); | |
1703 | + } | |
1704 | + | |
1705 | + // TODO looks ugly, refactor!!! | |
1706 | + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { | |
1707 | + this._tileLayersNum++; | |
1708 | + this._tileLayersToLoad++; | |
1709 | + layer.on('load', this._onTileLayerLoad, this); | |
1710 | + } | |
1711 | + | |
1712 | + if (this._loaded) { | |
1713 | + this._layerAdd(layer); | |
1714 | + } | |
1715 | + | |
1716 | + return this; | |
1717 | + }, | |
1718 | + | |
1719 | + removeLayer: function (layer) { | |
1720 | + var id = L.stamp(layer); | |
1721 | + | |
1722 | + if (!this._layers[id]) { return this; } | |
1723 | + | |
1724 | + if (this._loaded) { | |
1725 | + layer.onRemove(this); | |
1726 | + } | |
1727 | + | |
1728 | + delete this._layers[id]; | |
1729 | + | |
1730 | + if (this._loaded) { | |
1731 | + this.fire('layerremove', {layer: layer}); | |
1732 | + } | |
1733 | + | |
1734 | + if (this._zoomBoundLayers[id]) { | |
1735 | + delete this._zoomBoundLayers[id]; | |
1736 | + this._updateZoomLevels(); | |
1737 | + } | |
1738 | + | |
1739 | + // TODO looks ugly, refactor | |
1740 | + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { | |
1741 | + this._tileLayersNum--; | |
1742 | + this._tileLayersToLoad--; | |
1743 | + layer.off('load', this._onTileLayerLoad, this); | |
1744 | + } | |
1745 | + | |
1746 | + return this; | |
1747 | + }, | |
1748 | + | |
1749 | + hasLayer: function (layer) { | |
1750 | + if (!layer) { return false; } | |
1751 | + | |
1752 | + return (L.stamp(layer) in this._layers); | |
1753 | + }, | |
1754 | + | |
1755 | + eachLayer: function (method, context) { | |
1756 | + for (var i in this._layers) { | |
1757 | + method.call(context, this._layers[i]); | |
1758 | + } | |
1759 | + return this; | |
1760 | + }, | |
1761 | + | |
1762 | + invalidateSize: function (options) { | |
1763 | + if (!this._loaded) { return this; } | |
1764 | + | |
1765 | + options = L.extend({ | |
1766 | + animate: false, | |
1767 | + pan: true | |
1768 | + }, options === true ? {animate: true} : options); | |
1769 | + | |
1770 | + var oldSize = this.getSize(); | |
1771 | + this._sizeChanged = true; | |
1772 | + this._initialCenter = null; | |
1773 | + | |
1774 | + var newSize = this.getSize(), | |
1775 | + oldCenter = oldSize.divideBy(2).round(), | |
1776 | + newCenter = newSize.divideBy(2).round(), | |
1777 | + offset = oldCenter.subtract(newCenter); | |
1778 | + | |
1779 | + if (!offset.x && !offset.y) { return this; } | |
1780 | + | |
1781 | + if (options.animate && options.pan) { | |
1782 | + this.panBy(offset); | |
1783 | + | |
1784 | + } else { | |
1785 | + if (options.pan) { | |
1786 | + this._rawPanBy(offset); | |
1787 | + } | |
1788 | + | |
1789 | + this.fire('move'); | |
1790 | + | |
1791 | + if (options.debounceMoveend) { | |
1792 | + clearTimeout(this._sizeTimer); | |
1793 | + this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); | |
1794 | + } else { | |
1795 | + this.fire('moveend'); | |
1796 | + } | |
1797 | + } | |
1798 | + | |
1799 | + return this.fire('resize', { | |
1800 | + oldSize: oldSize, | |
1801 | + newSize: newSize | |
1802 | + }); | |
1803 | + }, | |
1804 | + | |
1805 | + // TODO handler.addTo | |
1806 | + addHandler: function (name, HandlerClass) { | |
1807 | + if (!HandlerClass) { return this; } | |
1808 | + | |
1809 | + var handler = this[name] = new HandlerClass(this); | |
1810 | + | |
1811 | + this._handlers.push(handler); | |
1812 | + | |
1813 | + if (this.options[name]) { | |
1814 | + handler.enable(); | |
1815 | + } | |
1816 | + | |
1817 | + return this; | |
1818 | + }, | |
1819 | + | |
1820 | + remove: function () { | |
1821 | + if (this._loaded) { | |
1822 | + this.fire('unload'); | |
1823 | + } | |
1824 | + | |
1825 | + this._initEvents('off'); | |
1826 | + | |
1827 | + try { | |
1828 | + // throws error in IE6-8 | |
1829 | + delete this._container._leaflet; | |
1830 | + } catch (e) { | |
1831 | + this._container._leaflet = undefined; | |
1832 | + } | |
1833 | + | |
1834 | + this._clearPanes(); | |
1835 | + if (this._clearControlPos) { | |
1836 | + this._clearControlPos(); | |
1837 | + } | |
1838 | + | |
1839 | + this._clearHandlers(); | |
1840 | + | |
1841 | + return this; | |
1842 | + }, | |
1843 | + | |
1844 | + | |
1845 | + // public methods for getting map state | |
1846 | + | |
1847 | + getCenter: function () { // (Boolean) -> LatLng | |
1848 | + this._checkIfLoaded(); | |
1849 | + | |
1850 | + if (this._initialCenter && !this._moved()) { | |
1851 | + return this._initialCenter; | |
1852 | + } | |
1853 | + return this.layerPointToLatLng(this._getCenterLayerPoint()); | |
1854 | + }, | |
1855 | + | |
1856 | + getZoom: function () { | |
1857 | + return this._zoom; | |
1858 | + }, | |
1859 | + | |
1860 | + getBounds: function () { | |
1861 | + var bounds = this.getPixelBounds(), | |
1862 | + sw = this.unproject(bounds.getBottomLeft()), | |
1863 | + ne = this.unproject(bounds.getTopRight()); | |
1864 | + | |
1865 | + return new L.LatLngBounds(sw, ne); | |
1866 | + }, | |
1867 | + | |
1868 | + getMinZoom: function () { | |
1869 | + return this.options.minZoom === undefined ? | |
1870 | + (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) : | |
1871 | + this.options.minZoom; | |
1872 | + }, | |
1873 | + | |
1874 | + getMaxZoom: function () { | |
1875 | + return this.options.maxZoom === undefined ? | |
1876 | + (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : | |
1877 | + this.options.maxZoom; | |
1878 | + }, | |
1879 | + | |
1880 | + getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number | |
1881 | + bounds = L.latLngBounds(bounds); | |
1882 | + | |
1883 | + var zoom = this.getMinZoom() - (inside ? 1 : 0), | |
1884 | + maxZoom = this.getMaxZoom(), | |
1885 | + size = this.getSize(), | |
1886 | + | |
1887 | + nw = bounds.getNorthWest(), | |
1888 | + se = bounds.getSouthEast(), | |
1889 | + | |
1890 | + zoomNotFound = true, | |
1891 | + boundsSize; | |
1892 | + | |
1893 | + padding = L.point(padding || [0, 0]); | |
1894 | + | |
1895 | + do { | |
1896 | + zoom++; | |
1897 | + boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding); | |
1898 | + zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y; | |
1899 | + | |
1900 | + } while (zoomNotFound && zoom <= maxZoom); | |
1901 | + | |
1902 | + if (zoomNotFound && inside) { | |
1903 | + return null; | |
1904 | + } | |
1905 | + | |
1906 | + return inside ? zoom : zoom - 1; | |
1907 | + }, | |
1908 | + | |
1909 | + getSize: function () { | |
1910 | + if (!this._size || this._sizeChanged) { | |
1911 | + this._size = new L.Point( | |
1912 | + this._container.clientWidth, | |
1913 | + this._container.clientHeight); | |
1914 | + | |
1915 | + this._sizeChanged = false; | |
1916 | + } | |
1917 | + return this._size.clone(); | |
1918 | + }, | |
1919 | + | |
1920 | + getPixelBounds: function () { | |
1921 | + var topLeftPoint = this._getTopLeftPoint(); | |
1922 | + return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); | |
1923 | + }, | |
1924 | + | |
1925 | + getPixelOrigin: function () { | |
1926 | + this._checkIfLoaded(); | |
1927 | + return this._initialTopLeftPoint; | |
1928 | + }, | |
1929 | + | |
1930 | + getPanes: function () { | |
1931 | + return this._panes; | |
1932 | + }, | |
1933 | + | |
1934 | + getContainer: function () { | |
1935 | + return this._container; | |
1936 | + }, | |
1937 | + | |
1938 | + | |
1939 | + // TODO replace with universal implementation after refactoring projections | |
1940 | + | |
1941 | + getZoomScale: function (toZoom) { | |
1942 | + var crs = this.options.crs; | |
1943 | + return crs.scale(toZoom) / crs.scale(this._zoom); | |
1944 | + }, | |
1945 | + | |
1946 | + getScaleZoom: function (scale) { | |
1947 | + return this._zoom + (Math.log(scale) / Math.LN2); | |
1948 | + }, | |
1949 | + | |
1950 | + | |
1951 | + // conversion methods | |
1952 | + | |
1953 | + project: function (latlng, zoom) { // (LatLng[, Number]) -> Point | |
1954 | + zoom = zoom === undefined ? this._zoom : zoom; | |
1955 | + return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); | |
1956 | + }, | |
1957 | + | |
1958 | + unproject: function (point, zoom) { // (Point[, Number]) -> LatLng | |
1959 | + zoom = zoom === undefined ? this._zoom : zoom; | |
1960 | + return this.options.crs.pointToLatLng(L.point(point), zoom); | |
1961 | + }, | |
1962 | + | |
1963 | + layerPointToLatLng: function (point) { // (Point) | |
1964 | + var projectedPoint = L.point(point).add(this.getPixelOrigin()); | |
1965 | + return this.unproject(projectedPoint); | |
1966 | + }, | |
1967 | + | |
1968 | + latLngToLayerPoint: function (latlng) { // (LatLng) | |
1969 | + var projectedPoint = this.project(L.latLng(latlng))._round(); | |
1970 | + return projectedPoint._subtract(this.getPixelOrigin()); | |
1971 | + }, | |
1972 | + | |
1973 | + containerPointToLayerPoint: function (point) { // (Point) | |
1974 | + return L.point(point).subtract(this._getMapPanePos()); | |
1975 | + }, | |
1976 | + | |
1977 | + layerPointToContainerPoint: function (point) { // (Point) | |
1978 | + return L.point(point).add(this._getMapPanePos()); | |
1979 | + }, | |
1980 | + | |
1981 | + containerPointToLatLng: function (point) { | |
1982 | + var layerPoint = this.containerPointToLayerPoint(L.point(point)); | |
1983 | + return this.layerPointToLatLng(layerPoint); | |
1984 | + }, | |
1985 | + | |
1986 | + latLngToContainerPoint: function (latlng) { | |
1987 | + return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); | |
1988 | + }, | |
1989 | + | |
1990 | + mouseEventToContainerPoint: function (e) { // (MouseEvent) | |
1991 | + return L.DomEvent.getMousePosition(e, this._container); | |
1992 | + }, | |
1993 | + | |
1994 | + mouseEventToLayerPoint: function (e) { // (MouseEvent) | |
1995 | + return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); | |
1996 | + }, | |
1997 | + | |
1998 | + mouseEventToLatLng: function (e) { // (MouseEvent) | |
1999 | + return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); | |
2000 | + }, | |
2001 | + | |
2002 | + | |
2003 | + // map initialization methods | |
2004 | + | |
2005 | + _initContainer: function (id) { | |
2006 | + var container = this._container = L.DomUtil.get(id); | |
2007 | + | |
2008 | + if (!container) { | |
2009 | + throw new Error('Map container not found.'); | |
2010 | + } else if (container._leaflet) { | |
2011 | + throw new Error('Map container is already initialized.'); | |
2012 | + } | |
2013 | + | |
2014 | + container._leaflet = true; | |
2015 | + }, | |
2016 | + | |
2017 | + _initLayout: function () { | |
2018 | + var container = this._container; | |
2019 | + | |
2020 | + L.DomUtil.addClass(container, 'leaflet-container' + | |
2021 | + (L.Browser.touch ? ' leaflet-touch' : '') + | |
2022 | + (L.Browser.retina ? ' leaflet-retina' : '') + | |
2023 | + (L.Browser.ielt9 ? ' leaflet-oldie' : '') + | |
2024 | + (this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); | |
2025 | + | |
2026 | + var position = L.DomUtil.getStyle(container, 'position'); | |
2027 | + | |
2028 | + if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { | |
2029 | + container.style.position = 'relative'; | |
2030 | + } | |
2031 | + | |
2032 | + this._initPanes(); | |
2033 | + | |
2034 | + if (this._initControlPos) { | |
2035 | + this._initControlPos(); | |
2036 | + } | |
2037 | + }, | |
2038 | + | |
2039 | + _initPanes: function () { | |
2040 | + var panes = this._panes = {}; | |
2041 | + | |
2042 | + this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); | |
2043 | + | |
2044 | + this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); | |
2045 | + panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); | |
2046 | + panes.shadowPane = this._createPane('leaflet-shadow-pane'); | |
2047 | + panes.overlayPane = this._createPane('leaflet-overlay-pane'); | |
2048 | + panes.markerPane = this._createPane('leaflet-marker-pane'); | |
2049 | + panes.popupPane = this._createPane('leaflet-popup-pane'); | |
2050 | + | |
2051 | + var zoomHide = ' leaflet-zoom-hide'; | |
2052 | + | |
2053 | + if (!this.options.markerZoomAnimation) { | |
2054 | + L.DomUtil.addClass(panes.markerPane, zoomHide); | |
2055 | + L.DomUtil.addClass(panes.shadowPane, zoomHide); | |
2056 | + L.DomUtil.addClass(panes.popupPane, zoomHide); | |
2057 | + } | |
2058 | + }, | |
2059 | + | |
2060 | + _createPane: function (className, container) { | |
2061 | + return L.DomUtil.create('div', className, container || this._panes.objectsPane); | |
2062 | + }, | |
2063 | + | |
2064 | + _clearPanes: function () { | |
2065 | + this._container.removeChild(this._mapPane); | |
2066 | + }, | |
2067 | + | |
2068 | + _addLayers: function (layers) { | |
2069 | + layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; | |
2070 | + | |
2071 | + for (var i = 0, len = layers.length; i < len; i++) { | |
2072 | + this.addLayer(layers[i]); | |
2073 | + } | |
2074 | + }, | |
2075 | + | |
2076 | + | |
2077 | + // private methods that modify map state | |
2078 | + | |
2079 | + _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { | |
2080 | + | |
2081 | + var zoomChanged = (this._zoom !== zoom); | |
2082 | + | |
2083 | + if (!afterZoomAnim) { | |
2084 | + this.fire('movestart'); | |
2085 | + | |
2086 | + if (zoomChanged) { | |
2087 | + this.fire('zoomstart'); | |
2088 | + } | |
2089 | + } | |
2090 | + | |
2091 | + this._zoom = zoom; | |
2092 | + this._initialCenter = center; | |
2093 | + | |
2094 | + this._initialTopLeftPoint = this._getNewTopLeftPoint(center); | |
2095 | + | |
2096 | + if (!preserveMapOffset) { | |
2097 | + L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); | |
2098 | + } else { | |
2099 | + this._initialTopLeftPoint._add(this._getMapPanePos()); | |
2100 | + } | |
2101 | + | |
2102 | + this._tileLayersToLoad = this._tileLayersNum; | |
2103 | + | |
2104 | + var loading = !this._loaded; | |
2105 | + this._loaded = true; | |
2106 | + | |
2107 | + this.fire('viewreset', {hard: !preserveMapOffset}); | |
2108 | + | |
2109 | + if (loading) { | |
2110 | + this.fire('load'); | |
2111 | + this.eachLayer(this._layerAdd, this); | |
2112 | + } | |
2113 | + | |
2114 | + this.fire('move'); | |
2115 | + | |
2116 | + if (zoomChanged || afterZoomAnim) { | |
2117 | + this.fire('zoomend'); | |
2118 | + } | |
2119 | + | |
2120 | + this.fire('moveend', {hard: !preserveMapOffset}); | |
2121 | + }, | |
2122 | + | |
2123 | + _rawPanBy: function (offset) { | |
2124 | + L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); | |
2125 | + }, | |
2126 | + | |
2127 | + _getZoomSpan: function () { | |
2128 | + return this.getMaxZoom() - this.getMinZoom(); | |
2129 | + }, | |
2130 | + | |
2131 | + _updateZoomLevels: function () { | |
2132 | + var i, | |
2133 | + minZoom = Infinity, | |
2134 | + maxZoom = -Infinity, | |
2135 | + oldZoomSpan = this._getZoomSpan(); | |
2136 | + | |
2137 | + for (i in this._zoomBoundLayers) { | |
2138 | + var layer = this._zoomBoundLayers[i]; | |
2139 | + if (!isNaN(layer.options.minZoom)) { | |
2140 | + minZoom = Math.min(minZoom, layer.options.minZoom); | |
2141 | + } | |
2142 | + if (!isNaN(layer.options.maxZoom)) { | |
2143 | + maxZoom = Math.max(maxZoom, layer.options.maxZoom); | |
2144 | + } | |
2145 | + } | |
2146 | + | |
2147 | + if (i === undefined) { // we have no tilelayers | |
2148 | + this._layersMaxZoom = this._layersMinZoom = undefined; | |
2149 | + } else { | |
2150 | + this._layersMaxZoom = maxZoom; | |
2151 | + this._layersMinZoom = minZoom; | |
2152 | + } | |
2153 | + | |
2154 | + if (oldZoomSpan !== this._getZoomSpan()) { | |
2155 | + this.fire('zoomlevelschange'); | |
2156 | + } | |
2157 | + }, | |
2158 | + | |
2159 | + _panInsideMaxBounds: function () { | |
2160 | + this.panInsideBounds(this.options.maxBounds); | |
2161 | + }, | |
2162 | + | |
2163 | + _checkIfLoaded: function () { | |
2164 | + if (!this._loaded) { | |
2165 | + throw new Error('Set map center and zoom first.'); | |
2166 | + } | |
2167 | + }, | |
2168 | + | |
2169 | + // map events | |
2170 | + | |
2171 | + _initEvents: function (onOff) { | |
2172 | + if (!L.DomEvent) { return; } | |
2173 | + | |
2174 | + onOff = onOff || 'on'; | |
2175 | + | |
2176 | + L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this); | |
2177 | + | |
2178 | + var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', | |
2179 | + 'mouseleave', 'mousemove', 'contextmenu'], | |
2180 | + i, len; | |
2181 | + | |
2182 | + for (i = 0, len = events.length; i < len; i++) { | |
2183 | + L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this); | |
2184 | + } | |
2185 | + | |
2186 | + if (this.options.trackResize) { | |
2187 | + L.DomEvent[onOff](window, 'resize', this._onResize, this); | |
2188 | + } | |
2189 | + }, | |
2190 | + | |
2191 | + _onResize: function () { | |
2192 | + L.Util.cancelAnimFrame(this._resizeRequest); | |
2193 | + this._resizeRequest = L.Util.requestAnimFrame( | |
2194 | + function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container); | |
2195 | + }, | |
2196 | + | |
2197 | + _onMouseClick: function (e) { | |
2198 | + if (!this._loaded || (!e._simulated && | |
2199 | + ((this.dragging && this.dragging.moved()) || | |
2200 | + (this.boxZoom && this.boxZoom.moved()))) || | |
2201 | + L.DomEvent._skipped(e)) { return; } | |
2202 | + | |
2203 | + this.fire('preclick'); | |
2204 | + this._fireMouseEvent(e); | |
2205 | + }, | |
2206 | + | |
2207 | + _fireMouseEvent: function (e) { | |
2208 | + if (!this._loaded || L.DomEvent._skipped(e)) { return; } | |
2209 | + | |
2210 | + var type = e.type; | |
2211 | + | |
2212 | + type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type)); | |
2213 | + | |
2214 | + if (!this.hasEventListeners(type)) { return; } | |
2215 | + | |
2216 | + if (type === 'contextmenu') { | |
2217 | + L.DomEvent.preventDefault(e); | |
2218 | + } | |
2219 | + | |
2220 | + var containerPoint = this.mouseEventToContainerPoint(e), | |
2221 | + layerPoint = this.containerPointToLayerPoint(containerPoint), | |
2222 | + latlng = this.layerPointToLatLng(layerPoint); | |
2223 | + | |
2224 | + this.fire(type, { | |
2225 | + latlng: latlng, | |
2226 | + layerPoint: layerPoint, | |
2227 | + containerPoint: containerPoint, | |
2228 | + originalEvent: e | |
2229 | + }); | |
2230 | + }, | |
2231 | + | |
2232 | + _onTileLayerLoad: function () { | |
2233 | + this._tileLayersToLoad--; | |
2234 | + if (this._tileLayersNum && !this._tileLayersToLoad) { | |
2235 | + this.fire('tilelayersload'); | |
2236 | + } | |
2237 | + }, | |
2238 | + | |
2239 | + _clearHandlers: function () { | |
2240 | + for (var i = 0, len = this._handlers.length; i < len; i++) { | |
2241 | + this._handlers[i].disable(); | |
2242 | + } | |
2243 | + }, | |
2244 | + | |
2245 | + whenReady: function (callback, context) { | |
2246 | + if (this._loaded) { | |
2247 | + callback.call(context || this, this); | |
2248 | + } else { | |
2249 | + this.on('load', callback, context); | |
2250 | + } | |
2251 | + return this; | |
2252 | + }, | |
2253 | + | |
2254 | + _layerAdd: function (layer) { | |
2255 | + layer.onAdd(this); | |
2256 | + this.fire('layeradd', {layer: layer}); | |
2257 | + }, | |
2258 | + | |
2259 | + | |
2260 | + // private methods for getting map state | |
2261 | + | |
2262 | + _getMapPanePos: function () { | |
2263 | + return L.DomUtil.getPosition(this._mapPane); | |
2264 | + }, | |
2265 | + | |
2266 | + _moved: function () { | |
2267 | + var pos = this._getMapPanePos(); | |
2268 | + return pos && !pos.equals([0, 0]); | |
2269 | + }, | |
2270 | + | |
2271 | + _getTopLeftPoint: function () { | |
2272 | + return this.getPixelOrigin().subtract(this._getMapPanePos()); | |
2273 | + }, | |
2274 | + | |
2275 | + _getNewTopLeftPoint: function (center, zoom) { | |
2276 | + var viewHalf = this.getSize()._divideBy(2); | |
2277 | + // TODO round on display, not calculation to increase precision? | |
2278 | + return this.project(center, zoom)._subtract(viewHalf)._round(); | |
2279 | + }, | |
2280 | + | |
2281 | + _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) { | |
2282 | + var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos()); | |
2283 | + return this.project(latlng, newZoom)._subtract(topLeft); | |
2284 | + }, | |
2285 | + | |
2286 | + // layer point of the current center | |
2287 | + _getCenterLayerPoint: function () { | |
2288 | + return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); | |
2289 | + }, | |
2290 | + | |
2291 | + // offset of the specified place to the current center in pixels | |
2292 | + _getCenterOffset: function (latlng) { | |
2293 | + return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); | |
2294 | + }, | |
2295 | + | |
2296 | + // adjust center for view to get inside bounds | |
2297 | + _limitCenter: function (center, zoom, bounds) { | |
2298 | + | |
2299 | + if (!bounds) { return center; } | |
2300 | + | |
2301 | + var centerPoint = this.project(center, zoom), | |
2302 | + viewHalf = this.getSize().divideBy(2), | |
2303 | + viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), | |
2304 | + offset = this._getBoundsOffset(viewBounds, bounds, zoom); | |
2305 | + | |
2306 | + return this.unproject(centerPoint.add(offset), zoom); | |
2307 | + }, | |
2308 | + | |
2309 | + // adjust offset for view to get inside bounds | |
2310 | + _limitOffset: function (offset, bounds) { | |
2311 | + if (!bounds) { return offset; } | |
2312 | + | |
2313 | + var viewBounds = this.getPixelBounds(), | |
2314 | + newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); | |
2315 | + | |
2316 | + return offset.add(this._getBoundsOffset(newBounds, bounds)); | |
2317 | + }, | |
2318 | + | |
2319 | + // returns offset needed for pxBounds to get inside maxBounds at a specified zoom | |
2320 | + _getBoundsOffset: function (pxBounds, maxBounds, zoom) { | |
2321 | + var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min), | |
2322 | + seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max), | |
2323 | + | |
2324 | + dx = this._rebound(nwOffset.x, -seOffset.x), | |
2325 | + dy = this._rebound(nwOffset.y, -seOffset.y); | |
2326 | + | |
2327 | + return new L.Point(dx, dy); | |
2328 | + }, | |
2329 | + | |
2330 | + _rebound: function (left, right) { | |
2331 | + return left + right > 0 ? | |
2332 | + Math.round(left - right) / 2 : | |
2333 | + Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); | |
2334 | + }, | |
2335 | + | |
2336 | + _limitZoom: function (zoom) { | |
2337 | + var min = this.getMinZoom(), | |
2338 | + max = this.getMaxZoom(); | |
2339 | + | |
2340 | + return Math.max(min, Math.min(max, zoom)); | |
2341 | + } | |
2342 | +}); | |
2343 | + | |
2344 | +L.map = function (id, options) { | |
2345 | + return new L.Map(id, options); | |
2346 | +}; | |
2347 | + | |
2348 | + | |
2349 | +/* | |
2350 | + * Mercator projection that takes into account that the Earth is not a perfect sphere. | |
2351 | + * Less popular than spherical mercator; used by projections like EPSG:3395. | |
2352 | + */ | |
2353 | + | |
2354 | +L.Projection.Mercator = { | |
2355 | + MAX_LATITUDE: 85.0840591556, | |
2356 | + | |
2357 | + R_MINOR: 6356752.314245179, | |
2358 | + R_MAJOR: 6378137, | |
2359 | + | |
2360 | + project: function (latlng) { // (LatLng) -> Point | |
2361 | + var d = L.LatLng.DEG_TO_RAD, | |
2362 | + max = this.MAX_LATITUDE, | |
2363 | + lat = Math.max(Math.min(max, latlng.lat), -max), | |
2364 | + r = this.R_MAJOR, | |
2365 | + r2 = this.R_MINOR, | |
2366 | + x = latlng.lng * d * r, | |
2367 | + y = lat * d, | |
2368 | + tmp = r2 / r, | |
2369 | + eccent = Math.sqrt(1.0 - tmp * tmp), | |
2370 | + con = eccent * Math.sin(y); | |
2371 | + | |
2372 | + con = Math.pow((1 - con) / (1 + con), eccent * 0.5); | |
2373 | + | |
2374 | + var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con; | |
2375 | + y = -r * Math.log(ts); | |
2376 | + | |
2377 | + return new L.Point(x, y); | |
2378 | + }, | |
2379 | + | |
2380 | + unproject: function (point) { // (Point, Boolean) -> LatLng | |
2381 | + var d = L.LatLng.RAD_TO_DEG, | |
2382 | + r = this.R_MAJOR, | |
2383 | + r2 = this.R_MINOR, | |
2384 | + lng = point.x * d / r, | |
2385 | + tmp = r2 / r, | |
2386 | + eccent = Math.sqrt(1 - (tmp * tmp)), | |
2387 | + ts = Math.exp(- point.y / r), | |
2388 | + phi = (Math.PI / 2) - 2 * Math.atan(ts), | |
2389 | + numIter = 15, | |
2390 | + tol = 1e-7, | |
2391 | + i = numIter, | |
2392 | + dphi = 0.1, | |
2393 | + con; | |
2394 | + | |
2395 | + while ((Math.abs(dphi) > tol) && (--i > 0)) { | |
2396 | + con = eccent * Math.sin(phi); | |
2397 | + dphi = (Math.PI / 2) - 2 * Math.atan(ts * | |
2398 | + Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi; | |
2399 | + phi += dphi; | |
2400 | + } | |
2401 | + | |
2402 | + return new L.LatLng(phi * d, lng); | |
2403 | + } | |
2404 | +}; | |
2405 | + | |
2406 | + | |
2407 | + | |
2408 | +L.CRS.EPSG3395 = L.extend({}, L.CRS, { | |
2409 | + code: 'EPSG:3395', | |
2410 | + | |
2411 | + projection: L.Projection.Mercator, | |
2412 | + | |
2413 | + transformation: (function () { | |
2414 | + var m = L.Projection.Mercator, | |
2415 | + r = m.R_MAJOR, | |
2416 | + scale = 0.5 / (Math.PI * r); | |
2417 | + | |
2418 | + return new L.Transformation(scale, 0.5, -scale, 0.5); | |
2419 | + }()) | |
2420 | +}); | |
2421 | + | |
2422 | + | |
2423 | +/* | |
2424 | + * L.TileLayer is used for standard xyz-numbered tile layers. | |
2425 | + */ | |
2426 | + | |
2427 | +L.TileLayer = L.Class.extend({ | |
2428 | + includes: L.Mixin.Events, | |
2429 | + | |
2430 | + options: { | |
2431 | + minZoom: 0, | |
2432 | + maxZoom: 18, | |
2433 | + tileSize: 256, | |
2434 | + subdomains: 'abc', | |
2435 | + errorTileUrl: '', | |
2436 | + attribution: '', | |
2437 | + zoomOffset: 0, | |
2438 | + opacity: 1, | |
2439 | + /* | |
2440 | + maxNativeZoom: null, | |
2441 | + zIndex: null, | |
2442 | + tms: false, | |
2443 | + continuousWorld: false, | |
2444 | + noWrap: false, | |
2445 | + zoomReverse: false, | |
2446 | + detectRetina: false, | |
2447 | + reuseTiles: false, | |
2448 | + bounds: false, | |
2449 | + */ | |
2450 | + unloadInvisibleTiles: L.Browser.mobile, | |
2451 | + updateWhenIdle: L.Browser.mobile | |
2452 | + }, | |
2453 | + | |
2454 | + initialize: function (url, options) { | |
2455 | + options = L.setOptions(this, options); | |
2456 | + | |
2457 | + // detecting retina displays, adjusting tileSize and zoom levels | |
2458 | + if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { | |
2459 | + | |
2460 | + options.tileSize = Math.floor(options.tileSize / 2); | |
2461 | + options.zoomOffset++; | |
2462 | + | |
2463 | + if (options.minZoom > 0) { | |
2464 | + options.minZoom--; | |
2465 | + } | |
2466 | + this.options.maxZoom--; | |
2467 | + } | |
2468 | + | |
2469 | + if (options.bounds) { | |
2470 | + options.bounds = L.latLngBounds(options.bounds); | |
2471 | + } | |
2472 | + | |
2473 | + this._url = url; | |
2474 | + | |
2475 | + var subdomains = this.options.subdomains; | |
2476 | + | |
2477 | + if (typeof subdomains === 'string') { | |
2478 | + this.options.subdomains = subdomains.split(''); | |
2479 | + } | |
2480 | + }, | |
2481 | + | |
2482 | + onAdd: function (map) { | |
2483 | + this._map = map; | |
2484 | + this._animated = map._zoomAnimated; | |
2485 | + | |
2486 | + // create a container div for tiles | |
2487 | + this._initContainer(); | |
2488 | + | |
2489 | + // set up events | |
2490 | + map.on({ | |
2491 | + 'viewreset': this._reset, | |
2492 | + 'moveend': this._update | |
2493 | + }, this); | |
2494 | + | |
2495 | + if (this._animated) { | |
2496 | + map.on({ | |
2497 | + 'zoomanim': this._animateZoom, | |
2498 | + 'zoomend': this._endZoomAnim | |
2499 | + }, this); | |
2500 | + } | |
2501 | + | |
2502 | + if (!this.options.updateWhenIdle) { | |
2503 | + this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this); | |
2504 | + map.on('move', this._limitedUpdate, this); | |
2505 | + } | |
2506 | + | |
2507 | + this._reset(); | |
2508 | + this._update(); | |
2509 | + }, | |
2510 | + | |
2511 | + addTo: function (map) { | |
2512 | + map.addLayer(this); | |
2513 | + return this; | |
2514 | + }, | |
2515 | + | |
2516 | + onRemove: function (map) { | |
2517 | + this._container.parentNode.removeChild(this._container); | |
2518 | + | |
2519 | + map.off({ | |
2520 | + 'viewreset': this._reset, | |
2521 | + 'moveend': this._update | |
2522 | + }, this); | |
2523 | + | |
2524 | + if (this._animated) { | |
2525 | + map.off({ | |
2526 | + 'zoomanim': this._animateZoom, | |
2527 | + 'zoomend': this._endZoomAnim | |
2528 | + }, this); | |
2529 | + } | |
2530 | + | |
2531 | + if (!this.options.updateWhenIdle) { | |
2532 | + map.off('move', this._limitedUpdate, this); | |
2533 | + } | |
2534 | + | |
2535 | + this._container = null; | |
2536 | + this._map = null; | |
2537 | + }, | |
2538 | + | |
2539 | + bringToFront: function () { | |
2540 | + var pane = this._map._panes.tilePane; | |
2541 | + | |
2542 | + if (this._container) { | |
2543 | + pane.appendChild(this._container); | |
2544 | + this._setAutoZIndex(pane, Math.max); | |
2545 | + } | |
2546 | + | |
2547 | + return this; | |
2548 | + }, | |
2549 | + | |
2550 | + bringToBack: function () { | |
2551 | + var pane = this._map._panes.tilePane; | |
2552 | + | |
2553 | + if (this._container) { | |
2554 | + pane.insertBefore(this._container, pane.firstChild); | |
2555 | + this._setAutoZIndex(pane, Math.min); | |
2556 | + } | |
2557 | + | |
2558 | + return this; | |
2559 | + }, | |
2560 | + | |
2561 | + getAttribution: function () { | |
2562 | + return this.options.attribution; | |
2563 | + }, | |
2564 | + | |
2565 | + getContainer: function () { | |
2566 | + return this._container; | |
2567 | + }, | |
2568 | + | |
2569 | + setOpacity: function (opacity) { | |
2570 | + this.options.opacity = opacity; | |
2571 | + | |
2572 | + if (this._map) { | |
2573 | + this._updateOpacity(); | |
2574 | + } | |
2575 | + | |
2576 | + return this; | |
2577 | + }, | |
2578 | + | |
2579 | + setZIndex: function (zIndex) { | |
2580 | + this.options.zIndex = zIndex; | |
2581 | + this._updateZIndex(); | |
2582 | + | |
2583 | + return this; | |
2584 | + }, | |
2585 | + | |
2586 | + setUrl: function (url, noRedraw) { | |
2587 | + this._url = url; | |
2588 | + | |
2589 | + if (!noRedraw) { | |
2590 | + this.redraw(); | |
2591 | + } | |
2592 | + | |
2593 | + return this; | |
2594 | + }, | |
2595 | + | |
2596 | + redraw: function () { | |
2597 | + if (this._map) { | |
2598 | + this._reset({hard: true}); | |
2599 | + this._update(); | |
2600 | + } | |
2601 | + return this; | |
2602 | + }, | |
2603 | + | |
2604 | + _updateZIndex: function () { | |
2605 | + if (this._container && this.options.zIndex !== undefined) { | |
2606 | + this._container.style.zIndex = this.options.zIndex; | |
2607 | + } | |
2608 | + }, | |
2609 | + | |
2610 | + _setAutoZIndex: function (pane, compare) { | |
2611 | + | |
2612 | + var layers = pane.children, | |
2613 | + edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min | |
2614 | + zIndex, i, len; | |
2615 | + | |
2616 | + for (i = 0, len = layers.length; i < len; i++) { | |
2617 | + | |
2618 | + if (layers[i] !== this._container) { | |
2619 | + zIndex = parseInt(layers[i].style.zIndex, 10); | |
2620 | + | |
2621 | + if (!isNaN(zIndex)) { | |
2622 | + edgeZIndex = compare(edgeZIndex, zIndex); | |
2623 | + } | |
2624 | + } | |
2625 | + } | |
2626 | + | |
2627 | + this.options.zIndex = this._container.style.zIndex = | |
2628 | + (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1); | |
2629 | + }, | |
2630 | + | |
2631 | + _updateOpacity: function () { | |
2632 | + var i, | |
2633 | + tiles = this._tiles; | |
2634 | + | |
2635 | + if (L.Browser.ielt9) { | |
2636 | + for (i in tiles) { | |
2637 | + L.DomUtil.setOpacity(tiles[i], this.options.opacity); | |
2638 | + } | |
2639 | + } else { | |
2640 | + L.DomUtil.setOpacity(this._container, this.options.opacity); | |
2641 | + } | |
2642 | + }, | |
2643 | + | |
2644 | + _initContainer: function () { | |
2645 | + var tilePane = this._map._panes.tilePane; | |
2646 | + | |
2647 | + if (!this._container) { | |
2648 | + this._container = L.DomUtil.create('div', 'leaflet-layer'); | |
2649 | + | |
2650 | + this._updateZIndex(); | |
2651 | + | |
2652 | + if (this._animated) { | |
2653 | + var className = 'leaflet-tile-container'; | |
2654 | + | |
2655 | + this._bgBuffer = L.DomUtil.create('div', className, this._container); | |
2656 | + this._tileContainer = L.DomUtil.create('div', className, this._container); | |
2657 | + | |
2658 | + } else { | |
2659 | + this._tileContainer = this._container; | |
2660 | + } | |
2661 | + | |
2662 | + tilePane.appendChild(this._container); | |
2663 | + | |
2664 | + if (this.options.opacity < 1) { | |
2665 | + this._updateOpacity(); | |
2666 | + } | |
2667 | + } | |
2668 | + }, | |
2669 | + | |
2670 | + _reset: function (e) { | |
2671 | + for (var key in this._tiles) { | |
2672 | + this.fire('tileunload', {tile: this._tiles[key]}); | |
2673 | + } | |
2674 | + | |
2675 | + this._tiles = {}; | |
2676 | + this._tilesToLoad = 0; | |
2677 | + | |
2678 | + if (this.options.reuseTiles) { | |
2679 | + this._unusedTiles = []; | |
2680 | + } | |
2681 | + | |
2682 | + this._tileContainer.innerHTML = ''; | |
2683 | + | |
2684 | + if (this._animated && e && e.hard) { | |
2685 | + this._clearBgBuffer(); | |
2686 | + } | |
2687 | + | |
2688 | + this._initContainer(); | |
2689 | + }, | |
2690 | + | |
2691 | + _getTileSize: function () { | |
2692 | + var map = this._map, | |
2693 | + zoom = map.getZoom() + this.options.zoomOffset, | |
2694 | + zoomN = this.options.maxNativeZoom, | |
2695 | + tileSize = this.options.tileSize; | |
2696 | + | |
2697 | + if (zoomN && zoom > zoomN) { | |
2698 | + tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize); | |
2699 | + } | |
2700 | + | |
2701 | + return tileSize; | |
2702 | + }, | |
2703 | + | |
2704 | + _update: function () { | |
2705 | + | |
2706 | + if (!this._map) { return; } | |
2707 | + | |
2708 | + var map = this._map, | |
2709 | + bounds = map.getPixelBounds(), | |
2710 | + zoom = map.getZoom(), | |
2711 | + tileSize = this._getTileSize(); | |
2712 | + | |
2713 | + if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { | |
2714 | + return; | |
2715 | + } | |
2716 | + | |
2717 | + var tileBounds = L.bounds( | |
2718 | + bounds.min.divideBy(tileSize)._floor(), | |
2719 | + bounds.max.divideBy(tileSize)._floor()); | |
2720 | + | |
2721 | + this._addTilesFromCenterOut(tileBounds); | |
2722 | + | |
2723 | + if (this.options.unloadInvisibleTiles || this.options.reuseTiles) { | |
2724 | + this._removeOtherTiles(tileBounds); | |
2725 | + } | |
2726 | + }, | |
2727 | + | |
2728 | + _addTilesFromCenterOut: function (bounds) { | |
2729 | + var queue = [], | |
2730 | + center = bounds.getCenter(); | |
2731 | + | |
2732 | + var j, i, point; | |
2733 | + | |
2734 | + for (j = bounds.min.y; j <= bounds.max.y; j++) { | |
2735 | + for (i = bounds.min.x; i <= bounds.max.x; i++) { | |
2736 | + point = new L.Point(i, j); | |
2737 | + | |
2738 | + if (this._tileShouldBeLoaded(point)) { | |
2739 | + queue.push(point); | |
2740 | + } | |
2741 | + } | |
2742 | + } | |
2743 | + | |
2744 | + var tilesToLoad = queue.length; | |
2745 | + | |
2746 | + if (tilesToLoad === 0) { return; } | |
2747 | + | |
2748 | + // load tiles in order of their distance to center | |
2749 | + queue.sort(function (a, b) { | |
2750 | + return a.distanceTo(center) - b.distanceTo(center); | |
2751 | + }); | |
2752 | + | |
2753 | + var fragment = document.createDocumentFragment(); | |
2754 | + | |
2755 | + // if its the first batch of tiles to load | |
2756 | + if (!this._tilesToLoad) { | |
2757 | + this.fire('loading'); | |
2758 | + } | |
2759 | + | |
2760 | + this._tilesToLoad += tilesToLoad; | |
2761 | + | |
2762 | + for (i = 0; i < tilesToLoad; i++) { | |
2763 | + this._addTile(queue[i], fragment); | |
2764 | + } | |
2765 | + | |
2766 | + this._tileContainer.appendChild(fragment); | |
2767 | + }, | |
2768 | + | |
2769 | + _tileShouldBeLoaded: function (tilePoint) { | |
2770 | + if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) { | |
2771 | + return false; // already loaded | |
2772 | + } | |
2773 | + | |
2774 | + var options = this.options; | |
2775 | + | |
2776 | + if (!options.continuousWorld) { | |
2777 | + var limit = this._getWrapTileNum(); | |
2778 | + | |
2779 | + // don't load if exceeds world bounds | |
2780 | + if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) || | |
2781 | + tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; } | |
2782 | + } | |
2783 | + | |
2784 | + if (options.bounds) { | |
2785 | + var tileSize = options.tileSize, | |
2786 | + nwPoint = tilePoint.multiplyBy(tileSize), | |
2787 | + sePoint = nwPoint.add([tileSize, tileSize]), | |
2788 | + nw = this._map.unproject(nwPoint), | |
2789 | + se = this._map.unproject(sePoint); | |
2790 | + | |
2791 | + // TODO temporary hack, will be removed after refactoring projections | |
2792 | + // https://github.com/Leaflet/Leaflet/issues/1618 | |
2793 | + if (!options.continuousWorld && !options.noWrap) { | |
2794 | + nw = nw.wrap(); | |
2795 | + se = se.wrap(); | |
2796 | + } | |
2797 | + | |
2798 | + if (!options.bounds.intersects([nw, se])) { return false; } | |
2799 | + } | |
2800 | + | |
2801 | + return true; | |
2802 | + }, | |
2803 | + | |
2804 | + _removeOtherTiles: function (bounds) { | |
2805 | + var kArr, x, y, key; | |
2806 | + | |
2807 | + for (key in this._tiles) { | |
2808 | + kArr = key.split(':'); | |
2809 | + x = parseInt(kArr[0], 10); | |
2810 | + y = parseInt(kArr[1], 10); | |
2811 | + | |
2812 | + // remove tile if it's out of bounds | |
2813 | + if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) { | |
2814 | + this._removeTile(key); | |
2815 | + } | |
2816 | + } | |
2817 | + }, | |
2818 | + | |
2819 | + _removeTile: function (key) { | |
2820 | + var tile = this._tiles[key]; | |
2821 | + | |
2822 | + this.fire('tileunload', {tile: tile, url: tile.src}); | |
2823 | + | |
2824 | + if (this.options.reuseTiles) { | |
2825 | + L.DomUtil.removeClass(tile, 'leaflet-tile-loaded'); | |
2826 | + this._unusedTiles.push(tile); | |
2827 | + | |
2828 | + } else if (tile.parentNode === this._tileContainer) { | |
2829 | + this._tileContainer.removeChild(tile); | |
2830 | + } | |
2831 | + | |
2832 | + // for https://github.com/CloudMade/Leaflet/issues/137 | |
2833 | + if (!L.Browser.android) { | |
2834 | + tile.onload = null; | |
2835 | + tile.src = L.Util.emptyImageUrl; | |
2836 | + } | |
2837 | + | |
2838 | + delete this._tiles[key]; | |
2839 | + }, | |
2840 | + | |
2841 | + _addTile: function (tilePoint, container) { | |
2842 | + var tilePos = this._getTilePos(tilePoint); | |
2843 | + | |
2844 | + // get unused tile - or create a new tile | |
2845 | + var tile = this._getTile(); | |
2846 | + | |
2847 | + /* | |
2848 | + Chrome 20 layouts much faster with top/left (verify with timeline, frames) | |
2849 | + Android 4 browser has display issues with top/left and requires transform instead | |
2850 | + (other browsers don't currently care) - see debug/hacks/jitter.html for an example | |
2851 | + */ | |
2852 | + L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome); | |
2853 | + | |
2854 | + this._tiles[tilePoint.x + ':' + tilePoint.y] = tile; | |
2855 | + | |
2856 | + this._loadTile(tile, tilePoint); | |
2857 | + | |
2858 | + if (tile.parentNode !== this._tileContainer) { | |
2859 | + container.appendChild(tile); | |
2860 | + } | |
2861 | + }, | |
2862 | + | |
2863 | + _getZoomForUrl: function () { | |
2864 | + | |
2865 | + var options = this.options, | |
2866 | + zoom = this._map.getZoom(); | |
2867 | + | |
2868 | + if (options.zoomReverse) { | |
2869 | + zoom = options.maxZoom - zoom; | |
2870 | + } | |
2871 | + | |
2872 | + zoom += options.zoomOffset; | |
2873 | + | |
2874 | + return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom; | |
2875 | + }, | |
2876 | + | |
2877 | + _getTilePos: function (tilePoint) { | |
2878 | + var origin = this._map.getPixelOrigin(), | |
2879 | + tileSize = this._getTileSize(); | |
2880 | + | |
2881 | + return tilePoint.multiplyBy(tileSize).subtract(origin); | |
2882 | + }, | |
2883 | + | |
2884 | + // image-specific code (override to implement e.g. Canvas or SVG tile layer) | |
2885 | + | |
2886 | + getTileUrl: function (tilePoint) { | |
2887 | + return L.Util.template(this._url, L.extend({ | |
2888 | + s: this._getSubdomain(tilePoint), | |
2889 | + z: tilePoint.z, | |
2890 | + x: tilePoint.x, | |
2891 | + y: tilePoint.y | |
2892 | + }, this.options)); | |
2893 | + }, | |
2894 | + | |
2895 | + _getWrapTileNum: function () { | |
2896 | + var crs = this._map.options.crs, | |
2897 | + size = crs.getSize(this._map.getZoom()); | |
2898 | + return size.divideBy(this._getTileSize())._floor(); | |
2899 | + }, | |
2900 | + | |
2901 | + _adjustTilePoint: function (tilePoint) { | |
2902 | + | |
2903 | + var limit = this._getWrapTileNum(); | |
2904 | + | |
2905 | + // wrap tile coordinates | |
2906 | + if (!this.options.continuousWorld && !this.options.noWrap) { | |
2907 | + tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x; | |
2908 | + } | |
2909 | + | |
2910 | + if (this.options.tms) { | |
2911 | + tilePoint.y = limit.y - tilePoint.y - 1; | |
2912 | + } | |
2913 | + | |
2914 | + tilePoint.z = this._getZoomForUrl(); | |
2915 | + }, | |
2916 | + | |
2917 | + _getSubdomain: function (tilePoint) { | |
2918 | + var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; | |
2919 | + return this.options.subdomains[index]; | |
2920 | + }, | |
2921 | + | |
2922 | + _getTile: function () { | |
2923 | + if (this.options.reuseTiles && this._unusedTiles.length > 0) { | |
2924 | + var tile = this._unusedTiles.pop(); | |
2925 | + this._resetTile(tile); | |
2926 | + return tile; | |
2927 | + } | |
2928 | + return this._createTile(); | |
2929 | + }, | |
2930 | + | |
2931 | + // Override if data stored on a tile needs to be cleaned up before reuse | |
2932 | + _resetTile: function (/*tile*/) {}, | |
2933 | + | |
2934 | + _createTile: function () { | |
2935 | + var tile = L.DomUtil.create('img', 'leaflet-tile'); | |
2936 | + tile.style.width = tile.style.height = this._getTileSize() + 'px'; | |
2937 | + tile.galleryimg = 'no'; | |
2938 | + | |
2939 | + tile.onselectstart = tile.onmousemove = L.Util.falseFn; | |
2940 | + | |
2941 | + if (L.Browser.ielt9 && this.options.opacity !== undefined) { | |
2942 | + L.DomUtil.setOpacity(tile, this.options.opacity); | |
2943 | + } | |
2944 | + // without this hack, tiles disappear after zoom on Chrome for Android | |
2945 | + // https://github.com/Leaflet/Leaflet/issues/2078 | |
2946 | + if (L.Browser.mobileWebkit3d) { | |
2947 | + tile.style.WebkitBackfaceVisibility = 'hidden'; | |
2948 | + } | |
2949 | + return tile; | |
2950 | + }, | |
2951 | + | |
2952 | + _loadTile: function (tile, tilePoint) { | |
2953 | + tile._layer = this; | |
2954 | + tile.onload = this._tileOnLoad; | |
2955 | + tile.onerror = this._tileOnError; | |
2956 | + | |
2957 | + this._adjustTilePoint(tilePoint); | |
2958 | + tile.src = this.getTileUrl(tilePoint); | |
2959 | + | |
2960 | + this.fire('tileloadstart', { | |
2961 | + tile: tile, | |
2962 | + url: tile.src | |
2963 | + }); | |
2964 | + }, | |
2965 | + | |
2966 | + _tileLoaded: function () { | |
2967 | + this._tilesToLoad--; | |
2968 | + | |
2969 | + if (this._animated) { | |
2970 | + L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated'); | |
2971 | + } | |
2972 | + | |
2973 | + if (!this._tilesToLoad) { | |
2974 | + this.fire('load'); | |
2975 | + | |
2976 | + if (this._animated) { | |
2977 | + // clear scaled tiles after all new tiles are loaded (for performance) | |
2978 | + clearTimeout(this._clearBgBufferTimer); | |
2979 | + this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500); | |
2980 | + } | |
2981 | + } | |
2982 | + }, | |
2983 | + | |
2984 | + _tileOnLoad: function () { | |
2985 | + var layer = this._layer; | |
2986 | + | |
2987 | + //Only if we are loading an actual image | |
2988 | + if (this.src !== L.Util.emptyImageUrl) { | |
2989 | + L.DomUtil.addClass(this, 'leaflet-tile-loaded'); | |
2990 | + | |
2991 | + layer.fire('tileload', { | |
2992 | + tile: this, | |
2993 | + url: this.src | |
2994 | + }); | |
2995 | + } | |
2996 | + | |
2997 | + layer._tileLoaded(); | |
2998 | + }, | |
2999 | + | |
3000 | + _tileOnError: function () { | |
3001 | + var layer = this._layer; | |
3002 | + | |
3003 | + layer.fire('tileerror', { | |
3004 | + tile: this, | |
3005 | + url: this.src | |
3006 | + }); | |
3007 | + | |
3008 | + var newUrl = layer.options.errorTileUrl; | |
3009 | + if (newUrl) { | |
3010 | + this.src = newUrl; | |
3011 | + } | |
3012 | + | |
3013 | + layer._tileLoaded(); | |
3014 | + } | |
3015 | +}); | |
3016 | + | |
3017 | +L.tileLayer = function (url, options) { | |
3018 | + return new L.TileLayer(url, options); | |
3019 | +}; | |
3020 | + | |
3021 | + | |
3022 | +/* | |
3023 | + * L.TileLayer.WMS is used for putting WMS tile layers on the map. | |
3024 | + */ | |
3025 | + | |
3026 | +L.TileLayer.WMS = L.TileLayer.extend({ | |
3027 | + | |
3028 | + defaultWmsParams: { | |
3029 | + service: 'WMS', | |
3030 | + request: 'GetMap', | |
3031 | + version: '1.1.1', | |
3032 | + layers: '', | |
3033 | + styles: '', | |
3034 | + format: 'image/jpeg', | |
3035 | + transparent: false | |
3036 | + }, | |
3037 | + | |
3038 | + initialize: function (url, options) { // (String, Object) | |
3039 | + | |
3040 | + this._url = url; | |
3041 | + | |
3042 | + var wmsParams = L.extend({}, this.defaultWmsParams), | |
3043 | + tileSize = options.tileSize || this.options.tileSize; | |
3044 | + | |
3045 | + if (options.detectRetina && L.Browser.retina) { | |
3046 | + wmsParams.width = wmsParams.height = tileSize * 2; | |
3047 | + } else { | |
3048 | + wmsParams.width = wmsParams.height = tileSize; | |
3049 | + } | |
3050 | + | |
3051 | + for (var i in options) { | |
3052 | + // all keys that are not TileLayer options go to WMS params | |
3053 | + if (!this.options.hasOwnProperty(i) && i !== 'crs') { | |
3054 | + wmsParams[i] = options[i]; | |
3055 | + } | |
3056 | + } | |
3057 | + | |
3058 | + this.wmsParams = wmsParams; | |
3059 | + | |
3060 | + L.setOptions(this, options); | |
3061 | + }, | |
3062 | + | |
3063 | + onAdd: function (map) { | |
3064 | + | |
3065 | + this._crs = this.options.crs || map.options.crs; | |
3066 | + | |
3067 | + this._wmsVersion = parseFloat(this.wmsParams.version); | |
3068 | + | |
3069 | + var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; | |
3070 | + this.wmsParams[projectionKey] = this._crs.code; | |
3071 | + | |
3072 | + L.TileLayer.prototype.onAdd.call(this, map); | |
3073 | + }, | |
3074 | + | |
3075 | + getTileUrl: function (tilePoint) { // (Point, Number) -> String | |
3076 | + | |
3077 | + var map = this._map, | |
3078 | + tileSize = this.options.tileSize, | |
3079 | + | |
3080 | + nwPoint = tilePoint.multiplyBy(tileSize), | |
3081 | + sePoint = nwPoint.add([tileSize, tileSize]), | |
3082 | + | |
3083 | + nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)), | |
3084 | + se = this._crs.project(map.unproject(sePoint, tilePoint.z)), | |
3085 | + bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? | |
3086 | + [se.y, nw.x, nw.y, se.x].join(',') : | |
3087 | + [nw.x, se.y, se.x, nw.y].join(','), | |
3088 | + | |
3089 | + url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)}); | |
3090 | + | |
3091 | + return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox; | |
3092 | + }, | |
3093 | + | |
3094 | + setParams: function (params, noRedraw) { | |
3095 | + | |
3096 | + L.extend(this.wmsParams, params); | |
3097 | + | |
3098 | + if (!noRedraw) { | |
3099 | + this.redraw(); | |
3100 | + } | |
3101 | + | |
3102 | + return this; | |
3103 | + } | |
3104 | +}); | |
3105 | + | |
3106 | +L.tileLayer.wms = function (url, options) { | |
3107 | + return new L.TileLayer.WMS(url, options); | |
3108 | +}; | |
3109 | + | |
3110 | + | |
3111 | +/* | |
3112 | + * L.TileLayer.Canvas is a class that you can use as a base for creating | |
3113 | + * dynamically drawn Canvas-based tile layers. | |
3114 | + */ | |
3115 | + | |
3116 | +L.TileLayer.Canvas = L.TileLayer.extend({ | |
3117 | + options: { | |
3118 | + async: false | |
3119 | + }, | |
3120 | + | |
3121 | + initialize: function (options) { | |
3122 | + L.setOptions(this, options); | |
3123 | + }, | |
3124 | + | |
3125 | + redraw: function () { | |
3126 | + if (this._map) { | |
3127 | + this._reset({hard: true}); | |
3128 | + this._update(); | |
3129 | + } | |
3130 | + | |
3131 | + for (var i in this._tiles) { | |
3132 | + this._redrawTile(this._tiles[i]); | |
3133 | + } | |
3134 | + return this; | |
3135 | + }, | |
3136 | + | |
3137 | + _redrawTile: function (tile) { | |
3138 | + this.drawTile(tile, tile._tilePoint, this._map._zoom); | |
3139 | + }, | |
3140 | + | |
3141 | + _createTile: function () { | |
3142 | + var tile = L.DomUtil.create('canvas', 'leaflet-tile'); | |
3143 | + tile.width = tile.height = this.options.tileSize; | |
3144 | + tile.onselectstart = tile.onmousemove = L.Util.falseFn; | |
3145 | + return tile; | |
3146 | + }, | |
3147 | + | |
3148 | + _loadTile: function (tile, tilePoint) { | |
3149 | + tile._layer = this; | |
3150 | + tile._tilePoint = tilePoint; | |
3151 | + | |
3152 | + this._redrawTile(tile); | |
3153 | + | |
3154 | + if (!this.options.async) { | |
3155 | + this.tileDrawn(tile); | |
3156 | + } | |
3157 | + }, | |
3158 | + | |
3159 | + drawTile: function (/*tile, tilePoint*/) { | |
3160 | + // override with rendering code | |
3161 | + }, | |
3162 | + | |
3163 | + tileDrawn: function (tile) { | |
3164 | + this._tileOnLoad.call(tile); | |
3165 | + } | |
3166 | +}); | |
3167 | + | |
3168 | + | |
3169 | +L.tileLayer.canvas = function (options) { | |
3170 | + return new L.TileLayer.Canvas(options); | |
3171 | +}; | |
3172 | + | |
3173 | + | |
3174 | +/* | |
3175 | + * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). | |
3176 | + */ | |
3177 | + | |
3178 | +L.ImageOverlay = L.Class.extend({ | |
3179 | + includes: L.Mixin.Events, | |
3180 | + | |
3181 | + options: { | |
3182 | + opacity: 1 | |
3183 | + }, | |
3184 | + | |
3185 | + initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) | |
3186 | + this._url = url; | |
3187 | + this._bounds = L.latLngBounds(bounds); | |
3188 | + | |
3189 | + L.setOptions(this, options); | |
3190 | + }, | |
3191 | + | |
3192 | + onAdd: function (map) { | |
3193 | + this._map = map; | |
3194 | + | |
3195 | + if (!this._image) { | |
3196 | + this._initImage(); | |
3197 | + } | |
3198 | + | |
3199 | + map._panes.overlayPane.appendChild(this._image); | |
3200 | + | |
3201 | + map.on('viewreset', this._reset, this); | |
3202 | + | |
3203 | + if (map.options.zoomAnimation && L.Browser.any3d) { | |
3204 | + map.on('zoomanim', this._animateZoom, this); | |
3205 | + } | |
3206 | + | |
3207 | + this._reset(); | |
3208 | + }, | |
3209 | + | |
3210 | + onRemove: function (map) { | |
3211 | + map.getPanes().overlayPane.removeChild(this._image); | |
3212 | + | |
3213 | + map.off('viewreset', this._reset, this); | |
3214 | + | |
3215 | + if (map.options.zoomAnimation) { | |
3216 | + map.off('zoomanim', this._animateZoom, this); | |
3217 | + } | |
3218 | + }, | |
3219 | + | |
3220 | + addTo: function (map) { | |
3221 | + map.addLayer(this); | |
3222 | + return this; | |
3223 | + }, | |
3224 | + | |
3225 | + setOpacity: function (opacity) { | |
3226 | + this.options.opacity = opacity; | |
3227 | + this._updateOpacity(); | |
3228 | + return this; | |
3229 | + }, | |
3230 | + | |
3231 | + // TODO remove bringToFront/bringToBack duplication from TileLayer/Path | |
3232 | + bringToFront: function () { | |
3233 | + if (this._image) { | |
3234 | + this._map._panes.overlayPane.appendChild(this._image); | |
3235 | + } | |
3236 | + return this; | |
3237 | + }, | |
3238 | + | |
3239 | + bringToBack: function () { | |
3240 | + var pane = this._map._panes.overlayPane; | |
3241 | + if (this._image) { | |
3242 | + pane.insertBefore(this._image, pane.firstChild); | |
3243 | + } | |
3244 | + return this; | |
3245 | + }, | |
3246 | + | |
3247 | + setUrl: function (url) { | |
3248 | + this._url = url; | |
3249 | + this._image.src = this._url; | |
3250 | + }, | |
3251 | + | |
3252 | + getAttribution: function () { | |
3253 | + return this.options.attribution; | |
3254 | + }, | |
3255 | + | |
3256 | + _initImage: function () { | |
3257 | + this._image = L.DomUtil.create('img', 'leaflet-image-layer'); | |
3258 | + | |
3259 | + if (this._map.options.zoomAnimation && L.Browser.any3d) { | |
3260 | + L.DomUtil.addClass(this._image, 'leaflet-zoom-animated'); | |
3261 | + } else { | |
3262 | + L.DomUtil.addClass(this._image, 'leaflet-zoom-hide'); | |
3263 | + } | |
3264 | + | |
3265 | + this._updateOpacity(); | |
3266 | + | |
3267 | + //TODO createImage util method to remove duplication | |
3268 | + L.extend(this._image, { | |
3269 | + galleryimg: 'no', | |
3270 | + onselectstart: L.Util.falseFn, | |
3271 | + onmousemove: L.Util.falseFn, | |
3272 | + onload: L.bind(this._onImageLoad, this), | |
3273 | + src: this._url | |
3274 | + }); | |
3275 | + }, | |
3276 | + | |
3277 | + _animateZoom: function (e) { | |
3278 | + var map = this._map, | |
3279 | + image = this._image, | |
3280 | + scale = map.getZoomScale(e.zoom), | |
3281 | + nw = this._bounds.getNorthWest(), | |
3282 | + se = this._bounds.getSouthEast(), | |
3283 | + | |
3284 | + topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center), | |
3285 | + size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft), | |
3286 | + origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale))); | |
3287 | + | |
3288 | + image.style[L.DomUtil.TRANSFORM] = | |
3289 | + L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; | |
3290 | + }, | |
3291 | + | |
3292 | + _reset: function () { | |
3293 | + var image = this._image, | |
3294 | + topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()), | |
3295 | + size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft); | |
3296 | + | |
3297 | + L.DomUtil.setPosition(image, topLeft); | |
3298 | + | |
3299 | + image.style.width = size.x + 'px'; | |
3300 | + image.style.height = size.y + 'px'; | |
3301 | + }, | |
3302 | + | |
3303 | + _onImageLoad: function () { | |
3304 | + this.fire('load'); | |
3305 | + }, | |
3306 | + | |
3307 | + _updateOpacity: function () { | |
3308 | + L.DomUtil.setOpacity(this._image, this.options.opacity); | |
3309 | + } | |
3310 | +}); | |
3311 | + | |
3312 | +L.imageOverlay = function (url, bounds, options) { | |
3313 | + return new L.ImageOverlay(url, bounds, options); | |
3314 | +}; | |
3315 | + | |
3316 | + | |
3317 | +/* | |
3318 | + * L.Icon is an image-based icon class that you can use with L.Marker for custom markers. | |
3319 | + */ | |
3320 | + | |
3321 | +L.Icon = L.Class.extend({ | |
3322 | + options: { | |
3323 | + /* | |
3324 | + iconUrl: (String) (required) | |
3325 | + iconRetinaUrl: (String) (optional, used for retina devices if detected) | |
3326 | + iconSize: (Point) (can be set through CSS) | |
3327 | + iconAnchor: (Point) (centered by default, can be set in CSS with negative margins) | |
3328 | + popupAnchor: (Point) (if not specified, popup opens in the anchor point) | |
3329 | + shadowUrl: (String) (no shadow by default) | |
3330 | + shadowRetinaUrl: (String) (optional, used for retina devices if detected) | |
3331 | + shadowSize: (Point) | |
3332 | + shadowAnchor: (Point) | |
3333 | + */ | |
3334 | + className: '' | |
3335 | + }, | |
3336 | + | |
3337 | + initialize: function (options) { | |
3338 | + L.setOptions(this, options); | |
3339 | + }, | |
3340 | + | |
3341 | + createIcon: function (oldIcon) { | |
3342 | + return this._createIcon('icon', oldIcon); | |
3343 | + }, | |
3344 | + | |
3345 | + createShadow: function (oldIcon) { | |
3346 | + return this._createIcon('shadow', oldIcon); | |
3347 | + }, | |
3348 | + | |
3349 | + _createIcon: function (name, oldIcon) { | |
3350 | + var src = this._getIconUrl(name); | |
3351 | + | |
3352 | + if (!src) { | |
3353 | + if (name === 'icon') { | |
3354 | + throw new Error('iconUrl not set in Icon options (see the docs).'); | |
3355 | + } | |
3356 | + return null; | |
3357 | + } | |
3358 | + | |
3359 | + var img; | |
3360 | + if (!oldIcon || oldIcon.tagName !== 'IMG') { | |
3361 | + img = this._createImg(src); | |
3362 | + } else { | |
3363 | + img = this._createImg(src, oldIcon); | |
3364 | + } | |
3365 | + this._setIconStyles(img, name); | |
3366 | + | |
3367 | + return img; | |
3368 | + }, | |
3369 | + | |
3370 | + _setIconStyles: function (img, name) { | |
3371 | + var options = this.options, | |
3372 | + size = L.point(options[name + 'Size']), | |
3373 | + anchor; | |
3374 | + | |
3375 | + if (name === 'shadow') { | |
3376 | + anchor = L.point(options.shadowAnchor || options.iconAnchor); | |
3377 | + } else { | |
3378 | + anchor = L.point(options.iconAnchor); | |
3379 | + } | |
3380 | + | |
3381 | + if (!anchor && size) { | |
3382 | + anchor = size.divideBy(2, true); | |
3383 | + } | |
3384 | + | |
3385 | + img.className = 'leaflet-marker-' + name + ' ' + options.className; | |
3386 | + | |
3387 | + if (anchor) { | |
3388 | + img.style.marginLeft = (-anchor.x) + 'px'; | |
3389 | + img.style.marginTop = (-anchor.y) + 'px'; | |
3390 | + } | |
3391 | + | |
3392 | + if (size) { | |
3393 | + img.style.width = size.x + 'px'; | |
3394 | + img.style.height = size.y + 'px'; | |
3395 | + } | |
3396 | + }, | |
3397 | + | |
3398 | + _createImg: function (src, el) { | |
3399 | + el = el || document.createElement('img'); | |
3400 | + el.src = src; | |
3401 | + return el; | |
3402 | + }, | |
3403 | + | |
3404 | + _getIconUrl: function (name) { | |
3405 | + if (L.Browser.retina && this.options[name + 'RetinaUrl']) { | |
3406 | + return this.options[name + 'RetinaUrl']; | |
3407 | + } | |
3408 | + return this.options[name + 'Url']; | |
3409 | + } | |
3410 | +}); | |
3411 | + | |
3412 | +L.icon = function (options) { | |
3413 | + return new L.Icon(options); | |
3414 | +}; | |
3415 | + | |
3416 | + | |
3417 | +/* | |
3418 | + * L.Icon.Default is the blue marker icon used by default in Leaflet. | |
3419 | + */ | |
3420 | + | |
3421 | +L.Icon.Default = L.Icon.extend({ | |
3422 | + | |
3423 | + options: { | |
3424 | + iconSize: [25, 41], | |
3425 | + iconAnchor: [12, 41], | |
3426 | + popupAnchor: [1, -34], | |
3427 | + | |
3428 | + shadowSize: [41, 41] | |
3429 | + }, | |
3430 | + | |
3431 | + _getIconUrl: function (name) { | |
3432 | + var key = name + 'Url'; | |
3433 | + | |
3434 | + if (this.options[key]) { | |
3435 | + return this.options[key]; | |
3436 | + } | |
3437 | + | |
3438 | + if (L.Browser.retina && name === 'icon') { | |
3439 | + name += '-2x'; | |
3440 | + } | |
3441 | + | |
3442 | + var path = L.Icon.Default.imagePath; | |
3443 | + | |
3444 | + if (!path) { | |
3445 | + throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.'); | |
3446 | + } | |
3447 | + | |
3448 | + return path + '/marker-' + name + '.png'; | |
3449 | + } | |
3450 | +}); | |
3451 | + | |
3452 | +L.Icon.Default.imagePath = (function () { | |
3453 | + var scripts = document.getElementsByTagName('script'), | |
3454 | + leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/; | |
3455 | + | |
3456 | + var i, len, src, matches, path; | |
3457 | + | |
3458 | + for (i = 0, len = scripts.length; i < len; i++) { | |
3459 | + src = scripts[i].src; | |
3460 | + matches = src.match(leafletRe); | |
3461 | + | |
3462 | + if (matches) { | |
3463 | + path = src.split(leafletRe)[0]; | |
3464 | + return (path ? path + '/' : '') + 'images'; | |
3465 | + } | |
3466 | + } | |
3467 | +}()); | |
3468 | + | |
3469 | + | |
3470 | +/* | |
3471 | + * L.Marker is used to display clickable/draggable icons on the map. | |
3472 | + */ | |
3473 | + | |
3474 | +L.Marker = L.Class.extend({ | |
3475 | + | |
3476 | + includes: L.Mixin.Events, | |
3477 | + | |
3478 | + options: { | |
3479 | + icon: new L.Icon.Default(), | |
3480 | + title: '', | |
3481 | + alt: '', | |
3482 | + clickable: true, | |
3483 | + draggable: false, | |
3484 | + keyboard: true, | |
3485 | + zIndexOffset: 0, | |
3486 | + opacity: 1, | |
3487 | + riseOnHover: false, | |
3488 | + riseOffset: 250 | |
3489 | + }, | |
3490 | + | |
3491 | + initialize: function (latlng, options) { | |
3492 | + L.setOptions(this, options); | |
3493 | + this._latlng = L.latLng(latlng); | |
3494 | + }, | |
3495 | + | |
3496 | + onAdd: function (map) { | |
3497 | + this._map = map; | |
3498 | + | |
3499 | + map.on('viewreset', this.update, this); | |
3500 | + | |
3501 | + this._initIcon(); | |
3502 | + this.update(); | |
3503 | + this.fire('add'); | |
3504 | + | |
3505 | + if (map.options.zoomAnimation && map.options.markerZoomAnimation) { | |
3506 | + map.on('zoomanim', this._animateZoom, this); | |
3507 | + } | |
3508 | + }, | |
3509 | + | |
3510 | + addTo: function (map) { | |
3511 | + map.addLayer(this); | |
3512 | + return this; | |
3513 | + }, | |
3514 | + | |
3515 | + onRemove: function (map) { | |
3516 | + if (this.dragging) { | |
3517 | + this.dragging.disable(); | |
3518 | + } | |
3519 | + | |
3520 | + this._removeIcon(); | |
3521 | + this._removeShadow(); | |
3522 | + | |
3523 | + this.fire('remove'); | |
3524 | + | |
3525 | + map.off({ | |
3526 | + 'viewreset': this.update, | |
3527 | + 'zoomanim': this._animateZoom | |
3528 | + }, this); | |
3529 | + | |
3530 | + this._map = null; | |
3531 | + }, | |
3532 | + | |
3533 | + getLatLng: function () { | |
3534 | + return this._latlng; | |
3535 | + }, | |
3536 | + | |
3537 | + setLatLng: function (latlng) { | |
3538 | + this._latlng = L.latLng(latlng); | |
3539 | + | |
3540 | + this.update(); | |
3541 | + | |
3542 | + return this.fire('move', { latlng: this._latlng }); | |
3543 | + }, | |
3544 | + | |
3545 | + setZIndexOffset: function (offset) { | |
3546 | + this.options.zIndexOffset = offset; | |
3547 | + this.update(); | |
3548 | + | |
3549 | + return this; | |
3550 | + }, | |
3551 | + | |
3552 | + setIcon: function (icon) { | |
3553 | + | |
3554 | + this.options.icon = icon; | |
3555 | + | |
3556 | + if (this._map) { | |
3557 | + this._initIcon(); | |
3558 | + this.update(); | |
3559 | + } | |
3560 | + | |
3561 | + if (this._popup) { | |
3562 | + this.bindPopup(this._popup); | |
3563 | + } | |
3564 | + | |
3565 | + return this; | |
3566 | + }, | |
3567 | + | |
3568 | + update: function () { | |
3569 | + if (this._icon) { | |
3570 | + var pos = this._map.latLngToLayerPoint(this._latlng).round(); | |
3571 | + this._setPos(pos); | |
3572 | + } | |
3573 | + | |
3574 | + return this; | |
3575 | + }, | |
3576 | + | |
3577 | + _initIcon: function () { | |
3578 | + var options = this.options, | |
3579 | + map = this._map, | |
3580 | + animation = (map.options.zoomAnimation && map.options.markerZoomAnimation), | |
3581 | + classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide'; | |
3582 | + | |
3583 | + var icon = options.icon.createIcon(this._icon), | |
3584 | + addIcon = false; | |
3585 | + | |
3586 | + // if we're not reusing the icon, remove the old one and init new one | |
3587 | + if (icon !== this._icon) { | |
3588 | + if (this._icon) { | |
3589 | + this._removeIcon(); | |
3590 | + } | |
3591 | + addIcon = true; | |
3592 | + | |
3593 | + if (options.title) { | |
3594 | + icon.title = options.title; | |
3595 | + } | |
3596 | + | |
3597 | + if (options.alt) { | |
3598 | + icon.alt = options.alt; | |
3599 | + } | |
3600 | + } | |
3601 | + | |
3602 | + L.DomUtil.addClass(icon, classToAdd); | |
3603 | + | |
3604 | + if (options.keyboard) { | |
3605 | + icon.tabIndex = '0'; | |
3606 | + } | |
3607 | + | |
3608 | + this._icon = icon; | |
3609 | + | |
3610 | + this._initInteraction(); | |
3611 | + | |
3612 | + if (options.riseOnHover) { | |
3613 | + L.DomEvent | |
3614 | + .on(icon, 'mouseover', this._bringToFront, this) | |
3615 | + .on(icon, 'mouseout', this._resetZIndex, this); | |
3616 | + } | |
3617 | + | |
3618 | + var newShadow = options.icon.createShadow(this._shadow), | |
3619 | + addShadow = false; | |
3620 | + | |
3621 | + if (newShadow !== this._shadow) { | |
3622 | + this._removeShadow(); | |
3623 | + addShadow = true; | |
3624 | + } | |
3625 | + | |
3626 | + if (newShadow) { | |
3627 | + L.DomUtil.addClass(newShadow, classToAdd); | |
3628 | + } | |
3629 | + this._shadow = newShadow; | |
3630 | + | |
3631 | + | |
3632 | + if (options.opacity < 1) { | |
3633 | + this._updateOpacity(); | |
3634 | + } | |
3635 | + | |
3636 | + | |
3637 | + var panes = this._map._panes; | |
3638 | + | |
3639 | + if (addIcon) { | |
3640 | + panes.markerPane.appendChild(this._icon); | |
3641 | + } | |
3642 | + | |
3643 | + if (newShadow && addShadow) { | |
3644 | + panes.shadowPane.appendChild(this._shadow); | |
3645 | + } | |
3646 | + }, | |
3647 | + | |
3648 | + _removeIcon: function () { | |
3649 | + if (this.options.riseOnHover) { | |
3650 | + L.DomEvent | |
3651 | + .off(this._icon, 'mouseover', this._bringToFront) | |
3652 | + .off(this._icon, 'mouseout', this._resetZIndex); | |
3653 | + } | |
3654 | + | |
3655 | + this._map._panes.markerPane.removeChild(this._icon); | |
3656 | + | |
3657 | + this._icon = null; | |
3658 | + }, | |
3659 | + | |
3660 | + _removeShadow: function () { | |
3661 | + if (this._shadow) { | |
3662 | + this._map._panes.shadowPane.removeChild(this._shadow); | |
3663 | + } | |
3664 | + this._shadow = null; | |
3665 | + }, | |
3666 | + | |
3667 | + _setPos: function (pos) { | |
3668 | + L.DomUtil.setPosition(this._icon, pos); | |
3669 | + | |
3670 | + if (this._shadow) { | |
3671 | + L.DomUtil.setPosition(this._shadow, pos); | |
3672 | + } | |
3673 | + | |
3674 | + this._zIndex = pos.y + this.options.zIndexOffset; | |
3675 | + | |
3676 | + this._resetZIndex(); | |
3677 | + }, | |
3678 | + | |
3679 | + _updateZIndex: function (offset) { | |
3680 | + this._icon.style.zIndex = this._zIndex + offset; | |
3681 | + }, | |
3682 | + | |
3683 | + _animateZoom: function (opt) { | |
3684 | + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); | |
3685 | + | |
3686 | + this._setPos(pos); | |
3687 | + }, | |
3688 | + | |
3689 | + _initInteraction: function () { | |
3690 | + | |
3691 | + if (!this.options.clickable) { return; } | |
3692 | + | |
3693 | + // TODO refactor into something shared with Map/Path/etc. to DRY it up | |
3694 | + | |
3695 | + var icon = this._icon, | |
3696 | + events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; | |
3697 | + | |
3698 | + L.DomUtil.addClass(icon, 'leaflet-clickable'); | |
3699 | + L.DomEvent.on(icon, 'click', this._onMouseClick, this); | |
3700 | + L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); | |
3701 | + | |
3702 | + for (var i = 0; i < events.length; i++) { | |
3703 | + L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); | |
3704 | + } | |
3705 | + | |
3706 | + if (L.Handler.MarkerDrag) { | |
3707 | + this.dragging = new L.Handler.MarkerDrag(this); | |
3708 | + | |
3709 | + if (this.options.draggable) { | |
3710 | + this.dragging.enable(); | |
3711 | + } | |
3712 | + } | |
3713 | + }, | |
3714 | + | |
3715 | + _onMouseClick: function (e) { | |
3716 | + var wasDragged = this.dragging && this.dragging.moved(); | |
3717 | + | |
3718 | + if (this.hasEventListeners(e.type) || wasDragged) { | |
3719 | + L.DomEvent.stopPropagation(e); | |
3720 | + } | |
3721 | + | |
3722 | + if (wasDragged) { return; } | |
3723 | + | |
3724 | + if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; } | |
3725 | + | |
3726 | + this.fire(e.type, { | |
3727 | + originalEvent: e, | |
3728 | + latlng: this._latlng | |
3729 | + }); | |
3730 | + }, | |
3731 | + | |
3732 | + _onKeyPress: function (e) { | |
3733 | + if (e.keyCode === 13) { | |
3734 | + this.fire('click', { | |
3735 | + originalEvent: e, | |
3736 | + latlng: this._latlng | |
3737 | + }); | |
3738 | + } | |
3739 | + }, | |
3740 | + | |
3741 | + _fireMouseEvent: function (e) { | |
3742 | + | |
3743 | + this.fire(e.type, { | |
3744 | + originalEvent: e, | |
3745 | + latlng: this._latlng | |
3746 | + }); | |
3747 | + | |
3748 | + // TODO proper custom event propagation | |
3749 | + // this line will always be called if marker is in a FeatureGroup | |
3750 | + if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { | |
3751 | + L.DomEvent.preventDefault(e); | |
3752 | + } | |
3753 | + if (e.type !== 'mousedown') { | |
3754 | + L.DomEvent.stopPropagation(e); | |
3755 | + } else { | |
3756 | + L.DomEvent.preventDefault(e); | |
3757 | + } | |
3758 | + }, | |
3759 | + | |
3760 | + setOpacity: function (opacity) { | |
3761 | + this.options.opacity = opacity; | |
3762 | + if (this._map) { | |
3763 | + this._updateOpacity(); | |
3764 | + } | |
3765 | + | |
3766 | + return this; | |
3767 | + }, | |
3768 | + | |
3769 | + _updateOpacity: function () { | |
3770 | + L.DomUtil.setOpacity(this._icon, this.options.opacity); | |
3771 | + if (this._shadow) { | |
3772 | + L.DomUtil.setOpacity(this._shadow, this.options.opacity); | |
3773 | + } | |
3774 | + }, | |
3775 | + | |
3776 | + _bringToFront: function () { | |
3777 | + this._updateZIndex(this.options.riseOffset); | |
3778 | + }, | |
3779 | + | |
3780 | + _resetZIndex: function () { | |
3781 | + this._updateZIndex(0); | |
3782 | + } | |
3783 | +}); | |
3784 | + | |
3785 | +L.marker = function (latlng, options) { | |
3786 | + return new L.Marker(latlng, options); | |
3787 | +}; | |
3788 | + | |
3789 | + | |
3790 | +/* | |
3791 | + * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon) | |
3792 | + * to use with L.Marker. | |
3793 | + */ | |
3794 | + | |
3795 | +L.DivIcon = L.Icon.extend({ | |
3796 | + options: { | |
3797 | + iconSize: [12, 12], // also can be set through CSS | |
3798 | + /* | |
3799 | + iconAnchor: (Point) | |
3800 | + popupAnchor: (Point) | |
3801 | + html: (String) | |
3802 | + bgPos: (Point) | |
3803 | + */ | |
3804 | + className: 'leaflet-div-icon', | |
3805 | + html: false | |
3806 | + }, | |
3807 | + | |
3808 | + createIcon: function (oldIcon) { | |
3809 | + var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), | |
3810 | + options = this.options; | |
3811 | + | |
3812 | + if (options.html !== false) { | |
3813 | + div.innerHTML = options.html; | |
3814 | + } else { | |
3815 | + div.innerHTML = ''; | |
3816 | + } | |
3817 | + | |
3818 | + if (options.bgPos) { | |
3819 | + div.style.backgroundPosition = | |
3820 | + (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px'; | |
3821 | + } | |
3822 | + | |
3823 | + this._setIconStyles(div, 'icon'); | |
3824 | + return div; | |
3825 | + }, | |
3826 | + | |
3827 | + createShadow: function () { | |
3828 | + return null; | |
3829 | + } | |
3830 | +}); | |
3831 | + | |
3832 | +L.divIcon = function (options) { | |
3833 | + return new L.DivIcon(options); | |
3834 | +}; | |
3835 | + | |
3836 | + | |
3837 | +/* | |
3838 | + * L.Popup is used for displaying popups on the map. | |
3839 | + */ | |
3840 | + | |
3841 | +L.Map.mergeOptions({ | |
3842 | + closePopupOnClick: true | |
3843 | +}); | |
3844 | + | |
3845 | +L.Popup = L.Class.extend({ | |
3846 | + includes: L.Mixin.Events, | |
3847 | + | |
3848 | + options: { | |
3849 | + minWidth: 50, | |
3850 | + maxWidth: 300, | |
3851 | + // maxHeight: null, | |
3852 | + autoPan: true, | |
3853 | + closeButton: true, | |
3854 | + offset: [0, 7], | |
3855 | + autoPanPadding: [5, 5], | |
3856 | + // autoPanPaddingTopLeft: null, | |
3857 | + // autoPanPaddingBottomRight: null, | |
3858 | + keepInView: false, | |
3859 | + className: '', | |
3860 | + zoomAnimation: true | |
3861 | + }, | |
3862 | + | |
3863 | + initialize: function (options, source) { | |
3864 | + L.setOptions(this, options); | |
3865 | + | |
3866 | + this._source = source; | |
3867 | + this._animated = L.Browser.any3d && this.options.zoomAnimation; | |
3868 | + this._isOpen = false; | |
3869 | + }, | |
3870 | + | |
3871 | + onAdd: function (map) { | |
3872 | + this._map = map; | |
3873 | + | |
3874 | + if (!this._container) { | |
3875 | + this._initLayout(); | |
3876 | + } | |
3877 | + | |
3878 | + var animFade = map.options.fadeAnimation; | |
3879 | + | |
3880 | + if (animFade) { | |
3881 | + L.DomUtil.setOpacity(this._container, 0); | |
3882 | + } | |
3883 | + map._panes.popupPane.appendChild(this._container); | |
3884 | + | |
3885 | + map.on(this._getEvents(), this); | |
3886 | + | |
3887 | + this.update(); | |
3888 | + | |
3889 | + if (animFade) { | |
3890 | + L.DomUtil.setOpacity(this._container, 1); | |
3891 | + } | |
3892 | + | |
3893 | + this.fire('open'); | |
3894 | + | |
3895 | + map.fire('popupopen', {popup: this}); | |
3896 | + | |
3897 | + if (this._source) { | |
3898 | + this._source.fire('popupopen', {popup: this}); | |
3899 | + } | |
3900 | + }, | |
3901 | + | |
3902 | + addTo: function (map) { | |
3903 | + map.addLayer(this); | |
3904 | + return this; | |
3905 | + }, | |
3906 | + | |
3907 | + openOn: function (map) { | |
3908 | + map.openPopup(this); | |
3909 | + return this; | |
3910 | + }, | |
3911 | + | |
3912 | + onRemove: function (map) { | |
3913 | + map._panes.popupPane.removeChild(this._container); | |
3914 | + | |
3915 | + L.Util.falseFn(this._container.offsetWidth); // force reflow | |
3916 | + | |
3917 | + map.off(this._getEvents(), this); | |
3918 | + | |
3919 | + if (map.options.fadeAnimation) { | |
3920 | + L.DomUtil.setOpacity(this._container, 0); | |
3921 | + } | |
3922 | + | |
3923 | + this._map = null; | |
3924 | + | |
3925 | + this.fire('close'); | |
3926 | + | |
3927 | + map.fire('popupclose', {popup: this}); | |
3928 | + | |
3929 | + if (this._source) { | |
3930 | + this._source.fire('popupclose', {popup: this}); | |
3931 | + } | |
3932 | + }, | |
3933 | + | |
3934 | + getLatLng: function () { | |
3935 | + return this._latlng; | |
3936 | + }, | |
3937 | + | |
3938 | + setLatLng: function (latlng) { | |
3939 | + this._latlng = L.latLng(latlng); | |
3940 | + if (this._map) { | |
3941 | + this._updatePosition(); | |
3942 | + this._adjustPan(); | |
3943 | + } | |
3944 | + return this; | |
3945 | + }, | |
3946 | + | |
3947 | + getContent: function () { | |
3948 | + return this._content; | |
3949 | + }, | |
3950 | + | |
3951 | + setContent: function (content) { | |
3952 | + this._content = content; | |
3953 | + this.update(); | |
3954 | + return this; | |
3955 | + }, | |
3956 | + | |
3957 | + update: function () { | |
3958 | + if (!this._map) { return; } | |
3959 | + | |
3960 | + this._container.style.visibility = 'hidden'; | |
3961 | + | |
3962 | + this._updateContent(); | |
3963 | + this._updateLayout(); | |
3964 | + this._updatePosition(); | |
3965 | + | |
3966 | + this._container.style.visibility = ''; | |
3967 | + | |
3968 | + this._adjustPan(); | |
3969 | + }, | |
3970 | + | |
3971 | + _getEvents: function () { | |
3972 | + var events = { | |
3973 | + viewreset: this._updatePosition | |
3974 | + }; | |
3975 | + | |
3976 | + if (this._animated) { | |
3977 | + events.zoomanim = this._zoomAnimation; | |
3978 | + } | |
3979 | + if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { | |
3980 | + events.preclick = this._close; | |
3981 | + } | |
3982 | + if (this.options.keepInView) { | |
3983 | + events.moveend = this._adjustPan; | |
3984 | + } | |
3985 | + | |
3986 | + return events; | |
3987 | + }, | |
3988 | + | |
3989 | + _close: function () { | |
3990 | + if (this._map) { | |
3991 | + this._map.closePopup(this); | |
3992 | + } | |
3993 | + }, | |
3994 | + | |
3995 | + _initLayout: function () { | |
3996 | + var prefix = 'leaflet-popup', | |
3997 | + containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' + | |
3998 | + (this._animated ? 'animated' : 'hide'), | |
3999 | + container = this._container = L.DomUtil.create('div', containerClass), | |
4000 | + closeButton; | |
4001 | + | |
4002 | + if (this.options.closeButton) { | |
4003 | + closeButton = this._closeButton = | |
4004 | + L.DomUtil.create('a', prefix + '-close-button', container); | |
4005 | + closeButton.href = '#close'; | |
4006 | + closeButton.innerHTML = '×'; | |
4007 | + L.DomEvent.disableClickPropagation(closeButton); | |
4008 | + | |
4009 | + L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); | |
4010 | + } | |
4011 | + | |
4012 | + var wrapper = this._wrapper = | |
4013 | + L.DomUtil.create('div', prefix + '-content-wrapper', container); | |
4014 | + L.DomEvent.disableClickPropagation(wrapper); | |
4015 | + | |
4016 | + this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); | |
4017 | + | |
4018 | + L.DomEvent.disableScrollPropagation(this._contentNode); | |
4019 | + L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); | |
4020 | + | |
4021 | + this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); | |
4022 | + this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); | |
4023 | + }, | |
4024 | + | |
4025 | + _updateContent: function () { | |
4026 | + if (!this._content) { return; } | |
4027 | + | |
4028 | + if (typeof this._content === 'string') { | |
4029 | + this._contentNode.innerHTML = this._content; | |
4030 | + } else { | |
4031 | + while (this._contentNode.hasChildNodes()) { | |
4032 | + this._contentNode.removeChild(this._contentNode.firstChild); | |
4033 | + } | |
4034 | + this._contentNode.appendChild(this._content); | |
4035 | + } | |
4036 | + this.fire('contentupdate'); | |
4037 | + }, | |
4038 | + | |
4039 | + _updateLayout: function () { | |
4040 | + var container = this._contentNode, | |
4041 | + style = container.style; | |
4042 | + | |
4043 | + style.width = ''; | |
4044 | + style.whiteSpace = 'nowrap'; | |
4045 | + | |
4046 | + var width = container.offsetWidth; | |
4047 | + width = Math.min(width, this.options.maxWidth); | |
4048 | + width = Math.max(width, this.options.minWidth); | |
4049 | + | |
4050 | + style.width = (width + 1) + 'px'; | |
4051 | + style.whiteSpace = ''; | |
4052 | + | |
4053 | + style.height = ''; | |
4054 | + | |
4055 | + var height = container.offsetHeight, | |
4056 | + maxHeight = this.options.maxHeight, | |
4057 | + scrolledClass = 'leaflet-popup-scrolled'; | |
4058 | + | |
4059 | + if (maxHeight && height > maxHeight) { | |
4060 | + style.height = maxHeight + 'px'; | |
4061 | + L.DomUtil.addClass(container, scrolledClass); | |
4062 | + } else { | |
4063 | + L.DomUtil.removeClass(container, scrolledClass); | |
4064 | + } | |
4065 | + | |
4066 | + this._containerWidth = this._container.offsetWidth; | |
4067 | + }, | |
4068 | + | |
4069 | + _updatePosition: function () { | |
4070 | + if (!this._map) { return; } | |
4071 | + | |
4072 | + var pos = this._map.latLngToLayerPoint(this._latlng), | |
4073 | + animated = this._animated, | |
4074 | + offset = L.point(this.options.offset); | |
4075 | + | |
4076 | + if (animated) { | |
4077 | + L.DomUtil.setPosition(this._container, pos); | |
4078 | + } | |
4079 | + | |
4080 | + this._containerBottom = -offset.y - (animated ? 0 : pos.y); | |
4081 | + this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x); | |
4082 | + | |
4083 | + // bottom position the popup in case the height of the popup changes (images loading etc) | |
4084 | + this._container.style.bottom = this._containerBottom + 'px'; | |
4085 | + this._container.style.left = this._containerLeft + 'px'; | |
4086 | + }, | |
4087 | + | |
4088 | + _zoomAnimation: function (opt) { | |
4089 | + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); | |
4090 | + | |
4091 | + L.DomUtil.setPosition(this._container, pos); | |
4092 | + }, | |
4093 | + | |
4094 | + _adjustPan: function () { | |
4095 | + if (!this.options.autoPan) { return; } | |
4096 | + | |
4097 | + var map = this._map, | |
4098 | + containerHeight = this._container.offsetHeight, | |
4099 | + containerWidth = this._containerWidth, | |
4100 | + | |
4101 | + layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); | |
4102 | + | |
4103 | + if (this._animated) { | |
4104 | + layerPos._add(L.DomUtil.getPosition(this._container)); | |
4105 | + } | |
4106 | + | |
4107 | + var containerPos = map.layerPointToContainerPoint(layerPos), | |
4108 | + padding = L.point(this.options.autoPanPadding), | |
4109 | + paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), | |
4110 | + paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), | |
4111 | + size = map.getSize(), | |
4112 | + dx = 0, | |
4113 | + dy = 0; | |
4114 | + | |
4115 | + if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right | |
4116 | + dx = containerPos.x + containerWidth - size.x + paddingBR.x; | |
4117 | + } | |
4118 | + if (containerPos.x - dx - paddingTL.x < 0) { // left | |
4119 | + dx = containerPos.x - paddingTL.x; | |
4120 | + } | |
4121 | + if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom | |
4122 | + dy = containerPos.y + containerHeight - size.y + paddingBR.y; | |
4123 | + } | |
4124 | + if (containerPos.y - dy - paddingTL.y < 0) { // top | |
4125 | + dy = containerPos.y - paddingTL.y; | |
4126 | + } | |
4127 | + | |
4128 | + if (dx || dy) { | |
4129 | + map | |
4130 | + .fire('autopanstart') | |
4131 | + .panBy([dx, dy]); | |
4132 | + } | |
4133 | + }, | |
4134 | + | |
4135 | + _onCloseButtonClick: function (e) { | |
4136 | + this._close(); | |
4137 | + L.DomEvent.stop(e); | |
4138 | + } | |
4139 | +}); | |
4140 | + | |
4141 | +L.popup = function (options, source) { | |
4142 | + return new L.Popup(options, source); | |
4143 | +}; | |
4144 | + | |
4145 | + | |
4146 | +L.Map.include({ | |
4147 | + openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object]) | |
4148 | + this.closePopup(); | |
4149 | + | |
4150 | + if (!(popup instanceof L.Popup)) { | |
4151 | + var content = popup; | |
4152 | + | |
4153 | + popup = new L.Popup(options) | |
4154 | + .setLatLng(latlng) | |
4155 | + .setContent(content); | |
4156 | + } | |
4157 | + popup._isOpen = true; | |
4158 | + | |
4159 | + this._popup = popup; | |
4160 | + return this.addLayer(popup); | |
4161 | + }, | |
4162 | + | |
4163 | + closePopup: function (popup) { | |
4164 | + if (!popup || popup === this._popup) { | |
4165 | + popup = this._popup; | |
4166 | + this._popup = null; | |
4167 | + } | |
4168 | + if (popup) { | |
4169 | + this.removeLayer(popup); | |
4170 | + popup._isOpen = false; | |
4171 | + } | |
4172 | + return this; | |
4173 | + } | |
4174 | +}); | |
4175 | + | |
4176 | + | |
4177 | +/* | |
4178 | + * Popup extension to L.Marker, adding popup-related methods. | |
4179 | + */ | |
4180 | + | |
4181 | +L.Marker.include({ | |
4182 | + openPopup: function () { | |
4183 | + if (this._popup && this._map && !this._map.hasLayer(this._popup)) { | |
4184 | + this._popup.setLatLng(this._latlng); | |
4185 | + this._map.openPopup(this._popup); | |
4186 | + } | |
4187 | + | |
4188 | + return this; | |
4189 | + }, | |
4190 | + | |
4191 | + closePopup: function () { | |
4192 | + if (this._popup) { | |
4193 | + this._popup._close(); | |
4194 | + } | |
4195 | + return this; | |
4196 | + }, | |
4197 | + | |
4198 | + togglePopup: function () { | |
4199 | + if (this._popup) { | |
4200 | + if (this._popup._isOpen) { | |
4201 | + this.closePopup(); | |
4202 | + } else { | |
4203 | + this.openPopup(); | |
4204 | + } | |
4205 | + } | |
4206 | + return this; | |
4207 | + }, | |
4208 | + | |
4209 | + bindPopup: function (content, options) { | |
4210 | + var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]); | |
4211 | + | |
4212 | + anchor = anchor.add(L.Popup.prototype.options.offset); | |
4213 | + | |
4214 | + if (options && options.offset) { | |
4215 | + anchor = anchor.add(options.offset); | |
4216 | + } | |
4217 | + | |
4218 | + options = L.extend({offset: anchor}, options); | |
4219 | + | |
4220 | + if (!this._popupHandlersAdded) { | |
4221 | + this | |
4222 | + .on('click', this.togglePopup, this) | |
4223 | + .on('remove', this.closePopup, this) | |
4224 | + .on('move', this._movePopup, this); | |
4225 | + this._popupHandlersAdded = true; | |
4226 | + } | |
4227 | + | |
4228 | + if (content instanceof L.Popup) { | |
4229 | + L.setOptions(content, options); | |
4230 | + this._popup = content; | |
4231 | + } else { | |
4232 | + this._popup = new L.Popup(options, this) | |
4233 | + .setContent(content); | |
4234 | + } | |
4235 | + | |
4236 | + return this; | |
4237 | + }, | |
4238 | + | |
4239 | + setPopupContent: function (content) { | |
4240 | + if (this._popup) { | |
4241 | + this._popup.setContent(content); | |
4242 | + } | |
4243 | + return this; | |
4244 | + }, | |
4245 | + | |
4246 | + unbindPopup: function () { | |
4247 | + if (this._popup) { | |
4248 | + this._popup = null; | |
4249 | + this | |
4250 | + .off('click', this.togglePopup, this) | |
4251 | + .off('remove', this.closePopup, this) | |
4252 | + .off('move', this._movePopup, this); | |
4253 | + this._popupHandlersAdded = false; | |
4254 | + } | |
4255 | + return this; | |
4256 | + }, | |
4257 | + | |
4258 | + getPopup: function () { | |
4259 | + return this._popup; | |
4260 | + }, | |
4261 | + | |
4262 | + _movePopup: function (e) { | |
4263 | + this._popup.setLatLng(e.latlng); | |
4264 | + } | |
4265 | +}); | |
4266 | + | |
4267 | + | |
4268 | +/* | |
4269 | + * L.LayerGroup is a class to combine several layers into one so that | |
4270 | + * you can manipulate the group (e.g. add/remove it) as one layer. | |
4271 | + */ | |
4272 | + | |
4273 | +L.LayerGroup = L.Class.extend({ | |
4274 | + initialize: function (layers) { | |
4275 | + this._layers = {}; | |
4276 | + | |
4277 | + var i, len; | |
4278 | + | |
4279 | + if (layers) { | |
4280 | + for (i = 0, len = layers.length; i < len; i++) { | |
4281 | + this.addLayer(layers[i]); | |
4282 | + } | |
4283 | + } | |
4284 | + }, | |
4285 | + | |
4286 | + addLayer: function (layer) { | |
4287 | + var id = this.getLayerId(layer); | |
4288 | + | |
4289 | + this._layers[id] = layer; | |
4290 | + | |
4291 | + if (this._map) { | |
4292 | + this._map.addLayer(layer); | |
4293 | + } | |
4294 | + | |
4295 | + return this; | |
4296 | + }, | |
4297 | + | |
4298 | + removeLayer: function (layer) { | |
4299 | + var id = layer in this._layers ? layer : this.getLayerId(layer); | |
4300 | + | |
4301 | + if (this._map && this._layers[id]) { | |
4302 | + this._map.removeLayer(this._layers[id]); | |
4303 | + } | |
4304 | + | |
4305 | + delete this._layers[id]; | |
4306 | + | |
4307 | + return this; | |
4308 | + }, | |
4309 | + | |
4310 | + hasLayer: function (layer) { | |
4311 | + if (!layer) { return false; } | |
4312 | + | |
4313 | + return (layer in this._layers || this.getLayerId(layer) in this._layers); | |
4314 | + }, | |
4315 | + | |
4316 | + clearLayers: function () { | |
4317 | + this.eachLayer(this.removeLayer, this); | |
4318 | + return this; | |
4319 | + }, | |
4320 | + | |
4321 | + invoke: function (methodName) { | |
4322 | + var args = Array.prototype.slice.call(arguments, 1), | |
4323 | + i, layer; | |
4324 | + | |
4325 | + for (i in this._layers) { | |
4326 | + layer = this._layers[i]; | |
4327 | + | |
4328 | + if (layer[methodName]) { | |
4329 | + layer[methodName].apply(layer, args); | |
4330 | + } | |
4331 | + } | |
4332 | + | |
4333 | + return this; | |
4334 | + }, | |
4335 | + | |
4336 | + onAdd: function (map) { | |
4337 | + this._map = map; | |
4338 | + this.eachLayer(map.addLayer, map); | |
4339 | + }, | |
4340 | + | |
4341 | + onRemove: function (map) { | |
4342 | + this.eachLayer(map.removeLayer, map); | |
4343 | + this._map = null; | |
4344 | + }, | |
4345 | + | |
4346 | + addTo: function (map) { | |
4347 | + map.addLayer(this); | |
4348 | + return this; | |
4349 | + }, | |
4350 | + | |
4351 | + eachLayer: function (method, context) { | |
4352 | + for (var i in this._layers) { | |
4353 | + method.call(context, this._layers[i]); | |
4354 | + } | |
4355 | + return this; | |
4356 | + }, | |
4357 | + | |
4358 | + getLayer: function (id) { | |
4359 | + return this._layers[id]; | |
4360 | + }, | |
4361 | + | |
4362 | + getLayers: function () { | |
4363 | + var layers = []; | |
4364 | + | |
4365 | + for (var i in this._layers) { | |
4366 | + layers.push(this._layers[i]); | |
4367 | + } | |
4368 | + return layers; | |
4369 | + }, | |
4370 | + | |
4371 | + setZIndex: function (zIndex) { | |
4372 | + return this.invoke('setZIndex', zIndex); | |
4373 | + }, | |
4374 | + | |
4375 | + getLayerId: function (layer) { | |
4376 | + return L.stamp(layer); | |
4377 | + } | |
4378 | +}); | |
4379 | + | |
4380 | +L.layerGroup = function (layers) { | |
4381 | + return new L.LayerGroup(layers); | |
4382 | +}; | |
4383 | + | |
4384 | + | |
4385 | +/* | |
4386 | + * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods | |
4387 | + * shared between a group of interactive layers (like vectors or markers). | |
4388 | + */ | |
4389 | + | |
4390 | +L.FeatureGroup = L.LayerGroup.extend({ | |
4391 | + includes: L.Mixin.Events, | |
4392 | + | |
4393 | + statics: { | |
4394 | + EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose' | |
4395 | + }, | |
4396 | + | |
4397 | + addLayer: function (layer) { | |
4398 | + if (this.hasLayer(layer)) { | |
4399 | + return this; | |
4400 | + } | |
4401 | + | |
4402 | + if ('on' in layer) { | |
4403 | + layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); | |
4404 | + } | |
4405 | + | |
4406 | + L.LayerGroup.prototype.addLayer.call(this, layer); | |
4407 | + | |
4408 | + if (this._popupContent && layer.bindPopup) { | |
4409 | + layer.bindPopup(this._popupContent, this._popupOptions); | |
4410 | + } | |
4411 | + | |
4412 | + return this.fire('layeradd', {layer: layer}); | |
4413 | + }, | |
4414 | + | |
4415 | + removeLayer: function (layer) { | |
4416 | + if (!this.hasLayer(layer)) { | |
4417 | + return this; | |
4418 | + } | |
4419 | + if (layer in this._layers) { | |
4420 | + layer = this._layers[layer]; | |
4421 | + } | |
4422 | + | |
4423 | + layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this); | |
4424 | + | |
4425 | + L.LayerGroup.prototype.removeLayer.call(this, layer); | |
4426 | + | |
4427 | + if (this._popupContent) { | |
4428 | + this.invoke('unbindPopup'); | |
4429 | + } | |
4430 | + | |
4431 | + return this.fire('layerremove', {layer: layer}); | |
4432 | + }, | |
4433 | + | |
4434 | + bindPopup: function (content, options) { | |
4435 | + this._popupContent = content; | |
4436 | + this._popupOptions = options; | |
4437 | + return this.invoke('bindPopup', content, options); | |
4438 | + }, | |
4439 | + | |
4440 | + openPopup: function (latlng) { | |
4441 | + // open popup on the first layer | |
4442 | + for (var id in this._layers) { | |
4443 | + this._layers[id].openPopup(latlng); | |
4444 | + break; | |
4445 | + } | |
4446 | + return this; | |
4447 | + }, | |
4448 | + | |
4449 | + setStyle: function (style) { | |
4450 | + return this.invoke('setStyle', style); | |
4451 | + }, | |
4452 | + | |
4453 | + bringToFront: function () { | |
4454 | + return this.invoke('bringToFront'); | |
4455 | + }, | |
4456 | + | |
4457 | + bringToBack: function () { | |
4458 | + return this.invoke('bringToBack'); | |
4459 | + }, | |
4460 | + | |
4461 | + getBounds: function () { | |
4462 | + var bounds = new L.LatLngBounds(); | |
4463 | + | |
4464 | + this.eachLayer(function (layer) { | |
4465 | + bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds()); | |
4466 | + }); | |
4467 | + | |
4468 | + return bounds; | |
4469 | + }, | |
4470 | + | |
4471 | + _propagateEvent: function (e) { | |
4472 | + e = L.extend({ | |
4473 | + layer: e.target, | |
4474 | + target: this | |
4475 | + }, e); | |
4476 | + this.fire(e.type, e); | |
4477 | + } | |
4478 | +}); | |
4479 | + | |
4480 | +L.featureGroup = function (layers) { | |
4481 | + return new L.FeatureGroup(layers); | |
4482 | +}; | |
4483 | + | |
4484 | + | |
4485 | +/* | |
4486 | + * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc. | |
4487 | + */ | |
4488 | + | |
4489 | +L.Path = L.Class.extend({ | |
4490 | + includes: [L.Mixin.Events], | |
4491 | + | |
4492 | + statics: { | |
4493 | + // how much to extend the clip area around the map view | |
4494 | + // (relative to its size, e.g. 0.5 is half the screen in each direction) | |
4495 | + // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is) | |
4496 | + CLIP_PADDING: (function () { | |
4497 | + var max = L.Browser.mobile ? 1280 : 2000, | |
4498 | + target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2; | |
4499 | + return Math.max(0, Math.min(0.5, target)); | |
4500 | + })() | |
4501 | + }, | |
4502 | + | |
4503 | + options: { | |
4504 | + stroke: true, | |
4505 | + color: '#0033ff', | |
4506 | + dashArray: null, | |
4507 | + lineCap: null, | |
4508 | + lineJoin: null, | |
4509 | + weight: 5, | |
4510 | + opacity: 0.5, | |
4511 | + | |
4512 | + fill: false, | |
4513 | + fillColor: null, //same as color by default | |
4514 | + fillOpacity: 0.2, | |
4515 | + | |
4516 | + clickable: true | |
4517 | + }, | |
4518 | + | |
4519 | + initialize: function (options) { | |
4520 | + L.setOptions(this, options); | |
4521 | + }, | |
4522 | + | |
4523 | + onAdd: function (map) { | |
4524 | + this._map = map; | |
4525 | + | |
4526 | + if (!this._container) { | |
4527 | + this._initElements(); | |
4528 | + this._initEvents(); | |
4529 | + } | |
4530 | + | |
4531 | + this.projectLatlngs(); | |
4532 | + this._updatePath(); | |
4533 | + | |
4534 | + if (this._container) { | |
4535 | + this._map._pathRoot.appendChild(this._container); | |
4536 | + } | |
4537 | + | |
4538 | + this.fire('add'); | |
4539 | + | |
4540 | + map.on({ | |
4541 | + 'viewreset': this.projectLatlngs, | |
4542 | + 'moveend': this._updatePath | |
4543 | + }, this); | |
4544 | + }, | |
4545 | + | |
4546 | + addTo: function (map) { | |
4547 | + map.addLayer(this); | |
4548 | + return this; | |
4549 | + }, | |
4550 | + | |
4551 | + onRemove: function (map) { | |
4552 | + map._pathRoot.removeChild(this._container); | |
4553 | + | |
4554 | + // Need to fire remove event before we set _map to null as the event hooks might need the object | |
4555 | + this.fire('remove'); | |
4556 | + this._map = null; | |
4557 | + | |
4558 | + if (L.Browser.vml) { | |
4559 | + this._container = null; | |
4560 | + this._stroke = null; | |
4561 | + this._fill = null; | |
4562 | + } | |
4563 | + | |
4564 | + map.off({ | |
4565 | + 'viewreset': this.projectLatlngs, | |
4566 | + 'moveend': this._updatePath | |
4567 | + }, this); | |
4568 | + }, | |
4569 | + | |
4570 | + projectLatlngs: function () { | |
4571 | + // do all projection stuff here | |
4572 | + }, | |
4573 | + | |
4574 | + setStyle: function (style) { | |
4575 | + L.setOptions(this, style); | |
4576 | + | |
4577 | + if (this._container) { | |
4578 | + this._updateStyle(); | |
4579 | + } | |
4580 | + | |
4581 | + return this; | |
4582 | + }, | |
4583 | + | |
4584 | + redraw: function () { | |
4585 | + if (this._map) { | |
4586 | + this.projectLatlngs(); | |
4587 | + this._updatePath(); | |
4588 | + } | |
4589 | + return this; | |
4590 | + } | |
4591 | +}); | |
4592 | + | |
4593 | +L.Map.include({ | |
4594 | + _updatePathViewport: function () { | |
4595 | + var p = L.Path.CLIP_PADDING, | |
4596 | + size = this.getSize(), | |
4597 | + panePos = L.DomUtil.getPosition(this._mapPane), | |
4598 | + min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()), | |
4599 | + max = min.add(size.multiplyBy(1 + p * 2)._round()); | |
4600 | + | |
4601 | + this._pathViewport = new L.Bounds(min, max); | |
4602 | + } | |
4603 | +}); | |
4604 | + | |
4605 | + | |
4606 | +/* | |
4607 | + * Extends L.Path with SVG-specific rendering code. | |
4608 | + */ | |
4609 | + | |
4610 | +L.Path.SVG_NS = 'http://www.w3.org/2000/svg'; | |
4611 | + | |
4612 | +L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect); | |
4613 | + | |
4614 | +L.Path = L.Path.extend({ | |
4615 | + statics: { | |
4616 | + SVG: L.Browser.svg | |
4617 | + }, | |
4618 | + | |
4619 | + bringToFront: function () { | |
4620 | + var root = this._map._pathRoot, | |
4621 | + path = this._container; | |
4622 | + | |
4623 | + if (path && root.lastChild !== path) { | |
4624 | + root.appendChild(path); | |
4625 | + } | |
4626 | + return this; | |
4627 | + }, | |
4628 | + | |
4629 | + bringToBack: function () { | |
4630 | + var root = this._map._pathRoot, | |
4631 | + path = this._container, | |
4632 | + first = root.firstChild; | |
4633 | + | |
4634 | + if (path && first !== path) { | |
4635 | + root.insertBefore(path, first); | |
4636 | + } | |
4637 | + return this; | |
4638 | + }, | |
4639 | + | |
4640 | + getPathString: function () { | |
4641 | + // form path string here | |
4642 | + }, | |
4643 | + | |
4644 | + _createElement: function (name) { | |
4645 | + return document.createElementNS(L.Path.SVG_NS, name); | |
4646 | + }, | |
4647 | + | |
4648 | + _initElements: function () { | |
4649 | + this._map._initPathRoot(); | |
4650 | + this._initPath(); | |
4651 | + this._initStyle(); | |
4652 | + }, | |
4653 | + | |
4654 | + _initPath: function () { | |
4655 | + this._container = this._createElement('g'); | |
4656 | + | |
4657 | + this._path = this._createElement('path'); | |
4658 | + | |
4659 | + if (this.options.className) { | |
4660 | + L.DomUtil.addClass(this._path, this.options.className); | |
4661 | + } | |
4662 | + | |
4663 | + this._container.appendChild(this._path); | |
4664 | + }, | |
4665 | + | |
4666 | + _initStyle: function () { | |
4667 | + if (this.options.stroke) { | |
4668 | + this._path.setAttribute('stroke-linejoin', 'round'); | |
4669 | + this._path.setAttribute('stroke-linecap', 'round'); | |
4670 | + } | |
4671 | + if (this.options.fill) { | |
4672 | + this._path.setAttribute('fill-rule', 'evenodd'); | |
4673 | + } | |
4674 | + if (this.options.pointerEvents) { | |
4675 | + this._path.setAttribute('pointer-events', this.options.pointerEvents); | |
4676 | + } | |
4677 | + if (!this.options.clickable && !this.options.pointerEvents) { | |
4678 | + this._path.setAttribute('pointer-events', 'none'); | |
4679 | + } | |
4680 | + this._updateStyle(); | |
4681 | + }, | |
4682 | + | |
4683 | + _updateStyle: function () { | |
4684 | + if (this.options.stroke) { | |
4685 | + this._path.setAttribute('stroke', this.options.color); | |
4686 | + this._path.setAttribute('stroke-opacity', this.options.opacity); | |
4687 | + this._path.setAttribute('stroke-width', this.options.weight); | |
4688 | + if (this.options.dashArray) { | |
4689 | + this._path.setAttribute('stroke-dasharray', this.options.dashArray); | |
4690 | + } else { | |
4691 | + this._path.removeAttribute('stroke-dasharray'); | |
4692 | + } | |
4693 | + if (this.options.lineCap) { | |
4694 | + this._path.setAttribute('stroke-linecap', this.options.lineCap); | |
4695 | + } | |
4696 | + if (this.options.lineJoin) { | |
4697 | + this._path.setAttribute('stroke-linejoin', this.options.lineJoin); | |
4698 | + } | |
4699 | + } else { | |
4700 | + this._path.setAttribute('stroke', 'none'); | |
4701 | + } | |
4702 | + if (this.options.fill) { | |
4703 | + this._path.setAttribute('fill', this.options.fillColor || this.options.color); | |
4704 | + this._path.setAttribute('fill-opacity', this.options.fillOpacity); | |
4705 | + } else { | |
4706 | + this._path.setAttribute('fill', 'none'); | |
4707 | + } | |
4708 | + }, | |
4709 | + | |
4710 | + _updatePath: function () { | |
4711 | + var str = this.getPathString(); | |
4712 | + if (!str) { | |
4713 | + // fix webkit empty string parsing bug | |
4714 | + str = 'M0 0'; | |
4715 | + } | |
4716 | + this._path.setAttribute('d', str); | |
4717 | + }, | |
4718 | + | |
4719 | + // TODO remove duplication with L.Map | |
4720 | + _initEvents: function () { | |
4721 | + if (this.options.clickable) { | |
4722 | + if (L.Browser.svg || !L.Browser.vml) { | |
4723 | + L.DomUtil.addClass(this._path, 'leaflet-clickable'); | |
4724 | + } | |
4725 | + | |
4726 | + L.DomEvent.on(this._container, 'click', this._onMouseClick, this); | |
4727 | + | |
4728 | + var events = ['dblclick', 'mousedown', 'mouseover', | |
4729 | + 'mouseout', 'mousemove', 'contextmenu']; | |
4730 | + for (var i = 0; i < events.length; i++) { | |
4731 | + L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this); | |
4732 | + } | |
4733 | + } | |
4734 | + }, | |
4735 | + | |
4736 | + _onMouseClick: function (e) { | |
4737 | + if (this._map.dragging && this._map.dragging.moved()) { return; } | |
4738 | + | |
4739 | + this._fireMouseEvent(e); | |
4740 | + }, | |
4741 | + | |
4742 | + _fireMouseEvent: function (e) { | |
4743 | + if (!this.hasEventListeners(e.type)) { return; } | |
4744 | + | |
4745 | + var map = this._map, | |
4746 | + containerPoint = map.mouseEventToContainerPoint(e), | |
4747 | + layerPoint = map.containerPointToLayerPoint(containerPoint), | |
4748 | + latlng = map.layerPointToLatLng(layerPoint); | |
4749 | + | |
4750 | + this.fire(e.type, { | |
4751 | + latlng: latlng, | |
4752 | + layerPoint: layerPoint, | |
4753 | + containerPoint: containerPoint, | |
4754 | + originalEvent: e | |
4755 | + }); | |
4756 | + | |
4757 | + if (e.type === 'contextmenu') { | |
4758 | + L.DomEvent.preventDefault(e); | |
4759 | + } | |
4760 | + if (e.type !== 'mousemove') { | |
4761 | + L.DomEvent.stopPropagation(e); | |
4762 | + } | |
4763 | + } | |
4764 | +}); | |
4765 | + | |
4766 | +L.Map.include({ | |
4767 | + _initPathRoot: function () { | |
4768 | + if (!this._pathRoot) { | |
4769 | + this._pathRoot = L.Path.prototype._createElement('svg'); | |
4770 | + this._panes.overlayPane.appendChild(this._pathRoot); | |
4771 | + | |
4772 | + if (this.options.zoomAnimation && L.Browser.any3d) { | |
4773 | + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated'); | |
4774 | + | |
4775 | + this.on({ | |
4776 | + 'zoomanim': this._animatePathZoom, | |
4777 | + 'zoomend': this._endPathZoom | |
4778 | + }); | |
4779 | + } else { | |
4780 | + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide'); | |
4781 | + } | |
4782 | + | |
4783 | + this.on('moveend', this._updateSvgViewport); | |
4784 | + this._updateSvgViewport(); | |
4785 | + } | |
4786 | + }, | |
4787 | + | |
4788 | + _animatePathZoom: function (e) { | |
4789 | + var scale = this.getZoomScale(e.zoom), | |
4790 | + offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min); | |
4791 | + | |
4792 | + this._pathRoot.style[L.DomUtil.TRANSFORM] = | |
4793 | + L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') '; | |
4794 | + | |
4795 | + this._pathZooming = true; | |
4796 | + }, | |
4797 | + | |
4798 | + _endPathZoom: function () { | |
4799 | + this._pathZooming = false; | |
4800 | + }, | |
4801 | + | |
4802 | + _updateSvgViewport: function () { | |
4803 | + | |
4804 | + if (this._pathZooming) { | |
4805 | + // Do not update SVGs while a zoom animation is going on otherwise the animation will break. | |
4806 | + // When the zoom animation ends we will be updated again anyway | |
4807 | + // This fixes the case where you do a momentum move and zoom while the move is still ongoing. | |
4808 | + return; | |
4809 | + } | |
4810 | + | |
4811 | + this._updatePathViewport(); | |
4812 | + | |
4813 | + var vp = this._pathViewport, | |
4814 | + min = vp.min, | |
4815 | + max = vp.max, | |
4816 | + width = max.x - min.x, | |
4817 | + height = max.y - min.y, | |
4818 | + root = this._pathRoot, | |
4819 | + pane = this._panes.overlayPane; | |
4820 | + | |
4821 | + // Hack to make flicker on drag end on mobile webkit less irritating | |
4822 | + if (L.Browser.mobileWebkit) { | |
4823 | + pane.removeChild(root); | |
4824 | + } | |
4825 | + | |
4826 | + L.DomUtil.setPosition(root, min); | |
4827 | + root.setAttribute('width', width); | |
4828 | + root.setAttribute('height', height); | |
4829 | + root.setAttribute('viewBox', [min.x, min.y, width, height].join(' ')); | |
4830 | + | |
4831 | + if (L.Browser.mobileWebkit) { | |
4832 | + pane.appendChild(root); | |
4833 | + } | |
4834 | + } | |
4835 | +}); | |
4836 | + | |
4837 | + | |
4838 | +/* | |
4839 | + * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods. | |
4840 | + */ | |
4841 | + | |
4842 | +L.Path.include({ | |
4843 | + | |
4844 | + bindPopup: function (content, options) { | |
4845 | + | |
4846 | + if (content instanceof L.Popup) { | |
4847 | + this._popup = content; | |
4848 | + } else { | |
4849 | + if (!this._popup || options) { | |
4850 | + this._popup = new L.Popup(options, this); | |
4851 | + } | |
4852 | + this._popup.setContent(content); | |
4853 | + } | |
4854 | + | |
4855 | + if (!this._popupHandlersAdded) { | |
4856 | + this | |
4857 | + .on('click', this._openPopup, this) | |
4858 | + .on('remove', this.closePopup, this); | |
4859 | + | |
4860 | + this._popupHandlersAdded = true; | |
4861 | + } | |
4862 | + | |
4863 | + return this; | |
4864 | + }, | |
4865 | + | |
4866 | + unbindPopup: function () { | |
4867 | + if (this._popup) { | |
4868 | + this._popup = null; | |
4869 | + this | |
4870 | + .off('click', this._openPopup) | |
4871 | + .off('remove', this.closePopup); | |
4872 | + | |
4873 | + this._popupHandlersAdded = false; | |
4874 | + } | |
4875 | + return this; | |
4876 | + }, | |
4877 | + | |
4878 | + openPopup: function (latlng) { | |
4879 | + | |
4880 | + if (this._popup) { | |
4881 | + // open the popup from one of the path's points if not specified | |
4882 | + latlng = latlng || this._latlng || | |
4883 | + this._latlngs[Math.floor(this._latlngs.length / 2)]; | |
4884 | + | |
4885 | + this._openPopup({latlng: latlng}); | |
4886 | + } | |
4887 | + | |
4888 | + return this; | |
4889 | + }, | |
4890 | + | |
4891 | + closePopup: function () { | |
4892 | + if (this._popup) { | |
4893 | + this._popup._close(); | |
4894 | + } | |
4895 | + return this; | |
4896 | + }, | |
4897 | + | |
4898 | + _openPopup: function (e) { | |
4899 | + this._popup.setLatLng(e.latlng); | |
4900 | + this._map.openPopup(this._popup); | |
4901 | + } | |
4902 | +}); | |
4903 | + | |
4904 | + | |
4905 | +/* | |
4906 | + * Vector rendering for IE6-8 through VML. | |
4907 | + * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! | |
4908 | + */ | |
4909 | + | |
4910 | +L.Browser.vml = !L.Browser.svg && (function () { | |
4911 | + try { | |
4912 | + var div = document.createElement('div'); | |
4913 | + div.innerHTML = '<v:shape adj="1"/>'; | |
4914 | + | |
4915 | + var shape = div.firstChild; | |
4916 | + shape.style.behavior = 'url(#default#VML)'; | |
4917 | + | |
4918 | + return shape && (typeof shape.adj === 'object'); | |
4919 | + | |
4920 | + } catch (e) { | |
4921 | + return false; | |
4922 | + } | |
4923 | +}()); | |
4924 | + | |
4925 | +L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ | |
4926 | + statics: { | |
4927 | + VML: true, | |
4928 | + CLIP_PADDING: 0.02 | |
4929 | + }, | |
4930 | + | |
4931 | + _createElement: (function () { | |
4932 | + try { | |
4933 | + document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); | |
4934 | + return function (name) { | |
4935 | + return document.createElement('<lvml:' + name + ' class="lvml">'); | |
4936 | + }; | |
4937 | + } catch (e) { | |
4938 | + return function (name) { | |
4939 | + return document.createElement( | |
4940 | + '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); | |
4941 | + }; | |
4942 | + } | |
4943 | + }()), | |
4944 | + | |
4945 | + _initPath: function () { | |
4946 | + var container = this._container = this._createElement('shape'); | |
4947 | + | |
4948 | + L.DomUtil.addClass(container, 'leaflet-vml-shape' + | |
4949 | + (this.options.className ? ' ' + this.options.className : '')); | |
4950 | + | |
4951 | + if (this.options.clickable) { | |
4952 | + L.DomUtil.addClass(container, 'leaflet-clickable'); | |
4953 | + } | |
4954 | + | |
4955 | + container.coordsize = '1 1'; | |
4956 | + | |
4957 | + this._path = this._createElement('path'); | |
4958 | + container.appendChild(this._path); | |
4959 | + | |
4960 | + this._map._pathRoot.appendChild(container); | |
4961 | + }, | |
4962 | + | |
4963 | + _initStyle: function () { | |
4964 | + this._updateStyle(); | |
4965 | + }, | |
4966 | + | |
4967 | + _updateStyle: function () { | |
4968 | + var stroke = this._stroke, | |
4969 | + fill = this._fill, | |
4970 | + options = this.options, | |
4971 | + container = this._container; | |
4972 | + | |
4973 | + container.stroked = options.stroke; | |
4974 | + container.filled = options.fill; | |
4975 | + | |
4976 | + if (options.stroke) { | |
4977 | + if (!stroke) { | |
4978 | + stroke = this._stroke = this._createElement('stroke'); | |
4979 | + stroke.endcap = 'round'; | |
4980 | + container.appendChild(stroke); | |
4981 | + } | |
4982 | + stroke.weight = options.weight + 'px'; | |
4983 | + stroke.color = options.color; | |
4984 | + stroke.opacity = options.opacity; | |
4985 | + | |
4986 | + if (options.dashArray) { | |
4987 | + stroke.dashStyle = L.Util.isArray(options.dashArray) ? | |
4988 | + options.dashArray.join(' ') : | |
4989 | + options.dashArray.replace(/( *, *)/g, ' '); | |
4990 | + } else { | |
4991 | + stroke.dashStyle = ''; | |
4992 | + } | |
4993 | + if (options.lineCap) { | |
4994 | + stroke.endcap = options.lineCap.replace('butt', 'flat'); | |
4995 | + } | |
4996 | + if (options.lineJoin) { | |
4997 | + stroke.joinstyle = options.lineJoin; | |
4998 | + } | |
4999 | + | |
5000 | + } else if (stroke) { | |
5001 | + container.removeChild(stroke); | |
5002 | + this._stroke = null; | |
5003 | + } | |
5004 | + | |
5005 | + if (options.fill) { | |
5006 | + if (!fill) { | |
5007 | + fill = this._fill = this._createElement('fill'); | |
5008 | + container.appendChild(fill); | |
5009 | + } | |
5010 | + fill.color = options.fillColor || options.color; | |
5011 | + fill.opacity = options.fillOpacity; | |
5012 | + | |
5013 | + } else if (fill) { | |
5014 | + container.removeChild(fill); | |
5015 | + this._fill = null; | |
5016 | + } | |
5017 | + }, | |
5018 | + | |
5019 | + _updatePath: function () { | |
5020 | + var style = this._container.style; | |
5021 | + | |
5022 | + style.display = 'none'; | |
5023 | + this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug | |
5024 | + style.display = ''; | |
5025 | + } | |
5026 | +}); | |
5027 | + | |
5028 | +L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : { | |
5029 | + _initPathRoot: function () { | |
5030 | + if (this._pathRoot) { return; } | |
5031 | + | |
5032 | + var root = this._pathRoot = document.createElement('div'); | |
5033 | + root.className = 'leaflet-vml-container'; | |
5034 | + this._panes.overlayPane.appendChild(root); | |
5035 | + | |
5036 | + this.on('moveend', this._updatePathViewport); | |
5037 | + this._updatePathViewport(); | |
5038 | + } | |
5039 | +}); | |
5040 | + | |
5041 | + | |
5042 | +/* | |
5043 | + * Vector rendering for all browsers that support canvas. | |
5044 | + */ | |
5045 | + | |
5046 | +L.Browser.canvas = (function () { | |
5047 | + return !!document.createElement('canvas').getContext; | |
5048 | +}()); | |
5049 | + | |
5050 | +L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({ | |
5051 | + statics: { | |
5052 | + //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value | |
5053 | + CANVAS: true, | |
5054 | + SVG: false | |
5055 | + }, | |
5056 | + | |
5057 | + redraw: function () { | |
5058 | + if (this._map) { | |
5059 | + this.projectLatlngs(); | |
5060 | + this._requestUpdate(); | |
5061 | + } | |
5062 | + return this; | |
5063 | + }, | |
5064 | + | |
5065 | + setStyle: function (style) { | |
5066 | + L.setOptions(this, style); | |
5067 | + | |
5068 | + if (this._map) { | |
5069 | + this._updateStyle(); | |
5070 | + this._requestUpdate(); | |
5071 | + } | |
5072 | + return this; | |
5073 | + }, | |
5074 | + | |
5075 | + onRemove: function (map) { | |
5076 | + map | |
5077 | + .off('viewreset', this.projectLatlngs, this) | |
5078 | + .off('moveend', this._updatePath, this); | |
5079 | + | |
5080 | + if (this.options.clickable) { | |
5081 | + this._map.off('click', this._onClick, this); | |
5082 | + this._map.off('mousemove', this._onMouseMove, this); | |
5083 | + } | |
5084 | + | |
5085 | + this._requestUpdate(); | |
5086 | + | |
5087 | + this.fire('remove'); | |
5088 | + this._map = null; | |
5089 | + }, | |
5090 | + | |
5091 | + _requestUpdate: function () { | |
5092 | + if (this._map && !L.Path._updateRequest) { | |
5093 | + L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map); | |
5094 | + } | |
5095 | + }, | |
5096 | + | |
5097 | + _fireMapMoveEnd: function () { | |
5098 | + L.Path._updateRequest = null; | |
5099 | + this.fire('moveend'); | |
5100 | + }, | |
5101 | + | |
5102 | + _initElements: function () { | |
5103 | + this._map._initPathRoot(); | |
5104 | + this._ctx = this._map._canvasCtx; | |
5105 | + }, | |
5106 | + | |
5107 | + _updateStyle: function () { | |
5108 | + var options = this.options; | |
5109 | + | |
5110 | + if (options.stroke) { | |
5111 | + this._ctx.lineWidth = options.weight; | |
5112 | + this._ctx.strokeStyle = options.color; | |
5113 | + } | |
5114 | + if (options.fill) { | |
5115 | + this._ctx.fillStyle = options.fillColor || options.color; | |
5116 | + } | |
5117 | + }, | |
5118 | + | |
5119 | + _drawPath: function () { | |
5120 | + var i, j, len, len2, point, drawMethod; | |
5121 | + | |
5122 | + this._ctx.beginPath(); | |
5123 | + | |
5124 | + for (i = 0, len = this._parts.length; i < len; i++) { | |
5125 | + for (j = 0, len2 = this._parts[i].length; j < len2; j++) { | |
5126 | + point = this._parts[i][j]; | |
5127 | + drawMethod = (j === 0 ? 'move' : 'line') + 'To'; | |
5128 | + | |
5129 | + this._ctx[drawMethod](point.x, point.y); | |
5130 | + } | |
5131 | + // TODO refactor ugly hack | |
5132 | + if (this instanceof L.Polygon) { | |
5133 | + this._ctx.closePath(); | |
5134 | + } | |
5135 | + } | |
5136 | + }, | |
5137 | + | |
5138 | + _checkIfEmpty: function () { | |
5139 | + return !this._parts.length; | |
5140 | + }, | |
5141 | + | |
5142 | + _updatePath: function () { | |
5143 | + if (this._checkIfEmpty()) { return; } | |
5144 | + | |
5145 | + var ctx = this._ctx, | |
5146 | + options = this.options; | |
5147 | + | |
5148 | + this._drawPath(); | |
5149 | + ctx.save(); | |
5150 | + this._updateStyle(); | |
5151 | + | |
5152 | + if (options.fill) { | |
5153 | + ctx.globalAlpha = options.fillOpacity; | |
5154 | + ctx.fill(); | |
5155 | + } | |
5156 | + | |
5157 | + if (options.stroke) { | |
5158 | + ctx.globalAlpha = options.opacity; | |
5159 | + ctx.stroke(); | |
5160 | + } | |
5161 | + | |
5162 | + ctx.restore(); | |
5163 | + | |
5164 | + // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature | |
5165 | + }, | |
5166 | + | |
5167 | + _initEvents: function () { | |
5168 | + if (this.options.clickable) { | |
5169 | + // TODO dblclick | |
5170 | + this._map.on('mousemove', this._onMouseMove, this); | |
5171 | + this._map.on('click', this._onClick, this); | |
5172 | + } | |
5173 | + }, | |
5174 | + | |
5175 | + _onClick: function (e) { | |
5176 | + if (this._containsPoint(e.layerPoint)) { | |
5177 | + this.fire('click', e); | |
5178 | + } | |
5179 | + }, | |
5180 | + | |
5181 | + _onMouseMove: function (e) { | |
5182 | + if (!this._map || this._map._animatingZoom) { return; } | |
5183 | + | |
5184 | + // TODO don't do on each move | |
5185 | + if (this._containsPoint(e.layerPoint)) { | |
5186 | + this._ctx.canvas.style.cursor = 'pointer'; | |
5187 | + this._mouseInside = true; | |
5188 | + this.fire('mouseover', e); | |
5189 | + | |
5190 | + } else if (this._mouseInside) { | |
5191 | + this._ctx.canvas.style.cursor = ''; | |
5192 | + this._mouseInside = false; | |
5193 | + this.fire('mouseout', e); | |
5194 | + } | |
5195 | + } | |
5196 | +}); | |
5197 | + | |
5198 | +L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : { | |
5199 | + _initPathRoot: function () { | |
5200 | + var root = this._pathRoot, | |
5201 | + ctx; | |
5202 | + | |
5203 | + if (!root) { | |
5204 | + root = this._pathRoot = document.createElement('canvas'); | |
5205 | + root.style.position = 'absolute'; | |
5206 | + ctx = this._canvasCtx = root.getContext('2d'); | |
5207 | + | |
5208 | + ctx.lineCap = 'round'; | |
5209 | + ctx.lineJoin = 'round'; | |
5210 | + | |
5211 | + this._panes.overlayPane.appendChild(root); | |
5212 | + | |
5213 | + if (this.options.zoomAnimation) { | |
5214 | + this._pathRoot.className = 'leaflet-zoom-animated'; | |
5215 | + this.on('zoomanim', this._animatePathZoom); | |
5216 | + this.on('zoomend', this._endPathZoom); | |
5217 | + } | |
5218 | + this.on('moveend', this._updateCanvasViewport); | |
5219 | + this._updateCanvasViewport(); | |
5220 | + } | |
5221 | + }, | |
5222 | + | |
5223 | + _updateCanvasViewport: function () { | |
5224 | + // don't redraw while zooming. See _updateSvgViewport for more details | |
5225 | + if (this._pathZooming) { return; } | |
5226 | + this._updatePathViewport(); | |
5227 | + | |
5228 | + var vp = this._pathViewport, | |
5229 | + min = vp.min, | |
5230 | + size = vp.max.subtract(min), | |
5231 | + root = this._pathRoot; | |
5232 | + | |
5233 | + //TODO check if this works properly on mobile webkit | |
5234 | + L.DomUtil.setPosition(root, min); | |
5235 | + root.width = size.x; | |
5236 | + root.height = size.y; | |
5237 | + root.getContext('2d').translate(-min.x, -min.y); | |
5238 | + } | |
5239 | +}); | |
5240 | + | |
5241 | + | |
5242 | +/* | |
5243 | + * L.LineUtil contains different utility functions for line segments | |
5244 | + * and polylines (clipping, simplification, distances, etc.) | |
5245 | + */ | |
5246 | + | |
5247 | +/*jshint bitwise:false */ // allow bitwise operations for this file | |
5248 | + | |
5249 | +L.LineUtil = { | |
5250 | + | |
5251 | + // Simplify polyline with vertex reduction and Douglas-Peucker simplification. | |
5252 | + // Improves rendering performance dramatically by lessening the number of points to draw. | |
5253 | + | |
5254 | + simplify: function (/*Point[]*/ points, /*Number*/ tolerance) { | |
5255 | + if (!tolerance || !points.length) { | |
5256 | + return points.slice(); | |
5257 | + } | |
5258 | + | |
5259 | + var sqTolerance = tolerance * tolerance; | |
5260 | + | |
5261 | + // stage 1: vertex reduction | |
5262 | + points = this._reducePoints(points, sqTolerance); | |
5263 | + | |
5264 | + // stage 2: Douglas-Peucker simplification | |
5265 | + points = this._simplifyDP(points, sqTolerance); | |
5266 | + | |
5267 | + return points; | |
5268 | + }, | |
5269 | + | |
5270 | + // distance from a point to a segment between two points | |
5271 | + pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { | |
5272 | + return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); | |
5273 | + }, | |
5274 | + | |
5275 | + closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { | |
5276 | + return this._sqClosestPointOnSegment(p, p1, p2); | |
5277 | + }, | |
5278 | + | |
5279 | + // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm | |
5280 | + _simplifyDP: function (points, sqTolerance) { | |
5281 | + | |
5282 | + var len = points.length, | |
5283 | + ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, | |
5284 | + markers = new ArrayConstructor(len); | |
5285 | + | |
5286 | + markers[0] = markers[len - 1] = 1; | |
5287 | + | |
5288 | + this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); | |
5289 | + | |
5290 | + var i, | |
5291 | + newPoints = []; | |
5292 | + | |
5293 | + for (i = 0; i < len; i++) { | |
5294 | + if (markers[i]) { | |
5295 | + newPoints.push(points[i]); | |
5296 | + } | |
5297 | + } | |
5298 | + | |
5299 | + return newPoints; | |
5300 | + }, | |
5301 | + | |
5302 | + _simplifyDPStep: function (points, markers, sqTolerance, first, last) { | |
5303 | + | |
5304 | + var maxSqDist = 0, | |
5305 | + index, i, sqDist; | |
5306 | + | |
5307 | + for (i = first + 1; i <= last - 1; i++) { | |
5308 | + sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); | |
5309 | + | |
5310 | + if (sqDist > maxSqDist) { | |
5311 | + index = i; | |
5312 | + maxSqDist = sqDist; | |
5313 | + } | |
5314 | + } | |
5315 | + | |
5316 | + if (maxSqDist > sqTolerance) { | |
5317 | + markers[index] = 1; | |
5318 | + | |
5319 | + this._simplifyDPStep(points, markers, sqTolerance, first, index); | |
5320 | + this._simplifyDPStep(points, markers, sqTolerance, index, last); | |
5321 | + } | |
5322 | + }, | |
5323 | + | |
5324 | + // reduce points that are too close to each other to a single point | |
5325 | + _reducePoints: function (points, sqTolerance) { | |
5326 | + var reducedPoints = [points[0]]; | |
5327 | + | |
5328 | + for (var i = 1, prev = 0, len = points.length; i < len; i++) { | |
5329 | + if (this._sqDist(points[i], points[prev]) > sqTolerance) { | |
5330 | + reducedPoints.push(points[i]); | |
5331 | + prev = i; | |
5332 | + } | |
5333 | + } | |
5334 | + if (prev < len - 1) { | |
5335 | + reducedPoints.push(points[len - 1]); | |
5336 | + } | |
5337 | + return reducedPoints; | |
5338 | + }, | |
5339 | + | |
5340 | + // Cohen-Sutherland line clipping algorithm. | |
5341 | + // Used to avoid rendering parts of a polyline that are not currently visible. | |
5342 | + | |
5343 | + clipSegment: function (a, b, bounds, useLastCode) { | |
5344 | + var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), | |
5345 | + codeB = this._getBitCode(b, bounds), | |
5346 | + | |
5347 | + codeOut, p, newCode; | |
5348 | + | |
5349 | + // save 2nd code to avoid calculating it on the next segment | |
5350 | + this._lastCode = codeB; | |
5351 | + | |
5352 | + while (true) { | |
5353 | + // if a,b is inside the clip window (trivial accept) | |
5354 | + if (!(codeA | codeB)) { | |
5355 | + return [a, b]; | |
5356 | + // if a,b is outside the clip window (trivial reject) | |
5357 | + } else if (codeA & codeB) { | |
5358 | + return false; | |
5359 | + // other cases | |
5360 | + } else { | |
5361 | + codeOut = codeA || codeB; | |
5362 | + p = this._getEdgeIntersection(a, b, codeOut, bounds); | |
5363 | + newCode = this._getBitCode(p, bounds); | |
5364 | + | |
5365 | + if (codeOut === codeA) { | |
5366 | + a = p; | |
5367 | + codeA = newCode; | |
5368 | + } else { | |
5369 | + b = p; | |
5370 | + codeB = newCode; | |
5371 | + } | |
5372 | + } | |
5373 | + } | |
5374 | + }, | |
5375 | + | |
5376 | + _getEdgeIntersection: function (a, b, code, bounds) { | |
5377 | + var dx = b.x - a.x, | |
5378 | + dy = b.y - a.y, | |
5379 | + min = bounds.min, | |
5380 | + max = bounds.max; | |
5381 | + | |
5382 | + if (code & 8) { // top | |
5383 | + return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y); | |
5384 | + } else if (code & 4) { // bottom | |
5385 | + return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y); | |
5386 | + } else if (code & 2) { // right | |
5387 | + return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx); | |
5388 | + } else if (code & 1) { // left | |
5389 | + return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx); | |
5390 | + } | |
5391 | + }, | |
5392 | + | |
5393 | + _getBitCode: function (/*Point*/ p, bounds) { | |
5394 | + var code = 0; | |
5395 | + | |
5396 | + if (p.x < bounds.min.x) { // left | |
5397 | + code |= 1; | |
5398 | + } else if (p.x > bounds.max.x) { // right | |
5399 | + code |= 2; | |
5400 | + } | |
5401 | + if (p.y < bounds.min.y) { // bottom | |
5402 | + code |= 4; | |
5403 | + } else if (p.y > bounds.max.y) { // top | |
5404 | + code |= 8; | |
5405 | + } | |
5406 | + | |
5407 | + return code; | |
5408 | + }, | |
5409 | + | |
5410 | + // square distance (to avoid unnecessary Math.sqrt calls) | |
5411 | + _sqDist: function (p1, p2) { | |
5412 | + var dx = p2.x - p1.x, | |
5413 | + dy = p2.y - p1.y; | |
5414 | + return dx * dx + dy * dy; | |
5415 | + }, | |
5416 | + | |
5417 | + // return closest point on segment or distance to that point | |
5418 | + _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { | |
5419 | + var x = p1.x, | |
5420 | + y = p1.y, | |
5421 | + dx = p2.x - x, | |
5422 | + dy = p2.y - y, | |
5423 | + dot = dx * dx + dy * dy, | |
5424 | + t; | |
5425 | + | |
5426 | + if (dot > 0) { | |
5427 | + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; | |
5428 | + | |
5429 | + if (t > 1) { | |
5430 | + x = p2.x; | |
5431 | + y = p2.y; | |
5432 | + } else if (t > 0) { | |
5433 | + x += dx * t; | |
5434 | + y += dy * t; | |
5435 | + } | |
5436 | + } | |
5437 | + | |
5438 | + dx = p.x - x; | |
5439 | + dy = p.y - y; | |
5440 | + | |
5441 | + return sqDist ? dx * dx + dy * dy : new L.Point(x, y); | |
5442 | + } | |
5443 | +}; | |
5444 | + | |
5445 | + | |
5446 | +/* | |
5447 | + * L.Polyline is used to display polylines on a map. | |
5448 | + */ | |
5449 | + | |
5450 | +L.Polyline = L.Path.extend({ | |
5451 | + initialize: function (latlngs, options) { | |
5452 | + L.Path.prototype.initialize.call(this, options); | |
5453 | + | |
5454 | + this._latlngs = this._convertLatLngs(latlngs); | |
5455 | + }, | |
5456 | + | |
5457 | + options: { | |
5458 | + // how much to simplify the polyline on each zoom level | |
5459 | + // more = better performance and smoother look, less = more accurate | |
5460 | + smoothFactor: 1.0, | |
5461 | + noClip: false | |
5462 | + }, | |
5463 | + | |
5464 | + projectLatlngs: function () { | |
5465 | + this._originalPoints = []; | |
5466 | + | |
5467 | + for (var i = 0, len = this._latlngs.length; i < len; i++) { | |
5468 | + this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]); | |
5469 | + } | |
5470 | + }, | |
5471 | + | |
5472 | + getPathString: function () { | |
5473 | + for (var i = 0, len = this._parts.length, str = ''; i < len; i++) { | |
5474 | + str += this._getPathPartStr(this._parts[i]); | |
5475 | + } | |
5476 | + return str; | |
5477 | + }, | |
5478 | + | |
5479 | + getLatLngs: function () { | |
5480 | + return this._latlngs; | |
5481 | + }, | |
5482 | + | |
5483 | + setLatLngs: function (latlngs) { | |
5484 | + this._latlngs = this._convertLatLngs(latlngs); | |
5485 | + return this.redraw(); | |
5486 | + }, | |
5487 | + | |
5488 | + addLatLng: function (latlng) { | |
5489 | + this._latlngs.push(L.latLng(latlng)); | |
5490 | + return this.redraw(); | |
5491 | + }, | |
5492 | + | |
5493 | + spliceLatLngs: function () { // (Number index, Number howMany) | |
5494 | + var removed = [].splice.apply(this._latlngs, arguments); | |
5495 | + this._convertLatLngs(this._latlngs, true); | |
5496 | + this.redraw(); | |
5497 | + return removed; | |
5498 | + }, | |
5499 | + | |
5500 | + closestLayerPoint: function (p) { | |
5501 | + var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null; | |
5502 | + | |
5503 | + for (var j = 0, jLen = parts.length; j < jLen; j++) { | |
5504 | + var points = parts[j]; | |
5505 | + for (var i = 1, len = points.length; i < len; i++) { | |
5506 | + p1 = points[i - 1]; | |
5507 | + p2 = points[i]; | |
5508 | + var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true); | |
5509 | + if (sqDist < minDistance) { | |
5510 | + minDistance = sqDist; | |
5511 | + minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2); | |
5512 | + } | |
5513 | + } | |
5514 | + } | |
5515 | + if (minPoint) { | |
5516 | + minPoint.distance = Math.sqrt(minDistance); | |
5517 | + } | |
5518 | + return minPoint; | |
5519 | + }, | |
5520 | + | |
5521 | + getBounds: function () { | |
5522 | + return new L.LatLngBounds(this.getLatLngs()); | |
5523 | + }, | |
5524 | + | |
5525 | + _convertLatLngs: function (latlngs, overwrite) { | |
5526 | + var i, len, target = overwrite ? latlngs : []; | |
5527 | + | |
5528 | + for (i = 0, len = latlngs.length; i < len; i++) { | |
5529 | + if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') { | |
5530 | + return; | |
5531 | + } | |
5532 | + target[i] = L.latLng(latlngs[i]); | |
5533 | + } | |
5534 | + return target; | |
5535 | + }, | |
5536 | + | |
5537 | + _initEvents: function () { | |
5538 | + L.Path.prototype._initEvents.call(this); | |
5539 | + }, | |
5540 | + | |
5541 | + _getPathPartStr: function (points) { | |
5542 | + var round = L.Path.VML; | |
5543 | + | |
5544 | + for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) { | |
5545 | + p = points[j]; | |
5546 | + if (round) { | |
5547 | + p._round(); | |
5548 | + } | |
5549 | + str += (j ? 'L' : 'M') + p.x + ' ' + p.y; | |
5550 | + } | |
5551 | + return str; | |
5552 | + }, | |
5553 | + | |
5554 | + _clipPoints: function () { | |
5555 | + var points = this._originalPoints, | |
5556 | + len = points.length, | |
5557 | + i, k, segment; | |
5558 | + | |
5559 | + if (this.options.noClip) { | |
5560 | + this._parts = [points]; | |
5561 | + return; | |
5562 | + } | |
5563 | + | |
5564 | + this._parts = []; | |
5565 | + | |
5566 | + var parts = this._parts, | |
5567 | + vp = this._map._pathViewport, | |
5568 | + lu = L.LineUtil; | |
5569 | + | |
5570 | + for (i = 0, k = 0; i < len - 1; i++) { | |
5571 | + segment = lu.clipSegment(points[i], points[i + 1], vp, i); | |
5572 | + if (!segment) { | |
5573 | + continue; | |
5574 | + } | |
5575 | + | |
5576 | + parts[k] = parts[k] || []; | |
5577 | + parts[k].push(segment[0]); | |
5578 | + | |
5579 | + // if segment goes out of screen, or it's the last one, it's the end of the line part | |
5580 | + if ((segment[1] !== points[i + 1]) || (i === len - 2)) { | |
5581 | + parts[k].push(segment[1]); | |
5582 | + k++; | |
5583 | + } | |
5584 | + } | |
5585 | + }, | |
5586 | + | |
5587 | + // simplify each clipped part of the polyline | |
5588 | + _simplifyPoints: function () { | |
5589 | + var parts = this._parts, | |
5590 | + lu = L.LineUtil; | |
5591 | + | |
5592 | + for (var i = 0, len = parts.length; i < len; i++) { | |
5593 | + parts[i] = lu.simplify(parts[i], this.options.smoothFactor); | |
5594 | + } | |
5595 | + }, | |
5596 | + | |
5597 | + _updatePath: function () { | |
5598 | + if (!this._map) { return; } | |
5599 | + | |
5600 | + this._clipPoints(); | |
5601 | + this._simplifyPoints(); | |
5602 | + | |
5603 | + L.Path.prototype._updatePath.call(this); | |
5604 | + } | |
5605 | +}); | |
5606 | + | |
5607 | +L.polyline = function (latlngs, options) { | |
5608 | + return new L.Polyline(latlngs, options); | |
5609 | +}; | |
5610 | + | |
5611 | + | |
5612 | +/* | |
5613 | + * L.PolyUtil contains utility functions for polygons (clipping, etc.). | |
5614 | + */ | |
5615 | + | |
5616 | +/*jshint bitwise:false */ // allow bitwise operations here | |
5617 | + | |
5618 | +L.PolyUtil = {}; | |
5619 | + | |
5620 | +/* | |
5621 | + * Sutherland-Hodgeman polygon clipping algorithm. | |
5622 | + * Used to avoid rendering parts of a polygon that are not currently visible. | |
5623 | + */ | |
5624 | +L.PolyUtil.clipPolygon = function (points, bounds) { | |
5625 | + var clippedPoints, | |
5626 | + edges = [1, 4, 2, 8], | |
5627 | + i, j, k, | |
5628 | + a, b, | |
5629 | + len, edge, p, | |
5630 | + lu = L.LineUtil; | |
5631 | + | |
5632 | + for (i = 0, len = points.length; i < len; i++) { | |
5633 | + points[i]._code = lu._getBitCode(points[i], bounds); | |
5634 | + } | |
5635 | + | |
5636 | + // for each edge (left, bottom, right, top) | |
5637 | + for (k = 0; k < 4; k++) { | |
5638 | + edge = edges[k]; | |
5639 | + clippedPoints = []; | |
5640 | + | |
5641 | + for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { | |
5642 | + a = points[i]; | |
5643 | + b = points[j]; | |
5644 | + | |
5645 | + // if a is inside the clip window | |
5646 | + if (!(a._code & edge)) { | |
5647 | + // if b is outside the clip window (a->b goes out of screen) | |
5648 | + if (b._code & edge) { | |
5649 | + p = lu._getEdgeIntersection(b, a, edge, bounds); | |
5650 | + p._code = lu._getBitCode(p, bounds); | |
5651 | + clippedPoints.push(p); | |
5652 | + } | |
5653 | + clippedPoints.push(a); | |
5654 | + | |
5655 | + // else if b is inside the clip window (a->b enters the screen) | |
5656 | + } else if (!(b._code & edge)) { | |
5657 | + p = lu._getEdgeIntersection(b, a, edge, bounds); | |
5658 | + p._code = lu._getBitCode(p, bounds); | |
5659 | + clippedPoints.push(p); | |
5660 | + } | |
5661 | + } | |
5662 | + points = clippedPoints; | |
5663 | + } | |
5664 | + | |
5665 | + return points; | |
5666 | +}; | |
5667 | + | |
5668 | + | |
5669 | +/* | |
5670 | + * L.Polygon is used to display polygons on a map. | |
5671 | + */ | |
5672 | + | |
5673 | +L.Polygon = L.Polyline.extend({ | |
5674 | + options: { | |
5675 | + fill: true | |
5676 | + }, | |
5677 | + | |
5678 | + initialize: function (latlngs, options) { | |
5679 | + L.Polyline.prototype.initialize.call(this, latlngs, options); | |
5680 | + this._initWithHoles(latlngs); | |
5681 | + }, | |
5682 | + | |
5683 | + _initWithHoles: function (latlngs) { | |
5684 | + var i, len, hole; | |
5685 | + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { | |
5686 | + this._latlngs = this._convertLatLngs(latlngs[0]); | |
5687 | + this._holes = latlngs.slice(1); | |
5688 | + | |
5689 | + for (i = 0, len = this._holes.length; i < len; i++) { | |
5690 | + hole = this._holes[i] = this._convertLatLngs(this._holes[i]); | |
5691 | + if (hole[0].equals(hole[hole.length - 1])) { | |
5692 | + hole.pop(); | |
5693 | + } | |
5694 | + } | |
5695 | + } | |
5696 | + | |
5697 | + // filter out last point if its equal to the first one | |
5698 | + latlngs = this._latlngs; | |
5699 | + | |
5700 | + if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) { | |
5701 | + latlngs.pop(); | |
5702 | + } | |
5703 | + }, | |
5704 | + | |
5705 | + projectLatlngs: function () { | |
5706 | + L.Polyline.prototype.projectLatlngs.call(this); | |
5707 | + | |
5708 | + // project polygon holes points | |
5709 | + // TODO move this logic to Polyline to get rid of duplication | |
5710 | + this._holePoints = []; | |
5711 | + | |
5712 | + if (!this._holes) { return; } | |
5713 | + | |
5714 | + var i, j, len, len2; | |
5715 | + | |
5716 | + for (i = 0, len = this._holes.length; i < len; i++) { | |
5717 | + this._holePoints[i] = []; | |
5718 | + | |
5719 | + for (j = 0, len2 = this._holes[i].length; j < len2; j++) { | |
5720 | + this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]); | |
5721 | + } | |
5722 | + } | |
5723 | + }, | |
5724 | + | |
5725 | + setLatLngs: function (latlngs) { | |
5726 | + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { | |
5727 | + this._initWithHoles(latlngs); | |
5728 | + return this.redraw(); | |
5729 | + } else { | |
5730 | + return L.Polyline.prototype.setLatLngs.call(this, latlngs); | |
5731 | + } | |
5732 | + }, | |
5733 | + | |
5734 | + _clipPoints: function () { | |
5735 | + var points = this._originalPoints, | |
5736 | + newParts = []; | |
5737 | + | |
5738 | + this._parts = [points].concat(this._holePoints); | |
5739 | + | |
5740 | + if (this.options.noClip) { return; } | |
5741 | + | |
5742 | + for (var i = 0, len = this._parts.length; i < len; i++) { | |
5743 | + var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport); | |
5744 | + if (clipped.length) { | |
5745 | + newParts.push(clipped); | |
5746 | + } | |
5747 | + } | |
5748 | + | |
5749 | + this._parts = newParts; | |
5750 | + }, | |
5751 | + | |
5752 | + _getPathPartStr: function (points) { | |
5753 | + var str = L.Polyline.prototype._getPathPartStr.call(this, points); | |
5754 | + return str + (L.Browser.svg ? 'z' : 'x'); | |
5755 | + } | |
5756 | +}); | |
5757 | + | |
5758 | +L.polygon = function (latlngs, options) { | |
5759 | + return new L.Polygon(latlngs, options); | |
5760 | +}; | |
5761 | + | |
5762 | + | |
5763 | +/* | |
5764 | + * Contains L.MultiPolyline and L.MultiPolygon layers. | |
5765 | + */ | |
5766 | + | |
5767 | +(function () { | |
5768 | + function createMulti(Klass) { | |
5769 | + | |
5770 | + return L.FeatureGroup.extend({ | |
5771 | + | |
5772 | + initialize: function (latlngs, options) { | |
5773 | + this._layers = {}; | |
5774 | + this._options = options; | |
5775 | + this.setLatLngs(latlngs); | |
5776 | + }, | |
5777 | + | |
5778 | + setLatLngs: function (latlngs) { | |
5779 | + var i = 0, | |
5780 | + len = latlngs.length; | |
5781 | + | |
5782 | + this.eachLayer(function (layer) { | |
5783 | + if (i < len) { | |
5784 | + layer.setLatLngs(latlngs[i++]); | |
5785 | + } else { | |
5786 | + this.removeLayer(layer); | |
5787 | + } | |
5788 | + }, this); | |
5789 | + | |
5790 | + while (i < len) { | |
5791 | + this.addLayer(new Klass(latlngs[i++], this._options)); | |
5792 | + } | |
5793 | + | |
5794 | + return this; | |
5795 | + }, | |
5796 | + | |
5797 | + getLatLngs: function () { | |
5798 | + var latlngs = []; | |
5799 | + | |
5800 | + this.eachLayer(function (layer) { | |
5801 | + latlngs.push(layer.getLatLngs()); | |
5802 | + }); | |
5803 | + | |
5804 | + return latlngs; | |
5805 | + } | |
5806 | + }); | |
5807 | + } | |
5808 | + | |
5809 | + L.MultiPolyline = createMulti(L.Polyline); | |
5810 | + L.MultiPolygon = createMulti(L.Polygon); | |
5811 | + | |
5812 | + L.multiPolyline = function (latlngs, options) { | |
5813 | + return new L.MultiPolyline(latlngs, options); | |
5814 | + }; | |
5815 | + | |
5816 | + L.multiPolygon = function (latlngs, options) { | |
5817 | + return new L.MultiPolygon(latlngs, options); | |
5818 | + }; | |
5819 | +}()); | |
5820 | + | |
5821 | + | |
5822 | +/* | |
5823 | + * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. | |
5824 | + */ | |
5825 | + | |
5826 | +L.Rectangle = L.Polygon.extend({ | |
5827 | + initialize: function (latLngBounds, options) { | |
5828 | + L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); | |
5829 | + }, | |
5830 | + | |
5831 | + setBounds: function (latLngBounds) { | |
5832 | + this.setLatLngs(this._boundsToLatLngs(latLngBounds)); | |
5833 | + }, | |
5834 | + | |
5835 | + _boundsToLatLngs: function (latLngBounds) { | |
5836 | + latLngBounds = L.latLngBounds(latLngBounds); | |
5837 | + return [ | |
5838 | + latLngBounds.getSouthWest(), | |
5839 | + latLngBounds.getNorthWest(), | |
5840 | + latLngBounds.getNorthEast(), | |
5841 | + latLngBounds.getSouthEast() | |
5842 | + ]; | |
5843 | + } | |
5844 | +}); | |
5845 | + | |
5846 | +L.rectangle = function (latLngBounds, options) { | |
5847 | + return new L.Rectangle(latLngBounds, options); | |
5848 | +}; | |
5849 | + | |
5850 | + | |
5851 | +/* | |
5852 | + * L.Circle is a circle overlay (with a certain radius in meters). | |
5853 | + */ | |
5854 | + | |
5855 | +L.Circle = L.Path.extend({ | |
5856 | + initialize: function (latlng, radius, options) { | |
5857 | + L.Path.prototype.initialize.call(this, options); | |
5858 | + | |
5859 | + this._latlng = L.latLng(latlng); | |
5860 | + this._mRadius = radius; | |
5861 | + }, | |
5862 | + | |
5863 | + options: { | |
5864 | + fill: true | |
5865 | + }, | |
5866 | + | |
5867 | + setLatLng: function (latlng) { | |
5868 | + this._latlng = L.latLng(latlng); | |
5869 | + return this.redraw(); | |
5870 | + }, | |
5871 | + | |
5872 | + setRadius: function (radius) { | |
5873 | + this._mRadius = radius; | |
5874 | + return this.redraw(); | |
5875 | + }, | |
5876 | + | |
5877 | + projectLatlngs: function () { | |
5878 | + var lngRadius = this._getLngRadius(), | |
5879 | + latlng = this._latlng, | |
5880 | + pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]); | |
5881 | + | |
5882 | + this._point = this._map.latLngToLayerPoint(latlng); | |
5883 | + this._radius = Math.max(this._point.x - pointLeft.x, 1); | |
5884 | + }, | |
5885 | + | |
5886 | + getBounds: function () { | |
5887 | + var lngRadius = this._getLngRadius(), | |
5888 | + latRadius = (this._mRadius / 40075017) * 360, | |
5889 | + latlng = this._latlng; | |
5890 | + | |
5891 | + return new L.LatLngBounds( | |
5892 | + [latlng.lat - latRadius, latlng.lng - lngRadius], | |
5893 | + [latlng.lat + latRadius, latlng.lng + lngRadius]); | |
5894 | + }, | |
5895 | + | |
5896 | + getLatLng: function () { | |
5897 | + return this._latlng; | |
5898 | + }, | |
5899 | + | |
5900 | + getPathString: function () { | |
5901 | + var p = this._point, | |
5902 | + r = this._radius; | |
5903 | + | |
5904 | + if (this._checkIfEmpty()) { | |
5905 | + return ''; | |
5906 | + } | |
5907 | + | |
5908 | + if (L.Browser.svg) { | |
5909 | + return 'M' + p.x + ',' + (p.y - r) + | |
5910 | + 'A' + r + ',' + r + ',0,1,1,' + | |
5911 | + (p.x - 0.1) + ',' + (p.y - r) + ' z'; | |
5912 | + } else { | |
5913 | + p._round(); | |
5914 | + r = Math.round(r); | |
5915 | + return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360); | |
5916 | + } | |
5917 | + }, | |
5918 | + | |
5919 | + getRadius: function () { | |
5920 | + return this._mRadius; | |
5921 | + }, | |
5922 | + | |
5923 | + // TODO Earth hardcoded, move into projection code! | |
5924 | + | |
5925 | + _getLatRadius: function () { | |
5926 | + return (this._mRadius / 40075017) * 360; | |
5927 | + }, | |
5928 | + | |
5929 | + _getLngRadius: function () { | |
5930 | + return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat); | |
5931 | + }, | |
5932 | + | |
5933 | + _checkIfEmpty: function () { | |
5934 | + if (!this._map) { | |
5935 | + return false; | |
5936 | + } | |
5937 | + var vp = this._map._pathViewport, | |
5938 | + r = this._radius, | |
5939 | + p = this._point; | |
5940 | + | |
5941 | + return p.x - r > vp.max.x || p.y - r > vp.max.y || | |
5942 | + p.x + r < vp.min.x || p.y + r < vp.min.y; | |
5943 | + } | |
5944 | +}); | |
5945 | + | |
5946 | +L.circle = function (latlng, radius, options) { | |
5947 | + return new L.Circle(latlng, radius, options); | |
5948 | +}; | |
5949 | + | |
5950 | + | |
5951 | +/* | |
5952 | + * L.CircleMarker is a circle overlay with a permanent pixel radius. | |
5953 | + */ | |
5954 | + | |
5955 | +L.CircleMarker = L.Circle.extend({ | |
5956 | + options: { | |
5957 | + radius: 10, | |
5958 | + weight: 2 | |
5959 | + }, | |
5960 | + | |
5961 | + initialize: function (latlng, options) { | |
5962 | + L.Circle.prototype.initialize.call(this, latlng, null, options); | |
5963 | + this._radius = this.options.radius; | |
5964 | + }, | |
5965 | + | |
5966 | + projectLatlngs: function () { | |
5967 | + this._point = this._map.latLngToLayerPoint(this._latlng); | |
5968 | + }, | |
5969 | + | |
5970 | + _updateStyle : function () { | |
5971 | + L.Circle.prototype._updateStyle.call(this); | |
5972 | + this.setRadius(this.options.radius); | |
5973 | + }, | |
5974 | + | |
5975 | + setLatLng: function (latlng) { | |
5976 | + L.Circle.prototype.setLatLng.call(this, latlng); | |
5977 | + if (this._popup && this._popup._isOpen) { | |
5978 | + this._popup.setLatLng(latlng); | |
5979 | + } | |
5980 | + return this; | |
5981 | + }, | |
5982 | + | |
5983 | + setRadius: function (radius) { | |
5984 | + this.options.radius = this._radius = radius; | |
5985 | + return this.redraw(); | |
5986 | + }, | |
5987 | + | |
5988 | + getRadius: function () { | |
5989 | + return this._radius; | |
5990 | + } | |
5991 | +}); | |
5992 | + | |
5993 | +L.circleMarker = function (latlng, options) { | |
5994 | + return new L.CircleMarker(latlng, options); | |
5995 | +}; | |
5996 | + | |
5997 | + | |
5998 | +/* | |
5999 | + * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines. | |
6000 | + */ | |
6001 | + | |
6002 | +L.Polyline.include(!L.Path.CANVAS ? {} : { | |
6003 | + _containsPoint: function (p, closed) { | |
6004 | + var i, j, k, len, len2, dist, part, | |
6005 | + w = this.options.weight / 2; | |
6006 | + | |
6007 | + if (L.Browser.touch) { | |
6008 | + w += 10; // polyline click tolerance on touch devices | |
6009 | + } | |
6010 | + | |
6011 | + for (i = 0, len = this._parts.length; i < len; i++) { | |
6012 | + part = this._parts[i]; | |
6013 | + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { | |
6014 | + if (!closed && (j === 0)) { | |
6015 | + continue; | |
6016 | + } | |
6017 | + | |
6018 | + dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]); | |
6019 | + | |
6020 | + if (dist <= w) { | |
6021 | + return true; | |
6022 | + } | |
6023 | + } | |
6024 | + } | |
6025 | + return false; | |
6026 | + } | |
6027 | +}); | |
6028 | + | |
6029 | + | |
6030 | +/* | |
6031 | + * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons. | |
6032 | + */ | |
6033 | + | |
6034 | +L.Polygon.include(!L.Path.CANVAS ? {} : { | |
6035 | + _containsPoint: function (p) { | |
6036 | + var inside = false, | |
6037 | + part, p1, p2, | |
6038 | + i, j, k, | |
6039 | + len, len2; | |
6040 | + | |
6041 | + // TODO optimization: check if within bounds first | |
6042 | + | |
6043 | + if (L.Polyline.prototype._containsPoint.call(this, p, true)) { | |
6044 | + // click on polygon border | |
6045 | + return true; | |
6046 | + } | |
6047 | + | |
6048 | + // ray casting algorithm for detecting if point is in polygon | |
6049 | + | |
6050 | + for (i = 0, len = this._parts.length; i < len; i++) { | |
6051 | + part = this._parts[i]; | |
6052 | + | |
6053 | + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { | |
6054 | + p1 = part[j]; | |
6055 | + p2 = part[k]; | |
6056 | + | |
6057 | + if (((p1.y > p.y) !== (p2.y > p.y)) && | |
6058 | + (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { | |
6059 | + inside = !inside; | |
6060 | + } | |
6061 | + } | |
6062 | + } | |
6063 | + | |
6064 | + return inside; | |
6065 | + } | |
6066 | +}); | |
6067 | + | |
6068 | + | |
6069 | +/* | |
6070 | + * Extends L.Circle with Canvas-specific code. | |
6071 | + */ | |
6072 | + | |
6073 | +L.Circle.include(!L.Path.CANVAS ? {} : { | |
6074 | + _drawPath: function () { | |
6075 | + var p = this._point; | |
6076 | + this._ctx.beginPath(); | |
6077 | + this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false); | |
6078 | + }, | |
6079 | + | |
6080 | + _containsPoint: function (p) { | |
6081 | + var center = this._point, | |
6082 | + w2 = this.options.stroke ? this.options.weight / 2 : 0; | |
6083 | + | |
6084 | + return (p.distanceTo(center) <= this._radius + w2); | |
6085 | + } | |
6086 | +}); | |
6087 | + | |
6088 | + | |
6089 | +/* | |
6090 | + * CircleMarker canvas specific drawing parts. | |
6091 | + */ | |
6092 | + | |
6093 | +L.CircleMarker.include(!L.Path.CANVAS ? {} : { | |
6094 | + _updateStyle: function () { | |
6095 | + L.Path.prototype._updateStyle.call(this); | |
6096 | + } | |
6097 | +}); | |
6098 | + | |
6099 | + | |
6100 | +/* | |
6101 | + * L.GeoJSON turns any GeoJSON data into a Leaflet layer. | |
6102 | + */ | |
6103 | + | |
6104 | +L.GeoJSON = L.FeatureGroup.extend({ | |
6105 | + | |
6106 | + initialize: function (geojson, options) { | |
6107 | + L.setOptions(this, options); | |
6108 | + | |
6109 | + this._layers = {}; | |
6110 | + | |
6111 | + if (geojson) { | |
6112 | + this.addData(geojson); | |
6113 | + } | |
6114 | + }, | |
6115 | + | |
6116 | + addData: function (geojson) { | |
6117 | + var features = L.Util.isArray(geojson) ? geojson : geojson.features, | |
6118 | + i, len, feature; | |
6119 | + | |
6120 | + if (features) { | |
6121 | + for (i = 0, len = features.length; i < len; i++) { | |
6122 | + // Only add this if geometry or geometries are set and not null | |
6123 | + feature = features[i]; | |
6124 | + if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { | |
6125 | + this.addData(features[i]); | |
6126 | + } | |
6127 | + } | |
6128 | + return this; | |
6129 | + } | |
6130 | + | |
6131 | + var options = this.options; | |
6132 | + | |
6133 | + if (options.filter && !options.filter(geojson)) { return; } | |
6134 | + | |
6135 | + var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options); | |
6136 | + layer.feature = L.GeoJSON.asFeature(geojson); | |
6137 | + | |
6138 | + layer.defaultOptions = layer.options; | |
6139 | + this.resetStyle(layer); | |
6140 | + | |
6141 | + if (options.onEachFeature) { | |
6142 | + options.onEachFeature(geojson, layer); | |
6143 | + } | |
6144 | + | |
6145 | + return this.addLayer(layer); | |
6146 | + }, | |
6147 | + | |
6148 | + resetStyle: function (layer) { | |
6149 | + var style = this.options.style; | |
6150 | + if (style) { | |
6151 | + // reset any custom styles | |
6152 | + L.Util.extend(layer.options, layer.defaultOptions); | |
6153 | + | |
6154 | + this._setLayerStyle(layer, style); | |
6155 | + } | |
6156 | + }, | |
6157 | + | |
6158 | + setStyle: function (style) { | |
6159 | + this.eachLayer(function (layer) { | |
6160 | + this._setLayerStyle(layer, style); | |
6161 | + }, this); | |
6162 | + }, | |
6163 | + | |
6164 | + _setLayerStyle: function (layer, style) { | |
6165 | + if (typeof style === 'function') { | |
6166 | + style = style(layer.feature); | |
6167 | + } | |
6168 | + if (layer.setStyle) { | |
6169 | + layer.setStyle(style); | |
6170 | + } | |
6171 | + } | |
6172 | +}); | |
6173 | + | |
6174 | +L.extend(L.GeoJSON, { | |
6175 | + geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) { | |
6176 | + var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, | |
6177 | + coords = geometry.coordinates, | |
6178 | + layers = [], | |
6179 | + latlng, latlngs, i, len; | |
6180 | + | |
6181 | + coordsToLatLng = coordsToLatLng || this.coordsToLatLng; | |
6182 | + | |
6183 | + switch (geometry.type) { | |
6184 | + case 'Point': | |
6185 | + latlng = coordsToLatLng(coords); | |
6186 | + return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); | |
6187 | + | |
6188 | + case 'MultiPoint': | |
6189 | + for (i = 0, len = coords.length; i < len; i++) { | |
6190 | + latlng = coordsToLatLng(coords[i]); | |
6191 | + layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); | |
6192 | + } | |
6193 | + return new L.FeatureGroup(layers); | |
6194 | + | |
6195 | + case 'LineString': | |
6196 | + latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng); | |
6197 | + return new L.Polyline(latlngs, vectorOptions); | |
6198 | + | |
6199 | + case 'Polygon': | |
6200 | + if (coords.length === 2 && !coords[1].length) { | |
6201 | + throw new Error('Invalid GeoJSON object.'); | |
6202 | + } | |
6203 | + latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); | |
6204 | + return new L.Polygon(latlngs, vectorOptions); | |
6205 | + | |
6206 | + case 'MultiLineString': | |
6207 | + latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); | |
6208 | + return new L.MultiPolyline(latlngs, vectorOptions); | |
6209 | + | |
6210 | + case 'MultiPolygon': | |
6211 | + latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng); | |
6212 | + return new L.MultiPolygon(latlngs, vectorOptions); | |
6213 | + | |
6214 | + case 'GeometryCollection': | |
6215 | + for (i = 0, len = geometry.geometries.length; i < len; i++) { | |
6216 | + | |
6217 | + layers.push(this.geometryToLayer({ | |
6218 | + geometry: geometry.geometries[i], | |
6219 | + type: 'Feature', | |
6220 | + properties: geojson.properties | |
6221 | + }, pointToLayer, coordsToLatLng, vectorOptions)); | |
6222 | + } | |
6223 | + return new L.FeatureGroup(layers); | |
6224 | + | |
6225 | + default: | |
6226 | + throw new Error('Invalid GeoJSON object.'); | |
6227 | + } | |
6228 | + }, | |
6229 | + | |
6230 | + coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng | |
6231 | + return new L.LatLng(coords[1], coords[0], coords[2]); | |
6232 | + }, | |
6233 | + | |
6234 | + coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array | |
6235 | + var latlng, i, len, | |
6236 | + latlngs = []; | |
6237 | + | |
6238 | + for (i = 0, len = coords.length; i < len; i++) { | |
6239 | + latlng = levelsDeep ? | |
6240 | + this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : | |
6241 | + (coordsToLatLng || this.coordsToLatLng)(coords[i]); | |
6242 | + | |
6243 | + latlngs.push(latlng); | |
6244 | + } | |
6245 | + | |
6246 | + return latlngs; | |
6247 | + }, | |
6248 | + | |
6249 | + latLngToCoords: function (latlng) { | |
6250 | + var coords = [latlng.lng, latlng.lat]; | |
6251 | + | |
6252 | + if (latlng.alt !== undefined) { | |
6253 | + coords.push(latlng.alt); | |
6254 | + } | |
6255 | + return coords; | |
6256 | + }, | |
6257 | + | |
6258 | + latLngsToCoords: function (latLngs) { | |
6259 | + var coords = []; | |
6260 | + | |
6261 | + for (var i = 0, len = latLngs.length; i < len; i++) { | |
6262 | + coords.push(L.GeoJSON.latLngToCoords(latLngs[i])); | |
6263 | + } | |
6264 | + | |
6265 | + return coords; | |
6266 | + }, | |
6267 | + | |
6268 | + getFeature: function (layer, newGeometry) { | |
6269 | + return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry); | |
6270 | + }, | |
6271 | + | |
6272 | + asFeature: function (geoJSON) { | |
6273 | + if (geoJSON.type === 'Feature') { | |
6274 | + return geoJSON; | |
6275 | + } | |
6276 | + | |
6277 | + return { | |
6278 | + type: 'Feature', | |
6279 | + properties: {}, | |
6280 | + geometry: geoJSON | |
6281 | + }; | |
6282 | + } | |
6283 | +}); | |
6284 | + | |
6285 | +var PointToGeoJSON = { | |
6286 | + toGeoJSON: function () { | |
6287 | + return L.GeoJSON.getFeature(this, { | |
6288 | + type: 'Point', | |
6289 | + coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) | |
6290 | + }); | |
6291 | + } | |
6292 | +}; | |
6293 | + | |
6294 | +L.Marker.include(PointToGeoJSON); | |
6295 | +L.Circle.include(PointToGeoJSON); | |
6296 | +L.CircleMarker.include(PointToGeoJSON); | |
6297 | + | |
6298 | +L.Polyline.include({ | |
6299 | + toGeoJSON: function () { | |
6300 | + return L.GeoJSON.getFeature(this, { | |
6301 | + type: 'LineString', | |
6302 | + coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs()) | |
6303 | + }); | |
6304 | + } | |
6305 | +}); | |
6306 | + | |
6307 | +L.Polygon.include({ | |
6308 | + toGeoJSON: function () { | |
6309 | + var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())], | |
6310 | + i, len, hole; | |
6311 | + | |
6312 | + coords[0].push(coords[0][0]); | |
6313 | + | |
6314 | + if (this._holes) { | |
6315 | + for (i = 0, len = this._holes.length; i < len; i++) { | |
6316 | + hole = L.GeoJSON.latLngsToCoords(this._holes[i]); | |
6317 | + hole.push(hole[0]); | |
6318 | + coords.push(hole); | |
6319 | + } | |
6320 | + } | |
6321 | + | |
6322 | + return L.GeoJSON.getFeature(this, { | |
6323 | + type: 'Polygon', | |
6324 | + coordinates: coords | |
6325 | + }); | |
6326 | + } | |
6327 | +}); | |
6328 | + | |
6329 | +(function () { | |
6330 | + function multiToGeoJSON(type) { | |
6331 | + return function () { | |
6332 | + var coords = []; | |
6333 | + | |
6334 | + this.eachLayer(function (layer) { | |
6335 | + coords.push(layer.toGeoJSON().geometry.coordinates); | |
6336 | + }); | |
6337 | + | |
6338 | + return L.GeoJSON.getFeature(this, { | |
6339 | + type: type, | |
6340 | + coordinates: coords | |
6341 | + }); | |
6342 | + }; | |
6343 | + } | |
6344 | + | |
6345 | + L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')}); | |
6346 | + L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')}); | |
6347 | + | |
6348 | + L.LayerGroup.include({ | |
6349 | + toGeoJSON: function () { | |
6350 | + | |
6351 | + var geometry = this.feature && this.feature.geometry, | |
6352 | + jsons = [], | |
6353 | + json; | |
6354 | + | |
6355 | + if (geometry && geometry.type === 'MultiPoint') { | |
6356 | + return multiToGeoJSON('MultiPoint').call(this); | |
6357 | + } | |
6358 | + | |
6359 | + var isGeometryCollection = geometry && geometry.type === 'GeometryCollection'; | |
6360 | + | |
6361 | + this.eachLayer(function (layer) { | |
6362 | + if (layer.toGeoJSON) { | |
6363 | + json = layer.toGeoJSON(); | |
6364 | + jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); | |
6365 | + } | |
6366 | + }); | |
6367 | + | |
6368 | + if (isGeometryCollection) { | |
6369 | + return L.GeoJSON.getFeature(this, { | |
6370 | + geometries: jsons, | |
6371 | + type: 'GeometryCollection' | |
6372 | + }); | |
6373 | + } | |
6374 | + | |
6375 | + return { | |
6376 | + type: 'FeatureCollection', | |
6377 | + features: jsons | |
6378 | + }; | |
6379 | + } | |
6380 | + }); | |
6381 | +}()); | |
6382 | + | |
6383 | +L.geoJson = function (geojson, options) { | |
6384 | + return new L.GeoJSON(geojson, options); | |
6385 | +}; | |
6386 | + | |
6387 | + | |
6388 | +/* | |
6389 | + * L.DomEvent contains functions for working with DOM events. | |
6390 | + */ | |
6391 | + | |
6392 | +L.DomEvent = { | |
6393 | + /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */ | |
6394 | + addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object]) | |
6395 | + | |
6396 | + var id = L.stamp(fn), | |
6397 | + key = '_leaflet_' + type + id, | |
6398 | + handler, originalHandler, newType; | |
6399 | + | |
6400 | + if (obj[key]) { return this; } | |
6401 | + | |
6402 | + handler = function (e) { | |
6403 | + return fn.call(context || obj, e || L.DomEvent._getEvent()); | |
6404 | + }; | |
6405 | + | |
6406 | + if (L.Browser.pointer && type.indexOf('touch') === 0) { | |
6407 | + return this.addPointerListener(obj, type, handler, id); | |
6408 | + } | |
6409 | + if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { | |
6410 | + this.addDoubleTapListener(obj, handler, id); | |
6411 | + } | |
6412 | + | |
6413 | + if ('addEventListener' in obj) { | |
6414 | + | |
6415 | + if (type === 'mousewheel') { | |
6416 | + obj.addEventListener('DOMMouseScroll', handler, false); | |
6417 | + obj.addEventListener(type, handler, false); | |
6418 | + | |
6419 | + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { | |
6420 | + | |
6421 | + originalHandler = handler; | |
6422 | + newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout'); | |
6423 | + | |
6424 | + handler = function (e) { | |
6425 | + if (!L.DomEvent._checkMouse(obj, e)) { return; } | |
6426 | + return originalHandler(e); | |
6427 | + }; | |
6428 | + | |
6429 | + obj.addEventListener(newType, handler, false); | |
6430 | + | |
6431 | + } else if (type === 'click' && L.Browser.android) { | |
6432 | + originalHandler = handler; | |
6433 | + handler = function (e) { | |
6434 | + return L.DomEvent._filterClick(e, originalHandler); | |
6435 | + }; | |
6436 | + | |
6437 | + obj.addEventListener(type, handler, false); | |
6438 | + } else { | |
6439 | + obj.addEventListener(type, handler, false); | |
6440 | + } | |
6441 | + | |
6442 | + } else if ('attachEvent' in obj) { | |
6443 | + obj.attachEvent('on' + type, handler); | |
6444 | + } | |
6445 | + | |
6446 | + obj[key] = handler; | |
6447 | + | |
6448 | + return this; | |
6449 | + }, | |
6450 | + | |
6451 | + removeListener: function (obj, type, fn) { // (HTMLElement, String, Function) | |
6452 | + | |
6453 | + var id = L.stamp(fn), | |
6454 | + key = '_leaflet_' + type + id, | |
6455 | + handler = obj[key]; | |
6456 | + | |
6457 | + if (!handler) { return this; } | |
6458 | + | |
6459 | + if (L.Browser.pointer && type.indexOf('touch') === 0) { | |
6460 | + this.removePointerListener(obj, type, id); | |
6461 | + } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { | |
6462 | + this.removeDoubleTapListener(obj, id); | |
6463 | + | |
6464 | + } else if ('removeEventListener' in obj) { | |
6465 | + | |
6466 | + if (type === 'mousewheel') { | |
6467 | + obj.removeEventListener('DOMMouseScroll', handler, false); | |
6468 | + obj.removeEventListener(type, handler, false); | |
6469 | + | |
6470 | + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { | |
6471 | + obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false); | |
6472 | + } else { | |
6473 | + obj.removeEventListener(type, handler, false); | |
6474 | + } | |
6475 | + } else if ('detachEvent' in obj) { | |
6476 | + obj.detachEvent('on' + type, handler); | |
6477 | + } | |
6478 | + | |
6479 | + obj[key] = null; | |
6480 | + | |
6481 | + return this; | |
6482 | + }, | |
6483 | + | |
6484 | + stopPropagation: function (e) { | |
6485 | + | |
6486 | + if (e.stopPropagation) { | |
6487 | + e.stopPropagation(); | |
6488 | + } else { | |
6489 | + e.cancelBubble = true; | |
6490 | + } | |
6491 | + L.DomEvent._skipped(e); | |
6492 | + | |
6493 | + return this; | |
6494 | + }, | |
6495 | + | |
6496 | + disableScrollPropagation: function (el) { | |
6497 | + var stop = L.DomEvent.stopPropagation; | |
6498 | + | |
6499 | + return L.DomEvent | |
6500 | + .on(el, 'mousewheel', stop) | |
6501 | + .on(el, 'MozMousePixelScroll', stop); | |
6502 | + }, | |
6503 | + | |
6504 | + disableClickPropagation: function (el) { | |
6505 | + var stop = L.DomEvent.stopPropagation; | |
6506 | + | |
6507 | + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { | |
6508 | + L.DomEvent.on(el, L.Draggable.START[i], stop); | |
6509 | + } | |
6510 | + | |
6511 | + return L.DomEvent | |
6512 | + .on(el, 'click', L.DomEvent._fakeStop) | |
6513 | + .on(el, 'dblclick', stop); | |
6514 | + }, | |
6515 | + | |
6516 | + preventDefault: function (e) { | |
6517 | + | |
6518 | + if (e.preventDefault) { | |
6519 | + e.preventDefault(); | |
6520 | + } else { | |
6521 | + e.returnValue = false; | |
6522 | + } | |
6523 | + return this; | |
6524 | + }, | |
6525 | + | |
6526 | + stop: function (e) { | |
6527 | + return L.DomEvent | |
6528 | + .preventDefault(e) | |
6529 | + .stopPropagation(e); | |
6530 | + }, | |
6531 | + | |
6532 | + getMousePosition: function (e, container) { | |
6533 | + if (!container) { | |
6534 | + return new L.Point(e.clientX, e.clientY); | |
6535 | + } | |
6536 | + | |
6537 | + var rect = container.getBoundingClientRect(); | |
6538 | + | |
6539 | + return new L.Point( | |
6540 | + e.clientX - rect.left - container.clientLeft, | |
6541 | + e.clientY - rect.top - container.clientTop); | |
6542 | + }, | |
6543 | + | |
6544 | + getWheelDelta: function (e) { | |
6545 | + | |
6546 | + var delta = 0; | |
6547 | + | |
6548 | + if (e.wheelDelta) { | |
6549 | + delta = e.wheelDelta / 120; | |
6550 | + } | |
6551 | + if (e.detail) { | |
6552 | + delta = -e.detail / 3; | |
6553 | + } | |
6554 | + return delta; | |
6555 | + }, | |
6556 | + | |
6557 | + _skipEvents: {}, | |
6558 | + | |
6559 | + _fakeStop: function (e) { | |
6560 | + // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) | |
6561 | + L.DomEvent._skipEvents[e.type] = true; | |
6562 | + }, | |
6563 | + | |
6564 | + _skipped: function (e) { | |
6565 | + var skipped = this._skipEvents[e.type]; | |
6566 | + // reset when checking, as it's only used in map container and propagates outside of the map | |
6567 | + this._skipEvents[e.type] = false; | |
6568 | + return skipped; | |
6569 | + }, | |
6570 | + | |
6571 | + // check if element really left/entered the event target (for mouseenter/mouseleave) | |
6572 | + _checkMouse: function (el, e) { | |
6573 | + | |
6574 | + var related = e.relatedTarget; | |
6575 | + | |
6576 | + if (!related) { return true; } | |
6577 | + | |
6578 | + try { | |
6579 | + while (related && (related !== el)) { | |
6580 | + related = related.parentNode; | |
6581 | + } | |
6582 | + } catch (err) { | |
6583 | + return false; | |
6584 | + } | |
6585 | + return (related !== el); | |
6586 | + }, | |
6587 | + | |
6588 | + _getEvent: function () { // evil magic for IE | |
6589 | + /*jshint noarg:false */ | |
6590 | + var e = window.event; | |
6591 | + if (!e) { | |
6592 | + var caller = arguments.callee.caller; | |
6593 | + while (caller) { | |
6594 | + e = caller['arguments'][0]; | |
6595 | + if (e && window.Event === e.constructor) { | |
6596 | + break; | |
6597 | + } | |
6598 | + caller = caller.caller; | |
6599 | + } | |
6600 | + } | |
6601 | + return e; | |
6602 | + }, | |
6603 | + | |
6604 | + // this is a horrible workaround for a bug in Android where a single touch triggers two click events | |
6605 | + _filterClick: function (e, handler) { | |
6606 | + var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), | |
6607 | + elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); | |
6608 | + | |
6609 | + // are they closer together than 500ms yet more than 100ms? | |
6610 | + // Android typically triggers them ~300ms apart while multiple listeners | |
6611 | + // on the same event should be triggered far faster; | |
6612 | + // or check if click is simulated on the element, and if it is, reject any non-simulated events | |
6613 | + | |
6614 | + if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { | |
6615 | + L.DomEvent.stop(e); | |
6616 | + return; | |
6617 | + } | |
6618 | + L.DomEvent._lastClick = timeStamp; | |
6619 | + | |
6620 | + return handler(e); | |
6621 | + } | |
6622 | +}; | |
6623 | + | |
6624 | +L.DomEvent.on = L.DomEvent.addListener; | |
6625 | +L.DomEvent.off = L.DomEvent.removeListener; | |
6626 | + | |
6627 | + | |
6628 | +/* | |
6629 | + * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too. | |
6630 | + */ | |
6631 | + | |
6632 | +L.Draggable = L.Class.extend({ | |
6633 | + includes: L.Mixin.Events, | |
6634 | + | |
6635 | + statics: { | |
6636 | + START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], | |
6637 | + END: { | |
6638 | + mousedown: 'mouseup', | |
6639 | + touchstart: 'touchend', | |
6640 | + pointerdown: 'touchend', | |
6641 | + MSPointerDown: 'touchend' | |
6642 | + }, | |
6643 | + MOVE: { | |
6644 | + mousedown: 'mousemove', | |
6645 | + touchstart: 'touchmove', | |
6646 | + pointerdown: 'touchmove', | |
6647 | + MSPointerDown: 'touchmove' | |
6648 | + } | |
6649 | + }, | |
6650 | + | |
6651 | + initialize: function (element, dragStartTarget) { | |
6652 | + this._element = element; | |
6653 | + this._dragStartTarget = dragStartTarget || element; | |
6654 | + }, | |
6655 | + | |
6656 | + enable: function () { | |
6657 | + if (this._enabled) { return; } | |
6658 | + | |
6659 | + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { | |
6660 | + L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); | |
6661 | + } | |
6662 | + | |
6663 | + this._enabled = true; | |
6664 | + }, | |
6665 | + | |
6666 | + disable: function () { | |
6667 | + if (!this._enabled) { return; } | |
6668 | + | |
6669 | + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { | |
6670 | + L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); | |
6671 | + } | |
6672 | + | |
6673 | + this._enabled = false; | |
6674 | + this._moved = false; | |
6675 | + }, | |
6676 | + | |
6677 | + _onDown: function (e) { | |
6678 | + this._moved = false; | |
6679 | + | |
6680 | + if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } | |
6681 | + | |
6682 | + L.DomEvent.stopPropagation(e); | |
6683 | + | |
6684 | + if (L.Draggable._disabled) { return; } | |
6685 | + | |
6686 | + L.DomUtil.disableImageDrag(); | |
6687 | + L.DomUtil.disableTextSelection(); | |
6688 | + | |
6689 | + if (this._moving) { return; } | |
6690 | + | |
6691 | + var first = e.touches ? e.touches[0] : e; | |
6692 | + | |
6693 | + this._startPoint = new L.Point(first.clientX, first.clientY); | |
6694 | + this._startPos = this._newPos = L.DomUtil.getPosition(this._element); | |
6695 | + | |
6696 | + L.DomEvent | |
6697 | + .on(document, L.Draggable.MOVE[e.type], this._onMove, this) | |
6698 | + .on(document, L.Draggable.END[e.type], this._onUp, this); | |
6699 | + }, | |
6700 | + | |
6701 | + _onMove: function (e) { | |
6702 | + if (e.touches && e.touches.length > 1) { | |
6703 | + this._moved = true; | |
6704 | + return; | |
6705 | + } | |
6706 | + | |
6707 | + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), | |
6708 | + newPoint = new L.Point(first.clientX, first.clientY), | |
6709 | + offset = newPoint.subtract(this._startPoint); | |
6710 | + | |
6711 | + if (!offset.x && !offset.y) { return; } | |
6712 | + if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; } | |
6713 | + | |
6714 | + L.DomEvent.preventDefault(e); | |
6715 | + | |
6716 | + if (!this._moved) { | |
6717 | + this.fire('dragstart'); | |
6718 | + | |
6719 | + this._moved = true; | |
6720 | + this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); | |
6721 | + | |
6722 | + L.DomUtil.addClass(document.body, 'leaflet-dragging'); | |
6723 | + this._lastTarget = e.target || e.srcElement; | |
6724 | + L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); | |
6725 | + } | |
6726 | + | |
6727 | + this._newPos = this._startPos.add(offset); | |
6728 | + this._moving = true; | |
6729 | + | |
6730 | + L.Util.cancelAnimFrame(this._animRequest); | |
6731 | + this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); | |
6732 | + }, | |
6733 | + | |
6734 | + _updatePosition: function () { | |
6735 | + this.fire('predrag'); | |
6736 | + L.DomUtil.setPosition(this._element, this._newPos); | |
6737 | + this.fire('drag'); | |
6738 | + }, | |
6739 | + | |
6740 | + _onUp: function () { | |
6741 | + L.DomUtil.removeClass(document.body, 'leaflet-dragging'); | |
6742 | + | |
6743 | + if (this._lastTarget) { | |
6744 | + L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); | |
6745 | + this._lastTarget = null; | |
6746 | + } | |
6747 | + | |
6748 | + for (var i in L.Draggable.MOVE) { | |
6749 | + L.DomEvent | |
6750 | + .off(document, L.Draggable.MOVE[i], this._onMove) | |
6751 | + .off(document, L.Draggable.END[i], this._onUp); | |
6752 | + } | |
6753 | + | |
6754 | + L.DomUtil.enableImageDrag(); | |
6755 | + L.DomUtil.enableTextSelection(); | |
6756 | + | |
6757 | + if (this._moved && this._moving) { | |
6758 | + // ensure drag is not fired after dragend | |
6759 | + L.Util.cancelAnimFrame(this._animRequest); | |
6760 | + | |
6761 | + this.fire('dragend', { | |
6762 | + distance: this._newPos.distanceTo(this._startPos) | |
6763 | + }); | |
6764 | + } | |
6765 | + | |
6766 | + this._moving = false; | |
6767 | + } | |
6768 | +}); | |
6769 | + | |
6770 | + | |
6771 | +/* | |
6772 | + L.Handler is a base class for handler classes that are used internally to inject | |
6773 | + interaction features like dragging to classes like Map and Marker. | |
6774 | +*/ | |
6775 | + | |
6776 | +L.Handler = L.Class.extend({ | |
6777 | + initialize: function (map) { | |
6778 | + this._map = map; | |
6779 | + }, | |
6780 | + | |
6781 | + enable: function () { | |
6782 | + if (this._enabled) { return; } | |
6783 | + | |
6784 | + this._enabled = true; | |
6785 | + this.addHooks(); | |
6786 | + }, | |
6787 | + | |
6788 | + disable: function () { | |
6789 | + if (!this._enabled) { return; } | |
6790 | + | |
6791 | + this._enabled = false; | |
6792 | + this.removeHooks(); | |
6793 | + }, | |
6794 | + | |
6795 | + enabled: function () { | |
6796 | + return !!this._enabled; | |
6797 | + } | |
6798 | +}); | |
6799 | + | |
6800 | + | |
6801 | +/* | |
6802 | + * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. | |
6803 | + */ | |
6804 | + | |
6805 | +L.Map.mergeOptions({ | |
6806 | + dragging: true, | |
6807 | + | |
6808 | + inertia: !L.Browser.android23, | |
6809 | + inertiaDeceleration: 3400, // px/s^2 | |
6810 | + inertiaMaxSpeed: Infinity, // px/s | |
6811 | + inertiaThreshold: L.Browser.touch ? 32 : 18, // ms | |
6812 | + easeLinearity: 0.25, | |
6813 | + | |
6814 | + // TODO refactor, move to CRS | |
6815 | + worldCopyJump: false | |
6816 | +}); | |
6817 | + | |
6818 | +L.Map.Drag = L.Handler.extend({ | |
6819 | + addHooks: function () { | |
6820 | + if (!this._draggable) { | |
6821 | + var map = this._map; | |
6822 | + | |
6823 | + this._draggable = new L.Draggable(map._mapPane, map._container); | |
6824 | + | |
6825 | + this._draggable.on({ | |
6826 | + 'dragstart': this._onDragStart, | |
6827 | + 'drag': this._onDrag, | |
6828 | + 'dragend': this._onDragEnd | |
6829 | + }, this); | |
6830 | + | |
6831 | + if (map.options.worldCopyJump) { | |
6832 | + this._draggable.on('predrag', this._onPreDrag, this); | |
6833 | + map.on('viewreset', this._onViewReset, this); | |
6834 | + | |
6835 | + map.whenReady(this._onViewReset, this); | |
6836 | + } | |
6837 | + } | |
6838 | + this._draggable.enable(); | |
6839 | + }, | |
6840 | + | |
6841 | + removeHooks: function () { | |
6842 | + this._draggable.disable(); | |
6843 | + }, | |
6844 | + | |
6845 | + moved: function () { | |
6846 | + return this._draggable && this._draggable._moved; | |
6847 | + }, | |
6848 | + | |
6849 | + _onDragStart: function () { | |
6850 | + var map = this._map; | |
6851 | + | |
6852 | + if (map._panAnim) { | |
6853 | + map._panAnim.stop(); | |
6854 | + } | |
6855 | + | |
6856 | + map | |
6857 | + .fire('movestart') | |
6858 | + .fire('dragstart'); | |
6859 | + | |
6860 | + if (map.options.inertia) { | |
6861 | + this._positions = []; | |
6862 | + this._times = []; | |
6863 | + } | |
6864 | + }, | |
6865 | + | |
6866 | + _onDrag: function () { | |
6867 | + if (this._map.options.inertia) { | |
6868 | + var time = this._lastTime = +new Date(), | |
6869 | + pos = this._lastPos = this._draggable._newPos; | |
6870 | + | |
6871 | + this._positions.push(pos); | |
6872 | + this._times.push(time); | |
6873 | + | |
6874 | + if (time - this._times[0] > 200) { | |
6875 | + this._positions.shift(); | |
6876 | + this._times.shift(); | |
6877 | + } | |
6878 | + } | |
6879 | + | |
6880 | + this._map | |
6881 | + .fire('move') | |
6882 | + .fire('drag'); | |
6883 | + }, | |
6884 | + | |
6885 | + _onViewReset: function () { | |
6886 | + // TODO fix hardcoded Earth values | |
6887 | + var pxCenter = this._map.getSize()._divideBy(2), | |
6888 | + pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); | |
6889 | + | |
6890 | + this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; | |
6891 | + this._worldWidth = this._map.project([0, 180]).x; | |
6892 | + }, | |
6893 | + | |
6894 | + _onPreDrag: function () { | |
6895 | + // TODO refactor to be able to adjust map pane position after zoom | |
6896 | + var worldWidth = this._worldWidth, | |
6897 | + halfWidth = Math.round(worldWidth / 2), | |
6898 | + dx = this._initialWorldOffset, | |
6899 | + x = this._draggable._newPos.x, | |
6900 | + newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, | |
6901 | + newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, | |
6902 | + newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; | |
6903 | + | |
6904 | + this._draggable._newPos.x = newX; | |
6905 | + }, | |
6906 | + | |
6907 | + _onDragEnd: function (e) { | |
6908 | + var map = this._map, | |
6909 | + options = map.options, | |
6910 | + delay = +new Date() - this._lastTime, | |
6911 | + | |
6912 | + noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; | |
6913 | + | |
6914 | + map.fire('dragend', e); | |
6915 | + | |
6916 | + if (noInertia) { | |
6917 | + map.fire('moveend'); | |
6918 | + | |
6919 | + } else { | |
6920 | + | |
6921 | + var direction = this._lastPos.subtract(this._positions[0]), | |
6922 | + duration = (this._lastTime + delay - this._times[0]) / 1000, | |
6923 | + ease = options.easeLinearity, | |
6924 | + | |
6925 | + speedVector = direction.multiplyBy(ease / duration), | |
6926 | + speed = speedVector.distanceTo([0, 0]), | |
6927 | + | |
6928 | + limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), | |
6929 | + limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), | |
6930 | + | |
6931 | + decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), | |
6932 | + offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); | |
6933 | + | |
6934 | + if (!offset.x || !offset.y) { | |
6935 | + map.fire('moveend'); | |
6936 | + | |
6937 | + } else { | |
6938 | + offset = map._limitOffset(offset, map.options.maxBounds); | |
6939 | + | |
6940 | + L.Util.requestAnimFrame(function () { | |
6941 | + map.panBy(offset, { | |
6942 | + duration: decelerationDuration, | |
6943 | + easeLinearity: ease, | |
6944 | + noMoveStart: true | |
6945 | + }); | |
6946 | + }); | |
6947 | + } | |
6948 | + } | |
6949 | + } | |
6950 | +}); | |
6951 | + | |
6952 | +L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); | |
6953 | + | |
6954 | + | |
6955 | +/* | |
6956 | + * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. | |
6957 | + */ | |
6958 | + | |
6959 | +L.Map.mergeOptions({ | |
6960 | + doubleClickZoom: true | |
6961 | +}); | |
6962 | + | |
6963 | +L.Map.DoubleClickZoom = L.Handler.extend({ | |
6964 | + addHooks: function () { | |
6965 | + this._map.on('dblclick', this._onDoubleClick, this); | |
6966 | + }, | |
6967 | + | |
6968 | + removeHooks: function () { | |
6969 | + this._map.off('dblclick', this._onDoubleClick, this); | |
6970 | + }, | |
6971 | + | |
6972 | + _onDoubleClick: function (e) { | |
6973 | + var map = this._map, | |
6974 | + zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1); | |
6975 | + | |
6976 | + if (map.options.doubleClickZoom === 'center') { | |
6977 | + map.setZoom(zoom); | |
6978 | + } else { | |
6979 | + map.setZoomAround(e.containerPoint, zoom); | |
6980 | + } | |
6981 | + } | |
6982 | +}); | |
6983 | + | |
6984 | +L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); | |
6985 | + | |
6986 | + | |
6987 | +/* | |
6988 | + * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. | |
6989 | + */ | |
6990 | + | |
6991 | +L.Map.mergeOptions({ | |
6992 | + scrollWheelZoom: true | |
6993 | +}); | |
6994 | + | |
6995 | +L.Map.ScrollWheelZoom = L.Handler.extend({ | |
6996 | + addHooks: function () { | |
6997 | + L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); | |
6998 | + L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault); | |
6999 | + this._delta = 0; | |
7000 | + }, | |
7001 | + | |
7002 | + removeHooks: function () { | |
7003 | + L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll); | |
7004 | + L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault); | |
7005 | + }, | |
7006 | + | |
7007 | + _onWheelScroll: function (e) { | |
7008 | + var delta = L.DomEvent.getWheelDelta(e); | |
7009 | + | |
7010 | + this._delta += delta; | |
7011 | + this._lastMousePos = this._map.mouseEventToContainerPoint(e); | |
7012 | + | |
7013 | + if (!this._startTime) { | |
7014 | + this._startTime = +new Date(); | |
7015 | + } | |
7016 | + | |
7017 | + var left = Math.max(40 - (+new Date() - this._startTime), 0); | |
7018 | + | |
7019 | + clearTimeout(this._timer); | |
7020 | + this._timer = setTimeout(L.bind(this._performZoom, this), left); | |
7021 | + | |
7022 | + L.DomEvent.preventDefault(e); | |
7023 | + L.DomEvent.stopPropagation(e); | |
7024 | + }, | |
7025 | + | |
7026 | + _performZoom: function () { | |
7027 | + var map = this._map, | |
7028 | + delta = this._delta, | |
7029 | + zoom = map.getZoom(); | |
7030 | + | |
7031 | + delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta); | |
7032 | + delta = Math.max(Math.min(delta, 4), -4); | |
7033 | + delta = map._limitZoom(zoom + delta) - zoom; | |
7034 | + | |
7035 | + this._delta = 0; | |
7036 | + this._startTime = null; | |
7037 | + | |
7038 | + if (!delta) { return; } | |
7039 | + | |
7040 | + if (map.options.scrollWheelZoom === 'center') { | |
7041 | + map.setZoom(zoom + delta); | |
7042 | + } else { | |
7043 | + map.setZoomAround(this._lastMousePos, zoom + delta); | |
7044 | + } | |
7045 | + } | |
7046 | +}); | |
7047 | + | |
7048 | +L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); | |
7049 | + | |
7050 | + | |
7051 | +/* | |
7052 | + * Extends the event handling code with double tap support for mobile browsers. | |
7053 | + */ | |
7054 | + | |
7055 | +L.extend(L.DomEvent, { | |
7056 | + | |
7057 | + _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', | |
7058 | + _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', | |
7059 | + | |
7060 | + // inspired by Zepto touch code by Thomas Fuchs | |
7061 | + addDoubleTapListener: function (obj, handler, id) { | |
7062 | + var last, | |
7063 | + doubleTap = false, | |
7064 | + delay = 250, | |
7065 | + touch, | |
7066 | + pre = '_leaflet_', | |
7067 | + touchstart = this._touchstart, | |
7068 | + touchend = this._touchend, | |
7069 | + trackedTouches = []; | |
7070 | + | |
7071 | + function onTouchStart(e) { | |
7072 | + var count; | |
7073 | + | |
7074 | + if (L.Browser.pointer) { | |
7075 | + trackedTouches.push(e.pointerId); | |
7076 | + count = trackedTouches.length; | |
7077 | + } else { | |
7078 | + count = e.touches.length; | |
7079 | + } | |
7080 | + if (count > 1) { | |
7081 | + return; | |
7082 | + } | |
7083 | + | |
7084 | + var now = Date.now(), | |
7085 | + delta = now - (last || now); | |
7086 | + | |
7087 | + touch = e.touches ? e.touches[0] : e; | |
7088 | + doubleTap = (delta > 0 && delta <= delay); | |
7089 | + last = now; | |
7090 | + } | |
7091 | + | |
7092 | + function onTouchEnd(e) { | |
7093 | + if (L.Browser.pointer) { | |
7094 | + var idx = trackedTouches.indexOf(e.pointerId); | |
7095 | + if (idx === -1) { | |
7096 | + return; | |
7097 | + } | |
7098 | + trackedTouches.splice(idx, 1); | |
7099 | + } | |
7100 | + | |
7101 | + if (doubleTap) { | |
7102 | + if (L.Browser.pointer) { | |
7103 | + // work around .type being readonly with MSPointer* events | |
7104 | + var newTouch = { }, | |
7105 | + prop; | |
7106 | + | |
7107 | + // jshint forin:false | |
7108 | + for (var i in touch) { | |
7109 | + prop = touch[i]; | |
7110 | + if (typeof prop === 'function') { | |
7111 | + newTouch[i] = prop.bind(touch); | |
7112 | + } else { | |
7113 | + newTouch[i] = prop; | |
7114 | + } | |
7115 | + } | |
7116 | + touch = newTouch; | |
7117 | + } | |
7118 | + touch.type = 'dblclick'; | |
7119 | + handler(touch); | |
7120 | + last = null; | |
7121 | + } | |
7122 | + } | |
7123 | + obj[pre + touchstart + id] = onTouchStart; | |
7124 | + obj[pre + touchend + id] = onTouchEnd; | |
7125 | + | |
7126 | + // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen | |
7127 | + // will not come through to us, so we will lose track of how many touches are ongoing | |
7128 | + var endElement = L.Browser.pointer ? document.documentElement : obj; | |
7129 | + | |
7130 | + obj.addEventListener(touchstart, onTouchStart, false); | |
7131 | + endElement.addEventListener(touchend, onTouchEnd, false); | |
7132 | + | |
7133 | + if (L.Browser.pointer) { | |
7134 | + endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false); | |
7135 | + } | |
7136 | + | |
7137 | + return this; | |
7138 | + }, | |
7139 | + | |
7140 | + removeDoubleTapListener: function (obj, id) { | |
7141 | + var pre = '_leaflet_'; | |
7142 | + | |
7143 | + obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); | |
7144 | + (L.Browser.pointer ? document.documentElement : obj).removeEventListener( | |
7145 | + this._touchend, obj[pre + this._touchend + id], false); | |
7146 | + | |
7147 | + if (L.Browser.pointer) { | |
7148 | + document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id], | |
7149 | + false); | |
7150 | + } | |
7151 | + | |
7152 | + return this; | |
7153 | + } | |
7154 | +}); | |
7155 | + | |
7156 | + | |
7157 | +/* | |
7158 | + * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. | |
7159 | + */ | |
7160 | + | |
7161 | +L.extend(L.DomEvent, { | |
7162 | + | |
7163 | + //static | |
7164 | + POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown', | |
7165 | + POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', | |
7166 | + POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', | |
7167 | + POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', | |
7168 | + | |
7169 | + _pointers: [], | |
7170 | + _pointerDocumentListener: false, | |
7171 | + | |
7172 | + // Provides a touch events wrapper for (ms)pointer events. | |
7173 | + // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019 | |
7174 | + //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 | |
7175 | + | |
7176 | + addPointerListener: function (obj, type, handler, id) { | |
7177 | + | |
7178 | + switch (type) { | |
7179 | + case 'touchstart': | |
7180 | + return this.addPointerListenerStart(obj, type, handler, id); | |
7181 | + case 'touchend': | |
7182 | + return this.addPointerListenerEnd(obj, type, handler, id); | |
7183 | + case 'touchmove': | |
7184 | + return this.addPointerListenerMove(obj, type, handler, id); | |
7185 | + default: | |
7186 | + throw 'Unknown touch event type'; | |
7187 | + } | |
7188 | + }, | |
7189 | + | |
7190 | + addPointerListenerStart: function (obj, type, handler, id) { | |
7191 | + var pre = '_leaflet_', | |
7192 | + pointers = this._pointers; | |
7193 | + | |
7194 | + var cb = function (e) { | |
7195 | + | |
7196 | + L.DomEvent.preventDefault(e); | |
7197 | + | |
7198 | + var alreadyInArray = false; | |
7199 | + for (var i = 0; i < pointers.length; i++) { | |
7200 | + if (pointers[i].pointerId === e.pointerId) { | |
7201 | + alreadyInArray = true; | |
7202 | + break; | |
7203 | + } | |
7204 | + } | |
7205 | + if (!alreadyInArray) { | |
7206 | + pointers.push(e); | |
7207 | + } | |
7208 | + | |
7209 | + e.touches = pointers.slice(); | |
7210 | + e.changedTouches = [e]; | |
7211 | + | |
7212 | + handler(e); | |
7213 | + }; | |
7214 | + | |
7215 | + obj[pre + 'touchstart' + id] = cb; | |
7216 | + obj.addEventListener(this.POINTER_DOWN, cb, false); | |
7217 | + | |
7218 | + // need to also listen for end events to keep the _pointers list accurate | |
7219 | + // this needs to be on the body and never go away | |
7220 | + if (!this._pointerDocumentListener) { | |
7221 | + var internalCb = function (e) { | |
7222 | + for (var i = 0; i < pointers.length; i++) { | |
7223 | + if (pointers[i].pointerId === e.pointerId) { | |
7224 | + pointers.splice(i, 1); | |
7225 | + break; | |
7226 | + } | |
7227 | + } | |
7228 | + }; | |
7229 | + //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there | |
7230 | + document.documentElement.addEventListener(this.POINTER_UP, internalCb, false); | |
7231 | + document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false); | |
7232 | + | |
7233 | + this._pointerDocumentListener = true; | |
7234 | + } | |
7235 | + | |
7236 | + return this; | |
7237 | + }, | |
7238 | + | |
7239 | + addPointerListenerMove: function (obj, type, handler, id) { | |
7240 | + var pre = '_leaflet_', | |
7241 | + touches = this._pointers; | |
7242 | + | |
7243 | + function cb(e) { | |
7244 | + | |
7245 | + // don't fire touch moves when mouse isn't down | |
7246 | + if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } | |
7247 | + | |
7248 | + for (var i = 0; i < touches.length; i++) { | |
7249 | + if (touches[i].pointerId === e.pointerId) { | |
7250 | + touches[i] = e; | |
7251 | + break; | |
7252 | + } | |
7253 | + } | |
7254 | + | |
7255 | + e.touches = touches.slice(); | |
7256 | + e.changedTouches = [e]; | |
7257 | + | |
7258 | + handler(e); | |
7259 | + } | |
7260 | + | |
7261 | + obj[pre + 'touchmove' + id] = cb; | |
7262 | + obj.addEventListener(this.POINTER_MOVE, cb, false); | |
7263 | + | |
7264 | + return this; | |
7265 | + }, | |
7266 | + | |
7267 | + addPointerListenerEnd: function (obj, type, handler, id) { | |
7268 | + var pre = '_leaflet_', | |
7269 | + touches = this._pointers; | |
7270 | + | |
7271 | + var cb = function (e) { | |
7272 | + for (var i = 0; i < touches.length; i++) { | |
7273 | + if (touches[i].pointerId === e.pointerId) { | |
7274 | + touches.splice(i, 1); | |
7275 | + break; | |
7276 | + } | |
7277 | + } | |
7278 | + | |
7279 | + e.touches = touches.slice(); | |
7280 | + e.changedTouches = [e]; | |
7281 | + | |
7282 | + handler(e); | |
7283 | + }; | |
7284 | + | |
7285 | + obj[pre + 'touchend' + id] = cb; | |
7286 | + obj.addEventListener(this.POINTER_UP, cb, false); | |
7287 | + obj.addEventListener(this.POINTER_CANCEL, cb, false); | |
7288 | + | |
7289 | + return this; | |
7290 | + }, | |
7291 | + | |
7292 | + removePointerListener: function (obj, type, id) { | |
7293 | + var pre = '_leaflet_', | |
7294 | + cb = obj[pre + type + id]; | |
7295 | + | |
7296 | + switch (type) { | |
7297 | + case 'touchstart': | |
7298 | + obj.removeEventListener(this.POINTER_DOWN, cb, false); | |
7299 | + break; | |
7300 | + case 'touchmove': | |
7301 | + obj.removeEventListener(this.POINTER_MOVE, cb, false); | |
7302 | + break; | |
7303 | + case 'touchend': | |
7304 | + obj.removeEventListener(this.POINTER_UP, cb, false); | |
7305 | + obj.removeEventListener(this.POINTER_CANCEL, cb, false); | |
7306 | + break; | |
7307 | + } | |
7308 | + | |
7309 | + return this; | |
7310 | + } | |
7311 | +}); | |
7312 | + | |
7313 | + | |
7314 | +/* | |
7315 | + * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. | |
7316 | + */ | |
7317 | + | |
7318 | +L.Map.mergeOptions({ | |
7319 | + touchZoom: L.Browser.touch && !L.Browser.android23, | |
7320 | + bounceAtZoomLimits: true | |
7321 | +}); | |
7322 | + | |
7323 | +L.Map.TouchZoom = L.Handler.extend({ | |
7324 | + addHooks: function () { | |
7325 | + L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); | |
7326 | + }, | |
7327 | + | |
7328 | + removeHooks: function () { | |
7329 | + L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); | |
7330 | + }, | |
7331 | + | |
7332 | + _onTouchStart: function (e) { | |
7333 | + var map = this._map; | |
7334 | + | |
7335 | + if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } | |
7336 | + | |
7337 | + var p1 = map.mouseEventToLayerPoint(e.touches[0]), | |
7338 | + p2 = map.mouseEventToLayerPoint(e.touches[1]), | |
7339 | + viewCenter = map._getCenterLayerPoint(); | |
7340 | + | |
7341 | + this._startCenter = p1.add(p2)._divideBy(2); | |
7342 | + this._startDist = p1.distanceTo(p2); | |
7343 | + | |
7344 | + this._moved = false; | |
7345 | + this._zooming = true; | |
7346 | + | |
7347 | + this._centerOffset = viewCenter.subtract(this._startCenter); | |
7348 | + | |
7349 | + if (map._panAnim) { | |
7350 | + map._panAnim.stop(); | |
7351 | + } | |
7352 | + | |
7353 | + L.DomEvent | |
7354 | + .on(document, 'touchmove', this._onTouchMove, this) | |
7355 | + .on(document, 'touchend', this._onTouchEnd, this); | |
7356 | + | |
7357 | + L.DomEvent.preventDefault(e); | |
7358 | + }, | |
7359 | + | |
7360 | + _onTouchMove: function (e) { | |
7361 | + var map = this._map; | |
7362 | + | |
7363 | + if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } | |
7364 | + | |
7365 | + var p1 = map.mouseEventToLayerPoint(e.touches[0]), | |
7366 | + p2 = map.mouseEventToLayerPoint(e.touches[1]); | |
7367 | + | |
7368 | + this._scale = p1.distanceTo(p2) / this._startDist; | |
7369 | + this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter); | |
7370 | + | |
7371 | + if (this._scale === 1) { return; } | |
7372 | + | |
7373 | + if (!map.options.bounceAtZoomLimits) { | |
7374 | + if ((map.getZoom() === map.getMinZoom() && this._scale < 1) || | |
7375 | + (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; } | |
7376 | + } | |
7377 | + | |
7378 | + if (!this._moved) { | |
7379 | + L.DomUtil.addClass(map._mapPane, 'leaflet-touching'); | |
7380 | + | |
7381 | + map | |
7382 | + .fire('movestart') | |
7383 | + .fire('zoomstart'); | |
7384 | + | |
7385 | + this._moved = true; | |
7386 | + } | |
7387 | + | |
7388 | + L.Util.cancelAnimFrame(this._animRequest); | |
7389 | + this._animRequest = L.Util.requestAnimFrame( | |
7390 | + this._updateOnMove, this, true, this._map._container); | |
7391 | + | |
7392 | + L.DomEvent.preventDefault(e); | |
7393 | + }, | |
7394 | + | |
7395 | + _updateOnMove: function () { | |
7396 | + var map = this._map, | |
7397 | + origin = this._getScaleOrigin(), | |
7398 | + center = map.layerPointToLatLng(origin), | |
7399 | + zoom = map.getScaleZoom(this._scale); | |
7400 | + | |
7401 | + map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, false, true); | |
7402 | + }, | |
7403 | + | |
7404 | + _onTouchEnd: function () { | |
7405 | + if (!this._moved || !this._zooming) { | |
7406 | + this._zooming = false; | |
7407 | + return; | |
7408 | + } | |
7409 | + | |
7410 | + var map = this._map; | |
7411 | + | |
7412 | + this._zooming = false; | |
7413 | + L.DomUtil.removeClass(map._mapPane, 'leaflet-touching'); | |
7414 | + L.Util.cancelAnimFrame(this._animRequest); | |
7415 | + | |
7416 | + L.DomEvent | |
7417 | + .off(document, 'touchmove', this._onTouchMove) | |
7418 | + .off(document, 'touchend', this._onTouchEnd); | |
7419 | + | |
7420 | + var origin = this._getScaleOrigin(), | |
7421 | + center = map.layerPointToLatLng(origin), | |
7422 | + | |
7423 | + oldZoom = map.getZoom(), | |
7424 | + floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom, | |
7425 | + roundZoomDelta = (floatZoomDelta > 0 ? | |
7426 | + Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)), | |
7427 | + | |
7428 | + zoom = map._limitZoom(oldZoom + roundZoomDelta), | |
7429 | + scale = map.getZoomScale(zoom) / this._scale; | |
7430 | + | |
7431 | + map._animateZoom(center, zoom, origin, scale); | |
7432 | + }, | |
7433 | + | |
7434 | + _getScaleOrigin: function () { | |
7435 | + var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale); | |
7436 | + return this._startCenter.add(centerOffset); | |
7437 | + } | |
7438 | +}); | |
7439 | + | |
7440 | +L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); | |
7441 | + | |
7442 | + | |
7443 | +/* | |
7444 | + * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. | |
7445 | + */ | |
7446 | + | |
7447 | +L.Map.mergeOptions({ | |
7448 | + tap: true, | |
7449 | + tapTolerance: 15 | |
7450 | +}); | |
7451 | + | |
7452 | +L.Map.Tap = L.Handler.extend({ | |
7453 | + addHooks: function () { | |
7454 | + L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); | |
7455 | + }, | |
7456 | + | |
7457 | + removeHooks: function () { | |
7458 | + L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); | |
7459 | + }, | |
7460 | + | |
7461 | + _onDown: function (e) { | |
7462 | + if (!e.touches) { return; } | |
7463 | + | |
7464 | + L.DomEvent.preventDefault(e); | |
7465 | + | |
7466 | + this._fireClick = true; | |
7467 | + | |
7468 | + // don't simulate click or track longpress if more than 1 touch | |
7469 | + if (e.touches.length > 1) { | |
7470 | + this._fireClick = false; | |
7471 | + clearTimeout(this._holdTimeout); | |
7472 | + return; | |
7473 | + } | |
7474 | + | |
7475 | + var first = e.touches[0], | |
7476 | + el = first.target; | |
7477 | + | |
7478 | + this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); | |
7479 | + | |
7480 | + // if touching a link, highlight it | |
7481 | + if (el.tagName && el.tagName.toLowerCase() === 'a') { | |
7482 | + L.DomUtil.addClass(el, 'leaflet-active'); | |
7483 | + } | |
7484 | + | |
7485 | + // simulate long hold but setting a timeout | |
7486 | + this._holdTimeout = setTimeout(L.bind(function () { | |
7487 | + if (this._isTapValid()) { | |
7488 | + this._fireClick = false; | |
7489 | + this._onUp(); | |
7490 | + this._simulateEvent('contextmenu', first); | |
7491 | + } | |
7492 | + }, this), 1000); | |
7493 | + | |
7494 | + L.DomEvent | |
7495 | + .on(document, 'touchmove', this._onMove, this) | |
7496 | + .on(document, 'touchend', this._onUp, this); | |
7497 | + }, | |
7498 | + | |
7499 | + _onUp: function (e) { | |
7500 | + clearTimeout(this._holdTimeout); | |
7501 | + | |
7502 | + L.DomEvent | |
7503 | + .off(document, 'touchmove', this._onMove, this) | |
7504 | + .off(document, 'touchend', this._onUp, this); | |
7505 | + | |
7506 | + if (this._fireClick && e && e.changedTouches) { | |
7507 | + | |
7508 | + var first = e.changedTouches[0], | |
7509 | + el = first.target; | |
7510 | + | |
7511 | + if (el && el.tagName && el.tagName.toLowerCase() === 'a') { | |
7512 | + L.DomUtil.removeClass(el, 'leaflet-active'); | |
7513 | + } | |
7514 | + | |
7515 | + // simulate click if the touch didn't move too much | |
7516 | + if (this._isTapValid()) { | |
7517 | + this._simulateEvent('click', first); | |
7518 | + } | |
7519 | + } | |
7520 | + }, | |
7521 | + | |
7522 | + _isTapValid: function () { | |
7523 | + return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; | |
7524 | + }, | |
7525 | + | |
7526 | + _onMove: function (e) { | |
7527 | + var first = e.touches[0]; | |
7528 | + this._newPos = new L.Point(first.clientX, first.clientY); | |
7529 | + }, | |
7530 | + | |
7531 | + _simulateEvent: function (type, e) { | |
7532 | + var simulatedEvent = document.createEvent('MouseEvents'); | |
7533 | + | |
7534 | + simulatedEvent._simulated = true; | |
7535 | + e.target._simulatedClick = true; | |
7536 | + | |
7537 | + simulatedEvent.initMouseEvent( | |
7538 | + type, true, true, window, 1, | |
7539 | + e.screenX, e.screenY, | |
7540 | + e.clientX, e.clientY, | |
7541 | + false, false, false, false, 0, null); | |
7542 | + | |
7543 | + e.target.dispatchEvent(simulatedEvent); | |
7544 | + } | |
7545 | +}); | |
7546 | + | |
7547 | +if (L.Browser.touch && !L.Browser.pointer) { | |
7548 | + L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); | |
7549 | +} | |
7550 | + | |
7551 | + | |
7552 | +/* | |
7553 | + * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map | |
7554 | + * (zoom to a selected bounding box), enabled by default. | |
7555 | + */ | |
7556 | + | |
7557 | +L.Map.mergeOptions({ | |
7558 | + boxZoom: true | |
7559 | +}); | |
7560 | + | |
7561 | +L.Map.BoxZoom = L.Handler.extend({ | |
7562 | + initialize: function (map) { | |
7563 | + this._map = map; | |
7564 | + this._container = map._container; | |
7565 | + this._pane = map._panes.overlayPane; | |
7566 | + this._moved = false; | |
7567 | + }, | |
7568 | + | |
7569 | + addHooks: function () { | |
7570 | + L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); | |
7571 | + }, | |
7572 | + | |
7573 | + removeHooks: function () { | |
7574 | + L.DomEvent.off(this._container, 'mousedown', this._onMouseDown); | |
7575 | + this._moved = false; | |
7576 | + }, | |
7577 | + | |
7578 | + moved: function () { | |
7579 | + return this._moved; | |
7580 | + }, | |
7581 | + | |
7582 | + _onMouseDown: function (e) { | |
7583 | + this._moved = false; | |
7584 | + | |
7585 | + if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } | |
7586 | + | |
7587 | + L.DomUtil.disableTextSelection(); | |
7588 | + L.DomUtil.disableImageDrag(); | |
7589 | + | |
7590 | + this._startLayerPoint = this._map.mouseEventToLayerPoint(e); | |
7591 | + | |
7592 | + L.DomEvent | |
7593 | + .on(document, 'mousemove', this._onMouseMove, this) | |
7594 | + .on(document, 'mouseup', this._onMouseUp, this) | |
7595 | + .on(document, 'keydown', this._onKeyDown, this); | |
7596 | + }, | |
7597 | + | |
7598 | + _onMouseMove: function (e) { | |
7599 | + if (!this._moved) { | |
7600 | + this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); | |
7601 | + L.DomUtil.setPosition(this._box, this._startLayerPoint); | |
7602 | + | |
7603 | + //TODO refactor: move cursor to styles | |
7604 | + this._container.style.cursor = 'crosshair'; | |
7605 | + this._map.fire('boxzoomstart'); | |
7606 | + } | |
7607 | + | |
7608 | + var startPoint = this._startLayerPoint, | |
7609 | + box = this._box, | |
7610 | + | |
7611 | + layerPoint = this._map.mouseEventToLayerPoint(e), | |
7612 | + offset = layerPoint.subtract(startPoint), | |
7613 | + | |
7614 | + newPos = new L.Point( | |
7615 | + Math.min(layerPoint.x, startPoint.x), | |
7616 | + Math.min(layerPoint.y, startPoint.y)); | |
7617 | + | |
7618 | + L.DomUtil.setPosition(box, newPos); | |
7619 | + | |
7620 | + this._moved = true; | |
7621 | + | |
7622 | + // TODO refactor: remove hardcoded 4 pixels | |
7623 | + box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px'; | |
7624 | + box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px'; | |
7625 | + }, | |
7626 | + | |
7627 | + _finish: function () { | |
7628 | + if (this._moved) { | |
7629 | + this._pane.removeChild(this._box); | |
7630 | + this._container.style.cursor = ''; | |
7631 | + } | |
7632 | + | |
7633 | + L.DomUtil.enableTextSelection(); | |
7634 | + L.DomUtil.enableImageDrag(); | |
7635 | + | |
7636 | + L.DomEvent | |
7637 | + .off(document, 'mousemove', this._onMouseMove) | |
7638 | + .off(document, 'mouseup', this._onMouseUp) | |
7639 | + .off(document, 'keydown', this._onKeyDown); | |
7640 | + }, | |
7641 | + | |
7642 | + _onMouseUp: function (e) { | |
7643 | + | |
7644 | + this._finish(); | |
7645 | + | |
7646 | + var map = this._map, | |
7647 | + layerPoint = map.mouseEventToLayerPoint(e); | |
7648 | + | |
7649 | + if (this._startLayerPoint.equals(layerPoint)) { return; } | |
7650 | + | |
7651 | + var bounds = new L.LatLngBounds( | |
7652 | + map.layerPointToLatLng(this._startLayerPoint), | |
7653 | + map.layerPointToLatLng(layerPoint)); | |
7654 | + | |
7655 | + map.fitBounds(bounds); | |
7656 | + | |
7657 | + map.fire('boxzoomend', { | |
7658 | + boxZoomBounds: bounds | |
7659 | + }); | |
7660 | + }, | |
7661 | + | |
7662 | + _onKeyDown: function (e) { | |
7663 | + if (e.keyCode === 27) { | |
7664 | + this._finish(); | |
7665 | + } | |
7666 | + } | |
7667 | +}); | |
7668 | + | |
7669 | +L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); | |
7670 | + | |
7671 | + | |
7672 | +/* | |
7673 | + * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. | |
7674 | + */ | |
7675 | + | |
7676 | +L.Map.mergeOptions({ | |
7677 | + keyboard: true, | |
7678 | + keyboardPanOffset: 80, | |
7679 | + keyboardZoomOffset: 1 | |
7680 | +}); | |
7681 | + | |
7682 | +L.Map.Keyboard = L.Handler.extend({ | |
7683 | + | |
7684 | + keyCodes: { | |
7685 | + left: [37], | |
7686 | + right: [39], | |
7687 | + down: [40], | |
7688 | + up: [38], | |
7689 | + zoomIn: [187, 107, 61, 171], | |
7690 | + zoomOut: [189, 109, 173] | |
7691 | + }, | |
7692 | + | |
7693 | + initialize: function (map) { | |
7694 | + this._map = map; | |
7695 | + | |
7696 | + this._setPanOffset(map.options.keyboardPanOffset); | |
7697 | + this._setZoomOffset(map.options.keyboardZoomOffset); | |
7698 | + }, | |
7699 | + | |
7700 | + addHooks: function () { | |
7701 | + var container = this._map._container; | |
7702 | + | |
7703 | + // make the container focusable by tabbing | |
7704 | + if (container.tabIndex === -1) { | |
7705 | + container.tabIndex = '0'; | |
7706 | + } | |
7707 | + | |
7708 | + L.DomEvent | |
7709 | + .on(container, 'focus', this._onFocus, this) | |
7710 | + .on(container, 'blur', this._onBlur, this) | |
7711 | + .on(container, 'mousedown', this._onMouseDown, this); | |
7712 | + | |
7713 | + this._map | |
7714 | + .on('focus', this._addHooks, this) | |
7715 | + .on('blur', this._removeHooks, this); | |
7716 | + }, | |
7717 | + | |
7718 | + removeHooks: function () { | |
7719 | + this._removeHooks(); | |
7720 | + | |
7721 | + var container = this._map._container; | |
7722 | + | |
7723 | + L.DomEvent | |
7724 | + .off(container, 'focus', this._onFocus, this) | |
7725 | + .off(container, 'blur', this._onBlur, this) | |
7726 | + .off(container, 'mousedown', this._onMouseDown, this); | |
7727 | + | |
7728 | + this._map | |
7729 | + .off('focus', this._addHooks, this) | |
7730 | + .off('blur', this._removeHooks, this); | |
7731 | + }, | |
7732 | + | |
7733 | + _onMouseDown: function () { | |
7734 | + if (this._focused) { return; } | |
7735 | + | |
7736 | + var body = document.body, | |
7737 | + docEl = document.documentElement, | |
7738 | + top = body.scrollTop || docEl.scrollTop, | |
7739 | + left = body.scrollLeft || docEl.scrollLeft; | |
7740 | + | |
7741 | + this._map._container.focus(); | |
7742 | + | |
7743 | + window.scrollTo(left, top); | |
7744 | + }, | |
7745 | + | |
7746 | + _onFocus: function () { | |
7747 | + this._focused = true; | |
7748 | + this._map.fire('focus'); | |
7749 | + }, | |
7750 | + | |
7751 | + _onBlur: function () { | |
7752 | + this._focused = false; | |
7753 | + this._map.fire('blur'); | |
7754 | + }, | |
7755 | + | |
7756 | + _setPanOffset: function (pan) { | |
7757 | + var keys = this._panKeys = {}, | |
7758 | + codes = this.keyCodes, | |
7759 | + i, len; | |
7760 | + | |
7761 | + for (i = 0, len = codes.left.length; i < len; i++) { | |
7762 | + keys[codes.left[i]] = [-1 * pan, 0]; | |
7763 | + } | |
7764 | + for (i = 0, len = codes.right.length; i < len; i++) { | |
7765 | + keys[codes.right[i]] = [pan, 0]; | |
7766 | + } | |
7767 | + for (i = 0, len = codes.down.length; i < len; i++) { | |
7768 | + keys[codes.down[i]] = [0, pan]; | |
7769 | + } | |
7770 | + for (i = 0, len = codes.up.length; i < len; i++) { | |
7771 | + keys[codes.up[i]] = [0, -1 * pan]; | |
7772 | + } | |
7773 | + }, | |
7774 | + | |
7775 | + _setZoomOffset: function (zoom) { | |
7776 | + var keys = this._zoomKeys = {}, | |
7777 | + codes = this.keyCodes, | |
7778 | + i, len; | |
7779 | + | |
7780 | + for (i = 0, len = codes.zoomIn.length; i < len; i++) { | |
7781 | + keys[codes.zoomIn[i]] = zoom; | |
7782 | + } | |
7783 | + for (i = 0, len = codes.zoomOut.length; i < len; i++) { | |
7784 | + keys[codes.zoomOut[i]] = -zoom; | |
7785 | + } | |
7786 | + }, | |
7787 | + | |
7788 | + _addHooks: function () { | |
7789 | + L.DomEvent.on(document, 'keydown', this._onKeyDown, this); | |
7790 | + }, | |
7791 | + | |
7792 | + _removeHooks: function () { | |
7793 | + L.DomEvent.off(document, 'keydown', this._onKeyDown, this); | |
7794 | + }, | |
7795 | + | |
7796 | + _onKeyDown: function (e) { | |
7797 | + var key = e.keyCode, | |
7798 | + map = this._map; | |
7799 | + | |
7800 | + if (key in this._panKeys) { | |
7801 | + | |
7802 | + if (map._panAnim && map._panAnim._inProgress) { return; } | |
7803 | + | |
7804 | + map.panBy(this._panKeys[key]); | |
7805 | + | |
7806 | + if (map.options.maxBounds) { | |
7807 | + map.panInsideBounds(map.options.maxBounds); | |
7808 | + } | |
7809 | + | |
7810 | + } else if (key in this._zoomKeys) { | |
7811 | + map.setZoom(map.getZoom() + this._zoomKeys[key]); | |
7812 | + | |
7813 | + } else { | |
7814 | + return; | |
7815 | + } | |
7816 | + | |
7817 | + L.DomEvent.stop(e); | |
7818 | + } | |
7819 | +}); | |
7820 | + | |
7821 | +L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); | |
7822 | + | |
7823 | + | |
7824 | +/* | |
7825 | + * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. | |
7826 | + */ | |
7827 | + | |
7828 | +L.Handler.MarkerDrag = L.Handler.extend({ | |
7829 | + initialize: function (marker) { | |
7830 | + this._marker = marker; | |
7831 | + }, | |
7832 | + | |
7833 | + addHooks: function () { | |
7834 | + var icon = this._marker._icon; | |
7835 | + if (!this._draggable) { | |
7836 | + this._draggable = new L.Draggable(icon, icon); | |
7837 | + } | |
7838 | + | |
7839 | + this._draggable | |
7840 | + .on('dragstart', this._onDragStart, this) | |
7841 | + .on('drag', this._onDrag, this) | |
7842 | + .on('dragend', this._onDragEnd, this); | |
7843 | + this._draggable.enable(); | |
7844 | + L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable'); | |
7845 | + }, | |
7846 | + | |
7847 | + removeHooks: function () { | |
7848 | + this._draggable | |
7849 | + .off('dragstart', this._onDragStart, this) | |
7850 | + .off('drag', this._onDrag, this) | |
7851 | + .off('dragend', this._onDragEnd, this); | |
7852 | + | |
7853 | + this._draggable.disable(); | |
7854 | + L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); | |
7855 | + }, | |
7856 | + | |
7857 | + moved: function () { | |
7858 | + return this._draggable && this._draggable._moved; | |
7859 | + }, | |
7860 | + | |
7861 | + _onDragStart: function () { | |
7862 | + this._marker | |
7863 | + .closePopup() | |
7864 | + .fire('movestart') | |
7865 | + .fire('dragstart'); | |
7866 | + }, | |
7867 | + | |
7868 | + _onDrag: function () { | |
7869 | + var marker = this._marker, | |
7870 | + shadow = marker._shadow, | |
7871 | + iconPos = L.DomUtil.getPosition(marker._icon), | |
7872 | + latlng = marker._map.layerPointToLatLng(iconPos); | |
7873 | + | |
7874 | + // update shadow position | |
7875 | + if (shadow) { | |
7876 | + L.DomUtil.setPosition(shadow, iconPos); | |
7877 | + } | |
7878 | + | |
7879 | + marker._latlng = latlng; | |
7880 | + | |
7881 | + marker | |
7882 | + .fire('move', {latlng: latlng}) | |
7883 | + .fire('drag'); | |
7884 | + }, | |
7885 | + | |
7886 | + _onDragEnd: function (e) { | |
7887 | + this._marker | |
7888 | + .fire('moveend') | |
7889 | + .fire('dragend', e); | |
7890 | + } | |
7891 | +}); | |
7892 | + | |
7893 | + | |
7894 | +/* | |
7895 | + * L.Control is a base class for implementing map controls. Handles positioning. | |
7896 | + * All other controls extend from this class. | |
7897 | + */ | |
7898 | + | |
7899 | +L.Control = L.Class.extend({ | |
7900 | + options: { | |
7901 | + position: 'topright' | |
7902 | + }, | |
7903 | + | |
7904 | + initialize: function (options) { | |
7905 | + L.setOptions(this, options); | |
7906 | + }, | |
7907 | + | |
7908 | + getPosition: function () { | |
7909 | + return this.options.position; | |
7910 | + }, | |
7911 | + | |
7912 | + setPosition: function (position) { | |
7913 | + var map = this._map; | |
7914 | + | |
7915 | + if (map) { | |
7916 | + map.removeControl(this); | |
7917 | + } | |
7918 | + | |
7919 | + this.options.position = position; | |
7920 | + | |
7921 | + if (map) { | |
7922 | + map.addControl(this); | |
7923 | + } | |
7924 | + | |
7925 | + return this; | |
7926 | + }, | |
7927 | + | |
7928 | + getContainer: function () { | |
7929 | + return this._container; | |
7930 | + }, | |
7931 | + | |
7932 | + addTo: function (map) { | |
7933 | + this._map = map; | |
7934 | + | |
7935 | + var container = this._container = this.onAdd(map), | |
7936 | + pos = this.getPosition(), | |
7937 | + corner = map._controlCorners[pos]; | |
7938 | + | |
7939 | + L.DomUtil.addClass(container, 'leaflet-control'); | |
7940 | + | |
7941 | + if (pos.indexOf('bottom') !== -1) { | |
7942 | + corner.insertBefore(container, corner.firstChild); | |
7943 | + } else { | |
7944 | + corner.appendChild(container); | |
7945 | + } | |
7946 | + | |
7947 | + return this; | |
7948 | + }, | |
7949 | + | |
7950 | + removeFrom: function (map) { | |
7951 | + var pos = this.getPosition(), | |
7952 | + corner = map._controlCorners[pos]; | |
7953 | + | |
7954 | + corner.removeChild(this._container); | |
7955 | + this._map = null; | |
7956 | + | |
7957 | + if (this.onRemove) { | |
7958 | + this.onRemove(map); | |
7959 | + } | |
7960 | + | |
7961 | + return this; | |
7962 | + }, | |
7963 | + | |
7964 | + _refocusOnMap: function () { | |
7965 | + if (this._map) { | |
7966 | + this._map.getContainer().focus(); | |
7967 | + } | |
7968 | + } | |
7969 | +}); | |
7970 | + | |
7971 | +L.control = function (options) { | |
7972 | + return new L.Control(options); | |
7973 | +}; | |
7974 | + | |
7975 | + | |
7976 | +// adds control-related methods to L.Map | |
7977 | + | |
7978 | +L.Map.include({ | |
7979 | + addControl: function (control) { | |
7980 | + control.addTo(this); | |
7981 | + return this; | |
7982 | + }, | |
7983 | + | |
7984 | + removeControl: function (control) { | |
7985 | + control.removeFrom(this); | |
7986 | + return this; | |
7987 | + }, | |
7988 | + | |
7989 | + _initControlPos: function () { | |
7990 | + var corners = this._controlCorners = {}, | |
7991 | + l = 'leaflet-', | |
7992 | + container = this._controlContainer = | |
7993 | + L.DomUtil.create('div', l + 'control-container', this._container); | |
7994 | + | |
7995 | + function createCorner(vSide, hSide) { | |
7996 | + var className = l + vSide + ' ' + l + hSide; | |
7997 | + | |
7998 | + corners[vSide + hSide] = L.DomUtil.create('div', className, container); | |
7999 | + } | |
8000 | + | |
8001 | + createCorner('top', 'left'); | |
8002 | + createCorner('top', 'right'); | |
8003 | + createCorner('bottom', 'left'); | |
8004 | + createCorner('bottom', 'right'); | |
8005 | + }, | |
8006 | + | |
8007 | + _clearControlPos: function () { | |
8008 | + this._container.removeChild(this._controlContainer); | |
8009 | + } | |
8010 | +}); | |
8011 | + | |
8012 | + | |
8013 | +/* | |
8014 | + * L.Control.Zoom is used for the default zoom buttons on the map. | |
8015 | + */ | |
8016 | + | |
8017 | +L.Control.Zoom = L.Control.extend({ | |
8018 | + options: { | |
8019 | + position: 'topleft', | |
8020 | + zoomInText: '+', | |
8021 | + zoomInTitle: 'Zoom in', | |
8022 | + zoomOutText: '-', | |
8023 | + zoomOutTitle: 'Zoom out' | |
8024 | + }, | |
8025 | + | |
8026 | + onAdd: function (map) { | |
8027 | + var zoomName = 'leaflet-control-zoom', | |
8028 | + container = L.DomUtil.create('div', zoomName + ' leaflet-bar'); | |
8029 | + | |
8030 | + this._map = map; | |
8031 | + | |
8032 | + this._zoomInButton = this._createButton( | |
8033 | + this.options.zoomInText, this.options.zoomInTitle, | |
8034 | + zoomName + '-in', container, this._zoomIn, this); | |
8035 | + this._zoomOutButton = this._createButton( | |
8036 | + this.options.zoomOutText, this.options.zoomOutTitle, | |
8037 | + zoomName + '-out', container, this._zoomOut, this); | |
8038 | + | |
8039 | + this._updateDisabled(); | |
8040 | + map.on('zoomend zoomlevelschange', this._updateDisabled, this); | |
8041 | + | |
8042 | + return container; | |
8043 | + }, | |
8044 | + | |
8045 | + onRemove: function (map) { | |
8046 | + map.off('zoomend zoomlevelschange', this._updateDisabled, this); | |
8047 | + }, | |
8048 | + | |
8049 | + _zoomIn: function (e) { | |
8050 | + this._map.zoomIn(e.shiftKey ? 3 : 1); | |
8051 | + }, | |
8052 | + | |
8053 | + _zoomOut: function (e) { | |
8054 | + this._map.zoomOut(e.shiftKey ? 3 : 1); | |
8055 | + }, | |
8056 | + | |
8057 | + _createButton: function (html, title, className, container, fn, context) { | |
8058 | + var link = L.DomUtil.create('a', className, container); | |
8059 | + link.innerHTML = html; | |
8060 | + link.href = '#'; | |
8061 | + link.title = title; | |
8062 | + | |
8063 | + var stop = L.DomEvent.stopPropagation; | |
8064 | + | |
8065 | + L.DomEvent | |
8066 | + .on(link, 'click', stop) | |
8067 | + .on(link, 'mousedown', stop) | |
8068 | + .on(link, 'dblclick', stop) | |
8069 | + .on(link, 'click', L.DomEvent.preventDefault) | |
8070 | + .on(link, 'click', fn, context) | |
8071 | + .on(link, 'click', this._refocusOnMap, context); | |
8072 | + | |
8073 | + return link; | |
8074 | + }, | |
8075 | + | |
8076 | + _updateDisabled: function () { | |
8077 | + var map = this._map, | |
8078 | + className = 'leaflet-disabled'; | |
8079 | + | |
8080 | + L.DomUtil.removeClass(this._zoomInButton, className); | |
8081 | + L.DomUtil.removeClass(this._zoomOutButton, className); | |
8082 | + | |
8083 | + if (map._zoom === map.getMinZoom()) { | |
8084 | + L.DomUtil.addClass(this._zoomOutButton, className); | |
8085 | + } | |
8086 | + if (map._zoom === map.getMaxZoom()) { | |
8087 | + L.DomUtil.addClass(this._zoomInButton, className); | |
8088 | + } | |
8089 | + } | |
8090 | +}); | |
8091 | + | |
8092 | +L.Map.mergeOptions({ | |
8093 | + zoomControl: true | |
8094 | +}); | |
8095 | + | |
8096 | +L.Map.addInitHook(function () { | |
8097 | + if (this.options.zoomControl) { | |
8098 | + this.zoomControl = new L.Control.Zoom(); | |
8099 | + this.addControl(this.zoomControl); | |
8100 | + } | |
8101 | +}); | |
8102 | + | |
8103 | +L.control.zoom = function (options) { | |
8104 | + return new L.Control.Zoom(options); | |
8105 | +}; | |
8106 | + | |
8107 | + | |
8108 | + | |
8109 | +/* | |
8110 | + * L.Control.Attribution is used for displaying attribution on the map (added by default). | |
8111 | + */ | |
8112 | + | |
8113 | +L.Control.Attribution = L.Control.extend({ | |
8114 | + options: { | |
8115 | + position: 'bottomright', | |
8116 | + prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' | |
8117 | + }, | |
8118 | + | |
8119 | + initialize: function (options) { | |
8120 | + L.setOptions(this, options); | |
8121 | + | |
8122 | + this._attributions = {}; | |
8123 | + }, | |
8124 | + | |
8125 | + onAdd: function (map) { | |
8126 | + this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); | |
8127 | + L.DomEvent.disableClickPropagation(this._container); | |
8128 | + | |
8129 | + for (var i in map._layers) { | |
8130 | + if (map._layers[i].getAttribution) { | |
8131 | + this.addAttribution(map._layers[i].getAttribution()); | |
8132 | + } | |
8133 | + } | |
8134 | + | |
8135 | + map | |
8136 | + .on('layeradd', this._onLayerAdd, this) | |
8137 | + .on('layerremove', this._onLayerRemove, this); | |
8138 | + | |
8139 | + this._update(); | |
8140 | + | |
8141 | + return this._container; | |
8142 | + }, | |
8143 | + | |
8144 | + onRemove: function (map) { | |
8145 | + map | |
8146 | + .off('layeradd', this._onLayerAdd) | |
8147 | + .off('layerremove', this._onLayerRemove); | |
8148 | + | |
8149 | + }, | |
8150 | + | |
8151 | + setPrefix: function (prefix) { | |
8152 | + this.options.prefix = prefix; | |
8153 | + this._update(); | |
8154 | + return this; | |
8155 | + }, | |
8156 | + | |
8157 | + addAttribution: function (text) { | |
8158 | + if (!text) { return; } | |
8159 | + | |
8160 | + if (!this._attributions[text]) { | |
8161 | + this._attributions[text] = 0; | |
8162 | + } | |
8163 | + this._attributions[text]++; | |
8164 | + | |
8165 | + this._update(); | |
8166 | + | |
8167 | + return this; | |
8168 | + }, | |
8169 | + | |
8170 | + removeAttribution: function (text) { | |
8171 | + if (!text) { return; } | |
8172 | + | |
8173 | + if (this._attributions[text]) { | |
8174 | + this._attributions[text]--; | |
8175 | + this._update(); | |
8176 | + } | |
8177 | + | |
8178 | + return this; | |
8179 | + }, | |
8180 | + | |
8181 | + _update: function () { | |
8182 | + if (!this._map) { return; } | |
8183 | + | |
8184 | + var attribs = []; | |
8185 | + | |
8186 | + for (var i in this._attributions) { | |
8187 | + if (this._attributions[i]) { | |
8188 | + attribs.push(i); | |
8189 | + } | |
8190 | + } | |
8191 | + | |
8192 | + var prefixAndAttribs = []; | |
8193 | + | |
8194 | + if (this.options.prefix) { | |
8195 | + prefixAndAttribs.push(this.options.prefix); | |
8196 | + } | |
8197 | + if (attribs.length) { | |
8198 | + prefixAndAttribs.push(attribs.join(', ')); | |
8199 | + } | |
8200 | + | |
8201 | + this._container.innerHTML = prefixAndAttribs.join(' | '); | |
8202 | + }, | |
8203 | + | |
8204 | + _onLayerAdd: function (e) { | |
8205 | + if (e.layer.getAttribution) { | |
8206 | + this.addAttribution(e.layer.getAttribution()); | |
8207 | + } | |
8208 | + }, | |
8209 | + | |
8210 | + _onLayerRemove: function (e) { | |
8211 | + if (e.layer.getAttribution) { | |
8212 | + this.removeAttribution(e.layer.getAttribution()); | |
8213 | + } | |
8214 | + } | |
8215 | +}); | |
8216 | + | |
8217 | +L.Map.mergeOptions({ | |
8218 | + attributionControl: true | |
8219 | +}); | |
8220 | + | |
8221 | +L.Map.addInitHook(function () { | |
8222 | + if (this.options.attributionControl) { | |
8223 | + this.attributionControl = (new L.Control.Attribution()).addTo(this); | |
8224 | + } | |
8225 | +}); | |
8226 | + | |
8227 | +L.control.attribution = function (options) { | |
8228 | + return new L.Control.Attribution(options); | |
8229 | +}; | |
8230 | + | |
8231 | + | |
8232 | +/* | |
8233 | + * L.Control.Scale is used for displaying metric/imperial scale on the map. | |
8234 | + */ | |
8235 | + | |
8236 | +L.Control.Scale = L.Control.extend({ | |
8237 | + options: { | |
8238 | + position: 'bottomleft', | |
8239 | + maxWidth: 100, | |
8240 | + metric: true, | |
8241 | + imperial: true, | |
8242 | + updateWhenIdle: false | |
8243 | + }, | |
8244 | + | |
8245 | + onAdd: function (map) { | |
8246 | + this._map = map; | |
8247 | + | |
8248 | + var className = 'leaflet-control-scale', | |
8249 | + container = L.DomUtil.create('div', className), | |
8250 | + options = this.options; | |
8251 | + | |
8252 | + this._addScales(options, className, container); | |
8253 | + | |
8254 | + map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); | |
8255 | + map.whenReady(this._update, this); | |
8256 | + | |
8257 | + return container; | |
8258 | + }, | |
8259 | + | |
8260 | + onRemove: function (map) { | |
8261 | + map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); | |
8262 | + }, | |
8263 | + | |
8264 | + _addScales: function (options, className, container) { | |
8265 | + if (options.metric) { | |
8266 | + this._mScale = L.DomUtil.create('div', className + '-line', container); | |
8267 | + } | |
8268 | + if (options.imperial) { | |
8269 | + this._iScale = L.DomUtil.create('div', className + '-line', container); | |
8270 | + } | |
8271 | + }, | |
8272 | + | |
8273 | + _update: function () { | |
8274 | + var bounds = this._map.getBounds(), | |
8275 | + centerLat = bounds.getCenter().lat, | |
8276 | + halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180), | |
8277 | + dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180, | |
8278 | + | |
8279 | + size = this._map.getSize(), | |
8280 | + options = this.options, | |
8281 | + maxMeters = 0; | |
8282 | + | |
8283 | + if (size.x > 0) { | |
8284 | + maxMeters = dist * (options.maxWidth / size.x); | |
8285 | + } | |
8286 | + | |
8287 | + this._updateScales(options, maxMeters); | |
8288 | + }, | |
8289 | + | |
8290 | + _updateScales: function (options, maxMeters) { | |
8291 | + if (options.metric && maxMeters) { | |
8292 | + this._updateMetric(maxMeters); | |
8293 | + } | |
8294 | + | |
8295 | + if (options.imperial && maxMeters) { | |
8296 | + this._updateImperial(maxMeters); | |
8297 | + } | |
8298 | + }, | |
8299 | + | |
8300 | + _updateMetric: function (maxMeters) { | |
8301 | + var meters = this._getRoundNum(maxMeters); | |
8302 | + | |
8303 | + this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px'; | |
8304 | + this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; | |
8305 | + }, | |
8306 | + | |
8307 | + _updateImperial: function (maxMeters) { | |
8308 | + var maxFeet = maxMeters * 3.2808399, | |
8309 | + scale = this._iScale, | |
8310 | + maxMiles, miles, feet; | |
8311 | + | |
8312 | + if (maxFeet > 5280) { | |
8313 | + maxMiles = maxFeet / 5280; | |
8314 | + miles = this._getRoundNum(maxMiles); | |
8315 | + | |
8316 | + scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px'; | |
8317 | + scale.innerHTML = miles + ' mi'; | |
8318 | + | |
8319 | + } else { | |
8320 | + feet = this._getRoundNum(maxFeet); | |
8321 | + | |
8322 | + scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px'; | |
8323 | + scale.innerHTML = feet + ' ft'; | |
8324 | + } | |
8325 | + }, | |
8326 | + | |
8327 | + _getScaleWidth: function (ratio) { | |
8328 | + return Math.round(this.options.maxWidth * ratio) - 10; | |
8329 | + }, | |
8330 | + | |
8331 | + _getRoundNum: function (num) { | |
8332 | + var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), | |
8333 | + d = num / pow10; | |
8334 | + | |
8335 | + d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1; | |
8336 | + | |
8337 | + return pow10 * d; | |
8338 | + } | |
8339 | +}); | |
8340 | + | |
8341 | +L.control.scale = function (options) { | |
8342 | + return new L.Control.Scale(options); | |
8343 | +}; | |
8344 | + | |
8345 | + | |
8346 | +/* | |
8347 | + * L.Control.Layers is a control to allow users to switch between different layers on the map. | |
8348 | + */ | |
8349 | + | |
8350 | +L.Control.Layers = L.Control.extend({ | |
8351 | + options: { | |
8352 | + collapsed: true, | |
8353 | + position: 'topright', | |
8354 | + autoZIndex: true | |
8355 | + }, | |
8356 | + | |
8357 | + initialize: function (baseLayers, overlays, options) { | |
8358 | + L.setOptions(this, options); | |
8359 | + | |
8360 | + this._layers = {}; | |
8361 | + this._lastZIndex = 0; | |
8362 | + this._handlingClick = false; | |
8363 | + | |
8364 | + for (var i in baseLayers) { | |
8365 | + this._addLayer(baseLayers[i], i); | |
8366 | + } | |
8367 | + | |
8368 | + for (i in overlays) { | |
8369 | + this._addLayer(overlays[i], i, true); | |
8370 | + } | |
8371 | + }, | |
8372 | + | |
8373 | + onAdd: function (map) { | |
8374 | + this._initLayout(); | |
8375 | + this._update(); | |
8376 | + | |
8377 | + map | |
8378 | + .on('layeradd', this._onLayerChange, this) | |
8379 | + .on('layerremove', this._onLayerChange, this); | |
8380 | + | |
8381 | + return this._container; | |
8382 | + }, | |
8383 | + | |
8384 | + onRemove: function (map) { | |
8385 | + map | |
8386 | + .off('layeradd', this._onLayerChange, this) | |
8387 | + .off('layerremove', this._onLayerChange, this); | |
8388 | + }, | |
8389 | + | |
8390 | + addBaseLayer: function (layer, name) { | |
8391 | + this._addLayer(layer, name); | |
8392 | + this._update(); | |
8393 | + return this; | |
8394 | + }, | |
8395 | + | |
8396 | + addOverlay: function (layer, name) { | |
8397 | + this._addLayer(layer, name, true); | |
8398 | + this._update(); | |
8399 | + return this; | |
8400 | + }, | |
8401 | + | |
8402 | + removeLayer: function (layer) { | |
8403 | + var id = L.stamp(layer); | |
8404 | + delete this._layers[id]; | |
8405 | + this._update(); | |
8406 | + return this; | |
8407 | + }, | |
8408 | + | |
8409 | + _initLayout: function () { | |
8410 | + var className = 'leaflet-control-layers', | |
8411 | + container = this._container = L.DomUtil.create('div', className); | |
8412 | + | |
8413 | + //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released | |
8414 | + container.setAttribute('aria-haspopup', true); | |
8415 | + | |
8416 | + if (!L.Browser.touch) { | |
8417 | + L.DomEvent | |
8418 | + .disableClickPropagation(container) | |
8419 | + .disableScrollPropagation(container); | |
8420 | + } else { | |
8421 | + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); | |
8422 | + } | |
8423 | + | |
8424 | + var form = this._form = L.DomUtil.create('form', className + '-list'); | |
8425 | + | |
8426 | + if (this.options.collapsed) { | |
8427 | + if (!L.Browser.android) { | |
8428 | + L.DomEvent | |
8429 | + .on(container, 'mouseover', this._expand, this) | |
8430 | + .on(container, 'mouseout', this._collapse, this); | |
8431 | + } | |
8432 | + var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); | |
8433 | + link.href = '#'; | |
8434 | + link.title = 'Layers'; | |
8435 | + | |
8436 | + if (L.Browser.touch) { | |
8437 | + L.DomEvent | |
8438 | + .on(link, 'click', L.DomEvent.stop) | |
8439 | + .on(link, 'click', this._expand, this); | |
8440 | + } | |
8441 | + else { | |
8442 | + L.DomEvent.on(link, 'focus', this._expand, this); | |
8443 | + } | |
8444 | + //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033 | |
8445 | + L.DomEvent.on(form, 'click', function () { | |
8446 | + setTimeout(L.bind(this._onInputClick, this), 0); | |
8447 | + }, this); | |
8448 | + | |
8449 | + this._map.on('click', this._collapse, this); | |
8450 | + // TODO keyboard accessibility | |
8451 | + } else { | |
8452 | + this._expand(); | |
8453 | + } | |
8454 | + | |
8455 | + this._baseLayersList = L.DomUtil.create('div', className + '-base', form); | |
8456 | + this._separator = L.DomUtil.create('div', className + '-separator', form); | |
8457 | + this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); | |
8458 | + | |
8459 | + container.appendChild(form); | |
8460 | + }, | |
8461 | + | |
8462 | + _addLayer: function (layer, name, overlay) { | |
8463 | + var id = L.stamp(layer); | |
8464 | + | |
8465 | + this._layers[id] = { | |
8466 | + layer: layer, | |
8467 | + name: name, | |
8468 | + overlay: overlay | |
8469 | + }; | |
8470 | + | |
8471 | + if (this.options.autoZIndex && layer.setZIndex) { | |
8472 | + this._lastZIndex++; | |
8473 | + layer.setZIndex(this._lastZIndex); | |
8474 | + } | |
8475 | + }, | |
8476 | + | |
8477 | + _update: function () { | |
8478 | + if (!this._container) { | |
8479 | + return; | |
8480 | + } | |
8481 | + | |
8482 | + this._baseLayersList.innerHTML = ''; | |
8483 | + this._overlaysList.innerHTML = ''; | |
8484 | + | |
8485 | + var baseLayersPresent = false, | |