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.6.2'; |
---|
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 | |
---|
138 | template: function (str, data) { |
---|
139 | return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { |
---|
140 | var value = data[key]; |
---|
141 | if (value === undefined) { |
---|
142 | throw new Error('No value provided for variable ' + str); |
---|
143 | } else if (typeof value === 'function') { |
---|
144 | value = value(data); |
---|
145 | } |
---|
146 | return value; |
---|
147 | }); |
---|
148 | }, |
---|
149 | |
---|
150 | isArray: function (obj) { |
---|
151 | return (Object.prototype.toString.call(obj) === '[object Array]'); |
---|
152 | }, |
---|
153 | |
---|
154 | emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' |
---|
155 | }; |
---|
156 | |
---|
157 | (function () { |
---|
158 | |
---|
159 | // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ |
---|
160 | |
---|
161 | function getPrefixed(name) { |
---|
162 | var i, fn, |
---|
163 | prefixes = ['webkit', 'moz', 'o', 'ms']; |
---|
164 | |
---|
165 | for (i = 0; i < prefixes.length && !fn; i++) { |
---|
166 | fn = window[prefixes[i] + name]; |
---|
167 | } |
---|
168 | |
---|
169 | return fn; |
---|
170 | } |
---|
171 | |
---|
172 | var lastTime = 0; |
---|
173 | |
---|
174 | function timeoutDefer(fn) { |
---|
175 | var time = +new Date(), |
---|
176 | timeToCall = Math.max(0, 16 - (time - lastTime)); |
---|
177 | |
---|
178 | lastTime = time + timeToCall; |
---|
179 | return window.setTimeout(fn, timeToCall); |
---|
180 | } |
---|
181 | |
---|
182 | var requestFn = window.requestAnimationFrame || |
---|
183 | getPrefixed('RequestAnimationFrame') || timeoutDefer; |
---|
184 | |
---|
185 | var cancelFn = window.cancelAnimationFrame || |
---|
186 | getPrefixed('CancelAnimationFrame') || |
---|
187 | getPrefixed('CancelRequestAnimationFrame') || |
---|
188 | function (id) { window.clearTimeout(id); }; |
---|
189 | |
---|
190 | |
---|
191 | L.Util.requestAnimFrame = function (fn, context, immediate, element) { |
---|
192 | fn = L.bind(fn, context); |
---|
193 | |
---|
194 | if (immediate && requestFn === timeoutDefer) { |
---|
195 | fn(); |
---|
196 | } else { |
---|
197 | return requestFn.call(window, fn, element); |
---|
198 | } |
---|
199 | }; |
---|
200 | |
---|
201 | L.Util.cancelAnimFrame = function (id) { |
---|
202 | if (id) { |
---|
203 | cancelFn.call(window, id); |
---|
204 | } |
---|
205 | }; |
---|
206 | |
---|
207 | }()); |
---|
208 | |
---|
209 | // shortcuts for most used utility functions |
---|
210 | L.extend = L.Util.extend; |
---|
211 | L.bind = L.Util.bind; |
---|
212 | L.stamp = L.Util.stamp; |
---|
213 | L.setOptions = L.Util.setOptions; |
---|
214 | |
---|
215 | |
---|
216 | /* |
---|
217 | * L.Class powers the OOP facilities of the library. |
---|
218 | * Thanks to John Resig and Dean Edwards for inspiration! |
---|
219 | */ |
---|
220 | |
---|
221 | L.Class = function () {}; |
---|
222 | |
---|
223 | L.Class.extend = function (props) { |
---|
224 | |
---|
225 | // extended class with the new prototype |
---|
226 | var NewClass = function () { |
---|
227 | |
---|
228 | // call the constructor |
---|
229 | if (this.initialize) { |
---|
230 | this.initialize.apply(this, arguments); |
---|
231 | } |
---|
232 | |
---|
233 | // call all constructor hooks |
---|
234 | if (this._initHooks) { |
---|
235 | this.callInitHooks(); |
---|
236 | } |
---|
237 | }; |
---|
238 | |
---|
239 | // instantiate class without calling constructor |
---|
240 | var F = function () {}; |
---|
241 | F.prototype = this.prototype; |
---|
242 | |
---|
243 | var proto = new F(); |
---|
244 | proto.constructor = NewClass; |
---|
245 | |
---|
246 | NewClass.prototype = proto; |
---|
247 | |
---|
248 | //inherit parent's statics |
---|
249 | for (var i in this) { |
---|
250 | if (this.hasOwnProperty(i) && i !== 'prototype') { |
---|
251 | NewClass[i] = this[i]; |
---|
252 | } |
---|
253 | } |
---|
254 | |
---|
255 | // mix static properties into the class |
---|
256 | if (props.statics) { |
---|
257 | L.extend(NewClass, props.statics); |
---|
258 | delete props.statics; |
---|
259 | } |
---|
260 | |
---|
261 | // mix includes into the prototype |
---|
262 | if (props.includes) { |
---|
263 | L.Util.extend.apply(null, [proto].concat(props.includes)); |
---|
264 | delete props.includes; |
---|
265 | } |
---|
266 | |
---|
267 | // merge options |
---|
268 | if (props.options && proto.options) { |
---|
269 | props.options = L.extend({}, proto.options, props.options); |
---|
270 | } |
---|
271 | |
---|
272 | // mix given properties into the prototype |
---|
273 | L.extend(proto, props); |
---|
274 | |
---|
275 | proto._initHooks = []; |
---|
276 | |
---|
277 | var parent = this; |
---|
278 | // jshint camelcase: false |
---|
279 | NewClass.__super__ = parent.prototype; |
---|
280 | |
---|
281 | // add method for calling all hooks |
---|
282 | proto.callInitHooks = function () { |
---|
283 | |
---|
284 | if (this._initHooksCalled) { return; } |
---|
285 | |
---|
286 | if (parent.prototype.callInitHooks) { |
---|
287 | parent.prototype.callInitHooks.call(this); |
---|
288 | } |
---|
289 | |
---|
290 | this._initHooksCalled = true; |
---|
291 | |
---|
292 | for (var i = 0, len = proto._initHooks.length; i < len; i++) { |
---|
293 | proto._initHooks[i].call(this); |
---|
294 | } |
---|
295 | }; |
---|
296 | |
---|
297 | return NewClass; |
---|
298 | }; |
---|
299 | |
---|
300 | |
---|
301 | // method for adding properties to prototype |
---|
302 | L.Class.include = function (props) { |
---|
303 | L.extend(this.prototype, props); |
---|
304 | }; |
---|
305 | |
---|
306 | // merge new default options to the Class |
---|
307 | L.Class.mergeOptions = function (options) { |
---|
308 | L.extend(this.prototype.options, options); |
---|
309 | }; |
---|
310 | |
---|
311 | // add a constructor hook |
---|
312 | L.Class.addInitHook = function (fn) { // (Function) || (String, args...) |
---|
313 | var args = Array.prototype.slice.call(arguments, 1); |
---|
314 | |
---|
315 | var init = typeof fn === 'function' ? fn : function () { |
---|
316 | this[fn].apply(this, args); |
---|
317 | }; |
---|
318 | |
---|
319 | this.prototype._initHooks = this.prototype._initHooks || []; |
---|
320 | this.prototype._initHooks.push(init); |
---|
321 | }; |
---|
322 | |
---|
323 | |
---|
324 | /* |
---|
325 | * L.Mixin.Events is used to add custom events functionality to Leaflet classes. |
---|
326 | */ |
---|
327 | |
---|
328 | var eventsKey = '_leaflet_events'; |
---|
329 | |
---|
330 | L.Mixin = {}; |
---|
331 | |
---|
332 | L.Mixin.Events = { |
---|
333 | |
---|
334 | addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object]) |
---|
335 | |
---|
336 | // types can be a map of types/handlers |
---|
337 | if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; } |
---|
338 | |
---|
339 | var events = this[eventsKey] = this[eventsKey] || {}, |
---|
340 | contextId = context && L.stamp(context), |
---|
341 | i, len, event, type, indexKey, indexLenKey, typeIndex; |
---|
342 | |
---|
343 | // types can be a string of space-separated words |
---|
344 | types = L.Util.splitWords(types); |
---|
345 | |
---|
346 | for (i = 0, len = types.length; i < len; i++) { |
---|
347 | event = { |
---|
348 | action: fn, |
---|
349 | context: context || this |
---|
350 | }; |
---|
351 | type = types[i]; |
---|
352 | |
---|
353 | if (context) { |
---|
354 | // store listeners of a particular context in a separate hash (if it has an id) |
---|
355 | // gives a major performance boost when removing thousands of map layers |
---|
356 | |
---|
357 | indexKey = type + '_idx'; |
---|
358 | indexLenKey = indexKey + '_len'; |
---|
359 | |
---|
360 | typeIndex = events[indexKey] = events[indexKey] || {}; |
---|
361 | |
---|
362 | if (!typeIndex[contextId]) { |
---|
363 | typeIndex[contextId] = []; |
---|
364 | |
---|
365 | // keep track of the number of keys in the index to quickly check if it's empty |
---|
366 | events[indexLenKey] = (events[indexLenKey] || 0) + 1; |
---|
367 | } |
---|
368 | |
---|
369 | typeIndex[contextId].push(event); |
---|
370 | |
---|
371 | |
---|
372 | } else { |
---|
373 | events[type] = events[type] || []; |
---|
374 | events[type].push(event); |
---|
375 | } |
---|
376 | } |
---|
377 | |
---|
378 | return this; |
---|
379 | }, |
---|
380 | |
---|
381 | hasEventListeners: function (type) { // (String) -> Boolean |
---|
382 | var events = this[eventsKey]; |
---|
383 | return !!events && ((type in events && events[type].length > 0) || |
---|
384 | (type + '_idx' in events && events[type + '_idx_len'] > 0)); |
---|
385 | }, |
---|
386 | |
---|
387 | removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object]) |
---|
388 | |
---|
389 | if (!this[eventsKey]) { |
---|
390 | return this; |
---|
391 | } |
---|
392 | |
---|
393 | if (!types) { |
---|
394 | return this.clearAllEventListeners(); |
---|
395 | } |
---|
396 | |
---|
397 | if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; } |
---|
398 | |
---|
399 | var events = this[eventsKey], |
---|
400 | contextId = context && L.stamp(context), |
---|
401 | i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed; |
---|
402 | |
---|
403 | types = L.Util.splitWords(types); |
---|
404 | |
---|
405 | for (i = 0, len = types.length; i < len; i++) { |
---|
406 | type = types[i]; |
---|
407 | indexKey = type + '_idx'; |
---|
408 | indexLenKey = indexKey + '_len'; |
---|
409 | |
---|
410 | typeIndex = events[indexKey]; |
---|
411 | |
---|
412 | if (!fn) { |
---|
413 | // clear all listeners for a type if function isn't specified |
---|
414 | delete events[type]; |
---|
415 | delete events[indexKey]; |
---|
416 | |
---|
417 | } else { |
---|
418 | listeners = context && 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 || this, 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 || this, 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 = !!window.ActiveXObject, |
---|
510 | ie6 = ie && !window.XMLHttpRequest, |
---|
511 | ie7 = ie && !document.querySelector, |
---|
512 | ielt9 = ie && !document.addEventListener, |
---|
513 | |
---|
514 | // terrible browser detection to work around Safari / iOS / Android browser bugs |
---|
515 | ua = navigator.userAgent.toLowerCase(), |
---|
516 | webkit = ua.indexOf('webkit') !== -1, |
---|
517 | chrome = ua.indexOf('chrome') !== -1, |
---|
518 | phantomjs = ua.indexOf('phantom') !== -1, |
---|
519 | android = ua.indexOf('android') !== -1, |
---|
520 | android23 = ua.search('android [23]') !== -1, |
---|
521 | |
---|
522 | mobile = typeof orientation !== undefined + '', |
---|
523 | msTouch = window.navigator && window.navigator.msPointerEnabled && |
---|
524 | window.navigator.msMaxTouchPoints, |
---|
525 | retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || |
---|
526 | ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && |
---|
527 | window.matchMedia('(min-resolution:144dpi)').matches), |
---|
528 | |
---|
529 | doc = document.documentElement, |
---|
530 | ie3d = ie && ('transition' in doc.style), |
---|
531 | webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()), |
---|
532 | gecko3d = 'MozPerspective' in doc.style, |
---|
533 | opera3d = 'OTransition' in doc.style, |
---|
534 | any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs; |
---|
535 | |
---|
536 | |
---|
537 | // PhantomJS has 'ontouchstart' in document.documentElement, but doesn't actually support touch. |
---|
538 | // https://github.com/Leaflet/Leaflet/pull/1434#issuecomment-13843151 |
---|
539 | |
---|
540 | var touch = !window.L_NO_TOUCH && !phantomjs && (function () { |
---|
541 | |
---|
542 | var startName = 'ontouchstart'; |
---|
543 | |
---|
544 | // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc. |
---|
545 | if (msTouch || (startName in doc)) { |
---|
546 | return true; |
---|
547 | } |
---|
548 | |
---|
549 | // Firefox/Gecko |
---|
550 | var div = document.createElement('div'), |
---|
551 | supported = false; |
---|
552 | |
---|
553 | if (!div.setAttribute) { |
---|
554 | return false; |
---|
555 | } |
---|
556 | div.setAttribute(startName, 'return;'); |
---|
557 | |
---|
558 | if (typeof div[startName] === 'function') { |
---|
559 | supported = true; |
---|
560 | } |
---|
561 | |
---|
562 | div.removeAttribute(startName); |
---|
563 | div = null; |
---|
564 | |
---|
565 | return supported; |
---|
566 | }()); |
---|
567 | |
---|
568 | |
---|
569 | L.Browser = { |
---|
570 | ie: ie, |
---|
571 | ie6: ie6, |
---|
572 | ie7: ie7, |
---|
573 | ielt9: ielt9, |
---|
574 | webkit: webkit, |
---|
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 | msTouch: msTouch, |
---|
594 | |
---|
595 | retina: retina |
---|
596 | }; |
---|
597 | |
---|
598 | }()); |
---|
599 | |
---|
600 | |
---|
601 | /* |
---|
602 | * L.Point represents a point with x and y coordinates. |
---|
603 | */ |
---|
604 | |
---|
605 | L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) { |
---|
606 | this.x = (round ? Math.round(x) : x); |
---|
607 | this.y = (round ? Math.round(y) : y); |
---|
608 | }; |
---|
609 | |
---|
610 | L.Point.prototype = { |
---|
611 | |
---|
612 | clone: function () { |
---|
613 | return new L.Point(this.x, this.y); |
---|
614 | }, |
---|
615 | |
---|
616 | // non-destructive, returns a new point |
---|
617 | add: function (point) { |
---|
618 | return this.clone()._add(L.point(point)); |
---|
619 | }, |
---|
620 | |
---|
621 | // destructive, used directly for performance in situations where it's safe to modify existing point |
---|
622 | _add: function (point) { |
---|
623 | this.x += point.x; |
---|
624 | this.y += point.y; |
---|
625 | return this; |
---|
626 | }, |
---|
627 | |
---|
628 | subtract: function (point) { |
---|
629 | return this.clone()._subtract(L.point(point)); |
---|
630 | }, |
---|
631 | |
---|
632 | _subtract: function (point) { |
---|
633 | this.x -= point.x; |
---|
634 | this.y -= point.y; |
---|
635 | return this; |
---|
636 | }, |
---|
637 | |
---|
638 | divideBy: function (num) { |
---|
639 | return this.clone()._divideBy(num); |
---|
640 | }, |
---|
641 | |
---|
642 | _divideBy: function (num) { |
---|
643 | this.x /= num; |
---|
644 | this.y /= num; |
---|
645 | return this; |
---|
646 | }, |
---|
647 | |
---|
648 | multiplyBy: function (num) { |
---|
649 | return this.clone()._multiplyBy(num); |
---|
650 | }, |
---|
651 | |
---|
652 | _multiplyBy: function (num) { |
---|
653 | this.x *= num; |
---|
654 | this.y *= num; |
---|
655 | return this; |
---|
656 | }, |
---|
657 | |
---|
658 | round: function () { |
---|
659 | return this.clone()._round(); |
---|
660 | }, |
---|
661 | |
---|
662 | _round: function () { |
---|
663 | this.x = Math.round(this.x); |
---|
664 | this.y = Math.round(this.y); |
---|
665 | return this; |
---|
666 | }, |
---|
667 | |
---|
668 | floor: function () { |
---|
669 | return this.clone()._floor(); |
---|
670 | }, |
---|
671 | |
---|
672 | _floor: function () { |
---|
673 | this.x = Math.floor(this.x); |
---|
674 | this.y = Math.floor(this.y); |
---|
675 | return this; |
---|
676 | }, |
---|
677 | |
---|
678 | distanceTo: function (point) { |
---|
679 | point = L.point(point); |
---|
680 | |
---|
681 | var x = point.x - this.x, |
---|
682 | y = point.y - this.y; |
---|
683 | |
---|
684 | return Math.sqrt(x * x + y * y); |
---|
685 | }, |
---|
686 | |
---|
687 | equals: function (point) { |
---|
688 | point = L.point(point); |
---|
689 | |
---|
690 | return point.x === this.x && |
---|
691 | point.y === this.y; |
---|
692 | }, |
---|
693 | |
---|
694 | contains: function (point) { |
---|
695 | point = L.point(point); |
---|
696 | |
---|
697 | return Math.abs(point.x) <= Math.abs(this.x) && |
---|
698 | Math.abs(point.y) <= Math.abs(this.y); |
---|
699 | }, |
---|
700 | |
---|
701 | toString: function () { |
---|
702 | return 'Point(' + |
---|
703 | L.Util.formatNum(this.x) + ', ' + |
---|
704 | L.Util.formatNum(this.y) + ')'; |
---|
705 | } |
---|
706 | }; |
---|
707 | |
---|
708 | L.point = function (x, y, round) { |
---|
709 | if (x instanceof L.Point) { |
---|
710 | return x; |
---|
711 | } |
---|
712 | if (L.Util.isArray(x)) { |
---|
713 | return new L.Point(x[0], x[1]); |
---|
714 | } |
---|
715 | if (x === undefined || x === null) { |
---|
716 | return x; |
---|
717 | } |
---|
718 | return new L.Point(x, y, round); |
---|
719 | }; |
---|
720 | |
---|
721 | |
---|
722 | /* |
---|
723 | * L.Bounds represents a rectangular area on the screen in pixel coordinates. |
---|
724 | */ |
---|
725 | |
---|
726 | L.Bounds = function (a, b) { //(Point, Point) or Point[] |
---|
727 | if (!a) { return; } |
---|
728 | |
---|
729 | var points = b ? [a, b] : a; |
---|
730 | |
---|
731 | for (var i = 0, len = points.length; i < len; i++) { |
---|
732 | this.extend(points[i]); |
---|
733 | } |
---|
734 | }; |
---|
735 | |
---|
736 | L.Bounds.prototype = { |
---|
737 | // extend the bounds to contain the given point |
---|
738 | extend: function (point) { // (Point) |
---|
739 | point = L.point(point); |
---|
740 | |
---|
741 | if (!this.min && !this.max) { |
---|
742 | this.min = point.clone(); |
---|
743 | this.max = point.clone(); |
---|
744 | } else { |
---|
745 | this.min.x = Math.min(point.x, this.min.x); |
---|
746 | this.max.x = Math.max(point.x, this.max.x); |
---|
747 | this.min.y = Math.min(point.y, this.min.y); |
---|
748 | this.max.y = Math.max(point.y, this.max.y); |
---|
749 | } |
---|
750 | return this; |
---|
751 | }, |
---|
752 | |
---|
753 | getCenter: function (round) { // (Boolean) -> Point |
---|
754 | return new L.Point( |
---|
755 | (this.min.x + this.max.x) / 2, |
---|
756 | (this.min.y + this.max.y) / 2, round); |
---|
757 | }, |
---|
758 | |
---|
759 | getBottomLeft: function () { // -> Point |
---|
760 | return new L.Point(this.min.x, this.max.y); |
---|
761 | }, |
---|
762 | |
---|
763 | getTopRight: function () { // -> Point |
---|
764 | return new L.Point(this.max.x, this.min.y); |
---|
765 | }, |
---|
766 | |
---|
767 | getSize: function () { |
---|
768 | return this.max.subtract(this.min); |
---|
769 | }, |
---|
770 | |
---|
771 | contains: function (obj) { // (Bounds) or (Point) -> Boolean |
---|
772 | var min, max; |
---|
773 | |
---|
774 | if (typeof obj[0] === 'number' || obj instanceof L.Point) { |
---|
775 | obj = L.point(obj); |
---|
776 | } else { |
---|
777 | obj = L.bounds(obj); |
---|
778 | } |
---|
779 | |
---|
780 | if (obj instanceof L.Bounds) { |
---|
781 | min = obj.min; |
---|
782 | max = obj.max; |
---|
783 | } else { |
---|
784 | min = max = obj; |
---|
785 | } |
---|
786 | |
---|
787 | return (min.x >= this.min.x) && |
---|
788 | (max.x <= this.max.x) && |
---|
789 | (min.y >= this.min.y) && |
---|
790 | (max.y <= this.max.y); |
---|
791 | }, |
---|
792 | |
---|
793 | intersects: function (bounds) { // (Bounds) -> Boolean |
---|
794 | bounds = L.bounds(bounds); |
---|
795 | |
---|
796 | var min = this.min, |
---|
797 | max = this.max, |
---|
798 | min2 = bounds.min, |
---|
799 | max2 = bounds.max, |
---|
800 | xIntersects = (max2.x >= min.x) && (min2.x <= max.x), |
---|
801 | yIntersects = (max2.y >= min.y) && (min2.y <= max.y); |
---|
802 | |
---|
803 | return xIntersects && yIntersects; |
---|
804 | }, |
---|
805 | |
---|
806 | isValid: function () { |
---|
807 | return !!(this.min && this.max); |
---|
808 | } |
---|
809 | }; |
---|
810 | |
---|
811 | L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) |
---|
812 | if (!a || a instanceof L.Bounds) { |
---|
813 | return a; |
---|
814 | } |
---|
815 | return new L.Bounds(a, b); |
---|
816 | }; |
---|
817 | |
---|
818 | |
---|
819 | /* |
---|
820 | * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix. |
---|
821 | */ |
---|
822 | |
---|
823 | L.Transformation = function (a, b, c, d) { |
---|
824 | this._a = a; |
---|
825 | this._b = b; |
---|
826 | this._c = c; |
---|
827 | this._d = d; |
---|
828 | }; |
---|
829 | |
---|
830 | L.Transformation.prototype = { |
---|
831 | transform: function (point, scale) { // (Point, Number) -> Point |
---|
832 | return this._transform(point.clone(), scale); |
---|
833 | }, |
---|
834 | |
---|
835 | // destructive transform (faster) |
---|
836 | _transform: function (point, scale) { |
---|
837 | scale = scale || 1; |
---|
838 | point.x = scale * (this._a * point.x + this._b); |
---|
839 | point.y = scale * (this._c * point.y + this._d); |
---|
840 | return point; |
---|
841 | }, |
---|
842 | |
---|
843 | untransform: function (point, scale) { |
---|
844 | scale = scale || 1; |
---|
845 | return new L.Point( |
---|
846 | (point.x / scale - this._b) / this._a, |
---|
847 | (point.y / scale - this._d) / this._c); |
---|
848 | } |
---|
849 | }; |
---|
850 | |
---|
851 | |
---|
852 | /* |
---|
853 | * L.DomUtil contains various utility functions for working with DOM. |
---|
854 | */ |
---|
855 | |
---|
856 | L.DomUtil = { |
---|
857 | get: function (id) { |
---|
858 | return (typeof id === 'string' ? document.getElementById(id) : id); |
---|
859 | }, |
---|
860 | |
---|
861 | getStyle: function (el, style) { |
---|
862 | |
---|
863 | var value = el.style[style]; |
---|
864 | |
---|
865 | if (!value && el.currentStyle) { |
---|
866 | value = el.currentStyle[style]; |
---|
867 | } |
---|
868 | |
---|
869 | if ((!value || value === 'auto') && document.defaultView) { |
---|
870 | var css = document.defaultView.getComputedStyle(el, null); |
---|
871 | value = css ? css[style] : null; |
---|
872 | } |
---|
873 | |
---|
874 | return value === 'auto' ? null : value; |
---|
875 | }, |
---|
876 | |
---|
877 | getViewportOffset: function (element) { |
---|
878 | |
---|
879 | var top = 0, |
---|
880 | left = 0, |
---|
881 | el = element, |
---|
882 | docBody = document.body, |
---|
883 | docEl = document.documentElement, |
---|
884 | pos, |
---|
885 | ie7 = L.Browser.ie7; |
---|
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 | // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else |
---|
933 | // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js |
---|
934 | if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) { |
---|
935 | left += el.scrollWidth - el.clientWidth; |
---|
936 | |
---|
937 | // ie7 shows the scrollbar by default and provides clientWidth counting it, so we |
---|
938 | // need to add it back in if it is visible; scrollbar is on the left as we are RTL |
---|
939 | if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' && |
---|
940 | L.DomUtil.getStyle(el, 'overflow') !== 'hidden') { |
---|
941 | left += 17; |
---|
942 | } |
---|
943 | } |
---|
944 | |
---|
945 | el = el.parentNode; |
---|
946 | } while (el); |
---|
947 | |
---|
948 | return new L.Point(left, top); |
---|
949 | }, |
---|
950 | |
---|
951 | documentIsLtr: function () { |
---|
952 | if (!L.DomUtil._docIsLtrCached) { |
---|
953 | L.DomUtil._docIsLtrCached = true; |
---|
954 | L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr'; |
---|
955 | } |
---|
956 | return L.DomUtil._docIsLtr; |
---|
957 | }, |
---|
958 | |
---|
959 | create: function (tagName, className, container) { |
---|
960 | |
---|
961 | var el = document.createElement(tagName); |
---|
962 | el.className = className; |
---|
963 | |
---|
964 | if (container) { |
---|
965 | container.appendChild(el); |
---|
966 | } |
---|
967 | |
---|
968 | return el; |
---|
969 | }, |
---|
970 | |
---|
971 | hasClass: function (el, name) { |
---|
972 | return (el.className.length > 0) && |
---|
973 | new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className); |
---|
974 | }, |
---|
975 | |
---|
976 | addClass: function (el, name) { |
---|
977 | if (!L.DomUtil.hasClass(el, name)) { |
---|
978 | el.className += (el.className ? ' ' : '') + name; |
---|
979 | } |
---|
980 | }, |
---|
981 | |
---|
982 | removeClass: function (el, name) { |
---|
983 | el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' ')); |
---|
984 | }, |
---|
985 | |
---|
986 | setOpacity: function (el, value) { |
---|
987 | |
---|
988 | if ('opacity' in el.style) { |
---|
989 | el.style.opacity = value; |
---|
990 | |
---|
991 | } else if ('filter' in el.style) { |
---|
992 | |
---|
993 | var filter = false, |
---|
994 | filterName = 'DXImageTransform.Microsoft.Alpha'; |
---|
995 | |
---|
996 | // filters collection throws an error if we try to retrieve a filter that doesn't exist |
---|
997 | try { |
---|
998 | filter = el.filters.item(filterName); |
---|
999 | } catch (e) { |
---|
1000 | // don't set opacity to 1 if we haven't already set an opacity, |
---|
1001 | // it isn't needed and breaks transparent pngs. |
---|
1002 | if (value === 1) { return; } |
---|
1003 | } |
---|
1004 | |
---|
1005 | value = Math.round(value * 100); |
---|
1006 | |
---|
1007 | if (filter) { |
---|
1008 | filter.Enabled = (value !== 100); |
---|
1009 | filter.Opacity = value; |
---|
1010 | } else { |
---|
1011 | el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; |
---|
1012 | } |
---|
1013 | } |
---|
1014 | }, |
---|
1015 | |
---|
1016 | testProp: function (props) { |
---|
1017 | |
---|
1018 | var style = document.documentElement.style; |
---|
1019 | |
---|
1020 | for (var i = 0; i < props.length; i++) { |
---|
1021 | if (props[i] in style) { |
---|
1022 | return props[i]; |
---|
1023 | } |
---|
1024 | } |
---|
1025 | return false; |
---|
1026 | }, |
---|
1027 | |
---|
1028 | getTranslateString: function (point) { |
---|
1029 | // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate |
---|
1030 | // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care |
---|
1031 | // (same speed either way), Opera 12 doesn't support translate3d |
---|
1032 | |
---|
1033 | var is3d = L.Browser.webkit3d, |
---|
1034 | open = 'translate' + (is3d ? '3d' : '') + '(', |
---|
1035 | close = (is3d ? ',0' : '') + ')'; |
---|
1036 | |
---|
1037 | return open + point.x + 'px,' + point.y + 'px' + close; |
---|
1038 | }, |
---|
1039 | |
---|
1040 | getScaleString: function (scale, origin) { |
---|
1041 | |
---|
1042 | var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))), |
---|
1043 | scaleStr = ' scale(' + scale + ') '; |
---|
1044 | |
---|
1045 | return preTranslateStr + scaleStr; |
---|
1046 | }, |
---|
1047 | |
---|
1048 | setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean]) |
---|
1049 | |
---|
1050 | // jshint camelcase: false |
---|
1051 | el._leaflet_pos = point; |
---|
1052 | |
---|
1053 | if (!disable3D && L.Browser.any3d) { |
---|
1054 | el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point); |
---|
1055 | |
---|
1056 | // workaround for Android 2/3 stability (https://github.com/CloudMade/Leaflet/issues/69) |
---|
1057 | if (L.Browser.mobileWebkit3d) { |
---|
1058 | el.style.WebkitBackfaceVisibility = 'hidden'; |
---|
1059 | } |
---|
1060 | } else { |
---|
1061 | el.style.left = point.x + 'px'; |
---|
1062 | el.style.top = point.y + 'px'; |
---|
1063 | } |
---|
1064 | }, |
---|
1065 | |
---|
1066 | getPosition: function (el) { |
---|
1067 | // this method is only used for elements previously positioned using setPosition, |
---|
1068 | // so it's safe to cache the position for performance |
---|
1069 | |
---|
1070 | // jshint camelcase: false |
---|
1071 | return el._leaflet_pos; |
---|
1072 | } |
---|
1073 | }; |
---|
1074 | |
---|
1075 | |
---|
1076 | // prefix style property names |
---|
1077 | |
---|
1078 | L.DomUtil.TRANSFORM = L.DomUtil.testProp( |
---|
1079 | ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); |
---|
1080 | |
---|
1081 | // webkitTransition comes first because some browser versions that drop vendor prefix don't do |
---|
1082 | // the same for the transitionend event, in particular the Android 4.1 stock browser |
---|
1083 | |
---|
1084 | L.DomUtil.TRANSITION = L.DomUtil.testProp( |
---|
1085 | ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); |
---|
1086 | |
---|
1087 | L.DomUtil.TRANSITION_END = |
---|
1088 | L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? |
---|
1089 | L.DomUtil.TRANSITION + 'End' : 'transitionend'; |
---|
1090 | |
---|
1091 | (function () { |
---|
1092 | var userSelectProperty = L.DomUtil.testProp( |
---|
1093 | ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); |
---|
1094 | |
---|
1095 | var userDragProperty = L.DomUtil.testProp( |
---|
1096 | ['userDrag', 'WebkitUserDrag', 'OUserDrag', 'MozUserDrag', 'msUserDrag']); |
---|
1097 | |
---|
1098 | L.extend(L.DomUtil, { |
---|
1099 | disableTextSelection: function () { |
---|
1100 | if (userSelectProperty) { |
---|
1101 | var style = document.documentElement.style; |
---|
1102 | this._userSelect = style[userSelectProperty]; |
---|
1103 | style[userSelectProperty] = 'none'; |
---|
1104 | } else { |
---|
1105 | L.DomEvent.on(window, 'selectstart', L.DomEvent.stop); |
---|
1106 | } |
---|
1107 | }, |
---|
1108 | |
---|
1109 | enableTextSelection: function () { |
---|
1110 | if (userSelectProperty) { |
---|
1111 | document.documentElement.style[userSelectProperty] = this._userSelect; |
---|
1112 | delete this._userSelect; |
---|
1113 | } else { |
---|
1114 | L.DomEvent.off(window, 'selectstart', L.DomEvent.stop); |
---|
1115 | } |
---|
1116 | }, |
---|
1117 | |
---|
1118 | disableImageDrag: function () { |
---|
1119 | if (userDragProperty) { |
---|
1120 | var style = document.documentElement.style; |
---|
1121 | this._userDrag = style[userDragProperty]; |
---|
1122 | style[userDragProperty] = 'none'; |
---|
1123 | } else { |
---|
1124 | L.DomEvent.on(window, 'dragstart', L.DomEvent.stop); |
---|
1125 | } |
---|
1126 | }, |
---|
1127 | |
---|
1128 | enableImageDrag: function () { |
---|
1129 | if (userDragProperty) { |
---|
1130 | document.documentElement.style[userDragProperty] = this._userDrag; |
---|
1131 | delete this._userDrag; |
---|
1132 | } else { |
---|
1133 | L.DomEvent.off(window, 'dragstart', L.DomEvent.stop); |
---|
1134 | } |
---|
1135 | } |
---|
1136 | }); |
---|
1137 | })(); |
---|
1138 | |
---|
1139 | |
---|
1140 | /* |
---|
1141 | * L.LatLng represents a geographical point with latitude and longitude coordinates. |
---|
1142 | */ |
---|
1143 | |
---|
1144 | L.LatLng = function (rawLat, rawLng) { // (Number, Number) |
---|
1145 | var lat = parseFloat(rawLat), |
---|
1146 | lng = parseFloat(rawLng); |
---|
1147 | |
---|
1148 | if (isNaN(lat) || isNaN(lng)) { |
---|
1149 | throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')'); |
---|
1150 | } |
---|
1151 | |
---|
1152 | this.lat = lat; |
---|
1153 | this.lng = lng; |
---|
1154 | }; |
---|
1155 | |
---|
1156 | L.extend(L.LatLng, { |
---|
1157 | DEG_TO_RAD: Math.PI / 180, |
---|
1158 | RAD_TO_DEG: 180 / Math.PI, |
---|
1159 | MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check |
---|
1160 | }); |
---|
1161 | |
---|
1162 | L.LatLng.prototype = { |
---|
1163 | equals: function (obj) { // (LatLng) -> Boolean |
---|
1164 | if (!obj) { return false; } |
---|
1165 | |
---|
1166 | obj = L.latLng(obj); |
---|
1167 | |
---|
1168 | var margin = Math.max( |
---|
1169 | Math.abs(this.lat - obj.lat), |
---|
1170 | Math.abs(this.lng - obj.lng)); |
---|
1171 | |
---|
1172 | return margin <= L.LatLng.MAX_MARGIN; |
---|
1173 | }, |
---|
1174 | |
---|
1175 | toString: function (precision) { // (Number) -> String |
---|
1176 | return 'LatLng(' + |
---|
1177 | L.Util.formatNum(this.lat, precision) + ', ' + |
---|
1178 | L.Util.formatNum(this.lng, precision) + ')'; |
---|
1179 | }, |
---|
1180 | |
---|
1181 | // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula |
---|
1182 | // TODO move to projection code, LatLng shouldn't know about Earth |
---|
1183 | distanceTo: function (other) { // (LatLng) -> Number |
---|
1184 | other = L.latLng(other); |
---|
1185 | |
---|
1186 | var R = 6378137, // earth radius in meters |
---|
1187 | d2r = L.LatLng.DEG_TO_RAD, |
---|
1188 | dLat = (other.lat - this.lat) * d2r, |
---|
1189 | dLon = (other.lng - this.lng) * d2r, |
---|
1190 | lat1 = this.lat * d2r, |
---|
1191 | lat2 = other.lat * d2r, |
---|
1192 | sin1 = Math.sin(dLat / 2), |
---|
1193 | sin2 = Math.sin(dLon / 2); |
---|
1194 | |
---|
1195 | var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); |
---|
1196 | |
---|
1197 | return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); |
---|
1198 | }, |
---|
1199 | |
---|
1200 | wrap: function (a, b) { // (Number, Number) -> LatLng |
---|
1201 | var lng = this.lng; |
---|
1202 | |
---|
1203 | a = a || -180; |
---|
1204 | b = b || 180; |
---|
1205 | |
---|
1206 | lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a); |
---|
1207 | |
---|
1208 | return new L.LatLng(this.lat, lng); |
---|
1209 | } |
---|
1210 | }; |
---|
1211 | |
---|
1212 | L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number) |
---|
1213 | if (a instanceof L.LatLng) { |
---|
1214 | return a; |
---|
1215 | } |
---|
1216 | if (L.Util.isArray(a)) { |
---|
1217 | return new L.LatLng(a[0], a[1]); |
---|
1218 | } |
---|
1219 | if (a === undefined || a === null) { |
---|
1220 | return a; |
---|
1221 | } |
---|
1222 | if (typeof a === 'object' && 'lat' in a) { |
---|
1223 | return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon); |
---|
1224 | } |
---|
1225 | return new L.LatLng(a, b); |
---|
1226 | }; |
---|
1227 | |
---|
1228 | |
---|
1229 | |
---|
1230 | /* |
---|
1231 | * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. |
---|
1232 | */ |
---|
1233 | |
---|
1234 | L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) |
---|
1235 | if (!southWest) { return; } |
---|
1236 | |
---|
1237 | var latlngs = northEast ? [southWest, northEast] : southWest; |
---|
1238 | |
---|
1239 | for (var i = 0, len = latlngs.length; i < len; i++) { |
---|
1240 | this.extend(latlngs[i]); |
---|
1241 | } |
---|
1242 | }; |
---|
1243 | |
---|
1244 | L.LatLngBounds.prototype = { |
---|
1245 | // extend the bounds to contain the given point or bounds |
---|
1246 | extend: function (obj) { // (LatLng) or (LatLngBounds) |
---|
1247 | if (!obj) { return this; } |
---|
1248 | |
---|
1249 | if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) { |
---|
1250 | obj = L.latLng(obj); |
---|
1251 | } else { |
---|
1252 | obj = L.latLngBounds(obj); |
---|
1253 | } |
---|
1254 | |
---|
1255 | if (obj instanceof L.LatLng) { |
---|
1256 | if (!this._southWest && !this._northEast) { |
---|
1257 | this._southWest = new L.LatLng(obj.lat, obj.lng); |
---|
1258 | this._northEast = new L.LatLng(obj.lat, obj.lng); |
---|
1259 | } else { |
---|
1260 | this._southWest.lat = Math.min(obj.lat, this._southWest.lat); |
---|
1261 | this._southWest.lng = Math.min(obj.lng, this._southWest.lng); |
---|
1262 | |
---|
1263 | this._northEast.lat = Math.max(obj.lat, this._northEast.lat); |
---|
1264 | this._northEast.lng = Math.max(obj.lng, this._northEast.lng); |
---|
1265 | } |
---|
1266 | } else if (obj instanceof L.LatLngBounds) { |
---|
1267 | this.extend(obj._southWest); |
---|
1268 | this.extend(obj._northEast); |
---|
1269 | } |
---|
1270 | return this; |
---|
1271 | }, |
---|
1272 | |
---|
1273 | // extend the bounds by a percentage |
---|
1274 | pad: function (bufferRatio) { // (Number) -> LatLngBounds |
---|
1275 | var sw = this._southWest, |
---|
1276 | ne = this._northEast, |
---|
1277 | heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, |
---|
1278 | widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; |
---|
1279 | |
---|
1280 | return new L.LatLngBounds( |
---|
1281 | new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), |
---|
1282 | new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); |
---|
1283 | }, |
---|
1284 | |
---|
1285 | getCenter: function () { // -> LatLng |
---|
1286 | return new L.LatLng( |
---|
1287 | (this._southWest.lat + this._northEast.lat) / 2, |
---|
1288 | (this._southWest.lng + this._northEast.lng) / 2); |
---|
1289 | }, |
---|
1290 | |
---|
1291 | getSouthWest: function () { |
---|
1292 | return this._southWest; |
---|
1293 | }, |
---|
1294 | |
---|
1295 | getNorthEast: function () { |
---|
1296 | return this._northEast; |
---|
1297 | }, |
---|
1298 | |
---|
1299 | getNorthWest: function () { |
---|
1300 | return new L.LatLng(this.getNorth(), this.getWest()); |
---|
1301 | }, |
---|
1302 | |
---|
1303 | getSouthEast: function () { |
---|
1304 | return new L.LatLng(this.getSouth(), this.getEast()); |
---|
1305 | }, |
---|
1306 | |
---|
1307 | getWest: function () { |
---|
1308 | return this._southWest.lng; |
---|
1309 | }, |
---|
1310 | |
---|
1311 | getSouth: function () { |
---|
1312 | return this._southWest.lat; |
---|
1313 | }, |
---|
1314 | |
---|
1315 | getEast: function () { |
---|
1316 | return this._northEast.lng; |
---|
1317 | }, |
---|
1318 | |
---|
1319 | getNorth: function () { |
---|
1320 | return this._northEast.lat; |
---|
1321 | }, |
---|
1322 | |
---|
1323 | contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean |
---|
1324 | if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { |
---|
1325 | obj = L.latLng(obj); |
---|
1326 | } else { |
---|
1327 | obj = L.latLngBounds(obj); |
---|
1328 | } |
---|
1329 | |
---|
1330 | var sw = this._southWest, |
---|
1331 | ne = this._northEast, |
---|
1332 | sw2, ne2; |
---|
1333 | |
---|
1334 | if (obj instanceof L.LatLngBounds) { |
---|
1335 | sw2 = obj.getSouthWest(); |
---|
1336 | ne2 = obj.getNorthEast(); |
---|
1337 | } else { |
---|
1338 | sw2 = ne2 = obj; |
---|
1339 | } |
---|
1340 | |
---|
1341 | return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && |
---|
1342 | (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); |
---|
1343 | }, |
---|
1344 | |
---|
1345 | intersects: function (bounds) { // (LatLngBounds) |
---|
1346 | bounds = L.latLngBounds(bounds); |
---|
1347 | |
---|
1348 | var sw = this._southWest, |
---|
1349 | ne = this._northEast, |
---|
1350 | sw2 = bounds.getSouthWest(), |
---|
1351 | ne2 = bounds.getNorthEast(), |
---|
1352 | |
---|
1353 | latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), |
---|
1354 | lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); |
---|
1355 | |
---|
1356 | return latIntersects && lngIntersects; |
---|
1357 | }, |
---|
1358 | |
---|
1359 | toBBoxString: function () { |
---|
1360 | return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); |
---|
1361 | }, |
---|
1362 | |
---|
1363 | equals: function (bounds) { // (LatLngBounds) |
---|
1364 | if (!bounds) { return false; } |
---|
1365 | |
---|
1366 | bounds = L.latLngBounds(bounds); |
---|
1367 | |
---|
1368 | return this._southWest.equals(bounds.getSouthWest()) && |
---|
1369 | this._northEast.equals(bounds.getNorthEast()); |
---|
1370 | }, |
---|
1371 | |
---|
1372 | isValid: function () { |
---|
1373 | return !!(this._southWest && this._northEast); |
---|
1374 | } |
---|
1375 | }; |
---|
1376 | |
---|
1377 | //TODO International date line? |
---|
1378 | |
---|
1379 | L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) |
---|
1380 | if (!a || a instanceof L.LatLngBounds) { |
---|
1381 | return a; |
---|
1382 | } |
---|
1383 | return new L.LatLngBounds(a, b); |
---|
1384 | }; |
---|
1385 | |
---|
1386 | |
---|
1387 | /* |
---|
1388 | * L.Projection contains various geographical projections used by CRS classes. |
---|
1389 | */ |
---|
1390 | |
---|
1391 | L.Projection = {}; |
---|
1392 | |
---|
1393 | |
---|
1394 | /* |
---|
1395 | * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. |
---|
1396 | */ |
---|
1397 | |
---|
1398 | L.Projection.SphericalMercator = { |
---|
1399 | MAX_LATITUDE: 85.0511287798, |
---|
1400 | |
---|
1401 | project: function (latlng) { // (LatLng) -> Point |
---|
1402 | var d = L.LatLng.DEG_TO_RAD, |
---|
1403 | max = this.MAX_LATITUDE, |
---|
1404 | lat = Math.max(Math.min(max, latlng.lat), -max), |
---|
1405 | x = latlng.lng * d, |
---|
1406 | y = lat * d; |
---|
1407 | |
---|
1408 | y = Math.log(Math.tan((Math.PI / 4) + (y / 2))); |
---|
1409 | |
---|
1410 | return new L.Point(x, y); |
---|
1411 | }, |
---|
1412 | |
---|
1413 | unproject: function (point) { // (Point, Boolean) -> LatLng |
---|
1414 | var d = L.LatLng.RAD_TO_DEG, |
---|
1415 | lng = point.x * d, |
---|
1416 | lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d; |
---|
1417 | |
---|
1418 | return new L.LatLng(lat, lng); |
---|
1419 | } |
---|
1420 | }; |
---|
1421 | |
---|
1422 | |
---|
1423 | /* |
---|
1424 | * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. |
---|
1425 | */ |
---|
1426 | |
---|
1427 | L.Projection.LonLat = { |
---|
1428 | project: function (latlng) { |
---|
1429 | return new L.Point(latlng.lng, latlng.lat); |
---|
1430 | }, |
---|
1431 | |
---|
1432 | unproject: function (point) { |
---|
1433 | return new L.LatLng(point.y, point.x); |
---|
1434 | } |
---|
1435 | }; |
---|
1436 | |
---|
1437 | |
---|
1438 | /* |
---|
1439 | * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet. |
---|
1440 | */ |
---|
1441 | |
---|
1442 | L.CRS = { |
---|
1443 | latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point |
---|
1444 | var projectedPoint = this.projection.project(latlng), |
---|
1445 | scale = this.scale(zoom); |
---|
1446 | |
---|
1447 | return this.transformation._transform(projectedPoint, scale); |
---|
1448 | }, |
---|
1449 | |
---|
1450 | pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng |
---|
1451 | var scale = this.scale(zoom), |
---|
1452 | untransformedPoint = this.transformation.untransform(point, scale); |
---|
1453 | |
---|
1454 | return this.projection.unproject(untransformedPoint); |
---|
1455 | }, |
---|
1456 | |
---|
1457 | project: function (latlng) { |
---|
1458 | return this.projection.project(latlng); |
---|
1459 | }, |
---|
1460 | |
---|
1461 | scale: function (zoom) { |
---|
1462 | return 256 * Math.pow(2, zoom); |
---|
1463 | } |
---|
1464 | }; |
---|
1465 | |
---|
1466 | |
---|
1467 | /* |
---|
1468 | * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps. |
---|
1469 | */ |
---|
1470 | |
---|
1471 | L.CRS.Simple = L.extend({}, L.CRS, { |
---|
1472 | projection: L.Projection.LonLat, |
---|
1473 | transformation: new L.Transformation(1, 0, -1, 0), |
---|
1474 | |
---|
1475 | scale: function (zoom) { |
---|
1476 | return Math.pow(2, zoom); |
---|
1477 | } |
---|
1478 | }); |
---|
1479 | |
---|
1480 | |
---|
1481 | /* |
---|
1482 | * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping |
---|
1483 | * and is used by Leaflet by default. |
---|
1484 | */ |
---|
1485 | |
---|
1486 | L.CRS.EPSG3857 = L.extend({}, L.CRS, { |
---|
1487 | code: 'EPSG:3857', |
---|
1488 | |
---|
1489 | projection: L.Projection.SphericalMercator, |
---|
1490 | transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5), |
---|
1491 | |
---|
1492 | project: function (latlng) { // (LatLng) -> Point |
---|
1493 | var projectedPoint = this.projection.project(latlng), |
---|
1494 | earthRadius = 6378137; |
---|
1495 | return projectedPoint.multiplyBy(earthRadius); |
---|
1496 | } |
---|
1497 | }); |
---|
1498 | |
---|
1499 | L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { |
---|
1500 | code: 'EPSG:900913' |
---|
1501 | }); |
---|
1502 | |
---|
1503 | |
---|
1504 | /* |
---|
1505 | * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists. |
---|
1506 | */ |
---|
1507 | |
---|
1508 | L.CRS.EPSG4326 = L.extend({}, L.CRS, { |
---|
1509 | code: 'EPSG:4326', |
---|
1510 | |
---|
1511 | projection: L.Projection.LonLat, |
---|
1512 | transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5) |
---|
1513 | }); |
---|
1514 | |
---|
1515 | |
---|
1516 | /* |
---|
1517 | * L.Map is the central class of the API - it is used to create a map. |
---|
1518 | */ |
---|
1519 | |
---|
1520 | L.Map = L.Class.extend({ |
---|
1521 | |
---|
1522 | includes: L.Mixin.Events, |
---|
1523 | |
---|
1524 | options: { |
---|
1525 | crs: L.CRS.EPSG3857, |
---|
1526 | |
---|
1527 | /* |
---|
1528 | center: LatLng, |
---|
1529 | zoom: Number, |
---|
1530 | layers: Array, |
---|
1531 | */ |
---|
1532 | |
---|
1533 | fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, |
---|
1534 | trackResize: true, |
---|
1535 | markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d |
---|
1536 | }, |
---|
1537 | |
---|
1538 | initialize: function (id, options) { // (HTMLElement or String, Object) |
---|
1539 | options = L.setOptions(this, options); |
---|
1540 | |
---|
1541 | this._initContainer(id); |
---|
1542 | this._initLayout(); |
---|
1543 | this._initEvents(); |
---|
1544 | |
---|
1545 | if (options.maxBounds) { |
---|
1546 | this.setMaxBounds(options.maxBounds); |
---|
1547 | } |
---|
1548 | |
---|
1549 | if (options.center && options.zoom !== undefined) { |
---|
1550 | this.setView(L.latLng(options.center), options.zoom, {reset: true}); |
---|
1551 | } |
---|
1552 | |
---|
1553 | this._handlers = []; |
---|
1554 | |
---|
1555 | this._layers = {}; |
---|
1556 | this._zoomBoundLayers = {}; |
---|
1557 | this._tileLayersNum = 0; |
---|
1558 | |
---|
1559 | this.callInitHooks(); |
---|
1560 | |
---|
1561 | this._addLayers(options.layers); |
---|
1562 | }, |
---|
1563 | |
---|
1564 | |
---|
1565 | // public methods that modify map state |
---|
1566 | |
---|
1567 | // replaced by animation-powered implementation in Map.PanAnimation.js |
---|
1568 | setView: function (center, zoom) { |
---|
1569 | this._resetView(L.latLng(center), this._limitZoom(zoom)); |
---|
1570 | return this; |
---|
1571 | }, |
---|
1572 | |
---|
1573 | setZoom: function (zoom, options) { |
---|
1574 | return this.setView(this.getCenter(), zoom, {zoom: options}); |
---|
1575 | }, |
---|
1576 | |
---|
1577 | zoomIn: function (delta, options) { |
---|
1578 | return this.setZoom(this._zoom + (delta || 1), options); |
---|
1579 | }, |
---|
1580 | |
---|
1581 | zoomOut: function (delta, options) { |
---|
1582 | return this.setZoom(this._zoom - (delta || 1), options); |
---|
1583 | }, |
---|
1584 | |
---|
1585 | setZoomAround: function (latlng, zoom, options) { |
---|
1586 | var scale = this.getZoomScale(zoom), |
---|
1587 | viewHalf = this.getSize().divideBy(2), |
---|
1588 | containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), |
---|
1589 | |
---|
1590 | centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), |
---|
1591 | newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); |
---|
1592 | |
---|
1593 | return this.setView(newCenter, zoom, {zoom: options}); |
---|
1594 | }, |
---|
1595 | |
---|
1596 | fitBounds: function (bounds, options) { |
---|
1597 | |
---|
1598 | options = options || {}; |
---|
1599 | bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); |
---|
1600 | |
---|
1601 | var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), |
---|
1602 | paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), |
---|
1603 | |
---|
1604 | zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)), |
---|
1605 | paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), |
---|
1606 | |
---|
1607 | swPoint = this.project(bounds.getSouthWest(), zoom), |
---|
1608 | nePoint = this.project(bounds.getNorthEast(), zoom), |
---|
1609 | center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); |
---|
1610 | |
---|
1611 | return this.setView(center, zoom, options); |
---|
1612 | }, |
---|
1613 | |
---|
1614 | fitWorld: function (options) { |
---|
1615 | return this.fitBounds([[-90, -180], [90, 180]], options); |
---|
1616 | }, |
---|
1617 | |
---|
1618 | panTo: function (center, options) { // (LatLng) |
---|
1619 | return this.setView(center, this._zoom, {pan: options}); |
---|
1620 | }, |
---|
1621 | |
---|
1622 | panBy: function (offset) { // (Point) |
---|
1623 | // replaced with animated panBy in Map.Animation.js |
---|
1624 | this.fire('movestart'); |
---|
1625 | |
---|
1626 | this._rawPanBy(L.point(offset)); |
---|
1627 | |
---|
1628 | this.fire('move'); |
---|
1629 | return this.fire('moveend'); |
---|
1630 | }, |
---|
1631 | |
---|
1632 | setMaxBounds: function (bounds) { |
---|
1633 | bounds = L.latLngBounds(bounds); |
---|
1634 | |
---|
1635 | this.options.maxBounds = bounds; |
---|
1636 | |
---|
1637 | if (!bounds) { |
---|
1638 | this._boundsMinZoom = null; |
---|
1639 | this.off('moveend', this._panInsideMaxBounds, this); |
---|
1640 | return this; |
---|
1641 | } |
---|
1642 | |
---|
1643 | var minZoom = this.getBoundsZoom(bounds, true); |
---|
1644 | |
---|
1645 | this._boundsMinZoom = minZoom; |
---|
1646 | |
---|
1647 | if (this._loaded) { |
---|
1648 | if (this._zoom < minZoom) { |
---|
1649 | this.setView(bounds.getCenter(), minZoom); |
---|
1650 | } else { |
---|
1651 | this.panInsideBounds(bounds); |
---|
1652 | } |
---|
1653 | } |
---|
1654 | |
---|
1655 | this.on('moveend', this._panInsideMaxBounds, this); |
---|
1656 | |
---|
1657 | return this; |
---|
1658 | }, |
---|
1659 | |
---|
1660 | panInsideBounds: function (bounds) { |
---|
1661 | bounds = L.latLngBounds(bounds); |
---|
1662 | |
---|
1663 | var viewBounds = this.getPixelBounds(), |
---|
1664 | viewSw = viewBounds.getBottomLeft(), |
---|
1665 | viewNe = viewBounds.getTopRight(), |
---|
1666 | sw = this.project(bounds.getSouthWest()), |
---|
1667 | ne = this.project(bounds.getNorthEast()), |
---|
1668 | dx = 0, |
---|
1669 | dy = 0; |
---|
1670 | |
---|
1671 | if (viewNe.y < ne.y) { // north |
---|
1672 | dy = Math.ceil(ne.y - viewNe.y); |
---|
1673 | } |
---|
1674 | if (viewNe.x > ne.x) { // east |
---|
1675 | dx = Math.floor(ne.x - viewNe.x); |
---|
1676 | } |
---|
1677 | if (viewSw.y > sw.y) { // south |
---|
1678 | dy = Math.floor(sw.y - viewSw.y); |
---|
1679 | } |
---|
1680 | if (viewSw.x < sw.x) { // west |
---|
1681 | dx = Math.ceil(sw.x - viewSw.x); |
---|
1682 | } |
---|
1683 | |
---|
1684 | if (dx || dy) { |
---|
1685 | return this.panBy([dx, dy]); |
---|
1686 | } |
---|
1687 | |
---|
1688 | return this; |
---|
1689 | }, |
---|
1690 | |
---|
1691 | addLayer: function (layer) { |
---|
1692 | // TODO method is too big, refactor |
---|
1693 | |
---|
1694 | var id = L.stamp(layer); |
---|
1695 | |
---|
1696 | if (this._layers[id]) { return this; } |
---|
1697 | |
---|
1698 | this._layers[id] = layer; |
---|
1699 | |
---|
1700 | // TODO getMaxZoom, getMinZoom in ILayer (instead of options) |
---|
1701 | if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { |
---|
1702 | this._zoomBoundLayers[id] = layer; |
---|
1703 | this._updateZoomLevels(); |
---|
1704 | } |
---|
1705 | |
---|
1706 | // TODO looks ugly, refactor!!! |
---|
1707 | if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { |
---|
1708 | this._tileLayersNum++; |
---|
1709 | this._tileLayersToLoad++; |
---|
1710 | layer.on('load', this._onTileLayerLoad, this); |
---|
1711 | } |
---|
1712 | |
---|
1713 | if (this._loaded) { |
---|
1714 | this._layerAdd(layer); |
---|
1715 | } |
---|
1716 | |
---|
1717 | return this; |
---|
1718 | }, |
---|
1719 | |
---|
1720 | removeLayer: function (layer) { |
---|
1721 | var id = L.stamp(layer); |
---|
1722 | |
---|
1723 | if (!this._layers[id]) { return; } |
---|
1724 | |
---|
1725 | if (this._loaded) { |
---|
1726 | layer.onRemove(this); |
---|
1727 | this.fire('layerremove', {layer: layer}); |
---|
1728 | } |
---|
1729 | |
---|
1730 | delete this._layers[id]; |
---|
1731 | if (this._zoomBoundLayers[id]) { |
---|
1732 | delete this._zoomBoundLayers[id]; |
---|
1733 | this._updateZoomLevels(); |
---|
1734 | } |
---|
1735 | |
---|
1736 | // TODO looks ugly, refactor |
---|
1737 | if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { |
---|
1738 | this._tileLayersNum--; |
---|
1739 | this._tileLayersToLoad--; |
---|
1740 | layer.off('load', this._onTileLayerLoad, this); |
---|
1741 | } |
---|
1742 | |
---|
1743 | return this; |
---|
1744 | }, |
---|
1745 | |
---|
1746 | hasLayer: function (layer) { |
---|
1747 | if (!layer) { return false; } |
---|
1748 | |
---|
1749 | return (L.stamp(layer) in this._layers); |
---|
1750 | }, |
---|
1751 | |
---|
1752 | eachLayer: function (method, context) { |
---|
1753 | for (var i in this._layers) { |
---|
1754 | method.call(context, this._layers[i]); |
---|
1755 | } |
---|
1756 | return this; |
---|
1757 | }, |
---|
1758 | |
---|
1759 | invalidateSize: function (options) { |
---|
1760 | options = L.extend({ |
---|
1761 | animate: false, |
---|
1762 | pan: true |
---|
1763 | }, options === true ? {animate: true} : options); |
---|
1764 | |
---|
1765 | var oldSize = this.getSize(); |
---|
1766 | this._sizeChanged = true; |
---|
1767 | |
---|
1768 | if (this.options.maxBounds) { |
---|
1769 | this.setMaxBounds(this.options.maxBounds); |
---|
1770 | } |
---|
1771 | |
---|
1772 | if (!this._loaded) { return this; } |
---|
1773 | |
---|
1774 | var newSize = this.getSize(), |
---|
1775 | offset = oldSize.subtract(newSize).divideBy(2).round(); |
---|
1776 | |
---|
1777 | if (!offset.x && !offset.y) { return this; } |
---|
1778 | |
---|
1779 | if (options.animate && options.pan) { |
---|
1780 | this.panBy(offset); |
---|
1781 | |
---|
1782 | } else { |
---|
1783 | if (options.pan) { |
---|
1784 | this._rawPanBy(offset); |
---|
1785 | } |
---|
1786 | |
---|
1787 | this.fire('move'); |
---|
1788 | |
---|
1789 | // make sure moveend is not fired too often on resize |
---|
1790 | clearTimeout(this._sizeTimer); |
---|
1791 | this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); |
---|
1792 | } |
---|
1793 | |
---|
1794 | return this.fire('resize', { |
---|
1795 | oldSize: oldSize, |
---|
1796 | newSize: newSize |
---|
1797 | }); |
---|
1798 | }, |
---|
1799 | |
---|
1800 | // TODO handler.addTo |
---|
1801 | addHandler: function (name, HandlerClass) { |
---|
1802 | if (!HandlerClass) { return; } |
---|
1803 | |
---|
1804 | var handler = this[name] = new HandlerClass(this); |
---|
1805 | |
---|
1806 | this._handlers.push(handler); |
---|
1807 | |
---|
1808 | if (this.options[name]) { |
---|
1809 | handler.enable(); |
---|
1810 | } |
---|
1811 | |
---|
1812 | return this; |
---|
1813 | }, |
---|
1814 | |
---|
1815 | remove: function () { |
---|
1816 | if (this._loaded) { |
---|
1817 | this.fire('unload'); |
---|
1818 | } |
---|
1819 | |
---|
1820 | this._initEvents('off'); |
---|
1821 | |
---|
1822 | delete this._container._leaflet; |
---|
1823 | |
---|
1824 | this._clearPanes(); |
---|
1825 | if (this._clearControlPos) { |
---|
1826 | this._clearControlPos(); |
---|
1827 | } |
---|
1828 | |
---|
1829 | this._clearHandlers(); |
---|
1830 | |
---|
1831 | return this; |
---|
1832 | }, |
---|
1833 | |
---|
1834 | |
---|
1835 | // public methods for getting map state |
---|
1836 | |
---|
1837 | getCenter: function () { // (Boolean) -> LatLng |
---|
1838 | this._checkIfLoaded(); |
---|
1839 | |
---|
1840 | if (!this._moved()) { |
---|
1841 | return this._initialCenter; |
---|
1842 | } |
---|
1843 | return this.layerPointToLatLng(this._getCenterLayerPoint()); |
---|
1844 | }, |
---|
1845 | |
---|
1846 | getZoom: function () { |
---|
1847 | return this._zoom; |
---|
1848 | }, |
---|
1849 | |
---|
1850 | getBounds: function () { |
---|
1851 | var bounds = this.getPixelBounds(), |
---|
1852 | sw = this.unproject(bounds.getBottomLeft()), |
---|
1853 | ne = this.unproject(bounds.getTopRight()); |
---|
1854 | |
---|
1855 | return new L.LatLngBounds(sw, ne); |
---|
1856 | }, |
---|
1857 | |
---|
1858 | getMinZoom: function () { |
---|
1859 | var z1 = this.options.minZoom || 0, |
---|
1860 | z2 = this._layersMinZoom || 0, |
---|
1861 | z3 = this._boundsMinZoom || 0; |
---|
1862 | |
---|
1863 | return Math.max(z1, z2, z3); |
---|
1864 | }, |
---|
1865 | |
---|
1866 | getMaxZoom: function () { |
---|
1867 | var z1 = this.options.maxZoom === undefined ? Infinity : this.options.maxZoom, |
---|
1868 | z2 = this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom; |
---|
1869 | |
---|
1870 | return Math.min(z1, z2); |
---|
1871 | }, |
---|
1872 | |
---|
1873 | getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number |
---|
1874 | bounds = L.latLngBounds(bounds); |
---|
1875 | |
---|
1876 | var zoom = this.getMinZoom() - (inside ? 1 : 0), |
---|
1877 | maxZoom = this.getMaxZoom(), |
---|
1878 | size = this.getSize(), |
---|
1879 | |
---|
1880 | nw = bounds.getNorthWest(), |
---|
1881 | se = bounds.getSouthEast(), |
---|
1882 | |
---|
1883 | zoomNotFound = true, |
---|
1884 | boundsSize; |
---|
1885 | |
---|
1886 | padding = L.point(padding || [0, 0]); |
---|
1887 | |
---|
1888 | do { |
---|
1889 | zoom++; |
---|
1890 | boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding); |
---|
1891 | zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y; |
---|
1892 | |
---|
1893 | } while (zoomNotFound && zoom <= maxZoom); |
---|
1894 | |
---|
1895 | if (zoomNotFound && inside) { |
---|
1896 | return null; |
---|
1897 | } |
---|
1898 | |
---|
1899 | return inside ? zoom : zoom - 1; |
---|
1900 | }, |
---|
1901 | |
---|
1902 | getSize: function () { |
---|
1903 | if (!this._size || this._sizeChanged) { |
---|
1904 | this._size = new L.Point( |
---|
1905 | this._container.clientWidth, |
---|
1906 | this._container.clientHeight); |
---|
1907 | |
---|
1908 | this._sizeChanged = false; |
---|
1909 | } |
---|
1910 | return this._size.clone(); |
---|
1911 | }, |
---|
1912 | |
---|
1913 | getPixelBounds: function () { |
---|
1914 | var topLeftPoint = this._getTopLeftPoint(); |
---|
1915 | return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); |
---|
1916 | }, |
---|
1917 | |
---|
1918 | getPixelOrigin: function () { |
---|
1919 | this._checkIfLoaded(); |
---|
1920 | return this._initialTopLeftPoint; |
---|
1921 | }, |
---|
1922 | |
---|
1923 | getPanes: function () { |
---|
1924 | return this._panes; |
---|
1925 | }, |
---|
1926 | |
---|
1927 | getContainer: function () { |
---|
1928 | return this._container; |
---|
1929 | }, |
---|
1930 | |
---|
1931 | |
---|
1932 | // TODO replace with universal implementation after refactoring projections |
---|
1933 | |
---|
1934 | getZoomScale: function (toZoom) { |
---|
1935 | var crs = this.options.crs; |
---|
1936 | return crs.scale(toZoom) / crs.scale(this._zoom); |
---|
1937 | }, |
---|
1938 | |
---|
1939 | getScaleZoom: function (scale) { |
---|
1940 | return this._zoom + (Math.log(scale) / Math.LN2); |
---|
1941 | }, |
---|
1942 | |
---|
1943 | |
---|
1944 | // conversion methods |
---|
1945 | |
---|
1946 | project: function (latlng, zoom) { // (LatLng[, Number]) -> Point |
---|
1947 | zoom = zoom === undefined ? this._zoom : zoom; |
---|
1948 | return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); |
---|
1949 | }, |
---|
1950 | |
---|
1951 | unproject: function (point, zoom) { // (Point[, Number]) -> LatLng |
---|
1952 | zoom = zoom === undefined ? this._zoom : zoom; |
---|
1953 | return this.options.crs.pointToLatLng(L.point(point), zoom); |
---|
1954 | }, |
---|
1955 | |
---|
1956 | layerPointToLatLng: function (point) { // (Point) |
---|
1957 | var projectedPoint = L.point(point).add(this.getPixelOrigin()); |
---|
1958 | return this.unproject(projectedPoint); |
---|
1959 | }, |
---|
1960 | |
---|
1961 | latLngToLayerPoint: function (latlng) { // (LatLng) |
---|
1962 | var projectedPoint = this.project(L.latLng(latlng))._round(); |
---|
1963 | return projectedPoint._subtract(this.getPixelOrigin()); |
---|
1964 | }, |
---|
1965 | |
---|
1966 | containerPointToLayerPoint: function (point) { // (Point) |
---|
1967 | return L.point(point).subtract(this._getMapPanePos()); |
---|
1968 | }, |
---|
1969 | |
---|
1970 | layerPointToContainerPoint: function (point) { // (Point) |
---|
1971 | return L.point(point).add(this._getMapPanePos()); |
---|
1972 | }, |
---|
1973 | |
---|
1974 | containerPointToLatLng: function (point) { |
---|
1975 | var layerPoint = this.containerPointToLayerPoint(L.point(point)); |
---|
1976 | return this.layerPointToLatLng(layerPoint); |
---|
1977 | }, |
---|
1978 | |
---|
1979 | latLngToContainerPoint: function (latlng) { |
---|
1980 | return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); |
---|
1981 | }, |
---|
1982 | |
---|
1983 | mouseEventToContainerPoint: function (e) { // (MouseEvent) |
---|
1984 | return L.DomEvent.getMousePosition(e, this._container); |
---|
1985 | }, |
---|
1986 | |
---|
1987 | mouseEventToLayerPoint: function (e) { // (MouseEvent) |
---|
1988 | return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); |
---|
1989 | }, |
---|
1990 | |
---|
1991 | mouseEventToLatLng: function (e) { // (MouseEvent) |
---|
1992 | return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); |
---|
1993 | }, |
---|
1994 | |
---|
1995 | |
---|
1996 | // map initialization methods |
---|
1997 | |
---|
1998 | _initContainer: function (id) { |
---|
1999 | var container = this._container = L.DomUtil.get(id); |
---|
2000 | |
---|
2001 | if (!container) { |
---|
2002 | throw new Error('Map container not found.'); |
---|
2003 | } else if (container._leaflet) { |
---|
2004 | throw new Error('Map container is already initialized.'); |
---|
2005 | } |
---|
2006 | |
---|
2007 | container._leaflet = true; |
---|
2008 | }, |
---|
2009 | |
---|
2010 | _initLayout: function () { |
---|
2011 | var container = this._container; |
---|
2012 | |
---|
2013 | L.DomUtil.addClass(container, 'leaflet-container' + |
---|
2014 | (L.Browser.touch ? ' leaflet-touch' : '') + |
---|
2015 | (L.Browser.retina ? ' leaflet-retina' : '') + |
---|
2016 | (this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); |
---|
2017 | |
---|
2018 | var position = L.DomUtil.getStyle(container, 'position'); |
---|
2019 | |
---|
2020 | if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { |
---|
2021 | container.style.position = 'relative'; |
---|
2022 | } |
---|
2023 | |
---|
2024 | this._initPanes(); |
---|
2025 | |
---|
2026 | if (this._initControlPos) { |
---|
2027 | this._initControlPos(); |
---|
2028 | } |
---|
2029 | }, |
---|
2030 | |
---|
2031 | _initPanes: function () { |
---|
2032 | var panes = this._panes = {}; |
---|
2033 | |
---|
2034 | this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); |
---|
2035 | |
---|
2036 | this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); |
---|
2037 | panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); |
---|
2038 | panes.shadowPane = this._createPane('leaflet-shadow-pane'); |
---|
2039 | panes.overlayPane = this._createPane('leaflet-overlay-pane'); |
---|
2040 | panes.markerPane = this._createPane('leaflet-marker-pane'); |
---|
2041 | panes.popupPane = this._createPane('leaflet-popup-pane'); |
---|
2042 | |
---|
2043 | var zoomHide = ' leaflet-zoom-hide'; |
---|
2044 | |
---|
2045 | if (!this.options.markerZoomAnimation) { |
---|
2046 | L.DomUtil.addClass(panes.markerPane, zoomHide); |
---|
2047 | L.DomUtil.addClass(panes.shadowPane, zoomHide); |
---|
2048 | L.DomUtil.addClass(panes.popupPane, zoomHide); |
---|
2049 | } |
---|
2050 | }, |
---|
2051 | |
---|
2052 | _createPane: function (className, container) { |
---|
2053 | return L.DomUtil.create('div', className, container || this._panes.objectsPane); |
---|
2054 | }, |
---|
2055 | |
---|
2056 | _clearPanes: function () { |
---|
2057 | this._container.removeChild(this._mapPane); |
---|
2058 | }, |
---|
2059 | |
---|
2060 | _addLayers: function (layers) { |
---|
2061 | layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; |
---|
2062 | |
---|
2063 | for (var i = 0, len = layers.length; i < len; i++) { |
---|
2064 | this.addLayer(layers[i]); |
---|
2065 | } |
---|
2066 | }, |
---|
2067 | |
---|
2068 | |
---|
2069 | // private methods that modify map state |
---|
2070 | |
---|
2071 | _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { |
---|
2072 | |
---|
2073 | var zoomChanged = (this._zoom !== zoom); |
---|
2074 | |
---|
2075 | if (!afterZoomAnim) { |
---|
2076 | this.fire('movestart'); |
---|
2077 | |
---|
2078 | if (zoomChanged) { |
---|
2079 | this.fire('zoomstart'); |
---|
2080 | } |
---|
2081 | } |
---|
2082 | |
---|
2083 | this._zoom = zoom; |
---|
2084 | this._initialCenter = center; |
---|
2085 | |
---|
2086 | this._initialTopLeftPoint = this._getNewTopLeftPoint(center); |
---|
2087 | |
---|
2088 | if (!preserveMapOffset) { |
---|
2089 | L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); |
---|
2090 | } else { |
---|
2091 | this._initialTopLeftPoint._add(this._getMapPanePos()); |
---|
2092 | } |
---|
2093 | |
---|
2094 | this._tileLayersToLoad = this._tileLayersNum; |
---|
2095 | |
---|
2096 | var loading = !this._loaded; |
---|
2097 | this._loaded = true; |
---|
2098 | |
---|
2099 | if (loading) { |
---|
2100 | this.fire('load'); |
---|
2101 | this.eachLayer(this._layerAdd, this); |
---|
2102 | } |
---|
2103 | |
---|
2104 | this.fire('viewreset', {hard: !preserveMapOffset}); |
---|
2105 | |
---|
2106 | this.fire('move'); |
---|
2107 | |
---|
2108 | if (zoomChanged || afterZoomAnim) { |
---|
2109 | this.fire('zoomend'); |
---|
2110 | } |
---|
2111 | |
---|
2112 | this.fire('moveend', {hard: !preserveMapOffset}); |
---|
2113 | }, |
---|
2114 | |
---|
2115 | _rawPanBy: function (offset) { |
---|
2116 | L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); |
---|
2117 | }, |
---|
2118 | |
---|
2119 | _getZoomSpan: function () { |
---|
2120 | return this.getMaxZoom() - this.getMinZoom(); |
---|
2121 | }, |
---|
2122 | |
---|
2123 | _updateZoomLevels: function () { |
---|
2124 | var i, |
---|
2125 | minZoom = Infinity, |
---|
2126 | maxZoom = -Infinity, |
---|
2127 | oldZoomSpan = this._getZoomSpan(); |
---|
2128 | |
---|
2129 | for (i in this._zoomBoundLayers) { |
---|
2130 | var layer = this._zoomBoundLayers[i]; |
---|
2131 | if (!isNaN(layer.options.minZoom)) { |
---|
2132 | minZoom = Math.min(minZoom, layer.options.minZoom); |
---|
2133 | } |
---|
2134 | if (!isNaN(layer.options.maxZoom)) { |
---|
2135 | maxZoom = Math.max(maxZoom, layer.options.maxZoom); |
---|
2136 | } |
---|
2137 | } |
---|
2138 | |
---|
2139 | if (i === undefined) { // we have no tilelayers |
---|
2140 | this._layersMaxZoom = this._layersMinZoom = undefined; |
---|
2141 | } else { |
---|
2142 | this._layersMaxZoom = maxZoom; |
---|
2143 | this._layersMinZoom = minZoom; |
---|
2144 | } |
---|
2145 | |
---|
2146 | if (oldZoomSpan !== this._getZoomSpan()) { |
---|
2147 | this.fire('zoomlevelschange'); |
---|
2148 | } |
---|
2149 | }, |
---|
2150 | |
---|
2151 | _panInsideMaxBounds: function () { |
---|
2152 | this.panInsideBounds(this.options.maxBounds); |
---|
2153 | }, |
---|
2154 | |
---|
2155 | _checkIfLoaded: function () { |
---|
2156 | if (!this._loaded) { |
---|
2157 | throw new Error('Set map center and zoom first.'); |
---|
2158 | } |
---|
2159 | }, |
---|
2160 | |
---|
2161 | // map events |
---|
2162 | |
---|
2163 | _initEvents: function (onOff) { |
---|
2164 | if (!L.DomEvent) { return; } |
---|
2165 | |
---|
2166 | onOff = onOff || 'on'; |
---|
2167 | |
---|
2168 | L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this); |
---|
2169 | |
---|
2170 | var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', |
---|
2171 | 'mouseleave', 'mousemove', 'contextmenu'], |
---|
2172 | i, len; |
---|
2173 | |
---|
2174 | for (i = 0, len = events.length; i < len; i++) { |
---|
2175 | L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this); |
---|
2176 | } |
---|
2177 | |
---|
2178 | if (this.options.trackResize) { |
---|
2179 | L.DomEvent[onOff](window, 'resize', this._onResize, this); |
---|
2180 | } |
---|
2181 | }, |
---|
2182 | |
---|
2183 | _onResize: function () { |
---|
2184 | L.Util.cancelAnimFrame(this._resizeRequest); |
---|
2185 | this._resizeRequest = L.Util.requestAnimFrame( |
---|
2186 | this.invalidateSize, this, false, this._container); |
---|
2187 | }, |
---|
2188 | |
---|
2189 | _onMouseClick: function (e) { |
---|
2190 | // jshint camelcase: false |
---|
2191 | if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) || e._leaflet_stop) { return; } |
---|
2192 | |
---|
2193 | this.fire('preclick'); |
---|
2194 | this._fireMouseEvent(e); |
---|
2195 | }, |
---|
2196 | |
---|
2197 | _fireMouseEvent: function (e) { |
---|
2198 | // jshint camelcase: false |
---|
2199 | if (!this._loaded || e._leaflet_stop) { return; } |
---|
2200 | |
---|
2201 | var type = e.type; |
---|
2202 | |
---|
2203 | type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type)); |
---|
2204 | |
---|
2205 | if (!this.hasEventListeners(type)) { return; } |
---|
2206 | |
---|
2207 | if (type === 'contextmenu') { |
---|
2208 | L.DomEvent.preventDefault(e); |
---|
2209 | } |
---|
2210 | |
---|
2211 | var containerPoint = this.mouseEventToContainerPoint(e), |
---|
2212 | layerPoint = this.containerPointToLayerPoint(containerPoint), |
---|
2213 | latlng = this.layerPointToLatLng(layerPoint); |
---|
2214 | |
---|
2215 | this.fire(type, { |
---|
2216 | latlng: latlng, |
---|
2217 | layerPoint: layerPoint, |
---|
2218 | containerPoint: containerPoint, |
---|
2219 | originalEvent: e |
---|
2220 | }); |
---|
2221 | }, |
---|
2222 | |
---|
2223 | _onTileLayerLoad: function () { |
---|
2224 | this._tileLayersToLoad--; |
---|
2225 | if (this._tileLayersNum && !this._tileLayersToLoad) { |
---|
2226 | this.fire('tilelayersload'); |
---|
2227 | } |
---|
2228 | }, |
---|
2229 | |
---|
2230 | _clearHandlers: function () { |
---|
2231 | for (var i = 0, len = this._handlers.length; i < len; i++) { |
---|
2232 | this._handlers[i].disable(); |
---|
2233 | } |
---|
2234 | }, |
---|
2235 | |
---|
2236 | whenReady: function (callback, context) { |
---|
2237 | if (this._loaded) { |
---|
2238 | callback.call(context || this, this); |
---|
2239 | } else { |
---|
2240 | this.on('load', callback, context); |
---|
2241 | } |
---|
2242 | return this; |
---|
2243 | }, |
---|
2244 | |
---|
2245 | _layerAdd: function (layer) { |
---|
2246 | layer.onAdd(this); |
---|
2247 | this.fire('layeradd', {layer: layer}); |
---|
2248 | }, |
---|
2249 | |
---|
2250 | |
---|
2251 | // private methods for getting map state |
---|
2252 | |
---|
2253 | _getMapPanePos: function () { |
---|
2254 | return L.DomUtil.getPosition(this._mapPane); |
---|
2255 | }, |
---|
2256 | |
---|
2257 | _moved: function () { |
---|
2258 | var pos = this._getMapPanePos(); |
---|
2259 | return pos && !pos.equals([0, 0]); |
---|
2260 | }, |
---|
2261 | |
---|
2262 | _getTopLeftPoint: function () { |
---|
2263 | return this.getPixelOrigin().subtract(this._getMapPanePos()); |
---|
2264 | }, |
---|
2265 | |
---|
2266 | _getNewTopLeftPoint: function (center, zoom) { |
---|
2267 | var viewHalf = this.getSize()._divideBy(2); |
---|
2268 | // TODO round on display, not calculation to increase precision? |
---|
2269 | return this.project(center, zoom)._subtract(viewHalf)._round(); |
---|
2270 | }, |
---|
2271 | |
---|
2272 | _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) { |
---|
2273 | var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos()); |
---|
2274 | return this.project(latlng, newZoom)._subtract(topLeft); |
---|
2275 | }, |
---|
2276 | |
---|
2277 | // layer point of the current center |
---|
2278 | _getCenterLayerPoint: function () { |
---|
2279 | return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); |
---|
2280 | }, |
---|
2281 | |
---|
2282 | // offset of the specified place to the current center in pixels |
---|
2283 | _getCenterOffset: function (latlng) { |
---|
2284 | return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); |
---|
2285 | }, |
---|
2286 | |
---|
2287 | _limitZoom: function (zoom) { |
---|
2288 | var min = this.getMinZoom(), |
---|
2289 | max = this.getMaxZoom(); |
---|
2290 | |
---|
2291 | return Math.max(min, Math.min(max, zoom)); |
---|
2292 | } |
---|
2293 | }); |
---|
2294 | |
---|
2295 | L.map = function (id, options) { |
---|
2296 | return new L.Map(id, options); |
---|
2297 | }; |
---|
2298 | |
---|
2299 | |
---|
2300 | /* |
---|
2301 | * Mercator projection that takes into account that the Earth is not a perfect sphere. |
---|
2302 | * Less popular than spherical mercator; used by projections like EPSG:3395. |
---|
2303 | */ |
---|
2304 | |
---|
2305 | L.Projection.Mercator = { |
---|
2306 | MAX_LATITUDE: 85.0840591556, |
---|
2307 | |
---|
2308 | R_MINOR: 6356752.314245179, |
---|
2309 | R_MAJOR: 6378137, |
---|
2310 | |
---|
2311 | project: function (latlng) { // (LatLng) -> Point |
---|
2312 | var d = L.LatLng.DEG_TO_RAD, |
---|
2313 | max = this.MAX_LATITUDE, |
---|
2314 | lat = Math.max(Math.min(max, latlng.lat), -max), |
---|
2315 | r = this.R_MAJOR, |
---|
2316 | r2 = this.R_MINOR, |
---|
2317 | x = latlng.lng * d * r, |
---|
2318 | y = lat * d, |
---|
2319 | tmp = r2 / r, |
---|
2320 | eccent = Math.sqrt(1.0 - tmp * tmp), |
---|
2321 | con = eccent * Math.sin(y); |
---|
2322 | |
---|
2323 | con = Math.pow((1 - con) / (1 + con), eccent * 0.5); |
---|
2324 | |
---|
2325 | var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con; |
---|
2326 | y = -r * Math.log(ts); |
---|
2327 | |
---|
2328 | return new L.Point(x, y); |
---|
2329 | }, |
---|
2330 | |
---|
2331 | unproject: function (point) { // (Point, Boolean) -> LatLng |
---|
2332 | var d = L.LatLng.RAD_TO_DEG, |
---|
2333 | r = this.R_MAJOR, |
---|
2334 | r2 = this.R_MINOR, |
---|
2335 | lng = point.x * d / r, |
---|
2336 | tmp = r2 / r, |
---|
2337 | eccent = Math.sqrt(1 - (tmp * tmp)), |
---|
2338 | ts = Math.exp(- point.y / r), |
---|
2339 | phi = (Math.PI / 2) - 2 * Math.atan(ts), |
---|
2340 | numIter = 15, |
---|
2341 | tol = 1e-7, |
---|
2342 | i = numIter, |
---|
2343 | dphi = 0.1, |
---|
2344 | con; |
---|
2345 | |
---|
2346 | while ((Math.abs(dphi) > tol) && (--i > 0)) { |
---|
2347 | con = eccent * Math.sin(phi); |
---|
2348 | dphi = (Math.PI / 2) - 2 * Math.atan(ts * |
---|
2349 | Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi; |
---|
2350 | phi += dphi; |
---|
2351 | } |
---|
2352 | |
---|
2353 | return new L.LatLng(phi * d, lng); |
---|
2354 | } |
---|
2355 | }; |
---|
2356 | |
---|
2357 | |
---|
2358 | |
---|
2359 | L.CRS.EPSG3395 = L.extend({}, L.CRS, { |
---|
2360 | code: 'EPSG:3395', |
---|
2361 | |
---|
2362 | projection: L.Projection.Mercator, |
---|
2363 | |
---|
2364 | transformation: (function () { |
---|
2365 | var m = L.Projection.Mercator, |
---|
2366 | r = m.R_MAJOR, |
---|
2367 | r2 = m.R_MINOR; |
---|
2368 | |
---|
2369 | return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5); |
---|
2370 | }()) |
---|
2371 | }); |
---|
2372 | |
---|
2373 | |
---|
2374 | /* |
---|
2375 | * L.TileLayer is used for standard xyz-numbered tile layers. |
---|
2376 | */ |
---|
2377 | |
---|
2378 | L.TileLayer = L.Class.extend({ |
---|
2379 | includes: L.Mixin.Events, |
---|
2380 | |
---|
2381 | options: { |
---|
2382 | minZoom: 0, |
---|
2383 | maxZoom: 18, |
---|
2384 | tileSize: 256, |
---|
2385 | subdomains: 'abc', |
---|
2386 | errorTileUrl: '', |
---|
2387 | attribution: '', |
---|
2388 | zoomOffset: 0, |
---|
2389 | opacity: 1, |
---|
2390 | /* (undefined works too) |
---|
2391 | zIndex: null, |
---|
2392 | tms: false, |
---|
2393 | continuousWorld: false, |
---|
2394 | noWrap: false, |
---|
2395 | zoomReverse: false, |
---|
2396 | detectRetina: false, |
---|
2397 | reuseTiles: false, |
---|
2398 | bounds: false, |
---|
2399 | */ |
---|
2400 | unloadInvisibleTiles: L.Browser.mobile, |
---|
2401 | updateWhenIdle: L.Browser.mobile |
---|
2402 | }, |
---|
2403 | |
---|
2404 | initialize: function (url, options) { |
---|
2405 | options = L.setOptions(this, options); |
---|
2406 | |
---|
2407 | // detecting retina displays, adjusting tileSize and zoom levels |
---|
2408 | if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { |
---|
2409 | |
---|
2410 | options.tileSize = Math.floor(options.tileSize / 2); |
---|
2411 | options.zoomOffset++; |
---|
2412 | |
---|
2413 | if (options.minZoom > 0) { |
---|
2414 | options.minZoom--; |
---|
2415 | } |
---|
2416 | this.options.maxZoom--; |
---|
2417 | } |
---|
2418 | |
---|
2419 | if (options.bounds) { |
---|
2420 | options.bounds = L.latLngBounds(options.bounds); |
---|
2421 | } |
---|
2422 | |
---|
2423 | this._url = url; |
---|
2424 | |
---|
2425 | var subdomains = this.options.subdomains; |
---|
2426 | |
---|
2427 | if (typeof subdomains === 'string') { |
---|
2428 | this.options.subdomains = subdomains.split(''); |
---|
2429 | } |
---|
2430 | }, |
---|
2431 | |
---|
2432 | onAdd: function (map) { |
---|
2433 | this._map = map; |
---|
2434 | this._animated = map._zoomAnimated; |
---|
2435 | |
---|
2436 | // create a container div for tiles |
---|
2437 | this._initContainer(); |
---|
2438 | |
---|
2439 | // create an image to clone for tiles |
---|
2440 | this._createTileProto(); |
---|
2441 | |
---|
2442 | // set up events |
---|
2443 | map.on({ |
---|
2444 | 'viewreset': this._reset, |
---|
2445 | 'moveend': this._update |
---|
2446 | }, this); |
---|
2447 | |
---|
2448 | if (this._animated) { |
---|
2449 | map.on({ |
---|
2450 | 'zoomanim': this._animateZoom, |
---|
2451 | 'zoomend': this._endZoomAnim |
---|
2452 | }, this); |
---|
2453 | } |
---|
2454 | |
---|
2455 | if (!this.options.updateWhenIdle) { |
---|
2456 | this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this); |
---|
2457 | map.on('move', this._limitedUpdate, this); |
---|
2458 | } |
---|
2459 | |
---|
2460 | this._reset(); |
---|
2461 | this._update(); |
---|
2462 | }, |
---|
2463 | |
---|
2464 | addTo: function (map) { |
---|
2465 | map.addLayer(this); |
---|
2466 | return this; |
---|
2467 | }, |
---|
2468 | |
---|
2469 | onRemove: function (map) { |
---|
2470 | this._container.parentNode.removeChild(this._container); |
---|
2471 | |
---|
2472 | map.off({ |
---|
2473 | 'viewreset': this._reset, |
---|
2474 | 'moveend': this._update |
---|
2475 | }, this); |
---|
2476 | |
---|
2477 | if (this._animated) { |
---|
2478 | map.off({ |
---|
2479 | 'zoomanim': this._animateZoom, |
---|
2480 | 'zoomend': this._endZoomAnim |
---|
2481 | }, this); |
---|
2482 | } |
---|
2483 | |
---|
2484 | if (!this.options.updateWhenIdle) { |
---|
2485 | map.off('move', this._limitedUpdate, this); |
---|
2486 | } |
---|
2487 | |
---|
2488 | this._container = null; |
---|
2489 | this._map = null; |
---|
2490 | }, |
---|
2491 | |
---|
2492 | bringToFront: function () { |
---|
2493 | var pane = this._map._panes.tilePane; |
---|
2494 | |
---|
2495 | if (this._container) { |
---|
2496 | pane.appendChild(this._container); |
---|
2497 | this._setAutoZIndex(pane, Math.max); |
---|
2498 | } |
---|
2499 | |
---|
2500 | return this; |
---|
2501 | }, |
---|
2502 | |
---|
2503 | bringToBack: function () { |
---|
2504 | var pane = this._map._panes.tilePane; |
---|
2505 | |
---|
2506 | if (this._container) { |
---|
2507 | pane.insertBefore(this._container, pane.firstChild); |
---|
2508 | this._setAutoZIndex(pane, Math.min); |
---|
2509 | } |
---|
2510 | |
---|
2511 | return this; |
---|
2512 | }, |
---|
2513 | |
---|
2514 | getAttribution: function () { |
---|
2515 | return this.options.attribution; |
---|
2516 | }, |
---|
2517 | |
---|
2518 | getContainer: function () { |
---|
2519 | return this._container; |
---|
2520 | }, |
---|
2521 | |
---|
2522 | setOpacity: function (opacity) { |
---|
2523 | this.options.opacity = opacity; |
---|
2524 | |
---|
2525 | if (this._map) { |
---|
2526 | this._updateOpacity(); |
---|
2527 | } |
---|
2528 | |
---|
2529 | return this; |
---|
2530 | }, |
---|
2531 | |
---|
2532 | setZIndex: function (zIndex) { |
---|
2533 | this.options.zIndex = zIndex; |
---|
2534 | this._updateZIndex(); |
---|
2535 | |
---|
2536 | return this; |
---|
2537 | }, |
---|
2538 | |
---|
2539 | setUrl: function (url, noRedraw) { |
---|
2540 | this._url = url; |
---|
2541 | |
---|
2542 | if (!noRedraw) { |
---|
2543 | this.redraw(); |
---|
2544 | } |
---|
2545 | |
---|
2546 | return this; |
---|
2547 | }, |
---|
2548 | |
---|
2549 | redraw: function () { |
---|
2550 | if (this._map) { |
---|
2551 | this._reset({hard: true}); |
---|
2552 | this._update(); |
---|
2553 | } |
---|
2554 | return this; |
---|
2555 | }, |
---|
2556 | |
---|
2557 | _updateZIndex: function () { |
---|
2558 | if (this._container && this.options.zIndex !== undefined) { |
---|
2559 | this._container.style.zIndex = this.options.zIndex; |
---|
2560 | } |
---|
2561 | }, |
---|
2562 | |
---|
2563 | _setAutoZIndex: function (pane, compare) { |
---|
2564 | |
---|
2565 | var layers = pane.children, |
---|
2566 | edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min |
---|
2567 | zIndex, i, len; |
---|
2568 | |
---|
2569 | for (i = 0, len = layers.length; i < len; i++) { |
---|
2570 | |
---|
2571 | if (layers[i] !== this._container) { |
---|
2572 | zIndex = parseInt(layers[i].style.zIndex, 10); |
---|
2573 | |
---|
2574 | if (!isNaN(zIndex)) { |
---|
2575 | edgeZIndex = compare(edgeZIndex, zIndex); |
---|
2576 | } |
---|
2577 | } |
---|
2578 | } |
---|
2579 | |
---|
2580 | this.options.zIndex = this._container.style.zIndex = |
---|
2581 | (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1); |
---|
2582 | }, |
---|
2583 | |
---|
2584 | _updateOpacity: function () { |
---|
2585 | var i, |
---|
2586 | tiles = this._tiles; |
---|
2587 | |
---|
2588 | if (L.Browser.ielt9) { |
---|
2589 | for (i in tiles) { |
---|
2590 | L.DomUtil.setOpacity(tiles[i], this.options.opacity); |
---|
2591 | } |
---|
2592 | } else { |
---|
2593 | L.DomUtil.setOpacity(this._container, this.options.opacity); |
---|
2594 | } |
---|
2595 | }, |
---|
2596 | |
---|
2597 | _initContainer: function () { |
---|
2598 | var tilePane = this._map._panes.tilePane; |
---|
2599 | |
---|
2600 | if (!this._container) { |
---|
2601 | this._container = L.DomUtil.create('div', 'leaflet-layer'); |
---|
2602 | |
---|
2603 | this._updateZIndex(); |
---|
2604 | |
---|
2605 | if (this._animated) { |
---|
2606 | var className = 'leaflet-tile-container leaflet-zoom-animated'; |
---|
2607 | |
---|
2608 | this._bgBuffer = L.DomUtil.create('div', className, this._container); |
---|
2609 | this._tileContainer = L.DomUtil.create('div', className, this._container); |
---|
2610 | |
---|
2611 | } else { |
---|
2612 | this._tileContainer = this._container; |
---|
2613 | } |
---|
2614 | |
---|
2615 | tilePane.appendChild(this._container); |
---|
2616 | |
---|
2617 | if (this.options.opacity < 1) { |
---|
2618 | this._updateOpacity(); |
---|
2619 | } |
---|
2620 | } |
---|
2621 | }, |
---|
2622 | |
---|
2623 | _reset: function (e) { |
---|
2624 | for (var key in this._tiles) { |
---|
2625 | this.fire('tileunload', {tile: this._tiles[key]}); |
---|
2626 | } |
---|
2627 | |
---|
2628 | this._tiles = {}; |
---|
2629 | this._tilesToLoad = 0; |
---|
2630 | |
---|
2631 | if (this.options.reuseTiles) { |
---|
2632 | this._unusedTiles = []; |
---|
2633 | } |
---|
2634 | |
---|
2635 | this._tileContainer.innerHTML = ''; |
---|
2636 | |
---|
2637 | if (this._animated && e && e.hard) { |
---|
2638 | this._clearBgBuffer(); |
---|
2639 | } |
---|
2640 | |
---|
2641 | this._initContainer(); |
---|
2642 | }, |
---|
2643 | |
---|
2644 | _update: function () { |
---|
2645 | |
---|
2646 | if (!this._map) { return; } |
---|
2647 | |
---|
2648 | var bounds = this._map.getPixelBounds(), |
---|
2649 | zoom = this._map.getZoom(), |
---|
2650 | tileSize = this.options.tileSize; |
---|
2651 | |
---|
2652 | if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { |
---|
2653 | return; |
---|
2654 | } |
---|
2655 | |
---|
2656 | var tileBounds = L.bounds( |
---|
2657 | bounds.min.divideBy(tileSize)._floor(), |
---|
2658 | bounds.max.divideBy(tileSize)._floor()); |
---|
2659 | |
---|
2660 | this._addTilesFromCenterOut(tileBounds); |
---|
2661 | |
---|
2662 | if (this.options.unloadInvisibleTiles || this.options.reuseTiles) { |
---|
2663 | this._removeOtherTiles(tileBounds); |
---|
2664 | } |
---|
2665 | }, |
---|
2666 | |
---|
2667 | _addTilesFromCenterOut: function (bounds) { |
---|
2668 | var queue = [], |
---|
2669 | center = bounds.getCenter(); |
---|
2670 | |
---|
2671 | var j, i, point; |
---|
2672 | |
---|
2673 | for (j = bounds.min.y; j <= bounds.max.y; j++) { |
---|
2674 | for (i = bounds.min.x; i <= bounds.max.x; i++) { |
---|
2675 | point = new L.Point(i, j); |
---|
2676 | |
---|
2677 | if (this._tileShouldBeLoaded(point)) { |
---|
2678 | queue.push(point); |
---|
2679 | } |
---|
2680 | } |
---|
2681 | } |
---|
2682 | |
---|
2683 | var tilesToLoad = queue.length; |
---|
2684 | |
---|
2685 | if (tilesToLoad === 0) { return; } |
---|
2686 | |
---|
2687 | // load tiles in order of their distance to center |
---|
2688 | queue.sort(function (a, b) { |
---|
2689 | return a.distanceTo(center) - b.distanceTo(center); |
---|
2690 | }); |
---|
2691 | |
---|
2692 | var fragment = document.createDocumentFragment(); |
---|
2693 | |
---|
2694 | // if its the first batch of tiles to load |
---|
2695 | if (!this._tilesToLoad) { |
---|
2696 | this.fire('loading'); |
---|
2697 | } |
---|
2698 | |
---|
2699 | this._tilesToLoad += tilesToLoad; |
---|
2700 | |
---|
2701 | for (i = 0; i < tilesToLoad; i++) { |
---|
2702 | this._addTile(queue[i], fragment); |
---|
2703 | } |
---|
2704 | |
---|
2705 | this._tileContainer.appendChild(fragment); |
---|
2706 | }, |
---|
2707 | |
---|
2708 | _tileShouldBeLoaded: function (tilePoint) { |
---|
2709 | if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) { |
---|
2710 | return false; // already loaded |
---|
2711 | } |
---|
2712 | |
---|
2713 | var options = this.options; |
---|
2714 | |
---|
2715 | if (!options.continuousWorld) { |
---|
2716 | var limit = this._getWrapTileNum(); |
---|
2717 | |
---|
2718 | // don't load if exceeds world bounds |
---|
2719 | if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) || |
---|
2720 | tilePoint.y < 0 || tilePoint.y >= limit) { return false; } |
---|
2721 | } |
---|
2722 | |
---|
2723 | if (options.bounds) { |
---|
2724 | var tileSize = options.tileSize, |
---|
2725 | nwPoint = tilePoint.multiplyBy(tileSize), |
---|
2726 | sePoint = nwPoint.add([tileSize, tileSize]), |
---|
2727 | nw = this._map.unproject(nwPoint), |
---|
2728 | se = this._map.unproject(sePoint); |
---|
2729 | |
---|
2730 | // TODO temporary hack, will be removed after refactoring projections |
---|
2731 | // https://github.com/Leaflet/Leaflet/issues/1618 |
---|
2732 | if (!options.continuousWorld && !options.noWrap) { |
---|
2733 | nw = nw.wrap(); |
---|
2734 | se = se.wrap(); |
---|
2735 | } |
---|
2736 | |
---|
2737 | if (!options.bounds.intersects([nw, se])) { return false; } |
---|
2738 | } |
---|
2739 | |
---|
2740 | return true; |
---|
2741 | }, |
---|
2742 | |
---|
2743 | _removeOtherTiles: function (bounds) { |
---|
2744 | var kArr, x, y, key; |
---|
2745 | |
---|
2746 | for (key in this._tiles) { |
---|
2747 | kArr = key.split(':'); |
---|
2748 | x = parseInt(kArr[0], 10); |
---|
2749 | y = parseInt(kArr[1], 10); |
---|
2750 | |
---|
2751 | // remove tile if it's out of bounds |
---|
2752 | if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) { |
---|
2753 | this._removeTile(key); |
---|
2754 | } |
---|
2755 | } |
---|
2756 | }, |
---|
2757 | |
---|
2758 | _removeTile: function (key) { |
---|
2759 | var tile = this._tiles[key]; |
---|
2760 | |
---|
2761 | this.fire('tileunload', {tile: tile, url: tile.src}); |
---|
2762 | |
---|
2763 | if (this.options.reuseTiles) { |
---|
2764 | L.DomUtil.removeClass(tile, 'leaflet-tile-loaded'); |
---|
2765 | this._unusedTiles.push(tile); |
---|
2766 | |
---|
2767 | } else if (tile.parentNode === this._tileContainer) { |
---|
2768 | this._tileContainer.removeChild(tile); |
---|
2769 | } |
---|
2770 | |
---|
2771 | // for https://github.com/CloudMade/Leaflet/issues/137 |
---|
2772 | if (!L.Browser.android) { |
---|
2773 | tile.onload = null; |
---|
2774 | tile.src = L.Util.emptyImageUrl; |
---|
2775 | } |
---|
2776 | |
---|
2777 | delete this._tiles[key]; |
---|
2778 | }, |
---|
2779 | |
---|
2780 | _addTile: function (tilePoint, container) { |
---|
2781 | var tilePos = this._getTilePos(tilePoint); |
---|
2782 | |
---|
2783 | // get unused tile - or create a new tile |
---|
2784 | var tile = this._getTile(); |
---|
2785 | |
---|
2786 | /* |
---|
2787 | Chrome 20 layouts much faster with top/left (verify with timeline, frames) |
---|
2788 | Android 4 browser has display issues with top/left and requires transform instead |
---|
2789 | Android 2 browser requires top/left or tiles disappear on load or first drag |
---|
2790 | (reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866 |
---|
2791 | (other browsers don't currently care) - see debug/hacks/jitter.html for an example |
---|
2792 | */ |
---|
2793 | L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23); |
---|
2794 | |
---|
2795 | this._tiles[tilePoint.x + ':' + tilePoint.y] = tile; |
---|
2796 | |
---|
2797 | this._loadTile(tile, tilePoint); |
---|
2798 | |
---|
2799 | if (tile.parentNode !== this._tileContainer) { |
---|
2800 | container.appendChild(tile); |
---|
2801 | } |
---|
2802 | }, |
---|
2803 | |
---|
2804 | _getZoomForUrl: function () { |
---|
2805 | |
---|
2806 | var options = this.options, |
---|
2807 | zoom = this._map.getZoom(); |
---|
2808 | |
---|
2809 | if (options.zoomReverse) { |
---|
2810 | zoom = options.maxZoom - zoom; |
---|
2811 | } |
---|
2812 | |
---|
2813 | return zoom + options.zoomOffset; |
---|
2814 | }, |
---|
2815 | |
---|
2816 | _getTilePos: function (tilePoint) { |
---|
2817 | var origin = this._map.getPixelOrigin(), |
---|
2818 | tileSize = this.options.tileSize; |
---|
2819 | |
---|
2820 | return tilePoint.multiplyBy(tileSize).subtract(origin); |
---|
2821 | }, |
---|
2822 | |
---|
2823 | // image-specific code (override to implement e.g. Canvas or SVG tile layer) |
---|
2824 | |
---|
2825 | getTileUrl: function (tilePoint) { |
---|
2826 | return L.Util.template(this._url, L.extend({ |
---|
2827 | s: this._getSubdomain(tilePoint), |
---|
2828 | z: tilePoint.z, |
---|
2829 | x: tilePoint.x, |
---|
2830 | y: tilePoint.y |
---|
2831 | }, this.options)); |
---|
2832 | }, |
---|
2833 | |
---|
2834 | _getWrapTileNum: function () { |
---|
2835 | // TODO refactor, limit is not valid for non-standard projections |
---|
2836 | return Math.pow(2, this._getZoomForUrl()); |
---|
2837 | }, |
---|
2838 | |
---|
2839 | _adjustTilePoint: function (tilePoint) { |
---|
2840 | |
---|
2841 | var limit = this._getWrapTileNum(); |
---|
2842 | |
---|
2843 | // wrap tile coordinates |
---|
2844 | if (!this.options.continuousWorld && !this.options.noWrap) { |
---|
2845 | tilePoint.x = ((tilePoint.x % limit) + limit) % limit; |
---|
2846 | } |
---|
2847 | |
---|
2848 | if (this.options.tms) { |
---|
2849 | tilePoint.y = limit - tilePoint.y - 1; |
---|
2850 | } |
---|
2851 | |
---|
2852 | tilePoint.z = this._getZoomForUrl(); |
---|
2853 | }, |
---|
2854 | |
---|
2855 | _getSubdomain: function (tilePoint) { |
---|
2856 | var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; |
---|
2857 | return this.options.subdomains[index]; |
---|
2858 | }, |
---|
2859 | |
---|
2860 | _createTileProto: function () { |
---|
2861 | var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile'); |
---|
2862 | img.style.width = img.style.height = this.options.tileSize + 'px'; |
---|
2863 | img.galleryimg = 'no'; |
---|
2864 | }, |
---|
2865 | |
---|
2866 | _getTile: function () { |
---|
2867 | if (this.options.reuseTiles && this._unusedTiles.length > 0) { |
---|
2868 | var tile = this._unusedTiles.pop(); |
---|
2869 | this._resetTile(tile); |
---|
2870 | return tile; |
---|
2871 | } |
---|
2872 | return this._createTile(); |
---|
2873 | }, |
---|
2874 | |
---|
2875 | // Override if data stored on a tile needs to be cleaned up before reuse |
---|
2876 | _resetTile: function (/*tile*/) {}, |
---|
2877 | |
---|
2878 | _createTile: function () { |
---|
2879 | var tile = this._tileImg.cloneNode(false); |
---|
2880 | tile.onselectstart = tile.onmousemove = L.Util.falseFn; |
---|
2881 | |
---|
2882 | if (L.Browser.ielt9 && this.options.opacity !== undefined) { |
---|
2883 | L.DomUtil.setOpacity(tile, this.options.opacity); |
---|
2884 | } |
---|
2885 | return tile; |
---|
2886 | }, |
---|
2887 | |
---|
2888 | _loadTile: function (tile, tilePoint) { |
---|
2889 | tile._layer = this; |
---|
2890 | tile.onload = this._tileOnLoad; |
---|
2891 | tile.onerror = this._tileOnError; |
---|
2892 | |
---|
2893 | this._adjustTilePoint(tilePoint); |
---|
2894 | tile.src = this.getTileUrl(tilePoint); |
---|
2895 | }, |
---|
2896 | |
---|
2897 | _tileLoaded: function () { |
---|
2898 | this._tilesToLoad--; |
---|
2899 | if (!this._tilesToLoad) { |
---|
2900 | this.fire('load'); |
---|
2901 | |
---|
2902 | if (this._animated) { |
---|
2903 | // clear scaled tiles after all new tiles are loaded (for performance) |
---|
2904 | clearTimeout(this._clearBgBufferTimer); |
---|
2905 | this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500); |
---|
2906 | } |
---|
2907 | } |
---|
2908 | }, |
---|
2909 | |
---|
2910 | _tileOnLoad: function () { |
---|
2911 | var layer = this._layer; |
---|
2912 | |
---|
2913 | //Only if we are loading an actual image |
---|
2914 | if (this.src !== L.Util.emptyImageUrl) { |
---|
2915 | L.DomUtil.addClass(this, 'leaflet-tile-loaded'); |
---|
2916 | |
---|
2917 | layer.fire('tileload', { |
---|
2918 | tile: this, |
---|
2919 | url: this.src |
---|
2920 | }); |
---|
2921 | } |
---|
2922 | |
---|
2923 | layer._tileLoaded(); |
---|
2924 | }, |
---|
2925 | |
---|
2926 | _tileOnError: function () { |
---|
2927 | var layer = this._layer; |
---|
2928 | |
---|
2929 | layer.fire('tileerror', { |
---|
2930 | tile: this, |
---|
2931 | url: this.src |
---|
2932 | }); |
---|
2933 | |
---|
2934 | var newUrl = layer.options.errorTileUrl; |
---|
2935 | if (newUrl) { |
---|
2936 | this.src = newUrl; |
---|
2937 | } |
---|
2938 | |
---|
2939 | layer._tileLoaded(); |
---|
2940 | } |
---|
2941 | }); |
---|
2942 | |
---|
2943 | L.tileLayer = function (url, options) { |
---|
2944 | return new L.TileLayer(url, options); |
---|
2945 | }; |
---|
2946 | |
---|
2947 | |
---|
2948 | /* |
---|
2949 | * L.TileLayer.WMS is used for putting WMS tile layers on the map. |
---|
2950 | */ |
---|
2951 | |
---|
2952 | L.TileLayer.WMS = L.TileLayer.extend({ |
---|
2953 | |
---|
2954 | defaultWmsParams: { |
---|
2955 | service: 'WMS', |
---|
2956 | request: 'GetMap', |
---|
2957 | version: '1.1.1', |
---|
2958 | layers: '', |
---|
2959 | styles: '', |
---|
2960 | format: 'image/jpeg', |
---|
2961 | transparent: false |
---|
2962 | }, |
---|
2963 | |
---|
2964 | initialize: function (url, options) { // (String, Object) |
---|
2965 | |
---|
2966 | this._url = url; |
---|
2967 | |
---|
2968 | var wmsParams = L.extend({}, this.defaultWmsParams), |
---|
2969 | tileSize = options.tileSize || this.options.tileSize; |
---|
2970 | |
---|
2971 | if (options.detectRetina && L.Browser.retina) { |
---|
2972 | wmsParams.width = wmsParams.height = tileSize * 2; |
---|
2973 | } else { |
---|
2974 | wmsParams.width = wmsParams.height = tileSize; |
---|
2975 | } |
---|
2976 | |
---|
2977 | for (var i in options) { |
---|
2978 | // all keys that are not TileLayer options go to WMS params |
---|
2979 | if (!this.options.hasOwnProperty(i) && i !== 'crs') { |
---|
2980 | wmsParams[i] = options[i]; |
---|
2981 | } |
---|
2982 | } |
---|
2983 | |
---|
2984 | this.wmsParams = wmsParams; |
---|
2985 | |
---|
2986 | L.setOptions(this, options); |
---|
2987 | }, |
---|
2988 | |
---|
2989 | onAdd: function (map) { |
---|
2990 | |
---|
2991 | this._crs = this.options.crs || map.options.crs; |
---|
2992 | |
---|
2993 | var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs'; |
---|
2994 | this.wmsParams[projectionKey] = this._crs.code; |
---|
2995 | |
---|
2996 | L.TileLayer.prototype.onAdd.call(this, map); |
---|
2997 | }, |
---|
2998 | |
---|
2999 | getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String |
---|
3000 | |
---|
3001 | var map = this._map, |
---|
3002 | tileSize = this.options.tileSize, |
---|
3003 | |
---|
3004 | nwPoint = tilePoint.multiplyBy(tileSize), |
---|
3005 | sePoint = nwPoint.add([tileSize, tileSize]), |
---|
3006 | |
---|
3007 | nw = this._crs.project(map.unproject(nwPoint, zoom)), |
---|
3008 | se = this._crs.project(map.unproject(sePoint, zoom)), |
---|
3009 | |
---|
3010 | bbox = [nw.x, se.y, se.x, nw.y].join(','), |
---|
3011 | |
---|
3012 | url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)}); |
---|
3013 | |
---|
3014 | return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox; |
---|
3015 | }, |
---|
3016 | |
---|
3017 | setParams: function (params, noRedraw) { |
---|
3018 | |
---|
3019 | L.extend(this.wmsParams, params); |
---|
3020 | |
---|
3021 | if (!noRedraw) { |
---|
3022 | this.redraw(); |
---|
3023 | } |
---|
3024 | |
---|
3025 | return this; |
---|
3026 | } |
---|
3027 | }); |
---|
3028 | |
---|
3029 | L.tileLayer.wms = function (url, options) { |
---|
3030 | return new L.TileLayer.WMS(url, options); |
---|
3031 | }; |
---|
3032 | |
---|
3033 | |
---|
3034 | /* |
---|
3035 | * L.TileLayer.Canvas is a class that you can use as a base for creating |
---|
3036 | * dynamically drawn Canvas-based tile layers. |
---|
3037 | */ |
---|
3038 | |
---|
3039 | L.TileLayer.Canvas = L.TileLayer.extend({ |
---|
3040 | options: { |
---|
3041 | async: false |
---|
3042 | }, |
---|
3043 | |
---|
3044 | initialize: function (options) { |
---|
3045 | L.setOptions(this, options); |
---|
3046 | }, |
---|
3047 | |
---|
3048 | redraw: function () { |
---|
3049 | for (var i in this._tiles) { |
---|
3050 | this._redrawTile(this._tiles[i]); |
---|
3051 | } |
---|
3052 | return this; |
---|
3053 | }, |
---|
3054 | |
---|
3055 | _redrawTile: function (tile) { |
---|
3056 | this.drawTile(tile, tile._tilePoint, this._map._zoom); |
---|
3057 | }, |
---|
3058 | |
---|
3059 | _createTileProto: function () { |
---|
3060 | var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile'); |
---|
3061 | proto.width = proto.height = this.options.tileSize; |
---|
3062 | }, |
---|
3063 | |
---|
3064 | _createTile: function () { |
---|
3065 | var tile = this._canvasProto.cloneNode(false); |
---|
3066 | tile.onselectstart = tile.onmousemove = L.Util.falseFn; |
---|
3067 | return tile; |
---|
3068 | }, |
---|
3069 | |
---|
3070 | _loadTile: function (tile, tilePoint) { |
---|
3071 | tile._layer = this; |
---|
3072 | tile._tilePoint = tilePoint; |
---|
3073 | |
---|
3074 | this._redrawTile(tile); |
---|
3075 | |
---|
3076 | if (!this.options.async) { |
---|
3077 | this.tileDrawn(tile); |
---|
3078 | } |
---|
3079 | }, |
---|
3080 | |
---|
3081 | drawTile: function (/*tile, tilePoint*/) { |
---|
3082 | // override with rendering code |
---|
3083 | }, |
---|
3084 | |
---|
3085 | tileDrawn: function (tile) { |
---|
3086 | this._tileOnLoad.call(tile); |
---|
3087 | } |
---|
3088 | }); |
---|
3089 | |
---|
3090 | |
---|
3091 | L.tileLayer.canvas = function (options) { |
---|
3092 | return new L.TileLayer.Canvas(options); |
---|
3093 | }; |
---|
3094 | |
---|
3095 | |
---|
3096 | /* |
---|
3097 | * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). |
---|
3098 | */ |
---|
3099 | |
---|
3100 | L.ImageOverlay = L.Class.extend({ |
---|
3101 | includes: L.Mixin.Events, |
---|
3102 | |
---|
3103 | options: { |
---|
3104 | opacity: 1 |
---|
3105 | }, |
---|
3106 | |
---|
3107 | initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) |
---|
3108 | this._url = url; |
---|
3109 | this._bounds = L.latLngBounds(bounds); |
---|
3110 | |
---|
3111 | L.setOptions(this, options); |
---|
3112 | }, |
---|
3113 | |
---|
3114 | onAdd: function (map) { |
---|
3115 | this._map = map; |
---|
3116 | |
---|
3117 | if (!this._image) { |
---|
3118 | this._initImage(); |
---|
3119 | } |
---|
3120 | |
---|
3121 | map._panes.overlayPane.appendChild(this._image); |
---|
3122 | |
---|
3123 | map.on('viewreset', this._reset, this); |
---|
3124 | |
---|
3125 | if (map.options.zoomAnimation && L.Browser.any3d) { |
---|
3126 | map.on('zoomanim', this._animateZoom, this); |
---|
3127 | } |
---|
3128 | |
---|
3129 | this._reset(); |
---|
3130 | }, |
---|
3131 | |
---|
3132 | onRemove: function (map) { |
---|
3133 | map.getPanes().overlayPane.removeChild(this._image); |
---|
3134 | |
---|
3135 | map.off('viewreset', this._reset, this); |
---|
3136 | |
---|
3137 | if (map.options.zoomAnimation) { |
---|
3138 | map.off('zoomanim', this._animateZoom, this); |
---|
3139 | } |
---|
3140 | }, |
---|
3141 | |
---|
3142 | addTo: function (map) { |
---|
3143 | map.addLayer(this); |
---|
3144 | return this; |
---|
3145 | }, |
---|
3146 | |
---|
3147 | setOpacity: function (opacity) { |
---|
3148 | this.options.opacity = opacity; |
---|
3149 | this._updateOpacity(); |
---|
3150 | return this; |
---|
3151 | }, |
---|
3152 | |
---|
3153 | // TODO remove bringToFront/bringToBack duplication from TileLayer/Path |
---|
3154 | bringToFront: function () { |
---|
3155 | if (this._image) { |
---|
3156 | this._map._panes.overlayPane.appendChild(this._image); |
---|
3157 | } |
---|
3158 | return this; |
---|
3159 | }, |
---|
3160 | |
---|
3161 | bringToBack: function () { |
---|
3162 | var pane = this._map._panes.overlayPane; |
---|
3163 | if (this._image) { |
---|
3164 | pane.insertBefore(this._image, pane.firstChild); |
---|
3165 | } |
---|
3166 | return this; |
---|
3167 | }, |
---|
3168 | |
---|
3169 | _initImage: function () { |
---|
3170 | this._image = L.DomUtil.create('img', 'leaflet-image-layer'); |
---|
3171 | |
---|
3172 | if (this._map.options.zoomAnimation && L.Browser.any3d) { |
---|
3173 | L.DomUtil.addClass(this._image, 'leaflet-zoom-animated'); |
---|
3174 | } else { |
---|
3175 | L.DomUtil.addClass(this._image, 'leaflet-zoom-hide'); |
---|
3176 | } |
---|
3177 | |
---|
3178 | this._updateOpacity(); |
---|
3179 | |
---|
3180 | //TODO createImage util method to remove duplication |
---|
3181 | L.extend(this._image, { |
---|
3182 | galleryimg: 'no', |
---|
3183 | onselectstart: L.Util.falseFn, |
---|
3184 | onmousemove: L.Util.falseFn, |
---|
3185 | onload: L.bind(this._onImageLoad, this), |
---|
3186 | src: this._url |
---|
3187 | }); |
---|
3188 | }, |
---|
3189 | |
---|
3190 | _animateZoom: function (e) { |
---|
3191 | var map = this._map, |
---|
3192 | image = this._image, |
---|
3193 | scale = map.getZoomScale(e.zoom), |
---|
3194 | nw = this._bounds.getNorthWest(), |
---|
3195 | se = this._bounds.getSouthEast(), |
---|
3196 | |
---|
3197 | topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center), |
---|
3198 | size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft), |
---|
3199 | origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale))); |
---|
3200 | |
---|
3201 | image.style[L.DomUtil.TRANSFORM] = |
---|
3202 | L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; |
---|
3203 | }, |
---|
3204 | |
---|
3205 | _reset: function () { |
---|
3206 | var image = this._image, |
---|
3207 | topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()), |
---|
3208 | size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft); |
---|
3209 | |
---|
3210 | L.DomUtil.setPosition(image, topLeft); |
---|
3211 | |
---|
3212 | image.style.width = size.x + 'px'; |
---|
3213 | image.style.height = size.y + 'px'; |
---|
3214 | }, |
---|
3215 | |
---|
3216 | _onImageLoad: function () { |
---|
3217 | this.fire('load'); |
---|
3218 | }, |
---|
3219 | |
---|
3220 | _updateOpacity: function () { |
---|
3221 | L.DomUtil.setOpacity(this._image, this.options.opacity); |
---|
3222 | } |
---|
3223 | }); |
---|
3224 | |
---|
3225 | L.imageOverlay = function (url, bounds, options) { |
---|
3226 | return new L.ImageOverlay(url, bounds, options); |
---|
3227 | }; |
---|
3228 | |
---|
3229 | |
---|
3230 | /* |
---|
3231 | * L.Icon is an image-based icon class that you can use with L.Marker for custom markers. |
---|
3232 | */ |
---|
3233 | |
---|
3234 | L.Icon = L.Class.extend({ |
---|
3235 | options: { |
---|
3236 | /* |
---|
3237 | iconUrl: (String) (required) |
---|
3238 | iconRetinaUrl: (String) (optional, used for retina devices if detected) |
---|
3239 | iconSize: (Point) (can be set through CSS) |
---|
3240 | iconAnchor: (Point) (centered by default, can be set in CSS with negative margins) |
---|
3241 | popupAnchor: (Point) (if not specified, popup opens in the anchor point) |
---|
3242 | shadowUrl: (String) (no shadow by default) |
---|
3243 | shadowRetinaUrl: (String) (optional, used for retina devices if detected) |
---|
3244 | shadowSize: (Point) |
---|
3245 | shadowAnchor: (Point) |
---|
3246 | */ |
---|
3247 | className: '' |
---|
3248 | }, |
---|
3249 | |
---|
3250 | initialize: function (options) { |
---|
3251 | L.setOptions(this, options); |
---|
3252 | }, |
---|
3253 | |
---|
3254 | createIcon: function (oldIcon) { |
---|
3255 | return this._createIcon('icon', oldIcon); |
---|
3256 | }, |
---|
3257 | |
---|
3258 | createShadow: function (oldIcon) { |
---|
3259 | return this._createIcon('shadow', oldIcon); |
---|
3260 | }, |
---|
3261 | |
---|
3262 | _createIcon: function (name, oldIcon) { |
---|
3263 | var src = this._getIconUrl(name); |
---|
3264 | |
---|
3265 | if (!src) { |
---|
3266 | if (name === 'icon') { |
---|
3267 | throw new Error('iconUrl not set in Icon options (see the docs).'); |
---|
3268 | } |
---|
3269 | return null; |
---|
3270 | } |
---|
3271 | |
---|
3272 | var img; |
---|
3273 | if (!oldIcon || oldIcon.tagName !== 'IMG') { |
---|
3274 | img = this._createImg(src); |
---|
3275 | } else { |
---|
3276 | img = this._createImg(src, oldIcon); |
---|
3277 | } |
---|
3278 | this._setIconStyles(img, name); |
---|
3279 | |
---|
3280 | return img; |
---|
3281 | }, |
---|
3282 | |
---|
3283 | _setIconStyles: function (img, name) { |
---|
3284 | var options = this.options, |
---|
3285 | size = L.point(options[name + 'Size']), |
---|
3286 | anchor; |
---|
3287 | |
---|
3288 | if (name === 'shadow') { |
---|
3289 | anchor = L.point(options.shadowAnchor || options.iconAnchor); |
---|
3290 | } else { |
---|
3291 | anchor = L.point(options.iconAnchor); |
---|
3292 | } |
---|
3293 | |
---|
3294 | if (!anchor && size) { |
---|
3295 | anchor = size.divideBy(2, true); |
---|
3296 | } |
---|
3297 | |
---|
3298 | img.className = 'leaflet-marker-' + name + ' ' + options.className; |
---|
3299 | |
---|
3300 | if (anchor) { |
---|
3301 | img.style.marginLeft = (-anchor.x) + 'px'; |
---|
3302 | img.style.marginTop = (-anchor.y) + 'px'; |
---|
3303 | } |
---|
3304 | |
---|
3305 | if (size) { |
---|
3306 | img.style.width = size.x + 'px'; |
---|
3307 | img.style.height = size.y + 'px'; |
---|
3308 | } |
---|
3309 | }, |
---|
3310 | |
---|
3311 | _createImg: function (src, el) { |
---|
3312 | |
---|
3313 | if (!L.Browser.ie6) { |
---|
3314 | if (!el) { |
---|
3315 | el = document.createElement('img'); |
---|
3316 | } |
---|
3317 | el.src = src; |
---|
3318 | } else { |
---|
3319 | if (!el) { |
---|
3320 | el = document.createElement('div'); |
---|
3321 | } |
---|
3322 | el.style.filter = |
---|
3323 | 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")'; |
---|
3324 | } |
---|
3325 | return el; |
---|
3326 | }, |
---|
3327 | |
---|
3328 | _getIconUrl: function (name) { |
---|
3329 | if (L.Browser.retina && this.options[name + 'RetinaUrl']) { |
---|
3330 | return this.options[name + 'RetinaUrl']; |
---|
3331 | } |
---|
3332 | return this.options[name + 'Url']; |
---|
3333 | } |
---|
3334 | }); |
---|
3335 | |
---|
3336 | L.icon = function (options) { |
---|
3337 | return new L.Icon(options); |
---|
3338 | }; |
---|
3339 | |
---|
3340 | |
---|
3341 | /* |
---|
3342 | * L.Icon.Default is the blue marker icon used by default in Leaflet. |
---|
3343 | */ |
---|
3344 | |
---|
3345 | L.Icon.Default = L.Icon.extend({ |
---|
3346 | |
---|
3347 | options: { |
---|
3348 | iconSize: [25, 41], |
---|
3349 | iconAnchor: [12, 41], |
---|
3350 | popupAnchor: [1, -34], |
---|
3351 | |
---|
3352 | shadowSize: [41, 41] |
---|
3353 | }, |
---|
3354 | |
---|
3355 | _getIconUrl: function (name) { |
---|
3356 | var key = name + 'Url'; |
---|
3357 | |
---|
3358 | if (this.options[key]) { |
---|
3359 | return this.options[key]; |
---|
3360 | } |
---|
3361 | |
---|
3362 | if (L.Browser.retina && name === 'icon') { |
---|
3363 | name += '-2x'; |
---|
3364 | } |
---|
3365 | |
---|
3366 | var path = L.Icon.Default.imagePath; |
---|
3367 | |
---|
3368 | if (!path) { |
---|
3369 | throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.'); |
---|
3370 | } |
---|
3371 | |
---|
3372 | return path + '/marker-' + name + '.png'; |
---|
3373 | } |
---|
3374 | }); |
---|
3375 | |
---|
3376 | L.Icon.Default.imagePath = (function () { |
---|
3377 | var scripts = document.getElementsByTagName('script'), |
---|
3378 | leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/; |
---|
3379 | |
---|
3380 | var i, len, src, matches, path; |
---|
3381 | |
---|
3382 | for (i = 0, len = scripts.length; i < len; i++) { |
---|
3383 | src = scripts[i].src; |
---|
3384 | matches = src.match(leafletRe); |
---|
3385 | |
---|
3386 | if (matches) { |
---|
3387 | path = src.split(leafletRe)[0]; |
---|
3388 | return (path ? path + '/' : '') + 'images'; |
---|
3389 | } |
---|
3390 | } |
---|
3391 | }()); |
---|
3392 | |
---|
3393 | |
---|
3394 | /* |
---|
3395 | * L.Marker is used to display clickable/draggable icons on the map. |
---|
3396 | */ |
---|
3397 | |
---|
3398 | L.Marker = L.Class.extend({ |
---|
3399 | |
---|
3400 | includes: L.Mixin.Events, |
---|
3401 | |
---|
3402 | options: { |
---|
3403 | icon: new L.Icon.Default(), |
---|
3404 | title: '', |
---|
3405 | clickable: true, |
---|
3406 | draggable: false, |
---|
3407 | keyboard: true, |
---|
3408 | zIndexOffset: 0, |
---|
3409 | opacity: 1, |
---|
3410 | riseOnHover: false, |
---|
3411 | riseOffset: 250 |
---|
3412 | }, |
---|
3413 | |
---|
3414 | initialize: function (latlng, options) { |
---|
3415 | L.setOptions(this, options); |
---|
3416 | this._latlng = L.latLng(latlng); |
---|
3417 | }, |
---|
3418 | |
---|
3419 | onAdd: function (map) { |
---|
3420 | this._map = map; |
---|
3421 | |
---|
3422 | map.on('viewreset', this.update, this); |
---|
3423 | |
---|
3424 | this._initIcon(); |
---|
3425 | this.update(); |
---|
3426 | |
---|
3427 | if (map.options.zoomAnimation && map.options.markerZoomAnimation) { |
---|
3428 | map.on('zoomanim', this._animateZoom, this); |
---|
3429 | } |
---|
3430 | }, |
---|
3431 | |
---|
3432 | addTo: function (map) { |
---|
3433 | map.addLayer(this); |
---|
3434 | return this; |
---|
3435 | }, |
---|
3436 | |
---|
3437 | onRemove: function (map) { |
---|
3438 | if (this.dragging) { |
---|
3439 | this.dragging.disable(); |
---|
3440 | } |
---|
3441 | |
---|
3442 | this._removeIcon(); |
---|
3443 | this._removeShadow(); |
---|
3444 | |
---|
3445 | this.fire('remove'); |
---|
3446 | |
---|
3447 | map.off({ |
---|
3448 | 'viewreset': this.update, |
---|
3449 | 'zoomanim': this._animateZoom |
---|
3450 | }, this); |
---|
3451 | |
---|
3452 | this._map = null; |
---|
3453 | }, |
---|
3454 | |
---|
3455 | getLatLng: function () { |
---|
3456 | return this._latlng; |
---|
3457 | }, |
---|
3458 | |
---|
3459 | setLatLng: function (latlng) { |
---|
3460 | this._latlng = L.latLng(latlng); |
---|
3461 | |
---|
3462 | this.update(); |
---|
3463 | |
---|
3464 | return this.fire('move', { latlng: this._latlng }); |
---|
3465 | }, |
---|
3466 | |
---|
3467 | setZIndexOffset: function (offset) { |
---|
3468 | this.options.zIndexOffset = offset; |
---|
3469 | this.update(); |
---|
3470 | |
---|
3471 | return this; |
---|
3472 | }, |
---|
3473 | |
---|
3474 | setIcon: function (icon) { |
---|
3475 | |
---|
3476 | this.options.icon = icon; |
---|
3477 | |
---|
3478 | if (this._map) { |
---|
3479 | this._initIcon(); |
---|
3480 | this.update(); |
---|
3481 | } |
---|
3482 | |
---|
3483 | return this; |
---|
3484 | }, |
---|
3485 | |
---|
3486 | update: function () { |
---|
3487 | if (this._icon) { |
---|
3488 | var pos = this._map.latLngToLayerPoint(this._latlng).round(); |
---|
3489 | this._setPos(pos); |
---|
3490 | } |
---|
3491 | |
---|
3492 | return this; |
---|
3493 | }, |
---|
3494 | |
---|
3495 | _initIcon: function () { |
---|
3496 | var options = this.options, |
---|
3497 | map = this._map, |
---|
3498 | animation = (map.options.zoomAnimation && map.options.markerZoomAnimation), |
---|
3499 | classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide'; |
---|
3500 | |
---|
3501 | var icon = options.icon.createIcon(this._icon), |
---|
3502 | addIcon = false; |
---|
3503 | |
---|
3504 | // if we're not reusing the icon, remove the old one and init new one |
---|
3505 | if (icon !== this._icon) { |
---|
3506 | if (this._icon) { |
---|
3507 | this._removeIcon(); |
---|
3508 | } |
---|
3509 | addIcon = true; |
---|
3510 | |
---|
3511 | if (options.title) { |
---|
3512 | icon.title = options.title; |
---|
3513 | } |
---|
3514 | } |
---|
3515 | |
---|
3516 | L.DomUtil.addClass(icon, classToAdd); |
---|
3517 | |
---|
3518 | if (options.keyboard) { |
---|
3519 | icon.tabIndex = '0'; |
---|
3520 | } |
---|
3521 | |
---|
3522 | this._icon = icon; |
---|
3523 | |
---|
3524 | this._initInteraction(); |
---|
3525 | |
---|
3526 | if (options.riseOnHover) { |
---|
3527 | L.DomEvent |
---|
3528 | .on(icon, 'mouseover', this._bringToFront, this) |
---|
3529 | .on(icon, 'mouseout', this._resetZIndex, this); |
---|
3530 | } |
---|
3531 | |
---|
3532 | var newShadow = options.icon.createShadow(this._shadow), |
---|
3533 | addShadow = false; |
---|
3534 | |
---|
3535 | if (newShadow !== this._shadow) { |
---|
3536 | this._removeShadow(); |
---|
3537 | addShadow = true; |
---|
3538 | |
---|
3539 | if (newShadow) { |
---|
3540 | L.DomUtil.addClass(newShadow, classToAdd); |
---|
3541 | } |
---|
3542 | } |
---|
3543 | this._shadow = newShadow; |
---|
3544 | |
---|
3545 | |
---|
3546 | if (options.opacity < 1) { |
---|
3547 | this._updateOpacity(); |
---|
3548 | } |
---|
3549 | |
---|
3550 | |
---|
3551 | var panes = this._map._panes; |
---|
3552 | |
---|
3553 | if (addIcon) { |
---|
3554 | panes.markerPane.appendChild(this._icon); |
---|
3555 | } |
---|
3556 | |
---|
3557 | if (newShadow && addShadow) { |
---|
3558 | panes.shadowPane.appendChild(this._shadow); |
---|
3559 | } |
---|
3560 | }, |
---|
3561 | |
---|
3562 | _removeIcon: function () { |
---|
3563 | if (this.options.riseOnHover) { |
---|
3564 | L.DomEvent |
---|
3565 | .off(this._icon, 'mouseover', this._bringToFront) |
---|
3566 | .off(this._icon, 'mouseout', this._resetZIndex); |
---|
3567 | } |
---|
3568 | |
---|
3569 | this._map._panes.markerPane.removeChild(this._icon); |
---|
3570 | |
---|
3571 | this._icon = null; |
---|
3572 | }, |
---|
3573 | |
---|
3574 | _removeShadow: function () { |
---|
3575 | if (this._shadow) { |
---|
3576 | this._map._panes.shadowPane.removeChild(this._shadow); |
---|
3577 | } |
---|
3578 | this._shadow = null; |
---|
3579 | }, |
---|
3580 | |
---|
3581 | _setPos: function (pos) { |
---|
3582 | L.DomUtil.setPosition(this._icon, pos); |
---|
3583 | |
---|
3584 | if (this._shadow) { |
---|
3585 | L.DomUtil.setPosition(this._shadow, pos); |
---|
3586 | } |
---|
3587 | |
---|
3588 | this._zIndex = pos.y + this.options.zIndexOffset; |
---|
3589 | |
---|
3590 | this._resetZIndex(); |
---|
3591 | }, |
---|
3592 | |
---|
3593 | _updateZIndex: function (offset) { |
---|
3594 | this._icon.style.zIndex = this._zIndex + offset; |
---|
3595 | }, |
---|
3596 | |
---|
3597 | _animateZoom: function (opt) { |
---|
3598 | var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); |
---|
3599 | |
---|
3600 | this._setPos(pos); |
---|
3601 | }, |
---|
3602 | |
---|
3603 | _initInteraction: function () { |
---|
3604 | |
---|
3605 | if (!this.options.clickable) { return; } |
---|
3606 | |
---|
3607 | // TODO refactor into something shared with Map/Path/etc. to DRY it up |
---|
3608 | |
---|
3609 | var icon = this._icon, |
---|
3610 | events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; |
---|
3611 | |
---|
3612 | L.DomUtil.addClass(icon, 'leaflet-clickable'); |
---|
3613 | L.DomEvent.on(icon, 'click', this._onMouseClick, this); |
---|
3614 | L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); |
---|
3615 | |
---|
3616 | for (var i = 0; i < events.length; i++) { |
---|
3617 | L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); |
---|
3618 | } |
---|
3619 | |
---|
3620 | if (L.Handler.MarkerDrag) { |
---|
3621 | this.dragging = new L.Handler.MarkerDrag(this); |
---|
3622 | |
---|
3623 | if (this.options.draggable) { |
---|
3624 | this.dragging.enable(); |
---|
3625 | } |
---|
3626 | } |
---|
3627 | }, |
---|
3628 | |
---|
3629 | _onMouseClick: function (e) { |
---|
3630 | var wasDragged = this.dragging && this.dragging.moved(); |
---|
3631 | |
---|
3632 | if (this.hasEventListeners(e.type) || wasDragged) { |
---|
3633 | L.DomEvent.stopPropagation(e); |
---|
3634 | } |
---|
3635 | |
---|
3636 | if (wasDragged) { return; } |
---|
3637 | |
---|
3638 | if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; } |
---|
3639 | |
---|
3640 | this.fire(e.type, { |
---|
3641 | originalEvent: e, |
---|
3642 | latlng: this._latlng |
---|
3643 | }); |
---|
3644 | }, |
---|
3645 | |
---|
3646 | _onKeyPress: function (e) { |
---|
3647 | if (e.keyCode === 13) { |
---|
3648 | this.fire('click', { |
---|
3649 | originalEvent: e, |
---|
3650 | latlng: this._latlng |
---|
3651 | }); |
---|
3652 | } |
---|
3653 | }, |
---|
3654 | |
---|
3655 | _fireMouseEvent: function (e) { |
---|
3656 | |
---|
3657 | this.fire(e.type, { |
---|
3658 | originalEvent: e, |
---|
3659 | latlng: this._latlng |
---|
3660 | }); |
---|
3661 | |
---|
3662 | // TODO proper custom event propagation |
---|
3663 | // this line will always be called if marker is in a FeatureGroup |
---|
3664 | if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { |
---|
3665 | L.DomEvent.preventDefault(e); |
---|
3666 | } |
---|
3667 | if (e.type !== 'mousedown') { |
---|
3668 | L.DomEvent.stopPropagation(e); |
---|
3669 | } else { |
---|
3670 | L.DomEvent.preventDefault(e); |
---|
3671 | } |
---|
3672 | }, |
---|
3673 | |
---|
3674 | setOpacity: function (opacity) { |
---|
3675 | this.options.opacity = opacity; |
---|
3676 | if (this._map) { |
---|
3677 | this._updateOpacity(); |
---|
3678 | } |
---|
3679 | }, |
---|
3680 | |
---|
3681 | _updateOpacity: function () { |
---|
3682 | L.DomUtil.setOpacity(this._icon, this.options.opacity); |
---|
3683 | if (this._shadow) { |
---|
3684 | L.DomUtil.setOpacity(this._shadow, this.options.opacity); |
---|
3685 | } |
---|
3686 | }, |
---|
3687 | |
---|
3688 | _bringToFront: function () { |
---|
3689 | this._updateZIndex(this.options.riseOffset); |
---|
3690 | }, |
---|
3691 | |
---|
3692 | _resetZIndex: function () { |
---|
3693 | this._updateZIndex(0); |
---|
3694 | } |
---|
3695 | }); |
---|
3696 | |
---|
3697 | L.marker = function (latlng, options) { |
---|
3698 | return new L.Marker(latlng, options); |
---|
3699 | }; |
---|
3700 | |
---|
3701 | |
---|
3702 | /* |
---|
3703 | * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon) |
---|
3704 | * to use with L.Marker. |
---|
3705 | */ |
---|
3706 | |
---|
3707 | L.DivIcon = L.Icon.extend({ |
---|
3708 | options: { |
---|
3709 | iconSize: [12, 12], // also can be set through CSS |
---|
3710 | /* |
---|
3711 | iconAnchor: (Point) |
---|
3712 | popupAnchor: (Point) |
---|
3713 | html: (String) |
---|
3714 | bgPos: (Point) |
---|
3715 | */ |
---|
3716 | className: 'leaflet-div-icon', |
---|
3717 | html: false |
---|
3718 | }, |
---|
3719 | |
---|
3720 | createIcon: function (oldIcon) { |
---|
3721 | var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), |
---|
3722 | options = this.options; |
---|
3723 | |
---|
3724 | if (options.html !== false) { |
---|
3725 | div.innerHTML = options.html; |
---|
3726 | } else { |
---|
3727 | div.innerHTML = ''; |
---|
3728 | } |
---|
3729 | |
---|
3730 | if (options.bgPos) { |
---|
3731 | div.style.backgroundPosition = |
---|
3732 | (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px'; |
---|
3733 | } |
---|
3734 | |
---|
3735 | this._setIconStyles(div, 'icon'); |
---|
3736 | return div; |
---|
3737 | }, |
---|
3738 | |
---|
3739 | createShadow: function () { |
---|
3740 | return null; |
---|
3741 | } |
---|
3742 | }); |
---|
3743 | |
---|
3744 | L.divIcon = function (options) { |
---|
3745 | return new L.DivIcon(options); |
---|
3746 | }; |
---|
3747 | |
---|
3748 | |
---|
3749 | /* |
---|
3750 | * L.Popup is used for displaying popups on the map. |
---|
3751 | */ |
---|
3752 | |
---|
3753 | L.Map.mergeOptions({ |
---|
3754 | closePopupOnClick: true |
---|
3755 | }); |
---|
3756 | |
---|
3757 | L.Popup = L.Class.extend({ |
---|
3758 | includes: L.Mixin.Events, |
---|
3759 | |
---|
3760 | options: { |
---|
3761 | minWidth: 50, |
---|
3762 | maxWidth: 300, |
---|
3763 | maxHeight: null, |
---|
3764 | autoPan: true, |
---|
3765 | closeButton: true, |
---|
3766 | offset: [0, 7], |
---|
3767 | autoPanPadding: [5, 5], |
---|
3768 | keepInView: false, |
---|
3769 | className: '', |
---|
3770 | zoomAnimation: true |
---|
3771 | }, |
---|
3772 | |
---|
3773 | initialize: function (options, source) { |
---|
3774 | L.setOptions(this, options); |
---|
3775 | |
---|
3776 | this._source = source; |
---|
3777 | this._animated = L.Browser.any3d && this.options.zoomAnimation; |
---|
3778 | this._isOpen = false; |
---|
3779 | }, |
---|
3780 | |
---|
3781 | onAdd: function (map) { |
---|
3782 | this._map = map; |
---|
3783 | |
---|
3784 | if (!this._container) { |
---|
3785 | this._initLayout(); |
---|
3786 | } |
---|
3787 | this._updateContent(); |
---|
3788 | |
---|
3789 | var animFade = map.options.fadeAnimation; |
---|
3790 | |
---|
3791 | if (animFade) { |
---|
3792 | L.DomUtil.setOpacity(this._container, 0); |
---|
3793 | } |
---|
3794 | map._panes.popupPane.appendChild(this._container); |
---|
3795 | |
---|
3796 | map.on(this._getEvents(), this); |
---|
3797 | |
---|
3798 | this._update(); |
---|
3799 | |
---|
3800 | if (animFade) { |
---|
3801 | L.DomUtil.setOpacity(this._container, 1); |
---|
3802 | } |
---|
3803 | |
---|
3804 | this.fire('open'); |
---|
3805 | |
---|
3806 | map.fire('popupopen', {popup: this}); |
---|
3807 | |
---|
3808 | if (this._source) { |
---|
3809 | this._source.fire('popupopen', {popup: this}); |
---|
3810 | } |
---|
3811 | }, |
---|
3812 | |
---|
3813 | addTo: function (map) { |
---|
3814 | map.addLayer(this); |
---|
3815 | return this; |
---|
3816 | }, |
---|
3817 | |
---|
3818 | openOn: function (map) { |
---|
3819 | map.openPopup(this); |
---|
3820 | return this; |
---|
3821 | }, |
---|
3822 | |
---|
3823 | onRemove: function (map) { |
---|
3824 | map._panes.popupPane.removeChild(this._container); |
---|
3825 | |
---|
3826 | L.Util.falseFn(this._container.offsetWidth); // force reflow |
---|
3827 | |
---|
3828 | map.off(this._getEvents(), this); |
---|
3829 | |
---|
3830 | if (map.options.fadeAnimation) { |
---|
3831 | L.DomUtil.setOpacity(this._container, 0); |
---|
3832 | } |
---|
3833 | |
---|
3834 | this._map = null; |
---|
3835 | |
---|
3836 | this.fire('close'); |
---|
3837 | |
---|
3838 | map.fire('popupclose', {popup: this}); |
---|
3839 | |
---|
3840 | if (this._source) { |
---|
3841 | this._source.fire('popupclose', {popup: this}); |
---|
3842 | } |
---|
3843 | }, |
---|
3844 | |
---|
3845 | setLatLng: function (latlng) { |
---|
3846 | this._latlng = L.latLng(latlng); |
---|
3847 | this._update(); |
---|
3848 | return this; |
---|
3849 | }, |
---|
3850 | |
---|
3851 | setContent: function (content) { |
---|
3852 | this._content = content; |
---|
3853 | this._update(); |
---|
3854 | return this; |
---|
3855 | }, |
---|
3856 | |
---|
3857 | _getEvents: function () { |
---|
3858 | var events = { |
---|
3859 | viewreset: this._updatePosition |
---|
3860 | }; |
---|
3861 | |
---|
3862 | if (this._animated) { |
---|
3863 | events.zoomanim = this._zoomAnimation; |
---|
3864 | } |
---|
3865 | if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { |
---|
3866 | events.preclick = this._close; |
---|
3867 | } |
---|
3868 | if (this.options.keepInView) { |
---|
3869 | events.moveend = this._adjustPan; |
---|
3870 | } |
---|
3871 | |
---|
3872 | return events; |
---|
3873 | }, |
---|
3874 | |
---|
3875 | _close: function () { |
---|
3876 | if (this._map) { |
---|
3877 | this._map.closePopup(this); |
---|
3878 | } |
---|
3879 | }, |
---|
3880 | |
---|
3881 | _initLayout: function () { |
---|
3882 | var prefix = 'leaflet-popup', |
---|
3883 | containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' + |
---|
3884 | (this._animated ? 'animated' : 'hide'), |
---|
3885 | container = this._container = L.DomUtil.create('div', containerClass), |
---|
3886 | closeButton; |
---|
3887 | |
---|
3888 | if (this.options.closeButton) { |
---|
3889 | closeButton = this._closeButton = |
---|
3890 | L.DomUtil.create('a', prefix + '-close-button', container); |
---|
3891 | closeButton.href = '#close'; |
---|
3892 | closeButton.innerHTML = '×'; |
---|
3893 | L.DomEvent.disableClickPropagation(closeButton); |
---|
3894 | |
---|
3895 | L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); |
---|
3896 | } |
---|
3897 | |
---|
3898 | var wrapper = this._wrapper = |
---|
3899 | L.DomUtil.create('div', prefix + '-content-wrapper', container); |
---|
3900 | L.DomEvent.disableClickPropagation(wrapper); |
---|
3901 | |
---|
3902 | this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); |
---|
3903 | L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation); |
---|
3904 | L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); |
---|
3905 | this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); |
---|
3906 | this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); |
---|
3907 | }, |
---|
3908 | |
---|
3909 | _update: function () { |
---|
3910 | if (!this._map) { return; } |
---|
3911 | |
---|
3912 | this._container.style.visibility = 'hidden'; |
---|
3913 | |
---|
3914 | this._updateContent(); |
---|
3915 | this._updateLayout(); |
---|
3916 | this._updatePosition(); |
---|
3917 | |
---|
3918 | this._container.style.visibility = ''; |
---|
3919 | |
---|
3920 | this._adjustPan(); |
---|
3921 | }, |
---|
3922 | |
---|
3923 | _updateContent: function () { |
---|
3924 | if (!this._content) { return; } |
---|
3925 | |
---|
3926 | if (typeof this._content === 'string') { |
---|
3927 | this._contentNode.innerHTML = this._content; |
---|
3928 | } else { |
---|
3929 | while (this._contentNode.hasChildNodes()) { |
---|
3930 | this._contentNode.removeChild(this._contentNode.firstChild); |
---|
3931 | } |
---|
3932 | this._contentNode.appendChild(this._content); |
---|
3933 | } |
---|
3934 | this.fire('contentupdate'); |
---|
3935 | }, |
---|
3936 | |
---|
3937 | _updateLayout: function () { |
---|
3938 | var container = this._contentNode, |
---|
3939 | style = container.style; |
---|
3940 | |
---|
3941 | style.width = ''; |
---|
3942 | style.whiteSpace = 'nowrap'; |
---|
3943 | |
---|
3944 | var width = container.offsetWidth; |
---|
3945 | width = Math.min(width, this.options.maxWidth); |
---|
3946 | width = Math.max(width, this.options.minWidth); |
---|
3947 | |
---|
3948 | style.width = (width + 1) + 'px'; |
---|
3949 | style.whiteSpace = ''; |
---|
3950 | |
---|
3951 | style.height = ''; |
---|
3952 | |
---|
3953 | var height = container.offsetHeight, |
---|
3954 | maxHeight = this.options.maxHeight, |
---|
3955 | scrolledClass = 'leaflet-popup-scrolled'; |
---|
3956 | |
---|
3957 | if (maxHeight && height > maxHeight) { |
---|
3958 | style.height = maxHeight + 'px'; |
---|
3959 | L.DomUtil.addClass(container, scrolledClass); |
---|
3960 | } else { |
---|
3961 | L.DomUtil.removeClass(container, scrolledClass); |
---|
3962 | } |
---|
3963 | |
---|
3964 | this._containerWidth = this._container.offsetWidth; |
---|
3965 | }, |
---|
3966 | |
---|
3967 | _updatePosition: function () { |
---|
3968 | if (!this._map) { return; } |
---|
3969 | |
---|
3970 | var pos = this._map.latLngToLayerPoint(this._latlng), |
---|
3971 | animated = this._animated, |
---|
3972 | offset = L.point(this.options.offset); |
---|
3973 | |
---|
3974 | if (animated) { |
---|
3975 | L.DomUtil.setPosition(this._container, pos); |
---|
3976 | } |
---|
3977 | |
---|
3978 | this._containerBottom = -offset.y - (animated ? 0 : pos.y); |
---|
3979 | this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x); |
---|
3980 | |
---|
3981 | // bottom position the popup in case the height of the popup changes (images loading etc) |
---|
3982 | this._container.style.bottom = this._containerBottom + 'px'; |
---|
3983 | this._container.style.left = this._containerLeft + 'px'; |
---|
3984 | }, |
---|
3985 | |
---|
3986 | _zoomAnimation: function (opt) { |
---|
3987 | var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); |
---|
3988 | |
---|
3989 | L.DomUtil.setPosition(this._container, pos); |
---|
3990 | }, |
---|
3991 | |
---|
3992 | _adjustPan: function () { |
---|
3993 | if (!this.options.autoPan) { return; } |
---|
3994 | |
---|
3995 | var map = this._map, |
---|
3996 | containerHeight = this._container.offsetHeight, |
---|
3997 | containerWidth = this._containerWidth, |
---|
3998 | |
---|
3999 | layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); |
---|
4000 | |
---|
4001 | if (this._animated) { |
---|
4002 | layerPos._add(L.DomUtil.getPosition(this._container)); |
---|
4003 | } |
---|
4004 | |
---|
4005 | var containerPos = map.layerPointToContainerPoint(layerPos), |
---|
4006 | padding = L.point(this.options.autoPanPadding), |
---|
4007 | size = map.getSize(), |
---|
4008 | dx = 0, |
---|
4009 | dy = 0; |
---|
4010 | |
---|
4011 | if (containerPos.x + containerWidth > size.x) { // right |
---|
4012 | dx = containerPos.x + containerWidth - size.x + padding.x; |
---|
4013 | } |
---|
4014 | if (containerPos.x - dx < 0) { // left |
---|
4015 | dx = containerPos.x - padding.x; |
---|
4016 | } |
---|
4017 | if (containerPos.y + containerHeight > size.y) { // bottom |
---|
4018 | dy = containerPos.y + containerHeight - size.y + padding.y; |
---|
4019 | } |
---|
4020 | if (containerPos.y - dy < 0) { // top |
---|
4021 | dy = containerPos.y - padding.y; |
---|
4022 | } |
---|
4023 | |
---|
4024 | if (dx || dy) { |
---|
4025 | map |
---|
4026 | .fire('autopanstart') |
---|
4027 | .panBy([dx, dy]); |
---|
4028 | } |
---|
4029 | }, |
---|
4030 | |
---|
4031 | _onCloseButtonClick: function (e) { |
---|
4032 | this._close(); |
---|
4033 | L.DomEvent.stop(e); |
---|
4034 | } |
---|
4035 | }); |
---|
4036 | |
---|
4037 | L.popup = function (options, source) { |
---|
4038 | return new L.Popup(options, source); |
---|
4039 | }; |
---|
4040 | |
---|
4041 | |
---|
4042 | L.Map.include({ |
---|
4043 | openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object]) |
---|
4044 | this.closePopup(); |
---|
4045 | |
---|
4046 | if (!(popup instanceof L.Popup)) { |
---|
4047 | var content = popup; |
---|
4048 | |
---|
4049 | popup = new L.Popup(options) |
---|
4050 | .setLatLng(latlng) |
---|
4051 | .setContent(content); |
---|
4052 | } |
---|
4053 | popup._isOpen = true; |
---|
4054 | |
---|
4055 | this._popup = popup; |
---|
4056 | return this.addLayer(popup); |
---|
4057 | }, |
---|
4058 | |
---|
4059 | closePopup: function (popup) { |
---|
4060 | if (!popup || popup === this._popup) { |
---|
4061 | popup = this._popup; |
---|
4062 | this._popup = null; |
---|
4063 | } |
---|
4064 | if (popup) { |
---|
4065 | this.removeLayer(popup); |
---|
4066 | popup._isOpen = false; |
---|
4067 | } |
---|
4068 | return this; |
---|
4069 | } |
---|
4070 | }); |
---|
4071 | |
---|
4072 | |
---|
4073 | /* |
---|
4074 | * Popup extension to L.Marker, adding popup-related methods. |
---|
4075 | */ |
---|
4076 | |
---|
4077 | L.Marker.include({ |
---|
4078 | openPopup: function () { |
---|
4079 | if (this._popup && this._map && !this._map.hasLayer(this._popup)) { |
---|
4080 | this._popup.setLatLng(this._latlng); |
---|
4081 | this._map.openPopup(this._popup); |
---|
4082 | } |
---|
4083 | |
---|
4084 | return this; |
---|
4085 | }, |
---|
4086 | |
---|
4087 | closePopup: function () { |
---|
4088 | if (this._popup) { |
---|
4089 | this._popup._close(); |
---|
4090 | } |
---|
4091 | return this; |
---|
4092 | }, |
---|
4093 | |
---|
4094 | togglePopup: function () { |
---|
4095 | if (this._popup) { |
---|
4096 | if (this._popup._isOpen) { |
---|
4097 | this.closePopup(); |
---|
4098 | } else { |
---|
4099 | this.openPopup(); |
---|
4100 | } |
---|
4101 | } |
---|
4102 | return this; |
---|
4103 | }, |
---|
4104 | |
---|
4105 | bindPopup: function (content, options) { |
---|
4106 | var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]); |
---|
4107 | |
---|
4108 | anchor = anchor.add(L.Popup.prototype.options.offset); |
---|
4109 | |
---|
4110 | if (options && options.offset) { |
---|
4111 | anchor = anchor.add(options.offset); |
---|
4112 | } |
---|
4113 | |
---|
4114 | options = L.extend({offset: anchor}, options); |
---|
4115 | |
---|
4116 | if (!this._popup) { |
---|
4117 | this |
---|
4118 | .on('click', this.togglePopup, this) |
---|
4119 | .on('remove', this.closePopup, this) |
---|
4120 | .on('move', this._movePopup, this); |
---|
4121 | } |
---|
4122 | |
---|
4123 | if (content instanceof L.Popup) { |
---|
4124 | L.setOptions(content, options); |
---|
4125 | this._popup = content; |
---|
4126 | } else { |
---|
4127 | this._popup = new L.Popup(options, this) |
---|
4128 | .setContent(content); |
---|
4129 | } |
---|
4130 | |
---|
4131 | return this; |
---|
4132 | }, |
---|
4133 | |
---|
4134 | setPopupContent: function (content) { |
---|
4135 | if (this._popup) { |
---|
4136 | this._popup.setContent(content); |
---|
4137 | } |
---|
4138 | return this; |
---|
4139 | }, |
---|
4140 | |
---|
4141 | unbindPopup: function () { |
---|
4142 | if (this._popup) { |
---|
4143 | this._popup = null; |
---|
4144 | this |
---|
4145 | .off('click', this.togglePopup) |
---|
4146 | .off('remove', this.closePopup) |
---|
4147 | .off('move', this._movePopup); |
---|
4148 | } |
---|
4149 | return this; |
---|
4150 | }, |
---|
4151 | |
---|
4152 | _movePopup: function (e) { |
---|
4153 | this._popup.setLatLng(e.latlng); |
---|
4154 | } |
---|
4155 | }); |
---|
4156 | |
---|
4157 | |
---|
4158 | /* |
---|
4159 | * L.LayerGroup is a class to combine several layers into one so that |
---|
4160 | * you can manipulate the group (e.g. add/remove it) as one layer. |
---|
4161 | */ |
---|
4162 | |
---|
4163 | L.LayerGroup = L.Class.extend({ |
---|
4164 | initialize: function (layers) { |
---|
4165 | this._layers = {}; |
---|
4166 | |
---|
4167 | var i, len; |
---|
4168 | |
---|
4169 | if (layers) { |
---|
4170 | for (i = 0, len = layers.length; i < len; i++) { |
---|
4171 | this.addLayer(layers[i]); |
---|
4172 | } |
---|
4173 | } |
---|
4174 | }, |
---|
4175 | |
---|
4176 | addLayer: function (layer) { |
---|
4177 | var id = this.getLayerId(layer); |
---|
4178 | |
---|
4179 | this._layers[id] = layer; |
---|
4180 | |
---|
4181 | if (this._map) { |
---|
4182 | this._map.addLayer(layer); |
---|
4183 | } |
---|
4184 | |
---|
4185 | return this; |
---|
4186 | }, |
---|
4187 | |
---|
4188 | removeLayer: function (layer) { |
---|
4189 | var id = layer in this._layers ? layer : this.getLayerId(layer); |
---|
4190 | |
---|
4191 | if (this._map && this._layers[id]) { |
---|
4192 | this._map.removeLayer(this._layers[id]); |
---|
4193 | } |
---|
4194 | |
---|
4195 | delete this._layers[id]; |
---|
4196 | |
---|
4197 | return this; |
---|
4198 | }, |
---|
4199 | |
---|
4200 | hasLayer: function (layer) { |
---|
4201 | if (!layer) { return false; } |
---|
4202 | |
---|
4203 | return (layer in this._layers || this.getLayerId(layer) in this._layers); |
---|
4204 | }, |
---|
4205 | |
---|
4206 | clearLayers: function () { |
---|
4207 | this.eachLayer(this.removeLayer, this); |
---|
4208 | return this; |
---|
4209 | }, |
---|
4210 | |
---|
4211 | invoke: function (methodName) { |
---|
4212 | var args = Array.prototype.slice.call(arguments, 1), |
---|
4213 | i, layer; |
---|
4214 | |
---|
4215 | for (i in this._layers) { |
---|
4216 | layer = this._layers[i]; |
---|
4217 | |
---|
4218 | if (layer[methodName]) { |
---|
4219 | layer[methodName].apply(layer, args); |
---|
4220 | } |
---|
4221 | } |
---|
4222 | |
---|
4223 | return this; |
---|
4224 | }, |
---|
4225 | |
---|
4226 | onAdd: function (map) { |
---|
4227 | this._map = map; |
---|
4228 | this.eachLayer(map.addLayer, map); |
---|
4229 | }, |
---|
4230 | |
---|
4231 | onRemove: function (map) { |
---|
4232 | this.eachLayer(map.removeLayer, map); |
---|
4233 | this._map = null; |
---|
4234 | }, |
---|
4235 | |
---|
4236 | addTo: function (map) { |
---|
4237 | map.addLayer(this); |
---|
4238 | return this; |
---|
4239 | }, |
---|
4240 | |
---|
4241 | eachLayer: function (method, context) { |
---|
4242 | for (var i in this._layers) { |
---|
4243 | method.call(context, this._layers[i]); |
---|
4244 | } |
---|
4245 | return this; |
---|
4246 | }, |
---|
4247 | |
---|
4248 | getLayer: function (id) { |
---|
4249 | return this._layers[id]; |
---|
4250 | }, |
---|
4251 | |
---|
4252 | getLayers: function () { |
---|
4253 | var layers = []; |
---|
4254 | |
---|
4255 | for (var i in this._layers) { |
---|
4256 | layers.push(this._layers[i]); |
---|
4257 | } |
---|
4258 | return layers; |
---|
4259 | }, |
---|
4260 | |
---|
4261 | setZIndex: function (zIndex) { |
---|
4262 | return this.invoke('setZIndex', zIndex); |
---|
4263 | }, |
---|
4264 | |
---|
4265 | getLayerId: function (layer) { |
---|
4266 | return L.stamp(layer); |
---|
4267 | } |
---|
4268 | }); |
---|
4269 | |
---|
4270 | L.layerGroup = function (layers) { |
---|
4271 | return new L.LayerGroup(layers); |
---|
4272 | }; |
---|
4273 | |
---|
4274 | |
---|
4275 | /* |
---|
4276 | * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods |
---|
4277 | * shared between a group of interactive layers (like vectors or markers). |
---|
4278 | */ |
---|
4279 | |
---|
4280 | L.FeatureGroup = L.LayerGroup.extend({ |
---|
4281 | includes: L.Mixin.Events, |
---|
4282 | |
---|
4283 | statics: { |
---|
4284 | EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose' |
---|
4285 | }, |
---|
4286 | |
---|
4287 | addLayer: function (layer) { |
---|
4288 | if (this.hasLayer(layer)) { |
---|
4289 | return this; |
---|
4290 | } |
---|
4291 | |
---|
4292 | layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); |
---|
4293 | |
---|
4294 | L.LayerGroup.prototype.addLayer.call(this, layer); |
---|
4295 | |
---|
4296 | if (this._popupContent && layer.bindPopup) { |
---|
4297 | layer.bindPopup(this._popupContent, this._popupOptions); |
---|
4298 | } |
---|
4299 | |
---|
4300 | return this.fire('layeradd', {layer: layer}); |
---|
4301 | }, |
---|
4302 | |
---|
4303 | removeLayer: function (layer) { |
---|
4304 | if (layer in this._layers) { |
---|
4305 | layer = this._layers[layer]; |
---|
4306 | } |
---|
4307 | |
---|
4308 | layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this); |
---|
4309 | |
---|
4310 | L.LayerGroup.prototype.removeLayer.call(this, layer); |
---|
4311 | |
---|
4312 | if (this._popupContent) { |
---|
4313 | this.invoke('unbindPopup'); |
---|
4314 | } |
---|
4315 | |
---|
4316 | return this.fire('layerremove', {layer: layer}); |
---|
4317 | }, |
---|
4318 | |
---|
4319 | bindPopup: function (content, options) { |
---|
4320 | this._popupContent = content; |
---|
4321 | this._popupOptions = options; |
---|
4322 | return this.invoke('bindPopup', content, options); |
---|
4323 | }, |
---|
4324 | |
---|
4325 | setStyle: function (style) { |
---|
4326 | return this.invoke('setStyle', style); |
---|
4327 | }, |
---|
4328 | |
---|
4329 | bringToFront: function () { |
---|
4330 | return this.invoke('bringToFront'); |
---|
4331 | }, |
---|
4332 | |
---|
4333 | bringToBack: function () { |
---|
4334 | return this.invoke('bringToBack'); |
---|
4335 | }, |
---|
4336 | |
---|
4337 | getBounds: function () { |
---|
4338 | var bounds = new L.LatLngBounds(); |
---|
4339 | |
---|
4340 | this.eachLayer(function (layer) { |
---|
4341 | bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds()); |
---|
4342 | }); |
---|
4343 | |
---|
4344 | return bounds; |
---|
4345 | }, |
---|
4346 | |
---|
4347 | _propagateEvent: function (e) { |
---|
4348 | if (!e.layer) { |
---|
4349 | e.layer = e.target; |
---|
4350 | } |
---|
4351 | e.target = this; |
---|
4352 | |
---|
4353 | this.fire(e.type, e); |
---|
4354 | } |
---|
4355 | }); |
---|
4356 | |
---|
4357 | L.featureGroup = function (layers) { |
---|
4358 | return new L.FeatureGroup(layers); |
---|
4359 | }; |
---|
4360 | |
---|
4361 | |
---|
4362 | /* |
---|
4363 | * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc. |
---|
4364 | */ |
---|
4365 | |
---|
4366 | L.Path = L.Class.extend({ |
---|
4367 | includes: [L.Mixin.Events], |
---|
4368 | |
---|
4369 | statics: { |
---|
4370 | // how much to extend the clip area around the map view |
---|
4371 | // (relative to its size, e.g. 0.5 is half the screen in each direction) |
---|
4372 | // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is) |
---|
4373 | CLIP_PADDING: L.Browser.mobile ? |
---|
4374 | Math.max(0, Math.min(0.5, |
---|
4375 | (1280 / Math.max(window.innerWidth, window.innerHeight) - 1) / 2)) : 0.5 |
---|
4376 | }, |
---|
4377 | |
---|
4378 | options: { |
---|
4379 | stroke: true, |
---|
4380 | color: '#0033ff', |
---|
4381 | dashArray: null, |
---|
4382 | weight: 5, |
---|
4383 | opacity: 0.5, |
---|
4384 | |
---|
4385 | fill: false, |
---|
4386 | fillColor: null, //same as color by default |
---|
4387 | fillOpacity: 0.2, |
---|
4388 | |
---|
4389 | clickable: true |
---|
4390 | }, |
---|
4391 | |
---|
4392 | initialize: function (options) { |
---|
4393 | L.setOptions(this, options); |
---|
4394 | }, |
---|
4395 | |
---|
4396 | onAdd: function (map) { |
---|
4397 | this._map = map; |
---|
4398 | |
---|
4399 | if (!this._container) { |
---|
4400 | this._initElements(); |
---|
4401 | this._initEvents(); |
---|
4402 | } |
---|
4403 | |
---|
4404 | this.projectLatlngs(); |
---|
4405 | this._updatePath(); |
---|
4406 | |
---|
4407 | if (this._container) { |
---|
4408 | this._map._pathRoot.appendChild(this._container); |
---|
4409 | } |
---|
4410 | |
---|
4411 | this.fire('add'); |
---|
4412 | |
---|
4413 | map.on({ |
---|
4414 | 'viewreset': this.projectLatlngs, |
---|
4415 | 'moveend': this._updatePath |
---|
4416 | }, this); |
---|
4417 | }, |
---|
4418 | |
---|
4419 | addTo: function (map) { |
---|
4420 | map.addLayer(this); |
---|
4421 | return this; |
---|
4422 | }, |
---|
4423 | |
---|
4424 | onRemove: function (map) { |
---|
4425 | map._pathRoot.removeChild(this._container); |
---|
4426 | |
---|
4427 | // Need to fire remove event before we set _map to null as the event hooks might need the object |
---|
4428 | this.fire('remove'); |
---|
4429 | this._map = null; |
---|
4430 | |
---|
4431 | if (L.Browser.vml) { |
---|
4432 | this._container = null; |
---|
4433 | this._stroke = null; |
---|
4434 | this._fill = null; |
---|
4435 | } |
---|
4436 | |
---|
4437 | map.off({ |
---|
4438 | 'viewreset': this.projectLatlngs, |
---|
4439 | 'moveend': this._updatePath |
---|
4440 | }, this); |
---|
4441 | }, |
---|
4442 | |
---|
4443 | projectLatlngs: function () { |
---|
4444 | // do all projection stuff here |
---|
4445 | }, |
---|
4446 | |
---|
4447 | setStyle: function (style) { |
---|
4448 | L.setOptions(this, style); |
---|
4449 | |
---|
4450 | if (this._container) { |
---|
4451 | this._updateStyle(); |
---|
4452 | } |
---|
4453 | |
---|
4454 | return this; |
---|
4455 | }, |
---|
4456 | |
---|
4457 | redraw: function () { |
---|
4458 | if (this._map) { |
---|
4459 | this.projectLatlngs(); |
---|
4460 | this._updatePath(); |
---|
4461 | } |
---|
4462 | return this; |
---|
4463 | } |
---|
4464 | }); |
---|
4465 | |
---|
4466 | L.Map.include({ |
---|
4467 | _updatePathViewport: function () { |
---|
4468 | var p = L.Path.CLIP_PADDING, |
---|
4469 | size = this.getSize(), |
---|
4470 | panePos = L.DomUtil.getPosition(this._mapPane), |
---|
4471 | min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()), |
---|
4472 | max = min.add(size.multiplyBy(1 + p * 2)._round()); |
---|
4473 | |
---|
4474 | this._pathViewport = new L.Bounds(min, max); |
---|
4475 | } |
---|
4476 | }); |
---|
4477 | |
---|
4478 | |
---|
4479 | /* |
---|
4480 | * Extends L.Path with SVG-specific rendering code. |
---|
4481 | */ |
---|
4482 | |
---|
4483 | L.Path.SVG_NS = 'http://www.w3.org/2000/svg'; |
---|
4484 | |
---|
4485 | L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect); |
---|
4486 | |
---|
4487 | L.Path = L.Path.extend({ |
---|
4488 | statics: { |
---|
4489 | SVG: L.Browser.svg |
---|
4490 | }, |
---|
4491 | |
---|
4492 | bringToFront: function () { |
---|
4493 | var root = this._map._pathRoot, |
---|
4494 | path = this._container; |
---|
4495 | |
---|
4496 | if (path && root.lastChild !== path) { |
---|
4497 | root.appendChild(path); |
---|
4498 | } |
---|
4499 | return this; |
---|
4500 | }, |
---|
4501 | |
---|
4502 | bringToBack: function () { |
---|
4503 | var root = this._map._pathRoot, |
---|
4504 | path = this._container, |
---|
4505 | first = root.firstChild; |
---|
4506 | |
---|
4507 | if (path && first !== path) { |
---|
4508 | root.insertBefore(path, first); |
---|
4509 | } |
---|
4510 | return this; |
---|
4511 | }, |
---|
4512 | |
---|
4513 | getPathString: function () { |
---|
4514 | // form path string here |
---|
4515 | }, |
---|
4516 | |
---|
4517 | _createElement: function (name) { |
---|
4518 | return document.createElementNS(L.Path.SVG_NS, name); |
---|
4519 | }, |
---|
4520 | |
---|
4521 | _initElements: function () { |
---|
4522 | this._map._initPathRoot(); |
---|
4523 | this._initPath(); |
---|
4524 | this._initStyle(); |
---|
4525 | }, |
---|
4526 | |
---|
4527 | _initPath: function () { |
---|
4528 | this._container = this._createElement('g'); |
---|
4529 | |
---|
4530 | this._path = this._createElement('path'); |
---|
4531 | this._container.appendChild(this._path); |
---|
4532 | }, |
---|
4533 | |
---|
4534 | _initStyle: function () { |
---|
4535 | if (this.options.stroke) { |
---|
4536 | this._path.setAttribute('stroke-linejoin', 'round'); |
---|
4537 | this._path.setAttribute('stroke-linecap', 'round'); |
---|
4538 | } |
---|
4539 | if (this.options.fill) { |
---|
4540 | this._path.setAttribute('fill-rule', 'evenodd'); |
---|
4541 | } |
---|
4542 | if (this.options.pointerEvents) { |
---|
4543 | this._path.setAttribute('pointer-events', this.options.pointerEvents); |
---|
4544 | } |
---|
4545 | if (!this.options.clickable && !this.options.pointerEvents) { |
---|
4546 | this._path.setAttribute('pointer-events', 'none'); |
---|
4547 | } |
---|
4548 | this._updateStyle(); |
---|
4549 | }, |
---|
4550 | |
---|
4551 | _updateStyle: function () { |
---|
4552 | if (this.options.stroke) { |
---|
4553 | this._path.setAttribute('stroke', this.options.color); |
---|
4554 | this._path.setAttribute('stroke-opacity', this.options.opacity); |
---|
4555 | this._path.setAttribute('stroke-width', this.options.weight); |
---|
4556 | if (this.options.dashArray) { |
---|
4557 | this._path.setAttribute('stroke-dasharray', this.options.dashArray); |
---|
4558 | } else { |
---|
4559 | this._path.removeAttribute('stroke-dasharray'); |
---|
4560 | } |
---|
4561 | } else { |
---|
4562 | this._path.setAttribute('stroke', 'none'); |
---|
4563 | } |
---|
4564 | if (this.options.fill) { |
---|
4565 | this._path.setAttribute('fill', this.options.fillColor || this.options.color); |
---|
4566 | this._path.setAttribute('fill-opacity', this.options.fillOpacity); |
---|
4567 | } else { |
---|
4568 | this._path.setAttribute('fill', 'none'); |
---|
4569 | } |
---|
4570 | }, |
---|
4571 | |
---|
4572 | _updatePath: function () { |
---|
4573 | var str = this.getPathString(); |
---|
4574 | if (!str) { |
---|
4575 | // fix webkit empty string parsing bug |
---|
4576 | str = 'M0 0'; |
---|
4577 | } |
---|
4578 | this._path.setAttribute('d', str); |
---|
4579 | }, |
---|
4580 | |
---|
4581 | // TODO remove duplication with L.Map |
---|
4582 | _initEvents: function () { |
---|
4583 | if (this.options.clickable) { |
---|
4584 | if (L.Browser.svg || !L.Browser.vml) { |
---|
4585 | this._path.setAttribute('class', 'leaflet-clickable'); |
---|
4586 | } |
---|
4587 | |
---|
4588 | L.DomEvent.on(this._container, 'click', this._onMouseClick, this); |
---|
4589 | |
---|
4590 | var events = ['dblclick', 'mousedown', 'mouseover', |
---|
4591 | 'mouseout', 'mousemove', 'contextmenu']; |
---|
4592 | for (var i = 0; i < events.length; i++) { |
---|
4593 | L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this); |
---|
4594 | } |
---|
4595 | } |
---|
4596 | }, |
---|
4597 | |
---|
4598 | _onMouseClick: function (e) { |
---|
4599 | if (this._map.dragging && this._map.dragging.moved()) { return; } |
---|
4600 | |
---|
4601 | this._fireMouseEvent(e); |
---|
4602 | }, |
---|
4603 | |
---|
4604 | _fireMouseEvent: function (e) { |
---|
4605 | if (!this.hasEventListeners(e.type)) { return; } |
---|
4606 | |
---|
4607 | var map = this._map, |
---|
4608 | containerPoint = map.mouseEventToContainerPoint(e), |
---|
4609 | layerPoint = map.containerPointToLayerPoint(containerPoint), |
---|
4610 | latlng = map.layerPointToLatLng(layerPoint); |
---|
4611 | |
---|
4612 | this.fire(e.type, { |
---|
4613 | latlng: latlng, |
---|
4614 | layerPoint: layerPoint, |
---|
4615 | containerPoint: containerPoint, |
---|
4616 | originalEvent: e |
---|
4617 | }); |
---|
4618 | |
---|
4619 | if (e.type === 'contextmenu') { |
---|
4620 | L.DomEvent.preventDefault(e); |
---|
4621 | } |
---|
4622 | if (e.type !== 'mousemove') { |
---|
4623 | L.DomEvent.stopPropagation(e); |
---|
4624 | } |
---|
4625 | } |
---|
4626 | }); |
---|
4627 | |
---|
4628 | L.Map.include({ |
---|
4629 | _initPathRoot: function () { |
---|
4630 | if (!this._pathRoot) { |
---|
4631 | this._pathRoot = L.Path.prototype._createElement('svg'); |
---|
4632 | this._panes.overlayPane.appendChild(this._pathRoot); |
---|
4633 | |
---|
4634 | if (this.options.zoomAnimation && L.Browser.any3d) { |
---|
4635 | this._pathRoot.setAttribute('class', ' leaflet-zoom-animated'); |
---|
4636 | |
---|
4637 | this.on({ |
---|
4638 | 'zoomanim': this._animatePathZoom, |
---|
4639 | 'zoomend': this._endPathZoom |
---|
4640 | }); |
---|
4641 | } else { |
---|
4642 | this._pathRoot.setAttribute('class', ' leaflet-zoom-hide'); |
---|
4643 | } |
---|
4644 | |
---|
4645 | this.on('moveend', this._updateSvgViewport); |
---|
4646 | this._updateSvgViewport(); |
---|
4647 | } |
---|
4648 | }, |
---|
4649 | |
---|
4650 | _animatePathZoom: function (e) { |
---|
4651 | var scale = this.getZoomScale(e.zoom), |
---|
4652 | offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min); |
---|
4653 | |
---|
4654 | this._pathRoot.style[L.DomUtil.TRANSFORM] = |
---|
4655 | L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') '; |
---|
4656 | |
---|
4657 | this._pathZooming = true; |
---|
4658 | }, |
---|
4659 | |
---|
4660 | _endPathZoom: function () { |
---|
4661 | this._pathZooming = false; |
---|
4662 | }, |
---|
4663 | |
---|
4664 | _updateSvgViewport: function () { |
---|
4665 | |
---|
4666 | if (this._pathZooming) { |
---|
4667 | // Do not update SVGs while a zoom animation is going on otherwise the animation will break. |
---|
4668 | // When the zoom animation ends we will be updated again anyway |
---|
4669 | // This fixes the case where you do a momentum move and zoom while the move is still ongoing. |
---|
4670 | return; |
---|
4671 | } |
---|
4672 | |
---|
4673 | this._updatePathViewport(); |
---|
4674 | |
---|
4675 | var vp = this._pathViewport, |
---|
4676 | min = vp.min, |
---|
4677 | max = vp.max, |
---|
4678 | width = max.x - min.x, |
---|
4679 | height = max.y - min.y, |
---|
4680 | root = this._pathRoot, |
---|
4681 | pane = this._panes.overlayPane; |
---|
4682 | |
---|
4683 | // Hack to make flicker on drag end on mobile webkit less irritating |
---|
4684 | if (L.Browser.mobileWebkit) { |
---|
4685 | pane.removeChild(root); |
---|
4686 | } |
---|
4687 | |
---|
4688 | L.DomUtil.setPosition(root, min); |
---|
4689 | root.setAttribute('width', width); |
---|
4690 | root.setAttribute('height', height); |
---|
4691 | root.setAttribute('viewBox', [min.x, min.y, width, height].join(' ')); |
---|
4692 | |
---|
4693 | if (L.Browser.mobileWebkit) { |
---|
4694 | pane.appendChild(root); |
---|
4695 | } |
---|
4696 | } |
---|
4697 | }); |
---|
4698 | |
---|
4699 | |
---|
4700 | /* |
---|
4701 | * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods. |
---|
4702 | */ |
---|
4703 | |
---|
4704 | L.Path.include({ |
---|
4705 | |
---|
4706 | bindPopup: function (content, options) { |
---|
4707 | |
---|
4708 | if (content instanceof L.Popup) { |
---|
4709 | this._popup = content; |
---|
4710 | } else { |
---|
4711 | if (!this._popup || options) { |
---|
4712 | this._popup = new L.Popup(options, this); |
---|
4713 | } |
---|
4714 | this._popup.setContent(content); |
---|
4715 | } |
---|
4716 | |
---|
4717 | if (!this._popupHandlersAdded) { |
---|
4718 | this |
---|
4719 | .on('click', this._openPopup, this) |
---|
4720 | .on('remove', this.closePopup, this); |
---|
4721 | |
---|
4722 | this._popupHandlersAdded = true; |
---|
4723 | } |
---|
4724 | |
---|
4725 | return this; |
---|
4726 | }, |
---|
4727 | |
---|
4728 | unbindPopup: function () { |
---|
4729 | if (this._popup) { |
---|
4730 | this._popup = null; |
---|
4731 | this |
---|
4732 | .off('click', this._openPopup) |
---|
4733 | .off('remove', this.closePopup); |
---|
4734 | |
---|
4735 | this._popupHandlersAdded = false; |
---|
4736 | } |
---|
4737 | return this; |
---|
4738 | }, |
---|
4739 | |
---|
4740 | openPopup: function (latlng) { |
---|
4741 | |
---|
4742 | if (this._popup) { |
---|
4743 | // open the popup from one of the path's points if not specified |
---|
4744 | latlng = latlng || this._latlng || |
---|
4745 | this._latlngs[Math.floor(this._latlngs.length / 2)]; |
---|
4746 | |
---|
4747 | this._openPopup({latlng: latlng}); |
---|
4748 | } |
---|
4749 | |
---|
4750 | return this; |
---|
4751 | }, |
---|
4752 | |
---|
4753 | closePopup: function () { |
---|
4754 | if (this._popup) { |
---|
4755 | this._popup._close(); |
---|
4756 | } |
---|
4757 | return this; |
---|
4758 | }, |
---|
4759 | |
---|
4760 | _openPopup: function (e) { |
---|
4761 | this._popup.setLatLng(e.latlng); |
---|
4762 | this._map.openPopup(this._popup); |
---|
4763 | } |
---|
4764 | }); |
---|
4765 | |
---|
4766 | |
---|
4767 | /* |
---|
4768 | * Vector rendering for IE6-8 through VML. |
---|
4769 | * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! |
---|
4770 | */ |
---|
4771 | |
---|
4772 | L.Browser.vml = !L.Browser.svg && (function () { |
---|
4773 | try { |
---|
4774 | var div = document.createElement('div'); |
---|
4775 | div.innerHTML = '<v:shape adj="1"/>'; |
---|
4776 | |
---|
4777 | var shape = div.firstChild; |
---|
4778 | shape.style.behavior = 'url(#default#VML)'; |
---|
4779 | |
---|
4780 | return shape && (typeof shape.adj === 'object'); |
---|
4781 | |
---|
4782 | } catch (e) { |
---|
4783 | return false; |
---|
4784 | } |
---|
4785 | }()); |
---|
4786 | |
---|
4787 | L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ |
---|
4788 | statics: { |
---|
4789 | VML: true, |
---|
4790 | CLIP_PADDING: 0.02 |
---|
4791 | }, |
---|
4792 | |
---|
4793 | _createElement: (function () { |
---|
4794 | try { |
---|
4795 | document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); |
---|
4796 | return function (name) { |
---|
4797 | return document.createElement('<lvml:' + name + ' class="lvml">'); |
---|
4798 | }; |
---|
4799 | } catch (e) { |
---|
4800 | return function (name) { |
---|
4801 | return document.createElement( |
---|
4802 | '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); |
---|
4803 | }; |
---|
4804 | } |
---|
4805 | }()), |
---|
4806 | |
---|
4807 | _initPath: function () { |
---|
4808 | var container = this._container = this._createElement('shape'); |
---|
4809 | L.DomUtil.addClass(container, 'leaflet-vml-shape'); |
---|
4810 | if (this.options.clickable) { |
---|
4811 | L.DomUtil.addClass(container, 'leaflet-clickable'); |
---|
4812 | } |
---|
4813 | container.coordsize = '1 1'; |
---|
4814 | |
---|
4815 | this._path = this._createElement('path'); |
---|
4816 | container.appendChild(this._path); |
---|
4817 | |
---|
4818 | this._map._pathRoot.appendChild(container); |
---|
4819 | }, |
---|
4820 | |
---|
4821 | _initStyle: function () { |
---|
4822 | this._updateStyle(); |
---|
4823 | }, |
---|
4824 | |
---|
4825 | _updateStyle: function () { |
---|
4826 | var stroke = this._stroke, |
---|
4827 | fill = this._fill, |
---|
4828 | options = this.options, |
---|
4829 | container = this._container; |
---|
4830 | |
---|
4831 | container.stroked = options.stroke; |
---|
4832 | container.filled = options.fill; |
---|
4833 | |
---|
4834 | if (options.stroke) { |
---|
4835 | if (!stroke) { |
---|
4836 | stroke = this._stroke = this._createElement('stroke'); |
---|
4837 | stroke.endcap = 'round'; |
---|
4838 | container.appendChild(stroke); |
---|
4839 | } |
---|
4840 | stroke.weight = options.weight + 'px'; |
---|
4841 | stroke.color = options.color; |
---|
4842 | stroke.opacity = options.opacity; |
---|
4843 | |
---|
4844 | if (options.dashArray) { |
---|
4845 | stroke.dashStyle = options.dashArray instanceof Array ? |
---|
4846 | options.dashArray.join(' ') : |
---|
4847 | options.dashArray.replace(/( *, *)/g, ' '); |
---|
4848 | } else { |
---|
4849 | stroke.dashStyle = ''; |
---|
4850 | } |
---|
4851 | |
---|
4852 | } else if (stroke) { |
---|
4853 | container.removeChild(stroke); |
---|
4854 | this._stroke = null; |
---|
4855 | } |
---|
4856 | |
---|
4857 | if (options.fill) { |
---|
4858 | if (!fill) { |
---|
4859 | fill = this._fill = this._createElement('fill'); |
---|
4860 | container.appendChild(fill); |
---|
4861 | } |
---|
4862 | fill.color = options.fillColor || options.color; |
---|
4863 | fill.opacity = options.fillOpacity; |
---|
4864 | |
---|
4865 | } else if (fill) { |
---|
4866 | container.removeChild(fill); |
---|
4867 | this._fill = null; |
---|
4868 | } |
---|
4869 | }, |
---|
4870 | |
---|
4871 | _updatePath: function () { |
---|
4872 | var style = this._container.style; |
---|
4873 | |
---|
4874 | style.display = 'none'; |
---|
4875 | this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug |
---|
4876 | style.display = ''; |
---|
4877 | } |
---|
4878 | }); |
---|
4879 | |
---|
4880 | L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : { |
---|
4881 | _initPathRoot: function () { |
---|
4882 | if (this._pathRoot) { return; } |
---|
4883 | |
---|
4884 | var root = this._pathRoot = document.createElement('div'); |
---|
4885 | root.className = 'leaflet-vml-container'; |
---|
4886 | this._panes.overlayPane.appendChild(root); |
---|
4887 | |
---|
4888 | this.on('moveend', this._updatePathViewport); |
---|
4889 | this._updatePathViewport(); |
---|
4890 | } |
---|
4891 | }); |
---|
4892 | |
---|
4893 | |
---|
4894 | /* |
---|
4895 | * Vector rendering for all browsers that support canvas. |
---|
4896 | */ |
---|
4897 | |
---|
4898 | L.Browser.canvas = (function () { |
---|
4899 | return !!document.createElement('canvas').getContext; |
---|
4900 | }()); |
---|
4901 | |
---|
4902 | L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({ |
---|
4903 | statics: { |
---|
4904 | //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value |
---|
4905 | CANVAS: true, |
---|
4906 | SVG: false |
---|
4907 | }, |
---|
4908 | |
---|
4909 | redraw: function () { |
---|
4910 | if (this._map) { |
---|
4911 | this.projectLatlngs(); |
---|
4912 | this._requestUpdate(); |
---|
4913 | } |
---|
4914 | return this; |
---|
4915 | }, |
---|
4916 | |
---|
4917 | setStyle: function (style) { |
---|
4918 | L.setOptions(this, style); |
---|
4919 | |
---|
4920 | if (this._map) { |
---|
4921 | this._updateStyle(); |
---|
4922 | this._requestUpdate(); |
---|
4923 | } |
---|
4924 | return this; |
---|
4925 | }, |
---|
4926 | |
---|
4927 | onRemove: function (map) { |
---|
4928 | map |
---|
4929 | .off('viewreset', this.projectLatlngs, this) |
---|
4930 | .off('moveend', this._updatePath, this); |
---|
4931 | |
---|
4932 | if (this.options.clickable) { |
---|
4933 | this._map.off('click', this._onClick, this); |
---|
4934 | this._map.off('mousemove', this._onMouseMove, this); |
---|
4935 | } |
---|
4936 | |
---|
4937 | this._requestUpdate(); |
---|
4938 | |
---|
4939 | this._map = null; |
---|
4940 | }, |
---|
4941 | |
---|
4942 | _requestUpdate: function () { |
---|
4943 | if (this._map && !L.Path._updateRequest) { |
---|
4944 | L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map); |
---|
4945 | } |
---|
4946 | }, |
---|
4947 | |
---|
4948 | _fireMapMoveEnd: function () { |
---|
4949 | L.Path._updateRequest = null; |
---|
4950 | this.fire('moveend'); |
---|
4951 | }, |
---|
4952 | |
---|
4953 | _initElements: function () { |
---|
4954 | this._map._initPathRoot(); |
---|
4955 | this._ctx = this._map._canvasCtx; |
---|
4956 | }, |
---|
4957 | |
---|
4958 | _updateStyle: function () { |
---|
4959 | var options = this.options; |
---|
4960 | |
---|
4961 | if (options.stroke) { |
---|
4962 | this._ctx.lineWidth = options.weight; |
---|
4963 | this._ctx.strokeStyle = options.color; |
---|
4964 | } |
---|
4965 | if (options.fill) { |
---|
4966 | this._ctx.fillStyle = options.fillColor || options.color; |
---|
4967 | } |
---|
4968 | }, |
---|
4969 | |
---|
4970 | _drawPath: function () { |
---|
4971 | var i, j, len, len2, point, drawMethod; |
---|
4972 | |
---|
4973 | this._ctx.beginPath(); |
---|
4974 | |
---|
4975 | for (i = 0, len = this._parts.length; i < len; i++) { |
---|
4976 | for (j = 0, len2 = this._parts[i].length; j < len2; j++) { |
---|
4977 | point = this._parts[i][j]; |
---|
4978 | drawMethod = (j === 0 ? 'move' : 'line') + 'To'; |
---|
4979 | |
---|
4980 | this._ctx[drawMethod](point.x, point.y); |
---|
4981 | } |
---|
4982 | // TODO refactor ugly hack |
---|
4983 | if (this instanceof L.Polygon) { |
---|
4984 | this._ctx.closePath(); |
---|
4985 | } |
---|
4986 | } |
---|
4987 | }, |
---|
4988 | |
---|
4989 | _checkIfEmpty: function () { |
---|
4990 | return !this._parts.length; |
---|
4991 | }, |
---|
4992 | |
---|
4993 | _updatePath: function () { |
---|
4994 | if (this._checkIfEmpty()) { return; } |
---|
4995 | |
---|
4996 | var ctx = this._ctx, |
---|
4997 | options = this.options; |
---|
4998 | |
---|
4999 | this._drawPath(); |
---|
5000 | ctx.save(); |
---|
5001 | this._updateStyle(); |
---|
5002 | |
---|
5003 | if (options.fill) { |
---|
5004 | ctx.globalAlpha = options.fillOpacity; |
---|
5005 | ctx.fill(); |
---|
5006 | } |
---|
5007 | |
---|
5008 | if (options.stroke) { |
---|
5009 | ctx.globalAlpha = options.opacity; |
---|
5010 | ctx.stroke(); |
---|
5011 | } |
---|
5012 | |
---|
5013 | ctx.restore(); |
---|
5014 | |
---|
5015 | // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature |
---|
5016 | }, |
---|
5017 | |
---|
5018 | _initEvents: function () { |
---|
5019 | if (this.options.clickable) { |
---|
5020 | // TODO dblclick |
---|
5021 | this._map.on('mousemove', this._onMouseMove, this); |
---|
5022 | this._map.on('click', this._onClick, this); |
---|
5023 | } |
---|
5024 | }, |
---|
5025 | |
---|
5026 | _onClick: function (e) { |
---|
5027 | if (this._containsPoint(e.layerPoint)) { |
---|
5028 | this.fire('click', e); |
---|
5029 | } |
---|
5030 | }, |
---|
5031 | |
---|
5032 | _onMouseMove: function (e) { |
---|
5033 | if (!this._map || this._map._animatingZoom) { return; } |
---|
5034 | |
---|
5035 | // TODO don't do on each move |
---|
5036 | if (this._containsPoint(e.layerPoint)) { |
---|
5037 | this._ctx.canvas.style.cursor = 'pointer'; |
---|
5038 | this._mouseInside = true; |
---|
5039 | this.fire('mouseover', e); |
---|
5040 | |
---|
5041 | } else if (this._mouseInside) { |
---|
5042 | this._ctx.canvas.style.cursor = ''; |
---|
5043 | this._mouseInside = false; |
---|
5044 | this.fire('mouseout', e); |
---|
5045 | } |
---|
5046 | } |
---|
5047 | }); |
---|
5048 | |
---|
5049 | L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : { |
---|
5050 | _initPathRoot: function () { |
---|
5051 | var root = this._pathRoot, |
---|
5052 | ctx; |
---|
5053 | |
---|
5054 | if (!root) { |
---|
5055 | root = this._pathRoot = document.createElement('canvas'); |
---|
5056 | root.style.position = 'absolute'; |
---|
5057 | ctx = this._canvasCtx = root.getContext('2d'); |
---|
5058 | |
---|
5059 | ctx.lineCap = 'round'; |
---|
5060 | ctx.lineJoin = 'round'; |
---|
5061 | |
---|
5062 | this._panes.overlayPane.appendChild(root); |
---|
5063 | |
---|
5064 | if (this.options.zoomAnimation) { |
---|
5065 | this._pathRoot.className = 'leaflet-zoom-animated'; |
---|
5066 | this.on('zoomanim', this._animatePathZoom); |
---|
5067 | this.on('zoomend', this._endPathZoom); |
---|
5068 | } |
---|
5069 | this.on('moveend', this._updateCanvasViewport); |
---|
5070 | this._updateCanvasViewport(); |
---|
5071 | } |
---|
5072 | }, |
---|
5073 | |
---|
5074 | _updateCanvasViewport: function () { |
---|
5075 | // don't redraw while zooming. See _updateSvgViewport for more details |
---|
5076 | if (this._pathZooming) { return; } |
---|
5077 | this._updatePathViewport(); |
---|
5078 | |
---|
5079 | var vp = this._pathViewport, |
---|
5080 | min = vp.min, |
---|
5081 | size = vp.max.subtract(min), |
---|
5082 | root = this._pathRoot; |
---|
5083 | |
---|
5084 | //TODO check if this works properly on mobile webkit |
---|
5085 | L.DomUtil.setPosition(root, min); |
---|
5086 | root.width = size.x; |
---|
5087 | root.height = size.y; |
---|
5088 | root.getContext('2d').translate(-min.x, -min.y); |
---|
5089 | } |
---|
5090 | }); |
---|
5091 | |
---|
5092 | |
---|
5093 | /* |
---|
5094 | * L.LineUtil contains different utility functions for line segments |
---|
5095 | * and polylines (clipping, simplification, distances, etc.) |
---|
5096 | */ |
---|
5097 | |
---|
5098 | /*jshint bitwise:false */ // allow bitwise oprations for this file |
---|
5099 | |
---|
5100 | L.LineUtil = { |
---|
5101 | |
---|
5102 | // Simplify polyline with vertex reduction and Douglas-Peucker simplification. |
---|
5103 | // Improves rendering performance dramatically by lessening the number of points to draw. |
---|
5104 | |
---|
5105 | simplify: function (/*Point[]*/ points, /*Number*/ tolerance) { |
---|
5106 | if (!tolerance || !points.length) { |
---|
5107 | return points.slice(); |
---|
5108 | } |
---|
5109 | |
---|
5110 | var sqTolerance = tolerance * tolerance; |
---|
5111 | |
---|
5112 | // stage 1: vertex reduction |
---|
5113 | points = this._reducePoints(points, sqTolerance); |
---|
5114 | |
---|
5115 | // stage 2: Douglas-Peucker simplification |
---|
5116 | points = this._simplifyDP(points, sqTolerance); |
---|
5117 | |
---|
5118 | return points; |
---|
5119 | }, |
---|
5120 | |
---|
5121 | // distance from a point to a segment between two points |
---|
5122 | pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { |
---|
5123 | return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); |
---|
5124 | }, |
---|
5125 | |
---|
5126 | closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { |
---|
5127 | return this._sqClosestPointOnSegment(p, p1, p2); |
---|
5128 | }, |
---|
5129 | |
---|
5130 | // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm |
---|
5131 | _simplifyDP: function (points, sqTolerance) { |
---|
5132 | |
---|
5133 | var len = points.length, |
---|
5134 | ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, |
---|
5135 | markers = new ArrayConstructor(len); |
---|
5136 | |
---|
5137 | markers[0] = markers[len - 1] = 1; |
---|
5138 | |
---|
5139 | this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); |
---|
5140 | |
---|
5141 | var i, |
---|
5142 | newPoints = []; |
---|
5143 | |
---|
5144 | for (i = 0; i < len; i++) { |
---|
5145 | if (markers[i]) { |
---|
5146 | newPoints.push(points[i]); |
---|
5147 | } |
---|
5148 | } |
---|
5149 | |
---|
5150 | return newPoints; |
---|
5151 | }, |
---|
5152 | |
---|
5153 | _simplifyDPStep: function (points, markers, sqTolerance, first, last) { |
---|
5154 | |
---|
5155 | var maxSqDist = 0, |
---|
5156 | index, i, sqDist; |
---|
5157 | |
---|
5158 | for (i = first + 1; i <= last - 1; i++) { |
---|
5159 | sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); |
---|
5160 | |
---|
5161 | if (sqDist > maxSqDist) { |
---|
5162 | index = i; |
---|
5163 | maxSqDist = sqDist; |
---|
5164 | } |
---|
5165 | } |
---|
5166 | |
---|
5167 | if (maxSqDist > sqTolerance) { |
---|
5168 | markers[index] = 1; |
---|
5169 | |
---|
5170 | this._simplifyDPStep(points, markers, sqTolerance, first, index); |
---|
5171 | this._simplifyDPStep(points, markers, sqTolerance, index, last); |
---|
5172 | } |
---|
5173 | }, |
---|
5174 | |
---|
5175 | // reduce points that are too close to each other to a single point |
---|
5176 | _reducePoints: function (points, sqTolerance) { |
---|
5177 | var reducedPoints = [points[0]]; |
---|
5178 | |
---|
5179 | for (var i = 1, prev = 0, len = points.length; i < len; i++) { |
---|
5180 | if (this._sqDist(points[i], points[prev]) > sqTolerance) { |
---|
5181 | reducedPoints.push(points[i]); |
---|
5182 | prev = i; |
---|
5183 | } |
---|
5184 | } |
---|
5185 | if (prev < len - 1) { |
---|
5186 | reducedPoints.push(points[len - 1]); |
---|
5187 | } |
---|
5188 | return reducedPoints; |
---|
5189 | }, |
---|
5190 | |
---|
5191 | // Cohen-Sutherland line clipping algorithm. |
---|
5192 | // Used to avoid rendering parts of a polyline that are not currently visible. |
---|
5193 | |
---|
5194 | clipSegment: function (a, b, bounds, useLastCode) { |
---|
5195 | var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), |
---|
5196 | codeB = this._getBitCode(b, bounds), |
---|
5197 | |
---|
5198 | codeOut, p, newCode; |
---|
5199 | |
---|
5200 | // save 2nd code to avoid calculating it on the next segment |
---|
5201 | this._lastCode = codeB; |
---|
5202 | |
---|
5203 | while (true) { |
---|
5204 | // if a,b is inside the clip window (trivial accept) |
---|
5205 | if (!(codeA | codeB)) { |
---|
5206 | return [a, b]; |
---|
5207 | // if a,b is outside the clip window (trivial reject) |
---|
5208 | } else if (codeA & codeB) { |
---|
5209 | return false; |
---|
5210 | // other cases |
---|
5211 | } else { |
---|
5212 | codeOut = codeA || codeB; |
---|
5213 | p = this._getEdgeIntersection(a, b, codeOut, bounds); |
---|
5214 | newCode = this._getBitCode(p, bounds); |
---|
5215 | |
---|
5216 | if (codeOut === codeA) { |
---|
5217 | a = p; |
---|
5218 | codeA = newCode; |
---|
5219 | } else { |
---|
5220 | b = p; |
---|
5221 | codeB = newCode; |
---|
5222 | } |
---|
5223 | } |
---|
5224 | } |
---|
5225 | }, |
---|
5226 | |
---|
5227 | _getEdgeIntersection: function (a, b, code, bounds) { |
---|
5228 | var dx = b.x - a.x, |
---|
5229 | dy = b.y - a.y, |
---|
5230 | min = bounds.min, |
---|
5231 | max = bounds.max; |
---|
5232 | |
---|
5233 | if (code & 8) { // top |
---|
5234 | return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y); |
---|
5235 | } else if (code & 4) { // bottom |
---|
5236 | return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y); |
---|
5237 | } else if (code & 2) { // right |
---|
5238 | return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx); |
---|
5239 | } else if (code & 1) { // left |
---|
5240 | return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx); |
---|
5241 | } |
---|
5242 | }, |
---|
5243 | |
---|
5244 | _getBitCode: function (/*Point*/ p, bounds) { |
---|
5245 | var code = 0; |
---|
5246 | |
---|
5247 | if (p.x < bounds.min.x) { // left |
---|
5248 | code |= 1; |
---|
5249 | } else if (p.x > bounds.max.x) { // right |
---|
5250 | code |= 2; |
---|
5251 | } |
---|
5252 | if (p.y < bounds.min.y) { // bottom |
---|
5253 | code |= 4; |
---|
5254 | } else if (p.y > bounds.max.y) { // top |
---|
5255 | code |= 8; |
---|
5256 | } |
---|
5257 | |
---|
5258 | return code; |
---|
5259 | }, |
---|
5260 | |
---|
5261 | // square distance (to avoid unnecessary Math.sqrt calls) |
---|
5262 | _sqDist: function (p1, p2) { |
---|
5263 | var dx = p2.x - p1.x, |
---|
5264 | dy = p2.y - p1.y; |
---|
5265 | return dx * dx + dy * dy; |
---|
5266 | }, |
---|
5267 | |
---|
5268 | // return closest point on segment or distance to that point |
---|
5269 | _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { |
---|
5270 | var x = p1.x, |
---|
5271 | y = p1.y, |
---|
5272 | dx = p2.x - x, |
---|
5273 | dy = p2.y - y, |
---|
5274 | dot = dx * dx + dy * dy, |
---|
5275 | t; |
---|
5276 | |
---|
5277 | if (dot > 0) { |
---|
5278 | t = ((p.x - x) * dx + (p.y - y) * dy) / dot; |
---|
5279 | |
---|
5280 | if (t > 1) { |
---|
5281 | x = p2.x; |
---|
5282 | y = p2.y; |
---|
5283 | } else if (t > 0) { |
---|
5284 | x += dx * t; |
---|
5285 | y += dy * t; |
---|
5286 | } |
---|
5287 | } |
---|
5288 | |
---|
5289 | dx = p.x - x; |
---|
5290 | dy = p.y - y; |
---|
5291 | |
---|
5292 | return sqDist ? dx * dx + dy * dy : new L.Point(x, y); |
---|
5293 | } |
---|
5294 | }; |
---|
5295 | |
---|
5296 | |
---|
5297 | /* |
---|
5298 | * L.Polyline is used to display polylines on a map. |
---|
5299 | */ |
---|
5300 | |
---|
5301 | L.Polyline = L.Path.extend({ |
---|
5302 | initialize: function (latlngs, options) { |
---|
5303 | L.Path.prototype.initialize.call(this, options); |
---|
5304 | |
---|
5305 | this._latlngs = this._convertLatLngs(latlngs); |
---|
5306 | }, |
---|
5307 | |
---|
5308 | options: { |
---|
5309 | // how much to simplify the polyline on each zoom level |
---|
5310 | // more = better performance and smoother look, less = more accurate |
---|
5311 | smoothFactor: 1.0, |
---|
5312 | noClip: false |
---|
5313 | }, |
---|
5314 | |
---|
5315 | projectLatlngs: function () { |
---|
5316 | this._originalPoints = []; |
---|
5317 | |
---|
5318 | for (var i = 0, len = this._latlngs.length; i < len; i++) { |
---|
5319 | this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]); |
---|
5320 | } |
---|
5321 | }, |
---|
5322 | |
---|
5323 | getPathString: function () { |
---|
5324 | for (var i = 0, len = this._parts.length, str = ''; i < len; i++) { |
---|
5325 | str += this._getPathPartStr(this._parts[i]); |
---|
5326 | } |
---|
5327 | return str; |
---|
5328 | }, |
---|
5329 | |
---|
5330 | getLatLngs: function () { |
---|
5331 | return this._latlngs; |
---|
5332 | }, |
---|
5333 | |
---|
5334 | setLatLngs: function (latlngs) { |
---|
5335 | this._latlngs = this._convertLatLngs(latlngs); |
---|
5336 | return this.redraw(); |
---|
5337 | }, |
---|
5338 | |
---|
5339 | addLatLng: function (latlng) { |
---|
5340 | this._latlngs.push(L.latLng(latlng)); |
---|
5341 | return this.redraw(); |
---|
5342 | }, |
---|
5343 | |
---|
5344 | spliceLatLngs: function () { // (Number index, Number howMany) |
---|
5345 | var removed = [].splice.apply(this._latlngs, arguments); |
---|
5346 | this._convertLatLngs(this._latlngs, true); |
---|
5347 | this.redraw(); |
---|
5348 | return removed; |
---|
5349 | }, |
---|
5350 | |
---|
5351 | closestLayerPoint: function (p) { |
---|
5352 | var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null; |
---|
5353 | |
---|
5354 | for (var j = 0, jLen = parts.length; j < jLen; j++) { |
---|
5355 | var points = parts[j]; |
---|
5356 | for (var i = 1, len = points.length; i < len; i++) { |
---|
5357 | p1 = points[i - 1]; |
---|
5358 | p2 = points[i]; |
---|
5359 | var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true); |
---|
5360 | if (sqDist < minDistance) { |
---|
5361 | minDistance = sqDist; |
---|
5362 | minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2); |
---|
5363 | } |
---|
5364 | } |
---|
5365 | } |
---|
5366 | if (minPoint) { |
---|
5367 | minPoint.distance = Math.sqrt(minDistance); |
---|
5368 | } |
---|
5369 | return minPoint; |
---|
5370 | }, |
---|
5371 | |
---|
5372 | getBounds: function () { |
---|
5373 | return new L.LatLngBounds(this.getLatLngs()); |
---|
5374 | }, |
---|
5375 | |
---|
5376 | _convertLatLngs: function (latlngs, overwrite) { |
---|
5377 | var i, len, target = overwrite ? latlngs : []; |
---|
5378 | |
---|
5379 | for (i = 0, len = latlngs.length; i < len; i++) { |
---|
5380 | if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') { |
---|
5381 | return; |
---|
5382 | } |
---|
5383 | target[i] = L.latLng(latlngs[i]); |
---|
5384 | } |
---|
5385 | return target; |
---|
5386 | }, |
---|
5387 | |
---|
5388 | _initEvents: function () { |
---|
5389 | L.Path.prototype._initEvents.call(this); |
---|
5390 | }, |
---|
5391 | |
---|
5392 | _getPathPartStr: function (points) { |
---|
5393 | var round = L.Path.VML; |
---|
5394 | |
---|
5395 | for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) { |
---|
5396 | p = points[j]; |
---|
5397 | if (round) { |
---|
5398 | p._round(); |
---|
5399 | } |
---|
5400 | str += (j ? 'L' : 'M') + p.x + ' ' + p.y; |
---|
5401 | } |
---|
5402 | return str; |
---|
5403 | }, |
---|
5404 | |
---|
5405 | _clipPoints: function () { |
---|
5406 | var points = this._originalPoints, |
---|
5407 | len = points.length, |
---|
5408 | i, k, segment; |
---|
5409 | |
---|
5410 | if (this.options.noClip) { |
---|
5411 | this._parts = [points]; |
---|
5412 | return; |
---|
5413 | } |
---|
5414 | |
---|
5415 | this._parts = []; |
---|
5416 | |
---|
5417 | var parts = this._parts, |
---|
5418 | vp = this._map._pathViewport, |
---|
5419 | lu = L.LineUtil; |
---|
5420 | |
---|
5421 | for (i = 0, k = 0; i < len - 1; i++) { |
---|
5422 | segment = lu.clipSegment(points[i], points[i + 1], vp, i); |
---|
5423 | if (!segment) { |
---|
5424 | continue; |
---|
5425 | } |
---|
5426 | |
---|
5427 | parts[k] = parts[k] || []; |
---|
5428 | parts[k].push(segment[0]); |
---|
5429 | |
---|
5430 | // if segment goes out of screen, or it's the last one, it's the end of the line part |
---|
5431 | if ((segment[1] !== points[i + 1]) || (i === len - 2)) { |
---|
5432 | parts[k].push(segment[1]); |
---|
5433 | k++; |
---|
5434 | } |
---|
5435 | } |
---|
5436 | }, |
---|
5437 | |
---|
5438 | // simplify each clipped part of the polyline |
---|
5439 | _simplifyPoints: function () { |
---|
5440 | var parts = this._parts, |
---|
5441 | lu = L.LineUtil; |
---|
5442 | |
---|
5443 | for (var i = 0, len = parts.length; i < len; i++) { |
---|
5444 | parts[i] = lu.simplify(parts[i], this.options.smoothFactor); |
---|
5445 | } |
---|
5446 | }, |
---|
5447 | |
---|
5448 | _updatePath: function () { |
---|
5449 | if (!this._map) { return; } |
---|
5450 | |
---|
5451 | this._clipPoints(); |
---|
5452 | this._simplifyPoints(); |
---|
5453 | |
---|
5454 | L.Path.prototype._updatePath.call(this); |
---|
5455 | } |
---|
5456 | }); |
---|
5457 | |
---|
5458 | L.polyline = function (latlngs, options) { |
---|
5459 | return new L.Polyline(latlngs, options); |
---|
5460 | }; |
---|
5461 | |
---|
5462 | |
---|
5463 | /* |
---|
5464 | * L.PolyUtil contains utility functions for polygons (clipping, etc.). |
---|
5465 | */ |
---|
5466 | |
---|
5467 | /*jshint bitwise:false */ // allow bitwise operations here |
---|
5468 | |
---|
5469 | L.PolyUtil = {}; |
---|
5470 | |
---|
5471 | /* |
---|
5472 | * Sutherland-Hodgeman polygon clipping algorithm. |
---|
5473 | * Used to avoid rendering parts of a polygon that are not currently visible. |
---|
5474 | */ |
---|
5475 | L.PolyUtil.clipPolygon = function (points, bounds) { |
---|
5476 | var clippedPoints, |
---|
5477 | edges = [1, 4, 2, 8], |
---|
5478 | i, j, k, |
---|
5479 | a, b, |
---|
5480 | len, edge, p, |
---|
5481 | lu = L.LineUtil; |
---|
5482 | |
---|
5483 | for (i = 0, len = points.length; i < len; i++) { |
---|
5484 | points[i]._code = lu._getBitCode(points[i], bounds); |
---|
5485 | } |
---|
5486 | |
---|
5487 | // for each edge (left, bottom, right, top) |
---|
5488 | for (k = 0; k < 4; k++) { |
---|
5489 | edge = edges[k]; |
---|
5490 | clippedPoints = []; |
---|
5491 | |
---|
5492 | for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { |
---|
5493 | a = points[i]; |
---|
5494 | b = points[j]; |
---|
5495 | |
---|
5496 | // if a is inside the clip window |
---|
5497 | if (!(a._code & edge)) { |
---|
5498 | // if b is outside the clip window (a->b goes out of screen) |
---|
5499 | if (b._code & edge) { |
---|
5500 | p = lu._getEdgeIntersection(b, a, edge, bounds); |
---|
5501 | p._code = lu._getBitCode(p, bounds); |
---|
5502 | clippedPoints.push(p); |
---|
5503 | } |
---|
5504 | clippedPoints.push(a); |
---|
5505 | |
---|
5506 | // else if b is inside the clip window (a->b enters the screen) |
---|
5507 | } else if (!(b._code & edge)) { |
---|
5508 | p = lu._getEdgeIntersection(b, a, edge, bounds); |
---|
5509 | p._code = lu._getBitCode(p, bounds); |
---|
5510 | clippedPoints.push(p); |
---|
5511 | } |
---|
5512 | } |
---|
5513 | points = clippedPoints; |
---|
5514 | } |
---|
5515 | |
---|
5516 | return points; |
---|
5517 | }; |
---|
5518 | |
---|
5519 | |
---|
5520 | /* |
---|
5521 | * L.Polygon is used to display polygons on a map. |
---|
5522 | */ |
---|
5523 | |
---|
5524 | L.Polygon = L.Polyline.extend({ |
---|
5525 | options: { |
---|
5526 | fill: true |
---|
5527 | }, |
---|
5528 | |
---|
5529 | initialize: function (latlngs, options) { |
---|
5530 | var i, len, hole; |
---|
5531 | |
---|
5532 | L.Polyline.prototype.initialize.call(this, latlngs, options); |
---|
5533 | |
---|
5534 | if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { |
---|
5535 | this._latlngs = this._convertLatLngs(latlngs[0]); |
---|
5536 | this._holes = latlngs.slice(1); |
---|
5537 | |
---|
5538 | for (i = 0, len = this._holes.length; i < len; i++) { |
---|
5539 | hole = this._holes[i] = this._convertLatLngs(this._holes[i]); |
---|
5540 | if (hole[0].equals(hole[hole.length - 1])) { |
---|
5541 | hole.pop(); |
---|
5542 | } |
---|
5543 | } |
---|
5544 | } |
---|
5545 | |
---|
5546 | // filter out last point if its equal to the first one |
---|
5547 | latlngs = this._latlngs; |
---|
5548 | |
---|
5549 | if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) { |
---|
5550 | latlngs.pop(); |
---|
5551 | } |
---|
5552 | }, |
---|
5553 | |
---|
5554 | projectLatlngs: function () { |
---|
5555 | L.Polyline.prototype.projectLatlngs.call(this); |
---|
5556 | |
---|
5557 | // project polygon holes points |
---|
5558 | // TODO move this logic to Polyline to get rid of duplication |
---|
5559 | this._holePoints = []; |
---|
5560 | |
---|
5561 | if (!this._holes) { return; } |
---|
5562 | |
---|
5563 | var i, j, len, len2; |
---|
5564 | |
---|
5565 | for (i = 0, len = this._holes.length; i < len; i++) { |
---|
5566 | this._holePoints[i] = []; |
---|
5567 | |
---|
5568 | for (j = 0, len2 = this._holes[i].length; j < len2; j++) { |
---|
5569 | this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]); |
---|
5570 | } |
---|
5571 | } |
---|
5572 | }, |
---|
5573 | |
---|
5574 | _clipPoints: function () { |
---|
5575 | var points = this._originalPoints, |
---|
5576 | newParts = []; |
---|
5577 | |
---|
5578 | this._parts = [points].concat(this._holePoints); |
---|
5579 | |
---|
5580 | if (this.options.noClip) { return; } |
---|
5581 | |
---|
5582 | for (var i = 0, len = this._parts.length; i < len; i++) { |
---|
5583 | var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport); |
---|
5584 | if (clipped.length) { |
---|
5585 | newParts.push(clipped); |
---|
5586 | } |
---|
5587 | } |
---|
5588 | |
---|
5589 | this._parts = newParts; |
---|
5590 | }, |
---|
5591 | |
---|
5592 | _getPathPartStr: function (points) { |
---|
5593 | var str = L.Polyline.prototype._getPathPartStr.call(this, points); |
---|
5594 | return str + (L.Browser.svg ? 'z' : 'x'); |
---|
5595 | } |
---|
5596 | }); |
---|
5597 | |
---|
5598 | L.polygon = function (latlngs, options) { |
---|
5599 | return new L.Polygon(latlngs, options); |
---|
5600 | }; |
---|
5601 | |
---|
5602 | |
---|
5603 | /* |
---|
5604 | * Contains L.MultiPolyline and L.MultiPolygon layers. |
---|
5605 | */ |
---|
5606 | |
---|
5607 | (function () { |
---|
5608 | function createMulti(Klass) { |
---|
5609 | |
---|
5610 | return L.FeatureGroup.extend({ |
---|
5611 | |
---|
5612 | initialize: function (latlngs, options) { |
---|
5613 | this._layers = {}; |
---|
5614 | this._options = options; |
---|
5615 | this.setLatLngs(latlngs); |
---|
5616 | }, |
---|
5617 | |
---|
5618 | setLatLngs: function (latlngs) { |
---|
5619 | var i = 0, |
---|
5620 | len = latlngs.length; |
---|
5621 | |
---|
5622 | this.eachLayer(function (layer) { |
---|
5623 | if (i < len) { |
---|
5624 | layer.setLatLngs(latlngs[i++]); |
---|
5625 | } else { |
---|
5626 | this.removeLayer(layer); |
---|
5627 | } |
---|
5628 | }, this); |
---|
5629 | |
---|
5630 | while (i < len) { |
---|
5631 | this.addLayer(new Klass(latlngs[i++], this._options)); |
---|
5632 | } |
---|
5633 | |
---|
5634 | return this; |
---|
5635 | } |
---|
5636 | }); |
---|
5637 | } |
---|
5638 | |
---|
5639 | L.MultiPolyline = createMulti(L.Polyline); |
---|
5640 | L.MultiPolygon = createMulti(L.Polygon); |
---|
5641 | |
---|
5642 | L.multiPolyline = function (latlngs, options) { |
---|
5643 | return new L.MultiPolyline(latlngs, options); |
---|
5644 | }; |
---|
5645 | |
---|
5646 | L.multiPolygon = function (latlngs, options) { |
---|
5647 | return new L.MultiPolygon(latlngs, options); |
---|
5648 | }; |
---|
5649 | }()); |
---|
5650 | |
---|
5651 | |
---|
5652 | /* |
---|
5653 | * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. |
---|
5654 | */ |
---|
5655 | |
---|
5656 | L.Rectangle = L.Polygon.extend({ |
---|
5657 | initialize: function (latLngBounds, options) { |
---|
5658 | L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); |
---|
5659 | }, |
---|
5660 | |
---|
5661 | setBounds: function (latLngBounds) { |
---|
5662 | this.setLatLngs(this._boundsToLatLngs(latLngBounds)); |
---|
5663 | }, |
---|
5664 | |
---|
5665 | _boundsToLatLngs: function (latLngBounds) { |
---|
5666 | latLngBounds = L.latLngBounds(latLngBounds); |
---|
5667 | return [ |
---|
5668 | latLngBounds.getSouthWest(), |
---|
5669 | latLngBounds.getNorthWest(), |
---|
5670 | latLngBounds.getNorthEast(), |
---|
5671 | latLngBounds.getSouthEast() |
---|
5672 | ]; |
---|
5673 | } |
---|
5674 | }); |
---|
5675 | |
---|
5676 | L.rectangle = function (latLngBounds, options) { |
---|
5677 | return new L.Rectangle(latLngBounds, options); |
---|
5678 | }; |
---|
5679 | |
---|
5680 | |
---|
5681 | /* |
---|
5682 | * L.Circle is a circle overlay (with a certain radius in meters). |
---|
5683 | */ |
---|
5684 | |
---|
5685 | L.Circle = L.Path.extend({ |
---|
5686 | initialize: function (latlng, radius, options) { |
---|
5687 | L.Path.prototype.initialize.call(this, options); |
---|
5688 | |
---|
5689 | this._latlng = L.latLng(latlng); |
---|
5690 | this._mRadius = radius; |
---|
5691 | }, |
---|
5692 | |
---|
5693 | options: { |
---|
5694 | fill: true |
---|
5695 | }, |
---|
5696 | |
---|
5697 | setLatLng: function (latlng) { |
---|
5698 | this._latlng = L.latLng(latlng); |
---|
5699 | return this.redraw(); |
---|
5700 | }, |
---|
5701 | |
---|
5702 | setRadius: function (radius) { |
---|
5703 | this._mRadius = radius; |
---|
5704 | return this.redraw(); |
---|
5705 | }, |
---|
5706 | |
---|
5707 | projectLatlngs: function () { |
---|
5708 | var lngRadius = this._getLngRadius(), |
---|
5709 | latlng = this._latlng, |
---|
5710 | pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]); |
---|
5711 | |
---|
5712 | this._point = this._map.latLngToLayerPoint(latlng); |
---|
5713 | this._radius = Math.max(this._point.x - pointLeft.x, 1); |
---|
5714 | }, |
---|
5715 | |
---|
5716 | getBounds: function () { |
---|
5717 | var lngRadius = this._getLngRadius(), |
---|
5718 | latRadius = (this._mRadius / 40075017) * 360, |
---|
5719 | latlng = this._latlng; |
---|
5720 | |
---|
5721 | return new L.LatLngBounds( |
---|
5722 | [latlng.lat - latRadius, latlng.lng - lngRadius], |
---|
5723 | [latlng.lat + latRadius, latlng.lng + lngRadius]); |
---|
5724 | }, |
---|
5725 | |
---|
5726 | getLatLng: function () { |
---|
5727 | return this._latlng; |
---|
5728 | }, |
---|
5729 | |
---|
5730 | getPathString: function () { |
---|
5731 | var p = this._point, |
---|
5732 | r = this._radius; |
---|
5733 | |
---|
5734 | if (this._checkIfEmpty()) { |
---|
5735 | return ''; |
---|
5736 | } |
---|
5737 | |
---|
5738 | if (L.Browser.svg) { |
---|
5739 | return 'M' + p.x + ',' + (p.y - r) + |
---|
5740 | 'A' + r + ',' + r + ',0,1,1,' + |
---|
5741 | (p.x - 0.1) + ',' + (p.y - r) + ' z'; |
---|
5742 | } else { |
---|
5743 | p._round(); |
---|
5744 | r = Math.round(r); |
---|
5745 | return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360); |
---|
5746 | } |
---|
5747 | }, |
---|
5748 | |
---|
5749 | getRadius: function () { |
---|
5750 | return this._mRadius; |
---|
5751 | }, |
---|
5752 | |
---|
5753 | // TODO Earth hardcoded, move into projection code! |
---|
5754 | |
---|
5755 | _getLatRadius: function () { |
---|
5756 | return (this._mRadius / 40075017) * 360; |
---|
5757 | }, |
---|
5758 | |
---|
5759 | _getLngRadius: function () { |
---|
5760 | return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat); |
---|
5761 | }, |
---|
5762 | |
---|
5763 | _checkIfEmpty: function () { |
---|
5764 | if (!this._map) { |
---|
5765 | return false; |
---|
5766 | } |
---|
5767 | var vp = this._map._pathViewport, |
---|
5768 | r = this._radius, |
---|
5769 | p = this._point; |
---|
5770 | |
---|
5771 | return p.x - r > vp.max.x || p.y - r > vp.max.y || |
---|
5772 | p.x + r < vp.min.x || p.y + r < vp.min.y; |
---|
5773 | } |
---|
5774 | }); |
---|
5775 | |
---|
5776 | L.circle = function (latlng, radius, options) { |
---|
5777 | return new L.Circle(latlng, radius, options); |
---|
5778 | }; |
---|
5779 | |
---|
5780 | |
---|
5781 | /* |
---|
5782 | * L.CircleMarker is a circle overlay with a permanent pixel radius. |
---|
5783 | */ |
---|
5784 | |
---|
5785 | L.CircleMarker = L.Circle.extend({ |
---|
5786 | options: { |
---|
5787 | radius: 10, |
---|
5788 | weight: 2 |
---|
5789 | }, |
---|
5790 | |
---|
5791 | initialize: function (latlng, options) { |
---|
5792 | L.Circle.prototype.initialize.call(this, latlng, null, options); |
---|
5793 | this._radius = this.options.radius; |
---|
5794 | }, |
---|
5795 | |
---|
5796 | projectLatlngs: function () { |
---|
5797 | this._point = this._map.latLngToLayerPoint(this._latlng); |
---|
5798 | }, |
---|
5799 | |
---|
5800 | _updateStyle : function () { |
---|
5801 | L.Circle.prototype._updateStyle.call(this); |
---|
5802 | this.setRadius(this.options.radius); |
---|
5803 | }, |
---|
5804 | |
---|
5805 | setRadius: function (radius) { |
---|
5806 | this.options.radius = this._radius = radius; |
---|
5807 | return this.redraw(); |
---|
5808 | } |
---|
5809 | }); |
---|
5810 | |
---|
5811 | L.circleMarker = function (latlng, options) { |
---|
5812 | return new L.CircleMarker(latlng, options); |
---|
5813 | }; |
---|
5814 | |
---|
5815 | |
---|
5816 | /* |
---|
5817 | * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines. |
---|
5818 | */ |
---|
5819 | |
---|
5820 | L.Polyline.include(!L.Path.CANVAS ? {} : { |
---|
5821 | _containsPoint: function (p, closed) { |
---|
5822 | var i, j, k, len, len2, dist, part, |
---|
5823 | w = this.options.weight / 2; |
---|
5824 | |
---|
5825 | if (L.Browser.touch) { |
---|
5826 | w += 10; // polyline click tolerance on touch devices |
---|
5827 | } |
---|
5828 | |
---|
5829 | for (i = 0, len = this._parts.length; i < len; i++) { |
---|
5830 | part = this._parts[i]; |
---|
5831 | for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { |
---|
5832 | if (!closed && (j === 0)) { |
---|
5833 | continue; |
---|
5834 | } |
---|
5835 | |
---|
5836 | dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]); |
---|
5837 | |
---|
5838 | if (dist <= w) { |
---|
5839 | return true; |
---|
5840 | } |
---|
5841 | } |
---|
5842 | } |
---|
5843 | return false; |
---|
5844 | } |
---|
5845 | }); |
---|
5846 | |
---|
5847 | |
---|
5848 | /* |
---|
5849 | * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons. |
---|
5850 | */ |
---|
5851 | |
---|
5852 | L.Polygon.include(!L.Path.CANVAS ? {} : { |
---|
5853 | _containsPoint: function (p) { |
---|
5854 | var inside = false, |
---|
5855 | part, p1, p2, |
---|
5856 | i, j, k, |
---|
5857 | len, len2; |
---|
5858 | |
---|
5859 | // TODO optimization: check if within bounds first |
---|
5860 | |
---|
5861 | if (L.Polyline.prototype._containsPoint.call(this, p, true)) { |
---|
5862 | // click on polygon border |
---|
5863 | return true; |
---|
5864 | } |
---|
5865 | |
---|
5866 | // ray casting algorithm for detecting if point is in polygon |
---|
5867 | |
---|
5868 | for (i = 0, len = this._parts.length; i < len; i++) { |
---|
5869 | part = this._parts[i]; |
---|
5870 | |
---|
5871 | for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { |
---|
5872 | p1 = part[j]; |
---|
5873 | p2 = part[k]; |
---|
5874 | |
---|
5875 | if (((p1.y > p.y) !== (p2.y > p.y)) && |
---|
5876 | (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { |
---|
5877 | inside = !inside; |
---|
5878 | } |
---|
5879 | } |
---|
5880 | } |
---|
5881 | |
---|
5882 | return inside; |
---|
5883 | } |
---|
5884 | }); |
---|
5885 | |
---|
5886 | |
---|
5887 | /* |
---|
5888 | * Extends L.Circle with Canvas-specific code. |
---|
5889 | */ |
---|
5890 | |
---|
5891 | L.Circle.include(!L.Path.CANVAS ? {} : { |
---|
5892 | _drawPath: function () { |
---|
5893 | var p = this._point; |
---|
5894 | this._ctx.beginPath(); |
---|
5895 | this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false); |
---|
5896 | }, |
---|
5897 | |
---|
5898 | _containsPoint: function (p) { |
---|
5899 | var center = this._point, |
---|
5900 | w2 = this.options.stroke ? this.options.weight / 2 : 0; |
---|
5901 | |
---|
5902 | return (p.distanceTo(center) <= this._radius + w2); |
---|
5903 | } |
---|
5904 | }); |
---|
5905 | |
---|
5906 | |
---|
5907 | /* |
---|
5908 | * CircleMarker canvas specific drawing parts. |
---|
5909 | */ |
---|
5910 | |
---|
5911 | L.CircleMarker.include(!L.Path.CANVAS ? {} : { |
---|
5912 | _updateStyle: function () { |
---|
5913 | L.Path.prototype._updateStyle.call(this); |
---|
5914 | } |
---|
5915 | }); |
---|
5916 | |
---|
5917 | |
---|
5918 | /* |
---|
5919 | * L.GeoJSON turns any GeoJSON data into a Leaflet layer. |
---|
5920 | */ |
---|
5921 | |
---|
5922 | L.GeoJSON = L.FeatureGroup.extend({ |
---|
5923 | |
---|
5924 | initialize: function (geojson, options) { |
---|
5925 | L.setOptions(this, options); |
---|
5926 | |
---|
5927 | this._layers = {}; |
---|
5928 | |
---|
5929 | if (geojson) { |
---|
5930 | this.addData(geojson); |
---|
5931 | } |
---|
5932 | }, |
---|
5933 | |
---|
5934 | addData: function (geojson) { |
---|
5935 | var features = L.Util.isArray(geojson) ? geojson : geojson.features, |
---|
5936 | i, len; |
---|
5937 | |
---|
5938 | if (features) { |
---|
5939 | for (i = 0, len = features.length; i < len; i++) { |
---|
5940 | // Only add this if geometry or geometries are set and not null |
---|
5941 | if (features[i].geometries || features[i].geometry || features[i].features) { |
---|
5942 | this.addData(features[i]); |
---|
5943 | } |
---|
5944 | } |
---|
5945 | return this; |
---|
5946 | } |
---|
5947 | |
---|
5948 | var options = this.options; |
---|
5949 | |
---|
5950 | if (options.filter && !options.filter(geojson)) { return; } |
---|
5951 | |
---|
5952 | var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng); |
---|
5953 | layer.feature = L.GeoJSON.asFeature(geojson); |
---|
5954 | |
---|
5955 | layer.defaultOptions = layer.options; |
---|
5956 | this.resetStyle(layer); |
---|
5957 | |
---|
5958 | if (options.onEachFeature) { |
---|
5959 | options.onEachFeature(geojson, layer); |
---|
5960 | } |
---|
5961 | |
---|
5962 | return this.addLayer(layer); |
---|
5963 | }, |
---|
5964 | |
---|
5965 | resetStyle: function (layer) { |
---|
5966 | var style = this.options.style; |
---|
5967 | if (style) { |
---|
5968 | // reset any custom styles |
---|
5969 | L.Util.extend(layer.options, layer.defaultOptions); |
---|
5970 | |
---|
5971 | this._setLayerStyle(layer, style); |
---|
5972 | } |
---|
5973 | }, |
---|
5974 | |
---|
5975 | setStyle: function (style) { |
---|
5976 | this.eachLayer(function (layer) { |
---|
5977 | this._setLayerStyle(layer, style); |
---|
5978 | }, this); |
---|
5979 | }, |
---|
5980 | |
---|
5981 | _setLayerStyle: function (layer, style) { |
---|
5982 | if (typeof style === 'function') { |
---|
5983 | style = style(layer.feature); |
---|
5984 | } |
---|
5985 | if (layer.setStyle) { |
---|
5986 | layer.setStyle(style); |
---|
5987 | } |
---|
5988 | } |
---|
5989 | }); |
---|
5990 | |
---|
5991 | L.extend(L.GeoJSON, { |
---|
5992 | geometryToLayer: function (geojson, pointToLayer, coordsToLatLng) { |
---|
5993 | var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, |
---|
5994 | coords = geometry.coordinates, |
---|
5995 | layers = [], |
---|
5996 | latlng, latlngs, i, len, layer; |
---|
5997 | |
---|
5998 | coordsToLatLng = coordsToLatLng || this.coordsToLatLng; |
---|
5999 | |
---|
6000 | switch (geometry.type) { |
---|
6001 | case 'Point': |
---|
6002 | latlng = coordsToLatLng(coords); |
---|
6003 | return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); |
---|
6004 | |
---|
6005 | case 'MultiPoint': |
---|
6006 | for (i = 0, len = coords.length; i < len; i++) { |
---|
6007 | latlng = coordsToLatLng(coords[i]); |
---|
6008 | layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); |
---|
6009 | layers.push(layer); |
---|
6010 | } |
---|
6011 | return new L.FeatureGroup(layers); |
---|
6012 | |
---|
6013 | case 'LineString': |
---|
6014 | latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng); |
---|
6015 | return new L.Polyline(latlngs); |
---|
6016 | |
---|
6017 | case 'Polygon': |
---|
6018 | latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); |
---|
6019 | return new L.Polygon(latlngs); |
---|
6020 | |
---|
6021 | case 'MultiLineString': |
---|
6022 | latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); |
---|
6023 | return new L.MultiPolyline(latlngs); |
---|
6024 | |
---|
6025 | case 'MultiPolygon': |
---|
6026 | latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng); |
---|
6027 | return new L.MultiPolygon(latlngs); |
---|
6028 | |
---|
6029 | case 'GeometryCollection': |
---|
6030 | for (i = 0, len = geometry.geometries.length; i < len; i++) { |
---|
6031 | |
---|
6032 | layer = this.geometryToLayer({ |
---|
6033 | geometry: geometry.geometries[i], |
---|
6034 | type: 'Feature', |
---|
6035 | properties: geojson.properties |
---|
6036 | }, pointToLayer, coordsToLatLng); |
---|
6037 | |
---|
6038 | layers.push(layer); |
---|
6039 | } |
---|
6040 | return new L.FeatureGroup(layers); |
---|
6041 | |
---|
6042 | default: |
---|
6043 | throw new Error('Invalid GeoJSON object.'); |
---|
6044 | } |
---|
6045 | }, |
---|
6046 | |
---|
6047 | coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng |
---|
6048 | return new L.LatLng(coords[1], coords[0]); |
---|
6049 | }, |
---|
6050 | |
---|
6051 | coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array |
---|
6052 | var latlng, i, len, |
---|
6053 | latlngs = []; |
---|
6054 | |
---|
6055 | for (i = 0, len = coords.length; i < len; i++) { |
---|
6056 | latlng = levelsDeep ? |
---|
6057 | this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : |
---|
6058 | (coordsToLatLng || this.coordsToLatLng)(coords[i]); |
---|
6059 | |
---|
6060 | latlngs.push(latlng); |
---|
6061 | } |
---|
6062 | |
---|
6063 | return latlngs; |
---|
6064 | }, |
---|
6065 | |
---|
6066 | latLngToCoords: function (latLng) { |
---|
6067 | return [latLng.lng, latLng.lat]; |
---|
6068 | }, |
---|
6069 | |
---|
6070 | latLngsToCoords: function (latLngs) { |
---|
6071 | var coords = []; |
---|
6072 | |
---|
6073 | for (var i = 0, len = latLngs.length; i < len; i++) { |
---|
6074 | coords.push(L.GeoJSON.latLngToCoords(latLngs[i])); |
---|
6075 | } |
---|
6076 | |
---|
6077 | return coords; |
---|
6078 | }, |
---|
6079 | |
---|
6080 | getFeature: function (layer, newGeometry) { |
---|
6081 | return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry); |
---|
6082 | }, |
---|
6083 | |
---|
6084 | asFeature: function (geoJSON) { |
---|
6085 | if (geoJSON.type === 'Feature') { |
---|
6086 | return geoJSON; |
---|
6087 | } |
---|
6088 | |
---|
6089 | return { |
---|
6090 | type: 'Feature', |
---|
6091 | properties: {}, |
---|
6092 | geometry: geoJSON |
---|
6093 | }; |
---|
6094 | } |
---|
6095 | }); |
---|
6096 | |
---|
6097 | var PointToGeoJSON = { |
---|
6098 | toGeoJSON: function () { |
---|
6099 | return L.GeoJSON.getFeature(this, { |
---|
6100 | type: 'Point', |
---|
6101 | coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) |
---|
6102 | }); |
---|
6103 | } |
---|
6104 | }; |
---|
6105 | |
---|
6106 | L.Marker.include(PointToGeoJSON); |
---|
6107 | L.Circle.include(PointToGeoJSON); |
---|
6108 | L.CircleMarker.include(PointToGeoJSON); |
---|
6109 | |
---|
6110 | L.Polyline.include({ |
---|
6111 | toGeoJSON: function () { |
---|
6112 | return L.GeoJSON.getFeature(this, { |
---|
6113 | type: 'LineString', |
---|
6114 | coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs()) |
---|
6115 | }); |
---|
6116 | } |
---|
6117 | }); |
---|
6118 | |
---|
6119 | L.Polygon.include({ |
---|
6120 | toGeoJSON: function () { |
---|
6121 | var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())], |
---|
6122 | i, len, hole; |
---|
6123 | |
---|
6124 | coords[0].push(coords[0][0]); |
---|
6125 | |
---|
6126 | if (this._holes) { |
---|
6127 | for (i = 0, len = this._holes.length; i < len; i++) { |
---|
6128 | hole = L.GeoJSON.latLngsToCoords(this._holes[i]); |
---|
6129 | hole.push(hole[0]); |
---|
6130 | coords.push(hole); |
---|
6131 | } |
---|
6132 | } |
---|
6133 | |
---|
6134 | return L.GeoJSON.getFeature(this, { |
---|
6135 | type: 'Polygon', |
---|
6136 | coordinates: coords |
---|
6137 | }); |
---|
6138 | } |
---|
6139 | }); |
---|
6140 | |
---|
6141 | (function () { |
---|
6142 | function includeMulti(Klass, type) { |
---|
6143 | Klass.include({ |
---|
6144 | toGeoJSON: function () { |
---|
6145 | var coords = []; |
---|
6146 | |
---|
6147 | this.eachLayer(function (layer) { |
---|
6148 | coords.push(layer.toGeoJSON().geometry.coordinates); |
---|
6149 | }); |
---|
6150 | |
---|
6151 | return L.GeoJSON.getFeature(this, { |
---|
6152 | type: type, |
---|
6153 | coordinates: coords |
---|
6154 | }); |
---|
6155 | } |
---|
6156 | }); |
---|
6157 | } |
---|
6158 | |
---|
6159 | includeMulti(L.MultiPolyline, 'MultiLineString'); |
---|
6160 | includeMulti(L.MultiPolygon, 'MultiPolygon'); |
---|
6161 | }()); |
---|
6162 | |
---|
6163 | L.LayerGroup.include({ |
---|
6164 | toGeoJSON: function () { |
---|
6165 | var features = []; |
---|
6166 | |
---|
6167 | this.eachLayer(function (layer) { |
---|
6168 | if (layer.toGeoJSON) { |
---|
6169 | features.push(L.GeoJSON.asFeature(layer.toGeoJSON())); |
---|
6170 | } |
---|
6171 | }); |
---|
6172 | |
---|
6173 | return { |
---|
6174 | type: 'FeatureCollection', |
---|
6175 | features: features |
---|
6176 | }; |
---|
6177 | } |
---|
6178 | }); |
---|
6179 | |
---|
6180 | L.geoJson = function (geojson, options) { |
---|
6181 | return new L.GeoJSON(geojson, options); |
---|
6182 | }; |
---|
6183 | |
---|
6184 | |
---|
6185 | /* |
---|
6186 | * L.DomEvent contains functions for working with DOM events. |
---|
6187 | */ |
---|
6188 | |
---|
6189 | L.DomEvent = { |
---|
6190 | /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */ |
---|
6191 | addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object]) |
---|
6192 | |
---|
6193 | var id = L.stamp(fn), |
---|
6194 | key = '_leaflet_' + type + id, |
---|
6195 | handler, originalHandler, newType; |
---|
6196 | |
---|
6197 | if (obj[key]) { return this; } |
---|
6198 | |
---|
6199 | handler = function (e) { |
---|
6200 | return fn.call(context || obj, e || L.DomEvent._getEvent()); |
---|
6201 | }; |
---|
6202 | |
---|
6203 | if (L.Browser.msTouch && type.indexOf('touch') === 0) { |
---|
6204 | return this.addMsTouchListener(obj, type, handler, id); |
---|
6205 | } |
---|
6206 | if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { |
---|
6207 | this.addDoubleTapListener(obj, handler, id); |
---|
6208 | } |
---|
6209 | |
---|
6210 | if ('addEventListener' in obj) { |
---|
6211 | |
---|
6212 | if (type === 'mousewheel') { |
---|
6213 | obj.addEventListener('DOMMouseScroll', handler, false); |
---|
6214 | obj.addEventListener(type, handler, false); |
---|
6215 | |
---|
6216 | } else if ((type === 'mouseenter') || (type === 'mouseleave')) { |
---|
6217 | |
---|
6218 | originalHandler = handler; |
---|
6219 | newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout'); |
---|
6220 | |
---|
6221 | handler = function (e) { |
---|
6222 | if (!L.DomEvent._checkMouse(obj, e)) { return; } |
---|
6223 | return originalHandler(e); |
---|
6224 | }; |
---|
6225 | |
---|
6226 | obj.addEventListener(newType, handler, false); |
---|
6227 | |
---|
6228 | } else if (type === 'click' && L.Browser.android) { |
---|
6229 | originalHandler = handler; |
---|
6230 | handler = function (e) { |
---|
6231 | return L.DomEvent._filterClick(e, originalHandler); |
---|
6232 | }; |
---|
6233 | |
---|
6234 | obj.addEventListener(type, handler, false); |
---|
6235 | } else { |
---|
6236 | obj.addEventListener(type, handler, false); |
---|
6237 | } |
---|
6238 | |
---|
6239 | } else if ('attachEvent' in obj) { |
---|
6240 | obj.attachEvent('on' + type, handler); |
---|
6241 | } |
---|
6242 | |
---|
6243 | obj[key] = handler; |
---|
6244 | |
---|
6245 | return this; |
---|
6246 | }, |
---|
6247 | |
---|
6248 | removeListener: function (obj, type, fn) { // (HTMLElement, String, Function) |
---|
6249 | |
---|
6250 | var id = L.stamp(fn), |
---|
6251 | key = '_leaflet_' + type + id, |
---|
6252 | handler = obj[key]; |
---|
6253 | |
---|
6254 | if (!handler) { return this; } |
---|
6255 | |
---|
6256 | if (L.Browser.msTouch && type.indexOf('touch') === 0) { |
---|
6257 | this.removeMsTouchListener(obj, type, id); |
---|
6258 | } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { |
---|
6259 | this.removeDoubleTapListener(obj, id); |
---|
6260 | |
---|
6261 | } else if ('removeEventListener' in obj) { |
---|
6262 | |
---|
6263 | if (type === 'mousewheel') { |
---|
6264 | obj.removeEventListener('DOMMouseScroll', handler, false); |
---|
6265 | obj.removeEventListener(type, handler, false); |
---|
6266 | |
---|
6267 | } else if ((type === 'mouseenter') || (type === 'mouseleave')) { |
---|
6268 | obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false); |
---|
6269 | } else { |
---|
6270 | obj.removeEventListener(type, handler, false); |
---|
6271 | } |
---|
6272 | } else if ('detachEvent' in obj) { |
---|
6273 | obj.detachEvent('on' + type, handler); |
---|
6274 | } |
---|
6275 | |
---|
6276 | obj[key] = null; |
---|
6277 | |
---|
6278 | return this; |
---|
6279 | }, |
---|
6280 | |
---|
6281 | stopPropagation: function (e) { |
---|
6282 | |
---|
6283 | if (e.stopPropagation) { |
---|
6284 | e.stopPropagation(); |
---|
6285 | } else { |
---|
6286 | e.cancelBubble = true; |
---|
6287 | } |
---|
6288 | return this; |
---|
6289 | }, |
---|
6290 | |
---|
6291 | disableClickPropagation: function (el) { |
---|
6292 | var stop = L.DomEvent.stopPropagation; |
---|
6293 | |
---|
6294 | for (var i = L.Draggable.START.length - 1; i >= 0; i--) { |
---|
6295 | L.DomEvent.addListener(el, L.Draggable.START[i], stop); |
---|
6296 | } |
---|
6297 | |
---|
6298 | return L.DomEvent |
---|
6299 | .addListener(el, 'click', L.DomEvent._fakeStop) |
---|
6300 | .addListener(el, 'dblclick', stop); |
---|
6301 | }, |
---|
6302 | |
---|
6303 | preventDefault: function (e) { |
---|
6304 | |
---|
6305 | if (e.preventDefault) { |
---|
6306 | e.preventDefault(); |
---|
6307 | } else { |
---|
6308 | e.returnValue = false; |
---|
6309 | } |
---|
6310 | return this; |
---|
6311 | }, |
---|
6312 | |
---|
6313 | stop: function (e) { |
---|
6314 | return L.DomEvent.preventDefault(e).stopPropagation(e); |
---|
6315 | }, |
---|
6316 | |
---|
6317 | getMousePosition: function (e, container) { |
---|
6318 | |
---|
6319 | var body = document.body, |
---|
6320 | docEl = document.documentElement, |
---|
6321 | x = e.pageX ? e.pageX : e.clientX + body.scrollLeft + docEl.scrollLeft, |
---|
6322 | y = e.pageY ? e.pageY : e.clientY + body.scrollTop + docEl.scrollTop, |
---|
6323 | pos = new L.Point(x, y); |
---|
6324 | |
---|
6325 | return (container ? pos._subtract(L.DomUtil.getViewportOffset(container)) : pos); |
---|
6326 | }, |
---|
6327 | |
---|
6328 | getWheelDelta: function (e) { |
---|
6329 | |
---|
6330 | var delta = 0; |
---|
6331 | |
---|
6332 | if (e.wheelDelta) { |
---|
6333 | delta = e.wheelDelta / 120; |
---|
6334 | } |
---|
6335 | if (e.detail) { |
---|
6336 | delta = -e.detail / 3; |
---|
6337 | } |
---|
6338 | return delta; |
---|
6339 | }, |
---|
6340 | |
---|
6341 | _fakeStop: function stop(e) { |
---|
6342 | // fakes stopPropagation by setting a special event flag checked in Map mouse events handler |
---|
6343 | // jshint camelcase: false |
---|
6344 | e._leaflet_stop = true; |
---|
6345 | }, |
---|
6346 | |
---|
6347 | // check if element really left/entered the event target (for mouseenter/mouseleave) |
---|
6348 | _checkMouse: function (el, e) { |
---|
6349 | |
---|
6350 | var related = e.relatedTarget; |
---|
6351 | |
---|
6352 | if (!related) { return true; } |
---|
6353 | |
---|
6354 | try { |
---|
6355 | while (related && (related !== el)) { |
---|
6356 | related = related.parentNode; |
---|
6357 | } |
---|
6358 | } catch (err) { |
---|
6359 | return false; |
---|
6360 | } |
---|
6361 | return (related !== el); |
---|
6362 | }, |
---|
6363 | |
---|
6364 | _getEvent: function () { // evil magic for IE |
---|
6365 | /*jshint noarg:false */ |
---|
6366 | var e = window.event; |
---|
6367 | if (!e) { |
---|
6368 | var caller = arguments.callee.caller; |
---|
6369 | while (caller) { |
---|
6370 | e = caller['arguments'][0]; |
---|
6371 | if (e && window.Event === e.constructor) { |
---|
6372 | break; |
---|
6373 | } |
---|
6374 | caller = caller.caller; |
---|
6375 | } |
---|
6376 | } |
---|
6377 | return e; |
---|
6378 | }, |
---|
6379 | |
---|
6380 | // this is a horrible workaround for a bug in Android where a single touch triggers two click events |
---|
6381 | _filterClick: function (e, handler) { |
---|
6382 | var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), |
---|
6383 | elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); |
---|
6384 | |
---|
6385 | // are they closer together than 1000ms yet more than 100ms? |
---|
6386 | // Android typically triggers them ~300ms apart while multiple listeners |
---|
6387 | // on the same event should be triggered far faster; |
---|
6388 | // or check if click is simulated on the element, and if it is, reject any non-simulated events |
---|
6389 | |
---|
6390 | if ((elapsed && elapsed > 100 && elapsed < 1000) || (e.target._simulatedClick && !e._simulated)) { |
---|
6391 | L.DomEvent.stop(e); |
---|
6392 | return; |
---|
6393 | } |
---|
6394 | L.DomEvent._lastClick = timeStamp; |
---|
6395 | |
---|
6396 | return handler(e); |
---|
6397 | } |
---|
6398 | }; |
---|
6399 | |
---|
6400 | L.DomEvent.on = L.DomEvent.addListener; |
---|
6401 | L.DomEvent.off = L.DomEvent.removeListener; |
---|
6402 | |
---|
6403 | |
---|
6404 | /* |
---|
6405 | * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too. |
---|
6406 | */ |
---|
6407 | |
---|
6408 | L.Draggable = L.Class.extend({ |
---|
6409 | includes: L.Mixin.Events, |
---|
6410 | |
---|
6411 | statics: { |
---|
6412 | START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], |
---|
6413 | END: { |
---|
6414 | mousedown: 'mouseup', |
---|
6415 | touchstart: 'touchend', |
---|
6416 | MSPointerDown: 'touchend' |
---|
6417 | }, |
---|
6418 | MOVE: { |
---|
6419 | mousedown: 'mousemove', |
---|
6420 | touchstart: 'touchmove', |
---|
6421 | MSPointerDown: 'touchmove' |
---|
6422 | } |
---|
6423 | }, |
---|
6424 | |
---|
6425 | initialize: function (element, dragStartTarget) { |
---|
6426 | this._element = element; |
---|
6427 | this._dragStartTarget = dragStartTarget || element; |
---|
6428 | }, |
---|
6429 | |
---|
6430 | enable: function () { |
---|
6431 | if (this._enabled) { return; } |
---|
6432 | |
---|
6433 | for (var i = L.Draggable.START.length - 1; i >= 0; i--) { |
---|
6434 | L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); |
---|
6435 | } |
---|
6436 | |
---|
6437 | this._enabled = true; |
---|
6438 | }, |
---|
6439 | |
---|
6440 | disable: function () { |
---|
6441 | if (!this._enabled) { return; } |
---|
6442 | |
---|
6443 | for (var i = L.Draggable.START.length - 1; i >= 0; i--) { |
---|
6444 | L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); |
---|
6445 | } |
---|
6446 | |
---|
6447 | this._enabled = false; |
---|
6448 | this._moved = false; |
---|
6449 | }, |
---|
6450 | |
---|
6451 | _onDown: function (e) { |
---|
6452 | if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } |
---|
6453 | |
---|
6454 | L.DomEvent |
---|
6455 | .stopPropagation(e); |
---|
6456 | |
---|
6457 | if (L.Draggable._disabled) { return; } |
---|
6458 | |
---|
6459 | L.DomUtil.disableImageDrag(); |
---|
6460 | L.DomUtil.disableTextSelection(); |
---|
6461 | |
---|
6462 | var first = e.touches ? e.touches[0] : e, |
---|
6463 | el = first.target; |
---|
6464 | |
---|
6465 | // if touching a link, highlight it |
---|
6466 | if (L.Browser.touch && el.tagName.toLowerCase() === 'a') { |
---|
6467 | L.DomUtil.addClass(el, 'leaflet-active'); |
---|
6468 | } |
---|
6469 | |
---|
6470 | this._moved = false; |
---|
6471 | |
---|
6472 | if (this._moving) { return; } |
---|
6473 | |
---|
6474 | this._startPoint = new L.Point(first.clientX, first.clientY); |
---|
6475 | this._startPos = this._newPos = L.DomUtil.getPosition(this._element); |
---|
6476 | |
---|
6477 | L.DomEvent |
---|
6478 | .on(document, L.Draggable.MOVE[e.type], this._onMove, this) |
---|
6479 | .on(document, L.Draggable.END[e.type], this._onUp, this); |
---|
6480 | }, |
---|
6481 | |
---|
6482 | _onMove: function (e) { |
---|
6483 | if (e.touches && e.touches.length > 1) { return; } |
---|
6484 | |
---|
6485 | var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), |
---|
6486 | newPoint = new L.Point(first.clientX, first.clientY), |
---|
6487 | offset = newPoint.subtract(this._startPoint); |
---|
6488 | |
---|
6489 | if (!offset.x && !offset.y) { return; } |
---|
6490 | |
---|
6491 | L.DomEvent.preventDefault(e); |
---|
6492 | |
---|
6493 | if (!this._moved) { |
---|
6494 | this.fire('dragstart'); |
---|
6495 | |
---|
6496 | this._moved = true; |
---|
6497 | this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); |
---|
6498 | |
---|
6499 | if (!L.Browser.touch) { |
---|
6500 | L.DomUtil.addClass(document.body, 'leaflet-dragging'); |
---|
6501 | } |
---|
6502 | } |
---|
6503 | |
---|
6504 | this._newPos = this._startPos.add(offset); |
---|
6505 | this._moving = true; |
---|
6506 | |
---|
6507 | L.Util.cancelAnimFrame(this._animRequest); |
---|
6508 | this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); |
---|
6509 | }, |
---|
6510 | |
---|
6511 | _updatePosition: function () { |
---|
6512 | this.fire('predrag'); |
---|
6513 | L.DomUtil.setPosition(this._element, this._newPos); |
---|
6514 | this.fire('drag'); |
---|
6515 | }, |
---|
6516 | |
---|
6517 | _onUp: function () { |
---|
6518 | if (!L.Browser.touch) { |
---|
6519 | L.DomUtil.removeClass(document.body, 'leaflet-dragging'); |
---|
6520 | } |
---|
6521 | |
---|
6522 | for (var i in L.Draggable.MOVE) { |
---|
6523 | L.DomEvent |
---|
6524 | .off(document, L.Draggable.MOVE[i], this._onMove) |
---|
6525 | .off(document, L.Draggable.END[i], this._onUp); |
---|
6526 | } |
---|
6527 | |
---|
6528 | L.DomUtil.enableImageDrag(); |
---|
6529 | L.DomUtil.enableTextSelection(); |
---|
6530 | |
---|
6531 | if (this._moved) { |
---|
6532 | // ensure drag is not fired after dragend |
---|
6533 | L.Util.cancelAnimFrame(this._animRequest); |
---|
6534 | |
---|
6535 | this.fire('dragend'); |
---|
6536 | } |
---|
6537 | |
---|
6538 | this._moving = false; |
---|
6539 | } |
---|
6540 | }); |
---|
6541 | |
---|
6542 | |
---|
6543 | /* |
---|
6544 | L.Handler is a base class for handler classes that are used internally to inject |
---|
6545 | interaction features like dragging to classes like Map and Marker. |
---|
6546 | */ |
---|
6547 | |
---|
6548 | L.Handler = L.Class.extend({ |
---|
6549 | initialize: function (map) { |
---|
6550 | this._map = map; |
---|
6551 | }, |
---|
6552 | |
---|
6553 | enable: function () { |
---|
6554 | if (this._enabled) { return; } |
---|
6555 | |
---|
6556 | this._enabled = true; |
---|
6557 | this.addHooks(); |
---|
6558 | }, |
---|
6559 | |
---|
6560 | disable: function () { |
---|
6561 | if (!this._enabled) { return; } |
---|
6562 | |
---|
6563 | this._enabled = false; |
---|
6564 | this.removeHooks(); |
---|
6565 | }, |
---|
6566 | |
---|
6567 | enabled: function () { |
---|
6568 | return !!this._enabled; |
---|
6569 | } |
---|
6570 | }); |
---|
6571 | |
---|
6572 | |
---|
6573 | /* |
---|
6574 | * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. |
---|
6575 | */ |
---|
6576 | |
---|
6577 | L.Map.mergeOptions({ |
---|
6578 | dragging: true, |
---|
6579 | |
---|
6580 | inertia: !L.Browser.android23, |
---|
6581 | inertiaDeceleration: 3400, // px/s^2 |
---|
6582 | inertiaMaxSpeed: Infinity, // px/s |
---|
6583 | inertiaThreshold: L.Browser.touch ? 32 : 18, // ms |
---|
6584 | easeLinearity: 0.25, |
---|
6585 | |
---|
6586 | // TODO refactor, move to CRS |
---|
6587 | worldCopyJump: false |
---|
6588 | }); |
---|
6589 | |
---|
6590 | L.Map.Drag = L.Handler.extend({ |
---|
6591 | addHooks: function () { |
---|
6592 | if (!this._draggable) { |
---|
6593 | var map = this._map; |
---|
6594 | |
---|
6595 | this._draggable = new L.Draggable(map._mapPane, map._container); |
---|
6596 | |
---|
6597 | this._draggable.on({ |
---|
6598 | 'dragstart': this._onDragStart, |
---|
6599 | 'drag': this._onDrag, |
---|
6600 | 'dragend': this._onDragEnd |
---|
6601 | }, this); |
---|
6602 | |
---|
6603 | if (map.options.worldCopyJump) { |
---|
6604 | this._draggable.on('predrag', this._onPreDrag, this); |
---|
6605 | map.on('viewreset', this._onViewReset, this); |
---|
6606 | } |
---|
6607 | } |
---|
6608 | this._draggable.enable(); |
---|
6609 | }, |
---|
6610 | |
---|
6611 | removeHooks: function () { |
---|
6612 | this._draggable.disable(); |
---|
6613 | }, |
---|
6614 | |
---|
6615 | moved: function () { |
---|
6616 | return this._draggable && this._draggable._moved; |
---|
6617 | }, |
---|
6618 | |
---|
6619 | _onDragStart: function () { |
---|
6620 | var map = this._map; |
---|
6621 | |
---|
6622 | if (map._panAnim) { |
---|
6623 | map._panAnim.stop(); |
---|
6624 | } |
---|
6625 | |
---|
6626 | map |
---|
6627 | .fire('movestart') |
---|
6628 | .fire('dragstart'); |
---|
6629 | |
---|
6630 | if (map.options.inertia) { |
---|
6631 | this._positions = []; |
---|
6632 | this._times = []; |
---|
6633 | } |
---|
6634 | }, |
---|
6635 | |
---|
6636 | _onDrag: function () { |
---|
6637 | if (this._map.options.inertia) { |
---|
6638 | var time = this._lastTime = +new Date(), |
---|
6639 | pos = this._lastPos = this._draggable._newPos; |
---|
6640 | |
---|
6641 | this._positions.push(pos); |
---|
6642 | this._times.push(time); |
---|
6643 | |
---|
6644 | if (time - this._times[0] > 200) { |
---|
6645 | this._positions.shift(); |
---|
6646 | this._times.shift(); |
---|
6647 | } |
---|
6648 | } |
---|
6649 | |
---|
6650 | this._map |
---|
6651 | .fire('move') |
---|
6652 | .fire('drag'); |
---|
6653 | }, |
---|
6654 | |
---|
6655 | _onViewReset: function () { |
---|
6656 | // TODO fix hardcoded Earth values |
---|
6657 | var pxCenter = this._map.getSize()._divideBy(2), |
---|
6658 | pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); |
---|
6659 | |
---|
6660 | this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; |
---|
6661 | this._worldWidth = this._map.project([0, 180]).x; |
---|
6662 | }, |
---|
6663 | |
---|
6664 | _onPreDrag: function () { |
---|
6665 | // TODO refactor to be able to adjust map pane position after zoom |
---|
6666 | var worldWidth = this._worldWidth, |
---|
6667 | halfWidth = Math.round(worldWidth / 2), |
---|
6668 | dx = this._initialWorldOffset, |
---|
6669 | x = this._draggable._newPos.x, |
---|
6670 | newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, |
---|
6671 | newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, |
---|
6672 | newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; |
---|
6673 | |
---|
6674 | this._draggable._newPos.x = newX; |
---|
6675 | }, |
---|
6676 | |
---|
6677 | _onDragEnd: function () { |
---|
6678 | var map = this._map, |
---|
6679 | options = map.options, |
---|
6680 | delay = +new Date() - this._lastTime, |
---|
6681 | |
---|
6682 | noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; |
---|
6683 | |
---|
6684 | map.fire('dragend'); |
---|
6685 | |
---|
6686 | if (noInertia) { |
---|
6687 | map.fire('moveend'); |
---|
6688 | |
---|
6689 | } else { |
---|
6690 | |
---|
6691 | var direction = this._lastPos.subtract(this._positions[0]), |
---|
6692 | duration = (this._lastTime + delay - this._times[0]) / 1000, |
---|
6693 | ease = options.easeLinearity, |
---|
6694 | |
---|
6695 | speedVector = direction.multiplyBy(ease / duration), |
---|
6696 | speed = speedVector.distanceTo([0, 0]), |
---|
6697 | |
---|
6698 | limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), |
---|
6699 | limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), |
---|
6700 | |
---|
6701 | decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), |
---|
6702 | offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); |
---|
6703 | |
---|
6704 | if (!offset.x || !offset.y) { |
---|
6705 | map.fire('moveend'); |
---|
6706 | |
---|
6707 | } else { |
---|
6708 | L.Util.requestAnimFrame(function () { |
---|
6709 | map.panBy(offset, { |
---|
6710 | duration: decelerationDuration, |
---|
6711 | easeLinearity: ease, |
---|
6712 | noMoveStart: true |
---|
6713 | }); |
---|
6714 | }); |
---|
6715 | } |
---|
6716 | } |
---|
6717 | } |
---|
6718 | }); |
---|
6719 | |
---|
6720 | L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); |
---|
6721 | |
---|
6722 | |
---|
6723 | /* |
---|
6724 | * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. |
---|
6725 | */ |
---|
6726 | |
---|
6727 | L.Map.mergeOptions({ |
---|
6728 | doubleClickZoom: true |
---|
6729 | }); |
---|
6730 | |
---|
6731 | L.Map.DoubleClickZoom = L.Handler.extend({ |
---|
6732 | addHooks: function () { |
---|
6733 | this._map.on('dblclick', this._onDoubleClick); |
---|
6734 | }, |
---|
6735 | |
---|
6736 | removeHooks: function () { |
---|
6737 | this._map.off('dblclick', this._onDoubleClick); |
---|
6738 | }, |
---|
6739 | |
---|
6740 | _onDoubleClick: function (e) { |
---|
6741 | this.setZoomAround(e.containerPoint, this._zoom + 1); |
---|
6742 | } |
---|
6743 | }); |
---|
6744 | |
---|
6745 | L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); |
---|
6746 | |
---|
6747 | |
---|
6748 | /* |
---|
6749 | * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. |
---|
6750 | */ |
---|
6751 | |
---|
6752 | L.Map.mergeOptions({ |
---|
6753 | scrollWheelZoom: true |
---|
6754 | }); |
---|
6755 | |
---|
6756 | L.Map.ScrollWheelZoom = L.Handler.extend({ |
---|
6757 | addHooks: function () { |
---|
6758 | L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); |
---|
6759 | L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault); |
---|
6760 | this._delta = 0; |
---|
6761 | }, |
---|
6762 | |
---|
6763 | removeHooks: function () { |
---|
6764 | L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll); |
---|
6765 | L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault); |
---|
6766 | }, |
---|
6767 | |
---|
6768 | _onWheelScroll: function (e) { |
---|
6769 | var delta = L.DomEvent.getWheelDelta(e); |
---|
6770 | |
---|
6771 | this._delta += delta; |
---|
6772 | this._lastMousePos = this._map.mouseEventToContainerPoint(e); |
---|
6773 | |
---|
6774 | if (!this._startTime) { |
---|
6775 | this._startTime = +new Date(); |
---|
6776 | } |
---|
6777 | |
---|
6778 | var left = Math.max(40 - (+new Date() - this._startTime), 0); |
---|
6779 | |
---|
6780 | clearTimeout(this._timer); |
---|
6781 | this._timer = setTimeout(L.bind(this._performZoom, this), left); |
---|
6782 | |
---|
6783 | L.DomEvent.preventDefault(e); |
---|
6784 | L.DomEvent.stopPropagation(e); |
---|
6785 | }, |
---|
6786 | |
---|
6787 | _performZoom: function () { |
---|
6788 | var map = this._map, |
---|
6789 | delta = this._delta, |
---|
6790 | zoom = map.getZoom(); |
---|
6791 | |
---|
6792 | delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta); |
---|
6793 | delta = Math.max(Math.min(delta, 4), -4); |
---|
6794 | delta = map._limitZoom(zoom + delta) - zoom; |
---|
6795 | |
---|
6796 | this._delta = 0; |
---|
6797 | this._startTime = null; |
---|
6798 | |
---|
6799 | if (!delta) { return; } |
---|
6800 | |
---|
6801 | map.setZoomAround(this._lastMousePos, zoom + delta); |
---|
6802 | } |
---|
6803 | }); |
---|
6804 | |
---|
6805 | L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); |
---|
6806 | |
---|
6807 | |
---|
6808 | /* |
---|
6809 | * Extends the event handling code with double tap support for mobile browsers. |
---|
6810 | */ |
---|
6811 | |
---|
6812 | L.extend(L.DomEvent, { |
---|
6813 | |
---|
6814 | _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart', |
---|
6815 | _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend', |
---|
6816 | |
---|
6817 | // inspired by Zepto touch code by Thomas Fuchs |
---|
6818 | addDoubleTapListener: function (obj, handler, id) { |
---|
6819 | var last, |
---|
6820 | doubleTap = false, |
---|
6821 | delay = 250, |
---|
6822 | touch, |
---|
6823 | pre = '_leaflet_', |
---|
6824 | touchstart = this._touchstart, |
---|
6825 | touchend = this._touchend, |
---|
6826 | trackedTouches = []; |
---|
6827 | |
---|
6828 | function onTouchStart(e) { |
---|
6829 | var count; |
---|
6830 | |
---|
6831 | if (L.Browser.msTouch) { |
---|
6832 | trackedTouches.push(e.pointerId); |
---|
6833 | count = trackedTouches.length; |
---|
6834 | } else { |
---|
6835 | count = e.touches.length; |
---|
6836 | } |
---|
6837 | if (count > 1) { |
---|
6838 | return; |
---|
6839 | } |
---|
6840 | |
---|
6841 | var now = Date.now(), |
---|
6842 | delta = now - (last || now); |
---|
6843 | |
---|
6844 | touch = e.touches ? e.touches[0] : e; |
---|
6845 | doubleTap = (delta > 0 && delta <= delay); |
---|
6846 | last = now; |
---|
6847 | } |
---|
6848 | |
---|
6849 | function onTouchEnd(e) { |
---|
6850 | if (L.Browser.msTouch) { |
---|
6851 | var idx = trackedTouches.indexOf(e.pointerId); |
---|
6852 | if (idx === -1) { |
---|
6853 | return; |
---|
6854 | } |
---|
6855 | trackedTouches.splice(idx, 1); |
---|
6856 | } |
---|
6857 | |
---|
6858 | if (doubleTap) { |
---|
6859 | if (L.Browser.msTouch) { |
---|
6860 | // work around .type being readonly with MSPointer* events |
---|
6861 | var newTouch = { }, |
---|
6862 | prop; |
---|
6863 | |
---|
6864 | // jshint forin:false |
---|
6865 | for (var i in touch) { |
---|
6866 | prop = touch[i]; |
---|
6867 | if (typeof prop === 'function') { |
---|
6868 | newTouch[i] = prop.bind(touch); |
---|
6869 | } else { |
---|
6870 | newTouch[i] = prop; |
---|
6871 | } |
---|
6872 | } |
---|
6873 | touch = newTouch; |
---|
6874 | } |
---|
6875 | touch.type = 'dblclick'; |
---|
6876 | handler(touch); |
---|
6877 | last = null; |
---|
6878 | } |
---|
6879 | } |
---|
6880 | obj[pre + touchstart + id] = onTouchStart; |
---|
6881 | obj[pre + touchend + id] = onTouchEnd; |
---|
6882 | |
---|
6883 | // on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen |
---|
6884 | // will not come through to us, so we will lose track of how many touches are ongoing |
---|
6885 | var endElement = L.Browser.msTouch ? document.documentElement : obj; |
---|
6886 | |
---|
6887 | obj.addEventListener(touchstart, onTouchStart, false); |
---|
6888 | endElement.addEventListener(touchend, onTouchEnd, false); |
---|
6889 | |
---|
6890 | if (L.Browser.msTouch) { |
---|
6891 | endElement.addEventListener('MSPointerCancel', onTouchEnd, false); |
---|
6892 | } |
---|
6893 | |
---|
6894 | return this; |
---|
6895 | }, |
---|
6896 | |
---|
6897 | removeDoubleTapListener: function (obj, id) { |
---|
6898 | var pre = '_leaflet_'; |
---|
6899 | |
---|
6900 | obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); |
---|
6901 | (L.Browser.msTouch ? document.documentElement : obj).removeEventListener( |
---|
6902 | this._touchend, obj[pre + this._touchend + id], false); |
---|
6903 | |
---|
6904 | if (L.Browser.msTouch) { |
---|
6905 | document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false); |
---|
6906 | } |
---|
6907 | |
---|
6908 | return this; |
---|
6909 | } |
---|
6910 | }); |
---|
6911 | |
---|
6912 | |
---|
6913 | /* |
---|
6914 | * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. |
---|
6915 | */ |
---|
6916 | |
---|
6917 | L.extend(L.DomEvent, { |
---|
6918 | |
---|
6919 | _msTouches: [], |
---|
6920 | _msDocumentListener: false, |
---|
6921 | |
---|
6922 | // Provides a touch events wrapper for msPointer events. |
---|
6923 | // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019 |
---|
6924 | |
---|
6925 | addMsTouchListener: function (obj, type, handler, id) { |
---|
6926 | |
---|
6927 | switch (type) { |
---|
6928 | case 'touchstart': |
---|
6929 | return this.addMsTouchListenerStart(obj, type, handler, id); |
---|
6930 | case 'touchend': |
---|
6931 | return this.addMsTouchListenerEnd(obj, type, handler, id); |
---|
6932 | case 'touchmove': |
---|
6933 | return this.addMsTouchListenerMove(obj, type, handler, id); |
---|
6934 | default: |
---|
6935 | throw 'Unknown touch event type'; |
---|
6936 | } |
---|
6937 | }, |
---|
6938 | |
---|
6939 | addMsTouchListenerStart: function (obj, type, handler, id) { |
---|
6940 | var pre = '_leaflet_', |
---|
6941 | touches = this._msTouches; |
---|
6942 | |
---|
6943 | var cb = function (e) { |
---|
6944 | |
---|
6945 | var alreadyInArray = false; |
---|
6946 | for (var i = 0; i < touches.length; i++) { |
---|
6947 | if (touches[i].pointerId === e.pointerId) { |
---|
6948 | alreadyInArray = true; |
---|
6949 | break; |
---|
6950 | } |
---|
6951 | } |
---|
6952 | if (!alreadyInArray) { |
---|
6953 | touches.push(e); |
---|
6954 | } |
---|
6955 | |
---|
6956 | e.touches = touches.slice(); |
---|
6957 | e.changedTouches = [e]; |
---|
6958 | |
---|
6959 | handler(e); |
---|
6960 | }; |
---|
6961 | |
---|
6962 | obj[pre + 'touchstart' + id] = cb; |
---|
6963 | obj.addEventListener('MSPointerDown', cb, false); |
---|
6964 | |
---|
6965 | // need to also listen for end events to keep the _msTouches list accurate |
---|
6966 | // this needs to be on the body and never go away |
---|
6967 | if (!this._msDocumentListener) { |
---|
6968 | var internalCb = function (e) { |
---|
6969 | for (var i = 0; i < touches.length; i++) { |
---|
6970 | if (touches[i].pointerId === e.pointerId) { |
---|
6971 | touches.splice(i, 1); |
---|
6972 | break; |
---|
6973 | } |
---|
6974 | } |
---|
6975 | }; |
---|
6976 | //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there |
---|
6977 | document.documentElement.addEventListener('MSPointerUp', internalCb, false); |
---|
6978 | document.documentElement.addEventListener('MSPointerCancel', internalCb, false); |
---|
6979 | |
---|
6980 | this._msDocumentListener = true; |
---|
6981 | } |
---|
6982 | |
---|
6983 | return this; |
---|
6984 | }, |
---|
6985 | |
---|
6986 | addMsTouchListenerMove: function (obj, type, handler, id) { |
---|
6987 | var pre = '_leaflet_', |
---|
6988 | touches = this._msTouches; |
---|
6989 | |
---|
6990 | function cb(e) { |
---|
6991 | |
---|
6992 | // don't fire touch moves when mouse isn't down |
---|
6993 | if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; } |
---|
6994 | |
---|
6995 | for (var i = 0; i < touches.length; i++) { |
---|
6996 | if (touches[i].pointerId === e.pointerId) { |
---|
6997 | touches[i] = e; |
---|
6998 | break; |
---|
6999 | } |
---|
7000 | } |
---|
7001 | |
---|
7002 | e.touches = touches.slice(); |
---|
7003 | e.changedTouches = [e]; |
---|
7004 | |
---|
7005 | handler(e); |
---|
7006 | } |
---|
7007 | |
---|
7008 | obj[pre + 'touchmove' + id] = cb; |
---|
7009 | obj.addEventListener('MSPointerMove', cb, false); |
---|
7010 | |
---|
7011 | return this; |
---|
7012 | }, |
---|
7013 | |
---|
7014 | addMsTouchListenerEnd: function (obj, type, handler, id) { |
---|
7015 | var pre = '_leaflet_', |
---|
7016 | touches = this._msTouches; |
---|
7017 | |
---|
7018 | var cb = function (e) { |
---|
7019 | for (var i = 0; i < touches.length; i++) { |
---|
7020 | if (touches[i].pointerId === e.pointerId) { |
---|
7021 | touches.splice(i, 1); |
---|
7022 | break; |
---|
7023 | } |
---|
7024 | } |
---|
7025 | |
---|
7026 | e.touches = touches.slice(); |
---|
7027 | e.changedTouches = [e]; |
---|
7028 | |
---|
7029 | handler(e); |
---|
7030 | }; |
---|
7031 | |
---|
7032 | obj[pre + 'touchend' + id] = cb; |
---|
7033 | obj.addEventListener('MSPointerUp', cb, false); |
---|
7034 | obj.addEventListener('MSPointerCancel', cb, false); |
---|
7035 | |
---|
7036 | return this; |
---|
7037 | }, |
---|
7038 | |
---|
7039 | removeMsTouchListener: function (obj, type, id) { |
---|
7040 | var pre = '_leaflet_', |
---|
7041 | cb = obj[pre + type + id]; |
---|
7042 | |
---|
7043 | switch (type) { |
---|
7044 | case 'touchstart': |
---|
7045 | obj.removeEventListener('MSPointerDown', cb, false); |
---|
7046 | break; |
---|
7047 | case 'touchmove': |
---|
7048 | obj.removeEventListener('MSPointerMove', cb, false); |
---|
7049 | break; |
---|
7050 | case 'touchend': |
---|
7051 | obj.removeEventListener('MSPointerUp', cb, false); |
---|
7052 | obj.removeEventListener('MSPointerCancel', cb, false); |
---|
7053 | break; |
---|
7054 | } |
---|
7055 | |
---|
7056 | return this; |
---|
7057 | } |
---|
7058 | }); |
---|
7059 | |
---|
7060 | |
---|
7061 | /* |
---|
7062 | * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. |
---|
7063 | */ |
---|
7064 | |
---|
7065 | L.Map.mergeOptions({ |
---|
7066 | touchZoom: L.Browser.touch && !L.Browser.android23 |
---|
7067 | }); |
---|
7068 | |
---|
7069 | L.Map.TouchZoom = L.Handler.extend({ |
---|
7070 | addHooks: function () { |
---|
7071 | L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); |
---|
7072 | }, |
---|
7073 | |
---|
7074 | removeHooks: function () { |
---|
7075 | L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); |
---|
7076 | }, |
---|
7077 | |
---|
7078 | _onTouchStart: function (e) { |
---|
7079 | var map = this._map; |
---|
7080 | |
---|
7081 | if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } |
---|
7082 | |
---|
7083 | var p1 = map.mouseEventToLayerPoint(e.touches[0]), |
---|
7084 | p2 = map.mouseEventToLayerPoint(e.touches[1]), |
---|
7085 | viewCenter = map._getCenterLayerPoint(); |
---|
7086 | |
---|
7087 | this._startCenter = p1.add(p2)._divideBy(2); |
---|
7088 | this._startDist = p1.distanceTo(p2); |
---|
7089 | |
---|
7090 | this._moved = false; |
---|
7091 | this._zooming = true; |
---|
7092 | |
---|
7093 | this._centerOffset = viewCenter.subtract(this._startCenter); |
---|
7094 | |
---|
7095 | if (map._panAnim) { |
---|
7096 | map._panAnim.stop(); |
---|
7097 | } |
---|
7098 | |
---|
7099 | L.DomEvent |
---|
7100 | .on(document, 'touchmove', this._onTouchMove, this) |
---|
7101 | .on(document, 'touchend', this._onTouchEnd, this); |
---|
7102 | |
---|
7103 | L.DomEvent.preventDefault(e); |
---|
7104 | }, |
---|
7105 | |
---|
7106 | _onTouchMove: function (e) { |
---|
7107 | var map = this._map; |
---|
7108 | |
---|
7109 | if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } |
---|
7110 | |
---|
7111 | var p1 = map.mouseEventToLayerPoint(e.touches[0]), |
---|
7112 | p2 = map.mouseEventToLayerPoint(e.touches[1]); |
---|
7113 | |
---|
7114 | this._scale = p1.distanceTo(p2) / this._startDist; |
---|
7115 | this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter); |
---|
7116 | |
---|
7117 | if (this._scale === 1) { return; } |
---|
7118 | |
---|
7119 | if (!this._moved) { |
---|
7120 | L.DomUtil.addClass(map._mapPane, 'leaflet-touching'); |
---|
7121 | |
---|
7122 | map |
---|
7123 | .fire('movestart') |
---|
7124 | .fire('zoomstart'); |
---|
7125 | |
---|
7126 | this._moved = true; |
---|
7127 | } |
---|
7128 | |
---|
7129 | L.Util.cancelAnimFrame(this._animRequest); |
---|
7130 | this._animRequest = L.Util.requestAnimFrame( |
---|
7131 | this._updateOnMove, this, true, this._map._container); |
---|
7132 | |
---|
7133 | L.DomEvent.preventDefault(e); |
---|
7134 | }, |
---|
7135 | |
---|
7136 | _updateOnMove: function () { |
---|
7137 | var map = this._map, |
---|
7138 | origin = this._getScaleOrigin(), |
---|
7139 | center = map.layerPointToLatLng(origin), |
---|
7140 | zoom = map.getScaleZoom(this._scale); |
---|
7141 | |
---|
7142 | map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta); |
---|
7143 | }, |
---|
7144 | |
---|
7145 | _onTouchEnd: function () { |
---|
7146 | if (!this._moved || !this._zooming) { |
---|
7147 | this._zooming = false; |
---|
7148 | return; |
---|
7149 | } |
---|
7150 | |
---|
7151 | var map = this._map; |
---|
7152 | |
---|
7153 | this._zooming = false; |
---|
7154 | L.DomUtil.removeClass(map._mapPane, 'leaflet-touching'); |
---|
7155 | L.Util.cancelAnimFrame(this._animRequest); |
---|
7156 | |
---|
7157 | L.DomEvent |
---|
7158 | .off(document, 'touchmove', this._onTouchMove) |
---|
7159 | .off(document, 'touchend', this._onTouchEnd); |
---|
7160 | |
---|
7161 | var origin = this._getScaleOrigin(), |
---|
7162 | center = map.layerPointToLatLng(origin), |
---|
7163 | |
---|
7164 | oldZoom = map.getZoom(), |
---|
7165 | floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom, |
---|
7166 | roundZoomDelta = (floatZoomDelta > 0 ? |
---|
7167 | Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)), |
---|
7168 | |
---|
7169 | zoom = map._limitZoom(oldZoom + roundZoomDelta), |
---|
7170 | scale = map.getZoomScale(zoom) / this._scale; |
---|
7171 | |
---|
7172 | map._animateZoom(center, zoom, origin, scale); |
---|
7173 | }, |
---|
7174 | |
---|
7175 | _getScaleOrigin: function () { |
---|
7176 | var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale); |
---|
7177 | return this._startCenter.add(centerOffset); |
---|
7178 | } |
---|
7179 | }); |
---|
7180 | |
---|
7181 | L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); |
---|
7182 | |
---|
7183 | |
---|
7184 | /* |
---|
7185 | * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. |
---|
7186 | */ |
---|
7187 | |
---|
7188 | L.Map.mergeOptions({ |
---|
7189 | tap: true, |
---|
7190 | tapTolerance: 15 |
---|
7191 | }); |
---|
7192 | |
---|
7193 | L.Map.Tap = L.Handler.extend({ |
---|
7194 | addHooks: function () { |
---|
7195 | L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); |
---|
7196 | }, |
---|
7197 | |
---|
7198 | removeHooks: function () { |
---|
7199 | L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); |
---|
7200 | }, |
---|
7201 | |
---|
7202 | _onDown: function (e) { |
---|
7203 | if (!e.touches) { return; } |
---|
7204 | |
---|
7205 | L.DomEvent.preventDefault(e); |
---|
7206 | |
---|
7207 | this._fireClick = true; |
---|
7208 | |
---|
7209 | // don't simulate click or track longpress if more than 1 touch |
---|
7210 | if (e.touches.length > 1) { |
---|
7211 | this._fireClick = false; |
---|
7212 | clearTimeout(this._holdTimeout); |
---|
7213 | return; |
---|
7214 | } |
---|
7215 | |
---|
7216 | var first = e.touches[0], |
---|
7217 | el = first.target; |
---|
7218 | |
---|
7219 | this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); |
---|
7220 | |
---|
7221 | // if touching a link, highlight it |
---|
7222 | if (el.tagName.toLowerCase() === 'a') { |
---|
7223 | L.DomUtil.addClass(el, 'leaflet-active'); |
---|
7224 | } |
---|
7225 | |
---|
7226 | // simulate long hold but setting a timeout |
---|
7227 | this._holdTimeout = setTimeout(L.bind(function () { |
---|
7228 | if (this._isTapValid()) { |
---|
7229 | this._fireClick = false; |
---|
7230 | this._onUp(); |
---|
7231 | this._simulateEvent('contextmenu', first); |
---|
7232 | } |
---|
7233 | }, this), 1000); |
---|
7234 | |
---|
7235 | L.DomEvent |
---|
7236 | .on(document, 'touchmove', this._onMove, this) |
---|
7237 | .on(document, 'touchend', this._onUp, this); |
---|
7238 | }, |
---|
7239 | |
---|
7240 | _onUp: function (e) { |
---|
7241 | clearTimeout(this._holdTimeout); |
---|
7242 | |
---|
7243 | L.DomEvent |
---|
7244 | .off(document, 'touchmove', this._onMove, this) |
---|
7245 | .off(document, 'touchend', this._onUp, this); |
---|
7246 | |
---|
7247 | if (this._fireClick && e && e.changedTouches) { |
---|
7248 | |
---|
7249 | var first = e.changedTouches[0], |
---|
7250 | el = first.target; |
---|
7251 | |
---|
7252 | if (el.tagName.toLowerCase() === 'a') { |
---|
7253 | L.DomUtil.removeClass(el, 'leaflet-active'); |
---|
7254 | } |
---|
7255 | |
---|
7256 | // simulate click if the touch didn't move too much |
---|
7257 | if (this._isTapValid()) { |
---|
7258 | this._simulateEvent('click', first); |
---|
7259 | } |
---|
7260 | } |
---|
7261 | }, |
---|
7262 | |
---|
7263 | _isTapValid: function () { |
---|
7264 | return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; |
---|
7265 | }, |
---|
7266 | |
---|
7267 | _onMove: function (e) { |
---|
7268 | var first = e.touches[0]; |
---|
7269 | this._newPos = new L.Point(first.clientX, first.clientY); |
---|
7270 | }, |
---|
7271 | |
---|
7272 | _simulateEvent: function (type, e) { |
---|
7273 | var simulatedEvent = document.createEvent('MouseEvents'); |
---|
7274 | |
---|
7275 | simulatedEvent._simulated = true; |
---|
7276 | e.target._simulatedClick = true; |
---|
7277 | |
---|
7278 | simulatedEvent.initMouseEvent( |
---|
7279 | type, true, true, window, 1, |
---|
7280 | e.screenX, e.screenY, |
---|
7281 | e.clientX, e.clientY, |
---|
7282 | false, false, false, false, 0, null); |
---|
7283 | |
---|
7284 | e.target.dispatchEvent(simulatedEvent); |
---|
7285 | } |
---|
7286 | }); |
---|
7287 | |
---|
7288 | if (L.Browser.touch && !L.Browser.msTouch) { |
---|
7289 | L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); |
---|
7290 | } |
---|
7291 | |
---|
7292 | |
---|
7293 | /* |
---|
7294 | * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map |
---|
7295 | * (zoom to a selected bounding box), enabled by default. |
---|
7296 | */ |
---|
7297 | |
---|
7298 | L.Map.mergeOptions({ |
---|
7299 | boxZoom: true |
---|
7300 | }); |
---|
7301 | |
---|
7302 | L.Map.BoxZoom = L.Handler.extend({ |
---|
7303 | initialize: function (map) { |
---|
7304 | this._map = map; |
---|
7305 | this._container = map._container; |
---|
7306 | this._pane = map._panes.overlayPane; |
---|
7307 | }, |
---|
7308 | |
---|
7309 | addHooks: function () { |
---|
7310 | L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); |
---|
7311 | }, |
---|
7312 | |
---|
7313 | removeHooks: function () { |
---|
7314 | L.DomEvent.off(this._container, 'mousedown', this._onMouseDown); |
---|
7315 | }, |
---|
7316 | |
---|
7317 | _onMouseDown: function (e) { |
---|
7318 | if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } |
---|
7319 | |
---|
7320 | L.DomUtil.disableTextSelection(); |
---|
7321 | L.DomUtil.disableImageDrag(); |
---|
7322 | |
---|
7323 | this._startLayerPoint = this._map.mouseEventToLayerPoint(e); |
---|
7324 | |
---|
7325 | this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); |
---|
7326 | L.DomUtil.setPosition(this._box, this._startLayerPoint); |
---|
7327 | |
---|
7328 | //TODO refactor: move cursor to styles |
---|
7329 | this._container.style.cursor = 'crosshair'; |
---|
7330 | |
---|
7331 | L.DomEvent |
---|
7332 | .on(document, 'mousemove', this._onMouseMove, this) |
---|
7333 | .on(document, 'mouseup', this._onMouseUp, this) |
---|
7334 | .on(document, 'keydown', this._onKeyDown, this); |
---|
7335 | |
---|
7336 | this._map.fire('boxzoomstart'); |
---|
7337 | }, |
---|
7338 | |
---|
7339 | _onMouseMove: function (e) { |
---|
7340 | var startPoint = this._startLayerPoint, |
---|
7341 | box = this._box, |
---|
7342 | |
---|
7343 | layerPoint = this._map.mouseEventToLayerPoint(e), |
---|
7344 | offset = layerPoint.subtract(startPoint), |
---|
7345 | |
---|
7346 | newPos = new L.Point( |
---|
7347 | Math.min(layerPoint.x, startPoint.x), |
---|
7348 | Math.min(layerPoint.y, startPoint.y)); |
---|
7349 | |
---|
7350 | L.DomUtil.setPosition(box, newPos); |
---|
7351 | |
---|
7352 | // TODO refactor: remove hardcoded 4 pixels |
---|
7353 | box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px'; |
---|
7354 | box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px'; |
---|
7355 | }, |
---|
7356 | |
---|
7357 | _finish: function () { |
---|
7358 | this._pane.removeChild(this._box); |
---|
7359 | this._container.style.cursor = ''; |
---|
7360 | |
---|
7361 | L.DomUtil.enableTextSelection(); |
---|
7362 | L.DomUtil.enableImageDrag(); |
---|
7363 | |
---|
7364 | L.DomEvent |
---|
7365 | .off(document, 'mousemove', this._onMouseMove) |
---|
7366 | .off(document, 'mouseup', this._onMouseUp) |
---|
7367 | .off(document, 'keydown', this._onKeyDown); |
---|
7368 | }, |
---|
7369 | |
---|
7370 | _onMouseUp: function (e) { |
---|
7371 | |
---|
7372 | this._finish(); |
---|
7373 | |
---|
7374 | var map = this._map, |
---|
7375 | layerPoint = map.mouseEventToLayerPoint(e); |
---|
7376 | |
---|
7377 | if (this._startLayerPoint.equals(layerPoint)) { return; } |
---|
7378 | |
---|
7379 | var bounds = new L.LatLngBounds( |
---|
7380 | map.layerPointToLatLng(this._startLayerPoint), |
---|
7381 | map.layerPointToLatLng(layerPoint)); |
---|
7382 | |
---|
7383 | map.fitBounds(bounds); |
---|
7384 | |
---|
7385 | map.fire('boxzoomend', { |
---|
7386 | boxZoomBounds: bounds |
---|
7387 | }); |
---|
7388 | }, |
---|
7389 | |
---|
7390 | _onKeyDown: function (e) { |
---|
7391 | if (e.keyCode === 27) { |
---|
7392 | this._finish(); |
---|
7393 | } |
---|
7394 | } |
---|
7395 | }); |
---|
7396 | |
---|
7397 | L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); |
---|
7398 | |
---|
7399 | |
---|
7400 | /* |
---|
7401 | * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. |
---|
7402 | */ |
---|
7403 | |
---|
7404 | L.Map.mergeOptions({ |
---|
7405 | keyboard: true, |
---|
7406 | keyboardPanOffset: 80, |
---|
7407 | keyboardZoomOffset: 1 |
---|
7408 | }); |
---|
7409 | |
---|
7410 | L.Map.Keyboard = L.Handler.extend({ |
---|
7411 | |
---|
7412 | keyCodes: { |
---|
7413 | left: [37], |
---|
7414 | right: [39], |
---|
7415 | down: [40], |
---|
7416 | up: [38], |
---|
7417 | zoomIn: [187, 107, 61], |
---|
7418 | zoomOut: [189, 109, 173] |
---|
7419 | }, |
---|
7420 | |
---|
7421 | initialize: function (map) { |
---|
7422 | this._map = map; |
---|
7423 | |
---|
7424 | this._setPanOffset(map.options.keyboardPanOffset); |
---|
7425 | this._setZoomOffset(map.options.keyboardZoomOffset); |
---|
7426 | }, |
---|
7427 | |
---|
7428 | addHooks: function () { |
---|
7429 | var container = this._map._container; |
---|
7430 | |
---|
7431 | // make the container focusable by tabbing |
---|
7432 | if (container.tabIndex === -1) { |
---|
7433 | container.tabIndex = '0'; |
---|
7434 | } |
---|
7435 | |
---|
7436 | L.DomEvent |
---|
7437 | .on(container, 'focus', this._onFocus, this) |
---|
7438 | .on(container, 'blur', this._onBlur, this) |
---|
7439 | .on(container, 'mousedown', this._onMouseDown, this); |
---|
7440 | |
---|
7441 | this._map |
---|
7442 | .on('focus', this._addHooks, this) |
---|
7443 | .on('blur', this._removeHooks, this); |
---|
7444 | }, |
---|
7445 | |
---|
7446 | removeHooks: function () { |
---|
7447 | this._removeHooks(); |
---|
7448 | |
---|
7449 | var container = this._map._container; |
---|
7450 | |
---|
7451 | L.DomEvent |
---|
7452 | .off(container, 'focus', this._onFocus, this) |
---|
7453 | .off(container, 'blur', this._onBlur, this) |
---|
7454 | .off(container, 'mousedown', this._onMouseDown, this); |
---|
7455 | |
---|
7456 | this._map |
---|
7457 | .off('focus', this._addHooks, this) |
---|
7458 | .off('blur', this._removeHooks, this); |
---|
7459 | }, |
---|
7460 | |
---|
7461 | _onMouseDown: function () { |
---|
7462 | if (this._focused) { return; } |
---|
7463 | |
---|
7464 | var body = document.body, |
---|
7465 | docEl = document.documentElement, |
---|
7466 | top = body.scrollTop || docEl.scrollTop, |
---|
7467 | left = body.scrollTop || docEl.scrollLeft; |
---|
7468 | |
---|
7469 | this._map._container.focus(); |
---|
7470 | |
---|
7471 | window.scrollTo(left, top); |
---|
7472 | }, |
---|
7473 | |
---|
7474 | _onFocus: function () { |
---|
7475 | this._focused = true; |
---|
7476 | this._map.fire('focus'); |
---|
7477 | }, |
---|
7478 | |
---|
7479 | _onBlur: function () { |
---|
7480 | this._focused = false; |
---|
7481 | this._map.fire('blur'); |
---|
7482 | }, |
---|
7483 | |
---|
7484 | _setPanOffset: function (pan) { |
---|
7485 | var keys = this._panKeys = {}, |
---|
7486 | codes = this.keyCodes, |
---|
7487 | i, len; |
---|
7488 | |
---|
7489 | for (i = 0, len = codes.left.length; i < len; i++) { |
---|
7490 | keys[codes.left[i]] = [-1 * pan, 0]; |
---|
7491 | } |
---|
7492 | for (i = 0, len = codes.right.length; i < len; i++) { |
---|
7493 | keys[codes.right[i]] = [pan, 0]; |
---|
7494 | } |
---|
7495 | for (i = 0, len = codes.down.length; i < len; i++) { |
---|
7496 | keys[codes.down[i]] = [0, pan]; |
---|
7497 | } |
---|
7498 | for (i = 0, len = codes.up.length; i < len; i++) { |
---|
7499 | keys[codes.up[i]] = [0, -1 * pan]; |
---|
7500 | } |
---|
7501 | }, |
---|
7502 | |
---|
7503 | _setZoomOffset: function (zoom) { |
---|
7504 | var keys = this._zoomKeys = {}, |
---|
7505 | codes = this.keyCodes, |
---|
7506 | i, len; |
---|
7507 | |
---|
7508 | for (i = 0, len = codes.zoomIn.length; i < len; i++) { |
---|
7509 | keys[codes.zoomIn[i]] = zoom; |
---|
7510 | } |
---|
7511 | for (i = 0, len = codes.zoomOut.length; i < len; i++) { |
---|
7512 | keys[codes.zoomOut[i]] = -zoom; |
---|
7513 | } |
---|
7514 | }, |
---|
7515 | |
---|
7516 | _addHooks: function () { |
---|
7517 | L.DomEvent.on(document, 'keydown', this._onKeyDown, this); |
---|
7518 | }, |
---|
7519 | |
---|
7520 | _removeHooks: function () { |
---|
7521 | L.DomEvent.off(document, 'keydown', this._onKeyDown, this); |
---|
7522 | }, |
---|
7523 | |
---|
7524 | _onKeyDown: function (e) { |
---|
7525 | var key = e.keyCode, |
---|
7526 | map = this._map; |
---|
7527 | |
---|
7528 | if (key in this._panKeys) { |
---|
7529 | |
---|
7530 | if (map._panAnim && map._panAnim._inProgress) { return; } |
---|
7531 | |
---|
7532 | map.panBy(this._panKeys[key]); |
---|
7533 | |
---|
7534 | if (map.options.maxBounds) { |
---|
7535 | map.panInsideBounds(map.options.maxBounds); |
---|
7536 | } |
---|
7537 | |
---|
7538 | } else if (key in this._zoomKeys) { |
---|
7539 | map.setZoom(map.getZoom() + this._zoomKeys[key]); |
---|
7540 | |
---|
7541 | } else { |
---|
7542 | return; |
---|
7543 | } |
---|
7544 | |
---|
7545 | L.DomEvent.stop(e); |
---|
7546 | } |
---|
7547 | }); |
---|
7548 | |
---|
7549 | L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); |
---|
7550 | |
---|
7551 | |
---|
7552 | /* |
---|
7553 | * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. |
---|
7554 | */ |
---|
7555 | |
---|
7556 | L.Handler.MarkerDrag = L.Handler.extend({ |
---|
7557 | initialize: function (marker) { |
---|
7558 | this._marker = marker; |
---|
7559 | }, |
---|
7560 | |
---|
7561 | addHooks: function () { |
---|
7562 | var icon = this._marker._icon; |
---|
7563 | if (!this._draggable) { |
---|
7564 | this._draggable = new L.Draggable(icon, icon); |
---|
7565 | } |
---|
7566 | |
---|
7567 | this._draggable |
---|
7568 | .on('dragstart', this._onDragStart, this) |
---|
7569 | .on('drag', this._onDrag, this) |
---|
7570 | .on('dragend', this._onDragEnd, this); |
---|
7571 | this._draggable.enable(); |
---|
7572 | }, |
---|
7573 | |
---|
7574 | removeHooks: function () { |
---|
7575 | this._draggable |
---|
7576 | .off('dragstart', this._onDragStart, this) |
---|
7577 | .off('drag', this._onDrag, this) |
---|
7578 | .off('dragend', this._onDragEnd, this); |
---|
7579 | |
---|
7580 | this._draggable.disable(); |
---|
7581 | }, |
---|
7582 | |
---|
7583 | moved: function () { |
---|
7584 | return this._draggable && this._draggable._moved; |
---|
7585 | }, |
---|
7586 | |
---|
7587 | _onDragStart: function () { |
---|
7588 | this._marker |
---|
7589 | .closePopup() |
---|
7590 | .fire('movestart') |
---|
7591 | .fire('dragstart'); |
---|
7592 | }, |
---|
7593 | |
---|
7594 | _onDrag: function () { |
---|
7595 | var marker = this._marker, |
---|
7596 | shadow = marker._shadow, |
---|
7597 | iconPos = L.DomUtil.getPosition(marker._icon), |
---|
7598 | latlng = marker._map.layerPointToLatLng(iconPos); |
---|
7599 | |
---|
7600 | // update shadow position |
---|
7601 | if (shadow) { |
---|
7602 | L.DomUtil.setPosition(shadow, iconPos); |
---|
7603 | } |
---|
7604 | |
---|
7605 | marker._latlng = latlng; |
---|
7606 | |
---|
7607 | marker |
---|
7608 | .fire('move', {latlng: latlng}) |
---|
7609 | .fire('drag'); |
---|
7610 | }, |
---|
7611 | |
---|
7612 | _onDragEnd: function () { |
---|
7613 | this._marker |
---|
7614 | .fire('moveend') |
---|
7615 | .fire('dragend'); |
---|
7616 | } |
---|
7617 | }); |
---|
7618 | |
---|
7619 | |
---|
7620 | /* |
---|
7621 | * L.Control is a base class for implementing map controls. Handles positioning. |
---|
7622 | * All other controls extend from this class. |
---|
7623 | */ |
---|
7624 | |
---|
7625 | L.Control = L.Class.extend({ |
---|
7626 | options: { |
---|
7627 | position: 'topright' |
---|
7628 | }, |
---|
7629 | |
---|
7630 | initialize: function (options) { |
---|
7631 | L.setOptions(this, options); |
---|
7632 | }, |
---|
7633 | |
---|
7634 | getPosition: function () { |
---|
7635 | return this.options.position; |
---|
7636 | }, |
---|
7637 | |
---|
7638 | setPosition: function (position) { |
---|
7639 | var map = this._map; |
---|
7640 | |
---|
7641 | if (map) { |
---|
7642 | map.removeControl(this); |
---|
7643 | } |
---|
7644 | |
---|
7645 | this.options.position = position; |
---|
7646 | |
---|
7647 | if (map) { |
---|
7648 | map.addControl(this); |
---|
7649 | } |
---|
7650 | |
---|
7651 | return this; |
---|
7652 | }, |
---|
7653 | |
---|
7654 | getContainer: function () { |
---|
7655 | return this._container; |
---|
7656 | }, |
---|
7657 | |
---|
7658 | addTo: function (map) { |
---|
7659 | this._map = map; |
---|
7660 | |
---|
7661 | var container = this._container = this.onAdd(map), |
---|
7662 | pos = this.getPosition(), |
---|
7663 | corner = map._controlCorners[pos]; |
---|
7664 | |
---|
7665 | L.DomUtil.addClass(container, 'leaflet-control'); |
---|
7666 | |
---|
7667 | if (pos.indexOf('bottom') !== -1) { |
---|
7668 | corner.insertBefore(container, corner.firstChild); |
---|
7669 | } else { |
---|
7670 | corner.appendChild(container); |
---|
7671 | } |
---|
7672 | |
---|
7673 | return this; |
---|
7674 | }, |
---|
7675 | |
---|
7676 | removeFrom: function (map) { |
---|
7677 | var pos = this.getPosition(), |
---|
7678 | corner = map._controlCorners[pos]; |
---|
7679 | |
---|
7680 | corner.removeChild(this._container); |
---|
7681 | this._map = null; |
---|
7682 | |
---|
7683 | if (this.onRemove) { |
---|
7684 | this.onRemove(map); |
---|
7685 | } |
---|
7686 | |
---|
7687 | return this; |
---|
7688 | } |
---|
7689 | }); |
---|
7690 | |
---|
7691 | L.control = function (options) { |
---|
7692 | return new L.Control(options); |
---|
7693 | }; |
---|
7694 | |
---|
7695 | |
---|
7696 | // adds control-related methods to L.Map |
---|
7697 | |
---|
7698 | L.Map.include({ |
---|
7699 | addControl: function (control) { |
---|
7700 | control.addTo(this); |
---|
7701 | return this; |
---|
7702 | }, |
---|
7703 | |
---|
7704 | removeControl: function (control) { |
---|
7705 | control.removeFrom(this); |
---|
7706 | return this; |
---|
7707 | }, |
---|
7708 | |
---|
7709 | _initControlPos: function () { |
---|
7710 | var corners = this._controlCorners = {}, |
---|
7711 | l = 'leaflet-', |
---|
7712 | container = this._controlContainer = |
---|
7713 | L.DomUtil.create('div', l + 'control-container', this._container); |
---|
7714 | |
---|
7715 | function createCorner(vSide, hSide) { |
---|
7716 | var className = l + vSide + ' ' + l + hSide; |
---|
7717 | |
---|
7718 | corners[vSide + hSide] = L.DomUtil.create('div', className, container); |
---|
7719 | } |
---|
7720 | |
---|
7721 | createCorner('top', 'left'); |
---|
7722 | createCorner('top', 'right'); |
---|
7723 | createCorner('bottom', 'left'); |
---|
7724 | createCorner('bottom', 'right'); |
---|
7725 | }, |
---|
7726 | |
---|
7727 | _clearControlPos: function () { |
---|
7728 | this._container.removeChild(this._controlContainer); |
---|
7729 | } |
---|
7730 | }); |
---|
7731 | |
---|
7732 | |
---|
7733 | /* |
---|
7734 | * L.Control.Zoom is used for the default zoom buttons on the map. |
---|
7735 | */ |
---|
7736 | |
---|
7737 | L.Control.Zoom = L.Control.extend({ |
---|
7738 | options: { |
---|
7739 | position: 'topleft' |
---|
7740 | }, |
---|
7741 | |
---|
7742 | onAdd: function (map) { |
---|
7743 | var zoomName = 'leaflet-control-zoom', |
---|
7744 | container = L.DomUtil.create('div', zoomName + ' leaflet-bar'); |
---|
7745 | |
---|
7746 | this._map = map; |
---|
7747 | |
---|
7748 | this._zoomInButton = this._createButton( |
---|
7749 | '+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this); |
---|
7750 | this._zoomOutButton = this._createButton( |
---|
7751 | '-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this); |
---|
7752 | |
---|
7753 | map.on('zoomend zoomlevelschange', this._updateDisabled, this); |
---|
7754 | |
---|
7755 | return container; |
---|
7756 | }, |
---|
7757 | |
---|
7758 | onRemove: function (map) { |
---|
7759 | map.off('zoomend zoomlevelschange', this._updateDisabled, this); |
---|
7760 | }, |
---|
7761 | |
---|
7762 | _zoomIn: function (e) { |
---|
7763 | this._map.zoomIn(e.shiftKey ? 3 : 1); |
---|
7764 | }, |
---|
7765 | |
---|
7766 | _zoomOut: function (e) { |
---|
7767 | this._map.zoomOut(e.shiftKey ? 3 : 1); |
---|
7768 | }, |
---|
7769 | |
---|
7770 | _createButton: function (html, title, className, container, fn, context) { |
---|
7771 | var link = L.DomUtil.create('a', className, container); |
---|
7772 | link.innerHTML = html; |
---|
7773 | link.href = '#'; |
---|
7774 | link.title = title; |
---|
7775 | |
---|
7776 | var stop = L.DomEvent.stopPropagation; |
---|
7777 | |
---|
7778 | L.DomEvent |
---|
7779 | .on(link, 'click', stop) |
---|
7780 | .on(link, 'mousedown', stop) |
---|
7781 | .on(link, 'dblclick', stop) |
---|
7782 | .on(link, 'click', L.DomEvent.preventDefault) |
---|
7783 | .on(link, 'click', fn, context); |
---|
7784 | |
---|
7785 | return link; |
---|
7786 | }, |
---|
7787 | |
---|
7788 | _updateDisabled: function () { |
---|
7789 | var map = this._map, |
---|
7790 | className = 'leaflet-disabled'; |
---|
7791 | |
---|
7792 | L.DomUtil.removeClass(this._zoomInButton, className); |
---|
7793 | L.DomUtil.removeClass(this._zoomOutButton, className); |
---|
7794 | |
---|
7795 | if (map._zoom === map.getMinZoom()) { |
---|
7796 | L.DomUtil.addClass(this._zoomOutButton, className); |
---|
7797 | } |
---|
7798 | if (map._zoom === map.getMaxZoom()) { |
---|
7799 | L.DomUtil.addClass(this._zoomInButton, className); |
---|
7800 | } |
---|
7801 | } |
---|
7802 | }); |
---|
7803 | |
---|
7804 | L.Map.mergeOptions({ |
---|
7805 | zoomControl: true |
---|
7806 | }); |
---|
7807 | |
---|
7808 | L.Map.addInitHook(function () { |
---|
7809 | if (this.options.zoomControl) { |
---|
7810 | this.zoomControl = new L.Control.Zoom(); |
---|
7811 | this.addControl(this.zoomControl); |
---|
7812 | } |
---|
7813 | }); |
---|
7814 | |
---|
7815 | L.control.zoom = function (options) { |
---|
7816 | return new L.Control.Zoom(options); |
---|
7817 | }; |
---|
7818 | |
---|
7819 | |
---|
7820 | |
---|
7821 | /* |
---|
7822 | * L.Control.Attribution is used for displaying attribution on the map (added by default). |
---|
7823 | */ |
---|
7824 | |
---|
7825 | L.Control.Attribution = L.Control.extend({ |
---|
7826 | options: { |
---|
7827 | position: 'bottomright', |
---|
7828 | prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' |
---|
7829 | }, |
---|
7830 | |
---|
7831 | initialize: function (options) { |
---|
7832 | L.setOptions(this, options); |
---|
7833 | |
---|
7834 | this._attributions = {}; |
---|
7835 | }, |
---|
7836 | |
---|
7837 | onAdd: function (map) { |
---|
7838 | this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); |
---|
7839 | L.DomEvent.disableClickPropagation(this._container); |
---|
7840 | |
---|
7841 | map |
---|
7842 | .on('layeradd', this._onLayerAdd, this) |
---|
7843 | .on('layerremove', this._onLayerRemove, this); |
---|
7844 | |
---|
7845 | this._update(); |
---|
7846 | |
---|
7847 | return this._container; |
---|
7848 | }, |
---|
7849 | |
---|
7850 | onRemove: function (map) { |
---|
7851 | map |
---|
7852 | .off('layeradd', this._onLayerAdd) |
---|
7853 | .off('layerremove', this._onLayerRemove); |
---|
7854 | |
---|
7855 | }, |
---|
7856 | |
---|
7857 | setPrefix: function (prefix) { |
---|
7858 | this.options.prefix = prefix; |
---|
7859 | this._update(); |
---|
7860 | return this; |
---|
7861 | }, |
---|
7862 | |
---|
7863 | addAttribution: function (text) { |
---|
7864 | if (!text) { return; } |
---|
7865 | |
---|
7866 | if (!this._attributions[text]) { |
---|
7867 | this._attributions[text] = 0; |
---|
7868 | } |
---|
7869 | this._attributions[text]++; |
---|
7870 | |
---|
7871 | this._update(); |
---|
7872 | |
---|
7873 | return this; |
---|
7874 | }, |
---|
7875 | |
---|
7876 | removeAttribution: function (text) { |
---|
7877 | if (!text) { return; } |
---|
7878 | |
---|
7879 | if (this._attributions[text]) { |
---|
7880 | this._attributions[text]--; |
---|
7881 | this._update(); |
---|
7882 | } |
---|
7883 | |
---|
7884 | return this; |
---|
7885 | }, |
---|
7886 | |
---|
7887 | _update: function () { |
---|
7888 | if (!this._map) { return; } |
---|
7889 | |
---|
7890 | var attribs = []; |
---|
7891 | |
---|
7892 | for (var i in this._attributions) { |
---|
7893 | if (this._attributions[i]) { |
---|
7894 | attribs.push(i); |
---|
7895 | } |
---|
7896 | } |
---|
7897 | |
---|
7898 | var prefixAndAttribs = []; |
---|
7899 | |
---|
7900 | if (this.options.prefix) { |
---|
7901 | prefixAndAttribs.push(this.options.prefix); |
---|
7902 | } |
---|
7903 | if (attribs.length) { |
---|
7904 | prefixAndAttribs.push(attribs.join(', ')); |
---|
7905 | } |
---|
7906 | |
---|
7907 | this._container.innerHTML = prefixAndAttribs.join(' | '); |
---|
7908 | }, |
---|
7909 | |
---|
7910 | _onLayerAdd: function (e) { |
---|
7911 | if (e.layer.getAttribution) { |
---|
7912 | this.addAttribution(e.layer.getAttribution()); |
---|
7913 | } |
---|
7914 | }, |
---|
7915 | |
---|
7916 | _onLayerRemove: function (e) { |
---|
7917 | if (e.layer.getAttribution) { |
---|
7918 | this.removeAttribution(e.layer.getAttribution()); |
---|
7919 | } |
---|
7920 | } |
---|
7921 | }); |
---|
7922 | |
---|
7923 | L.Map.mergeOptions({ |
---|
7924 | attributionControl: true |
---|
7925 | }); |
---|
7926 | |
---|
7927 | L.Map.addInitHook(function () { |
---|
7928 | if (this.options.attributionControl) { |
---|
7929 | this.attributionControl = (new L.Control.Attribution()).addTo(this); |
---|
7930 | } |
---|
7931 | }); |
---|
7932 | |
---|
7933 | L.control.attribution = function (options) { |
---|
7934 | return new L.Control.Attribution(options); |
---|
7935 | }; |
---|
7936 | |
---|
7937 | |
---|
7938 | /* |
---|
7939 | * L.Control.Scale is used for displaying metric/imperial scale on the map. |
---|
7940 | */ |
---|
7941 | |
---|
7942 | L.Control.Scale = L.Control.extend({ |
---|
7943 | options: { |
---|
7944 | position: 'bottomleft', |
---|
7945 | maxWidth: 100, |
---|
7946 | metric: true, |
---|
7947 | imperial: true, |
---|
7948 | updateWhenIdle: false |
---|
7949 | }, |
---|
7950 | |
---|
7951 | onAdd: function (map) { |
---|
7952 | this._map = map; |
---|
7953 | |
---|
7954 | var className = 'leaflet-control-scale', |
---|
7955 | container = L.DomUtil.create('div', className), |
---|
7956 | options = this.options; |
---|
7957 | |
---|
7958 | this._addScales(options, className, container); |
---|
7959 | |
---|
7960 | map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); |
---|
7961 | map.whenReady(this._update, this); |
---|
7962 | |
---|
7963 | return container; |
---|
7964 | }, |
---|
7965 | |
---|
7966 | onRemove: function (map) { |
---|
7967 | map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); |
---|
7968 | }, |
---|
7969 | |
---|
7970 | _addScales: function (options, className, container) { |
---|
7971 | if (options.metric) { |
---|
7972 | this._mScale = L.DomUtil.create('div', className + '-line', container); |
---|
7973 | } |
---|
7974 | if (options.imperial) { |
---|
7975 | this._iScale = L.DomUtil.create('div', className + '-line', container); |
---|
7976 | } |
---|
7977 | }, |
---|
7978 | |
---|
7979 | _update: function () { |
---|
7980 | var bounds = this._map.getBounds(), |
---|
7981 | centerLat = bounds.getCenter().lat, |
---|
7982 | halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180), |
---|
7983 | dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180, |
---|
7984 | |
---|
7985 | size = this._map.getSize(), |
---|
7986 | options = this.options, |
---|
7987 | maxMeters = 0; |
---|
7988 | |
---|
7989 | if (size.x > 0) { |
---|
7990 | maxMeters = dist * (options.maxWidth / size.x); |
---|
7991 | } |
---|
7992 | |
---|
7993 | this._updateScales(options, maxMeters); |
---|
7994 | }, |
---|
7995 | |
---|
7996 | _updateScales: function (options, maxMeters) { |
---|
7997 | if (options.metric && maxMeters) { |
---|
7998 | this._updateMetric(maxMeters); |
---|
7999 | } |
---|
8000 | |
---|
8001 | if (options.imperial && maxMeters) { |
---|
8002 | this._updateImperial(maxMeters); |
---|
8003 | } |
---|
8004 | }, |
---|
8005 | |
---|
8006 | _updateMetric: function (maxMeters) { |
---|
8007 | var meters = this._getRoundNum(maxMeters); |
---|
8008 | |
---|
8009 | this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px'; |
---|
8010 | this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; |
---|
8011 | }, |
---|
8012 | |
---|
8013 | _updateImperial: function (maxMeters) { |
---|
8014 | var maxFeet = maxMeters * 3.2808399, |
---|
8015 | scale = this._iScale, |
---|
8016 | maxMiles, miles, feet; |
---|
8017 | |
---|
8018 | if (maxFeet > 5280) { |
---|
8019 | maxMiles = maxFeet / 5280; |
---|
8020 | miles = this._getRoundNum(maxMiles); |
---|
8021 | |
---|
8022 | scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px'; |
---|
8023 | scale.innerHTML = miles + ' mi'; |
---|
8024 | |
---|
8025 | } else { |
---|
8026 | feet = this._getRoundNum(maxFeet); |
---|
8027 | |
---|
8028 | scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px'; |
---|
8029 | scale.innerHTML = feet + ' ft'; |
---|
8030 | } |
---|
8031 | }, |
---|
8032 | |
---|
8033 | _getScaleWidth: function (ratio) { |
---|
8034 | return Math.round(this.options.maxWidth * ratio) - 10; |
---|
8035 | }, |
---|
8036 | |
---|
8037 | _getRoundNum: function (num) { |
---|
8038 | var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), |
---|
8039 | d = num / pow10; |
---|
8040 | |
---|
8041 | d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1; |
---|
8042 | |
---|
8043 | return pow10 * d; |
---|
8044 | } |
---|
8045 | }); |
---|
8046 | |
---|
8047 | L.control.scale = function (options) { |
---|
8048 | return new L.Control.Scale(options); |
---|
8049 | }; |
---|
8050 | |
---|
8051 | |
---|
8052 | /* |
---|
8053 | * L.Control.Layers is a control to allow users to switch between different layers on the map. |
---|
8054 | */ |
---|
8055 | |
---|
8056 | L.Control.Layers = L.Control.extend({ |
---|
8057 | options: { |
---|
8058 | collapsed: true, |
---|
8059 | position: 'topright', |
---|
8060 | autoZIndex: true |
---|
8061 | }, |
---|
8062 | |
---|
8063 | initialize: function (baseLayers, overlays, options) { |
---|
8064 | L.setOptions(this, options); |
---|
8065 | |
---|
8066 | this._layers = {}; |
---|
8067 | this._lastZIndex = 0; |
---|
8068 | this._handlingClick = false; |
---|
8069 | |
---|
8070 | for (var i in baseLayers) { |
---|
8071 | this._addLayer(baseLayers[i], i); |
---|
8072 | } |
---|
8073 | |
---|
8074 | for (i in overlays) { |
---|
8075 | this._addLayer(overlays[i], i, true); |
---|
8076 | } |
---|
8077 | }, |
---|
8078 | |
---|
8079 | onAdd: function (map) { |
---|
8080 | this._initLayout(); |
---|
8081 | this._update(); |
---|
8082 | |
---|
8083 | map |
---|
8084 | .on('layeradd', this._onLayerChange, this) |
---|
8085 | .on('layerremove', this._onLayerChange, this); |
---|
8086 | |
---|
8087 | return this._container; |
---|
8088 | }, |
---|
8089 | |
---|
8090 | onRemove: function (map) { |
---|
8091 | map |
---|
8092 | .off('layeradd', this._onLayerChange) |
---|
8093 | .off('layerremove', this._onLayerChange); |
---|
8094 | }, |
---|
8095 | |
---|
8096 | addBaseLayer: function (layer, name) { |
---|
8097 | this._addLayer(layer, name); |
---|
8098 | this._update(); |
---|
8099 | return this; |
---|
8100 | }, |
---|
8101 | |
---|
8102 | addOverlay: function (layer, name) { |
---|
8103 | this._addLayer(layer, name, true); |
---|
8104 | this._update(); |
---|
8105 | return this; |
---|
8106 | }, |
---|
8107 | |
---|
8108 | removeLayer: function (layer) { |
---|
8109 | var id = L.stamp(layer); |
---|
8110 | delete this._layers[id]; |
---|
8111 | this._update(); |
---|
8112 | return this; |
---|
8113 | }, |
---|
8114 | |
---|
8115 | _initLayout: function () { |
---|
8116 | var className = 'leaflet-control-layers', |
---|
8117 | container = this._container = L.DomUtil.create('div', className); |
---|
8118 | |
---|
8119 | //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released |
---|
8120 | container.setAttribute('aria-haspopup', true); |
---|
8121 | |
---|
8122 | if (!L.Browser.touch) { |
---|
8123 | L.DomEvent.disableClickPropagation(container); |
---|
8124 | L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation); |
---|
8125 | } else { |
---|
8126 | L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); |
---|
8127 | } |
---|
8128 | |
---|
8129 | var form = this._form = L.DomUtil.create('form', className + '-list'); |
---|
8130 | |
---|
8131 | if (this.options.collapsed) { |
---|
8132 | if (!L.Browser.android) { |
---|
8133 | L.DomEvent |
---|
8134 | .on(container, 'mouseover', this._expand, this) |
---|
8135 | .on(container, 'mouseout', this._collapse, this); |
---|
8136 | } |
---|
8137 | var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); |
---|
8138 | link.href = '#'; |
---|
8139 | link.title = 'Layers'; |
---|
8140 | |
---|
8141 | if (L.Browser.touch) { |
---|
8142 | L.DomEvent |
---|
8143 | .on(link, 'click', L.DomEvent.stop) |
---|
8144 | .on(link, 'click', this._expand, this); |
---|
8145 | } |
---|
8146 | else { |
---|
8147 | L.DomEvent.on(link, 'focus', this._expand, this); |
---|
8148 | } |
---|
8149 | |
---|
8150 | this._map.on('click', this._collapse, this); |
---|
8151 | // TODO keyboard accessibility |
---|
8152 | } else { |
---|
8153 | this._expand(); |
---|
8154 | } |
---|
8155 | |
---|
8156 | this._baseLayersList = L.DomUtil.create('div', className + '-base', form); |
---|
8157 | this._separator = L.DomUtil.create('div', className + '-separator', form); |
---|
8158 | this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); |
---|
8159 | |
---|
8160 | container.appendChild(form); |
---|
8161 | }, |
---|
8162 | |
---|
8163 | _addLayer: function (layer, name, overlay) { |
---|
8164 | var id = L.stamp(layer); |
---|
8165 | |
---|
8166 | this._layers[id] = { |
---|
8167 | layer: layer, |
---|
8168 | name: name, |
---|
8169 | overlay: overlay |
---|
8170 | }; |
---|
8171 | |
---|
8172 | if (this.options.autoZIndex && layer.setZIndex) { |
---|
8173 | this._lastZIndex++; |
---|
8174 | layer.setZIndex(this._lastZIndex); |
---|
8175 | } |
---|
8176 | }, |
---|
8177 | |
---|
8178 | _update: function () { |
---|
8179 | if (!this._container) { |
---|
8180 | return; |
---|
8181 | } |
---|
8182 | |
---|
8183 | this._baseLayersList.innerHTML = ''; |
---|
8184 | this._overlaysList.innerHTML = ''; |
---|
8185 | |
---|
8186 | var baseLayersPresent = false, |
---|
8187 | overlaysPresent = false, |
---|
8188 | i, obj; |
---|
8189 | |
---|
8190 | for (i in this._layers) { |
---|
8191 | obj = this._layers[i]; |
---|
8192 | this._addItem(obj); |
---|
8193 | overlaysPresent = overlaysPresent || obj.overlay; |
---|
8194 | baseLayersPresent = baseLayersPresent || !obj.overlay; |
---|
8195 | } |
---|
8196 | |
---|
8197 | this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; |
---|
8198 | }, |
---|
8199 | |
---|
8200 | _onLayerChange: function (e) { |
---|
8201 | var obj = this._layers[L.stamp(e.layer)]; |
---|
8202 | |
---|
8203 | if (!obj) { return; } |
---|
8204 | |
---|
8205 | if (!this._handlingClick) { |
---|
8206 | this._update(); |
---|
8207 | } |
---|
8208 | |
---|
8209 | var type = obj.overlay ? |
---|
8210 | (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') : |
---|
8211 | (e.type === 'layeradd' ? 'baselayerchange' : null); |
---|
8212 | |
---|
8213 | if (type) { |
---|
8214 | this._map.fire(type, obj); |
---|
8215 | } |
---|
8216 | }, |
---|
8217 | |
---|
8218 | // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) |
---|
8219 | _createRadioElement: function (name, checked) { |
---|
8220 | |
---|
8221 | var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"'; |
---|
8222 | if (checked) { |
---|
8223 | radioHtml += ' checked="checked"'; |
---|
8224 | } |
---|
8225 | radioHtml += '/>'; |
---|
8226 | |
---|
8227 | var radioFragment = document.createElement('div'); |
---|
8228 | radioFragment.innerHTML = radioHtml; |
---|
8229 | |
---|
8230 | return radioFragment.firstChild; |
---|
8231 | }, |
---|
8232 | |
---|
8233 | _addItem: function (obj) { |
---|
8234 | var label = document.createElement('label'), |
---|
8235 | input, |
---|
8236 | checked = this._map.hasLayer(obj.layer); |
---|
8237 | |
---|
8238 | if (obj.overlay) { |
---|
8239 | input = document.createElement('input'); |
---|
8240 | input.type = 'checkbox'; |
---|
8241 | input.className = 'leaflet-control-layers-selector'; |
---|
8242 | input.defaultChecked = checked; |
---|
8243 | } else { |
---|
8244 | input = this._createRadioElement('leaflet-base-layers', checked); |
---|
8245 | } |
---|
8246 | |
---|
8247 | input.layerId = L.stamp(obj.layer); |
---|
8248 | |
---|
8249 | L.DomEvent.on(input, 'click', this._onInputClick, this); |
---|
8250 | |
---|
8251 | var name = document.createElement('span'); |
---|
8252 | name.innerHTML = ' ' + obj.name; |
---|
8253 | |
---|
8254 | label.appendChild(input); |
---|
8255 | label.appendChild(name); |
---|
8256 | |
---|
8257 | var container = obj.overlay ? this._overlaysList : this._baseLayersList; |
---|
8258 | container.appendChild(label); |
---|
8259 | |
---|
8260 | return label; |
---|
8261 | }, |
---|
8262 | |
---|
8263 | _onInputClick: function () { |
---|
8264 | var i, input, obj, |
---|
8265 | inputs = this._form.getElementsByTagName('input'), |
---|
8266 | inputsLen = inputs.length; |
---|
8267 | |
---|
8268 | this._handlingClick = true; |
---|
8269 | |
---|
8270 | for (i = 0; i < inputsLen; i++) { |
---|
8271 | input = inputs[i]; |
---|
8272 | obj = this._layers[input.layerId]; |
---|
8273 | |
---|
8274 | if (input.checked && !this._map.hasLayer(obj.layer)) { |
---|
8275 | this._map.addLayer(obj.layer); |
---|
8276 | |
---|
8277 | } else if (!input.checked && this._map.hasLayer(obj.layer)) { |
---|
8278 | this._map.removeLayer(obj.layer); |
---|
8279 | } |
---|
8280 | } |
---|
8281 | |
---|
8282 | this._handlingClick = false; |
---|
8283 | }, |
---|
8284 | |
---|
8285 | _expand: function () { |
---|
8286 | L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); |
---|
8287 | }, |
---|
8288 | |
---|
8289 | _collapse: function () { |
---|
8290 | this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', ''); |
---|
8291 | } |
---|
8292 | }); |
---|
8293 | |
---|
8294 | L.control.layers = function (baseLayers, overlays, options) { |
---|
8295 | return new L.Control.Layers(baseLayers, overlays, options); |
---|
8296 | }; |
---|
8297 | |
---|
8298 | |
---|
8299 | /* |
---|
8300 | * L.PosAnimation is used by Leaflet internally for pan animations. |
---|
8301 | */ |
---|
8302 | |
---|
8303 | L.PosAnimation = L.Class.extend({ |
---|
8304 | includes: L.Mixin.Events, |
---|
8305 | |
---|
8306 | run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number]) |
---|
8307 | this.stop(); |
---|
8308 | |
---|
8309 | this._el = el; |
---|
8310 | this._inProgress = true; |
---|
8311 | this._newPos = newPos; |
---|
8312 | |
---|
8313 | this.fire('start'); |
---|
8314 | |
---|
8315 | el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) + |
---|
8316 | 's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)'; |
---|
8317 | |
---|
8318 | L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this); |
---|
8319 | L.DomUtil.setPosition(el, newPos); |
---|
8320 | |
---|
8321 | // toggle reflow, Chrome flickers for some reason if you don't do this |
---|
8322 | L.Util.falseFn(el.offsetWidth); |
---|
8323 | |
---|
8324 | // there's no native way to track value updates of transitioned properties, so we imitate this |
---|
8325 | this._stepTimer = setInterval(L.bind(this._onStep, this), 50); |
---|
8326 | }, |
---|
8327 | |
---|
8328 | stop: function () { |
---|
8329 | if (!this._inProgress) { return; } |
---|
8330 | |
---|
8331 | // if we just removed the transition property, the element would jump to its final position, |
---|
8332 | // so we need to make it stay at the current position |
---|
8333 | |
---|
8334 | L.DomUtil.setPosition(this._el, this._getPos()); |
---|
8335 | this._onTransitionEnd(); |
---|
8336 | L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation |
---|
8337 | }, |
---|
8338 | |
---|
8339 | _onStep: function () { |
---|
8340 | // jshint camelcase: false |
---|
8341 | // make L.DomUtil.getPosition return intermediate position value during animation |
---|
8342 | this._el._leaflet_pos = this._getPos(); |
---|
8343 | |
---|
8344 | this.fire('step'); |
---|
8345 | }, |
---|
8346 | |
---|
8347 | // you can't easily get intermediate values of properties animated with CSS3 Transitions, |
---|
8348 | // we need to parse computed style (in case of transform it returns matrix string) |
---|
8349 | |
---|
8350 | _transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/, |
---|
8351 | |
---|
8352 | _getPos: function () { |
---|
8353 | var left, top, matches, |
---|
8354 | el = this._el, |
---|
8355 | style = window.getComputedStyle(el); |
---|
8356 | |
---|
8357 | if (L.Browser.any3d) { |
---|
8358 | matches = style[L.DomUtil.TRANSFORM].match(this._transformRe); |
---|
8359 | left = matches ? parseFloat(matches[1]) : 0; |
---|
8360 | top = matches ? parseFloat(matches[2]) : 0; |
---|
8361 | } else { |
---|
8362 | left = parseFloat(style.left); |
---|
8363 | top = parseFloat(style.top); |
---|
8364 | } |
---|
8365 | |
---|
8366 | return new L.Point(left, top, true); |
---|
8367 | }, |
---|
8368 | |
---|
8369 | _onTransitionEnd: function () { |
---|
8370 | L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this); |
---|
8371 | |
---|
8372 | if (!this._inProgress) { return; } |
---|
8373 | this._inProgress = false; |
---|
8374 | |
---|
8375 | this._el.style[L.DomUtil.TRANSITION] = ''; |
---|
8376 | |
---|
8377 | // jshint camelcase: false |
---|
8378 | // make sure L.DomUtil.getPosition returns the final position value after animation |
---|
8379 | this._el._leaflet_pos = this._newPos; |
---|
8380 | |
---|
8381 | clearInterval(this._stepTimer); |
---|
8382 | |
---|
8383 | this.fire('step').fire('end'); |
---|
8384 | } |
---|
8385 | |
---|
8386 | }); |
---|
8387 | |
---|
8388 | |
---|
8389 | /* |
---|
8390 | * Extends L.Map to handle panning animations. |
---|
8391 | */ |
---|
8392 | |
---|
8393 | L.Map.include({ |
---|
8394 | |
---|
8395 | setView: function (center, zoom, options) { |
---|
8396 | |
---|
8397 | zoom = this._limitZoom(zoom); |
---|
8398 | center = L.latLng(center); |
---|
8399 | options = options || {}; |
---|
8400 | |
---|
8401 | if (this._panAnim) { |
---|
8402 | this._panAnim.stop(); |
---|
8403 | } |
---|
8404 | |
---|
8405 | if (this._loaded && !options.reset && options !== true) { |
---|
8406 | |
---|
8407 | if (options.animate !== undefined) { |
---|
8408 | options.zoom = L.extend({animate: options.animate}, options.zoom); |
---|
8409 | options.pan = L.extend({animate: options.animate}, options.pan); |
---|
8410 | } |
---|
8411 | |
---|
8412 | // try animating pan or zoom |
---|
8413 | var animated = (this._zoom !== zoom) ? |
---|
8414 | this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : |
---|
8415 | this._tryAnimatedPan(center, options.pan); |
---|
8416 | |
---|
8417 | if (animated) { |
---|
8418 | // prevent resize handler call, the view will refresh after animation anyway |
---|
8419 | clearTimeout(this._sizeTimer); |
---|
8420 | return this; |
---|
8421 | } |
---|
8422 | } |
---|
8423 | |
---|
8424 | // animation didn't start, just reset the map view |
---|
8425 | this._resetView(center, zoom); |
---|
8426 | |
---|
8427 | return this; |
---|
8428 | }, |
---|
8429 | |
---|
8430 | panBy: function (offset, options) { |
---|
8431 | offset = L.point(offset).round(); |
---|
8432 | options = options || {}; |
---|
8433 | |
---|
8434 | if (!offset.x && !offset.y) { |
---|
8435 | return this; |
---|
8436 | } |
---|
8437 | |
---|
8438 | if (!this._panAnim) { |
---|
8439 | this._panAnim = new L.PosAnimation(); |
---|
8440 | |
---|
8441 | this._panAnim.on({ |
---|
8442 | 'step': this._onPanTransitionStep, |
---|
8443 | 'end': this._onPanTransitionEnd |
---|
8444 | }, this); |
---|
8445 | } |
---|
8446 | |
---|
8447 | // don't fire movestart if animating inertia |
---|
8448 | if (!options.noMoveStart) { |
---|
8449 | this.fire('movestart'); |
---|
8450 | } |
---|
8451 | |
---|
8452 | // animate pan unless animate: false specified |
---|
8453 | if (options.animate !== false) { |
---|
8454 | L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); |
---|
8455 | |
---|
8456 | var newPos = this._getMapPanePos().subtract(offset); |
---|
8457 | this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); |
---|
8458 | } else { |
---|
8459 | this._rawPanBy(offset); |
---|
8460 | this.fire('move').fire('moveend'); |
---|
8461 | } |
---|
8462 | |
---|
8463 | return this; |
---|
8464 | }, |
---|
8465 | |
---|
8466 | _onPanTransitionStep: function () { |
---|
8467 | this.fire('move'); |
---|
8468 | }, |
---|
8469 | |
---|
8470 | _onPanTransitionEnd: function () { |
---|
8471 | L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); |
---|
8472 | this.fire('moveend'); |
---|
8473 | }, |
---|
8474 | |
---|
8475 | _tryAnimatedPan: function (center, options) { |
---|
8476 | // difference between the new and current centers in pixels |
---|
8477 | var offset = this._getCenterOffset(center)._floor(); |
---|
8478 | |
---|
8479 | // don't animate too far unless animate: true specified in options |
---|
8480 | if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } |
---|
8481 | |
---|
8482 | this.panBy(offset, options); |
---|
8483 | |
---|
8484 | return true; |
---|
8485 | } |
---|
8486 | }); |
---|
8487 | |
---|
8488 | |
---|
8489 | /* |
---|
8490 | * L.PosAnimation fallback implementation that powers Leaflet pan animations |
---|
8491 | * in browsers that don't support CSS3 Transitions. |
---|
8492 | */ |
---|
8493 | |
---|
8494 | L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({ |
---|
8495 | |
---|
8496 | run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number]) |
---|
8497 | this.stop(); |
---|
8498 | |
---|
8499 | this._el = el; |
---|
8500 | this._inProgress = true; |
---|
8501 | this._duration = duration || 0.25; |
---|
8502 | this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); |
---|
8503 | |
---|
8504 | this._startPos = L.DomUtil.getPosition(el); |
---|
8505 | this._offset = newPos.subtract(this._startPos); |
---|
8506 | this._startTime = +new Date(); |
---|
8507 | |
---|
8508 | this.fire('start'); |
---|
8509 | |
---|
8510 | this._animate(); |
---|
8511 | }, |
---|
8512 | |
---|
8513 | stop: function () { |
---|
8514 | if (!this._inProgress) { return; } |
---|
8515 | |
---|
8516 | this._step(); |
---|
8517 | this._complete(); |
---|
8518 | }, |
---|
8519 | |
---|
8520 | _animate: function () { |
---|
8521 | // animation loop |
---|
8522 | this._animId = L.Util.requestAnimFrame(this._animate, this); |
---|
8523 | this._step(); |
---|
8524 | }, |
---|
8525 | |
---|
8526 | _step: function () { |
---|
8527 | var elapsed = (+new Date()) - this._startTime, |
---|
8528 | duration = this._duration * 1000; |
---|
8529 | |
---|
8530 | if (elapsed < duration) { |
---|
8531 | this._runFrame(this._easeOut(elapsed / duration)); |
---|
8532 | } else { |
---|
8533 | this._runFrame(1); |
---|
8534 | this._complete(); |
---|
8535 | } |
---|
8536 | }, |
---|
8537 | |
---|
8538 | _runFrame: function (progress) { |
---|
8539 | var pos = this._startPos.add(this._offset.multiplyBy(progress)); |
---|
8540 | L.DomUtil.setPosition(this._el, pos); |
---|
8541 | |
---|
8542 | this.fire('step'); |
---|
8543 | }, |
---|
8544 | |
---|
8545 | _complete: function () { |
---|
8546 | L.Util.cancelAnimFrame(this._animId); |
---|
8547 | |
---|
8548 | this._inProgress = false; |
---|
8549 | this.fire('end'); |
---|
8550 | }, |
---|
8551 | |
---|
8552 | _easeOut: function (t) { |
---|
8553 | return 1 - Math.pow(1 - t, this._easeOutPower); |
---|
8554 | } |
---|
8555 | }); |
---|
8556 | |
---|
8557 | |
---|
8558 | /* |
---|
8559 | * Extends L.Map to handle zoom animations. |
---|
8560 | */ |
---|
8561 | |
---|
8562 | L.Map.mergeOptions({ |
---|
8563 | zoomAnimation: true, |
---|
8564 | zoomAnimationThreshold: 4 |
---|
8565 | }); |
---|
8566 | |
---|
8567 | if (L.DomUtil.TRANSITION) { |
---|
8568 | |
---|
8569 | L.Map.addInitHook(function () { |
---|
8570 | // don't animate on browsers without hardware-accelerated transitions or old Android/Opera |
---|
8571 | this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION && |
---|
8572 | L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera; |
---|
8573 | |
---|
8574 | // zoom transitions run with the same duration for all layers, so if one of transitionend events |
---|
8575 | // happens after starting zoom animation (propagating to the map pane), we know that it ended globally |
---|
8576 | if (this._zoomAnimated) { |
---|
8577 | L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); |
---|
8578 | } |
---|
8579 | }); |
---|
8580 | } |
---|
8581 | |
---|
8582 | L.Map.include(!L.DomUtil.TRANSITION ? {} : { |
---|
8583 | |
---|
8584 | _catchTransitionEnd: function () { |
---|
8585 | if (this._animatingZoom) { |
---|
8586 | this._onZoomTransitionEnd(); |
---|
8587 | } |
---|
8588 | }, |
---|
8589 | |
---|
8590 | _tryAnimatedZoom: function (center, zoom, options) { |
---|
8591 | |
---|
8592 | if (this._animatingZoom) { return true; } |
---|
8593 | |
---|
8594 | options = options || {}; |
---|
8595 | |
---|
8596 | // don't animate if disabled, not supported or zoom difference is too large |
---|
8597 | if (!this._zoomAnimated || options.animate === false || |
---|
8598 | Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } |
---|
8599 | |
---|
8600 | // offset is the pixel coords of the zoom origin relative to the current center |
---|
8601 | var scale = this.getZoomScale(zoom), |
---|
8602 | offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale), |
---|
8603 | origin = this._getCenterLayerPoint()._add(offset); |
---|
8604 | |
---|
8605 | // don't animate if the zoom origin isn't within one screen from the current center, unless forced |
---|
8606 | if (options.animate !== true && !this.getSize().contains(offset)) { return false; } |
---|
8607 | |
---|
8608 | this |
---|
8609 | .fire('movestart') |
---|
8610 | .fire('zoomstart'); |
---|
8611 | |
---|
8612 | this._animateZoom(center, zoom, origin, scale, null, true); |
---|
8613 | |
---|
8614 | return true; |
---|
8615 | }, |
---|
8616 | |
---|
8617 | _animateZoom: function (center, zoom, origin, scale, delta, backwards) { |
---|
8618 | |
---|
8619 | this._animatingZoom = true; |
---|
8620 | |
---|
8621 | // put transform transition on all layers with leaflet-zoom-animated class |
---|
8622 | L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); |
---|
8623 | |
---|
8624 | // remember what center/zoom to set after animation |
---|
8625 | this._animateToCenter = center; |
---|
8626 | this._animateToZoom = zoom; |
---|
8627 | |
---|
8628 | // disable any dragging during animation |
---|
8629 | if (L.Draggable) { |
---|
8630 | L.Draggable._disabled = true; |
---|
8631 | } |
---|
8632 | |
---|
8633 | this.fire('zoomanim', { |
---|
8634 | center: center, |
---|
8635 | zoom: zoom, |
---|
8636 | origin: origin, |
---|
8637 | scale: scale, |
---|
8638 | delta: delta, |
---|
8639 | backwards: backwards |
---|
8640 | }); |
---|
8641 | }, |
---|
8642 | |
---|
8643 | _onZoomTransitionEnd: function () { |
---|
8644 | |
---|
8645 | this._animatingZoom = false; |
---|
8646 | |
---|
8647 | L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); |
---|
8648 | |
---|
8649 | this._resetView(this._animateToCenter, this._animateToZoom, true, true); |
---|
8650 | |
---|
8651 | if (L.Draggable) { |
---|
8652 | L.Draggable._disabled = false; |
---|
8653 | } |
---|
8654 | } |
---|
8655 | }); |
---|
8656 | |
---|
8657 | |
---|
8658 | /* |
---|
8659 | Zoom animation logic for L.TileLayer. |
---|
8660 | */ |
---|
8661 | |
---|
8662 | L.TileLayer.include({ |
---|
8663 | _animateZoom: function (e) { |
---|
8664 | if (!this._animating) { |
---|
8665 | this._animating = true; |
---|
8666 | this._prepareBgBuffer(); |
---|
8667 | } |
---|
8668 | |
---|
8669 | var bg = this._bgBuffer, |
---|
8670 | transform = L.DomUtil.TRANSFORM, |
---|
8671 | initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform], |
---|
8672 | scaleStr = L.DomUtil.getScaleString(e.scale, e.origin); |
---|
8673 | |
---|
8674 | bg.style[transform] = e.backwards ? |
---|
8675 | scaleStr + ' ' + initialTransform : |
---|
8676 | initialTransform + ' ' + scaleStr; |
---|
8677 | }, |
---|
8678 | |
---|
8679 | _endZoomAnim: function () { |
---|
8680 | var front = this._tileContainer, |
---|
8681 | bg = this._bgBuffer; |
---|
8682 | |
---|
8683 | front.style.visibility = ''; |
---|
8684 | front.parentNode.appendChild(front); // Bring to fore |
---|
8685 | |
---|
8686 | // force reflow |
---|
8687 | L.Util.falseFn(bg.offsetWidth); |
---|
8688 | |
---|
8689 | this._animating = false; |
---|
8690 | }, |
---|
8691 | |
---|
8692 | _clearBgBuffer: function () { |
---|
8693 | var map = this._map; |
---|
8694 | |
---|
8695 | if (map && !map._animatingZoom && !map.touchZoom._zooming) { |
---|
8696 | this._bgBuffer.innerHTML = ''; |
---|
8697 | this._bgBuffer.style[L.DomUtil.TRANSFORM] = ''; |
---|
8698 | } |
---|
8699 | }, |
---|
8700 | |
---|
8701 | _prepareBgBuffer: function () { |
---|
8702 | |
---|
8703 | var front = this._tileContainer, |
---|
8704 | bg = this._bgBuffer; |
---|
8705 | |
---|
8706 | // if foreground layer doesn't have many tiles but bg layer does, |
---|
8707 | // keep the existing bg layer and just zoom it some more |
---|
8708 | |
---|
8709 | var bgLoaded = this._getLoadedTilesPercentage(bg), |
---|
8710 | frontLoaded = this._getLoadedTilesPercentage(front); |
---|
8711 | |
---|
8712 | if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) { |
---|
8713 | |
---|
8714 | front.style.visibility = 'hidden'; |
---|
8715 | this._stopLoadingImages(front); |
---|
8716 | return; |
---|
8717 | } |
---|
8718 | |
---|
8719 | // prepare the buffer to become the front tile pane |
---|
8720 | bg.style.visibility = 'hidden'; |
---|
8721 | bg.style[L.DomUtil.TRANSFORM] = ''; |
---|
8722 | |
---|
8723 | // switch out the current layer to be the new bg layer (and vice-versa) |
---|
8724 | this._tileContainer = bg; |
---|
8725 | bg = this._bgBuffer = front; |
---|
8726 | |
---|
8727 | this._stopLoadingImages(bg); |
---|
8728 | |
---|
8729 | //prevent bg buffer from clearing right after zoom |
---|
8730 | clearTimeout(this._clearBgBufferTimer); |
---|
8731 | }, |
---|
8732 | |
---|
8733 | _getLoadedTilesPercentage: function (container) { |
---|
8734 | var tiles = container.getElementsByTagName('img'), |
---|
8735 | i, len, count = 0; |
---|
8736 | |
---|
8737 | for (i = 0, len = tiles.length; i < len; i++) { |
---|
8738 | if (tiles[i].complete) { |
---|
8739 | count++; |
---|
8740 | } |
---|
8741 | } |
---|
8742 | return count / len; |
---|
8743 | }, |
---|
8744 | |
---|
8745 | // stops loading all tiles in the background layer |
---|
8746 | _stopLoadingImages: function (container) { |
---|
8747 | var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')), |
---|
8748 | i, len, tile; |
---|
8749 | |
---|
8750 | for (i = 0, len = tiles.length; i < len; i++) { |
---|
8751 | tile = tiles[i]; |
---|
8752 | |
---|
8753 | if (!tile.complete) { |
---|
8754 | tile.onload = L.Util.falseFn; |
---|
8755 | tile.onerror = L.Util.falseFn; |
---|
8756 | tile.src = L.Util.emptyImageUrl; |
---|
8757 | |
---|
8758 | tile.parentNode.removeChild(tile); |
---|
8759 | } |
---|
8760 | } |
---|
8761 | } |
---|
8762 | }); |
---|
8763 | |
---|
8764 | |
---|
8765 | /* |
---|
8766 | * Provides L.Map with convenient shortcuts for using browser geolocation features. |
---|
8767 | */ |
---|
8768 | |
---|
8769 | L.Map.include({ |
---|
8770 | _defaultLocateOptions: { |
---|
8771 | watch: false, |
---|
8772 | setView: false, |
---|
8773 | maxZoom: Infinity, |
---|
8774 | timeout: 10000, |
---|
8775 | maximumAge: 0, |
---|
8776 | enableHighAccuracy: false |
---|
8777 | }, |
---|
8778 | |
---|
8779 | locate: function (/*Object*/ options) { |
---|
8780 | |
---|
8781 | options = this._locateOptions = L.extend(this._defaultLocateOptions, options); |
---|
8782 | |
---|
8783 | if (!navigator.geolocation) { |
---|
8784 | this._handleGeolocationError({ |
---|
8785 | code: 0, |
---|
8786 | message: 'Geolocation not supported.' |
---|
8787 | }); |
---|
8788 | return this; |
---|
8789 | } |
---|
8790 | |
---|
8791 | var onResponse = L.bind(this._handleGeolocationResponse, this), |
---|
8792 | onError = L.bind(this._handleGeolocationError, this); |
---|
8793 | |
---|
8794 | if (options.watch) { |
---|
8795 | this._locationWatchId = |
---|
8796 | navigator.geolocation.watchPosition(onResponse, onError, options); |
---|
8797 | } else { |
---|
8798 | navigator.geolocation.getCurrentPosition(onResponse, onError, options); |
---|
8799 | } |
---|
8800 | return this; |
---|
8801 | }, |
---|
8802 | |
---|
8803 | stopLocate: function () { |
---|
8804 | if (navigator.geolocation) { |
---|
8805 | navigator.geolocation.clearWatch(this._locationWatchId); |
---|
8806 | } |
---|
8807 | if (this._locateOptions) { |
---|
8808 | this._locateOptions.setView = false; |
---|
8809 | } |
---|
8810 | return this; |
---|
8811 | }, |
---|
8812 | |
---|
8813 | _handleGeolocationError: function (error) { |
---|
8814 | var c = error.code, |
---|
8815 | message = error.message || |
---|
8816 | (c === 1 ? 'permission denied' : |
---|
8817 | (c === 2 ? 'position unavailable' : 'timeout')); |
---|
8818 | |
---|
8819 | if (this._locateOptions.setView && !this._loaded) { |
---|
8820 | this.fitWorld(); |
---|
8821 | } |
---|
8822 | |
---|
8823 | this.fire('locationerror', { |
---|
8824 | code: c, |
---|
8825 | message: 'Geolocation error: ' + message + '.' |
---|
8826 | }); |
---|
8827 | }, |
---|
8828 | |
---|
8829 | _handleGeolocationResponse: function (pos) { |
---|
8830 | var lat = pos.coords.latitude, |
---|
8831 | lng = pos.coords.longitude, |
---|
8832 | latlng = new L.LatLng(lat, lng), |
---|
8833 | |
---|
8834 | latAccuracy = 180 * pos.coords.accuracy / 40075017, |
---|
8835 | lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat), |
---|
8836 | |
---|
8837 | bounds = L.latLngBounds( |
---|
8838 | [lat - latAccuracy, lng - lngAccuracy], |
---|
8839 | [lat + latAccuracy, lng + lngAccuracy]), |
---|
8840 | |
---|
8841 | options = this._locateOptions; |
---|
8842 | |
---|
8843 | if (options.setView) { |
---|
8844 | var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom); |
---|
8845 | this.setView(latlng, zoom); |
---|
8846 | } |
---|
8847 | |
---|
8848 | var data = { |
---|
8849 | latlng: latlng, |
---|
8850 | bounds: bounds, |
---|
8851 | }; |
---|
8852 | |
---|
8853 | for (var i in pos.coords) { |
---|
8854 | if (typeof pos.coords[i] === 'number') { |
---|
8855 | data[i] = pos.coords[i]; |
---|
8856 | } |
---|
8857 | } |
---|
8858 | |
---|
8859 | this.fire('locationfound', data); |
---|
8860 | } |
---|
8861 | }); |
---|
8862 | |
---|
8863 | |
---|
8864 | }(window, document)); |
---|