Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epsylon
GitHub Repository: epsylon/ufonet
Path: blob/master/core/js/leaflet/leaflet-src.js
1205 views
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.4';
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
L.extend(L.DomUtil, {
1096
disableTextSelection: function () {
1097
L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1098
if (userSelectProperty) {
1099
var style = document.documentElement.style;
1100
this._userSelect = style[userSelectProperty];
1101
style[userSelectProperty] = 'none';
1102
}
1103
},
1104
1105
enableTextSelection: function () {
1106
L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1107
if (userSelectProperty) {
1108
document.documentElement.style[userSelectProperty] = this._userSelect;
1109
delete this._userSelect;
1110
}
1111
},
1112
1113
disableImageDrag: function () {
1114
L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1115
},
1116
1117
enableImageDrag: function () {
1118
L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1119
}
1120
});
1121
})();
1122
1123
1124
/*
1125
* L.LatLng represents a geographical point with latitude and longitude coordinates.
1126
*/
1127
1128
L.LatLng = function (rawLat, rawLng) { // (Number, Number)
1129
var lat = parseFloat(rawLat),
1130
lng = parseFloat(rawLng);
1131
1132
if (isNaN(lat) || isNaN(lng)) {
1133
throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
1134
}
1135
1136
this.lat = lat;
1137
this.lng = lng;
1138
};
1139
1140
L.extend(L.LatLng, {
1141
DEG_TO_RAD: Math.PI / 180,
1142
RAD_TO_DEG: 180 / Math.PI,
1143
MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
1144
});
1145
1146
L.LatLng.prototype = {
1147
equals: function (obj) { // (LatLng) -> Boolean
1148
if (!obj) { return false; }
1149
1150
obj = L.latLng(obj);
1151
1152
var margin = Math.max(
1153
Math.abs(this.lat - obj.lat),
1154
Math.abs(this.lng - obj.lng));
1155
1156
return margin <= L.LatLng.MAX_MARGIN;
1157
},
1158
1159
toString: function (precision) { // (Number) -> String
1160
return 'LatLng(' +
1161
L.Util.formatNum(this.lat, precision) + ', ' +
1162
L.Util.formatNum(this.lng, precision) + ')';
1163
},
1164
1165
// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
1166
// TODO move to projection code, LatLng shouldn't know about Earth
1167
distanceTo: function (other) { // (LatLng) -> Number
1168
other = L.latLng(other);
1169
1170
var R = 6378137, // earth radius in meters
1171
d2r = L.LatLng.DEG_TO_RAD,
1172
dLat = (other.lat - this.lat) * d2r,
1173
dLon = (other.lng - this.lng) * d2r,
1174
lat1 = this.lat * d2r,
1175
lat2 = other.lat * d2r,
1176
sin1 = Math.sin(dLat / 2),
1177
sin2 = Math.sin(dLon / 2);
1178
1179
var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
1180
1181
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1182
},
1183
1184
wrap: function (a, b) { // (Number, Number) -> LatLng
1185
var lng = this.lng;
1186
1187
a = a || -180;
1188
b = b || 180;
1189
1190
lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
1191
1192
return new L.LatLng(this.lat, lng);
1193
}
1194
};
1195
1196
L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
1197
if (a instanceof L.LatLng) {
1198
return a;
1199
}
1200
if (L.Util.isArray(a)) {
1201
return new L.LatLng(a[0], a[1]);
1202
}
1203
if (a === undefined || a === null) {
1204
return a;
1205
}
1206
if (typeof a === 'object' && 'lat' in a) {
1207
return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
1208
}
1209
return new L.LatLng(a, b);
1210
};
1211
1212
1213
1214
/*
1215
* L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
1216
*/
1217
1218
L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1219
if (!southWest) { return; }
1220
1221
var latlngs = northEast ? [southWest, northEast] : southWest;
1222
1223
for (var i = 0, len = latlngs.length; i < len; i++) {
1224
this.extend(latlngs[i]);
1225
}
1226
};
1227
1228
L.LatLngBounds.prototype = {
1229
// extend the bounds to contain the given point or bounds
1230
extend: function (obj) { // (LatLng) or (LatLngBounds)
1231
if (!obj) { return this; }
1232
1233
if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
1234
obj = L.latLng(obj);
1235
} else {
1236
obj = L.latLngBounds(obj);
1237
}
1238
1239
if (obj instanceof L.LatLng) {
1240
if (!this._southWest && !this._northEast) {
1241
this._southWest = new L.LatLng(obj.lat, obj.lng);
1242
this._northEast = new L.LatLng(obj.lat, obj.lng);
1243
} else {
1244
this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
1245
this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
1246
1247
this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
1248
this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
1249
}
1250
} else if (obj instanceof L.LatLngBounds) {
1251
this.extend(obj._southWest);
1252
this.extend(obj._northEast);
1253
}
1254
return this;
1255
},
1256
1257
// extend the bounds by a percentage
1258
pad: function (bufferRatio) { // (Number) -> LatLngBounds
1259
var sw = this._southWest,
1260
ne = this._northEast,
1261
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1262
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1263
1264
return new L.LatLngBounds(
1265
new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1266
new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1267
},
1268
1269
getCenter: function () { // -> LatLng
1270
return new L.LatLng(
1271
(this._southWest.lat + this._northEast.lat) / 2,
1272
(this._southWest.lng + this._northEast.lng) / 2);
1273
},
1274
1275
getSouthWest: function () {
1276
return this._southWest;
1277
},
1278
1279
getNorthEast: function () {
1280
return this._northEast;
1281
},
1282
1283
getNorthWest: function () {
1284
return new L.LatLng(this.getNorth(), this.getWest());
1285
},
1286
1287
getSouthEast: function () {
1288
return new L.LatLng(this.getSouth(), this.getEast());
1289
},
1290
1291
getWest: function () {
1292
return this._southWest.lng;
1293
},
1294
1295
getSouth: function () {
1296
return this._southWest.lat;
1297
},
1298
1299
getEast: function () {
1300
return this._northEast.lng;
1301
},
1302
1303
getNorth: function () {
1304
return this._northEast.lat;
1305
},
1306
1307
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1308
if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1309
obj = L.latLng(obj);
1310
} else {
1311
obj = L.latLngBounds(obj);
1312
}
1313
1314
var sw = this._southWest,
1315
ne = this._northEast,
1316
sw2, ne2;
1317
1318
if (obj instanceof L.LatLngBounds) {
1319
sw2 = obj.getSouthWest();
1320
ne2 = obj.getNorthEast();
1321
} else {
1322
sw2 = ne2 = obj;
1323
}
1324
1325
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1326
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1327
},
1328
1329
intersects: function (bounds) { // (LatLngBounds)
1330
bounds = L.latLngBounds(bounds);
1331
1332
var sw = this._southWest,
1333
ne = this._northEast,
1334
sw2 = bounds.getSouthWest(),
1335
ne2 = bounds.getNorthEast(),
1336
1337
latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1338
lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1339
1340
return latIntersects && lngIntersects;
1341
},
1342
1343
toBBoxString: function () {
1344
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1345
},
1346
1347
equals: function (bounds) { // (LatLngBounds)
1348
if (!bounds) { return false; }
1349
1350
bounds = L.latLngBounds(bounds);
1351
1352
return this._southWest.equals(bounds.getSouthWest()) &&
1353
this._northEast.equals(bounds.getNorthEast());
1354
},
1355
1356
isValid: function () {
1357
return !!(this._southWest && this._northEast);
1358
}
1359
};
1360
1361
//TODO International date line?
1362
1363
L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
1364
if (!a || a instanceof L.LatLngBounds) {
1365
return a;
1366
}
1367
return new L.LatLngBounds(a, b);
1368
};
1369
1370
1371
/*
1372
* L.Projection contains various geographical projections used by CRS classes.
1373
*/
1374
1375
L.Projection = {};
1376
1377
1378
/*
1379
* Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
1380
*/
1381
1382
L.Projection.SphericalMercator = {
1383
MAX_LATITUDE: 85.0511287798,
1384
1385
project: function (latlng) { // (LatLng) -> Point
1386
var d = L.LatLng.DEG_TO_RAD,
1387
max = this.MAX_LATITUDE,
1388
lat = Math.max(Math.min(max, latlng.lat), -max),
1389
x = latlng.lng * d,
1390
y = lat * d;
1391
1392
y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
1393
1394
return new L.Point(x, y);
1395
},
1396
1397
unproject: function (point) { // (Point, Boolean) -> LatLng
1398
var d = L.LatLng.RAD_TO_DEG,
1399
lng = point.x * d,
1400
lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
1401
1402
return new L.LatLng(lat, lng);
1403
}
1404
};
1405
1406
1407
/*
1408
* Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
1409
*/
1410
1411
L.Projection.LonLat = {
1412
project: function (latlng) {
1413
return new L.Point(latlng.lng, latlng.lat);
1414
},
1415
1416
unproject: function (point) {
1417
return new L.LatLng(point.y, point.x);
1418
}
1419
};
1420
1421
1422
/*
1423
* L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
1424
*/
1425
1426
L.CRS = {
1427
latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
1428
var projectedPoint = this.projection.project(latlng),
1429
scale = this.scale(zoom);
1430
1431
return this.transformation._transform(projectedPoint, scale);
1432
},
1433
1434
pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
1435
var scale = this.scale(zoom),
1436
untransformedPoint = this.transformation.untransform(point, scale);
1437
1438
return this.projection.unproject(untransformedPoint);
1439
},
1440
1441
project: function (latlng) {
1442
return this.projection.project(latlng);
1443
},
1444
1445
scale: function (zoom) {
1446
return 256 * Math.pow(2, zoom);
1447
}
1448
};
1449
1450
1451
/*
1452
* A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
1453
*/
1454
1455
L.CRS.Simple = L.extend({}, L.CRS, {
1456
projection: L.Projection.LonLat,
1457
transformation: new L.Transformation(1, 0, -1, 0),
1458
1459
scale: function (zoom) {
1460
return Math.pow(2, zoom);
1461
}
1462
});
1463
1464
1465
/*
1466
* L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
1467
* and is used by Leaflet by default.
1468
*/
1469
1470
L.CRS.EPSG3857 = L.extend({}, L.CRS, {
1471
code: 'EPSG:3857',
1472
1473
projection: L.Projection.SphericalMercator,
1474
transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
1475
1476
project: function (latlng) { // (LatLng) -> Point
1477
var projectedPoint = this.projection.project(latlng),
1478
earthRadius = 6378137;
1479
return projectedPoint.multiplyBy(earthRadius);
1480
}
1481
});
1482
1483
L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
1484
code: 'EPSG:900913'
1485
});
1486
1487
1488
/*
1489
* L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
1490
*/
1491
1492
L.CRS.EPSG4326 = L.extend({}, L.CRS, {
1493
code: 'EPSG:4326',
1494
1495
projection: L.Projection.LonLat,
1496
transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
1497
});
1498
1499
1500
/*
1501
* L.Map is the central class of the API - it is used to create a map.
1502
*/
1503
1504
L.Map = L.Class.extend({
1505
1506
includes: L.Mixin.Events,
1507
1508
options: {
1509
crs: L.CRS.EPSG3857,
1510
1511
/*
1512
center: LatLng,
1513
zoom: Number,
1514
layers: Array,
1515
*/
1516
1517
fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
1518
trackResize: true,
1519
markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
1520
},
1521
1522
initialize: function (id, options) { // (HTMLElement or String, Object)
1523
options = L.setOptions(this, options);
1524
1525
this._initContainer(id);
1526
this._initLayout();
1527
this._initEvents();
1528
1529
if (options.maxBounds) {
1530
this.setMaxBounds(options.maxBounds);
1531
}
1532
1533
if (options.center && options.zoom !== undefined) {
1534
this.setView(L.latLng(options.center), options.zoom, {reset: true});
1535
}
1536
1537
this._handlers = [];
1538
1539
this._layers = {};
1540
this._zoomBoundLayers = {};
1541
this._tileLayersNum = 0;
1542
1543
this.callInitHooks();
1544
1545
this._addLayers(options.layers);
1546
},
1547
1548
1549
// public methods that modify map state
1550
1551
// replaced by animation-powered implementation in Map.PanAnimation.js
1552
setView: function (center, zoom) {
1553
this._resetView(L.latLng(center), this._limitZoom(zoom));
1554
return this;
1555
},
1556
1557
setZoom: function (zoom, options) {
1558
return this.setView(this.getCenter(), zoom, {zoom: options});
1559
},
1560
1561
zoomIn: function (delta, options) {
1562
return this.setZoom(this._zoom + (delta || 1), options);
1563
},
1564
1565
zoomOut: function (delta, options) {
1566
return this.setZoom(this._zoom - (delta || 1), options);
1567
},
1568
1569
setZoomAround: function (latlng, zoom, options) {
1570
var scale = this.getZoomScale(zoom),
1571
viewHalf = this.getSize().divideBy(2),
1572
containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
1573
1574
centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
1575
newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
1576
1577
return this.setView(newCenter, zoom, {zoom: options});
1578
},
1579
1580
fitBounds: function (bounds, options) {
1581
1582
options = options || {};
1583
bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
1584
1585
var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
1586
paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
1587
1588
zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)),
1589
paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
1590
1591
swPoint = this.project(bounds.getSouthWest(), zoom),
1592
nePoint = this.project(bounds.getNorthEast(), zoom),
1593
center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
1594
1595
return this.setView(center, zoom, options);
1596
},
1597
1598
fitWorld: function (options) {
1599
return this.fitBounds([[-90, -180], [90, 180]], options);
1600
},
1601
1602
panTo: function (center, options) { // (LatLng)
1603
return this.setView(center, this._zoom, {pan: options});
1604
},
1605
1606
panBy: function (offset) { // (Point)
1607
// replaced with animated panBy in Map.Animation.js
1608
this.fire('movestart');
1609
1610
this._rawPanBy(L.point(offset));
1611
1612
this.fire('move');
1613
return this.fire('moveend');
1614
},
1615
1616
setMaxBounds: function (bounds, options) {
1617
bounds = L.latLngBounds(bounds);
1618
1619
this.options.maxBounds = bounds;
1620
1621
if (!bounds) {
1622
this._boundsMinZoom = null;
1623
this.off('moveend', this._panInsideMaxBounds, this);
1624
return this;
1625
}
1626
1627
var minZoom = this.getBoundsZoom(bounds, true);
1628
1629
this._boundsMinZoom = minZoom;
1630
1631
if (this._loaded) {
1632
if (this._zoom < minZoom) {
1633
this.setView(bounds.getCenter(), minZoom, options);
1634
} else {
1635
this.panInsideBounds(bounds);
1636
}
1637
}
1638
1639
this.on('moveend', this._panInsideMaxBounds, this);
1640
1641
return this;
1642
},
1643
1644
panInsideBounds: function (bounds) {
1645
bounds = L.latLngBounds(bounds);
1646
1647
var viewBounds = this.getPixelBounds(),
1648
viewSw = viewBounds.getBottomLeft(),
1649
viewNe = viewBounds.getTopRight(),
1650
sw = this.project(bounds.getSouthWest()),
1651
ne = this.project(bounds.getNorthEast()),
1652
dx = 0,
1653
dy = 0;
1654
1655
if (viewNe.y < ne.y) { // north
1656
dy = Math.ceil(ne.y - viewNe.y);
1657
}
1658
if (viewNe.x > ne.x) { // east
1659
dx = Math.floor(ne.x - viewNe.x);
1660
}
1661
if (viewSw.y > sw.y) { // south
1662
dy = Math.floor(sw.y - viewSw.y);
1663
}
1664
if (viewSw.x < sw.x) { // west
1665
dx = Math.ceil(sw.x - viewSw.x);
1666
}
1667
1668
if (dx || dy) {
1669
return this.panBy([dx, dy]);
1670
}
1671
1672
return this;
1673
},
1674
1675
addLayer: function (layer) {
1676
// TODO method is too big, refactor
1677
1678
var id = L.stamp(layer);
1679
1680
if (this._layers[id]) { return this; }
1681
1682
this._layers[id] = layer;
1683
1684
// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
1685
if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
1686
this._zoomBoundLayers[id] = layer;
1687
this._updateZoomLevels();
1688
}
1689
1690
// TODO looks ugly, refactor!!!
1691
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1692
this._tileLayersNum++;
1693
this._tileLayersToLoad++;
1694
layer.on('load', this._onTileLayerLoad, this);
1695
}
1696
1697
if (this._loaded) {
1698
this._layerAdd(layer);
1699
}
1700
1701
return this;
1702
},
1703
1704
removeLayer: function (layer) {
1705
var id = L.stamp(layer);
1706
1707
if (!this._layers[id]) { return; }
1708
1709
if (this._loaded) {
1710
layer.onRemove(this);
1711
}
1712
1713
delete this._layers[id];
1714
1715
if (this._loaded) {
1716
this.fire('layerremove', {layer: layer});
1717
}
1718
1719
if (this._zoomBoundLayers[id]) {
1720
delete this._zoomBoundLayers[id];
1721
this._updateZoomLevels();
1722
}
1723
1724
// TODO looks ugly, refactor
1725
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
1726
this._tileLayersNum--;
1727
this._tileLayersToLoad--;
1728
layer.off('load', this._onTileLayerLoad, this);
1729
}
1730
1731
return this;
1732
},
1733
1734
hasLayer: function (layer) {
1735
if (!layer) { return false; }
1736
1737
return (L.stamp(layer) in this._layers);
1738
},
1739
1740
eachLayer: function (method, context) {
1741
for (var i in this._layers) {
1742
method.call(context, this._layers[i]);
1743
}
1744
return this;
1745
},
1746
1747
invalidateSize: function (options) {
1748
options = L.extend({
1749
animate: false,
1750
pan: true
1751
}, options === true ? {animate: true} : options);
1752
1753
var oldSize = this.getSize();
1754
this._sizeChanged = true;
1755
1756
if (this.options.maxBounds) {
1757
this.setMaxBounds(this.options.maxBounds);
1758
}
1759
1760
if (!this._loaded) { return this; }
1761
1762
var newSize = this.getSize(),
1763
offset = oldSize.subtract(newSize).divideBy(2).round();
1764
1765
if (!offset.x && !offset.y) { return this; }
1766
1767
if (options.animate && options.pan) {
1768
this.panBy(offset);
1769
1770
} else {
1771
if (options.pan) {
1772
this._rawPanBy(offset);
1773
}
1774
1775
this.fire('move');
1776
1777
// make sure moveend is not fired too often on resize
1778
clearTimeout(this._sizeTimer);
1779
this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
1780
}
1781
1782
return this.fire('resize', {
1783
oldSize: oldSize,
1784
newSize: newSize
1785
});
1786
},
1787
1788
// TODO handler.addTo
1789
addHandler: function (name, HandlerClass) {
1790
if (!HandlerClass) { return; }
1791
1792
var handler = this[name] = new HandlerClass(this);
1793
1794
this._handlers.push(handler);
1795
1796
if (this.options[name]) {
1797
handler.enable();
1798
}
1799
1800
return this;
1801
},
1802
1803
remove: function () {
1804
if (this._loaded) {
1805
this.fire('unload');
1806
}
1807
1808
this._initEvents('off');
1809
1810
delete this._container._leaflet;
1811
1812
this._clearPanes();
1813
if (this._clearControlPos) {
1814
this._clearControlPos();
1815
}
1816
1817
this._clearHandlers();
1818
1819
return this;
1820
},
1821
1822
1823
// public methods for getting map state
1824
1825
getCenter: function () { // (Boolean) -> LatLng
1826
this._checkIfLoaded();
1827
1828
if (!this._moved()) {
1829
return this._initialCenter;
1830
}
1831
return this.layerPointToLatLng(this._getCenterLayerPoint());
1832
},
1833
1834
getZoom: function () {
1835
return this._zoom;
1836
},
1837
1838
getBounds: function () {
1839
var bounds = this.getPixelBounds(),
1840
sw = this.unproject(bounds.getBottomLeft()),
1841
ne = this.unproject(bounds.getTopRight());
1842
1843
return new L.LatLngBounds(sw, ne);
1844
},
1845
1846
getMinZoom: function () {
1847
var z1 = this._layersMinZoom === undefined ? 0 : this._layersMinZoom,
1848
z2 = this._boundsMinZoom === undefined ? 0 : this._boundsMinZoom;
1849
return this.options.minZoom === undefined ? Math.max(z1, z2) : this.options.minZoom;
1850
},
1851
1852
getMaxZoom: function () {
1853
return this.options.maxZoom === undefined ?
1854
(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
1855
this.options.maxZoom;
1856
},
1857
1858
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
1859
bounds = L.latLngBounds(bounds);
1860
1861
var zoom = this.getMinZoom() - (inside ? 1 : 0),
1862
maxZoom = this.getMaxZoom(),
1863
size = this.getSize(),
1864
1865
nw = bounds.getNorthWest(),
1866
se = bounds.getSouthEast(),
1867
1868
zoomNotFound = true,
1869
boundsSize;
1870
1871
padding = L.point(padding || [0, 0]);
1872
1873
do {
1874
zoom++;
1875
boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
1876
zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
1877
1878
} while (zoomNotFound && zoom <= maxZoom);
1879
1880
if (zoomNotFound && inside) {
1881
return null;
1882
}
1883
1884
return inside ? zoom : zoom - 1;
1885
},
1886
1887
getSize: function () {
1888
if (!this._size || this._sizeChanged) {
1889
this._size = new L.Point(
1890
this._container.clientWidth,
1891
this._container.clientHeight);
1892
1893
this._sizeChanged = false;
1894
}
1895
return this._size.clone();
1896
},
1897
1898
getPixelBounds: function () {
1899
var topLeftPoint = this._getTopLeftPoint();
1900
return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
1901
},
1902
1903
getPixelOrigin: function () {
1904
this._checkIfLoaded();
1905
return this._initialTopLeftPoint;
1906
},
1907
1908
getPanes: function () {
1909
return this._panes;
1910
},
1911
1912
getContainer: function () {
1913
return this._container;
1914
},
1915
1916
1917
// TODO replace with universal implementation after refactoring projections
1918
1919
getZoomScale: function (toZoom) {
1920
var crs = this.options.crs;
1921
return crs.scale(toZoom) / crs.scale(this._zoom);
1922
},
1923
1924
getScaleZoom: function (scale) {
1925
return this._zoom + (Math.log(scale) / Math.LN2);
1926
},
1927
1928
1929
// conversion methods
1930
1931
project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
1932
zoom = zoom === undefined ? this._zoom : zoom;
1933
return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
1934
},
1935
1936
unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
1937
zoom = zoom === undefined ? this._zoom : zoom;
1938
return this.options.crs.pointToLatLng(L.point(point), zoom);
1939
},
1940
1941
layerPointToLatLng: function (point) { // (Point)
1942
var projectedPoint = L.point(point).add(this.getPixelOrigin());
1943
return this.unproject(projectedPoint);
1944
},
1945
1946
latLngToLayerPoint: function (latlng) { // (LatLng)
1947
var projectedPoint = this.project(L.latLng(latlng))._round();
1948
return projectedPoint._subtract(this.getPixelOrigin());
1949
},
1950
1951
containerPointToLayerPoint: function (point) { // (Point)
1952
return L.point(point).subtract(this._getMapPanePos());
1953
},
1954
1955
layerPointToContainerPoint: function (point) { // (Point)
1956
return L.point(point).add(this._getMapPanePos());
1957
},
1958
1959
containerPointToLatLng: function (point) {
1960
var layerPoint = this.containerPointToLayerPoint(L.point(point));
1961
return this.layerPointToLatLng(layerPoint);
1962
},
1963
1964
latLngToContainerPoint: function (latlng) {
1965
return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
1966
},
1967
1968
mouseEventToContainerPoint: function (e) { // (MouseEvent)
1969
return L.DomEvent.getMousePosition(e, this._container);
1970
},
1971
1972
mouseEventToLayerPoint: function (e) { // (MouseEvent)
1973
return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
1974
},
1975
1976
mouseEventToLatLng: function (e) { // (MouseEvent)
1977
return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
1978
},
1979
1980
1981
// map initialization methods
1982
1983
_initContainer: function (id) {
1984
var container = this._container = L.DomUtil.get(id);
1985
1986
if (!container) {
1987
throw new Error('Map container not found.');
1988
} else if (container._leaflet) {
1989
throw new Error('Map container is already initialized.');
1990
}
1991
1992
container._leaflet = true;
1993
},
1994
1995
_initLayout: function () {
1996
var container = this._container;
1997
1998
L.DomUtil.addClass(container, 'leaflet-container' +
1999
(L.Browser.touch ? ' leaflet-touch' : '') +
2000
(L.Browser.retina ? ' leaflet-retina' : '') +
2001
(this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
2002
2003
var position = L.DomUtil.getStyle(container, 'position');
2004
2005
if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2006
container.style.position = 'relative';
2007
}
2008
2009
this._initPanes();
2010
2011
if (this._initControlPos) {
2012
this._initControlPos();
2013
}
2014
},
2015
2016
_initPanes: function () {
2017
var panes = this._panes = {};
2018
2019
this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
2020
2021
this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
2022
panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
2023
panes.shadowPane = this._createPane('leaflet-shadow-pane');
2024
panes.overlayPane = this._createPane('leaflet-overlay-pane');
2025
panes.markerPane = this._createPane('leaflet-marker-pane');
2026
panes.popupPane = this._createPane('leaflet-popup-pane');
2027
2028
var zoomHide = ' leaflet-zoom-hide';
2029
2030
if (!this.options.markerZoomAnimation) {
2031
L.DomUtil.addClass(panes.markerPane, zoomHide);
2032
L.DomUtil.addClass(panes.shadowPane, zoomHide);
2033
L.DomUtil.addClass(panes.popupPane, zoomHide);
2034
}
2035
},
2036
2037
_createPane: function (className, container) {
2038
return L.DomUtil.create('div', className, container || this._panes.objectsPane);
2039
},
2040
2041
_clearPanes: function () {
2042
this._container.removeChild(this._mapPane);
2043
},
2044
2045
_addLayers: function (layers) {
2046
layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
2047
2048
for (var i = 0, len = layers.length; i < len; i++) {
2049
this.addLayer(layers[i]);
2050
}
2051
},
2052
2053
2054
// private methods that modify map state
2055
2056
_resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
2057
2058
var zoomChanged = (this._zoom !== zoom);
2059
2060
if (!afterZoomAnim) {
2061
this.fire('movestart');
2062
2063
if (zoomChanged) {
2064
this.fire('zoomstart');
2065
}
2066
}
2067
2068
this._zoom = zoom;
2069
this._initialCenter = center;
2070
2071
this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
2072
2073
if (!preserveMapOffset) {
2074
L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
2075
} else {
2076
this._initialTopLeftPoint._add(this._getMapPanePos());
2077
}
2078
2079
this._tileLayersToLoad = this._tileLayersNum;
2080
2081
var loading = !this._loaded;
2082
this._loaded = true;
2083
2084
if (loading) {
2085
this.fire('load');
2086
this.eachLayer(this._layerAdd, this);
2087
}
2088
2089
this.fire('viewreset', {hard: !preserveMapOffset});
2090
2091
this.fire('move');
2092
2093
if (zoomChanged || afterZoomAnim) {
2094
this.fire('zoomend');
2095
}
2096
2097
this.fire('moveend', {hard: !preserveMapOffset});
2098
},
2099
2100
_rawPanBy: function (offset) {
2101
L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
2102
},
2103
2104
_getZoomSpan: function () {
2105
return this.getMaxZoom() - this.getMinZoom();
2106
},
2107
2108
_updateZoomLevels: function () {
2109
var i,
2110
minZoom = Infinity,
2111
maxZoom = -Infinity,
2112
oldZoomSpan = this._getZoomSpan();
2113
2114
for (i in this._zoomBoundLayers) {
2115
var layer = this._zoomBoundLayers[i];
2116
if (!isNaN(layer.options.minZoom)) {
2117
minZoom = Math.min(minZoom, layer.options.minZoom);
2118
}
2119
if (!isNaN(layer.options.maxZoom)) {
2120
maxZoom = Math.max(maxZoom, layer.options.maxZoom);
2121
}
2122
}
2123
2124
if (i === undefined) { // we have no tilelayers
2125
this._layersMaxZoom = this._layersMinZoom = undefined;
2126
} else {
2127
this._layersMaxZoom = maxZoom;
2128
this._layersMinZoom = minZoom;
2129
}
2130
2131
if (oldZoomSpan !== this._getZoomSpan()) {
2132
this.fire('zoomlevelschange');
2133
}
2134
},
2135
2136
_panInsideMaxBounds: function () {
2137
this.panInsideBounds(this.options.maxBounds);
2138
},
2139
2140
_checkIfLoaded: function () {
2141
if (!this._loaded) {
2142
throw new Error('Set map center and zoom first.');
2143
}
2144
},
2145
2146
// map events
2147
2148
_initEvents: function (onOff) {
2149
if (!L.DomEvent) { return; }
2150
2151
onOff = onOff || 'on';
2152
2153
L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
2154
2155
var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
2156
'mouseleave', 'mousemove', 'contextmenu'],
2157
i, len;
2158
2159
for (i = 0, len = events.length; i < len; i++) {
2160
L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
2161
}
2162
2163
if (this.options.trackResize) {
2164
L.DomEvent[onOff](window, 'resize', this._onResize, this);
2165
}
2166
},
2167
2168
_onResize: function () {
2169
L.Util.cancelAnimFrame(this._resizeRequest);
2170
this._resizeRequest = L.Util.requestAnimFrame(
2171
this.invalidateSize, this, false, this._container);
2172
},
2173
2174
_onMouseClick: function (e) {
2175
if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) ||
2176
L.DomEvent._skipped(e)) { return; }
2177
2178
this.fire('preclick');
2179
this._fireMouseEvent(e);
2180
},
2181
2182
_fireMouseEvent: function (e) {
2183
if (!this._loaded || L.DomEvent._skipped(e)) { return; }
2184
2185
var type = e.type;
2186
2187
type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
2188
2189
if (!this.hasEventListeners(type)) { return; }
2190
2191
if (type === 'contextmenu') {
2192
L.DomEvent.preventDefault(e);
2193
}
2194
2195
var containerPoint = this.mouseEventToContainerPoint(e),
2196
layerPoint = this.containerPointToLayerPoint(containerPoint),
2197
latlng = this.layerPointToLatLng(layerPoint);
2198
2199
this.fire(type, {
2200
latlng: latlng,
2201
layerPoint: layerPoint,
2202
containerPoint: containerPoint,
2203
originalEvent: e
2204
});
2205
},
2206
2207
_onTileLayerLoad: function () {
2208
this._tileLayersToLoad--;
2209
if (this._tileLayersNum && !this._tileLayersToLoad) {
2210
this.fire('tilelayersload');
2211
}
2212
},
2213
2214
_clearHandlers: function () {
2215
for (var i = 0, len = this._handlers.length; i < len; i++) {
2216
this._handlers[i].disable();
2217
}
2218
},
2219
2220
whenReady: function (callback, context) {
2221
if (this._loaded) {
2222
callback.call(context || this, this);
2223
} else {
2224
this.on('load', callback, context);
2225
}
2226
return this;
2227
},
2228
2229
_layerAdd: function (layer) {
2230
layer.onAdd(this);
2231
this.fire('layeradd', {layer: layer});
2232
},
2233
2234
2235
// private methods for getting map state
2236
2237
_getMapPanePos: function () {
2238
return L.DomUtil.getPosition(this._mapPane);
2239
},
2240
2241
_moved: function () {
2242
var pos = this._getMapPanePos();
2243
return pos && !pos.equals([0, 0]);
2244
},
2245
2246
_getTopLeftPoint: function () {
2247
return this.getPixelOrigin().subtract(this._getMapPanePos());
2248
},
2249
2250
_getNewTopLeftPoint: function (center, zoom) {
2251
var viewHalf = this.getSize()._divideBy(2);
2252
// TODO round on display, not calculation to increase precision?
2253
return this.project(center, zoom)._subtract(viewHalf)._round();
2254
},
2255
2256
_latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
2257
var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
2258
return this.project(latlng, newZoom)._subtract(topLeft);
2259
},
2260
2261
// layer point of the current center
2262
_getCenterLayerPoint: function () {
2263
return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
2264
},
2265
2266
// offset of the specified place to the current center in pixels
2267
_getCenterOffset: function (latlng) {
2268
return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
2269
},
2270
2271
_limitZoom: function (zoom) {
2272
var min = this.getMinZoom(),
2273
max = this.getMaxZoom();
2274
2275
return Math.max(min, Math.min(max, zoom));
2276
}
2277
});
2278
2279
L.map = function (id, options) {
2280
return new L.Map(id, options);
2281
};
2282
2283
2284
/*
2285
* Mercator projection that takes into account that the Earth is not a perfect sphere.
2286
* Less popular than spherical mercator; used by projections like EPSG:3395.
2287
*/
2288
2289
L.Projection.Mercator = {
2290
MAX_LATITUDE: 85.0840591556,
2291
2292
R_MINOR: 6356752.314245179,
2293
R_MAJOR: 6378137,
2294
2295
project: function (latlng) { // (LatLng) -> Point
2296
var d = L.LatLng.DEG_TO_RAD,
2297
max = this.MAX_LATITUDE,
2298
lat = Math.max(Math.min(max, latlng.lat), -max),
2299
r = this.R_MAJOR,
2300
r2 = this.R_MINOR,
2301
x = latlng.lng * d * r,
2302
y = lat * d,
2303
tmp = r2 / r,
2304
eccent = Math.sqrt(1.0 - tmp * tmp),
2305
con = eccent * Math.sin(y);
2306
2307
con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
2308
2309
var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
2310
y = -r * Math.log(ts);
2311
2312
return new L.Point(x, y);
2313
},
2314
2315
unproject: function (point) { // (Point, Boolean) -> LatLng
2316
var d = L.LatLng.RAD_TO_DEG,
2317
r = this.R_MAJOR,
2318
r2 = this.R_MINOR,
2319
lng = point.x * d / r,
2320
tmp = r2 / r,
2321
eccent = Math.sqrt(1 - (tmp * tmp)),
2322
ts = Math.exp(- point.y / r),
2323
phi = (Math.PI / 2) - 2 * Math.atan(ts),
2324
numIter = 15,
2325
tol = 1e-7,
2326
i = numIter,
2327
dphi = 0.1,
2328
con;
2329
2330
while ((Math.abs(dphi) > tol) && (--i > 0)) {
2331
con = eccent * Math.sin(phi);
2332
dphi = (Math.PI / 2) - 2 * Math.atan(ts *
2333
Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
2334
phi += dphi;
2335
}
2336
2337
return new L.LatLng(phi * d, lng);
2338
}
2339
};
2340
2341
2342
2343
L.CRS.EPSG3395 = L.extend({}, L.CRS, {
2344
code: 'EPSG:3395',
2345
2346
projection: L.Projection.Mercator,
2347
2348
transformation: (function () {
2349
var m = L.Projection.Mercator,
2350
r = m.R_MAJOR,
2351
r2 = m.R_MINOR;
2352
2353
return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5);
2354
}())
2355
});
2356
2357
2358
/*
2359
* L.TileLayer is used for standard xyz-numbered tile layers.
2360
*/
2361
2362
L.TileLayer = L.Class.extend({
2363
includes: L.Mixin.Events,
2364
2365
options: {
2366
minZoom: 0,
2367
maxZoom: 18,
2368
tileSize: 256,
2369
subdomains: 'abc',
2370
errorTileUrl: '',
2371
attribution: '',
2372
zoomOffset: 0,
2373
opacity: 1,
2374
/* (undefined works too)
2375
zIndex: null,
2376
tms: false,
2377
continuousWorld: false,
2378
noWrap: false,
2379
zoomReverse: false,
2380
detectRetina: false,
2381
reuseTiles: false,
2382
bounds: false,
2383
*/
2384
unloadInvisibleTiles: L.Browser.mobile,
2385
updateWhenIdle: L.Browser.mobile
2386
},
2387
2388
initialize: function (url, options) {
2389
options = L.setOptions(this, options);
2390
2391
// detecting retina displays, adjusting tileSize and zoom levels
2392
if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
2393
2394
options.tileSize = Math.floor(options.tileSize / 2);
2395
options.zoomOffset++;
2396
2397
if (options.minZoom > 0) {
2398
options.minZoom--;
2399
}
2400
this.options.maxZoom--;
2401
}
2402
2403
if (options.bounds) {
2404
options.bounds = L.latLngBounds(options.bounds);
2405
}
2406
2407
this._url = url;
2408
2409
var subdomains = this.options.subdomains;
2410
2411
if (typeof subdomains === 'string') {
2412
this.options.subdomains = subdomains.split('');
2413
}
2414
},
2415
2416
onAdd: function (map) {
2417
this._map = map;
2418
this._animated = map._zoomAnimated;
2419
2420
// create a container div for tiles
2421
this._initContainer();
2422
2423
// create an image to clone for tiles
2424
this._createTileProto();
2425
2426
// set up events
2427
map.on({
2428
'viewreset': this._reset,
2429
'moveend': this._update
2430
}, this);
2431
2432
if (this._animated) {
2433
map.on({
2434
'zoomanim': this._animateZoom,
2435
'zoomend': this._endZoomAnim
2436
}, this);
2437
}
2438
2439
if (!this.options.updateWhenIdle) {
2440
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
2441
map.on('move', this._limitedUpdate, this);
2442
}
2443
2444
this._reset();
2445
this._update();
2446
},
2447
2448
addTo: function (map) {
2449
map.addLayer(this);
2450
return this;
2451
},
2452
2453
onRemove: function (map) {
2454
this._container.parentNode.removeChild(this._container);
2455
2456
map.off({
2457
'viewreset': this._reset,
2458
'moveend': this._update
2459
}, this);
2460
2461
if (this._animated) {
2462
map.off({
2463
'zoomanim': this._animateZoom,
2464
'zoomend': this._endZoomAnim
2465
}, this);
2466
}
2467
2468
if (!this.options.updateWhenIdle) {
2469
map.off('move', this._limitedUpdate, this);
2470
}
2471
2472
this._container = null;
2473
this._map = null;
2474
},
2475
2476
bringToFront: function () {
2477
var pane = this._map._panes.tilePane;
2478
2479
if (this._container) {
2480
pane.appendChild(this._container);
2481
this._setAutoZIndex(pane, Math.max);
2482
}
2483
2484
return this;
2485
},
2486
2487
bringToBack: function () {
2488
var pane = this._map._panes.tilePane;
2489
2490
if (this._container) {
2491
pane.insertBefore(this._container, pane.firstChild);
2492
this._setAutoZIndex(pane, Math.min);
2493
}
2494
2495
return this;
2496
},
2497
2498
getAttribution: function () {
2499
return this.options.attribution;
2500
},
2501
2502
getContainer: function () {
2503
return this._container;
2504
},
2505
2506
setOpacity: function (opacity) {
2507
this.options.opacity = opacity;
2508
2509
if (this._map) {
2510
this._updateOpacity();
2511
}
2512
2513
return this;
2514
},
2515
2516
setZIndex: function (zIndex) {
2517
this.options.zIndex = zIndex;
2518
this._updateZIndex();
2519
2520
return this;
2521
},
2522
2523
setUrl: function (url, noRedraw) {
2524
this._url = url;
2525
2526
if (!noRedraw) {
2527
this.redraw();
2528
}
2529
2530
return this;
2531
},
2532
2533
redraw: function () {
2534
if (this._map) {
2535
this._reset({hard: true});
2536
this._update();
2537
}
2538
return this;
2539
},
2540
2541
_updateZIndex: function () {
2542
if (this._container && this.options.zIndex !== undefined) {
2543
this._container.style.zIndex = this.options.zIndex;
2544
}
2545
},
2546
2547
_setAutoZIndex: function (pane, compare) {
2548
2549
var layers = pane.children,
2550
edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
2551
zIndex, i, len;
2552
2553
for (i = 0, len = layers.length; i < len; i++) {
2554
2555
if (layers[i] !== this._container) {
2556
zIndex = parseInt(layers[i].style.zIndex, 10);
2557
2558
if (!isNaN(zIndex)) {
2559
edgeZIndex = compare(edgeZIndex, zIndex);
2560
}
2561
}
2562
}
2563
2564
this.options.zIndex = this._container.style.zIndex =
2565
(isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
2566
},
2567
2568
_updateOpacity: function () {
2569
var i,
2570
tiles = this._tiles;
2571
2572
if (L.Browser.ielt9) {
2573
for (i in tiles) {
2574
L.DomUtil.setOpacity(tiles[i], this.options.opacity);
2575
}
2576
} else {
2577
L.DomUtil.setOpacity(this._container, this.options.opacity);
2578
}
2579
},
2580
2581
_initContainer: function () {
2582
var tilePane = this._map._panes.tilePane;
2583
2584
if (!this._container) {
2585
this._container = L.DomUtil.create('div', 'leaflet-layer');
2586
2587
this._updateZIndex();
2588
2589
if (this._animated) {
2590
var className = 'leaflet-tile-container leaflet-zoom-animated';
2591
2592
this._bgBuffer = L.DomUtil.create('div', className, this._container);
2593
this._tileContainer = L.DomUtil.create('div', className, this._container);
2594
2595
} else {
2596
this._tileContainer = this._container;
2597
}
2598
2599
tilePane.appendChild(this._container);
2600
2601
if (this.options.opacity < 1) {
2602
this._updateOpacity();
2603
}
2604
}
2605
},
2606
2607
_reset: function (e) {
2608
for (var key in this._tiles) {
2609
this.fire('tileunload', {tile: this._tiles[key]});
2610
}
2611
2612
this._tiles = {};
2613
this._tilesToLoad = 0;
2614
2615
if (this.options.reuseTiles) {
2616
this._unusedTiles = [];
2617
}
2618
2619
this._tileContainer.innerHTML = '';
2620
2621
if (this._animated && e && e.hard) {
2622
this._clearBgBuffer();
2623
}
2624
2625
this._initContainer();
2626
},
2627
2628
_update: function () {
2629
2630
if (!this._map) { return; }
2631
2632
var bounds = this._map.getPixelBounds(),
2633
zoom = this._map.getZoom(),
2634
tileSize = this.options.tileSize;
2635
2636
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
2637
return;
2638
}
2639
2640
var tileBounds = L.bounds(
2641
bounds.min.divideBy(tileSize)._floor(),
2642
bounds.max.divideBy(tileSize)._floor());
2643
2644
this._addTilesFromCenterOut(tileBounds);
2645
2646
if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
2647
this._removeOtherTiles(tileBounds);
2648
}
2649
},
2650
2651
_addTilesFromCenterOut: function (bounds) {
2652
var queue = [],
2653
center = bounds.getCenter();
2654
2655
var j, i, point;
2656
2657
for (j = bounds.min.y; j <= bounds.max.y; j++) {
2658
for (i = bounds.min.x; i <= bounds.max.x; i++) {
2659
point = new L.Point(i, j);
2660
2661
if (this._tileShouldBeLoaded(point)) {
2662
queue.push(point);
2663
}
2664
}
2665
}
2666
2667
var tilesToLoad = queue.length;
2668
2669
if (tilesToLoad === 0) { return; }
2670
2671
// load tiles in order of their distance to center
2672
queue.sort(function (a, b) {
2673
return a.distanceTo(center) - b.distanceTo(center);
2674
});
2675
2676
var fragment = document.createDocumentFragment();
2677
2678
// if its the first batch of tiles to load
2679
if (!this._tilesToLoad) {
2680
this.fire('loading');
2681
}
2682
2683
this._tilesToLoad += tilesToLoad;
2684
2685
for (i = 0; i < tilesToLoad; i++) {
2686
this._addTile(queue[i], fragment);
2687
}
2688
2689
this._tileContainer.appendChild(fragment);
2690
},
2691
2692
_tileShouldBeLoaded: function (tilePoint) {
2693
if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
2694
return false; // already loaded
2695
}
2696
2697
var options = this.options;
2698
2699
if (!options.continuousWorld) {
2700
var limit = this._getWrapTileNum();
2701
2702
// don't load if exceeds world bounds
2703
if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) ||
2704
tilePoint.y < 0 || tilePoint.y >= limit) { return false; }
2705
}
2706
2707
if (options.bounds) {
2708
var tileSize = options.tileSize,
2709
nwPoint = tilePoint.multiplyBy(tileSize),
2710
sePoint = nwPoint.add([tileSize, tileSize]),
2711
nw = this._map.unproject(nwPoint),
2712
se = this._map.unproject(sePoint);
2713
2714
// TODO temporary hack, will be removed after refactoring projections
2715
// https://github.com/Leaflet/Leaflet/issues/1618
2716
if (!options.continuousWorld && !options.noWrap) {
2717
nw = nw.wrap();
2718
se = se.wrap();
2719
}
2720
2721
if (!options.bounds.intersects([nw, se])) { return false; }
2722
}
2723
2724
return true;
2725
},
2726
2727
_removeOtherTiles: function (bounds) {
2728
var kArr, x, y, key;
2729
2730
for (key in this._tiles) {
2731
kArr = key.split(':');
2732
x = parseInt(kArr[0], 10);
2733
y = parseInt(kArr[1], 10);
2734
2735
// remove tile if it's out of bounds
2736
if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
2737
this._removeTile(key);
2738
}
2739
}
2740
},
2741
2742
_removeTile: function (key) {
2743
var tile = this._tiles[key];
2744
2745
this.fire('tileunload', {tile: tile, url: tile.src});
2746
2747
if (this.options.reuseTiles) {
2748
L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
2749
this._unusedTiles.push(tile);
2750
2751
} else if (tile.parentNode === this._tileContainer) {
2752
this._tileContainer.removeChild(tile);
2753
}
2754
2755
// for https://github.com/CloudMade/Leaflet/issues/137
2756
if (!L.Browser.android) {
2757
tile.onload = null;
2758
tile.src = L.Util.emptyImageUrl;
2759
}
2760
2761
delete this._tiles[key];
2762
},
2763
2764
_addTile: function (tilePoint, container) {
2765
var tilePos = this._getTilePos(tilePoint);
2766
2767
// get unused tile - or create a new tile
2768
var tile = this._getTile();
2769
2770
/*
2771
Chrome 20 layouts much faster with top/left (verify with timeline, frames)
2772
Android 4 browser has display issues with top/left and requires transform instead
2773
Android 2 browser requires top/left or tiles disappear on load or first drag
2774
(reappear after zoom) https://github.com/CloudMade/Leaflet/issues/866
2775
(other browsers don't currently care) - see debug/hacks/jitter.html for an example
2776
*/
2777
L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome || L.Browser.android23);
2778
2779
this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
2780
2781
this._loadTile(tile, tilePoint);
2782
2783
if (tile.parentNode !== this._tileContainer) {
2784
container.appendChild(tile);
2785
}
2786
},
2787
2788
_getZoomForUrl: function () {
2789
2790
var options = this.options,
2791
zoom = this._map.getZoom();
2792
2793
if (options.zoomReverse) {
2794
zoom = options.maxZoom - zoom;
2795
}
2796
2797
return zoom + options.zoomOffset;
2798
},
2799
2800
_getTilePos: function (tilePoint) {
2801
var origin = this._map.getPixelOrigin(),
2802
tileSize = this.options.tileSize;
2803
2804
return tilePoint.multiplyBy(tileSize).subtract(origin);
2805
},
2806
2807
// image-specific code (override to implement e.g. Canvas or SVG tile layer)
2808
2809
getTileUrl: function (tilePoint) {
2810
return L.Util.template(this._url, L.extend({
2811
s: this._getSubdomain(tilePoint),
2812
z: tilePoint.z,
2813
x: tilePoint.x,
2814
y: tilePoint.y
2815
}, this.options));
2816
},
2817
2818
_getWrapTileNum: function () {
2819
// TODO refactor, limit is not valid for non-standard projections
2820
return Math.pow(2, this._getZoomForUrl());
2821
},
2822
2823
_adjustTilePoint: function (tilePoint) {
2824
2825
var limit = this._getWrapTileNum();
2826
2827
// wrap tile coordinates
2828
if (!this.options.continuousWorld && !this.options.noWrap) {
2829
tilePoint.x = ((tilePoint.x % limit) + limit) % limit;
2830
}
2831
2832
if (this.options.tms) {
2833
tilePoint.y = limit - tilePoint.y - 1;
2834
}
2835
2836
tilePoint.z = this._getZoomForUrl();
2837
},
2838
2839
_getSubdomain: function (tilePoint) {
2840
var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
2841
return this.options.subdomains[index];
2842
},
2843
2844
_createTileProto: function () {
2845
var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
2846
img.style.width = img.style.height = this.options.tileSize + 'px';
2847
img.galleryimg = 'no';
2848
},
2849
2850
_getTile: function () {
2851
if (this.options.reuseTiles && this._unusedTiles.length > 0) {
2852
var tile = this._unusedTiles.pop();
2853
this._resetTile(tile);
2854
return tile;
2855
}
2856
return this._createTile();
2857
},
2858
2859
// Override if data stored on a tile needs to be cleaned up before reuse
2860
_resetTile: function (/*tile*/) {},
2861
2862
_createTile: function () {
2863
var tile = this._tileImg.cloneNode(false);
2864
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
2865
2866
if (L.Browser.ielt9 && this.options.opacity !== undefined) {
2867
L.DomUtil.setOpacity(tile, this.options.opacity);
2868
}
2869
return tile;
2870
},
2871
2872
_loadTile: function (tile, tilePoint) {
2873
tile._layer = this;
2874
tile.onload = this._tileOnLoad;
2875
tile.onerror = this._tileOnError;
2876
2877
this._adjustTilePoint(tilePoint);
2878
tile.src = this.getTileUrl(tilePoint);
2879
},
2880
2881
_tileLoaded: function () {
2882
this._tilesToLoad--;
2883
if (!this._tilesToLoad) {
2884
this.fire('load');
2885
2886
if (this._animated) {
2887
// clear scaled tiles after all new tiles are loaded (for performance)
2888
clearTimeout(this._clearBgBufferTimer);
2889
this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
2890
}
2891
}
2892
},
2893
2894
_tileOnLoad: function () {
2895
var layer = this._layer;
2896
2897
//Only if we are loading an actual image
2898
if (this.src !== L.Util.emptyImageUrl) {
2899
L.DomUtil.addClass(this, 'leaflet-tile-loaded');
2900
2901
layer.fire('tileload', {
2902
tile: this,
2903
url: this.src
2904
});
2905
}
2906
2907
layer._tileLoaded();
2908
},
2909
2910
_tileOnError: function () {
2911
var layer = this._layer;
2912
2913
layer.fire('tileerror', {
2914
tile: this,
2915
url: this.src
2916
});
2917
2918
var newUrl = layer.options.errorTileUrl;
2919
if (newUrl) {
2920
this.src = newUrl;
2921
}
2922
2923
layer._tileLoaded();
2924
}
2925
});
2926
2927
L.tileLayer = function (url, options) {
2928
return new L.TileLayer(url, options);
2929
};
2930
2931
2932
/*
2933
* L.TileLayer.WMS is used for putting WMS tile layers on the map.
2934
*/
2935
2936
L.TileLayer.WMS = L.TileLayer.extend({
2937
2938
defaultWmsParams: {
2939
service: 'WMS',
2940
request: 'GetMap',
2941
version: '1.1.1',
2942
layers: '',
2943
styles: '',
2944
format: 'image/jpeg',
2945
transparent: false
2946
},
2947
2948
initialize: function (url, options) { // (String, Object)
2949
2950
this._url = url;
2951
2952
var wmsParams = L.extend({}, this.defaultWmsParams),
2953
tileSize = options.tileSize || this.options.tileSize;
2954
2955
if (options.detectRetina && L.Browser.retina) {
2956
wmsParams.width = wmsParams.height = tileSize * 2;
2957
} else {
2958
wmsParams.width = wmsParams.height = tileSize;
2959
}
2960
2961
for (var i in options) {
2962
// all keys that are not TileLayer options go to WMS params
2963
if (!this.options.hasOwnProperty(i) && i !== 'crs') {
2964
wmsParams[i] = options[i];
2965
}
2966
}
2967
2968
this.wmsParams = wmsParams;
2969
2970
L.setOptions(this, options);
2971
},
2972
2973
onAdd: function (map) {
2974
2975
this._crs = this.options.crs || map.options.crs;
2976
2977
var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
2978
this.wmsParams[projectionKey] = this._crs.code;
2979
2980
L.TileLayer.prototype.onAdd.call(this, map);
2981
},
2982
2983
getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
2984
2985
var map = this._map,
2986
tileSize = this.options.tileSize,
2987
2988
nwPoint = tilePoint.multiplyBy(tileSize),
2989
sePoint = nwPoint.add([tileSize, tileSize]),
2990
2991
nw = this._crs.project(map.unproject(nwPoint, zoom)),
2992
se = this._crs.project(map.unproject(sePoint, zoom)),
2993
2994
bbox = [nw.x, se.y, se.x, nw.y].join(','),
2995
2996
url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
2997
2998
return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;
2999
},
3000
3001
setParams: function (params, noRedraw) {
3002
3003
L.extend(this.wmsParams, params);
3004
3005
if (!noRedraw) {
3006
this.redraw();
3007
}
3008
3009
return this;
3010
}
3011
});
3012
3013
L.tileLayer.wms = function (url, options) {
3014
return new L.TileLayer.WMS(url, options);
3015
};
3016
3017
3018
/*
3019
* L.TileLayer.Canvas is a class that you can use as a base for creating
3020
* dynamically drawn Canvas-based tile layers.
3021
*/
3022
3023
L.TileLayer.Canvas = L.TileLayer.extend({
3024
options: {
3025
async: false
3026
},
3027
3028
initialize: function (options) {
3029
L.setOptions(this, options);
3030
},
3031
3032
redraw: function () {
3033
if (this._map) {
3034
this._reset({hard: true});
3035
this._update();
3036
}
3037
3038
for (var i in this._tiles) {
3039
this._redrawTile(this._tiles[i]);
3040
}
3041
return this;
3042
},
3043
3044
_redrawTile: function (tile) {
3045
this.drawTile(tile, tile._tilePoint, this._map._zoom);
3046
},
3047
3048
_createTileProto: function () {
3049
var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
3050
proto.width = proto.height = this.options.tileSize;
3051
},
3052
3053
_createTile: function () {
3054
var tile = this._canvasProto.cloneNode(false);
3055
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
3056
return tile;
3057
},
3058
3059
_loadTile: function (tile, tilePoint) {
3060
tile._layer = this;
3061
tile._tilePoint = tilePoint;
3062
3063
this._redrawTile(tile);
3064
3065
if (!this.options.async) {
3066
this.tileDrawn(tile);
3067
}
3068
},
3069
3070
drawTile: function (/*tile, tilePoint*/) {
3071
// override with rendering code
3072
},
3073
3074
tileDrawn: function (tile) {
3075
this._tileOnLoad.call(tile);
3076
}
3077
});
3078
3079
3080
L.tileLayer.canvas = function (options) {
3081
return new L.TileLayer.Canvas(options);
3082
};
3083
3084
3085
/*
3086
* L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
3087
*/
3088
3089
L.ImageOverlay = L.Class.extend({
3090
includes: L.Mixin.Events,
3091
3092
options: {
3093
opacity: 1
3094
},
3095
3096
initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
3097
this._url = url;
3098
this._bounds = L.latLngBounds(bounds);
3099
3100
L.setOptions(this, options);
3101
},
3102
3103
onAdd: function (map) {
3104
this._map = map;
3105
3106
if (!this._image) {
3107
this._initImage();
3108
}
3109
3110
map._panes.overlayPane.appendChild(this._image);
3111
3112
map.on('viewreset', this._reset, this);
3113
3114
if (map.options.zoomAnimation && L.Browser.any3d) {
3115
map.on('zoomanim', this._animateZoom, this);
3116
}
3117
3118
this._reset();
3119
},
3120
3121
onRemove: function (map) {
3122
map.getPanes().overlayPane.removeChild(this._image);
3123
3124
map.off('viewreset', this._reset, this);
3125
3126
if (map.options.zoomAnimation) {
3127
map.off('zoomanim', this._animateZoom, this);
3128
}
3129
},
3130
3131
addTo: function (map) {
3132
map.addLayer(this);
3133
return this;
3134
},
3135
3136
setOpacity: function (opacity) {
3137
this.options.opacity = opacity;
3138
this._updateOpacity();
3139
return this;
3140
},
3141
3142
// TODO remove bringToFront/bringToBack duplication from TileLayer/Path
3143
bringToFront: function () {
3144
if (this._image) {
3145
this._map._panes.overlayPane.appendChild(this._image);
3146
}
3147
return this;
3148
},
3149
3150
bringToBack: function () {
3151
var pane = this._map._panes.overlayPane;
3152
if (this._image) {
3153
pane.insertBefore(this._image, pane.firstChild);
3154
}
3155
return this;
3156
},
3157
3158
_initImage: function () {
3159
this._image = L.DomUtil.create('img', 'leaflet-image-layer');
3160
3161
if (this._map.options.zoomAnimation && L.Browser.any3d) {
3162
L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
3163
} else {
3164
L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
3165
}
3166
3167
this._updateOpacity();
3168
3169
//TODO createImage util method to remove duplication
3170
L.extend(this._image, {
3171
galleryimg: 'no',
3172
onselectstart: L.Util.falseFn,
3173
onmousemove: L.Util.falseFn,
3174
onload: L.bind(this._onImageLoad, this),
3175
src: this._url
3176
});
3177
},
3178
3179
_animateZoom: function (e) {
3180
var map = this._map,
3181
image = this._image,
3182
scale = map.getZoomScale(e.zoom),
3183
nw = this._bounds.getNorthWest(),
3184
se = this._bounds.getSouthEast(),
3185
3186
topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
3187
size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
3188
origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
3189
3190
image.style[L.DomUtil.TRANSFORM] =
3191
L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
3192
},
3193
3194
_reset: function () {
3195
var image = this._image,
3196
topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
3197
size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
3198
3199
L.DomUtil.setPosition(image, topLeft);
3200
3201
image.style.width = size.x + 'px';
3202
image.style.height = size.y + 'px';
3203
},
3204
3205
_onImageLoad: function () {
3206
this.fire('load');
3207
},
3208
3209
_updateOpacity: function () {
3210
L.DomUtil.setOpacity(this._image, this.options.opacity);
3211
}
3212
});
3213
3214
L.imageOverlay = function (url, bounds, options) {
3215
return new L.ImageOverlay(url, bounds, options);
3216
};
3217
3218
3219
/*
3220
* L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
3221
*/
3222
3223
L.Icon = L.Class.extend({
3224
options: {
3225
/*
3226
iconUrl: (String) (required)
3227
iconRetinaUrl: (String) (optional, used for retina devices if detected)
3228
iconSize: (Point) (can be set through CSS)
3229
iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
3230
popupAnchor: (Point) (if not specified, popup opens in the anchor point)
3231
shadowUrl: (String) (no shadow by default)
3232
shadowRetinaUrl: (String) (optional, used for retina devices if detected)
3233
shadowSize: (Point)
3234
shadowAnchor: (Point)
3235
*/
3236
className: ''
3237
},
3238
3239
initialize: function (options) {
3240
L.setOptions(this, options);
3241
},
3242
3243
createIcon: function (oldIcon) {
3244
return this._createIcon('icon', oldIcon);
3245
},
3246
3247
createShadow: function (oldIcon) {
3248
return this._createIcon('shadow', oldIcon);
3249
},
3250
3251
_createIcon: function (name, oldIcon) {
3252
var src = this._getIconUrl(name);
3253
3254
if (!src) {
3255
if (name === 'icon') {
3256
throw new Error('iconUrl not set in Icon options (see the docs).');
3257
}
3258
return null;
3259
}
3260
3261
var img;
3262
if (!oldIcon || oldIcon.tagName !== 'IMG') {
3263
img = this._createImg(src);
3264
} else {
3265
img = this._createImg(src, oldIcon);
3266
}
3267
this._setIconStyles(img, name);
3268
3269
return img;
3270
},
3271
3272
_setIconStyles: function (img, name) {
3273
var options = this.options,
3274
size = L.point(options[name + 'Size']),
3275
anchor;
3276
3277
if (name === 'shadow') {
3278
anchor = L.point(options.shadowAnchor || options.iconAnchor);
3279
} else {
3280
anchor = L.point(options.iconAnchor);
3281
}
3282
3283
if (!anchor && size) {
3284
anchor = size.divideBy(2, true);
3285
}
3286
3287
img.className = 'leaflet-marker-' + name + ' ' + options.className;
3288
3289
if (anchor) {
3290
img.style.marginLeft = (-anchor.x) + 'px';
3291
img.style.marginTop = (-anchor.y) + 'px';
3292
}
3293
3294
if (size) {
3295
img.style.width = size.x + 'px';
3296
img.style.height = size.y + 'px';
3297
}
3298
},
3299
3300
_createImg: function (src, el) {
3301
3302
if (!L.Browser.ie6) {
3303
if (!el) {
3304
el = document.createElement('img');
3305
}
3306
el.src = src;
3307
} else {
3308
if (!el) {
3309
el = document.createElement('div');
3310
}
3311
el.style.filter =
3312
'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
3313
}
3314
return el;
3315
},
3316
3317
_getIconUrl: function (name) {
3318
if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
3319
return this.options[name + 'RetinaUrl'];
3320
}
3321
return this.options[name + 'Url'];
3322
}
3323
});
3324
3325
L.icon = function (options) {
3326
return new L.Icon(options);
3327
};
3328
3329
3330
/*
3331
* L.Icon.Default is the blue marker icon used by default in Leaflet.
3332
*/
3333
3334
L.Icon.Default = L.Icon.extend({
3335
3336
options: {
3337
iconSize: [25, 41],
3338
iconAnchor: [12, 41],
3339
popupAnchor: [1, -34],
3340
3341
shadowSize: [41, 41]
3342
},
3343
3344
_getIconUrl: function (name) {
3345
var key = name + 'Url';
3346
3347
if (this.options[key]) {
3348
return this.options[key];
3349
}
3350
3351
if (L.Browser.retina && name === 'icon') {
3352
name += '-2x';
3353
}
3354
3355
var path = L.Icon.Default.imagePath;
3356
3357
if (!path) {
3358
throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
3359
}
3360
3361
return path + '/marker-' + name + '.png';
3362
}
3363
});
3364
3365
L.Icon.Default.imagePath = (function () {
3366
var scripts = document.getElementsByTagName('script'),
3367
leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
3368
3369
var i, len, src, matches, path;
3370
3371
for (i = 0, len = scripts.length; i < len; i++) {
3372
src = scripts[i].src;
3373
matches = src.match(leafletRe);
3374
3375
if (matches) {
3376
path = src.split(leafletRe)[0];
3377
return (path ? path + '/' : '') + 'images';
3378
}
3379
}
3380
}());
3381
3382
3383
/*
3384
* L.Marker is used to display clickable/draggable icons on the map.
3385
*/
3386
3387
L.Marker = L.Class.extend({
3388
3389
includes: L.Mixin.Events,
3390
3391
options: {
3392
icon: new L.Icon.Default(),
3393
title: '',
3394
clickable: true,
3395
draggable: false,
3396
keyboard: true,
3397
zIndexOffset: 0,
3398
opacity: 1,
3399
riseOnHover: false,
3400
riseOffset: 250
3401
},
3402
3403
initialize: function (latlng, options) {
3404
L.setOptions(this, options);
3405
this._latlng = L.latLng(latlng);
3406
},
3407
3408
onAdd: function (map) {
3409
this._map = map;
3410
3411
map.on('viewreset', this.update, this);
3412
3413
this._initIcon();
3414
this.update();
3415
3416
if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
3417
map.on('zoomanim', this._animateZoom, this);
3418
}
3419
},
3420
3421
addTo: function (map) {
3422
map.addLayer(this);
3423
return this;
3424
},
3425
3426
onRemove: function (map) {
3427
if (this.dragging) {
3428
this.dragging.disable();
3429
}
3430
3431
this._removeIcon();
3432
this._removeShadow();
3433
3434
this.fire('remove');
3435
3436
map.off({
3437
'viewreset': this.update,
3438
'zoomanim': this._animateZoom
3439
}, this);
3440
3441
this._map = null;
3442
},
3443
3444
getLatLng: function () {
3445
return this._latlng;
3446
},
3447
3448
setLatLng: function (latlng) {
3449
this._latlng = L.latLng(latlng);
3450
3451
this.update();
3452
3453
return this.fire('move', { latlng: this._latlng });
3454
},
3455
3456
setZIndexOffset: function (offset) {
3457
this.options.zIndexOffset = offset;
3458
this.update();
3459
3460
return this;
3461
},
3462
3463
setIcon: function (icon) {
3464
3465
this.options.icon = icon;
3466
3467
if (this._map) {
3468
this._initIcon();
3469
this.update();
3470
}
3471
3472
return this;
3473
},
3474
3475
update: function () {
3476
if (this._icon) {
3477
var pos = this._map.latLngToLayerPoint(this._latlng).round();
3478
this._setPos(pos);
3479
}
3480
3481
return this;
3482
},
3483
3484
_initIcon: function () {
3485
var options = this.options,
3486
map = this._map,
3487
animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
3488
classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
3489
3490
var icon = options.icon.createIcon(this._icon),
3491
addIcon = false;
3492
3493
// if we're not reusing the icon, remove the old one and init new one
3494
if (icon !== this._icon) {
3495
if (this._icon) {
3496
this._removeIcon();
3497
}
3498
addIcon = true;
3499
3500
if (options.title) {
3501
icon.title = options.title;
3502
}
3503
}
3504
3505
L.DomUtil.addClass(icon, classToAdd);
3506
3507
if (options.keyboard) {
3508
icon.tabIndex = '0';
3509
}
3510
3511
this._icon = icon;
3512
3513
this._initInteraction();
3514
3515
if (options.riseOnHover) {
3516
L.DomEvent
3517
.on(icon, 'mouseover', this._bringToFront, this)
3518
.on(icon, 'mouseout', this._resetZIndex, this);
3519
}
3520
3521
var newShadow = options.icon.createShadow(this._shadow),
3522
addShadow = false;
3523
3524
if (newShadow !== this._shadow) {
3525
this._removeShadow();
3526
addShadow = true;
3527
}
3528
3529
if (newShadow) {
3530
L.DomUtil.addClass(newShadow, classToAdd);
3531
}
3532
this._shadow = newShadow;
3533
3534
3535
if (options.opacity < 1) {
3536
this._updateOpacity();
3537
}
3538
3539
3540
var panes = this._map._panes;
3541
3542
if (addIcon) {
3543
panes.markerPane.appendChild(this._icon);
3544
}
3545
3546
if (newShadow && addShadow) {
3547
panes.shadowPane.appendChild(this._shadow);
3548
}
3549
},
3550
3551
_removeIcon: function () {
3552
if (this.options.riseOnHover) {
3553
L.DomEvent
3554
.off(this._icon, 'mouseover', this._bringToFront)
3555
.off(this._icon, 'mouseout', this._resetZIndex);
3556
}
3557
3558
this._map._panes.markerPane.removeChild(this._icon);
3559
3560
this._icon = null;
3561
},
3562
3563
_removeShadow: function () {
3564
if (this._shadow) {
3565
this._map._panes.shadowPane.removeChild(this._shadow);
3566
}
3567
this._shadow = null;
3568
},
3569
3570
_setPos: function (pos) {
3571
L.DomUtil.setPosition(this._icon, pos);
3572
3573
if (this._shadow) {
3574
L.DomUtil.setPosition(this._shadow, pos);
3575
}
3576
3577
this._zIndex = pos.y + this.options.zIndexOffset;
3578
3579
this._resetZIndex();
3580
},
3581
3582
_updateZIndex: function (offset) {
3583
this._icon.style.zIndex = this._zIndex + offset;
3584
},
3585
3586
_animateZoom: function (opt) {
3587
var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3588
3589
this._setPos(pos);
3590
},
3591
3592
_initInteraction: function () {
3593
3594
if (!this.options.clickable) { return; }
3595
3596
// TODO refactor into something shared with Map/Path/etc. to DRY it up
3597
3598
var icon = this._icon,
3599
events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
3600
3601
L.DomUtil.addClass(icon, 'leaflet-clickable');
3602
L.DomEvent.on(icon, 'click', this._onMouseClick, this);
3603
L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
3604
3605
for (var i = 0; i < events.length; i++) {
3606
L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
3607
}
3608
3609
if (L.Handler.MarkerDrag) {
3610
this.dragging = new L.Handler.MarkerDrag(this);
3611
3612
if (this.options.draggable) {
3613
this.dragging.enable();
3614
}
3615
}
3616
},
3617
3618
_onMouseClick: function (e) {
3619
var wasDragged = this.dragging && this.dragging.moved();
3620
3621
if (this.hasEventListeners(e.type) || wasDragged) {
3622
L.DomEvent.stopPropagation(e);
3623
}
3624
3625
if (wasDragged) { return; }
3626
3627
if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
3628
3629
this.fire(e.type, {
3630
originalEvent: e,
3631
latlng: this._latlng
3632
});
3633
},
3634
3635
_onKeyPress: function (e) {
3636
if (e.keyCode === 13) {
3637
this.fire('click', {
3638
originalEvent: e,
3639
latlng: this._latlng
3640
});
3641
}
3642
},
3643
3644
_fireMouseEvent: function (e) {
3645
3646
this.fire(e.type, {
3647
originalEvent: e,
3648
latlng: this._latlng
3649
});
3650
3651
// TODO proper custom event propagation
3652
// this line will always be called if marker is in a FeatureGroup
3653
if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
3654
L.DomEvent.preventDefault(e);
3655
}
3656
if (e.type !== 'mousedown') {
3657
L.DomEvent.stopPropagation(e);
3658
} else {
3659
L.DomEvent.preventDefault(e);
3660
}
3661
},
3662
3663
setOpacity: function (opacity) {
3664
this.options.opacity = opacity;
3665
if (this._map) {
3666
this._updateOpacity();
3667
}
3668
3669
return this;
3670
},
3671
3672
_updateOpacity: function () {
3673
L.DomUtil.setOpacity(this._icon, this.options.opacity);
3674
if (this._shadow) {
3675
L.DomUtil.setOpacity(this._shadow, this.options.opacity);
3676
}
3677
},
3678
3679
_bringToFront: function () {
3680
this._updateZIndex(this.options.riseOffset);
3681
},
3682
3683
_resetZIndex: function () {
3684
this._updateZIndex(0);
3685
}
3686
});
3687
3688
L.marker = function (latlng, options) {
3689
return new L.Marker(latlng, options);
3690
};
3691
3692
3693
/*
3694
* L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
3695
* to use with L.Marker.
3696
*/
3697
3698
L.DivIcon = L.Icon.extend({
3699
options: {
3700
iconSize: [12, 12], // also can be set through CSS
3701
/*
3702
iconAnchor: (Point)
3703
popupAnchor: (Point)
3704
html: (String)
3705
bgPos: (Point)
3706
*/
3707
className: 'leaflet-div-icon',
3708
html: false
3709
},
3710
3711
createIcon: function (oldIcon) {
3712
var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
3713
options = this.options;
3714
3715
if (options.html !== false) {
3716
div.innerHTML = options.html;
3717
} else {
3718
div.innerHTML = '';
3719
}
3720
3721
if (options.bgPos) {
3722
div.style.backgroundPosition =
3723
(-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
3724
}
3725
3726
this._setIconStyles(div, 'icon');
3727
return div;
3728
},
3729
3730
createShadow: function () {
3731
return null;
3732
}
3733
});
3734
3735
L.divIcon = function (options) {
3736
return new L.DivIcon(options);
3737
};
3738
3739
3740
/*
3741
* L.Popup is used for displaying popups on the map.
3742
*/
3743
3744
L.Map.mergeOptions({
3745
closePopupOnClick: true
3746
});
3747
3748
L.Popup = L.Class.extend({
3749
includes: L.Mixin.Events,
3750
3751
options: {
3752
minWidth: 50,
3753
maxWidth: 300,
3754
maxHeight: null,
3755
autoPan: true,
3756
closeButton: true,
3757
offset: [0, 7],
3758
autoPanPadding: [5, 5],
3759
keepInView: false,
3760
className: '',
3761
zoomAnimation: true
3762
},
3763
3764
initialize: function (options, source) {
3765
L.setOptions(this, options);
3766
3767
this._source = source;
3768
this._animated = L.Browser.any3d && this.options.zoomAnimation;
3769
this._isOpen = false;
3770
},
3771
3772
onAdd: function (map) {
3773
this._map = map;
3774
3775
if (!this._container) {
3776
this._initLayout();
3777
}
3778
this._updateContent();
3779
3780
var animFade = map.options.fadeAnimation;
3781
3782
if (animFade) {
3783
L.DomUtil.setOpacity(this._container, 0);
3784
}
3785
map._panes.popupPane.appendChild(this._container);
3786
3787
map.on(this._getEvents(), this);
3788
3789
this._update();
3790
3791
if (animFade) {
3792
L.DomUtil.setOpacity(this._container, 1);
3793
}
3794
3795
this.fire('open');
3796
3797
map.fire('popupopen', {popup: this});
3798
3799
if (this._source) {
3800
this._source.fire('popupopen', {popup: this});
3801
}
3802
},
3803
3804
addTo: function (map) {
3805
map.addLayer(this);
3806
return this;
3807
},
3808
3809
openOn: function (map) {
3810
map.openPopup(this);
3811
return this;
3812
},
3813
3814
onRemove: function (map) {
3815
map._panes.popupPane.removeChild(this._container);
3816
3817
L.Util.falseFn(this._container.offsetWidth); // force reflow
3818
3819
map.off(this._getEvents(), this);
3820
3821
if (map.options.fadeAnimation) {
3822
L.DomUtil.setOpacity(this._container, 0);
3823
}
3824
3825
this._map = null;
3826
3827
this.fire('close');
3828
3829
map.fire('popupclose', {popup: this});
3830
3831
if (this._source) {
3832
this._source.fire('popupclose', {popup: this});
3833
}
3834
},
3835
3836
setLatLng: function (latlng) {
3837
this._latlng = L.latLng(latlng);
3838
this._update();
3839
return this;
3840
},
3841
3842
setContent: function (content) {
3843
this._content = content;
3844
this._update();
3845
return this;
3846
},
3847
3848
_getEvents: function () {
3849
var events = {
3850
viewreset: this._updatePosition
3851
};
3852
3853
if (this._animated) {
3854
events.zoomanim = this._zoomAnimation;
3855
}
3856
if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
3857
events.preclick = this._close;
3858
}
3859
if (this.options.keepInView) {
3860
events.moveend = this._adjustPan;
3861
}
3862
3863
return events;
3864
},
3865
3866
_close: function () {
3867
if (this._map) {
3868
this._map.closePopup(this);
3869
}
3870
},
3871
3872
_initLayout: function () {
3873
var prefix = 'leaflet-popup',
3874
containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
3875
(this._animated ? 'animated' : 'hide'),
3876
container = this._container = L.DomUtil.create('div', containerClass),
3877
closeButton;
3878
3879
if (this.options.closeButton) {
3880
closeButton = this._closeButton =
3881
L.DomUtil.create('a', prefix + '-close-button', container);
3882
closeButton.href = '#close';
3883
closeButton.innerHTML = '&#215;';
3884
L.DomEvent.disableClickPropagation(closeButton);
3885
3886
L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
3887
}
3888
3889
var wrapper = this._wrapper =
3890
L.DomUtil.create('div', prefix + '-content-wrapper', container);
3891
L.DomEvent.disableClickPropagation(wrapper);
3892
3893
this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
3894
L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation);
3895
L.DomEvent.on(this._contentNode, 'MozMousePixelScroll', L.DomEvent.stopPropagation);
3896
L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
3897
this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
3898
this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
3899
},
3900
3901
_update: function () {
3902
if (!this._map) { return; }
3903
3904
this._container.style.visibility = 'hidden';
3905
3906
this._updateContent();
3907
this._updateLayout();
3908
this._updatePosition();
3909
3910
this._container.style.visibility = '';
3911
3912
this._adjustPan();
3913
},
3914
3915
_updateContent: function () {
3916
if (!this._content) { return; }
3917
3918
if (typeof this._content === 'string') {
3919
this._contentNode.innerHTML = this._content;
3920
} else {
3921
while (this._contentNode.hasChildNodes()) {
3922
this._contentNode.removeChild(this._contentNode.firstChild);
3923
}
3924
this._contentNode.appendChild(this._content);
3925
}
3926
this.fire('contentupdate');
3927
},
3928
3929
_updateLayout: function () {
3930
var container = this._contentNode,
3931
style = container.style;
3932
3933
style.width = '';
3934
style.whiteSpace = 'nowrap';
3935
3936
var width = container.offsetWidth;
3937
width = Math.min(width, this.options.maxWidth);
3938
width = Math.max(width, this.options.minWidth);
3939
3940
style.width = (width + 1) + 'px';
3941
style.whiteSpace = '';
3942
3943
style.height = '';
3944
3945
var height = container.offsetHeight,
3946
maxHeight = this.options.maxHeight,
3947
scrolledClass = 'leaflet-popup-scrolled';
3948
3949
if (maxHeight && height > maxHeight) {
3950
style.height = maxHeight + 'px';
3951
L.DomUtil.addClass(container, scrolledClass);
3952
} else {
3953
L.DomUtil.removeClass(container, scrolledClass);
3954
}
3955
3956
this._containerWidth = this._container.offsetWidth;
3957
},
3958
3959
_updatePosition: function () {
3960
if (!this._map) { return; }
3961
3962
var pos = this._map.latLngToLayerPoint(this._latlng),
3963
animated = this._animated,
3964
offset = L.point(this.options.offset);
3965
3966
if (animated) {
3967
L.DomUtil.setPosition(this._container, pos);
3968
}
3969
3970
this._containerBottom = -offset.y - (animated ? 0 : pos.y);
3971
this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
3972
3973
// bottom position the popup in case the height of the popup changes (images loading etc)
3974
this._container.style.bottom = this._containerBottom + 'px';
3975
this._container.style.left = this._containerLeft + 'px';
3976
},
3977
3978
_zoomAnimation: function (opt) {
3979
var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
3980
3981
L.DomUtil.setPosition(this._container, pos);
3982
},
3983
3984
_adjustPan: function () {
3985
if (!this.options.autoPan) { return; }
3986
3987
var map = this._map,
3988
containerHeight = this._container.offsetHeight,
3989
containerWidth = this._containerWidth,
3990
3991
layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
3992
3993
if (this._animated) {
3994
layerPos._add(L.DomUtil.getPosition(this._container));
3995
}
3996
3997
var containerPos = map.layerPointToContainerPoint(layerPos),
3998
padding = L.point(this.options.autoPanPadding),
3999
size = map.getSize(),
4000
dx = 0,
4001
dy = 0;
4002
4003
if (containerPos.x + containerWidth > size.x) { // right
4004
dx = containerPos.x + containerWidth - size.x + padding.x;
4005
}
4006
if (containerPos.x - dx < 0) { // left
4007
dx = containerPos.x - padding.x;
4008
}
4009
if (containerPos.y + containerHeight > size.y) { // bottom
4010
dy = containerPos.y + containerHeight - size.y + padding.y;
4011
}
4012
if (containerPos.y - dy < 0) { // top
4013
dy = containerPos.y - padding.y;
4014
}
4015
4016
if (dx || dy) {
4017
map
4018
.fire('autopanstart')
4019
.panBy([dx, dy]);
4020
}
4021
},
4022
4023
_onCloseButtonClick: function (e) {
4024
this._close();
4025
L.DomEvent.stop(e);
4026
}
4027
});
4028
4029
L.popup = function (options, source) {
4030
return new L.Popup(options, source);
4031
};
4032
4033
4034
L.Map.include({
4035
openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])
4036
this.closePopup();
4037
4038
if (!(popup instanceof L.Popup)) {
4039
var content = popup;
4040
4041
popup = new L.Popup(options)
4042
.setLatLng(latlng)
4043
.setContent(content);
4044
}
4045
popup._isOpen = true;
4046
4047
this._popup = popup;
4048
return this.addLayer(popup);
4049
},
4050
4051
closePopup: function (popup) {
4052
if (!popup || popup === this._popup) {
4053
popup = this._popup;
4054
this._popup = null;
4055
}
4056
if (popup) {
4057
this.removeLayer(popup);
4058
popup._isOpen = false;
4059
}
4060
return this;
4061
}
4062
});
4063
4064
4065
/*
4066
* Popup extension to L.Marker, adding popup-related methods.
4067
*/
4068
4069
L.Marker.include({
4070
openPopup: function () {
4071
if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
4072
this._popup.setLatLng(this._latlng);
4073
this._map.openPopup(this._popup);
4074
}
4075
4076
return this;
4077
},
4078
4079
closePopup: function () {
4080
if (this._popup) {
4081
this._popup._close();
4082
}
4083
return this;
4084
},
4085
4086
togglePopup: function () {
4087
if (this._popup) {
4088
if (this._popup._isOpen) {
4089
this.closePopup();
4090
} else {
4091
this.openPopup();
4092
}
4093
}
4094
return this;
4095
},
4096
4097
bindPopup: function (content, options) {
4098
var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
4099
4100
anchor = anchor.add(L.Popup.prototype.options.offset);
4101
4102
if (options && options.offset) {
4103
anchor = anchor.add(options.offset);
4104
}
4105
4106
options = L.extend({offset: anchor}, options);
4107
4108
if (!this._popup) {
4109
this
4110
.on('click', this.togglePopup, this)
4111
.on('remove', this.closePopup, this)
4112
.on('move', this._movePopup, this);
4113
}
4114
4115
if (content instanceof L.Popup) {
4116
L.setOptions(content, options);
4117
this._popup = content;
4118
} else {
4119
this._popup = new L.Popup(options, this)
4120
.setContent(content);
4121
}
4122
4123
return this;
4124
},
4125
4126
setPopupContent: function (content) {
4127
if (this._popup) {
4128
this._popup.setContent(content);
4129
}
4130
return this;
4131
},
4132
4133
unbindPopup: function () {
4134
if (this._popup) {
4135
this._popup = null;
4136
this
4137
.off('click', this.togglePopup)
4138
.off('remove', this.closePopup)
4139
.off('move', this._movePopup);
4140
}
4141
return this;
4142
},
4143
4144
_movePopup: function (e) {
4145
this._popup.setLatLng(e.latlng);
4146
}
4147
});
4148
4149
4150
/*
4151
* L.LayerGroup is a class to combine several layers into one so that
4152
* you can manipulate the group (e.g. add/remove it) as one layer.
4153
*/
4154
4155
L.LayerGroup = L.Class.extend({
4156
initialize: function (layers) {
4157
this._layers = {};
4158
4159
var i, len;
4160
4161
if (layers) {
4162
for (i = 0, len = layers.length; i < len; i++) {
4163
this.addLayer(layers[i]);
4164
}
4165
}
4166
},
4167
4168
addLayer: function (layer) {
4169
var id = this.getLayerId(layer);
4170
4171
this._layers[id] = layer;
4172
4173
if (this._map) {
4174
this._map.addLayer(layer);
4175
}
4176
4177
return this;
4178
},
4179
4180
removeLayer: function (layer) {
4181
var id = layer in this._layers ? layer : this.getLayerId(layer);
4182
4183
if (this._map && this._layers[id]) {
4184
this._map.removeLayer(this._layers[id]);
4185
}
4186
4187
delete this._layers[id];
4188
4189
return this;
4190
},
4191
4192
hasLayer: function (layer) {
4193
if (!layer) { return false; }
4194
4195
return (layer in this._layers || this.getLayerId(layer) in this._layers);
4196
},
4197
4198
clearLayers: function () {
4199
this.eachLayer(this.removeLayer, this);
4200
return this;
4201
},
4202
4203
invoke: function (methodName) {
4204
var args = Array.prototype.slice.call(arguments, 1),
4205
i, layer;
4206
4207
for (i in this._layers) {
4208
layer = this._layers[i];
4209
4210
if (layer[methodName]) {
4211
layer[methodName].apply(layer, args);
4212
}
4213
}
4214
4215
return this;
4216
},
4217
4218
onAdd: function (map) {
4219
this._map = map;
4220
this.eachLayer(map.addLayer, map);
4221
},
4222
4223
onRemove: function (map) {
4224
this.eachLayer(map.removeLayer, map);
4225
this._map = null;
4226
},
4227
4228
addTo: function (map) {
4229
map.addLayer(this);
4230
return this;
4231
},
4232
4233
eachLayer: function (method, context) {
4234
for (var i in this._layers) {
4235
method.call(context, this._layers[i]);
4236
}
4237
return this;
4238
},
4239
4240
getLayer: function (id) {
4241
return this._layers[id];
4242
},
4243
4244
getLayers: function () {
4245
var layers = [];
4246
4247
for (var i in this._layers) {
4248
layers.push(this._layers[i]);
4249
}
4250
return layers;
4251
},
4252
4253
setZIndex: function (zIndex) {
4254
return this.invoke('setZIndex', zIndex);
4255
},
4256
4257
getLayerId: function (layer) {
4258
return L.stamp(layer);
4259
}
4260
});
4261
4262
L.layerGroup = function (layers) {
4263
return new L.LayerGroup(layers);
4264
};
4265
4266
4267
/*
4268
* L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
4269
* shared between a group of interactive layers (like vectors or markers).
4270
*/
4271
4272
L.FeatureGroup = L.LayerGroup.extend({
4273
includes: L.Mixin.Events,
4274
4275
statics: {
4276
EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
4277
},
4278
4279
addLayer: function (layer) {
4280
if (this.hasLayer(layer)) {
4281
return this;
4282
}
4283
4284
layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
4285
4286
L.LayerGroup.prototype.addLayer.call(this, layer);
4287
4288
if (this._popupContent && layer.bindPopup) {
4289
layer.bindPopup(this._popupContent, this._popupOptions);
4290
}
4291
4292
return this.fire('layeradd', {layer: layer});
4293
},
4294
4295
removeLayer: function (layer) {
4296
if (!this.hasLayer(layer)) {
4297
return this;
4298
}
4299
if (layer in this._layers) {
4300
layer = this._layers[layer];
4301
}
4302
4303
layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
4304
4305
L.LayerGroup.prototype.removeLayer.call(this, layer);
4306
4307
if (this._popupContent) {
4308
this.invoke('unbindPopup');
4309
}
4310
4311
return this.fire('layerremove', {layer: layer});
4312
},
4313
4314
bindPopup: function (content, options) {
4315
this._popupContent = content;
4316
this._popupOptions = options;
4317
return this.invoke('bindPopup', content, options);
4318
},
4319
4320
setStyle: function (style) {
4321
return this.invoke('setStyle', style);
4322
},
4323
4324
bringToFront: function () {
4325
return this.invoke('bringToFront');
4326
},
4327
4328
bringToBack: function () {
4329
return this.invoke('bringToBack');
4330
},
4331
4332
getBounds: function () {
4333
var bounds = new L.LatLngBounds();
4334
4335
this.eachLayer(function (layer) {
4336
bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
4337
});
4338
4339
return bounds;
4340
},
4341
4342
_propagateEvent: function (e) {
4343
if (!e.layer) {
4344
e.layer = e.target;
4345
}
4346
e.target = this;
4347
4348
this.fire(e.type, e);
4349
}
4350
});
4351
4352
L.featureGroup = function (layers) {
4353
return new L.FeatureGroup(layers);
4354
};
4355
4356
4357
/*
4358
* L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
4359
*/
4360
4361
L.Path = L.Class.extend({
4362
includes: [L.Mixin.Events],
4363
4364
statics: {
4365
// how much to extend the clip area around the map view
4366
// (relative to its size, e.g. 0.5 is half the screen in each direction)
4367
// set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
4368
CLIP_PADDING: (function () {
4369
var max = L.Browser.mobile ? 1280 : 2000,
4370
target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;
4371
return Math.max(0, Math.min(0.5, target));
4372
})()
4373
},
4374
4375
options: {
4376
stroke: true,
4377
color: '#0033ff',
4378
dashArray: null,
4379
weight: 5,
4380
opacity: 0.5,
4381
4382
fill: false,
4383
fillColor: null, //same as color by default
4384
fillOpacity: 0.2,
4385
4386
clickable: true
4387
},
4388
4389
initialize: function (options) {
4390
L.setOptions(this, options);
4391
},
4392
4393
onAdd: function (map) {
4394
this._map = map;
4395
4396
if (!this._container) {
4397
this._initElements();
4398
this._initEvents();
4399
}
4400
4401
this.projectLatlngs();
4402
this._updatePath();
4403
4404
if (this._container) {
4405
this._map._pathRoot.appendChild(this._container);
4406
}
4407
4408
this.fire('add');
4409
4410
map.on({
4411
'viewreset': this.projectLatlngs,
4412
'moveend': this._updatePath
4413
}, this);
4414
},
4415
4416
addTo: function (map) {
4417
map.addLayer(this);
4418
return this;
4419
},
4420
4421
onRemove: function (map) {
4422
map._pathRoot.removeChild(this._container);
4423
4424
// Need to fire remove event before we set _map to null as the event hooks might need the object
4425
this.fire('remove');
4426
this._map = null;
4427
4428
if (L.Browser.vml) {
4429
this._container = null;
4430
this._stroke = null;
4431
this._fill = null;
4432
}
4433
4434
map.off({
4435
'viewreset': this.projectLatlngs,
4436
'moveend': this._updatePath
4437
}, this);
4438
},
4439
4440
projectLatlngs: function () {
4441
// do all projection stuff here
4442
},
4443
4444
setStyle: function (style) {
4445
L.setOptions(this, style);
4446
4447
if (this._container) {
4448
this._updateStyle();
4449
}
4450
4451
return this;
4452
},
4453
4454
redraw: function () {
4455
if (this._map) {
4456
this.projectLatlngs();
4457
this._updatePath();
4458
}
4459
return this;
4460
}
4461
});
4462
4463
L.Map.include({
4464
_updatePathViewport: function () {
4465
var p = L.Path.CLIP_PADDING,
4466
size = this.getSize(),
4467
panePos = L.DomUtil.getPosition(this._mapPane),
4468
min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
4469
max = min.add(size.multiplyBy(1 + p * 2)._round());
4470
4471
this._pathViewport = new L.Bounds(min, max);
4472
}
4473
});
4474
4475
4476
/*
4477
* Extends L.Path with SVG-specific rendering code.
4478
*/
4479
4480
L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
4481
4482
L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
4483
4484
L.Path = L.Path.extend({
4485
statics: {
4486
SVG: L.Browser.svg
4487
},
4488
4489
bringToFront: function () {
4490
var root = this._map._pathRoot,
4491
path = this._container;
4492
4493
if (path && root.lastChild !== path) {
4494
root.appendChild(path);
4495
}
4496
return this;
4497
},
4498
4499
bringToBack: function () {
4500
var root = this._map._pathRoot,
4501
path = this._container,
4502
first = root.firstChild;
4503
4504
if (path && first !== path) {
4505
root.insertBefore(path, first);
4506
}
4507
return this;
4508
},
4509
4510
getPathString: function () {
4511
// form path string here
4512
},
4513
4514
_createElement: function (name) {
4515
return document.createElementNS(L.Path.SVG_NS, name);
4516
},
4517
4518
_initElements: function () {
4519
this._map._initPathRoot();
4520
this._initPath();
4521
this._initStyle();
4522
},
4523
4524
_initPath: function () {
4525
this._container = this._createElement('g');
4526
4527
this._path = this._createElement('path');
4528
this._container.appendChild(this._path);
4529
},
4530
4531
_initStyle: function () {
4532
if (this.options.stroke) {
4533
this._path.setAttribute('stroke-linejoin', 'round');
4534
this._path.setAttribute('stroke-linecap', 'round');
4535
}
4536
if (this.options.fill) {
4537
this._path.setAttribute('fill-rule', 'evenodd');
4538
}
4539
if (this.options.pointerEvents) {
4540
this._path.setAttribute('pointer-events', this.options.pointerEvents);
4541
}
4542
if (!this.options.clickable && !this.options.pointerEvents) {
4543
this._path.setAttribute('pointer-events', 'none');
4544
}
4545
this._updateStyle();
4546
},
4547
4548
_updateStyle: function () {
4549
if (this.options.stroke) {
4550
this._path.setAttribute('stroke', this.options.color);
4551
this._path.setAttribute('stroke-opacity', this.options.opacity);
4552
this._path.setAttribute('stroke-width', this.options.weight);
4553
if (this.options.dashArray) {
4554
this._path.setAttribute('stroke-dasharray', this.options.dashArray);
4555
} else {
4556
this._path.removeAttribute('stroke-dasharray');
4557
}
4558
} else {
4559
this._path.setAttribute('stroke', 'none');
4560
}
4561
if (this.options.fill) {
4562
this._path.setAttribute('fill', this.options.fillColor || this.options.color);
4563
this._path.setAttribute('fill-opacity', this.options.fillOpacity);
4564
} else {
4565
this._path.setAttribute('fill', 'none');
4566
}
4567
},
4568
4569
_updatePath: function () {
4570
var str = this.getPathString();
4571
if (!str) {
4572
// fix webkit empty string parsing bug
4573
str = 'M0 0';
4574
}
4575
this._path.setAttribute('d', str);
4576
},
4577
4578
// TODO remove duplication with L.Map
4579
_initEvents: function () {
4580
if (this.options.clickable) {
4581
if (L.Browser.svg || !L.Browser.vml) {
4582
this._path.setAttribute('class', 'leaflet-clickable');
4583
}
4584
4585
L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
4586
4587
var events = ['dblclick', 'mousedown', 'mouseover',
4588
'mouseout', 'mousemove', 'contextmenu'];
4589
for (var i = 0; i < events.length; i++) {
4590
L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
4591
}
4592
}
4593
},
4594
4595
_onMouseClick: function (e) {
4596
if (this._map.dragging && this._map.dragging.moved()) { return; }
4597
4598
this._fireMouseEvent(e);
4599
},
4600
4601
_fireMouseEvent: function (e) {
4602
if (!this.hasEventListeners(e.type)) { return; }
4603
4604
var map = this._map,
4605
containerPoint = map.mouseEventToContainerPoint(e),
4606
layerPoint = map.containerPointToLayerPoint(containerPoint),
4607
latlng = map.layerPointToLatLng(layerPoint);
4608
4609
this.fire(e.type, {
4610
latlng: latlng,
4611
layerPoint: layerPoint,
4612
containerPoint: containerPoint,
4613
originalEvent: e
4614
});
4615
4616
if (e.type === 'contextmenu') {
4617
L.DomEvent.preventDefault(e);
4618
}
4619
if (e.type !== 'mousemove') {
4620
L.DomEvent.stopPropagation(e);
4621
}
4622
}
4623
});
4624
4625
L.Map.include({
4626
_initPathRoot: function () {
4627
if (!this._pathRoot) {
4628
this._pathRoot = L.Path.prototype._createElement('svg');
4629
this._panes.overlayPane.appendChild(this._pathRoot);
4630
4631
if (this.options.zoomAnimation && L.Browser.any3d) {
4632
this._pathRoot.setAttribute('class', ' leaflet-zoom-animated');
4633
4634
this.on({
4635
'zoomanim': this._animatePathZoom,
4636
'zoomend': this._endPathZoom
4637
});
4638
} else {
4639
this._pathRoot.setAttribute('class', ' leaflet-zoom-hide');
4640
}
4641
4642
this.on('moveend', this._updateSvgViewport);
4643
this._updateSvgViewport();
4644
}
4645
},
4646
4647
_animatePathZoom: function (e) {
4648
var scale = this.getZoomScale(e.zoom),
4649
offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
4650
4651
this._pathRoot.style[L.DomUtil.TRANSFORM] =
4652
L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
4653
4654
this._pathZooming = true;
4655
},
4656
4657
_endPathZoom: function () {
4658
this._pathZooming = false;
4659
},
4660
4661
_updateSvgViewport: function () {
4662
4663
if (this._pathZooming) {
4664
// Do not update SVGs while a zoom animation is going on otherwise the animation will break.
4665
// When the zoom animation ends we will be updated again anyway
4666
// This fixes the case where you do a momentum move and zoom while the move is still ongoing.
4667
return;
4668
}
4669
4670
this._updatePathViewport();
4671
4672
var vp = this._pathViewport,
4673
min = vp.min,
4674
max = vp.max,
4675
width = max.x - min.x,
4676
height = max.y - min.y,
4677
root = this._pathRoot,
4678
pane = this._panes.overlayPane;
4679
4680
// Hack to make flicker on drag end on mobile webkit less irritating
4681
if (L.Browser.mobileWebkit) {
4682
pane.removeChild(root);
4683
}
4684
4685
L.DomUtil.setPosition(root, min);
4686
root.setAttribute('width', width);
4687
root.setAttribute('height', height);
4688
root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
4689
4690
if (L.Browser.mobileWebkit) {
4691
pane.appendChild(root);
4692
}
4693
}
4694
});
4695
4696
4697
/*
4698
* Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
4699
*/
4700
4701
L.Path.include({
4702
4703
bindPopup: function (content, options) {
4704
4705
if (content instanceof L.Popup) {
4706
this._popup = content;
4707
} else {
4708
if (!this._popup || options) {
4709
this._popup = new L.Popup(options, this);
4710
}
4711
this._popup.setContent(content);
4712
}
4713
4714
if (!this._popupHandlersAdded) {
4715
this
4716
.on('click', this._openPopup, this)
4717
.on('remove', this.closePopup, this);
4718
4719
this._popupHandlersAdded = true;
4720
}
4721
4722
return this;
4723
},
4724
4725
unbindPopup: function () {
4726
if (this._popup) {
4727
this._popup = null;
4728
this
4729
.off('click', this._openPopup)
4730
.off('remove', this.closePopup);
4731
4732
this._popupHandlersAdded = false;
4733
}
4734
return this;
4735
},
4736
4737
openPopup: function (latlng) {
4738
4739
if (this._popup) {
4740
// open the popup from one of the path's points if not specified
4741
latlng = latlng || this._latlng ||
4742
this._latlngs[Math.floor(this._latlngs.length / 2)];
4743
4744
this._openPopup({latlng: latlng});
4745
}
4746
4747
return this;
4748
},
4749
4750
closePopup: function () {
4751
if (this._popup) {
4752
this._popup._close();
4753
}
4754
return this;
4755
},
4756
4757
_openPopup: function (e) {
4758
this._popup.setLatLng(e.latlng);
4759
this._map.openPopup(this._popup);
4760
}
4761
});
4762
4763
4764
/*
4765
* Vector rendering for IE6-8 through VML.
4766
* Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
4767
*/
4768
4769
L.Browser.vml = !L.Browser.svg && (function () {
4770
try {
4771
var div = document.createElement('div');
4772
div.innerHTML = '<v:shape adj="1"/>';
4773
4774
var shape = div.firstChild;
4775
shape.style.behavior = 'url(#default#VML)';
4776
4777
return shape && (typeof shape.adj === 'object');
4778
4779
} catch (e) {
4780
return false;
4781
}
4782
}());
4783
4784
L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
4785
statics: {
4786
VML: true,
4787
CLIP_PADDING: 0.02
4788
},
4789
4790
_createElement: (function () {
4791
try {
4792
document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
4793
return function (name) {
4794
return document.createElement('<lvml:' + name + ' class="lvml">');
4795
};
4796
} catch (e) {
4797
return function (name) {
4798
return document.createElement(
4799
'<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
4800
};
4801
}
4802
}()),
4803
4804
_initPath: function () {
4805
var container = this._container = this._createElement('shape');
4806
L.DomUtil.addClass(container, 'leaflet-vml-shape');
4807
if (this.options.clickable) {
4808
L.DomUtil.addClass(container, 'leaflet-clickable');
4809
}
4810
container.coordsize = '1 1';
4811
4812
this._path = this._createElement('path');
4813
container.appendChild(this._path);
4814
4815
this._map._pathRoot.appendChild(container);
4816
},
4817
4818
_initStyle: function () {
4819
this._updateStyle();
4820
},
4821
4822
_updateStyle: function () {
4823
var stroke = this._stroke,
4824
fill = this._fill,
4825
options = this.options,
4826
container = this._container;
4827
4828
container.stroked = options.stroke;
4829
container.filled = options.fill;
4830
4831
if (options.stroke) {
4832
if (!stroke) {
4833
stroke = this._stroke = this._createElement('stroke');
4834
stroke.endcap = 'round';
4835
container.appendChild(stroke);
4836
}
4837
stroke.weight = options.weight + 'px';
4838
stroke.color = options.color;
4839
stroke.opacity = options.opacity;
4840
4841
if (options.dashArray) {
4842
stroke.dashStyle = options.dashArray instanceof Array ?
4843
options.dashArray.join(' ') :
4844
options.dashArray.replace(/( *, *)/g, ' ');
4845
} else {
4846
stroke.dashStyle = '';
4847
}
4848
4849
} else if (stroke) {
4850
container.removeChild(stroke);
4851
this._stroke = null;
4852
}
4853
4854
if (options.fill) {
4855
if (!fill) {
4856
fill = this._fill = this._createElement('fill');
4857
container.appendChild(fill);
4858
}
4859
fill.color = options.fillColor || options.color;
4860
fill.opacity = options.fillOpacity;
4861
4862
} else if (fill) {
4863
container.removeChild(fill);
4864
this._fill = null;
4865
}
4866
},
4867
4868
_updatePath: function () {
4869
var style = this._container.style;
4870
4871
style.display = 'none';
4872
this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
4873
style.display = '';
4874
}
4875
});
4876
4877
L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
4878
_initPathRoot: function () {
4879
if (this._pathRoot) { return; }
4880
4881
var root = this._pathRoot = document.createElement('div');
4882
root.className = 'leaflet-vml-container';
4883
this._panes.overlayPane.appendChild(root);
4884
4885
this.on('moveend', this._updatePathViewport);
4886
this._updatePathViewport();
4887
}
4888
});
4889
4890
4891
/*
4892
* Vector rendering for all browsers that support canvas.
4893
*/
4894
4895
L.Browser.canvas = (function () {
4896
return !!document.createElement('canvas').getContext;
4897
}());
4898
4899
L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
4900
statics: {
4901
//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
4902
CANVAS: true,
4903
SVG: false
4904
},
4905
4906
redraw: function () {
4907
if (this._map) {
4908
this.projectLatlngs();
4909
this._requestUpdate();
4910
}
4911
return this;
4912
},
4913
4914
setStyle: function (style) {
4915
L.setOptions(this, style);
4916
4917
if (this._map) {
4918
this._updateStyle();
4919
this._requestUpdate();
4920
}
4921
return this;
4922
},
4923
4924
onRemove: function (map) {
4925
map
4926
.off('viewreset', this.projectLatlngs, this)
4927
.off('moveend', this._updatePath, this);
4928
4929
if (this.options.clickable) {
4930
this._map.off('click', this._onClick, this);
4931
this._map.off('mousemove', this._onMouseMove, this);
4932
}
4933
4934
this._requestUpdate();
4935
4936
this._map = null;
4937
},
4938
4939
_requestUpdate: function () {
4940
if (this._map && !L.Path._updateRequest) {
4941
L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
4942
}
4943
},
4944
4945
_fireMapMoveEnd: function () {
4946
L.Path._updateRequest = null;
4947
this.fire('moveend');
4948
},
4949
4950
_initElements: function () {
4951
this._map._initPathRoot();
4952
this._ctx = this._map._canvasCtx;
4953
},
4954
4955
_updateStyle: function () {
4956
var options = this.options;
4957
4958
if (options.stroke) {
4959
this._ctx.lineWidth = options.weight;
4960
this._ctx.strokeStyle = options.color;
4961
}
4962
if (options.fill) {
4963
this._ctx.fillStyle = options.fillColor || options.color;
4964
}
4965
},
4966
4967
_drawPath: function () {
4968
var i, j, len, len2, point, drawMethod;
4969
4970
this._ctx.beginPath();
4971
4972
for (i = 0, len = this._parts.length; i < len; i++) {
4973
for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
4974
point = this._parts[i][j];
4975
drawMethod = (j === 0 ? 'move' : 'line') + 'To';
4976
4977
this._ctx[drawMethod](point.x, point.y);
4978
}
4979
// TODO refactor ugly hack
4980
if (this instanceof L.Polygon) {
4981
this._ctx.closePath();
4982
}
4983
}
4984
},
4985
4986
_checkIfEmpty: function () {
4987
return !this._parts.length;
4988
},
4989
4990
_updatePath: function () {
4991
if (this._checkIfEmpty()) { return; }
4992
4993
var ctx = this._ctx,
4994
options = this.options;
4995
4996
this._drawPath();
4997
ctx.save();
4998
this._updateStyle();
4999
5000
if (options.fill) {
5001
ctx.globalAlpha = options.fillOpacity;
5002
ctx.fill();
5003
}
5004
5005
if (options.stroke) {
5006
ctx.globalAlpha = options.opacity;
5007
ctx.stroke();
5008
}
5009
5010
ctx.restore();
5011
5012
// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
5013
},
5014
5015
_initEvents: function () {
5016
if (this.options.clickable) {
5017
// TODO dblclick
5018
this._map.on('mousemove', this._onMouseMove, this);
5019
this._map.on('click', this._onClick, this);
5020
}
5021
},
5022
5023
_onClick: function (e) {
5024
if (this._containsPoint(e.layerPoint)) {
5025
this.fire('click', e);
5026
}
5027
},
5028
5029
_onMouseMove: function (e) {
5030
if (!this._map || this._map._animatingZoom) { return; }
5031
5032
// TODO don't do on each move
5033
if (this._containsPoint(e.layerPoint)) {
5034
this._ctx.canvas.style.cursor = 'pointer';
5035
this._mouseInside = true;
5036
this.fire('mouseover', e);
5037
5038
} else if (this._mouseInside) {
5039
this._ctx.canvas.style.cursor = '';
5040
this._mouseInside = false;
5041
this.fire('mouseout', e);
5042
}
5043
}
5044
});
5045
5046
L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
5047
_initPathRoot: function () {
5048
var root = this._pathRoot,
5049
ctx;
5050
5051
if (!root) {
5052
root = this._pathRoot = document.createElement('canvas');
5053
root.style.position = 'absolute';
5054
ctx = this._canvasCtx = root.getContext('2d');
5055
5056
ctx.lineCap = 'round';
5057
ctx.lineJoin = 'round';
5058
5059
this._panes.overlayPane.appendChild(root);
5060
5061
if (this.options.zoomAnimation) {
5062
this._pathRoot.className = 'leaflet-zoom-animated';
5063
this.on('zoomanim', this._animatePathZoom);
5064
this.on('zoomend', this._endPathZoom);
5065
}
5066
this.on('moveend', this._updateCanvasViewport);
5067
this._updateCanvasViewport();
5068
}
5069
},
5070
5071
_updateCanvasViewport: function () {
5072
// don't redraw while zooming. See _updateSvgViewport for more details
5073
if (this._pathZooming) { return; }
5074
this._updatePathViewport();
5075
5076
var vp = this._pathViewport,
5077
min = vp.min,
5078
size = vp.max.subtract(min),
5079
root = this._pathRoot;
5080
5081
//TODO check if this works properly on mobile webkit
5082
L.DomUtil.setPosition(root, min);
5083
root.width = size.x;
5084
root.height = size.y;
5085
root.getContext('2d').translate(-min.x, -min.y);
5086
}
5087
});
5088
5089
5090
/*
5091
* L.LineUtil contains different utility functions for line segments
5092
* and polylines (clipping, simplification, distances, etc.)
5093
*/
5094
5095
/*jshint bitwise:false */ // allow bitwise oprations for this file
5096
5097
L.LineUtil = {
5098
5099
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5100
// Improves rendering performance dramatically by lessening the number of points to draw.
5101
5102
simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
5103
if (!tolerance || !points.length) {
5104
return points.slice();
5105
}
5106
5107
var sqTolerance = tolerance * tolerance;
5108
5109
// stage 1: vertex reduction
5110
points = this._reducePoints(points, sqTolerance);
5111
5112
// stage 2: Douglas-Peucker simplification
5113
points = this._simplifyDP(points, sqTolerance);
5114
5115
return points;
5116
},
5117
5118
// distance from a point to a segment between two points
5119
pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
5120
return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
5121
},
5122
5123
closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
5124
return this._sqClosestPointOnSegment(p, p1, p2);
5125
},
5126
5127
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5128
_simplifyDP: function (points, sqTolerance) {
5129
5130
var len = points.length,
5131
ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5132
markers = new ArrayConstructor(len);
5133
5134
markers[0] = markers[len - 1] = 1;
5135
5136
this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5137
5138
var i,
5139
newPoints = [];
5140
5141
for (i = 0; i < len; i++) {
5142
if (markers[i]) {
5143
newPoints.push(points[i]);
5144
}
5145
}
5146
5147
return newPoints;
5148
},
5149
5150
_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
5151
5152
var maxSqDist = 0,
5153
index, i, sqDist;
5154
5155
for (i = first + 1; i <= last - 1; i++) {
5156
sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
5157
5158
if (sqDist > maxSqDist) {
5159
index = i;
5160
maxSqDist = sqDist;
5161
}
5162
}
5163
5164
if (maxSqDist > sqTolerance) {
5165
markers[index] = 1;
5166
5167
this._simplifyDPStep(points, markers, sqTolerance, first, index);
5168
this._simplifyDPStep(points, markers, sqTolerance, index, last);
5169
}
5170
},
5171
5172
// reduce points that are too close to each other to a single point
5173
_reducePoints: function (points, sqTolerance) {
5174
var reducedPoints = [points[0]];
5175
5176
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5177
if (this._sqDist(points[i], points[prev]) > sqTolerance) {
5178
reducedPoints.push(points[i]);
5179
prev = i;
5180
}
5181
}
5182
if (prev < len - 1) {
5183
reducedPoints.push(points[len - 1]);
5184
}
5185
return reducedPoints;
5186
},
5187
5188
// Cohen-Sutherland line clipping algorithm.
5189
// Used to avoid rendering parts of a polyline that are not currently visible.
5190
5191
clipSegment: function (a, b, bounds, useLastCode) {
5192
var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
5193
codeB = this._getBitCode(b, bounds),
5194
5195
codeOut, p, newCode;
5196
5197
// save 2nd code to avoid calculating it on the next segment
5198
this._lastCode = codeB;
5199
5200
while (true) {
5201
// if a,b is inside the clip window (trivial accept)
5202
if (!(codeA | codeB)) {
5203
return [a, b];
5204
// if a,b is outside the clip window (trivial reject)
5205
} else if (codeA & codeB) {
5206
return false;
5207
// other cases
5208
} else {
5209
codeOut = codeA || codeB;
5210
p = this._getEdgeIntersection(a, b, codeOut, bounds);
5211
newCode = this._getBitCode(p, bounds);
5212
5213
if (codeOut === codeA) {
5214
a = p;
5215
codeA = newCode;
5216
} else {
5217
b = p;
5218
codeB = newCode;
5219
}
5220
}
5221
}
5222
},
5223
5224
_getEdgeIntersection: function (a, b, code, bounds) {
5225
var dx = b.x - a.x,
5226
dy = b.y - a.y,
5227
min = bounds.min,
5228
max = bounds.max;
5229
5230
if (code & 8) { // top
5231
return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
5232
} else if (code & 4) { // bottom
5233
return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
5234
} else if (code & 2) { // right
5235
return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
5236
} else if (code & 1) { // left
5237
return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
5238
}
5239
},
5240
5241
_getBitCode: function (/*Point*/ p, bounds) {
5242
var code = 0;
5243
5244
if (p.x < bounds.min.x) { // left
5245
code |= 1;
5246
} else if (p.x > bounds.max.x) { // right
5247
code |= 2;
5248
}
5249
if (p.y < bounds.min.y) { // bottom
5250
code |= 4;
5251
} else if (p.y > bounds.max.y) { // top
5252
code |= 8;
5253
}
5254
5255
return code;
5256
},
5257
5258
// square distance (to avoid unnecessary Math.sqrt calls)
5259
_sqDist: function (p1, p2) {
5260
var dx = p2.x - p1.x,
5261
dy = p2.y - p1.y;
5262
return dx * dx + dy * dy;
5263
},
5264
5265
// return closest point on segment or distance to that point
5266
_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
5267
var x = p1.x,
5268
y = p1.y,
5269
dx = p2.x - x,
5270
dy = p2.y - y,
5271
dot = dx * dx + dy * dy,
5272
t;
5273
5274
if (dot > 0) {
5275
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
5276
5277
if (t > 1) {
5278
x = p2.x;
5279
y = p2.y;
5280
} else if (t > 0) {
5281
x += dx * t;
5282
y += dy * t;
5283
}
5284
}
5285
5286
dx = p.x - x;
5287
dy = p.y - y;
5288
5289
return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
5290
}
5291
};
5292
5293
5294
/*
5295
* L.Polyline is used to display polylines on a map.
5296
*/
5297
5298
L.Polyline = L.Path.extend({
5299
initialize: function (latlngs, options) {
5300
L.Path.prototype.initialize.call(this, options);
5301
5302
this._latlngs = this._convertLatLngs(latlngs);
5303
},
5304
5305
options: {
5306
// how much to simplify the polyline on each zoom level
5307
// more = better performance and smoother look, less = more accurate
5308
smoothFactor: 1.0,
5309
noClip: false
5310
},
5311
5312
projectLatlngs: function () {
5313
this._originalPoints = [];
5314
5315
for (var i = 0, len = this._latlngs.length; i < len; i++) {
5316
this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
5317
}
5318
},
5319
5320
getPathString: function () {
5321
for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
5322
str += this._getPathPartStr(this._parts[i]);
5323
}
5324
return str;
5325
},
5326
5327
getLatLngs: function () {
5328
return this._latlngs;
5329
},
5330
5331
setLatLngs: function (latlngs) {
5332
this._latlngs = this._convertLatLngs(latlngs);
5333
return this.redraw();
5334
},
5335
5336
addLatLng: function (latlng) {
5337
this._latlngs.push(L.latLng(latlng));
5338
return this.redraw();
5339
},
5340
5341
spliceLatLngs: function () { // (Number index, Number howMany)
5342
var removed = [].splice.apply(this._latlngs, arguments);
5343
this._convertLatLngs(this._latlngs, true);
5344
this.redraw();
5345
return removed;
5346
},
5347
5348
closestLayerPoint: function (p) {
5349
var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
5350
5351
for (var j = 0, jLen = parts.length; j < jLen; j++) {
5352
var points = parts[j];
5353
for (var i = 1, len = points.length; i < len; i++) {
5354
p1 = points[i - 1];
5355
p2 = points[i];
5356
var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
5357
if (sqDist < minDistance) {
5358
minDistance = sqDist;
5359
minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
5360
}
5361
}
5362
}
5363
if (minPoint) {
5364
minPoint.distance = Math.sqrt(minDistance);
5365
}
5366
return minPoint;
5367
},
5368
5369
getBounds: function () {
5370
return new L.LatLngBounds(this.getLatLngs());
5371
},
5372
5373
_convertLatLngs: function (latlngs, overwrite) {
5374
var i, len, target = overwrite ? latlngs : [];
5375
5376
for (i = 0, len = latlngs.length; i < len; i++) {
5377
if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
5378
return;
5379
}
5380
target[i] = L.latLng(latlngs[i]);
5381
}
5382
return target;
5383
},
5384
5385
_initEvents: function () {
5386
L.Path.prototype._initEvents.call(this);
5387
},
5388
5389
_getPathPartStr: function (points) {
5390
var round = L.Path.VML;
5391
5392
for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
5393
p = points[j];
5394
if (round) {
5395
p._round();
5396
}
5397
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
5398
}
5399
return str;
5400
},
5401
5402
_clipPoints: function () {
5403
var points = this._originalPoints,
5404
len = points.length,
5405
i, k, segment;
5406
5407
if (this.options.noClip) {
5408
this._parts = [points];
5409
return;
5410
}
5411
5412
this._parts = [];
5413
5414
var parts = this._parts,
5415
vp = this._map._pathViewport,
5416
lu = L.LineUtil;
5417
5418
for (i = 0, k = 0; i < len - 1; i++) {
5419
segment = lu.clipSegment(points[i], points[i + 1], vp, i);
5420
if (!segment) {
5421
continue;
5422
}
5423
5424
parts[k] = parts[k] || [];
5425
parts[k].push(segment[0]);
5426
5427
// if segment goes out of screen, or it's the last one, it's the end of the line part
5428
if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
5429
parts[k].push(segment[1]);
5430
k++;
5431
}
5432
}
5433
},
5434
5435
// simplify each clipped part of the polyline
5436
_simplifyPoints: function () {
5437
var parts = this._parts,
5438
lu = L.LineUtil;
5439
5440
for (var i = 0, len = parts.length; i < len; i++) {
5441
parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
5442
}
5443
},
5444
5445
_updatePath: function () {
5446
if (!this._map) { return; }
5447
5448
this._clipPoints();
5449
this._simplifyPoints();
5450
5451
L.Path.prototype._updatePath.call(this);
5452
}
5453
});
5454
5455
L.polyline = function (latlngs, options) {
5456
return new L.Polyline(latlngs, options);
5457
};
5458
5459
5460
/*
5461
* L.PolyUtil contains utility functions for polygons (clipping, etc.).
5462
*/
5463
5464
/*jshint bitwise:false */ // allow bitwise operations here
5465
5466
L.PolyUtil = {};
5467
5468
/*
5469
* Sutherland-Hodgeman polygon clipping algorithm.
5470
* Used to avoid rendering parts of a polygon that are not currently visible.
5471
*/
5472
L.PolyUtil.clipPolygon = function (points, bounds) {
5473
var clippedPoints,
5474
edges = [1, 4, 2, 8],
5475
i, j, k,
5476
a, b,
5477
len, edge, p,
5478
lu = L.LineUtil;
5479
5480
for (i = 0, len = points.length; i < len; i++) {
5481
points[i]._code = lu._getBitCode(points[i], bounds);
5482
}
5483
5484
// for each edge (left, bottom, right, top)
5485
for (k = 0; k < 4; k++) {
5486
edge = edges[k];
5487
clippedPoints = [];
5488
5489
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
5490
a = points[i];
5491
b = points[j];
5492
5493
// if a is inside the clip window
5494
if (!(a._code & edge)) {
5495
// if b is outside the clip window (a->b goes out of screen)
5496
if (b._code & edge) {
5497
p = lu._getEdgeIntersection(b, a, edge, bounds);
5498
p._code = lu._getBitCode(p, bounds);
5499
clippedPoints.push(p);
5500
}
5501
clippedPoints.push(a);
5502
5503
// else if b is inside the clip window (a->b enters the screen)
5504
} else if (!(b._code & edge)) {
5505
p = lu._getEdgeIntersection(b, a, edge, bounds);
5506
p._code = lu._getBitCode(p, bounds);
5507
clippedPoints.push(p);
5508
}
5509
}
5510
points = clippedPoints;
5511
}
5512
5513
return points;
5514
};
5515
5516
5517
/*
5518
* L.Polygon is used to display polygons on a map.
5519
*/
5520
5521
L.Polygon = L.Polyline.extend({
5522
options: {
5523
fill: true
5524
},
5525
5526
initialize: function (latlngs, options) {
5527
var i, len, hole;
5528
5529
L.Polyline.prototype.initialize.call(this, latlngs, options);
5530
5531
if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
5532
this._latlngs = this._convertLatLngs(latlngs[0]);
5533
this._holes = latlngs.slice(1);
5534
5535
for (i = 0, len = this._holes.length; i < len; i++) {
5536
hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
5537
if (hole[0].equals(hole[hole.length - 1])) {
5538
hole.pop();
5539
}
5540
}
5541
}
5542
5543
// filter out last point if its equal to the first one
5544
latlngs = this._latlngs;
5545
5546
if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
5547
latlngs.pop();
5548
}
5549
},
5550
5551
projectLatlngs: function () {
5552
L.Polyline.prototype.projectLatlngs.call(this);
5553
5554
// project polygon holes points
5555
// TODO move this logic to Polyline to get rid of duplication
5556
this._holePoints = [];
5557
5558
if (!this._holes) { return; }
5559
5560
var i, j, len, len2;
5561
5562
for (i = 0, len = this._holes.length; i < len; i++) {
5563
this._holePoints[i] = [];
5564
5565
for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
5566
this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
5567
}
5568
}
5569
},
5570
5571
_clipPoints: function () {
5572
var points = this._originalPoints,
5573
newParts = [];
5574
5575
this._parts = [points].concat(this._holePoints);
5576
5577
if (this.options.noClip) { return; }
5578
5579
for (var i = 0, len = this._parts.length; i < len; i++) {
5580
var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
5581
if (clipped.length) {
5582
newParts.push(clipped);
5583
}
5584
}
5585
5586
this._parts = newParts;
5587
},
5588
5589
_getPathPartStr: function (points) {
5590
var str = L.Polyline.prototype._getPathPartStr.call(this, points);
5591
return str + (L.Browser.svg ? 'z' : 'x');
5592
}
5593
});
5594
5595
L.polygon = function (latlngs, options) {
5596
return new L.Polygon(latlngs, options);
5597
};
5598
5599
5600
/*
5601
* Contains L.MultiPolyline and L.MultiPolygon layers.
5602
*/
5603
5604
(function () {
5605
function createMulti(Klass) {
5606
5607
return L.FeatureGroup.extend({
5608
5609
initialize: function (latlngs, options) {
5610
this._layers = {};
5611
this._options = options;
5612
this.setLatLngs(latlngs);
5613
},
5614
5615
setLatLngs: function (latlngs) {
5616
var i = 0,
5617
len = latlngs.length;
5618
5619
this.eachLayer(function (layer) {
5620
if (i < len) {
5621
layer.setLatLngs(latlngs[i++]);
5622
} else {
5623
this.removeLayer(layer);
5624
}
5625
}, this);
5626
5627
while (i < len) {
5628
this.addLayer(new Klass(latlngs[i++], this._options));
5629
}
5630
5631
return this;
5632
},
5633
5634
getLatLngs: function () {
5635
var latlngs = [];
5636
5637
this.eachLayer(function (layer) {
5638
latlngs.push(layer.getLatLngs());
5639
});
5640
5641
return latlngs;
5642
}
5643
});
5644
}
5645
5646
L.MultiPolyline = createMulti(L.Polyline);
5647
L.MultiPolygon = createMulti(L.Polygon);
5648
5649
L.multiPolyline = function (latlngs, options) {
5650
return new L.MultiPolyline(latlngs, options);
5651
};
5652
5653
L.multiPolygon = function (latlngs, options) {
5654
return new L.MultiPolygon(latlngs, options);
5655
};
5656
}());
5657
5658
5659
/*
5660
* L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
5661
*/
5662
5663
L.Rectangle = L.Polygon.extend({
5664
initialize: function (latLngBounds, options) {
5665
L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
5666
},
5667
5668
setBounds: function (latLngBounds) {
5669
this.setLatLngs(this._boundsToLatLngs(latLngBounds));
5670
},
5671
5672
_boundsToLatLngs: function (latLngBounds) {
5673
latLngBounds = L.latLngBounds(latLngBounds);
5674
return [
5675
latLngBounds.getSouthWest(),
5676
latLngBounds.getNorthWest(),
5677
latLngBounds.getNorthEast(),
5678
latLngBounds.getSouthEast()
5679
];
5680
}
5681
});
5682
5683
L.rectangle = function (latLngBounds, options) {
5684
return new L.Rectangle(latLngBounds, options);
5685
};
5686
5687
5688
/*
5689
* L.Circle is a circle overlay (with a certain radius in meters).
5690
*/
5691
5692
L.Circle = L.Path.extend({
5693
initialize: function (latlng, radius, options) {
5694
L.Path.prototype.initialize.call(this, options);
5695
5696
this._latlng = L.latLng(latlng);
5697
this._mRadius = radius;
5698
},
5699
5700
options: {
5701
fill: true
5702
},
5703
5704
setLatLng: function (latlng) {
5705
this._latlng = L.latLng(latlng);
5706
return this.redraw();
5707
},
5708
5709
setRadius: function (radius) {
5710
this._mRadius = radius;
5711
return this.redraw();
5712
},
5713
5714
projectLatlngs: function () {
5715
var lngRadius = this._getLngRadius(),
5716
latlng = this._latlng,
5717
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
5718
5719
this._point = this._map.latLngToLayerPoint(latlng);
5720
this._radius = Math.max(this._point.x - pointLeft.x, 1);
5721
},
5722
5723
getBounds: function () {
5724
var lngRadius = this._getLngRadius(),
5725
latRadius = (this._mRadius / 40075017) * 360,
5726
latlng = this._latlng;
5727
5728
return new L.LatLngBounds(
5729
[latlng.lat - latRadius, latlng.lng - lngRadius],
5730
[latlng.lat + latRadius, latlng.lng + lngRadius]);
5731
},
5732
5733
getLatLng: function () {
5734
return this._latlng;
5735
},
5736
5737
getPathString: function () {
5738
var p = this._point,
5739
r = this._radius;
5740
5741
if (this._checkIfEmpty()) {
5742
return '';
5743
}
5744
5745
if (L.Browser.svg) {
5746
return 'M' + p.x + ',' + (p.y - r) +
5747
'A' + r + ',' + r + ',0,1,1,' +
5748
(p.x - 0.1) + ',' + (p.y - r) + ' z';
5749
} else {
5750
p._round();
5751
r = Math.round(r);
5752
return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
5753
}
5754
},
5755
5756
getRadius: function () {
5757
return this._mRadius;
5758
},
5759
5760
// TODO Earth hardcoded, move into projection code!
5761
5762
_getLatRadius: function () {
5763
return (this._mRadius / 40075017) * 360;
5764
},
5765
5766
_getLngRadius: function () {
5767
return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
5768
},
5769
5770
_checkIfEmpty: function () {
5771
if (!this._map) {
5772
return false;
5773
}
5774
var vp = this._map._pathViewport,
5775
r = this._radius,
5776
p = this._point;
5777
5778
return p.x - r > vp.max.x || p.y - r > vp.max.y ||
5779
p.x + r < vp.min.x || p.y + r < vp.min.y;
5780
}
5781
});
5782
5783
L.circle = function (latlng, radius, options) {
5784
return new L.Circle(latlng, radius, options);
5785
};
5786
5787
5788
/*
5789
* L.CircleMarker is a circle overlay with a permanent pixel radius.
5790
*/
5791
5792
L.CircleMarker = L.Circle.extend({
5793
options: {
5794
radius: 10,
5795
weight: 2
5796
},
5797
5798
initialize: function (latlng, options) {
5799
L.Circle.prototype.initialize.call(this, latlng, null, options);
5800
this._radius = this.options.radius;
5801
},
5802
5803
projectLatlngs: function () {
5804
this._point = this._map.latLngToLayerPoint(this._latlng);
5805
},
5806
5807
_updateStyle : function () {
5808
L.Circle.prototype._updateStyle.call(this);
5809
this.setRadius(this.options.radius);
5810
},
5811
5812
setRadius: function (radius) {
5813
this.options.radius = this._radius = radius;
5814
return this.redraw();
5815
}
5816
});
5817
5818
L.circleMarker = function (latlng, options) {
5819
return new L.CircleMarker(latlng, options);
5820
};
5821
5822
5823
/*
5824
* Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
5825
*/
5826
5827
L.Polyline.include(!L.Path.CANVAS ? {} : {
5828
_containsPoint: function (p, closed) {
5829
var i, j, k, len, len2, dist, part,
5830
w = this.options.weight / 2;
5831
5832
if (L.Browser.touch) {
5833
w += 10; // polyline click tolerance on touch devices
5834
}
5835
5836
for (i = 0, len = this._parts.length; i < len; i++) {
5837
part = this._parts[i];
5838
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
5839
if (!closed && (j === 0)) {
5840
continue;
5841
}
5842
5843
dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
5844
5845
if (dist <= w) {
5846
return true;
5847
}
5848
}
5849
}
5850
return false;
5851
}
5852
});
5853
5854
5855
/*
5856
* Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
5857
*/
5858
5859
L.Polygon.include(!L.Path.CANVAS ? {} : {
5860
_containsPoint: function (p) {
5861
var inside = false,
5862
part, p1, p2,
5863
i, j, k,
5864
len, len2;
5865
5866
// TODO optimization: check if within bounds first
5867
5868
if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
5869
// click on polygon border
5870
return true;
5871
}
5872
5873
// ray casting algorithm for detecting if point is in polygon
5874
5875
for (i = 0, len = this._parts.length; i < len; i++) {
5876
part = this._parts[i];
5877
5878
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
5879
p1 = part[j];
5880
p2 = part[k];
5881
5882
if (((p1.y > p.y) !== (p2.y > p.y)) &&
5883
(p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
5884
inside = !inside;
5885
}
5886
}
5887
}
5888
5889
return inside;
5890
}
5891
});
5892
5893
5894
/*
5895
* Extends L.Circle with Canvas-specific code.
5896
*/
5897
5898
L.Circle.include(!L.Path.CANVAS ? {} : {
5899
_drawPath: function () {
5900
var p = this._point;
5901
this._ctx.beginPath();
5902
this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
5903
},
5904
5905
_containsPoint: function (p) {
5906
var center = this._point,
5907
w2 = this.options.stroke ? this.options.weight / 2 : 0;
5908
5909
return (p.distanceTo(center) <= this._radius + w2);
5910
}
5911
});
5912
5913
5914
/*
5915
* CircleMarker canvas specific drawing parts.
5916
*/
5917
5918
L.CircleMarker.include(!L.Path.CANVAS ? {} : {
5919
_updateStyle: function () {
5920
L.Path.prototype._updateStyle.call(this);
5921
}
5922
});
5923
5924
5925
/*
5926
* L.GeoJSON turns any GeoJSON data into a Leaflet layer.
5927
*/
5928
5929
L.GeoJSON = L.FeatureGroup.extend({
5930
5931
initialize: function (geojson, options) {
5932
L.setOptions(this, options);
5933
5934
this._layers = {};
5935
5936
if (geojson) {
5937
this.addData(geojson);
5938
}
5939
},
5940
5941
addData: function (geojson) {
5942
var features = L.Util.isArray(geojson) ? geojson : geojson.features,
5943
i, len, feature;
5944
5945
if (features) {
5946
for (i = 0, len = features.length; i < len; i++) {
5947
// Only add this if geometry or geometries are set and not null
5948
feature = features[i];
5949
if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
5950
this.addData(features[i]);
5951
}
5952
}
5953
return this;
5954
}
5955
5956
var options = this.options;
5957
5958
if (options.filter && !options.filter(geojson)) { return; }
5959
5960
var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng);
5961
layer.feature = L.GeoJSON.asFeature(geojson);
5962
5963
layer.defaultOptions = layer.options;
5964
this.resetStyle(layer);
5965
5966
if (options.onEachFeature) {
5967
options.onEachFeature(geojson, layer);
5968
}
5969
5970
return this.addLayer(layer);
5971
},
5972
5973
resetStyle: function (layer) {
5974
var style = this.options.style;
5975
if (style) {
5976
// reset any custom styles
5977
L.Util.extend(layer.options, layer.defaultOptions);
5978
5979
this._setLayerStyle(layer, style);
5980
}
5981
},
5982
5983
setStyle: function (style) {
5984
this.eachLayer(function (layer) {
5985
this._setLayerStyle(layer, style);
5986
}, this);
5987
},
5988
5989
_setLayerStyle: function (layer, style) {
5990
if (typeof style === 'function') {
5991
style = style(layer.feature);
5992
}
5993
if (layer.setStyle) {
5994
layer.setStyle(style);
5995
}
5996
}
5997
});
5998
5999
L.extend(L.GeoJSON, {
6000
geometryToLayer: function (geojson, pointToLayer, coordsToLatLng) {
6001
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
6002
coords = geometry.coordinates,
6003
layers = [],
6004
latlng, latlngs, i, len, layer;
6005
6006
coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
6007
6008
switch (geometry.type) {
6009
case 'Point':
6010
latlng = coordsToLatLng(coords);
6011
return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
6012
6013
case 'MultiPoint':
6014
for (i = 0, len = coords.length; i < len; i++) {
6015
latlng = coordsToLatLng(coords[i]);
6016
layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
6017
layers.push(layer);
6018
}
6019
return new L.FeatureGroup(layers);
6020
6021
case 'LineString':
6022
latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
6023
return new L.Polyline(latlngs);
6024
6025
case 'Polygon':
6026
latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
6027
return new L.Polygon(latlngs);
6028
6029
case 'MultiLineString':
6030
latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
6031
return new L.MultiPolyline(latlngs);
6032
6033
case 'MultiPolygon':
6034
latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
6035
return new L.MultiPolygon(latlngs);
6036
6037
case 'GeometryCollection':
6038
for (i = 0, len = geometry.geometries.length; i < len; i++) {
6039
6040
layer = this.geometryToLayer({
6041
geometry: geometry.geometries[i],
6042
type: 'Feature',
6043
properties: geojson.properties
6044
}, pointToLayer, coordsToLatLng);
6045
6046
layers.push(layer);
6047
}
6048
return new L.FeatureGroup(layers);
6049
6050
default:
6051
throw new Error('Invalid GeoJSON object.');
6052
}
6053
},
6054
6055
coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
6056
return new L.LatLng(coords[1], coords[0]);
6057
},
6058
6059
coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
6060
var latlng, i, len,
6061
latlngs = [];
6062
6063
for (i = 0, len = coords.length; i < len; i++) {
6064
latlng = levelsDeep ?
6065
this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
6066
(coordsToLatLng || this.coordsToLatLng)(coords[i]);
6067
6068
latlngs.push(latlng);
6069
}
6070
6071
return latlngs;
6072
},
6073
6074
latLngToCoords: function (latLng) {
6075
return [latLng.lng, latLng.lat];
6076
},
6077
6078
latLngsToCoords: function (latLngs) {
6079
var coords = [];
6080
6081
for (var i = 0, len = latLngs.length; i < len; i++) {
6082
coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));
6083
}
6084
6085
return coords;
6086
},
6087
6088
getFeature: function (layer, newGeometry) {
6089
return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);
6090
},
6091
6092
asFeature: function (geoJSON) {
6093
if (geoJSON.type === 'Feature') {
6094
return geoJSON;
6095
}
6096
6097
return {
6098
type: 'Feature',
6099
properties: {},
6100
geometry: geoJSON
6101
};
6102
}
6103
});
6104
6105
var PointToGeoJSON = {
6106
toGeoJSON: function () {
6107
return L.GeoJSON.getFeature(this, {
6108
type: 'Point',
6109
coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
6110
});
6111
}
6112
};
6113
6114
L.Marker.include(PointToGeoJSON);
6115
L.Circle.include(PointToGeoJSON);
6116
L.CircleMarker.include(PointToGeoJSON);
6117
6118
L.Polyline.include({
6119
toGeoJSON: function () {
6120
return L.GeoJSON.getFeature(this, {
6121
type: 'LineString',
6122
coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())
6123
});
6124
}
6125
});
6126
6127
L.Polygon.include({
6128
toGeoJSON: function () {
6129
var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],
6130
i, len, hole;
6131
6132
coords[0].push(coords[0][0]);
6133
6134
if (this._holes) {
6135
for (i = 0, len = this._holes.length; i < len; i++) {
6136
hole = L.GeoJSON.latLngsToCoords(this._holes[i]);
6137
hole.push(hole[0]);
6138
coords.push(hole);
6139
}
6140
}
6141
6142
return L.GeoJSON.getFeature(this, {
6143
type: 'Polygon',
6144
coordinates: coords
6145
});
6146
}
6147
});
6148
6149
(function () {
6150
function includeMulti(Klass, type) {
6151
Klass.include({
6152
toGeoJSON: function () {
6153
var coords = [];
6154
6155
this.eachLayer(function (layer) {
6156
coords.push(layer.toGeoJSON().geometry.coordinates);
6157
});
6158
6159
return L.GeoJSON.getFeature(this, {
6160
type: type,
6161
coordinates: coords
6162
});
6163
}
6164
});
6165
}
6166
6167
includeMulti(L.MultiPolyline, 'MultiLineString');
6168
includeMulti(L.MultiPolygon, 'MultiPolygon');
6169
}());
6170
6171
L.LayerGroup.include({
6172
toGeoJSON: function () {
6173
var features = [];
6174
6175
this.eachLayer(function (layer) {
6176
if (layer.toGeoJSON) {
6177
features.push(L.GeoJSON.asFeature(layer.toGeoJSON()));
6178
}
6179
});
6180
6181
return {
6182
type: 'FeatureCollection',
6183
features: features
6184
};
6185
}
6186
});
6187
6188
L.geoJson = function (geojson, options) {
6189
return new L.GeoJSON(geojson, options);
6190
};
6191
6192
6193
/*
6194
* L.DomEvent contains functions for working with DOM events.
6195
*/
6196
6197
L.DomEvent = {
6198
/* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
6199
addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
6200
6201
var id = L.stamp(fn),
6202
key = '_leaflet_' + type + id,
6203
handler, originalHandler, newType;
6204
6205
if (obj[key]) { return this; }
6206
6207
handler = function (e) {
6208
return fn.call(context || obj, e || L.DomEvent._getEvent());
6209
};
6210
6211
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6212
return this.addMsTouchListener(obj, type, handler, id);
6213
}
6214
if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
6215
this.addDoubleTapListener(obj, handler, id);
6216
}
6217
6218
if ('addEventListener' in obj) {
6219
6220
if (type === 'mousewheel') {
6221
obj.addEventListener('DOMMouseScroll', handler, false);
6222
obj.addEventListener(type, handler, false);
6223
6224
} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
6225
6226
originalHandler = handler;
6227
newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
6228
6229
handler = function (e) {
6230
if (!L.DomEvent._checkMouse(obj, e)) { return; }
6231
return originalHandler(e);
6232
};
6233
6234
obj.addEventListener(newType, handler, false);
6235
6236
} else if (type === 'click' && L.Browser.android) {
6237
originalHandler = handler;
6238
handler = function (e) {
6239
return L.DomEvent._filterClick(e, originalHandler);
6240
};
6241
6242
obj.addEventListener(type, handler, false);
6243
} else {
6244
obj.addEventListener(type, handler, false);
6245
}
6246
6247
} else if ('attachEvent' in obj) {
6248
obj.attachEvent('on' + type, handler);
6249
}
6250
6251
obj[key] = handler;
6252
6253
return this;
6254
},
6255
6256
removeListener: function (obj, type, fn) { // (HTMLElement, String, Function)
6257
6258
var id = L.stamp(fn),
6259
key = '_leaflet_' + type + id,
6260
handler = obj[key];
6261
6262
if (!handler) { return this; }
6263
6264
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
6265
this.removeMsTouchListener(obj, type, id);
6266
} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
6267
this.removeDoubleTapListener(obj, id);
6268
6269
} else if ('removeEventListener' in obj) {
6270
6271
if (type === 'mousewheel') {
6272
obj.removeEventListener('DOMMouseScroll', handler, false);
6273
obj.removeEventListener(type, handler, false);
6274
6275
} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
6276
obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
6277
} else {
6278
obj.removeEventListener(type, handler, false);
6279
}
6280
} else if ('detachEvent' in obj) {
6281
obj.detachEvent('on' + type, handler);
6282
}
6283
6284
obj[key] = null;
6285
6286
return this;
6287
},
6288
6289
stopPropagation: function (e) {
6290
6291
if (e.stopPropagation) {
6292
e.stopPropagation();
6293
} else {
6294
e.cancelBubble = true;
6295
}
6296
return this;
6297
},
6298
6299
disableClickPropagation: function (el) {
6300
var stop = L.DomEvent.stopPropagation;
6301
6302
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6303
L.DomEvent.addListener(el, L.Draggable.START[i], stop);
6304
}
6305
6306
return L.DomEvent
6307
.addListener(el, 'click', L.DomEvent._fakeStop)
6308
.addListener(el, 'dblclick', stop);
6309
},
6310
6311
preventDefault: function (e) {
6312
6313
if (e.preventDefault) {
6314
e.preventDefault();
6315
} else {
6316
e.returnValue = false;
6317
}
6318
return this;
6319
},
6320
6321
stop: function (e) {
6322
return L.DomEvent.preventDefault(e).stopPropagation(e);
6323
},
6324
6325
getMousePosition: function (e, container) {
6326
6327
var ie7 = L.Browser.ie7,
6328
body = document.body,
6329
docEl = document.documentElement,
6330
x = e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft: e.clientX,
6331
y = e.pageY ? e.pageY - body.scrollTop - docEl.scrollTop: e.clientY,
6332
pos = new L.Point(x, y),
6333
rect = container.getBoundingClientRect(),
6334
left = rect.left - container.clientLeft,
6335
top = rect.top - container.clientTop;
6336
6337
// webkit (and ie <= 7) handles RTL scrollLeft different to everyone else
6338
// https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js
6339
if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) {
6340
left += container.scrollWidth - container.clientWidth;
6341
6342
// ie7 shows the scrollbar by default and provides clientWidth counting it, so we
6343
// need to add it back in if it is visible; scrollbar is on the left as we are RTL
6344
if (ie7 && L.DomUtil.getStyle(container, 'overflow-y') !== 'hidden' &&
6345
L.DomUtil.getStyle(container, 'overflow') !== 'hidden') {
6346
left += 17;
6347
}
6348
}
6349
6350
return pos._subtract(new L.Point(left, top));
6351
},
6352
6353
getWheelDelta: function (e) {
6354
6355
var delta = 0;
6356
6357
if (e.wheelDelta) {
6358
delta = e.wheelDelta / 120;
6359
}
6360
if (e.detail) {
6361
delta = -e.detail / 3;
6362
}
6363
return delta;
6364
},
6365
6366
_skipEvents: {},
6367
6368
_fakeStop: function (e) {
6369
// fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
6370
L.DomEvent._skipEvents[e.type] = true;
6371
},
6372
6373
_skipped: function (e) {
6374
var skipped = this._skipEvents[e.type];
6375
// reset when checking, as it's only used in map container and propagates outside of the map
6376
this._skipEvents[e.type] = false;
6377
return skipped;
6378
},
6379
6380
// check if element really left/entered the event target (for mouseenter/mouseleave)
6381
_checkMouse: function (el, e) {
6382
6383
var related = e.relatedTarget;
6384
6385
if (!related) { return true; }
6386
6387
try {
6388
while (related && (related !== el)) {
6389
related = related.parentNode;
6390
}
6391
} catch (err) {
6392
return false;
6393
}
6394
return (related !== el);
6395
},
6396
6397
_getEvent: function () { // evil magic for IE
6398
/*jshint noarg:false */
6399
var e = window.event;
6400
if (!e) {
6401
var caller = arguments.callee.caller;
6402
while (caller) {
6403
e = caller['arguments'][0];
6404
if (e && window.Event === e.constructor) {
6405
break;
6406
}
6407
caller = caller.caller;
6408
}
6409
}
6410
return e;
6411
},
6412
6413
// this is a horrible workaround for a bug in Android where a single touch triggers two click events
6414
_filterClick: function (e, handler) {
6415
var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
6416
elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
6417
6418
// are they closer together than 1000ms yet more than 100ms?
6419
// Android typically triggers them ~300ms apart while multiple listeners
6420
// on the same event should be triggered far faster;
6421
// or check if click is simulated on the element, and if it is, reject any non-simulated events
6422
6423
if ((elapsed && elapsed > 100 && elapsed < 1000) || (e.target._simulatedClick && !e._simulated)) {
6424
L.DomEvent.stop(e);
6425
return;
6426
}
6427
L.DomEvent._lastClick = timeStamp;
6428
6429
return handler(e);
6430
}
6431
};
6432
6433
L.DomEvent.on = L.DomEvent.addListener;
6434
L.DomEvent.off = L.DomEvent.removeListener;
6435
6436
6437
/*
6438
* L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
6439
*/
6440
6441
L.Draggable = L.Class.extend({
6442
includes: L.Mixin.Events,
6443
6444
statics: {
6445
START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
6446
END: {
6447
mousedown: 'mouseup',
6448
touchstart: 'touchend',
6449
MSPointerDown: 'touchend'
6450
},
6451
MOVE: {
6452
mousedown: 'mousemove',
6453
touchstart: 'touchmove',
6454
MSPointerDown: 'touchmove'
6455
}
6456
},
6457
6458
initialize: function (element, dragStartTarget) {
6459
this._element = element;
6460
this._dragStartTarget = dragStartTarget || element;
6461
},
6462
6463
enable: function () {
6464
if (this._enabled) { return; }
6465
6466
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6467
L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
6468
}
6469
6470
this._enabled = true;
6471
},
6472
6473
disable: function () {
6474
if (!this._enabled) { return; }
6475
6476
for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
6477
L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
6478
}
6479
6480
this._enabled = false;
6481
this._moved = false;
6482
},
6483
6484
_onDown: function (e) {
6485
if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
6486
6487
L.DomEvent
6488
.stopPropagation(e);
6489
6490
if (L.Draggable._disabled) { return; }
6491
6492
L.DomUtil.disableImageDrag();
6493
L.DomUtil.disableTextSelection();
6494
6495
var first = e.touches ? e.touches[0] : e,
6496
el = first.target;
6497
6498
// if touching a link, highlight it
6499
if (L.Browser.touch && el.tagName.toLowerCase() === 'a') {
6500
L.DomUtil.addClass(el, 'leaflet-active');
6501
}
6502
6503
this._moved = false;
6504
6505
if (this._moving) { return; }
6506
6507
this._startPoint = new L.Point(first.clientX, first.clientY);
6508
this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
6509
6510
L.DomEvent
6511
.on(document, L.Draggable.MOVE[e.type], this._onMove, this)
6512
.on(document, L.Draggable.END[e.type], this._onUp, this);
6513
},
6514
6515
_onMove: function (e) {
6516
if (e.touches && e.touches.length > 1) { return; }
6517
6518
var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
6519
newPoint = new L.Point(first.clientX, first.clientY),
6520
offset = newPoint.subtract(this._startPoint);
6521
6522
if (!offset.x && !offset.y) { return; }
6523
6524
L.DomEvent.preventDefault(e);
6525
6526
if (!this._moved) {
6527
this.fire('dragstart');
6528
6529
this._moved = true;
6530
this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
6531
6532
if (!L.Browser.touch) {
6533
L.DomUtil.addClass(document.body, 'leaflet-dragging');
6534
}
6535
}
6536
6537
this._newPos = this._startPos.add(offset);
6538
this._moving = true;
6539
6540
L.Util.cancelAnimFrame(this._animRequest);
6541
this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
6542
},
6543
6544
_updatePosition: function () {
6545
this.fire('predrag');
6546
L.DomUtil.setPosition(this._element, this._newPos);
6547
this.fire('drag');
6548
},
6549
6550
_onUp: function () {
6551
if (!L.Browser.touch) {
6552
L.DomUtil.removeClass(document.body, 'leaflet-dragging');
6553
}
6554
6555
for (var i in L.Draggable.MOVE) {
6556
L.DomEvent
6557
.off(document, L.Draggable.MOVE[i], this._onMove)
6558
.off(document, L.Draggable.END[i], this._onUp);
6559
}
6560
6561
L.DomUtil.enableImageDrag();
6562
L.DomUtil.enableTextSelection();
6563
6564
if (this._moved) {
6565
// ensure drag is not fired after dragend
6566
L.Util.cancelAnimFrame(this._animRequest);
6567
6568
this.fire('dragend');
6569
}
6570
6571
this._moving = false;
6572
}
6573
});
6574
6575
6576
/*
6577
L.Handler is a base class for handler classes that are used internally to inject
6578
interaction features like dragging to classes like Map and Marker.
6579
*/
6580
6581
L.Handler = L.Class.extend({
6582
initialize: function (map) {
6583
this._map = map;
6584
},
6585
6586
enable: function () {
6587
if (this._enabled) { return; }
6588
6589
this._enabled = true;
6590
this.addHooks();
6591
},
6592
6593
disable: function () {
6594
if (!this._enabled) { return; }
6595
6596
this._enabled = false;
6597
this.removeHooks();
6598
},
6599
6600
enabled: function () {
6601
return !!this._enabled;
6602
}
6603
});
6604
6605
6606
/*
6607
* L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
6608
*/
6609
6610
L.Map.mergeOptions({
6611
dragging: true,
6612
6613
inertia: !L.Browser.android23,
6614
inertiaDeceleration: 3400, // px/s^2
6615
inertiaMaxSpeed: Infinity, // px/s
6616
inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
6617
easeLinearity: 0.25,
6618
6619
// TODO refactor, move to CRS
6620
worldCopyJump: false
6621
});
6622
6623
L.Map.Drag = L.Handler.extend({
6624
addHooks: function () {
6625
if (!this._draggable) {
6626
var map = this._map;
6627
6628
this._draggable = new L.Draggable(map._mapPane, map._container);
6629
6630
this._draggable.on({
6631
'dragstart': this._onDragStart,
6632
'drag': this._onDrag,
6633
'dragend': this._onDragEnd
6634
}, this);
6635
6636
if (map.options.worldCopyJump) {
6637
this._draggable.on('predrag', this._onPreDrag, this);
6638
map.on('viewreset', this._onViewReset, this);
6639
6640
this._onViewReset();
6641
}
6642
}
6643
this._draggable.enable();
6644
},
6645
6646
removeHooks: function () {
6647
this._draggable.disable();
6648
},
6649
6650
moved: function () {
6651
return this._draggable && this._draggable._moved;
6652
},
6653
6654
_onDragStart: function () {
6655
var map = this._map;
6656
6657
if (map._panAnim) {
6658
map._panAnim.stop();
6659
}
6660
6661
map
6662
.fire('movestart')
6663
.fire('dragstart');
6664
6665
if (map.options.inertia) {
6666
this._positions = [];
6667
this._times = [];
6668
}
6669
},
6670
6671
_onDrag: function () {
6672
if (this._map.options.inertia) {
6673
var time = this._lastTime = +new Date(),
6674
pos = this._lastPos = this._draggable._newPos;
6675
6676
this._positions.push(pos);
6677
this._times.push(time);
6678
6679
if (time - this._times[0] > 200) {
6680
this._positions.shift();
6681
this._times.shift();
6682
}
6683
}
6684
6685
this._map
6686
.fire('move')
6687
.fire('drag');
6688
},
6689
6690
_onViewReset: function () {
6691
// TODO fix hardcoded Earth values
6692
var pxCenter = this._map.getSize()._divideBy(2),
6693
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
6694
6695
this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
6696
this._worldWidth = this._map.project([0, 180]).x;
6697
},
6698
6699
_onPreDrag: function () {
6700
// TODO refactor to be able to adjust map pane position after zoom
6701
var worldWidth = this._worldWidth,
6702
halfWidth = Math.round(worldWidth / 2),
6703
dx = this._initialWorldOffset,
6704
x = this._draggable._newPos.x,
6705
newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
6706
newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
6707
newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
6708
6709
this._draggable._newPos.x = newX;
6710
},
6711
6712
_onDragEnd: function () {
6713
var map = this._map,
6714
options = map.options,
6715
delay = +new Date() - this._lastTime,
6716
6717
noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
6718
6719
map.fire('dragend');
6720
6721
if (noInertia) {
6722
map.fire('moveend');
6723
6724
} else {
6725
6726
var direction = this._lastPos.subtract(this._positions[0]),
6727
duration = (this._lastTime + delay - this._times[0]) / 1000,
6728
ease = options.easeLinearity,
6729
6730
speedVector = direction.multiplyBy(ease / duration),
6731
speed = speedVector.distanceTo([0, 0]),
6732
6733
limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
6734
limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
6735
6736
decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
6737
offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
6738
6739
if (!offset.x || !offset.y) {
6740
map.fire('moveend');
6741
6742
} else {
6743
L.Util.requestAnimFrame(function () {
6744
map.panBy(offset, {
6745
duration: decelerationDuration,
6746
easeLinearity: ease,
6747
noMoveStart: true
6748
});
6749
});
6750
}
6751
}
6752
}
6753
});
6754
6755
L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
6756
6757
6758
/*
6759
* L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
6760
*/
6761
6762
L.Map.mergeOptions({
6763
doubleClickZoom: true
6764
});
6765
6766
L.Map.DoubleClickZoom = L.Handler.extend({
6767
addHooks: function () {
6768
this._map.on('dblclick', this._onDoubleClick);
6769
},
6770
6771
removeHooks: function () {
6772
this._map.off('dblclick', this._onDoubleClick);
6773
},
6774
6775
_onDoubleClick: function (e) {
6776
this.setZoomAround(e.containerPoint, this._zoom + 1);
6777
}
6778
});
6779
6780
L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
6781
6782
6783
/*
6784
* L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
6785
*/
6786
6787
L.Map.mergeOptions({
6788
scrollWheelZoom: true
6789
});
6790
6791
L.Map.ScrollWheelZoom = L.Handler.extend({
6792
addHooks: function () {
6793
L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
6794
L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
6795
this._delta = 0;
6796
},
6797
6798
removeHooks: function () {
6799
L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
6800
L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
6801
},
6802
6803
_onWheelScroll: function (e) {
6804
var delta = L.DomEvent.getWheelDelta(e);
6805
6806
this._delta += delta;
6807
this._lastMousePos = this._map.mouseEventToContainerPoint(e);
6808
6809
if (!this._startTime) {
6810
this._startTime = +new Date();
6811
}
6812
6813
var left = Math.max(40 - (+new Date() - this._startTime), 0);
6814
6815
clearTimeout(this._timer);
6816
this._timer = setTimeout(L.bind(this._performZoom, this), left);
6817
6818
L.DomEvent.preventDefault(e);
6819
L.DomEvent.stopPropagation(e);
6820
},
6821
6822
_performZoom: function () {
6823
var map = this._map,
6824
delta = this._delta,
6825
zoom = map.getZoom();
6826
6827
delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
6828
delta = Math.max(Math.min(delta, 4), -4);
6829
delta = map._limitZoom(zoom + delta) - zoom;
6830
6831
this._delta = 0;
6832
this._startTime = null;
6833
6834
if (!delta) { return; }
6835
6836
map.setZoomAround(this._lastMousePos, zoom + delta);
6837
}
6838
});
6839
6840
L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
6841
6842
6843
/*
6844
* Extends the event handling code with double tap support for mobile browsers.
6845
*/
6846
6847
L.extend(L.DomEvent, {
6848
6849
_touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
6850
_touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend',
6851
6852
// inspired by Zepto touch code by Thomas Fuchs
6853
addDoubleTapListener: function (obj, handler, id) {
6854
var last,
6855
doubleTap = false,
6856
delay = 250,
6857
touch,
6858
pre = '_leaflet_',
6859
touchstart = this._touchstart,
6860
touchend = this._touchend,
6861
trackedTouches = [];
6862
6863
function onTouchStart(e) {
6864
var count;
6865
6866
if (L.Browser.msTouch) {
6867
trackedTouches.push(e.pointerId);
6868
count = trackedTouches.length;
6869
} else {
6870
count = e.touches.length;
6871
}
6872
if (count > 1) {
6873
return;
6874
}
6875
6876
var now = Date.now(),
6877
delta = now - (last || now);
6878
6879
touch = e.touches ? e.touches[0] : e;
6880
doubleTap = (delta > 0 && delta <= delay);
6881
last = now;
6882
}
6883
6884
function onTouchEnd(e) {
6885
if (L.Browser.msTouch) {
6886
var idx = trackedTouches.indexOf(e.pointerId);
6887
if (idx === -1) {
6888
return;
6889
}
6890
trackedTouches.splice(idx, 1);
6891
}
6892
6893
if (doubleTap) {
6894
if (L.Browser.msTouch) {
6895
// work around .type being readonly with MSPointer* events
6896
var newTouch = { },
6897
prop;
6898
6899
// jshint forin:false
6900
for (var i in touch) {
6901
prop = touch[i];
6902
if (typeof prop === 'function') {
6903
newTouch[i] = prop.bind(touch);
6904
} else {
6905
newTouch[i] = prop;
6906
}
6907
}
6908
touch = newTouch;
6909
}
6910
touch.type = 'dblclick';
6911
handler(touch);
6912
last = null;
6913
}
6914
}
6915
obj[pre + touchstart + id] = onTouchStart;
6916
obj[pre + touchend + id] = onTouchEnd;
6917
6918
// on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen
6919
// will not come through to us, so we will lose track of how many touches are ongoing
6920
var endElement = L.Browser.msTouch ? document.documentElement : obj;
6921
6922
obj.addEventListener(touchstart, onTouchStart, false);
6923
endElement.addEventListener(touchend, onTouchEnd, false);
6924
6925
if (L.Browser.msTouch) {
6926
endElement.addEventListener('MSPointerCancel', onTouchEnd, false);
6927
}
6928
6929
return this;
6930
},
6931
6932
removeDoubleTapListener: function (obj, id) {
6933
var pre = '_leaflet_';
6934
6935
obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
6936
(L.Browser.msTouch ? document.documentElement : obj).removeEventListener(
6937
this._touchend, obj[pre + this._touchend + id], false);
6938
6939
if (L.Browser.msTouch) {
6940
document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false);
6941
}
6942
6943
return this;
6944
}
6945
});
6946
6947
6948
/*
6949
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
6950
*/
6951
6952
L.extend(L.DomEvent, {
6953
6954
_msTouches: [],
6955
_msDocumentListener: false,
6956
6957
// Provides a touch events wrapper for msPointer events.
6958
// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
6959
6960
addMsTouchListener: function (obj, type, handler, id) {
6961
6962
switch (type) {
6963
case 'touchstart':
6964
return this.addMsTouchListenerStart(obj, type, handler, id);
6965
case 'touchend':
6966
return this.addMsTouchListenerEnd(obj, type, handler, id);
6967
case 'touchmove':
6968
return this.addMsTouchListenerMove(obj, type, handler, id);
6969
default:
6970
throw 'Unknown touch event type';
6971
}
6972
},
6973
6974
addMsTouchListenerStart: function (obj, type, handler, id) {
6975
var pre = '_leaflet_',
6976
touches = this._msTouches;
6977
6978
var cb = function (e) {
6979
6980
var alreadyInArray = false;
6981
for (var i = 0; i < touches.length; i++) {
6982
if (touches[i].pointerId === e.pointerId) {
6983
alreadyInArray = true;
6984
break;
6985
}
6986
}
6987
if (!alreadyInArray) {
6988
touches.push(e);
6989
}
6990
6991
e.touches = touches.slice();
6992
e.changedTouches = [e];
6993
6994
handler(e);
6995
};
6996
6997
obj[pre + 'touchstart' + id] = cb;
6998
obj.addEventListener('MSPointerDown', cb, false);
6999
7000
// need to also listen for end events to keep the _msTouches list accurate
7001
// this needs to be on the body and never go away
7002
if (!this._msDocumentListener) {
7003
var internalCb = function (e) {
7004
for (var i = 0; i < touches.length; i++) {
7005
if (touches[i].pointerId === e.pointerId) {
7006
touches.splice(i, 1);
7007
break;
7008
}
7009
}
7010
};
7011
//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
7012
document.documentElement.addEventListener('MSPointerUp', internalCb, false);
7013
document.documentElement.addEventListener('MSPointerCancel', internalCb, false);
7014
7015
this._msDocumentListener = true;
7016
}
7017
7018
return this;
7019
},
7020
7021
addMsTouchListenerMove: function (obj, type, handler, id) {
7022
var pre = '_leaflet_',
7023
touches = this._msTouches;
7024
7025
function cb(e) {
7026
7027
// don't fire touch moves when mouse isn't down
7028
if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; }
7029
7030
for (var i = 0; i < touches.length; i++) {
7031
if (touches[i].pointerId === e.pointerId) {
7032
touches[i] = e;
7033
break;
7034
}
7035
}
7036
7037
e.touches = touches.slice();
7038
e.changedTouches = [e];
7039
7040
handler(e);
7041
}
7042
7043
obj[pre + 'touchmove' + id] = cb;
7044
obj.addEventListener('MSPointerMove', cb, false);
7045
7046
return this;
7047
},
7048
7049
addMsTouchListenerEnd: function (obj, type, handler, id) {
7050
var pre = '_leaflet_',
7051
touches = this._msTouches;
7052
7053
var cb = function (e) {
7054
for (var i = 0; i < touches.length; i++) {
7055
if (touches[i].pointerId === e.pointerId) {
7056
touches.splice(i, 1);
7057
break;
7058
}
7059
}
7060
7061
e.touches = touches.slice();
7062
e.changedTouches = [e];
7063
7064
handler(e);
7065
};
7066
7067
obj[pre + 'touchend' + id] = cb;
7068
obj.addEventListener('MSPointerUp', cb, false);
7069
obj.addEventListener('MSPointerCancel', cb, false);
7070
7071
return this;
7072
},
7073
7074
removeMsTouchListener: function (obj, type, id) {
7075
var pre = '_leaflet_',
7076
cb = obj[pre + type + id];
7077
7078
switch (type) {
7079
case 'touchstart':
7080
obj.removeEventListener('MSPointerDown', cb, false);
7081
break;
7082
case 'touchmove':
7083
obj.removeEventListener('MSPointerMove', cb, false);
7084
break;
7085
case 'touchend':
7086
obj.removeEventListener('MSPointerUp', cb, false);
7087
obj.removeEventListener('MSPointerCancel', cb, false);
7088
break;
7089
}
7090
7091
return this;
7092
}
7093
});
7094
7095
7096
/*
7097
* L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
7098
*/
7099
7100
L.Map.mergeOptions({
7101
touchZoom: L.Browser.touch && !L.Browser.android23
7102
});
7103
7104
L.Map.TouchZoom = L.Handler.extend({
7105
addHooks: function () {
7106
L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
7107
},
7108
7109
removeHooks: function () {
7110
L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
7111
},
7112
7113
_onTouchStart: function (e) {
7114
var map = this._map;
7115
7116
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
7117
7118
var p1 = map.mouseEventToLayerPoint(e.touches[0]),
7119
p2 = map.mouseEventToLayerPoint(e.touches[1]),
7120
viewCenter = map._getCenterLayerPoint();
7121
7122
this._startCenter = p1.add(p2)._divideBy(2);
7123
this._startDist = p1.distanceTo(p2);
7124
7125
this._moved = false;
7126
this._zooming = true;
7127
7128
this._centerOffset = viewCenter.subtract(this._startCenter);
7129
7130
if (map._panAnim) {
7131
map._panAnim.stop();
7132
}
7133
7134
L.DomEvent
7135
.on(document, 'touchmove', this._onTouchMove, this)
7136
.on(document, 'touchend', this._onTouchEnd, this);
7137
7138
L.DomEvent.preventDefault(e);
7139
},
7140
7141
_onTouchMove: function (e) {
7142
var map = this._map;
7143
7144
if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
7145
7146
var p1 = map.mouseEventToLayerPoint(e.touches[0]),
7147
p2 = map.mouseEventToLayerPoint(e.touches[1]);
7148
7149
this._scale = p1.distanceTo(p2) / this._startDist;
7150
this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
7151
7152
if (this._scale === 1) { return; }
7153
7154
if (!this._moved) {
7155
L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
7156
7157
map
7158
.fire('movestart')
7159
.fire('zoomstart');
7160
7161
this._moved = true;
7162
}
7163
7164
L.Util.cancelAnimFrame(this._animRequest);
7165
this._animRequest = L.Util.requestAnimFrame(
7166
this._updateOnMove, this, true, this._map._container);
7167
7168
L.DomEvent.preventDefault(e);
7169
},
7170
7171
_updateOnMove: function () {
7172
var map = this._map,
7173
origin = this._getScaleOrigin(),
7174
center = map.layerPointToLatLng(origin),
7175
zoom = map.getScaleZoom(this._scale);
7176
7177
map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta);
7178
},
7179
7180
_onTouchEnd: function () {
7181
if (!this._moved || !this._zooming) {
7182
this._zooming = false;
7183
return;
7184
}
7185
7186
var map = this._map;
7187
7188
this._zooming = false;
7189
L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
7190
L.Util.cancelAnimFrame(this._animRequest);
7191
7192
L.DomEvent
7193
.off(document, 'touchmove', this._onTouchMove)
7194
.off(document, 'touchend', this._onTouchEnd);
7195
7196
var origin = this._getScaleOrigin(),
7197
center = map.layerPointToLatLng(origin),
7198
7199
oldZoom = map.getZoom(),
7200
floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
7201
roundZoomDelta = (floatZoomDelta > 0 ?
7202
Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
7203
7204
zoom = map._limitZoom(oldZoom + roundZoomDelta),
7205
scale = map.getZoomScale(zoom) / this._scale;
7206
7207
map._animateZoom(center, zoom, origin, scale);
7208
},
7209
7210
_getScaleOrigin: function () {
7211
var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
7212
return this._startCenter.add(centerOffset);
7213
}
7214
});
7215
7216
L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
7217
7218
7219
/*
7220
* L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
7221
*/
7222
7223
L.Map.mergeOptions({
7224
tap: true,
7225
tapTolerance: 15
7226
});
7227
7228
L.Map.Tap = L.Handler.extend({
7229
addHooks: function () {
7230
L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
7231
},
7232
7233
removeHooks: function () {
7234
L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
7235
},
7236
7237
_onDown: function (e) {
7238
if (!e.touches) { return; }
7239
7240
L.DomEvent.preventDefault(e);
7241
7242
this._fireClick = true;
7243
7244
// don't simulate click or track longpress if more than 1 touch
7245
if (e.touches.length > 1) {
7246
this._fireClick = false;
7247
clearTimeout(this._holdTimeout);
7248
return;
7249
}
7250
7251
var first = e.touches[0],
7252
el = first.target;
7253
7254
this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
7255
7256
// if touching a link, highlight it
7257
if (el.tagName.toLowerCase() === 'a') {
7258
L.DomUtil.addClass(el, 'leaflet-active');
7259
}
7260
7261
// simulate long hold but setting a timeout
7262
this._holdTimeout = setTimeout(L.bind(function () {
7263
if (this._isTapValid()) {
7264
this._fireClick = false;
7265
this._onUp();
7266
this._simulateEvent('contextmenu', first);
7267
}
7268
}, this), 1000);
7269
7270
L.DomEvent
7271
.on(document, 'touchmove', this._onMove, this)
7272
.on(document, 'touchend', this._onUp, this);
7273
},
7274
7275
_onUp: function (e) {
7276
clearTimeout(this._holdTimeout);
7277
7278
L.DomEvent
7279
.off(document, 'touchmove', this._onMove, this)
7280
.off(document, 'touchend', this._onUp, this);
7281
7282
if (this._fireClick && e && e.changedTouches) {
7283
7284
var first = e.changedTouches[0],
7285
el = first.target;
7286
7287
if (el.tagName.toLowerCase() === 'a') {
7288
L.DomUtil.removeClass(el, 'leaflet-active');
7289
}
7290
7291
// simulate click if the touch didn't move too much
7292
if (this._isTapValid()) {
7293
this._simulateEvent('click', first);
7294
}
7295
}
7296
},
7297
7298
_isTapValid: function () {
7299
return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
7300
},
7301
7302
_onMove: function (e) {
7303
var first = e.touches[0];
7304
this._newPos = new L.Point(first.clientX, first.clientY);
7305
},
7306
7307
_simulateEvent: function (type, e) {
7308
var simulatedEvent = document.createEvent('MouseEvents');
7309
7310
simulatedEvent._simulated = true;
7311
e.target._simulatedClick = true;
7312
7313
simulatedEvent.initMouseEvent(
7314
type, true, true, window, 1,
7315
e.screenX, e.screenY,
7316
e.clientX, e.clientY,
7317
false, false, false, false, 0, null);
7318
7319
e.target.dispatchEvent(simulatedEvent);
7320
}
7321
});
7322
7323
if (L.Browser.touch && !L.Browser.msTouch) {
7324
L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
7325
}
7326
7327
7328
/*
7329
* L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
7330
* (zoom to a selected bounding box), enabled by default.
7331
*/
7332
7333
L.Map.mergeOptions({
7334
boxZoom: true
7335
});
7336
7337
L.Map.BoxZoom = L.Handler.extend({
7338
initialize: function (map) {
7339
this._map = map;
7340
this._container = map._container;
7341
this._pane = map._panes.overlayPane;
7342
},
7343
7344
addHooks: function () {
7345
L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
7346
},
7347
7348
removeHooks: function () {
7349
L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
7350
},
7351
7352
_onMouseDown: function (e) {
7353
if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
7354
7355
L.DomUtil.disableTextSelection();
7356
L.DomUtil.disableImageDrag();
7357
7358
this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
7359
7360
this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
7361
L.DomUtil.setPosition(this._box, this._startLayerPoint);
7362
7363
//TODO refactor: move cursor to styles
7364
this._container.style.cursor = 'crosshair';
7365
7366
L.DomEvent
7367
.on(document, 'mousemove', this._onMouseMove, this)
7368
.on(document, 'mouseup', this._onMouseUp, this)
7369
.on(document, 'keydown', this._onKeyDown, this);
7370
7371
this._map.fire('boxzoomstart');
7372
},
7373
7374
_onMouseMove: function (e) {
7375
var startPoint = this._startLayerPoint,
7376
box = this._box,
7377
7378
layerPoint = this._map.mouseEventToLayerPoint(e),
7379
offset = layerPoint.subtract(startPoint),
7380
7381
newPos = new L.Point(
7382
Math.min(layerPoint.x, startPoint.x),
7383
Math.min(layerPoint.y, startPoint.y));
7384
7385
L.DomUtil.setPosition(box, newPos);
7386
7387
// TODO refactor: remove hardcoded 4 pixels
7388
box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
7389
box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
7390
},
7391
7392
_finish: function () {
7393
this._pane.removeChild(this._box);
7394
this._container.style.cursor = '';
7395
7396
L.DomUtil.enableTextSelection();
7397
L.DomUtil.enableImageDrag();
7398
7399
L.DomEvent
7400
.off(document, 'mousemove', this._onMouseMove)
7401
.off(document, 'mouseup', this._onMouseUp)
7402
.off(document, 'keydown', this._onKeyDown);
7403
},
7404
7405
_onMouseUp: function (e) {
7406
7407
this._finish();
7408
7409
var map = this._map,
7410
layerPoint = map.mouseEventToLayerPoint(e);
7411
7412
if (this._startLayerPoint.equals(layerPoint)) { return; }
7413
7414
var bounds = new L.LatLngBounds(
7415
map.layerPointToLatLng(this._startLayerPoint),
7416
map.layerPointToLatLng(layerPoint));
7417
7418
map.fitBounds(bounds);
7419
7420
map.fire('boxzoomend', {
7421
boxZoomBounds: bounds
7422
});
7423
},
7424
7425
_onKeyDown: function (e) {
7426
if (e.keyCode === 27) {
7427
this._finish();
7428
}
7429
}
7430
});
7431
7432
L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
7433
7434
7435
/*
7436
* L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
7437
*/
7438
7439
L.Map.mergeOptions({
7440
keyboard: true,
7441
keyboardPanOffset: 80,
7442
keyboardZoomOffset: 1
7443
});
7444
7445
L.Map.Keyboard = L.Handler.extend({
7446
7447
keyCodes: {
7448
left: [37],
7449
right: [39],
7450
down: [40],
7451
up: [38],
7452
zoomIn: [187, 107, 61],
7453
zoomOut: [189, 109, 173]
7454
},
7455
7456
initialize: function (map) {
7457
this._map = map;
7458
7459
this._setPanOffset(map.options.keyboardPanOffset);
7460
this._setZoomOffset(map.options.keyboardZoomOffset);
7461
},
7462
7463
addHooks: function () {
7464
var container = this._map._container;
7465
7466
// make the container focusable by tabbing
7467
if (container.tabIndex === -1) {
7468
container.tabIndex = '0';
7469
}
7470
7471
L.DomEvent
7472
.on(container, 'focus', this._onFocus, this)
7473
.on(container, 'blur', this._onBlur, this)
7474
.on(container, 'mousedown', this._onMouseDown, this);
7475
7476
this._map
7477
.on('focus', this._addHooks, this)
7478
.on('blur', this._removeHooks, this);
7479
},
7480
7481
removeHooks: function () {
7482
this._removeHooks();
7483
7484
var container = this._map._container;
7485
7486
L.DomEvent
7487
.off(container, 'focus', this._onFocus, this)
7488
.off(container, 'blur', this._onBlur, this)
7489
.off(container, 'mousedown', this._onMouseDown, this);
7490
7491
this._map
7492
.off('focus', this._addHooks, this)
7493
.off('blur', this._removeHooks, this);
7494
},
7495
7496
_onMouseDown: function () {
7497
if (this._focused) { return; }
7498
7499
var body = document.body,
7500
docEl = document.documentElement,
7501
top = body.scrollTop || docEl.scrollTop,
7502
left = body.scrollLeft || docEl.scrollLeft;
7503
7504
this._map._container.focus();
7505
7506
window.scrollTo(left, top);
7507
},
7508
7509
_onFocus: function () {
7510
this._focused = true;
7511
this._map.fire('focus');
7512
},
7513
7514
_onBlur: function () {
7515
this._focused = false;
7516
this._map.fire('blur');
7517
},
7518
7519
_setPanOffset: function (pan) {
7520
var keys = this._panKeys = {},
7521
codes = this.keyCodes,
7522
i, len;
7523
7524
for (i = 0, len = codes.left.length; i < len; i++) {
7525
keys[codes.left[i]] = [-1 * pan, 0];
7526
}
7527
for (i = 0, len = codes.right.length; i < len; i++) {
7528
keys[codes.right[i]] = [pan, 0];
7529
}
7530
for (i = 0, len = codes.down.length; i < len; i++) {
7531
keys[codes.down[i]] = [0, pan];
7532
}
7533
for (i = 0, len = codes.up.length; i < len; i++) {
7534
keys[codes.up[i]] = [0, -1 * pan];
7535
}
7536
},
7537
7538
_setZoomOffset: function (zoom) {
7539
var keys = this._zoomKeys = {},
7540
codes = this.keyCodes,
7541
i, len;
7542
7543
for (i = 0, len = codes.zoomIn.length; i < len; i++) {
7544
keys[codes.zoomIn[i]] = zoom;
7545
}
7546
for (i = 0, len = codes.zoomOut.length; i < len; i++) {
7547
keys[codes.zoomOut[i]] = -zoom;
7548
}
7549
},
7550
7551
_addHooks: function () {
7552
L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
7553
},
7554
7555
_removeHooks: function () {
7556
L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
7557
},
7558
7559
_onKeyDown: function (e) {
7560
var key = e.keyCode,
7561
map = this._map;
7562
7563
if (key in this._panKeys) {
7564
7565
if (map._panAnim && map._panAnim._inProgress) { return; }
7566
7567
map.panBy(this._panKeys[key]);
7568
7569
if (map.options.maxBounds) {
7570
map.panInsideBounds(map.options.maxBounds);
7571
}
7572
7573
} else if (key in this._zoomKeys) {
7574
map.setZoom(map.getZoom() + this._zoomKeys[key]);
7575
7576
} else {
7577
return;
7578
}
7579
7580
L.DomEvent.stop(e);
7581
}
7582
});
7583
7584
L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
7585
7586
7587
/*
7588
* L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7589
*/
7590
7591
L.Handler.MarkerDrag = L.Handler.extend({
7592
initialize: function (marker) {
7593
this._marker = marker;
7594
},
7595
7596
addHooks: function () {
7597
var icon = this._marker._icon;
7598
if (!this._draggable) {
7599
this._draggable = new L.Draggable(icon, icon);
7600
}
7601
7602
this._draggable
7603
.on('dragstart', this._onDragStart, this)
7604
.on('drag', this._onDrag, this)
7605
.on('dragend', this._onDragEnd, this);
7606
this._draggable.enable();
7607
},
7608
7609
removeHooks: function () {
7610
this._draggable
7611
.off('dragstart', this._onDragStart, this)
7612
.off('drag', this._onDrag, this)
7613
.off('dragend', this._onDragEnd, this);
7614
7615
this._draggable.disable();
7616
},
7617
7618
moved: function () {
7619
return this._draggable && this._draggable._moved;
7620
},
7621
7622
_onDragStart: function () {
7623
this._marker
7624
.closePopup()
7625
.fire('movestart')
7626
.fire('dragstart');
7627
},
7628
7629
_onDrag: function () {
7630
var marker = this._marker,
7631
shadow = marker._shadow,
7632
iconPos = L.DomUtil.getPosition(marker._icon),
7633
latlng = marker._map.layerPointToLatLng(iconPos);
7634
7635
// update shadow position
7636
if (shadow) {
7637
L.DomUtil.setPosition(shadow, iconPos);
7638
}
7639
7640
marker._latlng = latlng;
7641
7642
marker
7643
.fire('move', {latlng: latlng})
7644
.fire('drag');
7645
},
7646
7647
_onDragEnd: function () {
7648
this._marker
7649
.fire('moveend')
7650
.fire('dragend');
7651
}
7652
});
7653
7654
7655
/*
7656
* L.Control is a base class for implementing map controls. Handles positioning.
7657
* All other controls extend from this class.
7658
*/
7659
7660
L.Control = L.Class.extend({
7661
options: {
7662
position: 'topright'
7663
},
7664
7665
initialize: function (options) {
7666
L.setOptions(this, options);
7667
},
7668
7669
getPosition: function () {
7670
return this.options.position;
7671
},
7672
7673
setPosition: function (position) {
7674
var map = this._map;
7675
7676
if (map) {
7677
map.removeControl(this);
7678
}
7679
7680
this.options.position = position;
7681
7682
if (map) {
7683
map.addControl(this);
7684
}
7685
7686
return this;
7687
},
7688
7689
getContainer: function () {
7690
return this._container;
7691
},
7692
7693
addTo: function (map) {
7694
this._map = map;
7695
7696
var container = this._container = this.onAdd(map),
7697
pos = this.getPosition(),
7698
corner = map._controlCorners[pos];
7699
7700
L.DomUtil.addClass(container, 'leaflet-control');
7701
7702
if (pos.indexOf('bottom') !== -1) {
7703
corner.insertBefore(container, corner.firstChild);
7704
} else {
7705
corner.appendChild(container);
7706
}
7707
7708
return this;
7709
},
7710
7711
removeFrom: function (map) {
7712
var pos = this.getPosition(),
7713
corner = map._controlCorners[pos];
7714
7715
corner.removeChild(this._container);
7716
this._map = null;
7717
7718
if (this.onRemove) {
7719
this.onRemove(map);
7720
}
7721
7722
return this;
7723
}
7724
});
7725
7726
L.control = function (options) {
7727
return new L.Control(options);
7728
};
7729
7730
7731
// adds control-related methods to L.Map
7732
7733
L.Map.include({
7734
addControl: function (control) {
7735
control.addTo(this);
7736
return this;
7737
},
7738
7739
removeControl: function (control) {
7740
control.removeFrom(this);
7741
return this;
7742
},
7743
7744
_initControlPos: function () {
7745
var corners = this._controlCorners = {},
7746
l = 'leaflet-',
7747
container = this._controlContainer =
7748
L.DomUtil.create('div', l + 'control-container', this._container);
7749
7750
function createCorner(vSide, hSide) {
7751
var className = l + vSide + ' ' + l + hSide;
7752
7753
corners[vSide + hSide] = L.DomUtil.create('div', className, container);
7754
}
7755
7756
createCorner('top', 'left');
7757
createCorner('top', 'right');
7758
createCorner('bottom', 'left');
7759
createCorner('bottom', 'right');
7760
},
7761
7762
_clearControlPos: function () {
7763
this._container.removeChild(this._controlContainer);
7764
}
7765
});
7766
7767
7768
/*
7769
* L.Control.Zoom is used for the default zoom buttons on the map.
7770
*/
7771
7772
L.Control.Zoom = L.Control.extend({
7773
options: {
7774
position: 'topleft'
7775
},
7776
7777
onAdd: function (map) {
7778
var zoomName = 'leaflet-control-zoom',
7779
container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
7780
7781
this._map = map;
7782
7783
this._zoomInButton = this._createButton(
7784
'+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this);
7785
this._zoomOutButton = this._createButton(
7786
'-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this);
7787
7788
map.on('zoomend zoomlevelschange', this._updateDisabled, this);
7789
7790
return container;
7791
},
7792
7793
onRemove: function (map) {
7794
map.off('zoomend zoomlevelschange', this._updateDisabled, this);
7795
},
7796
7797
_zoomIn: function (e) {
7798
this._map.zoomIn(e.shiftKey ? 3 : 1);
7799
},
7800
7801
_zoomOut: function (e) {
7802
this._map.zoomOut(e.shiftKey ? 3 : 1);
7803
},
7804
7805
_createButton: function (html, title, className, container, fn, context) {
7806
var link = L.DomUtil.create('a', className, container);
7807
link.innerHTML = html;
7808
link.href = '#';
7809
link.title = title;
7810
7811
var stop = L.DomEvent.stopPropagation;
7812
7813
L.DomEvent
7814
.on(link, 'click', stop)
7815
.on(link, 'mousedown', stop)
7816
.on(link, 'dblclick', stop)
7817
.on(link, 'click', L.DomEvent.preventDefault)
7818
.on(link, 'click', fn, context);
7819
7820
return link;
7821
},
7822
7823
_updateDisabled: function () {
7824
var map = this._map,
7825
className = 'leaflet-disabled';
7826
7827
L.DomUtil.removeClass(this._zoomInButton, className);
7828
L.DomUtil.removeClass(this._zoomOutButton, className);
7829
7830
if (map._zoom === map.getMinZoom()) {
7831
L.DomUtil.addClass(this._zoomOutButton, className);
7832
}
7833
if (map._zoom === map.getMaxZoom()) {
7834
L.DomUtil.addClass(this._zoomInButton, className);
7835
}
7836
}
7837
});
7838
7839
L.Map.mergeOptions({
7840
zoomControl: true
7841
});
7842
7843
L.Map.addInitHook(function () {
7844
if (this.options.zoomControl) {
7845
this.zoomControl = new L.Control.Zoom();
7846
this.addControl(this.zoomControl);
7847
}
7848
});
7849
7850
L.control.zoom = function (options) {
7851
return new L.Control.Zoom(options);
7852
};
7853
7854
7855
7856
/*
7857
* L.Control.Attribution is used for displaying attribution on the map (added by default).
7858
*/
7859
7860
L.Control.Attribution = L.Control.extend({
7861
options: {
7862
position: 'bottomright',
7863
prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
7864
},
7865
7866
initialize: function (options) {
7867
L.setOptions(this, options);
7868
7869
this._attributions = {};
7870
},
7871
7872
onAdd: function (map) {
7873
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
7874
L.DomEvent.disableClickPropagation(this._container);
7875
7876
map
7877
.on('layeradd', this._onLayerAdd, this)
7878
.on('layerremove', this._onLayerRemove, this);
7879
7880
this._update();
7881
7882
return this._container;
7883
},
7884
7885
onRemove: function (map) {
7886
map
7887
.off('layeradd', this._onLayerAdd)
7888
.off('layerremove', this._onLayerRemove);
7889
7890
},
7891
7892
setPrefix: function (prefix) {
7893
this.options.prefix = prefix;
7894
this._update();
7895
return this;
7896
},
7897
7898
addAttribution: function (text) {
7899
if (!text) { return; }
7900
7901
if (!this._attributions[text]) {
7902
this._attributions[text] = 0;
7903
}
7904
this._attributions[text]++;
7905
7906
this._update();
7907
7908
return this;
7909
},
7910
7911
removeAttribution: function (text) {
7912
if (!text) { return; }
7913
7914
if (this._attributions[text]) {
7915
this._attributions[text]--;
7916
this._update();
7917
}
7918
7919
return this;
7920
},
7921
7922
_update: function () {
7923
if (!this._map) { return; }
7924
7925
var attribs = [];
7926
7927
for (var i in this._attributions) {
7928
if (this._attributions[i]) {
7929
attribs.push(i);
7930
}
7931
}
7932
7933
var prefixAndAttribs = [];
7934
7935
if (this.options.prefix) {
7936
prefixAndAttribs.push(this.options.prefix);
7937
}
7938
if (attribs.length) {
7939
prefixAndAttribs.push(attribs.join(', '));
7940
}
7941
7942
this._container.innerHTML = prefixAndAttribs.join(' | ');
7943
},
7944
7945
_onLayerAdd: function (e) {
7946
if (e.layer.getAttribution) {
7947
this.addAttribution(e.layer.getAttribution());
7948
}
7949
},
7950
7951
_onLayerRemove: function (e) {
7952
if (e.layer.getAttribution) {
7953
this.removeAttribution(e.layer.getAttribution());
7954
}
7955
}
7956
});
7957
7958
L.Map.mergeOptions({
7959
attributionControl: true
7960
});
7961
7962
L.Map.addInitHook(function () {
7963
if (this.options.attributionControl) {
7964
this.attributionControl = (new L.Control.Attribution()).addTo(this);
7965
}
7966
});
7967
7968
L.control.attribution = function (options) {
7969
return new L.Control.Attribution(options);
7970
};
7971
7972
7973
/*
7974
* L.Control.Scale is used for displaying metric/imperial scale on the map.
7975
*/
7976
7977
L.Control.Scale = L.Control.extend({
7978
options: {
7979
position: 'bottomleft',
7980
maxWidth: 100,
7981
metric: true,
7982
imperial: true,
7983
updateWhenIdle: false
7984
},
7985
7986
onAdd: function (map) {
7987
this._map = map;
7988
7989
var className = 'leaflet-control-scale',
7990
container = L.DomUtil.create('div', className),
7991
options = this.options;
7992
7993
this._addScales(options, className, container);
7994
7995
map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
7996
map.whenReady(this._update, this);
7997
7998
return container;
7999
},
8000
8001
onRemove: function (map) {
8002
map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
8003
},
8004
8005
_addScales: function (options, className, container) {
8006
if (options.metric) {
8007
this._mScale = L.DomUtil.create('div', className + '-line', container);
8008
}
8009
if (options.imperial) {
8010
this._iScale = L.DomUtil.create('div', className + '-line', container);
8011
}
8012
},
8013
8014
_update: function () {
8015
var bounds = this._map.getBounds(),
8016
centerLat = bounds.getCenter().lat,
8017
halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
8018
dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
8019
8020
size = this._map.getSize(),
8021
options = this.options,
8022
maxMeters = 0;
8023
8024
if (size.x > 0) {
8025
maxMeters = dist * (options.maxWidth / size.x);
8026
}
8027
8028
this._updateScales(options, maxMeters);
8029
},
8030
8031
_updateScales: function (options, maxMeters) {
8032
if (options.metric && maxMeters) {
8033
this._updateMetric(maxMeters);
8034
}
8035
8036
if (options.imperial && maxMeters) {
8037
this._updateImperial(maxMeters);
8038
}
8039
},
8040
8041
_updateMetric: function (maxMeters) {
8042
var meters = this._getRoundNum(maxMeters);
8043
8044
this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
8045
this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
8046
},
8047
8048
_updateImperial: function (maxMeters) {
8049
var maxFeet = maxMeters * 3.2808399,
8050
scale = this._iScale,
8051
maxMiles, miles, feet;
8052
8053
if (maxFeet > 5280) {
8054
maxMiles = maxFeet / 5280;
8055
miles = this._getRoundNum(maxMiles);
8056
8057
scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
8058
scale.innerHTML = miles + ' mi';
8059
8060
} else {
8061
feet = this._getRoundNum(maxFeet);
8062
8063
scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
8064
scale.innerHTML = feet + ' ft';
8065
}
8066
},
8067
8068
_getScaleWidth: function (ratio) {
8069
return Math.round(this.options.maxWidth * ratio) - 10;
8070
},
8071
8072
_getRoundNum: function (num) {
8073
var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
8074
d = num / pow10;
8075
8076
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
8077
8078
return pow10 * d;
8079
}
8080
});
8081
8082
L.control.scale = function (options) {
8083
return new L.Control.Scale(options);
8084
};
8085
8086
8087
/*
8088
* L.Control.Layers is a control to allow users to switch between different layers on the map.
8089
*/
8090
8091
L.Control.Layers = L.Control.extend({
8092
options: {
8093
collapsed: true,
8094
position: 'topright',
8095
autoZIndex: true
8096
},
8097
8098
initialize: function (baseLayers, overlays, options) {
8099
L.setOptions(this, options);
8100
8101
this._layers = {};
8102
this._lastZIndex = 0;
8103
this._handlingClick = false;
8104
8105
for (var i in baseLayers) {
8106
this._addLayer(baseLayers[i], i);
8107
}
8108
8109
for (i in overlays) {
8110
this._addLayer(overlays[i], i, true);
8111
}
8112
},
8113
8114
onAdd: function (map) {
8115
this._initLayout();
8116
this._update();
8117
8118
map
8119
.on('layeradd', this._onLayerChange, this)
8120
.on('layerremove', this._onLayerChange, this);
8121
8122
return this._container;
8123
},
8124
8125
onRemove: function (map) {
8126
map
8127
.off('layeradd', this._onLayerChange)
8128
.off('layerremove', this._onLayerChange);
8129
},
8130
8131
addBaseLayer: function (layer, name) {
8132
this._addLayer(layer, name);
8133
this._update();
8134
return this;
8135
},
8136
8137
addOverlay: function (layer, name) {
8138
this._addLayer(layer, name, true);
8139
this._update();
8140
return this;
8141
},
8142
8143
removeLayer: function (layer) {
8144
var id = L.stamp(layer);
8145
delete this._layers[id];
8146
this._update();
8147
return this;
8148
},
8149
8150
_initLayout: function () {
8151
var className = 'leaflet-control-layers',
8152
container = this._container = L.DomUtil.create('div', className);
8153
8154
//Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
8155
container.setAttribute('aria-haspopup', true);
8156
8157
if (!L.Browser.touch) {
8158
L.DomEvent.disableClickPropagation(container);
8159
L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
8160
} else {
8161
L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
8162
}
8163
8164
var form = this._form = L.DomUtil.create('form', className + '-list');
8165
8166
if (this.options.collapsed) {
8167
if (!L.Browser.android) {
8168
L.DomEvent
8169
.on(container, 'mouseover', this._expand, this)
8170
.on(container, 'mouseout', this._collapse, this);
8171
}
8172
var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
8173
link.href = '#';
8174
link.title = 'Layers';
8175
8176
if (L.Browser.touch) {
8177
L.DomEvent
8178
.on(link, 'click', L.DomEvent.stop)
8179
.on(link, 'click', this._expand, this);
8180
}
8181
else {
8182
L.DomEvent.on(link, 'focus', this._expand, this);
8183
}
8184
8185
this._map.on('click', this._collapse, this);
8186
// TODO keyboard accessibility
8187
} else {
8188
this._expand();
8189
}
8190
8191
this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
8192
this._separator = L.DomUtil.create('div', className + '-separator', form);
8193
this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
8194
8195
container.appendChild(form);
8196
},
8197
8198
_addLayer: function (layer, name, overlay) {
8199
var id = L.stamp(layer);
8200
8201
this._layers[id] = {
8202
layer: layer,
8203
name: name,
8204
overlay: overlay
8205
};
8206
8207
if (this.options.autoZIndex && layer.setZIndex) {
8208
this._lastZIndex++;
8209
layer.setZIndex(this._lastZIndex);
8210
}
8211
},
8212
8213
_update: function () {
8214
if (!this._container) {
8215
return;
8216
}
8217
8218
this._baseLayersList.innerHTML = '';
8219
this._overlaysList.innerHTML = '';
8220
8221
var baseLayersPresent = false,
8222
overlaysPresent = false,
8223
i, obj;
8224
8225
for (i in this._layers) {
8226
obj = this._layers[i];
8227
this._addItem(obj);
8228
overlaysPresent = overlaysPresent || obj.overlay;
8229
baseLayersPresent = baseLayersPresent || !obj.overlay;
8230
}
8231
8232
this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
8233
},
8234
8235
_onLayerChange: function (e) {
8236
var obj = this._layers[L.stamp(e.layer)];
8237
8238
if (!obj) { return; }
8239
8240
if (!this._handlingClick) {
8241
this._update();
8242
}
8243
8244
var type = obj.overlay ?
8245
(e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
8246
(e.type === 'layeradd' ? 'baselayerchange' : null);
8247
8248
if (type) {
8249
this._map.fire(type, obj);
8250
}
8251
},
8252
8253
// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
8254
_createRadioElement: function (name, checked) {
8255
8256
var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
8257
if (checked) {
8258
radioHtml += ' checked="checked"';
8259
}
8260
radioHtml += '/>';
8261
8262
var radioFragment = document.createElement('div');
8263
radioFragment.innerHTML = radioHtml;
8264
8265
return radioFragment.firstChild;
8266
},
8267
8268
_addItem: function (obj) {
8269
var label = document.createElement('label'),
8270
input,
8271
checked = this._map.hasLayer(obj.layer);
8272
8273
if (obj.overlay) {
8274
input = document.createElement('input');
8275
input.type = 'checkbox';
8276
input.className = 'leaflet-control-layers-selector';
8277
input.defaultChecked = checked;
8278
} else {
8279
input = this._createRadioElement('leaflet-base-layers', checked);
8280
}
8281
8282
input.layerId = L.stamp(obj.layer);
8283
8284
L.DomEvent.on(input, 'click', this._onInputClick, this);
8285
8286
var name = document.createElement('span');
8287
name.innerHTML = ' ' + obj.name;
8288
8289
label.appendChild(input);
8290
label.appendChild(name);
8291
8292
var container = obj.overlay ? this._overlaysList : this._baseLayersList;
8293
container.appendChild(label);
8294
8295
return label;
8296
},
8297
8298
_onInputClick: function () {
8299
var i, input, obj,
8300
inputs = this._form.getElementsByTagName('input'),
8301
inputsLen = inputs.length;
8302
8303
this._handlingClick = true;
8304
8305
for (i = 0; i < inputsLen; i++) {
8306
input = inputs[i];
8307
obj = this._layers[input.layerId];
8308
8309
if (input.checked && !this._map.hasLayer(obj.layer)) {
8310
this._map.addLayer(obj.layer);
8311
8312
} else if (!input.checked && this._map.hasLayer(obj.layer)) {
8313
this._map.removeLayer(obj.layer);
8314
}
8315
}
8316
8317
this._handlingClick = false;
8318
},
8319
8320
_expand: function () {
8321
L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
8322
},
8323
8324
_collapse: function () {
8325
this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
8326
}
8327
});
8328
8329
L.control.layers = function (baseLayers, overlays, options) {
8330
return new L.Control.Layers(baseLayers, overlays, options);
8331
};
8332
8333
8334
/*
8335
* L.PosAnimation is used by Leaflet internally for pan animations.
8336
*/
8337
8338
L.PosAnimation = L.Class.extend({
8339
includes: L.Mixin.Events,
8340
8341
run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
8342
this.stop();
8343
8344
this._el = el;
8345
this._inProgress = true;
8346
this._newPos = newPos;
8347
8348
this.fire('start');
8349
8350
el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
8351
's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
8352
8353
L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
8354
L.DomUtil.setPosition(el, newPos);
8355
8356
// toggle reflow, Chrome flickers for some reason if you don't do this
8357
L.Util.falseFn(el.offsetWidth);
8358
8359
// there's no native way to track value updates of transitioned properties, so we imitate this
8360
this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
8361
},
8362
8363
stop: function () {
8364
if (!this._inProgress) { return; }
8365
8366
// if we just removed the transition property, the element would jump to its final position,
8367
// so we need to make it stay at the current position
8368
8369
L.DomUtil.setPosition(this._el, this._getPos());
8370
this._onTransitionEnd();
8371
L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
8372
},
8373
8374
_onStep: function () {
8375
var stepPos = this._getPos();
8376
if (!stepPos) {
8377
this._onTransitionEnd();
8378
return;
8379
}
8380
// jshint camelcase: false
8381
// make L.DomUtil.getPosition return intermediate position value during animation
8382
this._el._leaflet_pos = stepPos;
8383
8384
this.fire('step');
8385
},
8386
8387
// you can't easily get intermediate values of properties animated with CSS3 Transitions,
8388
// we need to parse computed style (in case of transform it returns matrix string)
8389
8390
_transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
8391
8392
_getPos: function () {
8393
var left, top, matches,
8394
el = this._el,
8395
style = window.getComputedStyle(el);
8396
8397
if (L.Browser.any3d) {
8398
matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
8399
if (!matches) { return; }
8400
left = parseFloat(matches[1]);
8401
top = parseFloat(matches[2]);
8402
} else {
8403
left = parseFloat(style.left);
8404
top = parseFloat(style.top);
8405
}
8406
8407
return new L.Point(left, top, true);
8408
},
8409
8410
_onTransitionEnd: function () {
8411
L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
8412
8413
if (!this._inProgress) { return; }
8414
this._inProgress = false;
8415
8416
this._el.style[L.DomUtil.TRANSITION] = '';
8417
8418
// jshint camelcase: false
8419
// make sure L.DomUtil.getPosition returns the final position value after animation
8420
this._el._leaflet_pos = this._newPos;
8421
8422
clearInterval(this._stepTimer);
8423
8424
this.fire('step').fire('end');
8425
}
8426
8427
});
8428
8429
8430
/*
8431
* Extends L.Map to handle panning animations.
8432
*/
8433
8434
L.Map.include({
8435
8436
setView: function (center, zoom, options) {
8437
8438
zoom = this._limitZoom(zoom);
8439
center = L.latLng(center);
8440
options = options || {};
8441
8442
if (this._panAnim) {
8443
this._panAnim.stop();
8444
}
8445
8446
if (this._loaded && !options.reset && options !== true) {
8447
8448
if (options.animate !== undefined) {
8449
options.zoom = L.extend({animate: options.animate}, options.zoom);
8450
options.pan = L.extend({animate: options.animate}, options.pan);
8451
}
8452
8453
// try animating pan or zoom
8454
var animated = (this._zoom !== zoom) ?
8455
this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
8456
this._tryAnimatedPan(center, options.pan);
8457
8458
if (animated) {
8459
// prevent resize handler call, the view will refresh after animation anyway
8460
clearTimeout(this._sizeTimer);
8461
return this;
8462
}
8463
}
8464
8465
// animation didn't start, just reset the map view
8466
this._resetView(center, zoom);
8467
8468
return this;
8469
},
8470
8471
panBy: function (offset, options) {
8472
offset = L.point(offset).round();
8473
options = options || {};
8474
8475
if (!offset.x && !offset.y) {
8476
return this;
8477
}
8478
8479
if (!this._panAnim) {
8480
this._panAnim = new L.PosAnimation();
8481
8482
this._panAnim.on({
8483
'step': this._onPanTransitionStep,
8484
'end': this._onPanTransitionEnd
8485
}, this);
8486
}
8487
8488
// don't fire movestart if animating inertia
8489
if (!options.noMoveStart) {
8490
this.fire('movestart');
8491
}
8492
8493
// animate pan unless animate: false specified
8494
if (options.animate !== false) {
8495
L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
8496
8497
var newPos = this._getMapPanePos().subtract(offset);
8498
this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
8499
} else {
8500
this._rawPanBy(offset);
8501
this.fire('move').fire('moveend');
8502
}
8503
8504
return this;
8505
},
8506
8507
_onPanTransitionStep: function () {
8508
this.fire('move');
8509
},
8510
8511
_onPanTransitionEnd: function () {
8512
L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
8513
this.fire('moveend');
8514
},
8515
8516
_tryAnimatedPan: function (center, options) {
8517
// difference between the new and current centers in pixels
8518
var offset = this._getCenterOffset(center)._floor();
8519
8520
// don't animate too far unless animate: true specified in options
8521
if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
8522
8523
this.panBy(offset, options);
8524
8525
return true;
8526
}
8527
});
8528
8529
8530
/*
8531
* L.PosAnimation fallback implementation that powers Leaflet pan animations
8532
* in browsers that don't support CSS3 Transitions.
8533
*/
8534
8535
L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
8536
8537
run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
8538
this.stop();
8539
8540
this._el = el;
8541
this._inProgress = true;
8542
this._duration = duration || 0.25;
8543
this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
8544
8545
this._startPos = L.DomUtil.getPosition(el);
8546
this._offset = newPos.subtract(this._startPos);
8547
this._startTime = +new Date();
8548
8549
this.fire('start');
8550
8551
this._animate();
8552
},
8553
8554
stop: function () {
8555
if (!this._inProgress) { return; }
8556
8557
this._step();
8558
this._complete();
8559
},
8560
8561
_animate: function () {
8562
// animation loop
8563
this._animId = L.Util.requestAnimFrame(this._animate, this);
8564
this._step();
8565
},
8566
8567
_step: function () {
8568
var elapsed = (+new Date()) - this._startTime,
8569
duration = this._duration * 1000;
8570
8571
if (elapsed < duration) {
8572
this._runFrame(this._easeOut(elapsed / duration));
8573
} else {
8574
this._runFrame(1);
8575
this._complete();
8576
}
8577
},
8578
8579
_runFrame: function (progress) {
8580
var pos = this._startPos.add(this._offset.multiplyBy(progress));
8581
L.DomUtil.setPosition(this._el, pos);
8582
8583
this.fire('step');
8584
},
8585
8586
_complete: function () {
8587
L.Util.cancelAnimFrame(this._animId);
8588
8589
this._inProgress = false;
8590
this.fire('end');
8591
},
8592
8593
_easeOut: function (t) {
8594
return 1 - Math.pow(1 - t, this._easeOutPower);
8595
}
8596
});
8597
8598
8599
/*
8600
* Extends L.Map to handle zoom animations.
8601
*/
8602
8603
L.Map.mergeOptions({
8604
zoomAnimation: true,
8605
zoomAnimationThreshold: 4
8606
});
8607
8608
if (L.DomUtil.TRANSITION) {
8609
8610
L.Map.addInitHook(function () {
8611
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
8612
this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
8613
L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
8614
8615
// zoom transitions run with the same duration for all layers, so if one of transitionend events
8616
// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
8617
if (this._zoomAnimated) {
8618
L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
8619
}
8620
});
8621
}
8622
8623
L.Map.include(!L.DomUtil.TRANSITION ? {} : {
8624
8625
_catchTransitionEnd: function () {
8626
if (this._animatingZoom) {
8627
this._onZoomTransitionEnd();
8628
}
8629
},
8630
8631
_nothingToAnimate: function () {
8632
return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
8633
},
8634
8635
_tryAnimatedZoom: function (center, zoom, options) {
8636
8637
if (this._animatingZoom) { return true; }
8638
8639
options = options || {};
8640
8641
// don't animate if disabled, not supported or zoom difference is too large
8642
if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
8643
Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
8644
8645
// offset is the pixel coords of the zoom origin relative to the current center
8646
var scale = this.getZoomScale(zoom),
8647
offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
8648
origin = this._getCenterLayerPoint()._add(offset);
8649
8650
// don't animate if the zoom origin isn't within one screen from the current center, unless forced
8651
if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
8652
8653
this
8654
.fire('movestart')
8655
.fire('zoomstart');
8656
8657
this._animateZoom(center, zoom, origin, scale, null, true);
8658
8659
return true;
8660
},
8661
8662
_animateZoom: function (center, zoom, origin, scale, delta, backwards) {
8663
8664
this._animatingZoom = true;
8665
8666
// put transform transition on all layers with leaflet-zoom-animated class
8667
L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
8668
8669
// remember what center/zoom to set after animation
8670
this._animateToCenter = center;
8671
this._animateToZoom = zoom;
8672
8673
// disable any dragging during animation
8674
if (L.Draggable) {
8675
L.Draggable._disabled = true;
8676
}
8677
8678
this.fire('zoomanim', {
8679
center: center,
8680
zoom: zoom,
8681
origin: origin,
8682
scale: scale,
8683
delta: delta,
8684
backwards: backwards
8685
});
8686
},
8687
8688
_onZoomTransitionEnd: function () {
8689
8690
this._animatingZoom = false;
8691
8692
L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
8693
8694
this._resetView(this._animateToCenter, this._animateToZoom, true, true);
8695
8696
if (L.Draggable) {
8697
L.Draggable._disabled = false;
8698
}
8699
}
8700
});
8701
8702
8703
/*
8704
Zoom animation logic for L.TileLayer.
8705
*/
8706
8707
L.TileLayer.include({
8708
_animateZoom: function (e) {
8709
if (!this._animating) {
8710
this._animating = true;
8711
this._prepareBgBuffer();
8712
}
8713
8714
var bg = this._bgBuffer,
8715
transform = L.DomUtil.TRANSFORM,
8716
initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
8717
scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
8718
8719
bg.style[transform] = e.backwards ?
8720
scaleStr + ' ' + initialTransform :
8721
initialTransform + ' ' + scaleStr;
8722
},
8723
8724
_endZoomAnim: function () {
8725
var front = this._tileContainer,
8726
bg = this._bgBuffer;
8727
8728
front.style.visibility = '';
8729
front.parentNode.appendChild(front); // Bring to fore
8730
8731
// force reflow
8732
L.Util.falseFn(bg.offsetWidth);
8733
8734
this._animating = false;
8735
},
8736
8737
_clearBgBuffer: function () {
8738
var map = this._map;
8739
8740
if (map && !map._animatingZoom && !map.touchZoom._zooming) {
8741
this._bgBuffer.innerHTML = '';
8742
this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
8743
}
8744
},
8745
8746
_prepareBgBuffer: function () {
8747
8748
var front = this._tileContainer,
8749
bg = this._bgBuffer;
8750
8751
// if foreground layer doesn't have many tiles but bg layer does,
8752
// keep the existing bg layer and just zoom it some more
8753
8754
var bgLoaded = this._getLoadedTilesPercentage(bg),
8755
frontLoaded = this._getLoadedTilesPercentage(front);
8756
8757
if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
8758
8759
front.style.visibility = 'hidden';
8760
this._stopLoadingImages(front);
8761
return;
8762
}
8763
8764
// prepare the buffer to become the front tile pane
8765
bg.style.visibility = 'hidden';
8766
bg.style[L.DomUtil.TRANSFORM] = '';
8767
8768
// switch out the current layer to be the new bg layer (and vice-versa)
8769
this._tileContainer = bg;
8770
bg = this._bgBuffer = front;
8771
8772
this._stopLoadingImages(bg);
8773
8774
//prevent bg buffer from clearing right after zoom
8775
clearTimeout(this._clearBgBufferTimer);
8776
},
8777
8778
_getLoadedTilesPercentage: function (container) {
8779
var tiles = container.getElementsByTagName('img'),
8780
i, len, count = 0;
8781
8782
for (i = 0, len = tiles.length; i < len; i++) {
8783
if (tiles[i].complete) {
8784
count++;
8785
}
8786
}
8787
return count / len;
8788
},
8789
8790
// stops loading all tiles in the background layer
8791
_stopLoadingImages: function (container) {
8792
var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
8793
i, len, tile;
8794
8795
for (i = 0, len = tiles.length; i < len; i++) {
8796
tile = tiles[i];
8797
8798
if (!tile.complete) {
8799
tile.onload = L.Util.falseFn;
8800
tile.onerror = L.Util.falseFn;
8801
tile.src = L.Util.emptyImageUrl;
8802
8803
tile.parentNode.removeChild(tile);
8804
}
8805
}
8806
}
8807
});
8808
8809
8810
/*
8811
* Provides L.Map with convenient shortcuts for using browser geolocation features.
8812
*/
8813
8814
L.Map.include({
8815
_defaultLocateOptions: {
8816
watch: false,
8817
setView: false,
8818
maxZoom: Infinity,
8819
timeout: 10000,
8820
maximumAge: 0,
8821
enableHighAccuracy: false
8822
},
8823
8824
locate: function (/*Object*/ options) {
8825
8826
options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
8827
8828
if (!navigator.geolocation) {
8829
this._handleGeolocationError({
8830
code: 0,
8831
message: 'Geolocation not supported.'
8832
});
8833
return this;
8834
}
8835
8836
var onResponse = L.bind(this._handleGeolocationResponse, this),
8837
onError = L.bind(this._handleGeolocationError, this);
8838
8839
if (options.watch) {
8840
this._locationWatchId =
8841
navigator.geolocation.watchPosition(onResponse, onError, options);
8842
} else {
8843
navigator.geolocation.getCurrentPosition(onResponse, onError, options);
8844
}
8845
return this;
8846
},
8847
8848
stopLocate: function () {
8849
if (navigator.geolocation) {
8850
navigator.geolocation.clearWatch(this._locationWatchId);
8851
}
8852
if (this._locateOptions) {
8853
this._locateOptions.setView = false;
8854
}
8855
return this;
8856
},
8857
8858
_handleGeolocationError: function (error) {
8859
var c = error.code,
8860
message = error.message ||
8861
(c === 1 ? 'permission denied' :
8862
(c === 2 ? 'position unavailable' : 'timeout'));
8863
8864
if (this._locateOptions.setView && !this._loaded) {
8865
this.fitWorld();
8866
}
8867
8868
this.fire('locationerror', {
8869
code: c,
8870
message: 'Geolocation error: ' + message + '.'
8871
});
8872
},
8873
8874
_handleGeolocationResponse: function (pos) {
8875
var lat = pos.coords.latitude,
8876
lng = pos.coords.longitude,
8877
latlng = new L.LatLng(lat, lng),
8878
8879
latAccuracy = 180 * pos.coords.accuracy / 40075017,
8880
lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),
8881
8882
bounds = L.latLngBounds(
8883
[lat - latAccuracy, lng - lngAccuracy],
8884
[lat + latAccuracy, lng + lngAccuracy]),
8885
8886
options = this._locateOptions;
8887
8888
if (options.setView) {
8889
var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
8890
this.setView(latlng, zoom);
8891
}
8892
8893
var data = {
8894
latlng: latlng,
8895
bounds: bounds
8896
};
8897
8898
for (var i in pos.coords) {
8899
if (typeof pos.coords[i] === 'number') {
8900
data[i] = pos.coords[i];
8901
}
8902
}
8903
8904
this.fire('locationfound', data);
8905
}
8906
});
8907
8908
8909
}(window, document));
8910