PageSourceSearch

https://mrsale.hu/wp-content/plugins/hungarian-pickup-points-for-w…/assets/js/frontend.min.js?ver=4.2.8

js mrsale.hu collected 2026-10-02 19:46:57 UTC 618,931 bytes, 19,350 lines download raw bytes

1(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2const zipCodes = require('./zip-codes.js');
3var L = require('leaflet');
4require('leaflet.markercluster');
5require("leaflet.featuregroup.subgroup");
6var bodyScrollLock = require('body-scroll-lock');
7var JsSearch = require('js-search');
8
9jQuery(document).ready(function($) {
10
11	//Settings page
12	var vp_woo_pont_frontend = {
13		$overlay: $('.vp-woo-pont-modal-bg'),
14		$modal: $('.vp-woo-pont-modal'),
15		$close_button: $('.vp-woo-pont-modal-map-close'),
16		$filters: $('.vp-woo-pont-modal-sidebar-filters'),
17		$list: $('.vp-woo-pont-modal-sidebar-results'),
18		$search_field: $('.vp-woo-pont-modal-sidebar-search-field'),
19		$search_field_clear: $('.vp-woo-pont-modal-sidebar-search-clear'),
20		$map: false,
21		map_loaded: false,
22		markerClusters: false,
23		groups: [],
24		list: false,
25		enabled_filters: [],
26		search: false,
27		selected_point: false,
28		selected_pickup_point: false,
29		providers: false,
30		markerIcons: [],
31		method_changed: false,
32		clickedMarker: false,
33		checkout_needs_reload: false,
34		is_checkoutwc: false,
35		notes: false,
36		billing_country: false,
37		features: [],
38		init: function() {
39
40			//Initialize a map
41			this.$map = L.map('vp-woo-pont-modal-map',{
42				renderer: L.canvas(),
43			});
44
45			//this.$map.attributionControl.setPrefix('');
46
47			//Move zoom controls to bottom right
48			this.$map.zoomControl.setPosition('bottomright');
49
50			//Get default parameters for markerClusters
51			var markerClusterParams = {
52			  spiderfyOnMaxZoom: true,
53			  showCoverageOnHover: false,
54			  zoomToBoundsOnClick: true,
55			  disableClusteringAtZoom: 16,
56				removeOutsideVisibleBounds: true,
57				chunkedLoading: true,
58				chunkProgress: function(processed, total) {
59					// Optional: show loading progress
60					console.log(`Clustering: ${Math.round((processed/total)*100)}%`);
61				},
62				chunkDelay: 0, // Reduce delay for faster processing
63				maxClusterRadius: 80, // Slightly smaller for better performance
64				animate: true, // Disable animations for large datasets
65				spiderfyDistanceMultiplier: 1.2,
66				// Custom cluster icon for better performance
67				iconCreateFunction: function(cluster) {
68					var count = cluster.getChildCount();
69					var size = count < 100 ? 'small' : count < 1000 ? 'medium' : 'large';
70					return new L.DivIcon({ 
71						html: '<div><span>' + count + '</span></div>', 
72						className: 'marker-cluster marker-cluster-' + size, 
73						iconSize: new L.Point(40, 40) 
74					});
75				}
76			};
77
78			//If custom parameters set
79			if(vp_woo_pont_frontend_params.markerClusterParams) {
80				markerClusterParams = vp_woo_pont_frontend_params.markerClusterParams;
81			}
82
83			//Create cluster layer
84			this.markerClusters = new L.MarkerClusterGroup(markerClusterParams);
85
86			//Initialize search engine, which will search for name, city, zip code and address
87			this.search = new JsSearch.Search('name');
88			this.search.addIndex('name');
89			this.search.addIndex('city_nfd');
90			this.search.addIndex('zip');
91			this.search.addIndex('addr_nfd');
92			this.search.addIndex('keywords');
93
94			//When map is moved, update sidebar results
95			this.$map.on("moveend", function (e) {
96
97				//Only, if a point is not selected yet
98				//In that case, clicking on the map will update the sidebar only
99				if(!vp_woo_pont_frontend.selected_point) {
100					vp_woo_pont_frontend.syncSidebar();
101				}
102
103			});
104
105			//When map is moved, update sidebar results
106			this.$map.on("movestart", function (e) {
107				vp_woo_pont_frontend.$modal.removeClass('search-focused');
108			});
109
110			//Click action to show the modal
111			$(document).on( 'click', '#vp-woo-pont-show-map', this.show_modal );
112			$(document).on( 'click', '.vp-woo-pont-show-map', this.show_modal );
113
114			//When filters changed, update stuff
115			$(document).on( 'change', '.vp-woo-pont-modal-sidebar-filters input', this.toggle_layers );
116
117			//Highlight search box text on click
118			this.$search_field.click(function () {
119				vp_woo_pont_frontend.$modal.addClass('search-focused');
120			});
121
122			//Prevent hitting enter from refreshing the page
123			this.$search_field.keypress(function (e) {
124				if (e.which == 13) {
125					e.preventDefault();
126					vp_woo_pont_frontend.$search_field.blur();
127				}
128			});
129
130			//Fix iOS jumping
131			if(this.isiOS()) {
132				var searchFieldElement = this.$search_field[0];
133				searchFieldElement.addEventListener('touchstart', (event) => {
134				    event.stopPropagation();
135				    searchFieldElement.style.transform = 'TranslateY(-10000px)'
136				    searchFieldElement.focus();
137				    setTimeout(function () { searchFieldElement.style.transform = 'none' }, 100);
138				});
139			}
140
141			//When search field typed, start search with a delay
142			var timeout = null;
143			this.$search_field.keyup(function() {
144				var keyword = $(this).val();
145
146				clearTimeout(timeout);
147				timeout = setTimeout(() => {
148					//Only if at least 2 characters
149					if(keyword.length > 2) {
150						vp_woo_pont_frontend.show_search_results(keyword);
151					}
152				}, 200);
153
154				//Show clear button
155				if(keyword.length > 0) {
156					vp_woo_pont_frontend.$search_field_clear.show();
157				} else {
158					vp_woo_pont_frontend.$search_field_clear.hide();
159				}
160
161			});
162
163			//Hide modal when esc pressed
164			$(document).keyup(function(event){
165				if(event.which=='27'){
166
167					//If we are in the search field, just clear that first
168					if(vp_woo_pont_frontend.$search_field.is(":focus")) {
169						vp_woo_pont_frontend.$search_field.blur();
170						vp_woo_pont_frontend.$modal.removeClass('search-focused');
171					} else {
172						vp_woo_pont_frontend.hide_modal();
173					}
174				}
175			});
176
177			//Select a point in the sidebar
178			this.$list.on( 'click', '.vp-woo-pont-modal-sidebar-result', this.select_in_sidebar );
179
180			//Select a pickup point for checkout
181			this.$list.on( 'click', '.vp-woo-pont-modal-sidebar-result-select', this.select_pickup_point );
182
183			//Search clear button function
184			this.$search_field_clear.click(function(){
185				vp_woo_pont_frontend.$search_field.val('');
186				vp_woo_pont_frontend.$modal.removeClass('point-selected');
187				vp_woo_pont_frontend.$modal.removeClass('search-focused');
188				vp_woo_pont_frontend.$modal.removeClass('no-search-result');
189				vp_woo_pont_frontend.$modal.removeClass('has-search-result');
190				vp_woo_pont_frontend.selected_point = false;
191				vp_woo_pont_frontend.$list.find('li.selected').removeClass('selected');
192				$('.leaflet-marker-icon.selected').removeClass('selected');
193				$(this).hide();
194				return false;
195			});
196
197			//Hide modal
198			this.$close_button.click(function(){
199				vp_woo_pont_frontend.hide_modal();
200				return false;
201			});
202
203			//Check if its a custom CheckoutWC page
204			if($('body').hasClass('checkout-wc')) {
205				vp_woo_pont_frontend.is_checkoutwc = true;
206			}
207
208			//When shipping method changed to vp pont
209			$('form.checkout').on( 'change', 'input[name^="shipping_method"]', this.on_shipping_method_change );
210			$( document.body ).on( 'updated_checkout', this.on_checkout_updated );
211			$( document.body ).on( 'updated_shipping_method', this.on_cart_updated );
212
213			//On page load of shipping fields not visible, we need to reload the checkout page
214			var method = $("#shipping_method input:checked").val();
215			if(method) {
216				vp_woo_pont_frontend.show_and_hide_shipping_address(method.includes('vp_pont'));
217			} else {
218				var method = $('#shipping_method .shipping_method').val();
219				if(method) {
220					vp_woo_pont_frontend.show_and_hide_shipping_address(method.includes('vp_pont'));
221				}
222			}
223
224			//Run on page load too
225			this.on_cart_updated();
226
227			//If theres a payment method condition set for the pricing, update the checkout if payment method changed
228			if(vp_woo_pont_frontend_params.refresh_payment_methods) {
229				$( 'form.checkout' ).on( 'change', 'input[name^="payment_method"]', function() {
230					$('body').trigger('update_checkout');
231				});
232			}
233
234			//Toggle open hours in the sidebar
235			this.$list.on( 'click', '.vp-woo-pont-modal-sidebar-result .open-hours', this.toggle_open_hours );
236
237		},
238		toggle_open_hours: function() {
239			$(this).toggleClass('open');
240		},
241		on_shipping_method_change: function() {
242			var method = $(this).val();
243			vp_woo_pont_frontend.method_changed = true;
244			vp_woo_pont_frontend.show_and_hide_shipping_address(method.includes('vp_pont'));
245		},
246		show_and_hide_shipping_address: function(hide) {
247			//Don't do anything for CheckoutWC
248			if(!vp_woo_pont_frontend.is_checkoutwc) {
249				if(hide) {
250					$('.woocommerce-shipping-fields').hide();
251					$('#ship-to-different-address-checkbox').prop('checked', false);
252					$('#ship-to-different-address-checkbox').trigger( 'change' )
253				} else {
254					$('.woocommerce-shipping-fields').show();
255				}
256			}
257		},
258		on_checkout_updated: function() {
259			if(!$('.vp-woo-pont-review-order-selected').length && vp_woo_pont_frontend.method_changed && vp_woo_pont_frontend_params.show_on_change) {
260				$('#vp-woo-pont-show-map').trigger('click');
261			}
262		},
263		show_modal: function() {
264
265			//Show the overlay
266			vp_woo_pont_frontend.$overlay.addClass('show');
267
268			//Show the modal
269			setTimeout(function() {
270				vp_woo_pont_frontend.$modal.addClass('show');
271			}, 1);
272
273			//Get available providers
274			var shipping_costs = $(this).data('shipping-costs');
275			if(!shipping_costs) shipping_costs = $(this).parent().data('shipping-costs');
276			vp_woo_pont_frontend.providers = shipping_costs;
277
278			//Get notes
279			var notes = $(this).data('notes');
280			if(!notes) notes = $(this).parent().data('notes');
281			vp_woo_pont_frontend.notes = notes;
282
283			//Set country based on priority: billing_country > shipping_country > billing-country > default
284			var current_billing_country;
285			if($('#billing_country').length && $('#billing_country').val()) {
286				current_billing_country = $('#billing_country').val();
287			} else if($('#shipping_country').length && $('#shipping_country').val()) {
288				current_billing_country = $('#shipping_country').val();
289			} else if($('#billing-country').length && $('#billing-country').val()) {
290				current_billing_country = $('#billing-country').val();
291			} else {
292				current_billing_country = vp_woo_pont_frontend_params.default_country || 'HU';
293			}
294
295			//Setup provider filter
296			vp_woo_pont_frontend.setup_provider_filters(vp_woo_pont_frontend.providers, current_billing_country);
297
298			//If we changed the country, we might need to reload the map points
299			if(current_billing_country != vp_woo_pont_frontend.billing_country) {
300				vp_woo_pont_frontend.map_loaded = false;
301				vp_woo_pont_frontend.billing_country = current_billing_country;
302
303				//Clear existing markers and groups
304				if(vp_woo_pont_frontend.markerClusters) {
305					vp_woo_pont_frontend.markerClusters.clearLayers();
306				}
307
308				//Clear all groups
309				Object.keys(vp_woo_pont_frontend.groups).forEach(function(group){
310					if(vp_woo_pont_frontend.groups[group]) {
311						vp_woo_pont_frontend.groups[group].clearLayers();
312						vp_woo_pont_frontend.$map.removeLayer(vp_woo_pont_frontend.groups[group]);
313					}
314				});
315
316				//Reset groups array
317				vp_woo_pont_frontend.groups = [];
318
319				//Reset search index
320				vp_woo_pont_frontend.search = new JsSearch.Search('name');
321				vp_woo_pont_frontend.search.addIndex('name');
322				vp_woo_pont_frontend.search.addIndex('city_nfd');
323				vp_woo_pont_frontend.search.addIndex('zip');
324				vp_woo_pont_frontend.search.addIndex('addr_nfd');
325				vp_woo_pont_frontend.search.addIndex('keywords');
326
327				//Clear sidebar
328				vp_woo_pont_frontend.$list.html('');
329			}
330
331			//Init the map, but only once, when the modal is opened
332			if(!vp_woo_pont_frontend.map_loaded) {
333				vp_woo_pont_frontend.map_loaded = true;
334				vp_woo_pont_frontend.init_map();
335			} else {
336				vp_woo_pont_frontend.geolocate();
337			}
338
339			//Remove points from map
340			vp_woo_pont_frontend.hide_unwanted_providers();
341
342			//Disable scrolling
343			bodyScrollLock.disableBodyScroll(vp_woo_pont_frontend.$modal[0], {
344				allowTouchMove: vp_woo_pont_frontend.$list
345			});
346
347			//For developers to run extra stuff
348			$( document.body ).trigger( 'vp_woo_pont_modal_shown' );
349			$( document.body ).addClass('vp-woo-pont-modal-visible');
350
351			return false;
352		},
353		hide_modal: function() {
354
355			//Hide the modal
356			vp_woo_pont_frontend.$modal.removeClass('show');
357
358			//Hide the overlay
359			setTimeout(function() {
360				vp_woo_pont_frontend.$overlay.removeClass('show');
361			}, 100);
362
363			//Enable scrolling
364			bodyScrollLock.clearAllBodyScrollLocks();
365
366			$( document.body ).removeClass('vp-woo-pont-modal-visible');
367
368		},
369		init_map: function() {
370
371			//Center map
372			this.geolocate();
373
374			//Get default parameters
375			//Get default parameters
376			var mapParameters = {
377				maxZoom: 19,
378				attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
379				detectRetina: false,
380				tileLayer: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
381			};
382
383			//Check if custom params set
384			if(vp_woo_pont_frontend_params.mapParameters) {
385				mapParameters = vp_woo_pont_frontend_params.mapParameters;
386			}
387
388			//Load image layers and set attribution
389			L.tileLayer(mapParameters.tileLayer, mapParameters).addTo(this.$map);
390
391			//Setup markers
392			vp_woo_pont_frontend.markerClusters.addTo(vp_woo_pont_frontend.$map);
393			vp_woo_pont_frontend.markerClusters._getExpandedVisibleBounds = function () {
394				return vp_woo_pont_frontend.markerClusters._map.getBounds();
395			};
396
397			//Create groups
398			vp_woo_pont_frontend_params.enabled_providers.forEach(function(provider){
399				vp_woo_pont_frontend.groups[provider] = L.featureGroup.subGroup(vp_woo_pont_frontend.markerClusters);
400				vp_woo_pont_frontend.markerIcons[provider] = L.divIcon({html: '<div><i class="vp-woo-pont-provider-icon-'+provider+'"></i></div>', className: 'vp-woo-pont-marker '+provider, iconSize: [48, 55], iconAnchor: [24, 52]});
401			});
402
403			//Loop through json files
404			Object.keys(vp_woo_pont_frontend_params.files).forEach(function(json){
405
406				//Check for country specific files
407				vp_woo_pont_frontend_params.files[json].forEach(function(file){
408
409					//Skip if the country doesn't match
410					if(vp_woo_pont_frontend.billing_country != file.country) {
411						return; //skip this file
412					}
413
414					$.getJSON(vp_woo_pont_frontend_params.db_url+file.file, function (data) {
415						vp_woo_pont_frontend.process_provider(json, data);
416
417						//Refresh sidebar
418						vp_woo_pont_frontend.syncSidebar();
419
420					});		
421
422				});
423
424			});
425
426			//Check for custom points
427			if(vp_woo_pont_frontend_params.custom_points) {
428
429				//Only custom providers
430				var custom_points = vp_woo_pont_frontend_params.custom_points.filter(obj => {
431					return obj.provider === 'custom' && !obj.hidden
432				});
433				vp_woo_pont_frontend.process_provider('custom', custom_points);
434
435				//Refresh sidebar
436				vp_woo_pont_frontend.syncSidebar();
437			}
438
439		},
440		find_custom_point_override: function(data, provider) {
441			var custom_points = vp_woo_pont_frontend_params.custom_points;
442			if(custom_points) {
443				custom_points.forEach((item, i) => {
444					if(item.provider == provider && item.id == data.id) {
445						data = item;
446
447						if(item.hidden) {
448							data = false;
449						}
450					}
451				});
452			}
453			return data;
454		},
455		process_provider: function(provider, data) {
456			var allDocuments = [];
457			var markers = [];
458			var batchSize = 4000; // Process 4000 markers at a time
459			var currentIndex = 0;
460			var features = [];
461			
462			// Show loading indicator
463			vp_woo_pont_frontend.$modal.addClass('loading-markers');
464			
465			function processBatch() {
466				var endIndex = Math.min(currentIndex + batchSize, data.length);
467				var batchDocuments = [];
468				var batchMarkers = [];
469				
470				for (var i = currentIndex; i < endIndex; i++) {
471					var a = data[i];
472					
473					// Process marker data
474					if(a.country && a.country != 'HU' && a.country != 'hu') {
475						a.city_nfd = a.city;
476						a.addr_nfd = a.addr;
477					} else {
478						a.city_nfd = a.city.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
479						a.addr_nfd = a.addr.normalize("NFD").replace(/[\u0300-\u036f]/g, "");	
480					}
481										
482					// Only create marker if group exists
483					var targetGroup = null;
484					if(provider == 'postapont') {
485						a.provider = provider+'_'+a.group;
486						targetGroup = vp_woo_pont_frontend.groups[provider+'_'+a.group];
487					} else {
488						a.provider = provider;
489						targetGroup = vp_woo_pont_frontend.groups[provider];
490					}
491					
492					if(targetGroup) {
493						var marker = L.marker(new L.LatLng(a.lat, a.lon), { 
494							data: a,
495							riseOnHover: true // Better performance than default
496						});
497						a.marker_id = L.stamp(marker);
498						marker.on('click', vp_woo_pont_frontend.select_in_map);
499
500						//If the point has a features array, add feature classes to the marker for styling
501						if(a.features && Array.isArray(a.features)) {
502							var classes = 'vp-woo-pont-marker '+a.provider;
503							a.features.forEach(function(feature){
504								if(features.includes(feature) == false) {
505									features.push(feature);
506								}
507								classes += " feature-"+feature;
508							});
509							marker.setIcon(L.divIcon({html: '<div><i class="vp-woo-pont-provider-icon-'+a.provider+'"></i></div>', className: classes, iconSize: [48, 55], iconAnchor: [24, 52]}));
510						} else {
511							marker.setIcon(vp_woo_pont_frontend.markerIcons[a.provider]);
512						}
513						
514						batchMarkers.push({marker: marker, group: targetGroup});
515						batchDocuments.push(a);
516					}
517				}
518				
519				// Add markers to groups in batch
520				batchMarkers.forEach(function(item) {
521					item.marker.addTo(item.group);
522				});
523				
524				// Add to collections
525				markers = markers.concat(batchMarkers);
526				allDocuments = allDocuments.concat(batchDocuments);
527				
528				currentIndex = endIndex;
529				
530				// Update progress (optional)
531				var progress = Math.round((currentIndex / data.length) * 100);
532				console.log(`Processing markers: ${progress}%`);
533				
534				if (currentIndex < data.length) {
535					// Continue processing in next frame
536					requestAnimationFrame(processBatch);
537				} else {
538					// Finished processing
539					finishProcessing();
540				}
541
542				//Set features
543				vp_woo_pont_frontend.features[provider] = features;
544
545				console.log(vp_woo_pont_frontend.features);
546
547			}
548			
549			function finishProcessing() {
550				// Add groups to map
551				if(provider == 'postapont') {
552					var groups = ['10', '20', '30', '50', '70'];
553					groups.forEach(function(group){
554						if(vp_woo_pont_frontend.groups[provider+'_'+group]) {
555							vp_woo_pont_frontend.groups[provider+'_'+group].addTo(vp_woo_pont_frontend.$map);
556						}
557					});
558				} else {
559					if(vp_woo_pont_frontend.groups[provider]) {
560						vp_woo_pont_frontend.groups[provider].addTo(vp_woo_pont_frontend.$map);
561					}
562				}
563				
564				// Add to search index
565				vp_woo_pont_frontend.search.addDocuments(allDocuments);
566				vp_woo_pont_frontend.hide_unwanted_providers();
567				
568				// Remove loading indicator
569				vp_woo_pont_frontend.$modal.removeClass('loading-markers');
570				
571				// Refresh sidebar
572				vp_woo_pont_frontend.syncSidebar();
573			}
574			
575			// Start processing
576			requestAnimationFrame(processBatch);
577		},
578		toggle_layers: function() {
579			var id = $(this).attr('id');
580			
581			// Check if this is a feature filter
582			if(id.startsWith('feature-')) {
583				var featureId = id.replace('feature-', '');
584				var provider = $(this).parent().data('provider');
585				var filteredGroupKey = provider + '_' + id;
586				
587				if($(this).prop('checked')) {
588					// Create filtered group if it doesn't exist
589					if(!vp_woo_pont_frontend.groups[filteredGroupKey]) {
590						var filteredGroup = L.featureGroup.subGroup(vp_woo_pont_frontend.markerClusters);
591						
592						// Copy markers that have the required feature
593						vp_woo_pont_frontend.groups[provider].eachLayer(function(marker) {
594							var pointData = marker.options.data;
595							var hasFeature = pointData.features && Array.isArray(pointData.features) && pointData.features.includes(featureId);
596							
597							if(hasFeature) {
598								filteredGroup.addLayer(marker);
599							}
600						});
601						
602						vp_woo_pont_frontend.groups[filteredGroupKey] = filteredGroup;
603					}
604					
605					// Hide regular group, show filtered group
606					vp_woo_pont_frontend.$map.removeLayer(vp_woo_pont_frontend.groups[provider]);
607					vp_woo_pont_frontend.$map.addLayer(vp_woo_pont_frontend.groups[filteredGroupKey]);
608				} else {
609					// Hide filtered group, show regular group
610					if(vp_woo_pont_frontend.groups[filteredGroupKey]) {
611						vp_woo_pont_frontend.$map.removeLayer(vp_woo_pont_frontend.groups[filteredGroupKey]);
612					}
613					vp_woo_pont_frontend.$map.addLayer(vp_woo_pont_frontend.groups[provider]);
614				}
615			} else {
616				// Handle regular provider toggle
617				var provider = id.replace('provider-', '');
618				var $allProviderCheckboxes = vp_woo_pont_frontend.$filters.find('li:not(.filter-feature) input[type="checkbox"]');
619				var allCheckedBefore = true;
620				$allProviderCheckboxes.each(function() {
621					if($(this).attr('id') === id) {
622						// This one just changed, so its state is already toggled; check the opposite
623						if($(this).prop('checked')) allCheckedBefore = allCheckedBefore && false;
624					} else {
625						if(!$(this).prop('checked')) allCheckedBefore = false;
626					}
627				});
628
629				if(allCheckedBefore && !$(this).prop('checked')) {
630					// All were checked before this click — solo-select the clicked provider
631					$(this).prop('checked', true);
632					$allProviderCheckboxes.each(function() {
633						var pid = $(this).attr('id').replace('provider-', '');
634						if($(this).attr('id') !== id) {
635							$(this).prop('checked', false);
636							vp_woo_pont_frontend.$map.removeLayer(vp_woo_pont_frontend.groups[pid]);
637						} else {
638							vp_woo_pont_frontend.$map.addLayer(vp_woo_pont_frontend.groups[pid]);
639						}
640					});
641				} else {
642					if($(this).prop('checked')) {
643						vp_woo_pont_frontend.$map.addLayer(vp_woo_pont_frontend.groups[provider]);
644					} else {
645						vp_woo_pont_frontend.$map.removeLayer(vp_woo_pont_frontend.groups[provider]);
646					}
647				}
648			}
649
650			//Refresh enabled provider list
651			vp_woo_pont_frontend.enabled_filters = [];
652			vp_woo_pont_frontend.$filters.find('input:checked').each(function(){
653				var provider = $(this).parent().data('provider');
654				vp_woo_pont_frontend.enabled_filters.push(provider);
655			});
656
657			//Refresh sidebar
658			vp_woo_pont_frontend.syncSidebar();
659		},
660		setup_provider_filters: function(providers, country) {
661			vp_woo_pont_frontend.$filters.html(''); //reset content
662			vp_woo_pont_frontend.enabled_filters = [];
663
664			//Loop throguh providers and create list items
665			Object.keys(providers).forEach(function(provider_id){
666				var provider = providers[provider_id];
667
668				//Set cost
669				var cost = provider.formatted_net;
670				if(vp_woo_pont_frontend_params.prices_including_tax) cost = provider.formatted_gross
671
672				//Render HTML
673				var list_item = $('<li data-provider="'+provider_id+'"><input type="checkbox" checked id="provider-'+provider_id+'"><label for="provider-'+provider_id+'"><i class="vp-woo-pont-provider-icon-'+provider_id+'"></i><strong>'+provider.label+'</strong><em>'+cost+'</em></label></li>');
674				vp_woo_pont_frontend.enabled_filters.push(provider_id);
675				vp_woo_pont_frontend.$filters.append(list_item);
676			});
677
678			//If theres only one provider, just hide it, no need for the filters
679			if(Object.keys(providers).length == 1) {
680				vp_woo_pont_frontend.$filters.hide();
681			} else {
682				vp_woo_pont_frontend.$filters.show();
683			}
684
685			//Setup features filters too
686			var available_providers = Object.keys(providers);
687			Object.keys(vp_woo_pont_frontend_params.features).forEach(function(feature_id){
688				var feature = vp_woo_pont_frontend_params.features[feature_id];
689
690				if(!available_providers.includes(feature_id)) {
691					return; //skip this feature
692				}
693
694				//Check for country support
695				if(feature.countries && Array.isArray(feature.countries)) {
696					if(!feature.countries.includes(country)) {
697						return; //skip this feature
698					}
699				}
700
701				//Render HTML
702				var list_item = $('<li data-provider="'+feature_id+'" class="filter-feature" data-feature="'+feature.id+'"><input type="checkbox" id="feature-'+feature.id+'"><label for="feature-'+feature.id+'"><i class="vp-woo-pont-feature-icon-'+feature.id+'"></i><strong>'+feature.label+'</strong><small>'+feature.description+'</small></label></li>');
703				vp_woo_pont_frontend.$filters.append(list_item);
704			});
705			
706
707		},
708		hide_unwanted_providers: function() {
709			Object.keys(vp_woo_pont_frontend.groups).forEach(function(group){
710				if(!Object.keys(vp_woo_pont_frontend.providers).includes(group)) {
711					vp_woo_pont_frontend.$map.removeLayer(vp_woo_pont_frontend.groups[group]);
712				} else {
713					vp_woo_pont_frontend.$map.addLayer(vp_woo_pont_frontend.groups[group]);
714				}
715			});	
716		},
717		syncSidebar: function() {
718
719			//Clear the list
720			vp_woo_pont_frontend.$list.html('');
721
722			//Create new array
723			//TODO: sort ascending
724
725			//Get checked provider filters (not feature filters)
726			vp_woo_pont_frontend.$filters.find('input:checked').not('[id^="feature-"]').each(function(){
727				var provider = $(this).parent().data('provider');
728				var provider_limit = 0;
729
730				// Check if there's an active feature filter for this provider
731				var activeGroup = provider;
732				vp_woo_pont_frontend.$filters.find('input[id^="feature-"]:checked').each(function(){
733					var featureProvider = $(this).parent().data('provider');
734					if(featureProvider === provider) {
735						var featureId = $(this).attr('id');
736						activeGroup = provider + '_' + featureId;
737						return false; // break loop
738					}
739				});
740
741				//Loop through layers(only the ones that are checked in the filter) and in the viewport and append to the list.
742				//Only append 10 / group for performance reasons, i think its still usable this way
743				//Only change the sidebar, if a point is not selected already
744				if(vp_woo_pont_frontend.groups && vp_woo_pont_frontend.groups[activeGroup]) {
745					vp_woo_pont_frontend.groups[activeGroup].eachLayer(function (layer) {
746						if (vp_woo_pont_frontend.$map.getBounds().contains(layer.getLatLng())) {
747							if(provider_limit < 10) {
748								layer.options.data.marker_id = L.stamp(layer);
749								vp_woo_pont_frontend.add_to_sidebar(layer.options.data, provider);
750							}
751							provider_limit++;
752						}
753				  });
754				}
755
756			});
757
758		},
759		show_search_results: function(keyword) {
760
761			//Remove accents for more accurate results
762			keyword = keyword.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
763			var search_results = vp_woo_pont_frontend.search.search(keyword);
764
765			//On search, remove selected point
766			vp_woo_pont_frontend.selected_point = false;
767
768			//Set helper classes for styling and mobile
769			vp_woo_pont_frontend.$modal.removeClass('point-selected');
770
771			//Remove if provider not selected
772			if(vp_woo_pont_frontend.enabled_filters) {
773				search_results = search_results.filter(result => vp_woo_pont_frontend.enabled_filters.includes(result.provider));
774			}
775
776			if(search_results.length == 0) {
777
778				//Add helper classes
779				vp_woo_pont_frontend.$modal.addClass('no-search-result');
780				vp_woo_pont_frontend.$modal.removeClass('has-search-result');
781
782				//Add no results message
783				var list_item = $('#vp-woo-pont-modal-no-result-sample').clone();
784				vp_woo_pont_frontend.$list.find('.vp-woo-pont-modal-sidebar-no-result').remove();
785				vp_woo_pont_frontend.$list.prepend(list_item);
786
787			} else {
788
789				//Add helper classes
790				vp_woo_pont_frontend.$modal.removeClass('no-search-result');
791				vp_woo_pont_frontend.$modal.addClass('has-search-result');
792
793				//Clear the list
794				vp_woo_pont_frontend.$list.html('');
795
796				//Limit to max 100 search results
797				if (search_results.length > 100) search_results.length = 100;
798
799				//Add results one by one
800				//// TODO: do this with vanilla js, so its a lit faster
801				search_results.forEach(function(result){
802					vp_woo_pont_frontend.add_to_sidebar(result, result.provider);
803				});
804
805			}
806		},
807		add_to_sidebar(item, provider) {
808
809			//// TODO: do this with vanilla js, so its a little faster
810			var list_item = $('#vp-woo-pont-modal-list-item-sample').clone();
811			var provider_price = vp_woo_pont_frontend.providers[provider];
812
813			//If theres no price, skip
814			if(!provider_price) {
815				return;
816			}
817
818			list_item.removeAttr('id');
819			list_item.find('.name').text(item.name);
820			list_item.find('.addr').text(item.addr+', '+item.zip+' '+item.city);
821			if(vp_woo_pont_frontend_params.prices_including_tax) {
822				list_item.find('.cost').html(provider_price.formatted_gross);
823			} else {
824				list_item.find('.cost').html(provider_price.formatted_net);
825			}
826
827			list_item.find('.comment').text(item.comment);
828			list_item.attr('data-provider', provider);
829			list_item.attr('data-id', item.id);
830			list_item.attr('data-marker-id', item.marker_id);
831			list_item.attr('data-country', item.country || 'HU');
832			list_item.find('.icon').addClass('vp-woo-pont-provider-icon-'+provider);
833			if(item.hasOwnProperty('cod') && !item.cod) {
834				list_item.find('.cod-notice').addClass("show");
835			}
836
837			//Show extra note if needed
838			if(vp_woo_pont_frontend.notes && vp_woo_pont_frontend.notes[provider]) {
839				list_item.find('.comment').prepend('<div class="extra-note">'+vp_woo_pont_frontend.notes[provider]+'</div>');
840			}
841
842			//Show extra features
843			if(item.features && Array.isArray(item.features)) {
844				item.features.forEach(function(feature){
845					list_item.addClass('feature-'+feature);
846				});
847			}
848
849			//Setup opening hours
850			if(vp_woo_pont_frontend_params.open_hours && item.hours) {
851				if(typeof item.hours === 'string') {
852					list_item.find('.open-hours .value').text(item.hours);
853				} else {
854					for (var key in item.hours) {
855						var index = key-1;
856						if (item.hours.hasOwnProperty(key)) {
857							if(item.hours[key]) {
858								list_item.find('.open-hours li:eq('+index+') .value').text(item.hours[key]);
859							}
860						}
861					}
862				}
863				list_item.find('.open-hours').addClass('has-hours');
864			}
865
866			//Check if need to be selected
867			var selected = false;
868			if(vp_woo_pont_frontend.selected_point && vp_woo_pont_frontend.selected_point.id == item.id && vp_woo_pont_frontend.selected_point.provider == provider) {
869				list_item.addClass('selected');
870				selected = true;
871			}
872
873			//If its the selected one, move that to the top of the list
874			if(selected) {
875				//Check if already on the list
876				if(vp_woo_pont_frontend.$list.find('li.selected').length < 1) {
877					vp_woo_pont_frontend.$list.prepend(list_item);
878				}
879			} else {
880				vp_woo_pont_frontend.$list.append(list_item);
881			}
882
883		},
884		select_in_sidebar: function() {
885
886			//If filter is not turned on, turn it on so the layer is visible on the map
887			var provider = $(this).data('provider');
888			if(!vp_woo_pont_frontend.$filters.find('input#provider-'+provider).is(':checked')) {
889				vp_woo_pont_frontend.$filters.find('input#provider-'+provider).prop('checked', true);
890				vp_woo_pont_frontend.$filters.find('input#provider-'+provider).trigger('change');
891			}
892
893			//Add selected class
894			vp_woo_pont_frontend.$list.find('li.selected').removeClass('selected');
895			$(this).addClass('selected');
896
897			//Remove active marker class
898			$('.leaflet-marker-icon.selected').removeClass('selected');
899
900			//Store selected value
901			vp_woo_pont_frontend.selected_point = {
902				provider: provider,
903				id: $(this).data('id'),
904				marker_id: parseInt($(this).data('marker-id'), 10)
905			}
906
907			//For developers to run extra stuff
908			$( document.body ).trigger( 'vp_woo_pont_modal_point_picked' );
909
910			//Set helper classes for styling and mobile
911			vp_woo_pont_frontend.$modal.addClass('point-selected');
912			vp_woo_pont_frontend.$modal.removeClass('no-search-result');
913			vp_woo_pont_frontend.$modal.removeClass('has-search-result');
914
915			//Focus on map
916			var layer = vp_woo_pont_frontend.markerClusters.getLayer(vp_woo_pont_frontend.selected_point.marker_id);
917  			vp_woo_pont_frontend.$map.setView([layer.getLatLng().lat, layer.getLatLng().lng], 17);
918			$(layer._icon).addClass('selected');
919
920			setTimeout(function() {
921				$(layer._icon).addClass('selected');
922			}, 300);
923
924			vp_woo_pont_frontend.clickedMarker = layer;
925
926			return false;
927		},
928		select_in_map: function(e) {
929
930			//Remove active marker class
931			$('.leaflet-marker-icon.selected').removeClass('selected');
932
933			//Add selected class
934			var layer = vp_woo_pont_frontend.markerClusters.getLayer(e.target.options.data.marker_id);
935			$(layer._icon).addClass('selected');
936
937			//Store selected value
938			vp_woo_pont_frontend.selected_point = {
939				provider: e.target.options.data.provider,
940				id: e.target.options.data.id,
941				marker_id: e.target.options.data.marker_id
942			}
943
944			//For developers to run extra stuff
945			$( document.body ).trigger( 'vp_woo_pont_modal_point_picked' );
946
947			//Set helper classes for styling and mobile
948			vp_woo_pont_frontend.$modal.addClass('point-selected');
949			vp_woo_pont_frontend.$modal.removeClass('no-search-result');
950			vp_woo_pont_frontend.$modal.removeClass('has-search-result');
951
952			//Move map to center
953			vp_woo_pont_frontend.$map.setView(e.latlng);
954
955			//Sync sidebar, which wills how results from the viewport from the map, which includes the selected point anyway
956			vp_woo_pont_frontend.syncSidebar();
957
958			//Make sure the selected point is always in the sidebar, at the beginning
959			vp_woo_pont_frontend.add_to_sidebar(e.target.options.data, e.target.options.data.provider);
960
961			//Scroll to the top of the list, so the selected point is visible
962			vp_woo_pont_frontend.$list.animate({scrollTop: 0});
963
964		},
965		select_pickup_point: function() {
966
967			//Collect data
968			var $item = $(this).parent();
969			var provider = $item.data('provider');
970			var id = $item.data('id');
971			var page = 'checkout';
972			var security = ''
973			var ajax_url = '';
974			if($('body').hasClass('woocommerce-cart')) page = 'cart';
975
976			//Set based on page
977			ajax_url = vp_woo_pont_frontend_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'vp_woo_pont_select' );
978			security = vp_woo_pont_frontend_params.nonce;
979
980			//Create reuqest
981			var data = {
982				action: 'vp_woo_pont_select',
983				security: security,
984				provider: provider,
985				id: id,
986				country: $item.data('country')
987			};
988
989			//If its a my account page
990			if($('.vp-woo-pont-show-map-my-account').length) {
991				data.account = true;
992			}
993
994			//Loading indicator animaiton
995			$item.addClass( 'processing' );	
996			if ($.fn.block) {	
997				$item.block({
998					message: null,
999					overlayCSS: {
1000						background: '#fff',
1001						opacity: 0.6
1002					}
1003				});
1004			}
1005
1006			//Make ajax request
1007			$.post(ajax_url, data, function(response) {
1008
1009				//Remove loading indicator
1010				if ($.fn.block) {
1011					$item.unblock();
1012				}
1013				$item.removeClass('processing');
1014
1015				//TODO handle error
1016				if(response.success) {
1017
1018					// Refresh the review order section, so the selected point is visible automatically
1019					$('body').trigger('update_checkout');
1020
1021					//Update cart
1022					if($('.cart_totals input.shipping_method:checked').length) {
1023						$('.cart_totals input.shipping_method:checked').trigger( 'change' );
1024					} else {
1025						$('.cart_totals input.shipping_method').trigger( 'change' ); //This is when the only available option is the pont shipping one
1026					}
1027
1028					//Hide the modal window
1029					vp_woo_pont_frontend.hide_modal();
1030
1031					//Store it locally too
1032					vp_woo_pont_frontend.selected_pickup_point = response.data.point;
1033
1034					//For the checkout block
1035					localStorage.setItem('selectedMapDetails', JSON.stringify(response.data.point));
1036
1037					//For developers to run extra stuff
1038					$( document.body ).trigger( 'vp_woo_pont_modal_point_selected' );
1039
1040					//If its a my account page
1041					if($('.vp-woo-pont-show-map-my-account').length) {
1042						$('.vp-woo-pont-review-order-selected').show();
1043						$('.vp-woo-pont-review-order-selected strong').text(response.data.point.name);
1044						$('.vp-woo-pont-review-order-selected i').addClass('vp-woo-pont-provider-icon-'+response.data.point.provider);
1045					}
1046
1047				} else {
1048
1049				}
1050
1051			});
1052
1053			return false;
1054		},
1055		on_cart_updated: function() {
1056			var selected_method = $('.cart_totals .shipping_method:checked').val();
1057
1058			//Means theres only one shipping method, so its not a checkbox, but check if its the pont one
1059			if(!selected_method) selected_method = $('.cart_totals .shipping_method').val();
1060
1061			if(selected_method) {
1062				if(selected_method.includes('vp_pont')) {
1063					$('.woocommerce-shipping-destination').hide();
1064					$('.shipping-calculator-button').hide();
1065				} else {
1066					$('.woocommerce-shipping-destination').show();
1067					$('.shipping-calculator-button').show();
1068				}
1069			}
1070		},
1071		geolocate: function() {
1072			var selected_postcode = $('#billing_postcode').val();
1073			var selected_country = vp_woo_pont_frontend.billing_country || 'HU';
1074			var selected_state = '';
1075			if(selected_postcode && selected_country == 'HU') {
1076				selected_postcode = parseInt(selected_postcode);
1077				$.each(zipCodes.vp_woo_pont_state_postcodes, function(state, postcodes) {
1078					if(postcodes.includes(selected_postcode)) {
1079						selected_state = state;
1080						return false;
1081					}
1082				});
1083			}
1084
1085			if(selected_state && zipCodes.vp_woo_pont_state_coordinates[selected_state] !== undefined) {
1086				var selected_state_coordinates = zipCodes.vp_woo_pont_state_coordinates[selected_state];
1087				vp_woo_pont_frontend.$map.setView(selected_state_coordinates, 10);
1088			} else {
1089
1090				var default_center_position = vp_woo_pont_frontend_params.default_center_position;
1091
1092				//Center billing country
1093				if(selected_country && zipCodes.vp_woo_pont_country_coordinates[selected_country] !== undefined) {
1094					default_center_position = zipCodes.vp_woo_pont_country_coordinates[selected_country];
1095				}
1096
1097				vp_woo_pont_frontend.$map.setView(default_center_position, 8);
1098			}
1099		},
1100		isiOS: function() {
1101			return [
1102				'iPad Simulator',
1103				'iPhone Simulator',
1104				'iPod Simulator',
1105				'iPad',
1106				'iPhone',
1107				'iPod'
1108			].includes(navigator.platform)
1109			// iPad on iOS 13 detection
1110			|| (navigator.userAgent.includes("Mac") && "ontouchend" in document)
1111		}
1112	}
1113
1114	if($('.vp-woo-pont-modal').length) {
1115		vp_woo_pont_frontend.init();
1116
1117		//Init as a global variable
1118		window.vp_woo_pont_frontend = vp_woo_pont_frontend;
1119
1120	}
1121});
1122
1123
1124
1125},{"./zip-codes.js":2,"body-scroll-lock":3,"js-search":4,"leaflet":7,"leaflet.featuregroup.subgroup":5,"leaflet.markercluster":6}],2:[function(require,module,exports){
1126//Simple geolocate function based on ZIP codes, just to zoom in on the state
1127const vp_woo_pont_state_postcodes = {'BU':[1011,1012,1013,1014,1015,1016,1021,1022,1023,1024,1025,1026,1027,1028,1029,1031,1032,1033,1034,1035,1036,1037,1038,1039,1041,1042,1043,1044,1045,1046,1047,1048,1051,1052,1053,1054,1055,1056,1061,1062,1063,1064,1065,1066,1067,1068,1069,1071,1072,1073,1074,1075,1076,1077,1078,1081,1082,1083,1084,1085,1086,1087,1088,1089,1091,1092,1093,1094,1095,1096,1097,1098,1101,1102,1103,1104,1105,1106,1107,1108,1111,1112,1113,1114,1115,1116,1117,1118,1119,1121,1122,1123,1124,1125,1126,1131,1132,1133,1134,1135,1136,1137,1138,1139,1141,1142,1143,1144,1145,1146,1147,1148,1149,1151,1152,1153,1154,1155,1156,1157,1158,1161,1162,1163,1164,1165,1171,1172,1173,1174,1181,1182,1183,1184,1185,1186,1188,1191,1192,1193,1194,1195,1196,1201,1202,1203,1204,1205,1211,1212,1213,1214,1215,1221,1222,1223,1224,1225,1237,1238,1239,1529],'PE':[2000,2009,2011,2014,2015,2016,2017,2021,2022,2023,2024,2025,2026,2030,2035,2036,2040,2049,2051,2053,2071,2072,2074,2080,2081,2084,2085,2086,2087,2089,2092,2093,2094,2095,2097,2098,2099,2100,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2142,2143,2144,2145,2163,2164,2165,2167,2170,2173,2174,2181,2183,2184,2185,2191,2192,2193,2194,2209,2211,2212,2213,2214,2215,2216,2217,2220,2233,2234,2235,2251,2252,2253,2254,2255,2300,2310,2314,2315,2317,2318,2319,2321,2322,2330,2335,2336,2337,2338,2339,2340,2345,2347,2351,2360,2363,2365,2366,2367,2370,2371,2373,2375,2376,2378,2381,2440,2461,2510,2600,2613,2614,2615,2621,2623,2626,2627,2628,2629,2631,2632,2633,2634,2635,2637,2638,2639,2681,2683,2700,2711,2712,2713,2721,2723,2724,2730,2735,2736,2737,2738,2740,2746,2747,2760,2764,2765,2766,2767,2768,2769,2898,3356,3604,3630,3647,3775,3874,3905,3906,4836,6332,6781,7092,7163,7212,7954,7981,8193,8292,8321,8351,8357,8619,8640,8873,8881,8929,9651,9707,9791,9825],'VA':[2013,2027,2038,2039,2182,2532,2745,3212,3422,3462,3622,3623,3672,3757,3762,3795,3933,3992,4274,4761,4821,4942,4971,5062,5065,5091,5324,5622,6422,6784,7300,7385,7511,7517,7678,7763,7833,7847,7935,7957,8122,8428,8863,9325,9354,9500,9511,9514,9515,9516,9517,9521,9522,9523,9531,9541,9544,9545,9547,9548,9549,9551,9552,9553,9554,9555,9556,9561,9600,9608,9609,9611,9622,9623,9624,9625,9631,9632,9633,9634,9635,9641,9643,9652,9653,9654,9661,9662,9663,9664,9665,9671,9672,9673,9674,9683,9684,9685,9721,9722,9724,9725,9726,9733,9734,9735,9737,9739,9741,9742,9743,9744,9745,9746,9747,9748,9751,9752,9754,9756,9757,9761,9762,9763,9764,9766,9771,9772,9774,9775,9776,9777,9781,9782,9783,9784,9789,9792,9793,9794,9795,9796,9797,9798,9799,9800,9811,9812,9813,9814,9821,9823,9826,9831,9832,9833,9834,9835,9836,9841,9842,9900,9909,9912,9915,9917,9918,9919,9921,9922,9923,9931,9932,9934,9936,9937,9938,9941,9942,9944,9945,9946,9951,9952,9953,9955,9961,9962,9970,9981,9982,9983,9985],'KE':[2028,2067,2146,2225,2242,2500,2509,2517,2518,2519,2522,2523,2524,2525,2526,2527,2529,2531,2533,2534,2536,2537,2541,2543,2544,2545,2800,2821,2823,2824,2831,2832,2833,2834,2835,2836,2837,2852,2854,2856,2858,2859,2861,2862,2870,2879,2881,2882,2883,2884,2885,2886,2887,2888,2890,2897,2899,2911,2931,2941,2942,2943,2944,2945,2946,2947,2948,2949,3374,3910,4145,4965,5536,5539,7200,7334,7668,8625,8736],'ZA':[2045,2161,2241,2610,2644,2671,2698,2889,3263,3574,3716,3721,3765,3886,5125,5624,5920,6077,6640,7025,7272,7285,7386,7695,7761,7966,8072,8313,8314,8315,8316,8341,8353,8354,8355,8356,8360,8371,8372,8373,8380,8391,8392,8393,8394,8429,8475,8477,8479,8563,8595,8716,8741,8742,8743,8745,8746,8747,8749,8751,8752,8753,8754,8756,8761,8762,8764,8765,8767,8771,87
112772,8773,8774,8776,8777,8778,8782,8784,8785,8788,8789,8790,8792,8793,8795,8797,8798,8799,8800,8808,8809,8821,8822,8824,8825,8827,8831,8834,8835,8855,8856,8861,8862,8866,8868,8872,8874,8879,8882,8883,8885,8886,8887,8888,8891,8893,8900,8911,8912,8913,8914,8915,8917,8918,8919,8921,8923,8924,8925,8931,8932,8934,8935,8936,8943,8944,8945,8946,8947,8948,8949,8951,8953,8954,8956,8957,8958,8960,8966,8969,8971,8973,8975,8976,8977,8978,8981,8983,8984,8985,8986,8988,8990,8991,8992,8994,8995,8996,8997,8998,8999,9235,9300,9324,9542,9612,9621,9636,9727,9738],'FE':[2060,2063,2064,2065,2066,2091,2400,2407,2421,2422,2423,2424,2425,2426,2427,2428,2431,2432,2433,2434,2435,2451,2453,2454,2455,2456,2457,2459,2462,2465,2471,2472,2473,2475,2476,2477,2481,2483,2484,2485,2490,3024,3787,3864,4241,4763,4954,5725,6755,6785,7000,7003,7011,7012,7013,7014,7015,7016,7017,7018,7019,7041,7672,7751,7854,8000,8019,8043,8044,8045,8051,8052,8054,8055,8056,8065,8071,8073,8081,8082,8083,8086,8087,8088,8089,8092,8095,8096,8097,8111,8112,8121,8124,8125,8126,8127,8128,8130,8131,8132,8133,8135,8136,8137,8138,8139,8141,8143,8144,8145,8146,8151,8153,8154,8155,8156,8286,8558,9681],'BZ':[2073,2132,2162,2316,2463,2535,2612,2624,2699,2822,2840,2921,3154,3243,3327,3328,3400,3413,3416,3417,3418,3421,3423,3424,3425,3431,3432,3433,3434,3441,3442,3443,3444,3450,3458,3459,3461,3463,3464,3465,3466,3467,3500,3501,3508,3510,3515,3516,3517,3518,3519,3521,3524,3525,3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3552,3553,3554,3555,3556,3559,3561,3563,3564,3565,3571,3572,3573,3575,3576,3578,3579,3580,3586,3587,3588,3589,3592,3593,3594,3595,3596,3597,3598,3599,3608,3626,3635,3636,3641,3642,3643,3644,3645,3646,3648,3652,3653,3654,3655,3656,3657,3659,3663,3664,3671,3700,3704,3711,3712,3713,3714,3715,3717,3718,3720,3722,3723,3724,3726,3728,3729,3733,3735,3741,3742,3743,3744,3751,3752,3753,3754,3755,3756,3758,3759,3761,3768,3770,3773,3776,3777,3778,3779,3780,3783,3786,3791,3792,3793,3796,3800,3809,3811,3814,3815,3816,3817,3825,3826,3831,3832,3833,3834,3836,3837,3841,3842,3843,3844,3846,3847,3848,3851,3852,3853,3854,3855,3860,3863,3865,3866,3871,3873,3875,3876,3881,3882,3887,3888,3891,3892,3896,3897,3898,3899,3900,3903,3904,3907,3908,3909,3915,3916,3917,3918,3921,3922,3923,3924,3925,3926,3927,3928,3929,3931,3932,3935,3936,3942,3943,3944,3945,3950,3952,3955,3956,3957,3958,3959,3961,3962,3963,3964,3965,3967,3971,3972,3973,3974,3976,3977,3978,3980,3985,3987,3988,3989,3991,3993,3996,3997,4075,4114,4133,4622,4826,4914,5222,5440,5900,5903,5904,5905,6795,7045,7052,7083,7150,7158,7187,7228,7281,7305,7370,7383,7439,7681,7682,7716,7973,8074,8157,8458,8644,8695,8719,8876,8895,8896,9061,9071,9073,9134,9152,9184,9464,9682,9719,9736,9773],'SO':[2083,2151,2521,2625,2855,2903,3016,3348,3384,3388,3411,3562,3577,3603,3734,3769,3813,3821,3893,3894,4177,4231,4373,4565,5008,5212,5556,6341,7086,7191,7253,7255,7256,7258,7261,7271,7274,7275,7276,7279,7282,7284,7286,7394,7400,7431,7432,7434,7435,7436,7441,7442,7443,7444,7452,7453,7454,7456,7457,7458,7463,7464,7465,7471,7472,7473,7474,7476,7477,7478,7479,7500,7512,7513,7514,7515,7516,7521,7522,7523,7525,7527,7530,7532,7533,7535,7536,7538,7542,7543,7544,7551,7552,7555,7556,7557,7561,7562,7563,7564,7570,7582,7584,7
1127585,7587,7588,7589,7918,7921,7922,7924,7976,7979,7987,7988,8041,8455,8495,8600,8609,8611,8612,8613,8614,8618,8621,8622,8623,8624,8626,8628,8630,8636,8637,8638,8646,8647,8648,8649,8651,8652,8653,8654,8656,8658,8660,8666,8667,8668,8671,8672,8673,8674,8675,8676,8681,8683,8684,8685,8691,8692,8693,8694,8696,8698,8699,8705,8706,8707,8708,8709,8710,8711,8712,8713,8714,8717,8718,8721,8722,8723,8724,8725,8726,8728,8731,8732,8733,8735,8737,8738,8739,8840,8849,8851,8853,8865,9131,9167,9676,9740,9935],'VE':[2096,2131,2243,3022,3066,3176,3345,3600,3621,3662,4275,4623,4755,4941,5000,5200,5449,5743,5919,6230,6445,6648,6912,7227,7541,7728,7756,7960,8085,8100,8103,8104,8105,8109,8142,8163,8164,8171,8172,8174,8175,8181,8182,8183,8184,8192,8194,8195,8196,8200,8220,8225,8226,8227,8228,8229,8230,8233,8236,8237,8241,8242,8243,8245,8247,8248,8251,8252,8253,8254,8255,8256,8257,8258,8261,8262,8263,8264,8265,8271,8272,8275,8281,8282,8283,8284,8291,8294,8295,8296,8297,8300,8308,8311,8312,8318,8319,8344,8345,8347,8348,8349,8352,8400,8411,8412,8413,8415,8416,8417,8418,8419,8420,8422,8423,8424,8427,8430,8431,8432,8433,8435,8438,8439,8440,8441,8442,8443,8444,8445,8446,8447,8448,8449,8451,8452,8454,8456,8457,8460,8469,8471,8473,8474,8476,8478,8481,8482,8483,8484,8485,8491,8492,8493,8494,8496,8497,8500,8511,8512,8513,8514,8515,8516,8517,8518,8521,8522,8523,8531,8532,8533,8541,8542,8543,8551,8552,8554,8555,8556,8557,8561,8562,8564,8565,8571,8572,8581,8582,8591,8592,8593,8594,8596,8597,8598,8697,8700,8858,9147,9451,9533,9534,9535,9913],'JN':[2133,2134,3651,5051,5052,5053,5054,5055,5061,5063,5064,5071,5081,5082,5083,5084,5085,5092,5093,5094,5095,5100,5111,5121,5122,5123,5124,5126,5130,5135,5136,5137,5141,5142,5143,5144,5152,5211,5213,5231,5232,5233,5234,5235,5241,5243,5244,5300,5309,5310,5321,5322,5323,5331,5340,5349,5350,5358,5359,5361,5362,5363,5400,5411,5412,5430,5435,5452,5453,5461,5462,5463,5464,5465,5471,5472,5474,5475,5476,5746,6043,7038,7130,7675,7741,7755,8161,8796,8864,8877],'GS':[2141,2230,2853,3153,3941,3994,4150,4943,4955,6115,6132,6769,6794,7257,7352,7742,7900,7914,8123,8897,9000,9011,9012,9021,9022,9023,9024,9025,9026,9027,9028,9029,9030,9062,9063,9072,9074,9081,9082,9083,9084,9085,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9111,9121,9122,9123,9124,9125,9126,9127,9132,9133,9135,9136,9141,9142,9143,9145,9146,9151,9154,9155,9161,9162,9163,9164,9165,9168,9169,9171,9172,9174,9175,9176,9177,9178,9181,9183,9200,9211,9221,9222,9223,9224,9225,9226,9228,9231,9232,9233,9234,9241,9242,9243,9244,9311,9312,9313,9314,9315,9316,9317,9321,9322,9323,9326,9327,9330,9339,9341,9342,9343,9344,9345,9346,9351,9352,9353,9361,9362,9363,9364,9365,9371,9372,9374,9375,9400,9407,9408,9421,9422,9423,9431,9433,9434,9435,9436,9437,9441,9442,9443,9444,9461,9462,9463,9471,9472,9473,9474,9475,9476,9481,9482,9483,9484,9485,9491,9492,9493,9494,9933],'SZ':[2166,2244,2464,2528,2900,3326,3627,3658,3767,4136,4138,4232,4233,4234,4235,4244,4245,4246,4266,4267,4300,4311,4320,4324,4325,4326,4327,4331,4332,4333,4334,4335,4337,4338,4341,4342,4343,4351,4352,4353,4354,4355,4356,4361,4362,4363,4371,4372,4374,4375,4376,4400,4405,4412,4431,4432,4433,4434,4440,4445,4446,4447,4450,4455,4456,4461,4463,4464,4465,4466,4467,4468,4471,4472,4474,4475,4481,4483,4484,4485,4486,4487,4488,4491,4493,4494,4496,4501,4502,4503,4511,4515,4516,4517,4521,4522,4523,4524,4525,4531,4532,4533,4534,4535,4536,4537,4541,4542,4543,4544,4546,4547,4551,4552,4553,4554,4555,4556,4557,4561,4562,4563,4564,4566,4567,4600,4611,4621,4624,4625,4627,4628,4631,4632,4633,4634,4635,4641,4643,4644,4645,4646,4700,4721,4722,4731,4732,4734,4735,4737,4741,4742,4743,4745,4746,4752,4754,4756,4762,4764,4765,4766,4767,4800,4803,4804,4811,4812,4813,4822,4823,4824,4831,4832,4833,4834,4835,4841,4842,4843,4844,4845,4900,4911,4912,4913,4921,4922,4931,4932,4933,4934,4935,4936,4937,4944,4945,4946,4947,4948,4951,4953,4956,4961,4962,4963,4966,4967,4968,4969,4972,4973,4974,4975,4976,4977,5700,7087,7143,7745,7811,7824,8191,8244,8425,8426,8734,9113,9512,9749,9824,9943,9954],'NO':[2175,2176,2177,2611,2616,2617,2618,2619,2640,2641,2642,2643,2645,2646,2647,2648,2649,2651,2652,2653,2655,2656,2659,2660,2668,2669,2672,2673,2675,2676,2677,2678,2682,2685,2686,2687,2688,2691,2692,2693,2694,2696,2697,3034,3041,3042,3043,3044,3045,3046,3047,3051,3052,3053,3060,3063,3065,3067,3068,3069,3070,3073,3074,3075,3077,3078,3082,3100,3102,3104,3109,3121,3123,3124,3125,3126,3127,3128,3129,3132,3133,3134,3135,3136,3137,3138,3141,3142,3143,3144,3
1127145,3146,3147,3151,3152,3155,3161,3162,3163,3165,3170,3175,3177,3178,3179,3181,3182,3183,3184,3185,3186,3187,3188,3625,3812,3872,3934,3999,4492,7226,7700,7735,8434,9153,9495,9675],'HE':[2200,2377,2658,3000,3011,3013,3015,3023,3031,3032,3035,3036,3200,3213,3214,3231,3232,3233,3234,3235,3240,3242,3244,3245,3246,3247,3248,3250,3252,3253,3254,3255,3256,3257,3258,3259,3261,3262,3264,3265,3271,3272,3273,3274,3275,3281,3282,3283,3284,3291,3292,3293,3294,3295,3296,3300,3304,3321,3322,3323,3324,3325,3331,3332,3334,3336,3337,3341,3343,3344,3346,3347,3349,3350,3351,3352,3353,3354,3355,3357,3358,3359,3360,3369,3371,3373,3375,3377,3378,3379,3381,3382,3383,3385,3386,3387,3390,3394,3395,3396,3397,3398,3399,3414,3557,5516,5745,5836,6200,6758,7064,7132,7142,7539,7545,7720,7972,8553,8878,9723],'BA':[2309,2364,2654,2750,2851,2896,3021,3064,3131,3211,3221,3335,3426,3731,3763,3764,3895,4071,4642,5420,5534,5553,6134,6346,6612,6646,6767,6923,7094,7273,7283,7304,7331,7332,7333,7342,7343,7344,7345,7346,7347,7348,7349,7351,7381,7384,7391,7393,7396,7455,7537,7553,7586,7600,7621,7622,7623,7624,7625,7626,7627,7628,7629,7630,7631,7632,7633,7634,7635,7636,7661,7663,7664,7666,7671,7677,7683,7691,7693,7696,7711,7712,7714,7715,7717,7718,7723,7724,7725,7726,7727,7731,7732,7733,7737,7743,7744,7747,7752,7753,7757,7759,7762,7766,7768,7771,7772,7773,7774,7775,7781,7782,7783,7784,7785,7812,7813,7814,7815,7817,7818,7822,7823,7826,7827,7831,7834,7837,7838,7839,7843,7846,7849,7850,7851,7853,7912,7913,7915,7923,7925,7926,7934,7936,7951,7953,7958,7964,7967,7968,7971,7975,7980,7985,8042,8053,8134,8246,8273,8274,8342,8346,8414,8468,8617,8655,8744,9144,9245,9700],'TO':[2344,2657,2755,3012,3412,3884,3885,3937,3954,4117,4141,4495,4545,6090,7020,7026,7027,7030,7039,7042,7043,7044,7047,7051,7054,7056,7057,7061,7062,7063,7065,7066,7067,7068,7071,7072,7081,7082,7084,7085,7090,7091,7093,7095,7097,7098,7099,7100,7121,7122,7131,7133,7134,7135,7136,7139,7140,7144,7145,7146,7147,7148,7149,7159,7161,7162,7164,7171,7172,7173,7174,7175,7176,7181,7182,7183,7184,7185,7186,7192,7193,7194,7195,7211,7213,7214,7215,7224,7225,7251,7252,7341,7353,7354,7355,7356,7361,7362,7475,7841,7932,7937,7977,8060,8854,9019,9173],'BK':[2458,3368,3551,3591,4135,4336,6000,6008,6031,6032,6033,6034,6035,6041,6044,6045,6050,6055,6060,6062,6064,6065,6066,6067,6070,6075,6076,6078,6080,6085,6086,6087,6088,6096,6097,6098,6100,6111,6112,6113,6114,6116,6120,6131,6133,6136,6211,6221,6222,6223,6224,6236,6237,6238,6239,6300,6320,6323,6325,6326,6327,6328,6331,6333,6334,6336,6337,6342,6343,6344,6345,6347,6348,6351,6352,6353,6400,6411,6412,6413,6414,6421,6423,6424,6425,6430,6435,6440,6444,6446,6447,6448,6449,6451,6452,6453,6454,6455,6456,6500,6503,6511,6512,6513,6521,6522,6523,6524,6525,6527,6528,6782,7524,7526,7754,8162,8330,8409,9086,9112,9532],'HB':[3014,3849,4000,4002,4024,4025,4026,4027,4028,4029,4030,4031,4032,4033,4034,4060,4063,4064,4065,4066,4067,4069,4074,4078,4079,4080,4085,4086,4087,4090,4096,4097,4100,4103,4110,4115,4116,4118,4119,4121,4122,4123,4124,4125,4126,4127,4128,4130,4132,4134,4137,4142,4143,4144,4146,4161,4162,4163,4164,4171,4172,4173,4174,4175,4176,4181,4183,4184,4200,4211,4212,4220,4224,4225,4242,4243,4251,4252,4253,4254,4262,4263,4264,4271,4272,4273,4281,4283,4284,4285,4286,4287,4288,4482,4751,4964,6042,6235,6922,7694,7836,7940,8066,8627],'CS':[3033,3732,3794,3995,4952,5451,5665,5940,6135,6600,6621,6622,6623,6624,6625,6630,6635,6636,6645,6647,6700,6710,6720,6721,6722,6723,6724,6725,6726,6727,6728,6729,6750,6753,6754,6756,6757,6760,6762,6763,6764,6765,6768,6771,6772,6773,6774,6775,6783,6786,6787,6791,6792,6793,6800,6806,6821,6900,6903,6911,6913,6914,6915,6916,6917,6921,6931,6932,6933,7165,7800,8989],'BE':[3372,3661,3877,4558,4733,5500,5502,5510,5515,5520,5525,5526,5527,5530,5537,5538,5540,5551,5552,5555,5561,5600,5609,5621,5623,5630,5641,5643,5650,5661,5662,5663,5664,5666,5667,5668,5671,5672,5673,5674,5675,5703,5711,5712,5720,5726,5727,5731,5732,5734,5741,5742,5744,5747,5751,5752,5800,5811,5820,5830,5837,5838,5925,5931,5932,5945,5946,5948,6311,6335,6766,7673,8093,8152,8635,9373,9513,9730,9914]};
1128
1129const vp_woo_pont_state_coordinates = {
1130	'BU': [47.4894, 19.1041],
1131	'PE': [47.3833, 19.4404],
1132	'VA': [47.1312, 16.8118],
1133	'BK': [46.6076, 19.3895],
1134	'BE': [46.6908, 21.0699],
1135	'BA': [46.0688, 18.2382],
1136	'BZ': [48.2170, 21.0209],
1137	'CS': [46.4470, 20.2191],
1138	'FE': [47.1779, 18.5847],
vendor: 5,070 bytes, lines 1139-1199
1139	'GS': [47.6404, 17.3863],
1140	'HB': [47.4835, 21.6140],
1141	'HE': [47.8131, 20.1920],
1142	'JN': [47.2266, 20.5014],
1143	'KE': [47.6198, 18.3420],
1144	'NO': [48.0130, 19.5863],
1145	'SO': [46.4838, 17.6299],
1146	'SZ': [48.0260, 22.1060],
1147	'TO': [46.5258, 18.5796],
1148	'VE': [47.1683, 17.7088],
1149	'ZA': [46.6823, 16.9070]
1150};
1151const vp_woo_pont_country_coordinates = {
1152	'CZ': [49.8509, 15.6689],
1153	'SK': [48.8394, 19.8575],
1154	'RO': [45.9280, 24.8392],
1155	'PL': [52.1721, 19.3782],
1156	'DE': [51.1657, 10.4515],
1157	'AT': [47.5162, 14.5501],
1158	'SI': [46.1512, 14.9955],
1159	'HR': [45.1000, 15.2000],
1160	'BA': [44.0000, 18.0000],
1161	'RS': [44.0165, 21.0059],
1162	'UA': [48.3794, 31.1656],
1163	'BG': [42.7339, 25.4858],
1164	'HR': [45.1000, 15.2000],
1165	'IT': [41.8719, 12.5674],
1166	'CH': [46.8182, 8.2275],
1167	'LI': [47.1660, 9.5554],
1168	'MK': [41.6086, 21.7453],
1169	'AL': [41.1533, 20.1683],
1170	'ME': [42.7087, 19.3744],
1171	'XK': [42.6026, 20.9020],
1172	'GR': [39.0742, 21.8243],
1173	'TR': [38.9637, 35.2433],
1174	'MD': [47.4116, 28.3699],
1175	'BY': [53.7098, 27.9534],
1176	'RU': [61.5240, 105.3188],
1177	'LT': [55.1694, 23.8813],
1178	'LV': [56.8796, 24.6032],
1179	'EE': [58.5953, 25.0136],
1180	'FI': [61.9241, 25.7482],
1181	'NO': [60.4720, 8.4689],
1182	'SE': [60.1282, 18.6435],
1183	'DK': [56.2639, 9.5018],
1184	'FR': [46.6034, 1.8883],
1185	'NL': [52.1326, 5.2913],
1186	'LU': [49.8153, 6.1296],
1187	'PT': [39.3999, -8.2245],
1188	'IE': [53.4129, -8.2439],
1189}
1190
1191module.exports = {
1192	vp_woo_pont_state_postcodes,
1193    vp_woo_pont_state_coordinates,
1194    vp_woo_pont_country_coordinates,
1195};
1196},{}],3:[function(require,module,exports){
1197!function(e,o){if("function"==typeof define&&define.amd)define(["exports"],o);else if("undefined"!=typeof exports)o(exports);else{var t={};o(t),e.bodyScrollLock=t}}(this,function(exports){"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=!1;if("undefined"!=typeof window){var e={get passive(){t=!0}};window.addEventListener("testPassive",null,e),window.removeEventListener("testPassive",null,e)}var n="undefined"!=typeof window&&window.navigator&&window.navigator.platform&&(/iP(ad|hone|od)/.test(window.navigator.platform)||"MacIntel"===window.navigator.platform&&1<window.navigator.maxTouchPoints),i=[],d=!1,l=-1,c=void 0,s=void 0,u=void 0,a=function(o){return i.some(function(e){return!(!e.options.allowTouchMove||!e.options.allowTouchMove(o))})},v=function(e){var o=e||window.event;return!!a(o.target)||(1<o.touches.length||(o.preventDefault&&o.preventDefault(),!1))},r=function(){void 0!==u&&(document.body.style.paddingRight=u,u=void 0),void 0!==c&&(document.body.style.overflow=c,c=void 0)},f=function(){if(void 0!==s){var e=-parseInt(document.body.style.top,10),o=-parseInt(document.body.style.left,10);document.body.style.position=s.position,document.body.style.top=s.top,document.body.style.left=s.left,window.scrollTo(o,e),s=void 0}};exports.disableBodyScroll=function(r,e){if(r){if(!i.some(function(e){return e.targetElement===r})){var o={targetElement:r,options:e||{}};i=[].concat(function(e){if(Array.isArray(e)){for(var o=0,t=Array(e.length);o<e.length;o++)t[o]=e[o];return t}return Array.from(e)}(i),[o]),n?window.requestAnimationFrame(function(){if(void 0===s){s={position:document.body.style.position,top:document.body.style.top,left:document.body.style.left};var e=window,o=e.scrollY,t=e.scrollX,n=e.innerHeight;document.body.style.position="fixed",document.body.style.top=-o,document.body.style.left=-t,setTimeout(function(){return window.requestAnimationFrame(function(){var e=n-window.innerHeight;e&&n<=o&&(document.body.style.top=-(o+e))})},300)}}):function(e){if(void 0===u){var o=!!e&&!0===e.reserveScrollBarGap,t=window.innerWidth-document.documentElement.clientWidth;if(o&&0<t){var n=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"),10);u=document.body.style.paddingRight,document.body.style.paddingRight=n+t+"px"}}void 0===c&&(c=document.body.style.overflow,document.body.style.overflow="hidden")}(e),n&&(r.ontouchstart=function(e){1===e.targetTouches.length&&(l=e.targetTouches[0].clientY)},r.ontouchmove=function(e){var o,t,n,i;1===e.targetTouches.length&&(t=r,i=(o=e).targetTouches[0].clientY-l,!a(o.target)&&(t&&0===t.scrollTop&&0<i?v(o):(n=t)&&n.scrollHeight-n.scrollTop<=n.clientHeight&&i<0?v(o):o.stopPropagation()))},d||(document.addEventListener("touchmove",v,t?{passive:!1}:void 0),d=!0))}}else console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.")},exports.clearAllBodyScrollLocks=function(){n&&(i.forEach(function(e){e.targetElement.ontouchstart=null,e.targetElement.ontouchmove=null}),d&&(document.removeEventListener("touchmove",v,t?{passive:!1}:void 0),d=!1),l=-1),n?f():r(),i=[]},exports.enableBodyScroll=function(o){o?(i=i.filter(function(e){return e.targetElement!==o}),n&&(o.ontouchstart=null,o.ontouchmove=null,d&&0===i.length&&(document.removeEventListener("touchmove",v,t?{passive:!1}:void 0),d=!1)),n?f():r()):console.error("enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.")}});
1198
1199},{}],4:[function(require,module,exports){
vendor: 25,863 bytes, lines 1200-2107
1200(function (global, factory) {
1201  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
1202  typeof define === 'function' && define.amd ? define(['exports'], factory) :
1203  (global = global || self, factory(global.JsSearch = {}));
1204}(this, (function (exports) { 'use strict';
1205
1206  /**
1207   * Indexes for all substring searches (e.g. the term "cat" is indexed as "c", "ca", "cat", "a", "at", and "t").
1208   */
1209  var AllSubstringsIndexStrategy = /*#__PURE__*/function () {
1210    function AllSubstringsIndexStrategy() {}
1211
1212    var _proto = AllSubstringsIndexStrategy.prototype;
1213
1214    /**
1215     * @inheritDocs
1216     */
1217    _proto.expandToken = function expandToken(token) {
1218      var expandedTokens = [];
1219      var string;
1220
1221      for (var i = 0, length = token.length; i < length; ++i) {
1222        string = '';
1223
1224        for (var j = i; j < length; ++j) {
1225          string += token.charAt(j);
1226          expandedTokens.push(string);
1227        }
1228      }
1229
1230      return expandedTokens;
1231    };
1232
1233    return AllSubstringsIndexStrategy;
1234  }();
1235
1236  /**
1237   * Indexes for exact word matches.
1238   */
1239  var ExactWordIndexStrategy = /*#__PURE__*/function () {
1240    function ExactWordIndexStrategy() {}
1241
1242    var _proto = ExactWordIndexStrategy.prototype;
1243
1244    /**
1245     * @inheritDocs
1246     */
1247    _proto.expandToken = function expandToken(token) {
1248      return token ? [token] : [];
1249    };
1250
1251    return ExactWordIndexStrategy;
1252  }();
1253
1254  /**
1255   * Indexes for prefix searches (e.g. the term "cat" is indexed as "c", "ca", and "cat" allowing prefix search lookups).
1256   */
1257  var PrefixIndexStrategy = /*#__PURE__*/function () {
1258    function PrefixIndexStrategy() {}
1259
1260    var _proto = PrefixIndexStrategy.prototype;
1261
1262    /**
1263     * @inheritDocs
1264     */
1265    _proto.expandToken = function expandToken(token) {
1266      var expandedTokens = [];
1267      var string = '';
1268
1269      for (var i = 0, length = token.length; i < length; ++i) {
1270        string += token.charAt(i);
1271        expandedTokens.push(string);
1272      }
1273
1274      return expandedTokens;
1275    };
1276
1277    return PrefixIndexStrategy;
1278  }();
1279
1280  /**
1281   * Enforces case-sensitive text matches.
1282   */
1283  var CaseSensitiveSanitizer = /*#__PURE__*/function () {
1284    function CaseSensitiveSanitizer() {}
1285
1286    var _proto = CaseSensitiveSanitizer.prototype;
1287
1288    /**
1289     * @inheritDocs
1290     */
1291    _proto.sanitize = function sanitize(text) {
1292      return text ? text.trim() : '';
1293    };
1294
1295    return CaseSensitiveSanitizer;
1296  }();
1297
1298  /**
1299   * Sanitizes text by converting to a locale-friendly lower-case version and triming leading and trailing whitespace.
1300   */
1301  var LowerCaseSanitizer = /*#__PURE__*/function () {
1302    function LowerCaseSanitizer() {}
1303
1304    var _proto = LowerCaseSanitizer.prototype;
1305
1306    /**
1307     * @inheritDocs
1308     */
1309    _proto.sanitize = function sanitize(text) {
1310      return text ? text.toLocaleLowerCase().trim() : '';
1311    };
1312
1313    return LowerCaseSanitizer;
1314  }();
1315
1316  /**
1317   * Find and return a nested object value.
1318   *
1319   * @param object to crawl
1320   * @param path Property path
1321   * @returns {any}
1322   */
1323  function getNestedFieldValue(object, path) {
1324    path = path || [];
1325    object = object || {};
1326    var value = object; // walk down the property path
1327
1328    for (var i = 0; i < path.length; i++) {
1329      value = value[path[i]];
1330
1331      if (value == null) {
1332        return null;
1333      }
1334    }
1335
1336    return value;
1337  }
1338
1339  /**
1340   * Search index capable of returning results matching a set of tokens and ranked according to TF-IDF.
1341   */
1342  var TfIdfSearchIndex = /*#__PURE__*/function () {
1343    function TfIdfSearchIndex(uidFieldName) {
1344      this._uidFieldName = uidFieldName;
1345      this._tokenToIdfCache = {};
1346      this._tokenMap = {};
1347    }
1348    /**
1349     * @inheritDocs
1350     */
1351
1352
1353    var _proto = TfIdfSearchIndex.prototype;
1354
1355    _proto.indexDocument = function indexDocument(token, uid, doc) {
1356      this._tokenToIdfCache = {}; // New index invalidates previous IDF caches
1357
1358      var tokenMap = this._tokenMap;
1359      var tokenDatum;
1360
1361      if (typeof tokenMap[token] !== 'object') {
1362        tokenMap[token] = tokenDatum = {
1363          $numDocumentOccurrences: 0,
1364          $totalNumOccurrences: 1,
1365          $uidMap: {}
1366        };
1367      } else {
1368        tokenDatum = tokenMap[token];
1369        tokenDatum.$totalNumOccurrences++;
1370      }
1371
1372      var uidMap = tokenDatum.$uidMap;
1373
1374      if (typeof uidMap[uid] !== 'object') {
1375        tokenDatum.$numDocumentOccurrences++;
1376        uidMap[uid] = {
1377          $document: doc,
1378          $numTokenOccurrences: 1
1379        };
1380      } else {
1381        uidMap[uid].$numTokenOccurrences++;
1382      }
1383    }
1384    /**
1385     * @inheritDocs
1386     */
1387    ;
1388
1389    _proto.search = function search(tokens, corpus) {
1390      var uidToDocumentMap = {};
1391
1392      for (var i = 0, numTokens = tokens.length; i < numTokens; i++) {
1393        var token = tokens[i];
1394        var tokenMetadata = this._tokenMap[token]; // Short circuit if no matches were found for any given token.
1395
1396        if (!tokenMetadata) {
1397          return [];
1398        }
1399
1400        if (i === 0) {
1401          var keys = Object.keys(tokenMetadata.$uidMap);
1402
1403          for (var j = 0, numKeys = keys.length; j < numKeys; j++) {
1404            var uid = keys[j];
1405            uidToDocumentMap[uid] = tokenMetadata.$uidMap[uid].$document;
1406          }
1407        } else {
1408          var keys = Object.keys(uidToDocumentMap);
1409
1410          for (var j = 0, numKeys = keys.length; j < numKeys; j++) {
1411            var uid = keys[j];
1412
1413            if (typeof tokenMetadata.$uidMap[uid] !== 'object') {
1414              delete uidToDocumentMap[uid];
1415            }
1416          }
1417        }
1418      }
1419
1420      var documents = [];
1421
1422      for (var uid in uidToDocumentMap) {
1423        documents.push(uidToDocumentMap[uid]);
1424      }
1425
1426      var calculateTfIdf = this._createCalculateTfIdf(); // Return documents sorted by TF-IDF
1427
1428
1429      return documents.sort(function (documentA, documentB) {
1430        return calculateTfIdf(tokens, documentB, corpus) - calculateTfIdf(tokens, documentA, corpus);
1431      });
1432    };
1433
1434    _proto._createCalculateIdf = function _createCalculateIdf() {
1435      var tokenMap = this._tokenMap;
1436      var tokenToIdfCache = this._tokenToIdfCache;
1437      return function calculateIdf(token, documents) {
1438        if (!tokenToIdfCache[token]) {
1439          var numDocumentsWithToken = typeof tokenMap[token] !== 'undefined' ? tokenMap[token].$numDocumentOccurrences : 0;
1440          tokenToIdfCache[token] = 1 + Math.log(documents.length / (1 + numDocumentsWithToken));
1441        }
1442
1443        return tokenToIdfCache[token];
1444      };
1445    };
1446
1447    _proto._createCalculateTfIdf = function _createCalculateTfIdf() {
1448      var tokenMap = this._tokenMap;
1449      var uidFieldName = this._uidFieldName;
1450
1451      var calculateIdf = this._createCalculateIdf();
1452
1453      return function calculateTfIdf(tokens, document, documents) {
1454        var score = 0;
1455
1456        for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
1457          var token = tokens[i];
1458          var inverseDocumentFrequency = calculateIdf(token, documents);
1459
1460          if (inverseDocumentFrequency === Infinity) {
1461            inverseDocumentFrequency = 0;
1462          }
1463
1464          var uid;
1465
1466          if (uidFieldName instanceof Array) {
1467            uid = document && getNestedFieldValue(document, uidFieldName);
1468          } else {
1469            uid = document && document[uidFieldName];
1470          }
1471
1472          var termFrequency = typeof tokenMap[token] !== 'undefined' && typeof tokenMap[token].$uidMap[uid] !== 'undefined' ? tokenMap[token].$uidMap[uid].$numTokenOccurrences : 0;
1473          score += termFrequency * inverseDocumentFrequency;
1474        }
1475
1476        return score;
1477      };
1478    };
1479
1480    return TfIdfSearchIndex;
1481  }();
1482
1483  /**
1484   * Search index capable of returning results matching a set of tokens but without any meaningful rank or order.
1485   */
1486  var UnorderedSearchIndex = /*#__PURE__*/function () {
1487    function UnorderedSearchIndex() {
1488      this._tokenToUidToDocumentMap = {};
1489    }
1490    /**
1491     * @inheritDocs
1492     */
1493
1494
1495    var _proto = UnorderedSearchIndex.prototype;
1496
1497    _proto.indexDocument = function indexDocument(token, uid, doc) {
1498      if (typeof this._tokenToUidToDocumentMap[token] !== 'object') {
1499        this._tokenToUidToDocumentMap[token] = {};
1500      }
1501
1502      this._tokenToUidToDocumentMap[token][uid] = doc;
1503    }
1504    /**
1505     * @inheritDocs
1506     */
1507    ;
1508
1509    _proto.search = function search(tokens, corpus) {
1510      var intersectingDocumentMap = {};
1511      var tokenToUidToDocumentMap = this._tokenToUidToDocumentMap;
1512
1513      for (var i = 0, numTokens = tokens.length; i < numTokens; i++) {
1514        var token = tokens[i];
1515        var documentMap = tokenToUidToDocumentMap[token]; // Short circuit if no matches were found for any given token.
1516
1517        if (!documentMap) {
1518          return [];
1519        }
1520
1521        if (i === 0) {
1522          var keys = Object.keys(documentMap);
1523
1524          for (var j = 0, numKeys = keys.length; j < numKeys; j++) {
1525            var uid = keys[j];
1526            intersectingDocumentMap[uid] = documentMap[uid];
1527          }
1528        } else {
1529          var keys = Object.keys(intersectingDocumentMap);
1530
1531          for (var j = 0, numKeys = keys.length; j < numKeys; j++) {
1532            var uid = keys[j];
1533
1534            if (typeof documentMap[uid] !== 'object') {
1535              delete intersectingDocumentMap[uid];
1536            }
1537          }
1538        }
1539      }
1540
1541      var keys = Object.keys(intersectingDocumentMap);
1542      var documents = [];
1543
1544      for (var i = 0, numKeys = keys.length; i < numKeys; i++) {
1545        var uid = keys[i];
1546        documents.push(intersectingDocumentMap[uid]);
1547      }
1548
1549      return documents;
1550    };
1551
1552    return UnorderedSearchIndex;
1553  }();
1554
1555  var REGEX = /[^a-zа-яё0-9\-']+/i;
1556  /**
1557   * Simple tokenizer that splits strings on whitespace characters and returns an array of all non-empty substrings.
1558   */
1559
1560  var SimpleTokenizer = /*#__PURE__*/function () {
1561    function SimpleTokenizer() {}
1562
1563    var _proto = SimpleTokenizer.prototype;
1564
1565    /**
1566     * @inheritDocs
1567     */
1568    _proto.tokenize = function tokenize(text) {
1569      return text.split(REGEX).filter(function (text) {
1570        return text;
1571      } // Filter empty tokens
1572      );
1573    };
1574
1575    return SimpleTokenizer;
1576  }();
1577
1578  /**
1579   * Stemming is the process of reducing search tokens to their root (or stem) so that searches for different forms of a
1580   * word will match. For example "search", "searching" and "searched" are all reduced to the stem "search".
1581   *
1582   * <p>This stemming tokenizer converts tokens (words) to their stem forms before returning them. It requires an
1583   * external stemming function to be provided; for this purpose I recommend the NPM 'porter-stemmer' library.
1584   *
1585   * <p>For more information see http : //tartarus.org/~martin/PorterStemmer/
1586   */
1587  var StemmingTokenizer = /*#__PURE__*/function () {
1588    /**
1589     * Constructor.
1590     *
1591     * @param stemmingFunction Function capable of accepting a word and returning its stem.
1592     * @param decoratedIndexStrategy Index strategy to be run after all stop words have been removed.
1593     */
1594    function StemmingTokenizer(stemmingFunction, decoratedTokenizer) {
1595      this._stemmingFunction = stemmingFunction;
1596      this._tokenizer = decoratedTokenizer;
1597    }
1598    /**
1599     * @inheritDocs
1600     */
1601
1602
1603    var _proto = StemmingTokenizer.prototype;
1604
1605    _proto.tokenize = function tokenize(text) {
1606      return this._tokenizer.tokenize(text).map(this._stemmingFunction);
1607    };
1608
1609    return StemmingTokenizer;
1610  }();
1611
1612  /**
1613   * Stop words list copied from Lunr JS.
1614   */
1615  var StopWordsMap = {
1616    a: true,
1617    able: true,
1618    about: true,
1619    across: true,
1620    after: true,
1621    all: true,
1622    almost: true,
1623    also: true,
1624    am: true,
1625    among: true,
1626    an: true,
1627    and: true,
1628    any: true,
1629    are: true,
1630    as: true,
1631    at: true,
1632    be: true,
1633    because: true,
1634    been: true,
1635    but: true,
1636    by: true,
1637    can: true,
1638    cannot: true,
1639    could: true,
1640    dear: true,
1641    did: true,
1642    'do': true,
1643    does: true,
1644    either: true,
1645    'else': true,
1646    ever: true,
1647    every: true,
1648    'for': true,
1649    from: true,
1650    'get': true,
1651    got: true,
1652    had: true,
1653    has: true,
1654    have: true,
1655    he: true,
1656    her: true,
1657    hers: true,
1658    him: true,
1659    his: true,
1660    how: true,
1661    however: true,
1662    i: true,
1663    'if': true,
1664    'in': true,
1665    into: true,
1666    is: true,
1667    it: true,
1668    its: true,
1669    just: true,
1670    least: true,
1671    "let": true,
1672    like: true,
1673    likely: true,
1674    may: true,
1675    me: true,
1676    might: true,
1677    most: true,
1678    must: true,
1679    my: true,
1680    neither: true,
1681    no: true,
1682    nor: true,
1683    not: true,
1684    of: true,
1685    off: true,
1686    often: true,
1687    on: true,
1688    only: true,
1689    or: true,
1690    other: true,
1691    our: true,
1692    own: true,
1693    rather: true,
1694    said: true,
1695    say: true,
1696    says: true,
1697    she: true,
1698    should: true,
1699    since: true,
1700    so: true,
1701    some: true,
1702    than: true,
1703    that: true,
1704    the: true,
1705    their: true,
1706    them: true,
1707    then: true,
1708    there: true,
1709    these: true,
1710    they: true,
1711    'this': true,
1712    tis: true,
1713    to: true,
1714    too: true,
1715    twas: true,
1716    us: true,
1717    wants: true,
1718    was: true,
1719    we: true,
1720    were: true,
1721    what: true,
1722    when: true,
1723    where: true,
1724    which: true,
1725    'while': true,
1726    who: true,
1727    whom: true,
1728    why: true,
1729    will: true,
1730    'with': true,
1731    would: true,
1732    yet: true,
1733    you: true,
1734    your: true
1735  }; // Prevent false positives for inherited properties
1736
1737  StopWordsMap.constructor = false;
1738  StopWordsMap.hasOwnProperty = false;
1739  StopWordsMap.isPrototypeOf = false;
1740  StopWordsMap.propertyIsEnumerable = false;
1741  StopWordsMap.toLocaleString = false;
1742  StopWordsMap.toString = false;
1743  StopWordsMap.valueOf = false;
1744
1745  /**
1746   * Stop words are very common (e.g. "a", "and", "the") and are often not semantically meaningful in the context of a
1747   * search. This tokenizer removes stop words from a set of tokens before passing the remaining tokens along for
1748   * indexing or searching purposes.
1749   */
1750
1751  var StopWordsTokenizer = /*#__PURE__*/function () {
1752    /**
1753     * Constructor.
1754     *
1755     * @param decoratedIndexStrategy Index strategy to be run after all stop words have been removed.
1756     */
1757    function StopWordsTokenizer(decoratedTokenizer) {
1758      this._tokenizer = decoratedTokenizer;
1759    }
1760    /**
1761     * @inheritDocs
1762     */
1763
1764
1765    var _proto = StopWordsTokenizer.prototype;
1766
1767    _proto.tokenize = function tokenize(text) {
1768      return this._tokenizer.tokenize(text).filter(function (token) {
1769        return !StopWordsMap[token];
1770      });
1771    };
1772
1773    return StopWordsTokenizer;
1774  }();
1775
1776  function _defineProperties(target, props) {
1777    for (var i = 0; i < props.length; i++) {
1778      var descriptor = props[i];
1779      descriptor.enumerable = descriptor.enumerable || false;
1780      descriptor.configurable = true;
1781      if ("value" in descriptor) descriptor.writable = true;
1782      Object.defineProperty(target, descriptor.key, descriptor);
1783    }
1784  }
1785
1786  function _createClass(Constructor, protoProps, staticProps) {
1787    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
1788    if (staticProps) _defineProperties(Constructor, staticProps);
1789    return Constructor;
1790  }
1791
1792  /**
1793   * Simple client-side searching within a set of documents.
1794   *
1795   * <p>Documents can be searched by any number of fields. Indexing and search strategies are highly customizable.
1796   */
1797  var Search = /*#__PURE__*/function () {
1798    /**
1799     * Array containing either a property name or a path (list of property names) to a nested value
1800     */
1801
1802    /**
1803     * Constructor.
1804     * @param uidFieldName Field containing values that uniquely identify search documents; this field's values are used
1805     *                     to ensure that a search result set does not contain duplicate objects.
1806     */
1807    function Search(uidFieldName) {
1808      if (!uidFieldName) {
1809        throw Error('js-search requires a uid field name constructor parameter');
1810      }
1811
1812      this._uidFieldName = uidFieldName; // Set default/recommended strategies
1813
1814      this._indexStrategy = new PrefixIndexStrategy();
1815      this._searchIndex = new TfIdfSearchIndex(uidFieldName);
1816      this._sanitizer = new LowerCaseSanitizer();
1817      this._tokenizer = new SimpleTokenizer();
1818      this._documents = [];
1819      this._searchableFields = [];
1820    }
1821    /**
1822     * Override the default index strategy.
1823     * @param value Custom index strategy
1824     * @throws Error if documents have already been indexed by this search instance
1825     */
1826
1827
1828    var _proto = Search.prototype;
1829
1830    /**
1831     * Add a searchable document to the index. Document will automatically be indexed for search.
1832     * @param document
1833     */
1834    _proto.addDocument = function addDocument(document) {
1835      this.addDocuments([document]);
1836    }
1837    /**
1838     * Adds searchable documents to the index. Documents will automatically be indexed for search.
1839     * @param document
1840     */
1841    ;
1842
1843    _proto.addDocuments = function addDocuments(documents) {
1844      this._documents = this._documents.concat(documents);
1845      this.indexDocuments_(documents, this._searchableFields);
1846    }
1847    /**
1848     * Add a new searchable field to the index. Existing documents will automatically be indexed using this new field.
1849     *
1850     * @param field Searchable field or field path. Pass a string to index a top-level field and an array of strings for nested fields.
1851     */
1852    ;
1853
1854    _proto.addIndex = function addIndex(field) {
1855      this._searchableFields.push(field);
1856
1857      this.indexDocuments_(this._documents, [field]);
1858    }
1859    /**
1860     * Search all documents for ones matching the specified query text.
1861     * @param query
1862     * @returns {Array<Object>}
1863     */
1864    ;
1865
1866    _proto.search = function search(query) {
1867      var tokens = this._tokenizer.tokenize(this._sanitizer.sanitize(query));
1868
1869      return this._searchIndex.search(tokens, this._documents);
1870    }
1871    /**
1872     * @param documents
1873     * @param _searchableFields Array containing property names and paths (lists of property names) to nested values
1874     * @private
1875     */
1876    ;
1877
1878    _proto.indexDocuments_ = function indexDocuments_(documents, _searchableFields) {
1879      this._initialized = true;
1880      var indexStrategy = this._indexStrategy;
1881      var sanitizer = this._sanitizer;
1882      var searchIndex = this._searchIndex;
1883      var tokenizer = this._tokenizer;
1884      var uidFieldName = this._uidFieldName;
1885
1886      for (var di = 0, numDocuments = documents.length; di < numDocuments; di++) {
1887        var doc = documents[di];
1888        var uid;
1889
1890        if (uidFieldName instanceof Array) {
1891          uid = getNestedFieldValue(doc, uidFieldName);
1892        } else {
1893          uid = doc[uidFieldName];
1894        }
1895
1896        for (var sfi = 0, numSearchableFields = _searchableFields.length; sfi < numSearchableFields; sfi++) {
1897          var fieldValue;
1898          var searchableField = _searchableFields[sfi];
1899
1900          if (searchableField instanceof Array) {
1901            fieldValue = getNestedFieldValue(doc, searchableField);
1902          } else {
1903            fieldValue = doc[searchableField];
1904          }
1905
1906          if (fieldValue != null && typeof fieldValue !== 'string' && fieldValue.toString) {
1907            fieldValue = fieldValue.toString();
1908          }
1909
1910          if (typeof fieldValue === 'string') {
1911            var fieldTokens = tokenizer.tokenize(sanitizer.sanitize(fieldValue));
1912
1913            for (var fti = 0, numFieldValues = fieldTokens.length; fti < numFieldValues; fti++) {
1914              var fieldToken = fieldTokens[fti];
1915              var expandedTokens = indexStrategy.expandToken(fieldToken);
1916
1917              for (var eti = 0, nummExpandedTokens = expandedTokens.length; eti < nummExpandedTokens; eti++) {
1918                var expandedToken = expandedTokens[eti];
1919                searchIndex.indexDocument(expandedToken, uid, doc);
1920              }
1921            }
1922          }
1923        }
1924      }
1925    };
1926
1927    _createClass(Search, [{
1928      key: "indexStrategy",
1929      set: function set(value) {
1930        if (this._initialized) {
1931          throw Error('IIndexStrategy cannot be set after initialization');
1932        }
1933
1934        this._indexStrategy = value;
1935      },
1936      get: function get() {
1937        return this._indexStrategy;
1938      }
1939      /**
1940       * Override the default text sanitizing strategy.
1941       * @param value Custom text sanitizing strategy
1942       * @throws Error if documents have already been indexed by this search instance
1943       */
1944
1945    }, {
1946      key: "sanitizer",
1947      set: function set(value) {
1948        if (this._initialized) {
1949          throw Error('ISanitizer cannot be set after initialization');
1950        }
1951
1952        this._sanitizer = value;
1953      },
1954      get: function get() {
1955        return this._sanitizer;
1956      }
1957      /**
1958       * Override the default search index strategy.
1959       * @param value Custom search index strategy
1960       * @throws Error if documents have already been indexed
1961       */
1962
1963    }, {
1964      key: "searchIndex",
1965      set: function set(value) {
1966        if (this._initialized) {
1967          throw Error('ISearchIndex cannot be set after initialization');
1968        }
1969
1970        this._searchIndex = value;
1971      },
1972      get: function get() {
1973        return this._searchIndex;
1974      }
1975      /**
1976       * Override the default text tokenizing strategy.
1977       * @param value Custom text tokenizing strategy
1978       * @throws Error if documents have already been indexed by this search instance
1979       */
1980
1981    }, {
1982      key: "tokenizer",
1983      set: function set(value) {
1984        if (this._initialized) {
1985          throw Error('ITokenizer cannot be set after initialization');
1986        }
1987
1988        this._tokenizer = value;
1989      },
1990      get: function get() {
1991        return this._tokenizer;
1992      }
1993    }]);
1994
1995    return Search;
1996  }();
1997
1998  /**
1999   * This utility highlights the occurrences of tokens within a string of text. It can be used to give visual indicators
2000   * of match criteria within searchable fields.
2001   *
2002   * <p>For performance purposes this highlighter only works with full-word or prefix token indexes.
2003   */
2004  var TokenHighlighter = /*#__PURE__*/function () {
2005    /**
2006     * Constructor.
2007     *
2008     * @param opt_indexStrategy Index strategy used by Search
2009     * @param opt_sanitizer Sanitizer used by Search
2010     * @param opt_wrapperTagName Optional wrapper tag name; defaults to 'mark' (e.g. <mark>)
2011     */
2012    function TokenHighlighter(opt_indexStrategy, opt_sanitizer, opt_wrapperTagName) {
2013      this._indexStrategy = opt_indexStrategy || new PrefixIndexStrategy();
2014      this._sanitizer = opt_sanitizer || new LowerCaseSanitizer();
2015      this._wrapperTagName = opt_wrapperTagName || 'mark';
2016    }
2017    /**
2018     * Highlights token occurrences within a string by wrapping them with a DOM element.
2019     *
2020     * @param text e.g. "john wayne"
2021     * @param tokens e.g. ["wa"]
2022     * @returns {string} e.g. "john <mark>wa</mark>yne"
2023     */
2024
2025
2026    var _proto = TokenHighlighter.prototype;
2027
2028    _proto.highlight = function highlight(text, tokens) {
2029      var tagsLength = this._wrapText('').length;
2030
2031      var tokenDictionary = Object.create(null); // Create a token map for easier lookup below.
2032
2033      for (var i = 0, numTokens = tokens.length; i < numTokens; i++) {
2034        var token = this._sanitizer.sanitize(tokens[i]);
2035
2036        var expandedTokens = this._indexStrategy.expandToken(token);
2037
2038        for (var j = 0, numExpandedTokens = expandedTokens.length; j < numExpandedTokens; j++) {
2039          var expandedToken = expandedTokens[j];
2040
2041          if (!tokenDictionary[expandedToken]) {
2042            tokenDictionary[expandedToken] = [token];
2043          } else {
2044            tokenDictionary[expandedToken].push(token);
2045          }
2046        }
2047      } // Track actualCurrentWord and sanitizedCurrentWord separately in case we encounter nested tags.
2048
2049
2050      var actualCurrentWord = '';
2051      var sanitizedCurrentWord = '';
2052      var currentWordStartIndex = 0; // Note this assumes either prefix or full word matching.
2053
2054      for (var i = 0, textLength = text.length; i < textLength; i++) {
2055        var character = text.charAt(i);
2056
2057        if (character === ' ') {
2058          actualCurrentWord = '';
2059          sanitizedCurrentWord = '';
2060          currentWordStartIndex = i + 1;
2061        } else {
2062          actualCurrentWord += character;
2063          sanitizedCurrentWord += this._sanitizer.sanitize(character);
2064        }
2065
2066        if (tokenDictionary[sanitizedCurrentWord] && tokenDictionary[sanitizedCurrentWord].indexOf(sanitizedCurrentWord) >= 0) {
2067          actualCurrentWord = this._wrapText(actualCurrentWord);
2068          text = text.substring(0, currentWordStartIndex) + actualCurrentWord + text.substring(i + 1);
2069          i += tagsLength;
2070          textLength += tagsLength;
2071        }
2072      }
2073
2074      return text;
2075    }
2076    /**
2077     * @param text to wrap
2078     * @returns Text wrapped by wrapper tag (e.g. "foo" becomes "<mark>foo</mark>")
2079     * @private
2080     */
2081    ;
2082
2083    _proto._wrapText = function _wrapText(text) {
2084      var tagName = this._wrapperTagName;
2085      return "<" + tagName + ">" + text + "</" + tagName + ">";
2086    };
2087
2088    return TokenHighlighter;
2089  }();
2090
2091  exports.AllSubstringsIndexStrategy = AllSubstringsIndexStrategy;
2092  exports.CaseSensitiveSanitizer = CaseSensitiveSanitizer;
2093  exports.ExactWordIndexStrategy = ExactWordIndexStrategy;
2094  exports.LowerCaseSanitizer = LowerCaseSanitizer;
2095  exports.PrefixIndexStrategy = PrefixIndexStrategy;
2096  exports.Search = Search;
2097  exports.SimpleTokenizer = SimpleTokenizer;
2098  exports.StemmingTokenizer = StemmingTokenizer;
2099  exports.StopWordsMap = StopWordsMap;
2100  exports.StopWordsTokenizer = StopWordsTokenizer;
2101  exports.TfIdfSearchIndex = TfIdfSearchIndex;
2102  exports.TokenHighlighter = TokenHighlighter;
2103  exports.UnorderedSearchIndex = UnorderedSearchIndex;
2104
2105  Object.defineProperty(exports, '__esModule', { value: true });
2106
2107})));
vendor: 2,293 bytes, lines 2108-2116
2108
2109},{}],5:[function(require,module,exports){
2110/*!
2111 Leaflet.FeatureGroup.SubGroup 1.0.2+00bb0d4
2112 (c) 2015-2017 Boris Seang
2113 License BSD-2-Clause
2114 */
2115!function(e,r){"function"==typeof define&&define.amd?define(["leaflet"],r):r("object"==typeof module&&module.exports?require("leaflet"):e.L)}(this,function(e){e.FeatureGroup.SubGroup=e.FeatureGroup.extend({initialize:function(r,t){e.FeatureGroup.prototype.initialize.call(this,t),this.setParentGroup(r)},setParentGroup:function(r){var t=r instanceof e.LayerGroup;return this._parentGroup=r,this.onAdd=t?"function"==typeof r.addLayers?this._onAddToGroupBatch:this._onAddToGroup:this._onAddToMap,this.onRemove=t?"function"==typeof r.removeLayers?this._onRemoveFromGroupBatch:this._onRemoveFromGroup:this._onRemoveFromMap,this.addLayer=t?this._addLayerToGroup:this._addLayerToMap,this.removeLayer=t?this._removeLayerFromGroup:this._removeLayerFromMap,this},setParentGroupSafe:function(e){var r=this._map;return r&&r.removeLayer(this),this.setParentGroup(e),r&&r.addLayer(this),this},getParentGroup:function(){return this._parentGroup},_onAddToGroupBatch:function(e){var r=this.getLayers();this._map=e,this._parentGroup.addLayers(r)},_onRemoveFromGroupBatch:function(){var e=this.getLayers();this._parentGroup.removeLayers(e),this._map=null},_onAddToGroup:function(e){var r=this._parentGroup;this._map=e,this.eachLayer(r.addLayer,r)},_onRemoveFromGroup:function(){var e=this._parentGroup;this.eachLayer(e.removeLayer,e),this._map=null},_onAddToMap:e.FeatureGroup.prototype.onAdd,_onRemoveFromMap:e.FeatureGroup.prototype.onRemove,_addLayerToGroup:function(e){if(this.hasLayer(e))return this;e.addEventParent(this);var r=this.getLayerId(e);return this._layers[r]=e,this._map&&this._parentGroup.addLayer(e),this.fire("layeradd",{layer:e})},_removeLayerFromGroup:function(e){if(!this.hasLayer(e))return this;var r=e in this._layers?e:this.getLayerId(e);return e=this._layers[r],e.removeEventParent(this),this._map&&e&&this._parentGroup.removeLayer(e),delete this._layers[r],this.fire("layerremove",{layer:e})},_addLayerToMap:e.FeatureGroup.prototype.addLayer,_removeLayerFromMap:e.FeatureGroup.prototype.removeLayer}),e.featureGroup.subGroup=function(r,t){return new e.FeatureGroup.SubGroup(r,t)}});
2116},{"leaflet":7}],6:[function(require,module,exports){
vendor: 81,106 bytes, lines 2117-4833
2117/*
2118 * Leaflet.markercluster 1.5.3+master.e5124b2,
2119 * Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
2120 * https://github.com/Leaflet/Leaflet.markercluster
2121 * (c) 2012-2017, Dave Leaver, smartrak
2122 */
2123(function (global, factory) {
2124	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
2125	typeof define === 'function' && define.amd ? define(['exports'], factory) :
2126	(global = global || self, factory((global.Leaflet = global.Leaflet || {}, global.Leaflet.markercluster = {})));
2127}(this, function (exports) { 'use strict';
2128
2129	/*
2130	 * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
2131	 */
2132
2133	var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
2134
2135		options: {
2136			maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
2137			iconCreateFunction: null,
2138			clusterPane: L.Marker.prototype.options.pane,
2139
2140			spiderfyOnEveryZoom: false,
2141			spiderfyOnMaxZoom: true,
2142			showCoverageOnHover: true,
2143			zoomToBoundsOnClick: true,
2144			singleMarkerMode: false,
2145
2146			disableClusteringAtZoom: null,
2147
2148			// Setting this to false prevents the removal of any clusters outside of the viewpoint, which
2149			// is the default behaviour for performance reasons.
2150			removeOutsideVisibleBounds: true,
2151
2152			// Set to false to disable all animations (zoom and spiderfy).
2153			// If false, option animateAddingMarkers below has no effect.
2154			// If L.DomUtil.TRANSITION is falsy, this option has no effect.
2155			animate: true,
2156
2157			//Whether to animate adding markers after adding the MarkerClusterGroup to the map
2158			// If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
2159			animateAddingMarkers: false,
2160
2161			// Make it possible to provide custom function to calculate spiderfy shape positions
2162			spiderfyShapePositions: null,
2163
2164			//Increase to increase the distance away that spiderfied markers appear from the center
2165			spiderfyDistanceMultiplier: 1,
2166
2167			// Make it possible to specify a polyline options on a spider leg
2168			spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
2169
2170			// When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
2171			chunkedLoading: false,
2172			chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
2173			chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
2174			chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
2175
2176			//Options to pass to the L.Polygon constructor
2177			polygonOptions: {}
2178		},
2179
2180		initialize: function (options) {
2181			L.Util.setOptions(this, options);
2182			if (!this.options.iconCreateFunction) {
2183				this.options.iconCreateFunction = this._defaultIconCreateFunction;
2184			}
2185
2186			this._featureGroup = L.featureGroup();
2187			this._featureGroup.addEventParent(this);
2188
2189			this._nonPointGroup = L.featureGroup();
2190			this._nonPointGroup.addEventParent(this);
2191
2192			this._inZoomAnimation = 0;
2193			this._needsClustering = [];
2194			this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
2195			//The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
2196			this._currentShownBounds = null;
2197
2198			this._queue = [];
2199
2200			this._childMarkerEventHandlers = {
2201				'dragstart': this._childMarkerDragStart,
2202				'move': this._childMarkerMoved,
2203				'dragend': this._childMarkerDragEnd,
2204			};
2205
2206			// Hook the appropriate animation methods.
2207			var animate = L.DomUtil.TRANSITION && this.options.animate;
2208			L.extend(this, animate ? this._withAnimation : this._noAnimation);
2209			// Remember which MarkerCluster class to instantiate (animated or not).
2210			this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated;
2211		},
2212
2213		addLayer: function (layer) {
2214
2215			if (layer instanceof L.LayerGroup) {
2216				return this.addLayers([layer]);
2217			}
2218
2219			//Don't cluster non point data
2220			if (!layer.getLatLng) {
2221				this._nonPointGroup.addLayer(layer);
2222				this.fire('layeradd', { layer: layer });
2223				return this;
2224			}
2225
2226			if (!this._map) {
2227				this._needsClustering.push(layer);
2228				this.fire('layeradd', { layer: layer });
2229				return this;
2230			}
2231
2232			if (this.hasLayer(layer)) {
2233				return this;
2234			}
2235
2236
2237			//If we have already clustered we'll need to add this one to a cluster
2238
2239			if (this._unspiderfy) {
2240				this._unspiderfy();
2241			}
2242
2243			this._addLayer(layer, this._maxZoom);
2244			this.fire('layeradd', { layer: layer });
2245
2246			// Refresh bounds and weighted positions.
2247			this._topClusterLevel._recalculateBounds();
2248
2249			this._refreshClustersIcons();
2250
2251			//Work out what is visible
2252			var visibleLayer = layer,
2253			    currentZoom = this._zoom;
2254			if (layer.__parent) {
2255				while (visibleLayer.__parent._zoom >= currentZoom) {
2256					visibleLayer = visibleLayer.__parent;
2257				}
2258			}
2259
2260			if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
2261				if (this.options.animateAddingMarkers) {
2262					this._animationAddLayer(layer, visibleLayer);
2263				} else {
2264					this._animationAddLayerNonAnimated(layer, visibleLayer);
2265				}
2266			}
2267			return this;
2268		},
2269
2270		removeLayer: function (layer) {
2271
2272			if (layer instanceof L.LayerGroup) {
2273				return this.removeLayers([layer]);
2274			}
2275
2276			//Non point layers
2277			if (!layer.getLatLng) {
2278				this._nonPointGroup.removeLayer(layer);
2279				this.fire('layerremove', { layer: layer });
2280				return this;
2281			}
2282
2283			if (!this._map) {
2284				if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
2285					this._needsRemoving.push({ layer: layer, latlng: layer._latlng });
2286				}
2287				this.fire('layerremove', { layer: layer });
2288				return this;
2289			}
2290
2291			if (!layer.__parent) {
2292				return this;
2293			}
2294
2295			if (this._unspiderfy) {
2296				this._unspiderfy();
2297				this._unspiderfyLayer(layer);
2298			}
2299
2300			//Remove the marker from clusters
2301			this._removeLayer(layer, true);
2302			this.fire('layerremove', { layer: layer });
2303
2304			// Refresh bounds and weighted positions.
2305			this._topClusterLevel._recalculateBounds();
2306
2307			this._refreshClustersIcons();
2308
2309			layer.off(this._childMarkerEventHandlers, this);
2310
2311			if (this._featureGroup.hasLayer(layer)) {
2312				this._featureGroup.removeLayer(layer);
2313				if (layer.clusterShow) {
2314					layer.clusterShow();
2315				}
2316			}
2317
2318			return this;
2319		},
2320
2321		//Takes an array of markers and adds them in bulk
2322		addLayers: function (layersArray, skipLayerAddEvent) {
2323			if (!L.Util.isArray(layersArray)) {
2324				return this.addLayer(layersArray);
2325			}
2326
2327			var fg = this._featureGroup,
2328			    npg = this._nonPointGroup,
2329			    chunked = this.options.chunkedLoading,
2330			    chunkInterval = this.options.chunkInterval,
2331			    chunkProgress = this.options.chunkProgress,
2332			    l = layersArray.length,
2333			    offset = 0,
2334			    originalArray = true,
2335			    m;
2336
2337			if (this._map) {
2338				var started = (new Date()).getTime();
2339				var process = L.bind(function () {
2340					var start = (new Date()).getTime();
2341
2342					// Make sure to unspiderfy before starting to add some layers
2343					if (this._map && this._unspiderfy) {
2344						this._unspiderfy();
2345					}
2346
2347					for (; offset < l; offset++) {
2348						if (chunked && offset % 200 === 0) {
2349							// every couple hundred markers, instrument the time elapsed since processing started:
2350							var elapsed = (new Date()).getTime() - start;
2351							if (elapsed > chunkInterval) {
2352								break; // been working too hard, time to take a break :-)
2353							}
2354						}
2355
2356						m = layersArray[offset];
2357
2358						// Group of layers, append children to layersArray and skip.
2359						// Side effects:
2360						// - Total increases, so chunkProgress ratio jumps backward.
2361						// - Groups are not included in this group, only their non-group child layers (hasLayer).
2362						// Changing array length while looping does not affect performance in current browsers:
2363						// http://jsperf.com/for-loop-changing-length/6
2364						if (m instanceof L.LayerGroup) {
2365							if (originalArray) {
2366								layersArray = layersArray.slice();
2367								originalArray = false;
2368							}
2369							this._extractNonGroupLayers(m, layersArray);
2370							l = layersArray.length;
2371							continue;
2372						}
2373
2374						//Not point data, can't be clustered
2375						if (!m.getLatLng) {
2376							npg.addLayer(m);
2377							if (!skipLayerAddEvent) {
2378								this.fire('layeradd', { layer: m });
2379							}
2380							continue;
2381						}
2382
2383						if (this.hasLayer(m)) {
2384							continue;
2385						}
2386
2387						this._addLayer(m, this._maxZoom);
2388						if (!skipLayerAddEvent) {
2389							this.fire('layeradd', { layer: m });
2390						}
2391
2392						//If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will
2393						if (m.__parent) {
2394							if (m.__parent.getChildCount() === 2) {
2395								var markers = m.__parent.getAllChildMarkers(),
2396								    otherMarker = markers[0] === m ? markers[1] : markers[0];
2397								fg.removeLayer(otherMarker);
2398							}
2399						}
2400					}
2401
2402					if (chunkProgress) {
2403						// report progress and time elapsed:
2404						chunkProgress(offset, l, (new Date()).getTime() - started);
2405					}
2406
2407					// Completed processing all markers.
2408					if (offset === l) {
2409
2410						// Refresh bounds and weighted positions.
2411						this._topClusterLevel._recalculateBounds();
2412
2413						this._refreshClustersIcons();
2414
2415						this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
2416					} else {
2417						setTimeout(process, this.options.chunkDelay);
2418					}
2419				}, this);
2420
2421				process();
2422			} else {
2423				var needsClustering = this._needsClustering;
2424
2425				for (; offset < l; offset++) {
2426					m = layersArray[offset];
2427
2428					// Group of layers, append children to layersArray and skip.
2429					if (m instanceof L.LayerGroup) {
2430						if (originalArray) {
2431							layersArray = layersArray.slice();
2432							originalArray = false;
2433						}
2434						this._extractNonGroupLayers(m, layersArray);
2435						l = layersArray.length;
2436						continue;
2437					}
2438
2439					//Not point data, can't be clustered
2440					if (!m.getLatLng) {
2441						npg.addLayer(m);
2442						continue;
2443					}
2444
2445					if (this.hasLayer(m)) {
2446						continue;
2447					}
2448
2449					needsClustering.push(m);
2450				}
2451			}
2452			return this;
2453		},
2454
2455		//Takes an array of markers and removes them in bulk
2456		removeLayers: function (layersArray) {
2457			var i, m,
2458			    l = layersArray.length,
2459			    fg = this._featureGroup,
2460			    npg = this._nonPointGroup,
2461			    originalArray = true;
2462
2463			if (!this._map) {
2464				for (i = 0; i < l; i++) {
2465					m = layersArray[i];
2466
2467					// Group of layers, append children to layersArray and skip.
2468					if (m instanceof L.LayerGroup) {
2469						if (originalArray) {
2470							layersArray = layersArray.slice();
2471							originalArray = false;
2472						}
2473						this._extractNonGroupLayers(m, layersArray);
2474						l = layersArray.length;
2475						continue;
2476					}
2477
2478					this._arraySplice(this._needsClustering, m);
2479					npg.removeLayer(m);
2480					if (this.hasLayer(m)) {
2481						this._needsRemoving.push({ layer: m, latlng: m._latlng });
2482					}
2483					this.fire('layerremove', { layer: m });
2484				}
2485				return this;
2486			}
2487
2488			if (this._unspiderfy) {
2489				this._unspiderfy();
2490
2491				// Work on a copy of the array, so that next loop is not affected.
2492				var layersArray2 = layersArray.slice(),
2493				    l2 = l;
2494				for (i = 0; i < l2; i++) {
2495					m = layersArray2[i];
2496
2497					// Group of layers, append children to layersArray and skip.
2498					if (m instanceof L.LayerGroup) {
2499						this._extractNonGroupLayers(m, layersArray2);
2500						l2 = layersArray2.length;
2501						continue;
2502					}
2503
2504					this._unspiderfyLayer(m);
2505				}
2506			}
2507
2508			for (i = 0; i < l; i++) {
2509				m = layersArray[i];
2510
2511				// Group of layers, append children to layersArray and skip.
2512				if (m instanceof L.LayerGroup) {
2513					if (originalArray) {
2514						layersArray = layersArray.slice();
2515						originalArray = false;
2516					}
2517					this._extractNonGroupLayers(m, layersArray);
2518					l = layersArray.length;
2519					continue;
2520				}
2521
2522				if (!m.__parent) {
2523					npg.removeLayer(m);
2524					this.fire('layerremove', { layer: m });
2525					continue;
2526				}
2527
2528				this._removeLayer(m, true, true);
2529				this.fire('layerremove', { layer: m });
2530
2531				if (fg.hasLayer(m)) {
2532					fg.removeLayer(m);
2533					if (m.clusterShow) {
2534						m.clusterShow();
2535					}
2536				}
2537			}
2538
2539			// Refresh bounds and weighted positions.
2540			this._topClusterLevel._recalculateBounds();
2541
2542			this._refreshClustersIcons();
2543
2544			//Fix up the clusters and markers on the map
2545			this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
2546
2547			return this;
2548		},
2549
2550		//Removes all layers from the MarkerClusterGroup
2551		clearLayers: function () {
2552			//Need our own special implementation as the LayerGroup one doesn't work for us
2553
2554			//If we aren't on the map (yet), blow away the markers we know of
2555			if (!this._map) {
2556				this._needsClustering = [];
2557				this._needsRemoving = [];
2558				delete this._gridClusters;
2559				delete this._gridUnclustered;
2560			}
2561
2562			if (this._noanimationUnspiderfy) {
2563				this._noanimationUnspiderfy();
2564			}
2565
2566			//Remove all the visible layers
2567			this._featureGroup.clearLayers();
2568			this._nonPointGroup.clearLayers();
2569
2570			this.eachLayer(function (marker) {
2571				marker.off(this._childMarkerEventHandlers, this);
2572				delete marker.__parent;
2573			}, this);
2574
2575			if (this._map) {
2576				//Reset _topClusterLevel and the DistanceGrids
2577				this._generateInitialClusters();
2578			}
2579
2580			return this;
2581		},
2582
2583		//Override FeatureGroup.getBounds as it doesn't work
2584		getBounds: function () {
2585			var bounds = new L.LatLngBounds();
2586
2587			if (this._topClusterLevel) {
2588				bounds.extend(this._topClusterLevel._bounds);
2589			}
2590
2591			for (var i = this._needsClustering.length - 1; i >= 0; i--) {
2592				bounds.extend(this._needsClustering[i].getLatLng());
2593			}
2594
2595			bounds.extend(this._nonPointGroup.getBounds());
2596
2597			return bounds;
2598		},
2599
2600		//Overrides LayerGroup.eachLayer
2601		eachLayer: function (method, context) {
2602			var markers = this._needsClustering.slice(),
2603				needsRemoving = this._needsRemoving,
2604				thisNeedsRemoving, i, j;
2605
2606			if (this._topClusterLevel) {
2607				this._topClusterLevel.getAllChildMarkers(markers);
2608			}
2609
2610			for (i = markers.length - 1; i >= 0; i--) {
2611				thisNeedsRemoving = true;
2612
2613				for (j = needsRemoving.length - 1; j >= 0; j--) {
2614					if (needsRemoving[j].layer === markers[i]) {
2615						thisNeedsRemoving = false;
2616						break;
2617					}
2618				}
2619
2620				if (thisNeedsRemoving) {
2621					method.call(context, markers[i]);
2622				}
2623			}
2624
2625			this._nonPointGroup.eachLayer(method, context);
2626		},
2627
2628		//Overrides LayerGroup.getLayers
2629		getLayers: function () {
2630			var layers = [];
2631			this.eachLayer(function (l) {
2632				layers.push(l);
2633			});
2634			return layers;
2635		},
2636
2637		//Overrides LayerGroup.getLayer, WARNING: Really bad performance
2638		getLayer: function (id) {
2639			var result = null;
2640
2641			id = parseInt(id, 10);
2642
2643			this.eachLayer(function (l) {
2644				if (L.stamp(l) === id) {
2645					result = l;
2646				}
2647			});
2648
2649			return result;
2650		},
2651
2652		//Returns true if the given layer is in this MarkerClusterGroup
2653		hasLayer: function (layer) {
2654			if (!layer) {
2655				return false;
2656			}
2657
2658			var i, anArray = this._needsClustering;
2659
2660			for (i = anArray.length - 1; i >= 0; i--) {
2661				if (anArray[i] === layer) {
2662					return true;
2663				}
2664			}
2665
2666			anArray = this._needsRemoving;
2667			for (i = anArray.length - 1; i >= 0; i--) {
2668				if (anArray[i].layer === layer) {
2669					return false;
2670				}
2671			}
2672
2673			return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
2674		},
2675
2676		//Zoom down to show the given layer (spiderfying if necessary) then calls the callback
2677		zoomToShowLayer: function (layer, callback) {
2678
2679			var map = this._map;
2680
2681			if (typeof callback !== 'function') {
2682				callback = function () {};
2683			}
2684
2685			var showMarker = function () {
2686				// Assumes that map.hasLayer checks for direct appearance on map, not recursively calling
2687				// hasLayer on Layer Groups that are on map (typically not calling this MarkerClusterGroup.hasLayer, which would always return true)
2688				if ((map.hasLayer(layer) || map.hasLayer(layer.__parent)) && !this._inZoomAnimation) {
2689					this._map.off('moveend', showMarker, this);
2690					this.off('animationend', showMarker, this);
2691
2692					if (map.hasLayer(layer)) {
2693						callback();
2694					} else if (layer.__parent._icon) {
2695						this.once('spiderfied', callback, this);
2696						layer.__parent.spiderfy();
2697					}
2698				}
2699			};
2700
2701			if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
2702				//Layer is visible ond on screen, immediate return
2703				callback();
2704			} else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
2705				//Layer should be visible at this zoom level. It must not be on screen so just pan over to it
2706				this._map.on('moveend', showMarker, this);
2707				this._map.panTo(layer.getLatLng());
2708			} else {
2709				this._map.on('moveend', showMarker, this);
2710				this.on('animationend', showMarker, this);
2711				layer.__parent.zoomToBounds();
2712			}
2713		},
2714
2715		//Overrides FeatureGroup.onAdd
2716		onAdd: function (map) {
2717			this._map = map;
2718			var i, l, layer;
2719
2720			if (!isFinite(this._map.getMaxZoom())) {
2721				throw "Map has no maxZoom specified";
2722			}
2723
2724			this._featureGroup.addTo(map);
2725			this._nonPointGroup.addTo(map);
2726
2727			if (!this._gridClusters) {
2728				this._generateInitialClusters();
2729			}
2730
2731			this._maxLat = map.options.crs.projection.MAX_LATITUDE;
2732
2733			//Restore all the positions as they are in the MCG before removing them
2734			for (i = 0, l = this._needsRemoving.length; i < l; i++) {
2735				layer = this._needsRemoving[i];
2736				layer.newlatlng = layer.layer._latlng;
2737				layer.layer._latlng = layer.latlng;
2738			}
2739			//Remove them, then restore their new positions
2740			for (i = 0, l = this._needsRemoving.length; i < l; i++) {
2741				layer = this._needsRemoving[i];
2742				this._removeLayer(layer.layer, true);
2743				layer.layer._latlng = layer.newlatlng;
2744			}
2745			this._needsRemoving = [];
2746
2747			//Remember the current zoom level and bounds
2748			this._zoom = Math.round(this._map._zoom);
2749			this._currentShownBounds = this._getExpandedVisibleBounds();
2750
2751			this._map.on('zoomend', this._zoomEnd, this);
2752			this._map.on('moveend', this._moveEnd, this);
2753
2754			if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
2755				this._spiderfierOnAdd();
2756			}
2757
2758			this._bindEvents();
2759
2760			//Actually add our markers to the map:
2761			l = this._needsClustering;
2762			this._needsClustering = [];
2763			this.addLayers(l, true);
2764		},
2765
2766		//Overrides FeatureGroup.onRemove
2767		onRemove: function (map) {
2768			map.off('zoomend', this._zoomEnd, this);
2769			map.off('moveend', this._moveEnd, this);
2770
2771			this._unbindEvents();
2772
2773			//In case we are in a cluster animation
2774			this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
2775
2776			if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
2777				this._spiderfierOnRemove();
2778			}
2779
2780			delete this._maxLat;
2781
2782			//Clean up all the layers we added to the map
2783			this._hideCoverage();
2784			this._featureGroup.remove();
2785			this._nonPointGroup.remove();
2786
2787			this._featureGroup.clearLayers();
2788
2789			this._map = null;
2790		},
2791
2792		getVisibleParent: function (marker) {
2793			var vMarker = marker;
2794			while (vMarker && !vMarker._icon) {
2795				vMarker = vMarker.__parent;
2796			}
2797			return vMarker || null;
2798		},
2799
2800		//Remove the given object from the given array
2801		_arraySplice: function (anArray, obj) {
2802			for (var i = anArray.length - 1; i >= 0; i--) {
2803				if (anArray[i] === obj) {
2804					anArray.splice(i, 1);
2805					return true;
2806				}
2807			}
2808		},
2809
2810		/**
2811		 * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom.
2812		 * @param marker to be removed from _gridUnclustered.
2813		 * @param z integer bottom start zoom level (included)
2814		 * @private
2815		 */
2816		_removeFromGridUnclustered: function (marker, z) {
2817			var map = this._map,
2818			    gridUnclustered = this._gridUnclustered,
2819				minZoom = Math.floor(this._map.getMinZoom());
2820
2821			for (; z >= minZoom; z--) {
2822				if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
2823					break;
2824				}
2825			}
2826		},
2827
2828		_childMarkerDragStart: function (e) {
2829			e.target.__dragStart = e.target._latlng;
2830		},
2831
2832		_childMarkerMoved: function (e) {
2833			if (!this._ignoreMove && !e.target.__dragStart) {
2834				var isPopupOpen = e.target._popup && e.target._popup.isOpen();
2835
2836				this._moveChild(e.target, e.oldLatLng, e.latlng);
2837
2838				if (isPopupOpen) {
2839					e.target.openPopup();
2840				}
2841			}
2842		},
2843
2844		_moveChild: function (layer, from, to) {
2845			layer._latlng = from;
2846			this.removeLayer(layer);
2847
2848			layer._latlng = to;
2849			this.addLayer(layer);
2850		},
2851
2852		_childMarkerDragEnd: function (e) {
2853			var dragStart = e.target.__dragStart;
2854			delete e.target.__dragStart;
2855			if (dragStart) {
2856				this._moveChild(e.target, dragStart, e.target._latlng);
2857			}		
2858		},
2859
2860
2861		//Internal function for removing a marker from everything.
2862		//dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
2863		_removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
2864			var gridClusters = this._gridClusters,
2865				gridUnclustered = this._gridUnclustered,
2866				fg = this._featureGroup,
2867				map = this._map,
2868				minZoom = Math.floor(this._map.getMinZoom());
2869
2870			//Remove the marker from distance clusters it might be in
2871			if (removeFromDistanceGrid) {
2872				this._removeFromGridUnclustered(marker, this._maxZoom);
2873			}
2874
2875			//Work our way up the clusters removing them as we go if required
2876			var cluster = marker.__parent,
2877				markers = cluster._markers,
2878				otherMarker;
2879
2880			//Remove the marker from the immediate parents marker list
2881			this._arraySplice(markers, marker);
2882
2883			while (cluster) {
2884				cluster._childCount--;
2885				cluster._boundsNeedUpdate = true;
2886
2887				if (cluster._zoom < minZoom) {
2888					//Top level, do nothing
2889					break;
2890				} else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
2891					//We need to push the other marker up to the parent
2892					otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
2893
2894					//Update distance grid
2895					gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
2896					gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
2897
2898					//Move otherMarker up to parent
2899					this._arraySplice(cluster.__parent._childClusters, cluster);
2900					cluster.__parent._markers.push(otherMarker);
2901					otherMarker.__parent = cluster.__parent;
2902
2903					if (cluster._icon) {
2904						//Cluster is currently on the map, need to put the marker on the map instead
2905						fg.removeLayer(cluster);
2906						if (!dontUpdateMap) {
2907							fg.addLayer(otherMarker);
2908						}
2909					}
2910				} else {
2911					cluster._iconNeedsUpdate = true;
2912				}
2913
2914				cluster = cluster.__parent;
2915			}
2916
2917			delete marker.__parent;
2918		},
2919
2920		_isOrIsParent: function (el, oel) {
2921			while (oel) {
2922				if (el === oel) {
2923					return true;
2924				}
2925				oel = oel.parentNode;
2926			}
2927			return false;
2928		},
2929
2930		//Override L.Evented.fire
2931		fire: function (type, data, propagate) {
2932			if (data && data.layer instanceof L.MarkerCluster) {
2933				//Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
2934				if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
2935					return;
2936				}
2937				type = 'cluster' + type;
2938			}
2939
2940			L.FeatureGroup.prototype.fire.call(this, type, data, propagate);
2941		},
2942
2943		//Override L.Evented.listens
2944		listens: function (type, propagate) {
2945			return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate);
2946		},
2947
2948		//Default functionality
2949		_defaultIconCreateFunction: function (cluster) {
2950			var childCount = cluster.getChildCount();
2951
2952			var c = ' marker-cluster-';
2953			if (childCount < 10) {
2954				c += 'small';
2955			} else if (childCount < 100) {
2956				c += 'medium';
2957			} else {
2958				c += 'large';
2959			}
2960
2961			return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
2962		},
2963
2964		_bindEvents: function () {
2965			var map = this._map,
2966			    spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
2967			    showCoverageOnHover = this.options.showCoverageOnHover,
2968			    zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
2969			    spiderfyOnEveryZoom = this.options.spiderfyOnEveryZoom;
2970
2971			//Zoom on cluster click or spiderfy if we are at the lowest level
2972			if (spiderfyOnMaxZoom || zoomToBoundsOnClick || spiderfyOnEveryZoom) {
2973				this.on('clusterclick clusterkeypress', this._zoomOrSpiderfy, this);
2974			}
2975
2976			//Show convex hull (boundary) polygon on mouse over
2977			if (showCoverageOnHover) {
2978				this.on('clustermouseover', this._showCoverage, this);
2979				this.on('clustermouseout', this._hideCoverage, this);
2980				map.on('zoomend', this._hideCoverage, this);
2981			}
2982		},
2983
2984		_zoomOrSpiderfy: function (e) {
2985			var cluster = e.layer,
2986			    bottomCluster = cluster;
2987
2988			if (e.type === 'clusterkeypress' && e.originalEvent && e.originalEvent.keyCode !== 13) {
2989				return;
2990			}
2991
2992			while (bottomCluster._childClusters.length === 1) {
2993				bottomCluster = bottomCluster._childClusters[0];
2994			}
2995
2996			if (bottomCluster._zoom === this._maxZoom &&
2997				bottomCluster._childCount === cluster._childCount &&
2998				this.options.spiderfyOnMaxZoom) {
2999
3000				// All child markers are contained in a single cluster from this._maxZoom to this cluster.
3001				cluster.spiderfy();
3002			} else if (this.options.zoomToBoundsOnClick) {
3003				cluster.zoomToBounds();
3004			}
3005
3006			if (this.options.spiderfyOnEveryZoom) {
3007				cluster.spiderfy();
3008			}
3009
3010			// Focus the map again for keyboard users.
3011			if (e.originalEvent && e.originalEvent.keyCode === 13) {
3012				this._map._container.focus();
3013			}
3014		},
3015
3016		_showCoverage: function (e) {
3017			var map = this._map;
3018			if (this._inZoomAnimation) {
3019				return;
3020			}
3021			if (this._shownPolygon) {
3022				map.removeLayer(this._shownPolygon);
3023			}
3024			if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
3025				this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
3026				map.addLayer(this._shownPolygon);
3027			}
3028		},
3029
3030		_hideCoverage: function () {
3031			if (this._shownPolygon) {
3032				this._map.removeLayer(this._shownPolygon);
3033				this._shownPolygon = null;
3034			}
3035		},
3036
3037		_unbindEvents: function () {
3038			var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
3039				showCoverageOnHover = this.options.showCoverageOnHover,
3040				zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
3041				spiderfyOnEveryZoom = this.options.spiderfyOnEveryZoom,
3042				map = this._map;
3043
3044			if (spiderfyOnMaxZoom || zoomToBoundsOnClick || spiderfyOnEveryZoom) {
3045				this.off('clusterclick clusterkeypress', this._zoomOrSpiderfy, this);
3046			}
3047			if (showCoverageOnHover) {
3048				this.off('clustermouseover', this._showCoverage, this);
3049				this.off('clustermouseout', this._hideCoverage, this);
3050				map.off('zoomend', this._hideCoverage, this);
3051			}
3052		},
3053
3054		_zoomEnd: function () {
3055			if (!this._map) { //May have been removed from the map by a zoomEnd handler
3056				return;
3057			}
3058			this._mergeSplitClusters();
3059
3060			this._zoom = Math.round(this._map._zoom);
3061			this._currentShownBounds = this._getExpandedVisibleBounds();
3062		},
3063
3064		_moveEnd: function () {
3065			if (this._inZoomAnimation) {
3066				return;
3067			}
3068
3069			var newBounds = this._getExpandedVisibleBounds();
3070
3071			this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds);
3072			this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds);
3073
3074			this._currentShownBounds = newBounds;
3075			return;
3076		},
3077
3078		_generateInitialClusters: function () {
3079			var maxZoom = Math.ceil(this._map.getMaxZoom()),
3080				minZoom = Math.floor(this._map.getMinZoom()),
3081				radius = this.options.maxClusterRadius,
3082				radiusFn = radius;
3083
3084			//If we just set maxClusterRadius to a single number, we need to create
3085			//a simple function to return that number. Otherwise, we just have to
3086			//use the function we've passed in.
3087			if (typeof radius !== "function") {
3088				radiusFn = function () { return radius; };
3089			}
3090
3091			if (this.options.disableClusteringAtZoom !== null) {
3092				maxZoom = this.options.disableClusteringAtZoom - 1;
3093			}
3094			this._maxZoom = maxZoom;
3095			this._gridClusters = {};
3096			this._gridUnclustered = {};
3097
3098			//Set up DistanceGrids for each zoom
3099			for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
3100				this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
3101				this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
3102			}
3103
3104			// Instantiate the appropriate L.MarkerCluster class (animated or not).
3105			this._topClusterLevel = new this._markerCluster(this, minZoom - 1);
3106		},
3107
3108		//Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
3109		_addLayer: function (layer, zoom) {
3110			var gridClusters = this._gridClusters,
3111			    gridUnclustered = this._gridUnclustered,
3112				minZoom = Math.floor(this._map.getMinZoom()),
3113			    markerPoint, z;
3114
3115			if (this.options.singleMarkerMode) {
3116				this._overrideMarkerIcon(layer);
3117			}
3118
3119			layer.on(this._childMarkerEventHandlers, this);
3120
3121			//Find the lowest zoom level to slot this one in
3122			for (; zoom >= minZoom; zoom--) {
3123				markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
3124
3125				//Try find a cluster close by
3126				var closest = gridClusters[zoom].getNearObject(markerPoint);
3127				if (closest) {
3128					closest._addChild(layer);
3129					layer.__parent = closest;
3130					return;
3131				}
3132
3133				//Try find a marker close by to form a new cluster with
3134				closest = gridUnclustered[zoom].getNearObject(markerPoint);
3135				if (closest) {
3136					var parent = closest.__parent;
3137					if (parent) {
3138						this._removeLayer(closest, false);
3139					}
3140
3141					//Create new cluster with these 2 in it
3142
3143					var newCluster = new this._markerCluster(this, zoom, closest, layer);
3144					gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
3145					closest.__parent = newCluster;
3146					layer.__parent = newCluster;
3147
3148					//First create any new intermediate parent clusters that don't exist
3149					var lastParent = newCluster;
3150					for (z = zoom - 1; z > parent._zoom; z--) {
3151						lastParent = new this._markerCluster(this, z, lastParent);
3152						gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
3153					}
3154					parent._addChild(lastParent);
3155
3156					//Remove closest from this zoom level and any above that it is in, replace with newCluster
3157					this._removeFromGridUnclustered(closest, zoom);
3158
3159					return;
3160				}
3161
3162				//Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
3163				gridUnclustered[zoom].addObject(layer, markerPoint);
3164			}
3165
3166			//Didn't get in anything, add us to the top
3167			this._topClusterLevel._addChild(layer);
3168			layer.__parent = this._topClusterLevel;
3169			return;
3170		},
3171
3172		/**
3173		 * Refreshes the icon of all "dirty" visible clusters.
3174		 * Non-visible "dirty" clusters will be updated when they are added to the map.
3175		 * @private
3176		 */
3177		_refreshClustersIcons: function () {
3178			this._featureGroup.eachLayer(function (c) {
3179				if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
3180					c._updateIcon();
3181				}
3182			});
3183		},
3184
3185		//Enqueue code to fire after the marker expand/contract has happened
3186		_enqueue: function (fn) {
3187			this._queue.push(fn);
3188			if (!this._queueTimeout) {
3189				this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
3190			}
3191		},
3192		_processQueue: function () {
3193			for (var i = 0; i < this._queue.length; i++) {
3194				this._queue[i].call(this);
3195			}
3196			this._queue.length = 0;
3197			clearTimeout(this._queueTimeout);
3198			this._queueTimeout = null;
3199		},
3200
3201		//Merge and split any existing clusters that are too big or small
3202		_mergeSplitClusters: function () {
3203			var mapZoom = Math.round(this._map._zoom);
3204
3205			//In case we are starting to split before the animation finished
3206			this._processQueue();
3207
3208			if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
3209				this._animationStart();
3210				//Remove clusters now off screen
3211				this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds());
3212
3213				this._animationZoomIn(this._zoom, mapZoom);
3214
3215			} else if (this._zoom > mapZoom) { //Zoom out, merge
3216				this._animationStart();
3217
3218				this._animationZoomOut(this._zoom, mapZoom);
3219			} else {
3220				this._moveEnd();
3221			}
3222		},
3223
3224		//Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan)
3225		_getExpandedVisibleBounds: function () {
3226			if (!this.options.removeOutsideVisibleBounds) {
3227				return this._mapBoundsInfinite;
3228			} else if (L.Browser.mobile) {
3229				return this._checkBoundsMaxLat(this._map.getBounds());
3230			}
3231
3232			return this._checkBoundsMaxLat(this._map.getBounds().pad(1)); // Padding expands the bounds by its own dimensions but scaled with the given factor.
3233		},
3234
3235		/**
3236		 * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude
3237		 * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https://en.wikipedia.org/wiki/Web_Mercator#Formulas).
3238		 * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without
3239		 * this option (or outside MCG) will have their position floored (ceiled) by the projection and rendered at that limit,
3240		 * making the user think that MCG "eats" them and never displays them again.
3241		 * @param bounds L.LatLngBounds
3242		 * @returns {L.LatLngBounds}
3243		 * @private
3244		 */
3245		_checkBoundsMaxLat: function (bounds) {
3246			var maxLat = this._maxLat;
3247
3248			if (maxLat !== undefined) {
3249				if (bounds.getNorth() >= maxLat) {
3250					bounds._northEast.lat = Infinity;
3251				}
3252				if (bounds.getSouth() <= -maxLat) {
3253					bounds._southWest.lat = -Infinity;
3254				}
3255			}
3256
3257			return bounds;
3258		},
3259
3260		//Shared animation code
3261		_animationAddLayerNonAnimated: function (layer, newCluster) {
3262			if (newCluster === layer) {
3263				this._featureGroup.addLayer(layer);
3264			} else if (newCluster._childCount === 2) {
3265				newCluster._addToMap();
3266
3267				var markers = newCluster.getAllChildMarkers();
3268				this._featureGroup.removeLayer(markers[0]);
3269				this._featureGroup.removeLayer(markers[1]);
3270			} else {
3271				newCluster._updateIcon();
3272			}
3273		},
3274
3275		/**
3276		 * Extracts individual (i.e. non-group) layers from a Layer Group.
3277		 * @param group to extract layers from.
3278		 * @param output {Array} in which to store the extracted layers.
3279		 * @returns {*|Array}
3280		 * @private
3281		 */
3282		_extractNonGroupLayers: function (group, output) {
3283			var layers = group.getLayers(),
3284			    i = 0,
3285			    layer;
3286
3287			output = output || [];
3288
3289			for (; i < layers.length; i++) {
3290				layer = layers[i];
3291
3292				if (layer instanceof L.LayerGroup) {
3293					this._extractNonGroupLayers(layer, output);
3294					continue;
3295				}
3296
3297				output.push(layer);
3298			}
3299
3300			return output;
3301		},
3302
3303		/**
3304		 * Implements the singleMarkerMode option.
3305		 * @param layer Marker to re-style using the Clusters iconCreateFunction.
3306		 * @returns {L.Icon} The newly created icon.
3307		 * @private
3308		 */
3309		_overrideMarkerIcon: function (layer) {
3310			var icon = layer.options.icon = this.options.iconCreateFunction({
3311				getChildCount: function () {
3312					return 1;
3313				},
3314				getAllChildMarkers: function () {
3315					return [layer];
3316				}
3317			});
3318
3319			return icon;
3320		}
3321	});
3322
3323	// Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
3324	L.MarkerClusterGroup.include({
3325		_mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
3326	});
3327
3328	L.MarkerClusterGroup.include({
3329		_noAnimation: {
3330			//Non Animated versions of everything
3331			_animationStart: function () {
3332				//Do nothing...
3333			},
3334			_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
3335				this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
3336				this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
3337
3338				//We didn't actually animate, but we use this event to mean "clustering animations have finished"
3339				this.fire('animationend');
3340			},
3341			_animationZoomOut: function (previousZoomLevel, newZoomLevel) {
3342				this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
3343				this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
3344
3345				//We didn't actually animate, but we use this event to mean "clustering animations have finished"
3346				this.fire('animationend');
3347			},
3348			_animationAddLayer: function (layer, newCluster) {
3349				this._animationAddLayerNonAnimated(layer, newCluster);
3350			}
3351		},
3352
3353		_withAnimation: {
3354			//Animated versions here
3355			_animationStart: function () {
3356				this._map._mapPane.className += ' leaflet-cluster-anim';
3357				this._inZoomAnimation++;
3358			},
3359
3360			_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
3361				var bounds = this._getExpandedVisibleBounds(),
3362				    fg = this._featureGroup,
3363					minZoom = Math.floor(this._map.getMinZoom()),
3364				    i;
3365
3366				this._ignoreMove = true;
3367
3368				//Add all children of current clusters to map and remove those clusters from map
3369				this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
3370					var startPos = c._latlng,
3371					    markers  = c._markers,
3372					    m;
3373
3374					if (!bounds.contains(startPos)) {
3375						startPos = null;
3376					}
3377
3378					if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
3379						fg.removeLayer(c);
3380						c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
3381					} else {
3382						//Fade out old cluster
3383						c.clusterHide();
3384						c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
3385					}
3386
3387					//Remove all markers that aren't visible any more
3388					//TODO: Do we actually need to do this on the higher levels too?
3389					for (i = markers.length - 1; i >= 0; i--) {
3390						m = markers[i];
3391						if (!bounds.contains(m._latlng)) {
3392							fg.removeLayer(m);
3393						}
3394					}
3395
3396				});
3397
3398				this._forceLayout();
3399
3400				//Update opacities
3401				this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
3402				//TODO Maybe? Update markers in _recursivelyBecomeVisible
3403				fg.eachLayer(function (n) {
3404					if (!(n instanceof L.MarkerCluster) && n._icon) {
3405						n.clusterShow();
3406					}
3407				});
3408
3409				//update the positions of the just added clusters/markers
3410				this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
3411					c._recursivelyRestoreChildPositions(newZoomLevel);
3412				});
3413
3414				this._ignoreMove = false;
3415
3416				//Remove the old clusters and close the zoom animation
3417				this._enqueue(function () {
3418					//update the positions of the just added clusters/markers
3419					this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
3420						fg.removeLayer(c);
3421						c.clusterShow();
3422					});
3423
3424					this._animationEnd();
3425				});
3426			},
3427
3428			_animationZoomOut: function (previousZoomLevel, newZoomLevel) {
3429				this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
3430
3431				//Need to add markers for those that weren't on the map before but are now
3432				this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
3433				//Remove markers that were on the map before but won't be now
3434				this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds());
3435			},
3436
3437			_animationAddLayer: function (layer, newCluster) {
3438				var me = this,
3439				    fg = this._featureGroup;
3440
3441				fg.addLayer(layer);
3442				if (newCluster !== layer) {
3443					if (newCluster._childCount > 2) { //Was already a cluster
3444
3445						newCluster._updateIcon();
3446						this._forceLayout();
3447						this._animationStart();
3448
3449						layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
3450						layer.clusterHide();
3451
3452						this._enqueue(function () {
3453							fg.removeLayer(layer);
3454							layer.clusterShow();
3455
3456							me._animationEnd();
3457						});
3458
3459					} else { //Just became a cluster
3460						this._forceLayout();
3461
3462						me._animationStart();
3463						me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom);
3464					}
3465				}
3466			}
3467		},
3468
3469		// Private methods for animated versions.
3470		_animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
3471			var bounds = this._getExpandedVisibleBounds(),
3472				minZoom = Math.floor(this._map.getMinZoom());
3473
3474			//Animate all of the markers in the clusters to move to their cluster center point
3475			cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel);
3476
3477			var me = this;
3478
3479			//Update the opacity (If we immediately set it they won't animate)
3480			this._forceLayout();
3481			cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
3482
3483			//TODO: Maybe use the transition timing stuff to make this more reliable
3484			//When the animations are done, tidy up
3485			this._enqueue(function () {
3486
3487				//This cluster stopped being a cluster before the timeout fired
3488				if (cluster._childCount === 1) {
3489					var m = cluster._markers[0];
3490					//If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it
3491					this._ignoreMove = true;
3492					m.setLatLng(m.getLatLng());
3493					this._ignoreMove = false;
3494					if (m.clusterShow) {
3495						m.clusterShow();
3496					}
3497				} else {
3498					cluster._recursively(bounds, newZoomLevel, minZoom, function (c) {
3499						c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1);
3500					});
3501				}
3502				me._animationEnd();
3503			});
3504		},
3505
3506		_animationEnd: function () {
3507			if (this._map) {
3508				this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
3509			}
3510			this._inZoomAnimation--;
3511			this.fire('animationend');
3512		},
3513
3514		//Force a browser layout of stuff in the map
3515		// Should apply the current opacity and location to all elements so we can update them again for an animation
3516		_forceLayout: function () {
3517			//In my testing this works, infact offsetWidth of any element seems to work.
3518			//Could loop all this._layers and do this for each _icon if it stops working
3519
3520			L.Util.falseFn(document.body.offsetWidth);
3521		}
3522	});
3523
3524	L.markerClusterGroup = function (options) {
3525		return new L.MarkerClusterGroup(options);
3526	};
3527
3528	var MarkerCluster = L.MarkerCluster = L.Marker.extend({
3529		options: L.Icon.prototype.options,
3530
3531		initialize: function (group, zoom, a, b) {
3532
3533			L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0),
3534	            { icon: this, pane: group.options.clusterPane });
3535
3536			this._group = group;
3537			this._zoom = zoom;
3538
3539			this._markers = [];
3540			this._childClusters = [];
3541			this._childCount = 0;
3542			this._iconNeedsUpdate = true;
3543			this._boundsNeedUpdate = true;
3544
3545			this._bounds = new L.LatLngBounds();
3546
3547			if (a) {
3548				this._addChild(a);
3549			}
3550			if (b) {
3551				this._addChild(b);
3552			}
3553		},
3554
3555		//Recursively retrieve all child markers of this cluster
3556		getAllChildMarkers: function (storageArray, ignoreDraggedMarker) {
3557			storageArray = storageArray || [];
3558
3559			for (var i = this._childClusters.length - 1; i >= 0; i--) {
3560				this._childClusters[i].getAllChildMarkers(storageArray, ignoreDraggedMarker);
3561			}
3562
3563			for (var j = this._markers.length - 1; j >= 0; j--) {
3564				if (ignoreDraggedMarker && this._markers[j].__dragStart) {
3565					continue;
3566				}
3567				storageArray.push(this._markers[j]);
3568			}
3569
3570			return storageArray;
3571		},
3572
3573		//Returns the count of how many child markers we have
3574		getChildCount: function () {
3575			return this._childCount;
3576		},
3577
3578		//Zoom to the minimum of showing all of the child markers, or the extents of this cluster
3579		zoomToBounds: function (fitBoundsOptions) {
3580			var childClusters = this._childClusters.slice(),
3581				map = this._group._map,
3582				boundsZoom = map.getBoundsZoom(this._bounds),
3583				zoom = this._zoom + 1,
3584				mapZoom = map.getZoom(),
3585				i;
3586
3587			//calculate how far we need to zoom down to see all of the markers
3588			while (childClusters.length > 0 && boundsZoom > zoom) {
3589				zoom++;
3590				var newClusters = [];
3591				for (i = 0; i < childClusters.length; i++) {
3592					newClusters = newClusters.concat(childClusters[i]._childClusters);
3593				}
3594				childClusters = newClusters;
3595			}
3596
3597			if (boundsZoom > zoom) {
3598				this._group._map.setView(this._latlng, zoom);
3599			} else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
3600				this._group._map.setView(this._latlng, mapZoom + 1);
3601			} else {
3602				this._group._map.fitBounds(this._bounds, fitBoundsOptions);
3603			}
3604		},
3605
3606		getBounds: function () {
3607			var bounds = new L.LatLngBounds();
3608			bounds.extend(this._bounds);
3609			return bounds;
3610		},
3611
3612		_updateIcon: function () {
3613			this._iconNeedsUpdate = true;
3614			if (this._icon) {
3615				this.setIcon(this);
3616			}
3617		},
3618
3619		//Cludge for Icon, we pretend to be an icon for performance
3620		createIcon: function () {
3621			if (this._iconNeedsUpdate) {
3622				this._iconObj = this._group.options.iconCreateFunction(this);
3623				this._iconNeedsUpdate = false;
3624			}
3625			return this._iconObj.createIcon();
3626		},
3627		createShadow: function () {
3628			return this._iconObj.createShadow();
3629		},
3630
3631
3632		_addChild: function (new1, isNotificationFromChild) {
3633
3634			this._iconNeedsUpdate = true;
3635
3636			this._boundsNeedUpdate = true;
3637			this._setClusterCenter(new1);
3638
3639			if (new1 instanceof L.MarkerCluster) {
3640				if (!isNotificationFromChild) {
3641					this._childClusters.push(new1);
3642					new1.__parent = this;
3643				}
3644				this._childCount += new1._childCount;
3645			} else {
3646				if (!isNotificationFromChild) {
3647					this._markers.push(new1);
3648				}
3649				this._childCount++;
3650			}
3651
3652			if (this.__parent) {
3653				this.__parent._addChild(new1, true);
3654			}
3655		},
3656
3657		/**
3658		 * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position.
3659		 * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet.
3660		 * @private
3661		 */
3662		_setClusterCenter: function (child) {
3663			if (!this._cLatLng) {
3664				// when clustering, take position of the first point as the cluster center
3665				this._cLatLng = child._cLatLng || child._latlng;
3666			}
3667		},
3668
3669		/**
3670		 * Assigns impossible bounding values so that the next extend entirely determines the new bounds.
3671		 * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class.
3672		 * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended.
3673		 * @private
3674		 */
3675		_resetBounds: function () {
3676			var bounds = this._bounds;
3677
3678			if (bounds._southWest) {
3679				bounds._southWest.lat = Infinity;
3680				bounds._southWest.lng = Infinity;
3681			}
3682			if (bounds._northEast) {
3683				bounds._northEast.lat = -Infinity;
3684				bounds._northEast.lng = -Infinity;
3685			}
3686		},
3687
3688		_recalculateBounds: function () {
3689			var markers = this._markers,
3690			    childClusters = this._childClusters,
3691			    latSum = 0,
3692			    lngSum = 0,
3693			    totalCount = this._childCount,
3694			    i, child, childLatLng, childCount;
3695
3696			// Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
3697			if (totalCount === 0) {
3698				return;
3699			}
3700
3701			// Reset rather than creating a new object, for performance.
3702			this._resetBounds();
3703
3704			// Child markers.
3705			for (i = 0; i < markers.length; i++) {
3706				childLatLng = markers[i]._latlng;
3707
3708				this._bounds.extend(childLatLng);
3709
3710				latSum += childLatLng.lat;
3711				lngSum += childLatLng.lng;
3712			}
3713
3714			// Child clusters.
3715			for (i = 0; i < childClusters.length; i++) {
3716				child = childClusters[i];
3717
3718				// Re-compute child bounds and weighted position first if necessary.
3719				if (child._boundsNeedUpdate) {
3720					child._recalculateBounds();
3721				}
3722
3723				this._bounds.extend(child._bounds);
3724
3725				childLatLng = child._wLatLng;
3726				childCount = child._childCount;
3727
3728				latSum += childLatLng.lat * childCount;
3729				lngSum += childLatLng.lng * childCount;
3730			}
3731
3732			this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount);
3733
3734			// Reset dirty flag.
3735			this._boundsNeedUpdate = false;
3736		},
3737
3738		//Set our markers position as given and add it to the map
3739		_addToMap: function (startPos) {
3740			if (startPos) {
3741				this._backupLatlng = this._latlng;
3742				this.setLatLng(startPos);
3743			}
3744			this._group._featureGroup.addLayer(this);
3745		},
3746
3747		_recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
3748			this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
3749				function (c) {
3750					var markers = c._markers,
3751						i, m;
3752					for (i = markers.length - 1; i >= 0; i--) {
3753						m = markers[i];
3754
3755						//Only do it if the icon is still on the map
3756						if (m._icon) {
3757							m._setPos(center);
3758							m.clusterHide();
3759						}
3760					}
3761				},
3762				function (c) {
3763					var childClusters = c._childClusters,
3764						j, cm;
3765					for (j = childClusters.length - 1; j >= 0; j--) {
3766						cm = childClusters[j];
3767						if (cm._icon) {
3768							cm._setPos(center);
3769							cm.clusterHide();
3770						}
3771					}
3772				}
3773			);
3774		},
3775
3776		_recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
3777			this._recursively(bounds, newZoomLevel, mapMinZoom,
3778				function (c) {
3779					c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
3780
3781					//TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
3782					//As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
3783					if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
3784						c.clusterShow();
3785						c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
3786					} else {
3787						c.clusterHide();
3788					}
3789
3790					c._addToMap();
3791				}
3792			);
3793		},
3794
3795		_recursivelyBecomeVisible: function (bounds, zoomLevel) {
3796			this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) {
3797				c.clusterShow();
3798			});
3799		},
3800
3801		_recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
3802			this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
3803				function (c) {
3804					if (zoomLevel === c._zoom) {
3805						return;
3806					}
3807
3808					//Add our child markers at startPos (so they can be animated out)
3809					for (var i = c._markers.length - 1; i >= 0; i--) {
3810						var nm = c._markers[i];
3811
3812						if (!bounds.contains(nm._latlng)) {
3813							continue;
3814						}
3815
3816						if (startPos) {
3817							nm._backupLatlng = nm.getLatLng();
3818
3819							nm.setLatLng(startPos);
3820							if (nm.clusterHide) {
3821								nm.clusterHide();
3822							}
3823						}
3824
3825						c._group._featureGroup.addLayer(nm);
3826					}
3827				},
3828				function (c) {
3829					c._addToMap(startPos);
3830				}
3831			);
3832		},
3833
3834		_recursivelyRestoreChildPositions: function (zoomLevel) {
3835			//Fix positions of child markers
3836			for (var i = this._markers.length - 1; i >= 0; i--) {
3837				var nm = this._markers[i];
3838				if (nm._backupLatlng) {
3839					nm.setLatLng(nm._backupLatlng);
3840					delete nm._backupLatlng;
3841				}
3842			}
3843
3844			if (zoomLevel - 1 === this._zoom) {
3845				//Reposition child clusters
3846				for (var j = this._childClusters.length - 1; j >= 0; j--) {
3847					this._childClusters[j]._restorePosition();
3848				}
3849			} else {
3850				for (var k = this._childClusters.length - 1; k >= 0; k--) {
3851					this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
3852				}
3853			}
3854		},
3855
3856		_restorePosition: function () {
3857			if (this._backupLatlng) {
3858				this.setLatLng(this._backupLatlng);
3859				delete this._backupLatlng;
3860			}
3861		},
3862
3863		//exceptBounds: If set, don't remove any markers/clusters in it
3864		_recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
3865			var m, i;
3866			this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
3867				function (c) {
3868					//Remove markers at every level
3869					for (i = c._markers.length - 1; i >= 0; i--) {
3870						m = c._markers[i];
3871						if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
3872							c._group._featureGroup.removeLayer(m);
3873							if (m.clusterShow) {
3874								m.clusterShow();
3875							}
3876						}
3877					}
3878				},
3879				function (c) {
3880					//Remove child clusters at just the bottom level
3881					for (i = c._childClusters.length - 1; i >= 0; i--) {
3882						m = c._childClusters[i];
3883						if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
3884							c._group._featureGroup.removeLayer(m);
3885							if (m.clusterShow) {
3886								m.clusterShow();
3887							}
3888						}
3889					}
3890				}
3891			);
3892		},
3893
3894		//Run the given functions recursively to this and child clusters
3895		// boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
3896		// zoomLevelToStart: zoom level to start running functions (inclusive)
3897		// zoomLevelToStop: zoom level to stop running functions (inclusive)
3898		// runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
3899		// runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
3900		_recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
3901			var childClusters = this._childClusters,
3902			    zoom = this._zoom,
3903			    i, c;
3904
3905			if (zoomLevelToStart <= zoom) {
3906				if (runAtEveryLevel) {
3907					runAtEveryLevel(this);
3908				}
3909				if (runAtBottomLevel && zoom === zoomLevelToStop) {
3910					runAtBottomLevel(this);
3911				}
3912			}
3913
3914			if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
3915				for (i = childClusters.length - 1; i >= 0; i--) {
3916					c = childClusters[i];
3917					if (c._boundsNeedUpdate) {
3918						c._recalculateBounds();
3919					}
3920					if (boundsToApplyTo.intersects(c._bounds)) {
3921						c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
3922					}
3923				}
3924			}
3925		},
3926
3927		//Returns true if we are the parent of only one cluster and that cluster is the same as us
3928		_isSingleParent: function () {
3929			//Don't need to check this._markers as the rest won't work if there are any
3930			return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
3931		}
3932	});
3933
3934	/*
3935	* Extends L.Marker to include two extra methods: clusterHide and clusterShow.
3936	* 
3937	* They work as setOpacity(0) and setOpacity(1) respectively, but
3938	* don't overwrite the options.opacity
3939	* 
3940	*/
3941
3942	L.Marker.include({
3943		clusterHide: function () {
3944			var backup = this.options.opacity;
3945			this.setOpacity(0);
3946			this.options.opacity = backup;
3947			return this;
3948		},
3949		
3950		clusterShow: function () {
3951			return this.setOpacity(this.options.opacity);
3952		}
3953	});
3954
3955	L.DistanceGrid = function (cellSize) {
3956		this._cellSize = cellSize;
3957		this._sqCellSize = cellSize * cellSize;
3958		this._grid = {};
3959		this._objectPoint = { };
3960	};
3961
3962	L.DistanceGrid.prototype = {
3963
3964		addObject: function (obj, point) {
3965			var x = this._getCoord(point.x),
3966			    y = this._getCoord(point.y),
3967			    grid = this._grid,
3968			    row = grid[y] = grid[y] || {},
3969			    cell = row[x] = row[x] || [],
3970			    stamp = L.Util.stamp(obj);
3971
3972			this._objectPoint[stamp] = point;
3973
3974			cell.push(obj);
3975		},
3976
3977		updateObject: function (obj, point) {
3978			this.removeObject(obj);
3979			this.addObject(obj, point);
3980		},
3981
3982		//Returns true if the object was found
3983		removeObject: function (obj, point) {
3984			var x = this._getCoord(point.x),
3985			    y = this._getCoord(point.y),
3986			    grid = this._grid,
3987			    row = grid[y] = grid[y] || {},
3988			    cell = row[x] = row[x] || [],
3989			    i, len;
3990
3991			delete this._objectPoint[L.Util.stamp(obj)];
3992
3993			for (i = 0, len = cell.length; i < len; i++) {
3994				if (cell[i] === obj) {
3995
3996					cell.splice(i, 1);
3997
3998					if (len === 1) {
3999						delete row[x];
4000					}
4001
4002					return true;
4003				}
4004			}
4005
4006		},
4007
4008		eachObject: function (fn, context) {
4009			var i, j, k, len, row, cell, removed,
4010			    grid = this._grid;
4011
4012			for (i in grid) {
4013				row = grid[i];
4014
4015				for (j in row) {
4016					cell = row[j];
4017
4018					for (k = 0, len = cell.length; k < len; k++) {
4019						removed = fn.call(context, cell[k]);
4020						if (removed) {
4021							k--;
4022							len--;
4023						}
4024					}
4025				}
4026			}
4027		},
4028
4029		getNearObject: function (point) {
4030			var x = this._getCoord(point.x),
4031			    y = this._getCoord(point.y),
4032			    i, j, k, row, cell, len, obj, dist,
4033			    objectPoint = this._objectPoint,
4034			    closestDistSq = this._sqCellSize,
4035			    closest = null;
4036
4037			for (i = y - 1; i <= y + 1; i++) {
4038				row = this._grid[i];
4039				if (row) {
4040
4041					for (j = x - 1; j <= x + 1; j++) {
4042						cell = row[j];
4043						if (cell) {
4044
4045							for (k = 0, len = cell.length; k < len; k++) {
4046								obj = cell[k];
4047								dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
4048								if (dist < closestDistSq ||
4049									dist <= closestDistSq && closest === null) {
4050									closestDistSq = dist;
4051									closest = obj;
4052								}
4053							}
4054						}
4055					}
4056				}
4057			}
4058			return closest;
4059		},
4060
4061		_getCoord: function (x) {
4062			var coord = Math.floor(x / this._cellSize);
4063			return isFinite(coord) ? coord : x;
4064		},
4065
4066		_sqDist: function (p, p2) {
4067			var dx = p2.x - p.x,
4068			    dy = p2.y - p.y;
4069			return dx * dx + dy * dy;
4070		}
4071	};
4072
4073	/* Copyright (c) 2012 the authors listed at the following URL, and/or
4074	the authors of referenced articles or incorporated external code:
4075	http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
4076
4077	Permission is hereby granted, free of charge, to any person obtaining
4078	a copy of this software and associated documentation files (the
4079	"Software"), to deal in the Software without restriction, including
4080	without limitation the rights to use, copy, modify, merge, publish,
4081	distribute, sublicense, and/or sell copies of the Software, and to
4082	permit persons to whom the Software is furnished to do so, subject to
4083	the following conditions:
4084
4085	The above copyright notice and this permission notice shall be
4086	included in all copies or substantial portions of the Software.
4087
4088	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
4089	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
4090	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
4091	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
4092	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
4093	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
4094	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4095
4096	Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
4097	*/
4098
4099	(function () {
4100		L.QuickHull = {
4101
4102			/*
4103			 * @param {Object} cpt a point to be measured from the baseline
4104			 * @param {Array} bl the baseline, as represented by a two-element
4105			 *   array of latlng objects.
4106			 * @returns {Number} an approximate distance measure
4107			 */
4108			getDistant: function (cpt, bl) {
4109				var vY = bl[1].lat - bl[0].lat,
4110					vX = bl[0].lng - bl[1].lng;
4111				return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
4112			},
4113
4114			/*
4115			 * @param {Array} baseLine a two-element array of latlng objects
4116			 *   representing the baseline to project from
4117			 * @param {Array} latLngs an array of latlng objects
4118			 * @returns {Object} the maximum point and all new points to stay
4119			 *   in consideration for the hull.
4120			 */
4121			findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
4122				var maxD = 0,
4123					maxPt = null,
4124					newPoints = [],
4125					i, pt, d;
4126
4127				for (i = latLngs.length - 1; i >= 0; i--) {
4128					pt = latLngs[i];
4129					d = this.getDistant(pt, baseLine);
4130
4131					if (d > 0) {
4132						newPoints.push(pt);
4133					} else {
4134						continue;
4135					}
4136
4137					if (d > maxD) {
4138						maxD = d;
4139						maxPt = pt;
4140					}
4141				}
4142
4143				return { maxPoint: maxPt, newPoints: newPoints };
4144			},
4145
4146
4147			/*
4148			 * Given a baseline, compute the convex hull of latLngs as an array
4149			 * of latLngs.
4150			 *
4151			 * @param {Array} latLngs
4152			 * @returns {Array}
4153			 */
4154			buildConvexHull: function (baseLine, latLngs) {
4155				var convexHullBaseLines = [],
4156					t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
4157
4158				if (t.maxPoint) { // if there is still a point "outside" the base line
4159					convexHullBaseLines =
4160						convexHullBaseLines.concat(
4161							this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
4162						);
4163					convexHullBaseLines =
4164						convexHullBaseLines.concat(
4165							this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
4166						);
4167					return convexHullBaseLines;
4168				} else {  // if there is no more point "outside" the base line, the current base line is part of the convex hull
4169					return [baseLine[0]];
4170				}
4171			},
4172
4173			/*
4174			 * Given an array of latlngs, compute a convex hull as an array
4175			 * of latlngs
4176			 *
4177			 * @param {Array} latLngs
4178			 * @returns {Array}
4179			 */
4180			getConvexHull: function (latLngs) {
4181				// find first baseline
4182				var maxLat = false, minLat = false,
4183					maxLng = false, minLng = false,
4184					maxLatPt = null, minLatPt = null,
4185					maxLngPt = null, minLngPt = null,
4186					maxPt = null, minPt = null,
4187					i;
4188
4189				for (i = latLngs.length - 1; i >= 0; i--) {
4190					var pt = latLngs[i];
4191					if (maxLat === false || pt.lat > maxLat) {
4192						maxLatPt = pt;
4193						maxLat = pt.lat;
4194					}
4195					if (minLat === false || pt.lat < minLat) {
4196						minLatPt = pt;
4197						minLat = pt.lat;
4198					}
4199					if (maxLng === false || pt.lng > maxLng) {
4200						maxLngPt = pt;
4201						maxLng = pt.lng;
4202					}
4203					if (minLng === false || pt.lng < minLng) {
4204						minLngPt = pt;
4205						minLng = pt.lng;
4206					}
4207				}
4208				
4209				if (minLat !== maxLat) {
4210					minPt = minLatPt;
4211					maxPt = maxLatPt;
4212				} else {
4213					minPt = minLngPt;
4214					maxPt = maxLngPt;
4215				}
4216
4217				var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
4218									this.buildConvexHull([maxPt, minPt], latLngs));
4219				return ch;
4220			}
4221		};
4222	}());
4223
4224	L.MarkerCluster.include({
4225		getConvexHull: function () {
4226			var childMarkers = this.getAllChildMarkers(),
4227				points = [],
4228				p, i;
4229
4230			for (i = childMarkers.length - 1; i >= 0; i--) {
4231				p = childMarkers[i].getLatLng();
4232				points.push(p);
4233			}
4234
4235			return L.QuickHull.getConvexHull(points);
4236		}
4237	});
4238
4239	//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
4240	//Huge thanks to jawj for implementing it first to make my job easy :-)
4241
4242	L.MarkerCluster.include({
4243
4244		_2PI: Math.PI * 2,
4245		_circleFootSeparation: 25, //related to circumference of circle
4246		_circleStartAngle: 0,
4247
4248		_spiralFootSeparation:  28, //related to size of spiral (experiment!)
4249		_spiralLengthStart: 11,
4250		_spiralLengthFactor: 5,
4251
4252		_circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
4253									// 0 -> always spiral; Infinity -> always circle
4254
4255		spiderfy: function () {
4256			if (this._group._spiderfied === this || this._group._inZoomAnimation) {
4257				return;
4258			}
4259
4260			var childMarkers = this.getAllChildMarkers(null, true),
4261				group = this._group,
4262				map = group._map,
4263				center = map.latLngToLayerPoint(this._latlng),
4264				positions;
4265
4266			this._group._unspiderfy();
4267			this._group._spiderfied = this;
4268
4269			//TODO Maybe: childMarkers order by distance to center
4270
4271			if (this._group.options.spiderfyShapePositions) {
4272				positions = this._group.options.spiderfyShapePositions(childMarkers.length, center);
4273			} else if (childMarkers.length >= this._circleSpiralSwitchover) {
4274				positions = this._generatePointsSpiral(childMarkers.length, center);
4275			} else {
4276				center.y += 10; // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
4277				positions = this._generatePointsCircle(childMarkers.length, center);
4278			}
4279
4280			this._animationSpiderfy(childMarkers, positions);
4281		},
4282
4283		unspiderfy: function (zoomDetails) {
4284			/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
4285			if (this._group._inZoomAnimation) {
4286				return;
4287			}
4288			this._animationUnspiderfy(zoomDetails);
4289
4290			this._group._spiderfied = null;
4291		},
4292
4293		_generatePointsCircle: function (count, centerPt) {
4294			var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
4295				legLength = circumference / this._2PI,  //radius from circumference
4296				angleStep = this._2PI / count,
4297				res = [],
4298				i, angle;
4299
4300			legLength = Math.max(legLength, 35); // Minimum distance to get outside the cluster icon.
4301
4302			res.length = count;
4303
4304			for (i = 0; i < count; i++) { // Clockwise, like spiral.
4305				angle = this._circleStartAngle + i * angleStep;
4306				res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
4307			}
4308
4309			return res;
4310		},
4311
4312		_generatePointsSpiral: function (count, centerPt) {
4313			var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier,
4314				legLength = spiderfyDistanceMultiplier * this._spiralLengthStart,
4315				separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
4316				lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
4317				angle = 0,
4318				res = [],
4319				i;
4320
4321			res.length = count;
4322
4323			// Higher index, closer position to cluster center.
4324			for (i = count; i >= 0; i--) {
4325				// Skip the first position, so that we are already farther from center and we avoid
4326				// being under the default cluster icon (especially important for Circle Markers).
4327				if (i < count) {
4328					res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
4329				}
4330				angle += separation / legLength + i * 0.0005;
4331				legLength += lengthFactor / angle;
4332			}
4333			return res;
4334		},
4335
4336		_noanimationUnspiderfy: function () {
4337			var group = this._group,
4338				map = group._map,
4339				fg = group._featureGroup,
4340				childMarkers = this.getAllChildMarkers(null, true),
4341				m, i;
4342
4343			group._ignoreMove = true;
4344
4345			this.setOpacity(1);
4346			for (i = childMarkers.length - 1; i >= 0; i--) {
4347				m = childMarkers[i];
4348
4349				fg.removeLayer(m);
4350
4351				if (m._preSpiderfyLatlng) {
4352					m.setLatLng(m._preSpiderfyLatlng);
4353					delete m._preSpiderfyLatlng;
4354				}
4355				if (m.setZIndexOffset) {
4356					m.setZIndexOffset(0);
4357				}
4358
4359				if (m._spiderLeg) {
4360					map.removeLayer(m._spiderLeg);
4361					delete m._spiderLeg;
4362				}
4363			}
4364
4365			group.fire('unspiderfied', {
4366				cluster: this,
4367				markers: childMarkers
4368			});
4369			group._ignoreMove = false;
4370			group._spiderfied = null;
4371		}
4372	});
4373
4374	//Non Animated versions of everything
4375	L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
4376		_animationSpiderfy: function (childMarkers, positions) {
4377			var group = this._group,
4378				map = group._map,
4379				fg = group._featureGroup,
4380				legOptions = this._group.options.spiderLegPolylineOptions,
4381				i, m, leg, newPos;
4382
4383			group._ignoreMove = true;
4384
4385			// Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
4386			// The reverse order trick no longer improves performance on modern browsers.
4387			for (i = 0; i < childMarkers.length; i++) {
4388				newPos = map.layerPointToLatLng(positions[i]);
4389				m = childMarkers[i];
4390
4391				// Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
4392				leg = new L.Polyline([this._latlng, newPos], legOptions);
4393				map.addLayer(leg);
4394				m._spiderLeg = leg;
4395
4396				// Now add the marker.
4397				m._preSpiderfyLatlng = m._latlng;
4398				m.setLatLng(newPos);
4399				if (m.setZIndexOffset) {
4400					m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
4401				}
4402
4403				fg.addLayer(m);
4404			}
4405			this.setOpacity(0.3);
4406
4407			group._ignoreMove = false;
4408			group.fire('spiderfied', {
4409				cluster: this,
4410				markers: childMarkers
4411			});
4412		},
4413
4414		_animationUnspiderfy: function () {
4415			this._noanimationUnspiderfy();
4416		}
4417	});
4418
4419	//Animated versions here
4420	L.MarkerCluster.include({
4421
4422		_animationSpiderfy: function (childMarkers, positions) {
4423			var me = this,
4424				group = this._group,
4425				map = group._map,
4426				fg = group._featureGroup,
4427				thisLayerLatLng = this._latlng,
4428				thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng),
4429				svg = L.Path.SVG,
4430				legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation.
4431				finalLegOpacity = legOptions.opacity,
4432				i, m, leg, legPath, legLength, newPos;
4433
4434			if (finalLegOpacity === undefined) {
4435				finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity;
4436			}
4437
4438			if (svg) {
4439				// If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
4440				legOptions.opacity = 0;
4441
4442				// Add the class for CSS transitions.
4443				legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg';
4444			} else {
4445				// Make sure we have a defined opacity.
4446				legOptions.opacity = finalLegOpacity;
4447			}
4448
4449			group._ignoreMove = true;
4450
4451			// Add markers and spider legs to map, hidden at our center point.
4452			// Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
4453			// The reverse order trick no longer improves performance on modern browsers.
4454			for (i = 0; i < childMarkers.length; i++) {
4455				m = childMarkers[i];
4456
4457				newPos = map.layerPointToLatLng(positions[i]);
4458
4459				// Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
4460				leg = new L.Polyline([thisLayerLatLng, newPos], legOptions);
4461				map.addLayer(leg);
4462				m._spiderLeg = leg;
4463
4464				// Explanations: https://jakearchibald.com/2013/animated-line-drawing-svg/
4465				// In our case the transition property is declared in the CSS file.
4466				if (svg) {
4467					legPath = leg._path;
4468					legLength = legPath.getTotalLength() + 0.1; // Need a small extra length to avoid remaining dot in Firefox.
4469					legPath.style.strokeDasharray = legLength; // Just 1 length is enough, it will be duplicated.
4470					legPath.style.strokeDashoffset = legLength;
4471				}
4472
4473				// If it is a marker, add it now and we'll animate it out
4474				if (m.setZIndexOffset) {
4475					m.setZIndexOffset(1000000); // Make normal markers appear on top of EVERYTHING
4476				}
4477				if (m.clusterHide) {
4478					m.clusterHide();
4479				}
4480				
4481				// Vectors just get immediately added
4482				fg.addLayer(m);
4483
4484				if (m._setPos) {
4485					m._setPos(thisLayerPos);
4486				}
4487			}
4488
4489			group._forceLayout();
4490			group._animationStart();
4491
4492			// Reveal markers and spider legs.
4493			for (i = childMarkers.length - 1; i >= 0; i--) {
4494				newPos = map.layerPointToLatLng(positions[i]);
4495				m = childMarkers[i];
4496
4497				//Move marker to new position
4498				m._preSpiderfyLatlng = m._latlng;
4499				m.setLatLng(newPos);
4500				
4501				if (m.clusterShow) {
4502					m.clusterShow();
4503				}
4504
4505				// Animate leg (animation is actually delegated to CSS transition).
4506				if (svg) {
4507					leg = m._spiderLeg;
4508					legPath = leg._path;
4509					legPath.style.strokeDashoffset = 0;
4510					//legPath.style.strokeOpacity = finalLegOpacity;
4511					leg.setStyle({opacity: finalLegOpacity});
4512				}
4513			}
4514			this.setOpacity(0.3);
4515
4516			group._ignoreMove = false;
4517
4518			setTimeout(function () {
4519				group._animationEnd();
4520				group.fire('spiderfied', {
4521					cluster: me,
4522					markers: childMarkers
4523				});
4524			}, 200);
4525		},
4526
4527		_animationUnspiderfy: function (zoomDetails) {
4528			var me = this,
4529				group = this._group,
4530				map = group._map,
4531				fg = group._featureGroup,
4532				thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
4533				childMarkers = this.getAllChildMarkers(null, true),
4534				svg = L.Path.SVG,
4535				m, i, leg, legPath, legLength, nonAnimatable;
4536
4537			group._ignoreMove = true;
4538			group._animationStart();
4539
4540			//Make us visible and bring the child markers back in
4541			this.setOpacity(1);
4542			for (i = childMarkers.length - 1; i >= 0; i--) {
4543				m = childMarkers[i];
4544
4545				//Marker was added to us after we were spiderfied
4546				if (!m._preSpiderfyLatlng) {
4547					continue;
4548				}
4549
4550				//Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
4551				m.closePopup();
4552
4553				//Fix up the location to the real one
4554				m.setLatLng(m._preSpiderfyLatlng);
4555				delete m._preSpiderfyLatlng;
4556
4557				//Hack override the location to be our center
4558				nonAnimatable = true;
4559				if (m._setPos) {
4560					m._setPos(thisLayerPos);
4561					nonAnimatable = false;
4562				}
4563				if (m.clusterHide) {
4564					m.clusterHide();
4565					nonAnimatable = false;
4566				}
4567				if (nonAnimatable) {
4568					fg.removeLayer(m);
4569				}
4570
4571				// Animate the spider leg back in (animation is actually delegated to CSS transition).
4572				if (svg) {
4573					leg = m._spiderLeg;
4574					legPath = leg._path;
4575					legLength = legPath.getTotalLength() + 0.1;
4576					legPath.style.strokeDashoffset = legLength;
4577					leg.setStyle({opacity: 0});
4578				}
4579			}
4580
4581			group._ignoreMove = false;
4582
4583			setTimeout(function () {
4584				//If we have only <= one child left then that marker will be shown on the map so don't remove it!
4585				var stillThereChildCount = 0;
4586				for (i = childMarkers.length - 1; i >= 0; i--) {
4587					m = childMarkers[i];
4588					if (m._spiderLeg) {
4589						stillThereChildCount++;
4590					}
4591				}
4592
4593
4594				for (i = childMarkers.length - 1; i >= 0; i--) {
4595					m = childMarkers[i];
4596
4597					if (!m._spiderLeg) { //Has already been unspiderfied
4598						continue;
4599					}
4600
4601					if (m.clusterShow) {
4602						m.clusterShow();
4603					}
4604					if (m.setZIndexOffset) {
4605						m.setZIndexOffset(0);
4606					}
4607
4608					if (stillThereChildCount > 1) {
4609						fg.removeLayer(m);
4610					}
4611
4612					map.removeLayer(m._spiderLeg);
4613					delete m._spiderLeg;
4614				}
4615				group._animationEnd();
4616				group.fire('unspiderfied', {
4617					cluster: me,
4618					markers: childMarkers
4619				});
4620			}, 200);
4621		}
4622	});
4623
4624
4625	L.MarkerClusterGroup.include({
4626		//The MarkerCluster currently spiderfied (if any)
4627		_spiderfied: null,
4628
4629		unspiderfy: function () {
4630			this._unspiderfy.apply(this, arguments);
4631		},
4632
4633		_spiderfierOnAdd: function () {
4634			this._map.on('click', this._unspiderfyWrapper, this);
4635
4636			if (this._map.options.zoomAnimation) {
4637				this._map.on('zoomstart', this._unspiderfyZoomStart, this);
4638			}
4639			//Browsers without zoomAnimation or a big zoom don't fire zoomstart
4640			this._map.on('zoomend', this._noanimationUnspiderfy, this);
4641
4642			if (!L.Browser.touch) {
4643				this._map.getRenderer(this);
4644				//Needs to happen in the pageload, not after, or animations don't work in webkit
4645				//  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
4646				//Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
4647			}
4648		},
4649
4650		_spiderfierOnRemove: function () {
4651			this._map.off('click', this._unspiderfyWrapper, this);
4652			this._map.off('zoomstart', this._unspiderfyZoomStart, this);
4653			this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
4654			this._map.off('zoomend', this._noanimationUnspiderfy, this);
4655
4656			//Ensure that markers are back where they should be
4657			// Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
4658			this._noanimationUnspiderfy();
4659		},
4660
4661		//On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
4662		//This means we can define the animation they do rather than Markers doing an animation to their actual location
4663		_unspiderfyZoomStart: function () {
4664			if (!this._map) { //May have been removed from the map by a zoomEnd handler
4665				return;
4666			}
4667
4668			this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
4669		},
4670
4671		_unspiderfyZoomAnim: function (zoomDetails) {
4672			//Wait until the first zoomanim after the user has finished touch-zooming before running the animation
4673			if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
4674				return;
4675			}
4676
4677			this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
4678			this._unspiderfy(zoomDetails);
4679		},
4680
4681		_unspiderfyWrapper: function () {
4682			/// <summary>_unspiderfy but passes no arguments</summary>
4683			this._unspiderfy();
4684		},
4685
4686		_unspiderfy: function (zoomDetails) {
4687			if (this._spiderfied) {
4688				this._spiderfied.unspiderfy(zoomDetails);
4689			}
4690		},
4691
4692		_noanimationUnspiderfy: function () {
4693			if (this._spiderfied) {
4694				this._spiderfied._noanimationUnspiderfy();
4695			}
4696		},
4697
4698		//If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
4699		_unspiderfyLayer: function (layer) {
4700			if (layer._spiderLeg) {
4701				this._featureGroup.removeLayer(layer);
4702
4703				if (layer.clusterShow) {
4704					layer.clusterShow();
4705				}
4706					//Position will be fixed up immediately in _animationUnspiderfy
4707				if (layer.setZIndexOffset) {
4708					layer.setZIndexOffset(0);
4709				}
4710
4711				this._map.removeLayer(layer._spiderLeg);
4712				delete layer._spiderLeg;
4713			}
4714		}
4715	});
4716
4717	/**
4718	 * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
4719	 * markers' icon options and refreshing their icon and their parent clusters
4720	 * accordingly (case where their iconCreateFunction uses data of childMarkers
4721	 * to make up the cluster icon).
4722	 */
4723
4724
4725	L.MarkerClusterGroup.include({
4726		/**
4727		 * Updates the icon of all clusters which are parents of the given marker(s).
4728		 * In singleMarkerMode, also updates the given marker(s) icon.
4729		 * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)|
4730		 * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent
4731		 * clusters need to be updated. If not provided, retrieves all child markers of this.
4732		 * @returns {L.MarkerClusterGroup}
4733		 */
4734		refreshClusters: function (layers) {
4735			if (!layers) {
4736				layers = this._topClusterLevel.getAllChildMarkers();
4737			} else if (layers instanceof L.MarkerClusterGroup) {
4738				layers = layers._topClusterLevel.getAllChildMarkers();
4739			} else if (layers instanceof L.LayerGroup) {
4740				layers = layers._layers;
4741			} else if (layers instanceof L.MarkerCluster) {
4742				layers = layers.getAllChildMarkers();
4743			} else if (layers instanceof L.Marker) {
4744				layers = [layers];
4745			} // else: must be an Array(L.Marker)|Map(L.Marker)
4746			this._flagParentsIconsNeedUpdate(layers);
4747			this._refreshClustersIcons();
4748
4749			// In case of singleMarkerMode, also re-draw the markers.
4750			if (this.options.singleMarkerMode) {
4751				this._refreshSingleMarkerModeMarkers(layers);
4752			}
4753
4754			return this;
4755		},
4756
4757		/**
4758		 * Simply flags all parent clusters of the given markers as having a "dirty" icon.
4759		 * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
4760		 * @private
4761		 */
4762		_flagParentsIconsNeedUpdate: function (layers) {
4763			var id, parent;
4764
4765			// Assumes layers is an Array or an Object whose prototype is non-enumerable.
4766			for (id in layers) {
4767				// Flag parent clusters' icon as "dirty", all the way up.
4768				// Dumb process that flags multiple times upper parents, but still
4769				// much more efficient than trying to be smart and make short lists,
4770				// at least in the case of a hierarchy following a power law:
4771				// http://jsperf.com/flag-nodes-in-power-hierarchy/2
4772				parent = layers[id].__parent;
4773				while (parent) {
4774					parent._iconNeedsUpdate = true;
4775					parent = parent.__parent;
4776				}
4777			}
4778		},
4779
4780		/**
4781		 * Re-draws the icon of the supplied markers.
4782		 * To be used in singleMarkerMode only.
4783		 * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
4784		 * @private
4785		 */
4786		_refreshSingleMarkerModeMarkers: function (layers) {
4787			var id, layer;
4788
4789			for (id in layers) {
4790				layer = layers[id];
4791
4792				// Make sure we do not override markers that do not belong to THIS group.
4793				if (this.hasLayer(layer)) {
4794					// Need to re-create the icon first, then re-draw the marker.
4795					layer.setIcon(this._overrideMarkerIcon(layer));
4796				}
4797			}
4798		}
4799	});
4800
4801	L.Marker.include({
4802		/**
4803		 * Updates the given options in the marker's icon and refreshes the marker.
4804		 * @param options map object of icon options.
4805		 * @param directlyRefreshClusters boolean (optional) true to trigger
4806		 * MCG.refreshClustersOf() right away with this single marker.
4807		 * @returns {L.Marker}
4808		 */
4809		refreshIconOptions: function (options, directlyRefreshClusters) {
4810			var icon = this.options.icon;
4811
4812			L.setOptions(icon, options);
4813
4814			this.setIcon(icon);
4815
4816			// Shortcut to refresh the associated MCG clusters right away.
4817			// To be used when refreshing a single marker.
4818			// Otherwise, better use MCG.refreshClusters() once at the end with
4819			// the list of modified markers.
4820			if (directlyRefreshClusters && this.__parent) {
4821				this.__parent._group.refreshClusters(this);
4822			}
4823
4824			return this;
4825		}
4826	});
4827
4828	exports.MarkerClusterGroup = MarkerClusterGroup;
4829	exports.MarkerCluster = MarkerCluster;
4830
4831	Object.defineProperty(exports, '__esModule', { value: true });
4832
4833}));
4834
4835
4836},{}],7:[function(require,module,exports){
vendor: 450,189 bytes, lines 4837-19347
4837/* @preserve
4838 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
4839 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4840 */
4841
4842(function (global, factory) {
4843  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4844  typeof define === 'function' && define.amd ? define(['exports'], factory) :
4845  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
4846})(this, (function (exports) { 'use strict';
4847
4848  var version = "1.9.4";
4849
4850  /*
4851   * @namespace Util
4852   *
4853   * Various utility functions, used by Leaflet internally.
4854   */
4855
4856  // @function extend(dest: Object, src?: Object): Object
4857  // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
4858  function extend(dest) {
4859  	var i, j, len, src;
4860
4861  	for (j = 1, len = arguments.length; j < len; j++) {
4862  		src = arguments[j];
4863  		for (i in src) {
4864  			dest[i] = src[i];
4865  		}
4866  	}
4867  	return dest;
4868  }
4869
4870  // @function create(proto: Object, properties?: Object): Object
4871  // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
4872  var create$2 = Object.create || (function () {
4873  	function F() {}
4874  	return function (proto) {
4875  		F.prototype = proto;
4876  		return new F();
4877  	};
4878  })();
4879
4880  // @function bind(fn: Function, …): Function
4881  // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
4882  // Has a `L.bind()` shortcut.
4883  function bind(fn, obj) {
4884  	var slice = Array.prototype.slice;
4885
4886  	if (fn.bind) {
4887  		return fn.bind.apply(fn, slice.call(arguments, 1));
4888  	}
4889
4890  	var args = slice.call(arguments, 2);
4891
4892  	return function () {
4893  		return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
4894  	};
4895  }
4896
4897  // @property lastId: Number
4898  // Last unique ID used by [`stamp()`](#util-stamp)
4899  var lastId = 0;
4900
4901  // @function stamp(obj: Object): Number
4902  // Returns the unique ID of an object, assigning it one if it doesn't have it.
4903  function stamp(obj) {
4904  	if (!('_leaflet_id' in obj)) {
4905  		obj['_leaflet_id'] = ++lastId;
4906  	}
4907  	return obj._leaflet_id;
4908  }
4909
4910  // @function throttle(fn: Function, time: Number, context: Object): Function
4911  // Returns a function which executes function `fn` with the given scope `context`
4912  // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
4913  // `fn` will be called no more than one time per given amount of `time`. The arguments
4914  // received by the bound function will be any arguments passed when binding the
4915  // function, followed by any arguments passed when invoking the bound function.
4916  // Has an `L.throttle` shortcut.
4917  function throttle(fn, time, context) {
4918  	var lock, args, wrapperFn, later;
4919
4920  	later = function () {
4921  		// reset lock and call if queued
4922  		lock = false;
4923  		if (args) {
4924  			wrapperFn.apply(context, args);
4925  			args = false;
4926  		}
4927  	};
4928
4929  	wrapperFn = function () {
4930  		if (lock) {
4931  			// called too soon, queue to call later
4932  			args = arguments;
4933
4934  		} else {
4935  			// call and lock until later
4936  			fn.apply(context, arguments);
4937  			setTimeout(later, time);
4938  			lock = true;
4939  		}
4940  	};
4941
4942  	return wrapperFn;
4943  }
4944
4945  // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
4946  // Returns the number `num` modulo `range` in such a way so it lies within
4947  // `range[0]` and `range[1]`. The returned value will be always smaller than
4948  // `range[1]` unless `includeMax` is set to `true`.
4949  function wrapNum(x, range, includeMax) {
4950  	var max = range[1],
4951  	    min = range[0],
4952  	    d = max - min;
4953  	return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
4954  }
4955
4956  // @function falseFn(): Function
4957  // Returns a function which always returns `false`.
4958  function falseFn() { return false; }
4959
4960  // @function formatNum(num: Number, precision?: Number|false): Number
4961  // Returns the number `num` rounded with specified `precision`.
4962  // The default `precision` value is 6 decimal places.
4963  // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
4964  function formatNum(num, precision) {
4965  	if (precision === false) { return num; }
4966  	var pow = Math.pow(10, precision === undefined ? 6 : precision);
4967  	return Math.round(num * pow) / pow;
4968  }
4969
4970  // @function trim(str: String): String
4971  // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
4972  function trim(str) {
4973  	return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
4974  }
4975
4976  // @function splitWords(str: String): String[]
4977  // Trims and splits the string on whitespace and returns the array of parts.
4978  function splitWords(str) {
4979  	return trim(str).split(/\s+/);
4980  }
4981
4982  // @function setOptions(obj: Object, options: Object): Object
4983  // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
4984  function setOptions(obj, options) {
4985  	if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
4986  		obj.options = obj.options ? create$2(obj.options) : {};
4987  	}
4988  	for (var i in options) {
4989  		obj.options[i] = options[i];
4990  	}
4991  	return obj.options;
4992  }
4993
4994  // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
4995  // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
4996  // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
4997  // be appended at the end. If `uppercase` is `true`, the parameter names will
4998  // be uppercased (e.g. `'?A=foo&B=bar'`)
4999  function getParamString(obj, existingUrl, uppercase) {
5000  	var params = [];
5001  	for (var i in obj) {
5002  		params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
5003  	}
5004  	return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
5005  }
5006
5007  var templateRe = /\{ *([\w_ -]+) *\}/g;
5008
5009  // @function template(str: String, data: Object): String
5010  // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
5011  // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
5012  // `('Hello foo, bar')`. You can also specify functions instead of strings for
5013  // data values — they will be evaluated passing `data` as an argument.
5014  function template(str, data) {
5015  	return str.replace(templateRe, function (str, key) {
5016  		var value = data[key];
5017
5018  		if (value === undefined) {
5019  			throw new Error('No value provided for variable ' + str);
5020
5021  		} else if (typeof value === 'function') {
5022  			value = value(data);
5023  		}
5024  		return value;
5025  	});
5026  }
5027
5028  // @function isArray(obj): Boolean
5029  // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
5030  var isArray = Array.isArray || function (obj) {
5031  	return (Object.prototype.toString.call(obj) === '[object Array]');
5032  };
5033
5034  // @function indexOf(array: Array, el: Object): Number
5035  // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
5036  function indexOf(array, el) {
5037  	for (var i = 0; i < array.length; i++) {
5038  		if (array[i] === el) { return i; }
5039  	}
5040  	return -1;
5041  }
5042
5043  // @property emptyImageUrl: String
5044  // Data URI string containing a base64-encoded empty GIF image.
5045  // Used as a hack to free memory from unused images on WebKit-powered
5046  // mobile devices (by setting image `src` to this string).
5047  var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
5048
5049  // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
5050
5051  function getPrefixed(name) {
5052  	return window['webkit' + name] || window['moz' + name] || window['ms' + name];
5053  }
5054
5055  var lastTime = 0;
5056
5057  // fallback for IE 7-8
5058  function timeoutDefer(fn) {
5059  	var time = +new Date(),
5060  	    timeToCall = Math.max(0, 16 - (time - lastTime));
5061
5062  	lastTime = time + timeToCall;
5063  	return window.setTimeout(fn, timeToCall);
5064  }
5065
5066  var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
5067  var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
5068  		getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
5069
5070  // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
5071  // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
5072  // `context` if given. When `immediate` is set, `fn` is called immediately if
5073  // the browser doesn't have native support for
5074  // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
5075  // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
5076  function requestAnimFrame(fn, context, immediate) {
5077  	if (immediate && requestFn === timeoutDefer) {
5078  		fn.call(context);
5079  	} else {
5080  		return requestFn.call(window, bind(fn, context));
5081  	}
5082  }
5083
5084  // @function cancelAnimFrame(id: Number): undefined
5085  // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
5086  function cancelAnimFrame(id) {
5087  	if (id) {
5088  		cancelFn.call(window, id);
5089  	}
5090  }
5091
5092  var Util = {
5093    __proto__: null,
5094    extend: extend,
5095    create: create$2,
5096    bind: bind,
5097    get lastId () { return lastId; },
5098    stamp: stamp,
5099    throttle: throttle,
5100    wrapNum: wrapNum,
5101    falseFn: falseFn,
5102    formatNum: formatNum,
5103    trim: trim,
5104    splitWords: splitWords,
5105    setOptions: setOptions,
5106    getParamString: getParamString,
5107    template: template,
5108    isArray: isArray,
5109    indexOf: indexOf,
5110    emptyImageUrl: emptyImageUrl,
5111    requestFn: requestFn,
5112    cancelFn: cancelFn,
5113    requestAnimFrame: requestAnimFrame,
5114    cancelAnimFrame: cancelAnimFrame
5115  };
5116
5117  // @class Class
5118  // @aka L.Class
5119
5120  // @section
5121  // @uninheritable
5122
5123  // Thanks to John Resig and Dean Edwards for inspiration!
5124
5125  function Class() {}
5126
5127  Class.extend = function (props) {
5128
5129  	// @function extend(props: Object): Function
5130  	// [Extends the current class](#class-inheritance) given the properties to be included.
5131  	// Returns a Javascript function that is a class constructor (to be called with `new`).
5132  	var NewClass = function () {
5133
5134  		setOptions(this);
5135
5136  		// call the constructor
5137  		if (this.initialize) {
5138  			this.initialize.apply(this, arguments);
5139  		}
5140
5141  		// call all constructor hooks
5142  		this.callInitHooks();
5143  	};
5144
5145  	var parentProto = NewClass.__super__ = this.prototype;
5146
5147  	var proto = create$2(parentProto);
5148  	proto.constructor = NewClass;
5149
5150  	NewClass.prototype = proto;
5151
5152  	// inherit parent's statics
5153  	for (var i in this) {
5154  		if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
5155  			NewClass[i] = this[i];
5156  		}
5157  	}
5158
5159  	// mix static properties into the class
5160  	if (props.statics) {
5161  		extend(NewClass, props.statics);
5162  	}
5163
5164  	// mix includes into the prototype
5165  	if (props.includes) {
5166  		checkDeprecatedMixinEvents(props.includes);
5167  		extend.apply(null, [proto].concat(props.includes));
5168  	}
5169
5170  	// mix given properties into the prototype
5171  	extend(proto, props);
5172  	delete proto.statics;
5173  	delete proto.includes;
5174
5175  	// merge options
5176  	if (proto.options) {
5177  		proto.options = parentProto.options ? create$2(parentProto.options) : {};
5178  		extend(proto.options, props.options);
5179  	}
5180
5181  	proto._initHooks = [];
5182
5183  	// add method for calling all hooks
5184  	proto.callInitHooks = function () {
5185
5186  		if (this._initHooksCalled) { return; }
5187
5188  		if (parentProto.callInitHooks) {
5189  			parentProto.callInitHooks.call(this);
5190  		}
5191
5192  		this._initHooksCalled = true;
5193
5194  		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
5195  			proto._initHooks[i].call(this);
5196  		}
5197  	};
5198
5199  	return NewClass;
5200  };
5201
5202
5203  // @function include(properties: Object): this
5204  // [Includes a mixin](#class-includes) into the current class.
5205  Class.include = function (props) {
5206  	var parentOptions = this.prototype.options;
5207  	extend(this.prototype, props);
5208  	if (props.options) {
5209  		this.prototype.options = parentOptions;
5210  		this.mergeOptions(props.options);
5211  	}
5212  	return this;
5213  };
5214
5215  // @function mergeOptions(options: Object): this
5216  // [Merges `options`](#class-options) into the defaults of the class.
5217  Class.mergeOptions = function (options) {
5218  	extend(this.prototype.options, options);
5219  	return this;
5220  };
5221
5222  // @function addInitHook(fn: Function): this
5223  // Adds a [constructor hook](#class-constructor-hooks) to the class.
5224  Class.addInitHook = function (fn) { // (Function) || (String, args...)
5225  	var args = Array.prototype.slice.call(arguments, 1);
5226
5227  	var init = typeof fn === 'function' ? fn : function () {
5228  		this[fn].apply(this, args);
5229  	};
5230
5231  	this.prototype._initHooks = this.prototype._initHooks || [];
5232  	this.prototype._initHooks.push(init);
5233  	return this;
5234  };
5235
5236  function checkDeprecatedMixinEvents(includes) {
5237  	/* global L: true */
5238  	if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
5239
5240  	includes = isArray(includes) ? includes : [includes];
5241
5242  	for (var i = 0; i < includes.length; i++) {
5243  		if (includes[i] === L.Mixin.Events) {
5244  			console.warn('Deprecated include of L.Mixin.Events: ' +
5245  				'this property will be removed in future releases, ' +
5246  				'please inherit from L.Evented instead.', new Error().stack);
5247  		}
5248  	}
5249  }
5250
5251  /*
5252   * @class Evented
5253   * @aka L.Evented
5254   * @inherits Class
5255   *
5256   * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
5257   *
5258   * @example
5259   *
5260   * ```js
5261   * map.on('click', function(e) {
5262   * 	alert(e.latlng);
5263   * } );
5264   * ```
5265   *
5266   * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
5267   *
5268   * ```js
5269   * function onClick(e) { ... }
5270   *
5271   * map.on('click', onClick);
5272   * map.off('click', onClick);
5273   * ```
5274   */
5275
5276  var Events = {
5277  	/* @method on(type: String, fn: Function, context?: Object): this
5278  	 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
5279  	 *
5280  	 * @alternative
5281  	 * @method on(eventMap: Object): this
5282  	 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
5283  	 */
5284  	on: function (types, fn, context) {
5285
5286  		// types can be a map of types/handlers
5287  		if (typeof types === 'object') {
5288  			for (var type in types) {
5289  				// we don't process space-separated events here for performance;
5290  				// it's a hot path since Layer uses the on(obj) syntax
5291  				this._on(type, types[type], fn);
5292  			}
5293
5294  		} else {
5295  			// types can be a string of space-separated words
5296  			types = splitWords(types);
5297
5298  			for (var i = 0, len = types.length; i < len; i++) {
5299  				this._on(types[i], fn, context);
5300  			}
5301  		}
5302
5303  		return this;
5304  	},
5305
5306  	/* @method off(type: String, fn?: Function, context?: Object): this
5307  	 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
5308  	 *
5309  	 * @alternative
5310  	 * @method off(eventMap: Object): this
5311  	 * Removes a set of type/listener pairs.
5312  	 *
5313  	 * @alternative
5314  	 * @method off: this
5315  	 * Removes all listeners to all events on the object. This includes implicitly attached events.
5316  	 */
5317  	off: function (types, fn, context) {
5318
5319  		if (!arguments.length) {
5320  			// clear all listeners if called without arguments
5321  			delete this._events;
5322
5323  		} else if (typeof types === 'object') {
5324  			for (var type in types) {
5325  				this._off(type, types[type], fn);
5326  			}
5327
5328  		} else {
5329  			types = splitWords(types);
5330
5331  			var removeAll = arguments.length === 1;
5332  			for (var i = 0, len = types.length; i < len; i++) {
5333  				if (removeAll) {
5334  					this._off(types[i]);
5335  				} else {
5336  					this._off(types[i], fn, context);
5337  				}
5338  			}
5339  		}
5340
5341  		return this;
5342  	},
5343
5344  	// attach listener (without syntactic sugar now)
5345  	_on: function (type, fn, context, _once) {
5346  		if (typeof fn !== 'function') {
5347  			console.warn('wrong listener type: ' + typeof fn);
5348  			return;
5349  		}
5350
5351  		// check if fn already there
5352  		if (this._listens(type, fn, context) !== false) {
5353  			return;
5354  		}
5355
5356  		if (context === this) {
5357  			// Less memory footprint.
5358  			context = undefined;
5359  		}
5360
5361  		var newListener = {fn: fn, ctx: context};
5362  		if (_once) {
5363  			newListener.once = true;
5364  		}
5365
5366  		this._events = this._events || {};
5367  		this._events[type] = this._events[type] || [];
5368  		this._events[type].push(newListener);
5369  	},
5370
5371  	_off: function (type, fn, context) {
5372  		var listeners,
5373  		    i,
5374  		    len;
5375
5376  		if (!this._events) {
5377  			return;
5378  		}
5379
5380  		listeners = this._events[type];
5381  		if (!listeners) {
5382  			return;
5383  		}
5384
5385  		if (arguments.length === 1) { // remove all
5386  			if (this._firingCount) {
5387  				// Set all removed listeners to noop
5388  				// so they are not called if remove happens in fire
5389  				for (i = 0, len = listeners.length; i < len; i++) {
5390  					listeners[i].fn = falseFn;
5391  				}
5392  			}
5393  			// clear all listeners for a type if function isn't specified
5394  			delete this._events[type];
5395  			return;
5396  		}
5397
5398  		if (typeof fn !== 'function') {
5399  			console.warn('wrong listener type: ' + typeof fn);
5400  			return;
5401  		}
5402
5403  		// find fn and remove it
5404  		var index = this._listens(type, fn, context);
5405  		if (index !== false) {
5406  			var listener = listeners[index];
5407  			if (this._firingCount) {
5408  				// set the removed listener to noop so that's not called if remove happens in fire
5409  				listener.fn = falseFn;
5410
5411  				/* copy array in case events are being fired */
5412  				this._events[type] = listeners = listeners.slice();
5413  			}
5414  			listeners.splice(index, 1);
5415  		}
5416  	},
5417
5418  	// @method fire(type: String, data?: Object, propagate?: Boolean): this
5419  	// Fires an event of the specified type. You can optionally provide a data
5420  	// object — the first argument of the listener function will contain its
5421  	// properties. The event can optionally be propagated to event parents.
5422  	fire: function (type, data, propagate) {
5423  		if (!this.listens(type, propagate)) { return this; }
5424
5425  		var event = extend({}, data, {
5426  			type: type,
5427  			target: this,
5428  			sourceTarget: data && data.sourceTarget || this
5429  		});
5430
5431  		if (this._events) {
5432  			var listeners = this._events[type];
5433  			if (listeners) {
5434  				this._firingCount = (this._firingCount + 1) || 1;
5435  				for (var i = 0, len = listeners.length; i < len; i++) {
5436  					var l = listeners[i];
5437  					// off overwrites l.fn, so we need to copy fn to a var
5438  					var fn = l.fn;
5439  					if (l.once) {
5440  						this.off(type, fn, l.ctx);
5441  					}
5442  					fn.call(l.ctx || this, event);
5443  				}
5444
5445  				this._firingCount--;
5446  			}
5447  		}
5448
5449  		if (propagate) {
5450  			// propagate the event to parents (set with addEventParent)
5451  			this._propagateEvent(event);
5452  		}
5453
5454  		return this;
5455  	},
5456
5457  	// @method listens(type: String, propagate?: Boolean): Boolean
5458  	// @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
5459  	// Returns `true` if a particular event type has any listeners attached to it.
5460  	// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
5461  	listens: function (type, fn, context, propagate) {
5462  		if (typeof type !== 'string') {
5463  			console.warn('"string" type argument expected');
5464  		}
5465
5466  		// we don't overwrite the input `fn` value, because we need to use it for propagation
5467  		var _fn = fn;
5468  		if (typeof fn !== 'function') {
5469  			propagate = !!fn;
5470  			_fn = undefined;
5471  			context = undefined;
5472  		}
5473
5474  		var listeners = this._events && this._events[type];
5475  		if (listeners && listeners.length) {
5476  			if (this._listens(type, _fn, context) !== false) {
5477  				return true;
5478  			}
5479  		}
5480
5481  		if (propagate) {
5482  			// also check parents for listeners if event propagates
5483  			for (var id in this._eventParents) {
5484  				if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
5485  			}
5486  		}
5487  		return false;
5488  	},
5489
5490  	// returns the index (number) or false
5491  	_listens: function (type, fn, context) {
5492  		if (!this._events) {
5493  			return false;
5494  		}
5495
5496  		var listeners = this._events[type] || [];
5497  		if (!fn) {
5498  			return !!listeners.length;
5499  		}
5500
5501  		if (context === this) {
5502  			// Less memory footprint.
5503  			context = undefined;
5504  		}
5505
5506  		for (var i = 0, len = listeners.length; i < len; i++) {
5507  			if (listeners[i].fn === fn && listeners[i].ctx === context) {
5508  				return i;
5509  			}
5510  		}
5511  		return false;
5512
5513  	},
5514
5515  	// @method once(…): this
5516  	// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
5517  	once: function (types, fn, context) {
5518
5519  		// types can be a map of types/handlers
5520  		if (typeof types === 'object') {
5521  			for (var type in types) {
5522  				// we don't process space-separated events here for performance;
5523  				// it's a hot path since Layer uses the on(obj) syntax
5524  				this._on(type, types[type], fn, true);
5525  			}
5526
5527  		} else {
5528  			// types can be a string of space-separated words
5529  			types = splitWords(types);
5530
5531  			for (var i = 0, len = types.length; i < len; i++) {
5532  				this._on(types[i], fn, context, true);
5533  			}
5534  		}
5535
5536  		return this;
5537  	},
5538
5539  	// @method addEventParent(obj: Evented): this
5540  	// Adds an event parent - an `Evented` that will receive propagated events
5541  	addEventParent: function (obj) {
5542  		this._eventParents = this._eventParents || {};
5543  		this._eventParents[stamp(obj)] = obj;
5544  		return this;
5545  	},
5546
5547  	// @method removeEventParent(obj: Evented): this
5548  	// Removes an event parent, so it will stop receiving propagated events
5549  	removeEventParent: function (obj) {
5550  		if (this._eventParents) {
5551  			delete this._eventParents[stamp(obj)];
5552  		}
5553  		return this;
5554  	},
5555
5556  	_propagateEvent: function (e) {
5557  		for (var id in this._eventParents) {
5558  			this._eventParents[id].fire(e.type, extend({
5559  				layer: e.target,
5560  				propagatedFrom: e.target
5561  			}, e), true);
5562  		}
5563  	}
5564  };
5565
5566  // aliases; we should ditch those eventually
5567
5568  // @method addEventListener(…): this
5569  // Alias to [`on(…)`](#evented-on)
5570  Events.addEventListener = Events.on;
5571
5572  // @method removeEventListener(…): this
5573  // Alias to [`off(…)`](#evented-off)
5574
5575  // @method clearAllEventListeners(…): this
5576  // Alias to [`off()`](#evented-off)
5577  Events.removeEventListener = Events.clearAllEventListeners = Events.off;
5578
5579  // @method addOneTimeEventListener(…): this
5580  // Alias to [`once(…)`](#evented-once)
5581  Events.addOneTimeEventListener = Events.once;
5582
5583  // @method fireEvent(…): this
5584  // Alias to [`fire(…)`](#evented-fire)
5585  Events.fireEvent = Events.fire;
5586
5587  // @method hasEventListeners(…): Boolean
5588  // Alias to [`listens(…)`](#evented-listens)
5589  Events.hasEventListeners = Events.listens;
5590
5591  var Evented = Class.extend(Events);
5592
5593  /*
5594   * @class Point
5595   * @aka L.Point
5596   *
5597   * Represents a point with `x` and `y` coordinates in pixels.
5598   *
5599   * @example
5600   *
5601   * ```js
5602   * var point = L.point(200, 300);
5603   * ```
5604   *
5605   * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
5606   *
5607   * ```js
5608   * map.panBy([200, 300]);
5609   * map.panBy(L.point(200, 300));
5610   * ```
5611   *
5612   * Note that `Point` does not inherit from Leaflet's `Class` object,
5613   * which means new classes can't inherit from it, and new methods
5614   * can't be added to it with the `include` function.
5615   */
5616
5617  function Point(x, y, round) {
5618  	// @property x: Number; The `x` coordinate of the point
5619  	this.x = (round ? Math.round(x) : x);
5620  	// @property y: Number; The `y` coordinate of the point
5621  	this.y = (round ? Math.round(y) : y);
5622  }
5623
5624  var trunc = Math.trunc || function (v) {
5625  	return v > 0 ? Math.floor(v) : Math.ceil(v);
5626  };
5627
5628  Point.prototype = {
5629
5630  	// @method clone(): Point
5631  	// Returns a copy of the current point.
5632  	clone: function () {
5633  		return new Point(this.x, this.y);
5634  	},
5635
5636  	// @method add(otherPoint: Point): Point
5637  	// Returns the result of addition of the current and the given points.
5638  	add: function (point) {
5639  		// non-destructive, returns a new point
5640  		return this.clone()._add(toPoint(point));
5641  	},
5642
5643  	_add: function (point) {
5644  		// destructive, used directly for performance in situations where it's safe to modify existing point
5645  		this.x += point.x;
5646  		this.y += point.y;
5647  		return this;
5648  	},
5649
5650  	// @method subtract(otherPoint: Point): Point
5651  	// Returns the result of subtraction of the given point from the current.
5652  	subtract: function (point) {
5653  		return this.clone()._subtract(toPoint(point));
5654  	},
5655
5656  	_subtract: function (point) {
5657  		this.x -= point.x;
5658  		this.y -= point.y;
5659  		return this;
5660  	},
5661
5662  	// @method divideBy(num: Number): Point
5663  	// Returns the result of division of the current point by the given number.
5664  	divideBy: function (num) {
5665  		return this.clone()._divideBy(num);
5666  	},
5667
5668  	_divideBy: function (num) {
5669  		this.x /= num;
5670  		this.y /= num;
5671  		return this;
5672  	},
5673
5674  	// @method multiplyBy(num: Number): Point
5675  	// Returns the result of multiplication of the current point by the given number.
5676  	multiplyBy: function (num) {
5677  		return this.clone()._multiplyBy(num);
5678  	},
5679
5680  	_multiplyBy: function (num) {
5681  		this.x *= num;
5682  		this.y *= num;
5683  		return this;
5684  	},
5685
5686  	// @method scaleBy(scale: Point): Point
5687  	// Multiply each coordinate of the current point by each coordinate of
5688  	// `scale`. In linear algebra terms, multiply the point by the
5689  	// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
5690  	// defined by `scale`.
5691  	scaleBy: function (point) {
5692  		return new Point(this.x * point.x, this.y * point.y);
5693  	},
5694
5695  	// @method unscaleBy(scale: Point): Point
5696  	// Inverse of `scaleBy`. Divide each coordinate of the current point by
5697  	// each coordinate of `scale`.
5698  	unscaleBy: function (point) {
5699  		return new Point(this.x / point.x, this.y / point.y);
5700  	},
5701
5702  	// @method round(): Point
5703  	// Returns a copy of the current point with rounded coordinates.
5704  	round: function () {
5705  		return this.clone()._round();
5706  	},
5707
5708  	_round: function () {
5709  		this.x = Math.round(this.x);
5710  		this.y = Math.round(this.y);
5711  		return this;
5712  	},
5713
5714  	// @method floor(): Point
5715  	// Returns a copy of the current point with floored coordinates (rounded down).
5716  	floor: function () {
5717  		return this.clone()._floor();
5718  	},
5719
5720  	_floor: function () {
5721  		this.x = Math.floor(this.x);
5722  		this.y = Math.floor(this.y);
5723  		return this;
5724  	},
5725
5726  	// @method ceil(): Point
5727  	// Returns a copy of the current point with ceiled coordinates (rounded up).
5728  	ceil: function () {
5729  		return this.clone()._ceil();
5730  	},
5731
5732  	_ceil: function () {
5733  		this.x = Math.ceil(this.x);
5734  		this.y = Math.ceil(this.y);
5735  		return this;
5736  	},
5737
5738  	// @method trunc(): Point
5739  	// Returns a copy of the current point with truncated coordinates (rounded towards zero).
5740  	trunc: function () {
5741  		return this.clone()._trunc();
5742  	},
5743
5744  	_trunc: function () {
5745  		this.x = trunc(this.x);
5746  		this.y = trunc(this.y);
5747  		return this;
5748  	},
5749
5750  	// @method distanceTo(otherPoint: Point): Number
5751  	// Returns the cartesian distance between the current and the given points.
5752  	distanceTo: function (point) {
5753  		point = toPoint(point);
5754
5755  		var x = point.x - this.x,
5756  		    y = point.y - this.y;
5757
5758  		return Math.sqrt(x * x + y * y);
5759  	},
5760
5761  	// @method equals(otherPoint: Point): Boolean
5762  	// Returns `true` if the given point has the same coordinates.
5763  	equals: function (point) {
5764  		point = toPoint(point);
5765
5766  		return point.x === this.x &&
5767  		       point.y === this.y;
5768  	},
5769
5770  	// @method contains(otherPoint: Point): Boolean
5771  	// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
5772  	contains: function (point) {
5773  		point = toPoint(point);
5774
5775  		return Math.abs(point.x) <= Math.abs(this.x) &&
5776  		       Math.abs(point.y) <= Math.abs(this.y);
5777  	},
5778
5779  	// @method toString(): String
5780  	// Returns a string representation of the point for debugging purposes.
5781  	toString: function () {
5782  		return 'Point(' +
5783  		        formatNum(this.x) + ', ' +
5784  		        formatNum(this.y) + ')';
5785  	}
5786  };
5787
5788  // @factory L.point(x: Number, y: Number, round?: Boolean)
5789  // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
5790
5791  // @alternative
5792  // @factory L.point(coords: Number[])
5793  // Expects an array of the form `[x, y]` instead.
5794
5795  // @alternative
5796  // @factory L.point(coords: Object)
5797  // Expects a plain object of the form `{x: Number, y: Number}` instead.
5798  function toPoint(x, y, round) {
5799  	if (x instanceof Point) {
5800  		return x;
5801  	}
5802  	if (isArray(x)) {
5803  		return new Point(x[0], x[1]);
5804  	}
5805  	if (x === undefined || x === null) {
5806  		return x;
5807  	}
5808  	if (typeof x === 'object' && 'x' in x && 'y' in x) {
5809  		return new Point(x.x, x.y);
5810  	}
5811  	return new Point(x, y, round);
5812  }
5813
5814  /*
5815   * @class Bounds
5816   * @aka L.Bounds
5817   *
5818   * Represents a rectangular area in pixel coordinates.
5819   *
5820   * @example
5821   *
5822   * ```js
5823   * var p1 = L.point(10, 10),
5824   * p2 = L.point(40, 60),
5825   * bounds = L.bounds(p1, p2);
5826   * ```
5827   *
5828   * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
5829   *
5830   * ```js
5831   * otherBounds.intersects([[10, 10], [40, 60]]);
5832   * ```
5833   *
5834   * Note that `Bounds` does not inherit from Leaflet's `Class` object,
5835   * which means new classes can't inherit from it, and new methods
5836   * can't be added to it with the `include` function.
5837   */
5838
5839  function Bounds(a, b) {
5840  	if (!a) { return; }
5841
5842  	var points = b ? [a, b] : a;
5843
5844  	for (var i = 0, len = points.length; i < len; i++) {
5845  		this.extend(points[i]);
5846  	}
5847  }
5848
5849  Bounds.prototype = {
5850  	// @method extend(point: Point): this
5851  	// Extends the bounds to contain the given point.
5852
5853  	// @alternative
5854  	// @method extend(otherBounds: Bounds): this
5855  	// Extend the bounds to contain the given bounds
5856  	extend: function (obj) {
5857  		var min2, max2;
5858  		if (!obj) { return this; }
5859
5860  		if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
5861  			min2 = max2 = toPoint(obj);
5862  		} else {
5863  			obj = toBounds(obj);
5864  			min2 = obj.min;
5865  			max2 = obj.max;
5866
5867  			if (!min2 || !max2) { return this; }
5868  		}
5869
5870  		// @property min: Point
5871  		// The top left corner of the rectangle.
5872  		// @property max: Point
5873  		// The bottom right corner of the rectangle.
5874  		if (!this.min && !this.max) {
5875  			this.min = min2.clone();
5876  			this.max = max2.clone();
5877  		} else {
5878  			this.min.x = Math.min(min2.x, this.min.x);
5879  			this.max.x = Math.max(max2.x, this.max.x);
5880  			this.min.y = Math.min(min2.y, this.min.y);
5881  			this.max.y = Math.max(max2.y, this.max.y);
5882  		}
5883  		return this;
5884  	},
5885
5886  	// @method getCenter(round?: Boolean): Point
5887  	// Returns the center point of the bounds.
5888  	getCenter: function (round) {
5889  		return toPoint(
5890  		        (this.min.x + this.max.x) / 2,
5891  		        (this.min.y + this.max.y) / 2, round);
5892  	},
5893
5894  	// @method getBottomLeft(): Point
5895  	// Returns the bottom-left point of the bounds.
5896  	getBottomLeft: function () {
5897  		return toPoint(this.min.x, this.max.y);
5898  	},
5899
5900  	// @method getTopRight(): Point
5901  	// Returns the top-right point of the bounds.
5902  	getTopRight: function () { // -> Point
5903  		return toPoint(this.max.x, this.min.y);
5904  	},
5905
5906  	// @method getTopLeft(): Point
5907  	// Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
5908  	getTopLeft: function () {
5909  		return this.min; // left, top
5910  	},
5911
5912  	// @method getBottomRight(): Point
5913  	// Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
5914  	getBottomRight: function () {
5915  		return this.max; // right, bottom
5916  	},
5917
5918  	// @method getSize(): Point
5919  	// Returns the size of the given bounds
5920  	getSize: function () {
5921  		return this.max.subtract(this.min);
5922  	},
5923
5924  	// @method contains(otherBounds: Bounds): Boolean
5925  	// Returns `true` if the rectangle contains the given one.
5926  	// @alternative
5927  	// @method contains(point: Point): Boolean
5928  	// Returns `true` if the rectangle contains the given point.
5929  	contains: function (obj) {
5930  		var min, max;
5931
5932  		if (typeof obj[0] === 'number' || obj instanceof Point) {
5933  			obj = toPoint(obj);
5934  		} else {
5935  			obj = toBounds(obj);
5936  		}
5937
5938  		if (obj instanceof Bounds) {
5939  			min = obj.min;
5940  			max = obj.max;
5941  		} else {
5942  			min = max = obj;
5943  		}
5944
5945  		return (min.x >= this.min.x) &&
5946  		       (max.x <= this.max.x) &&
5947  		       (min.y >= this.min.y) &&
5948  		       (max.y <= this.max.y);
5949  	},
5950
5951  	// @method intersects(otherBounds: Bounds): Boolean
5952  	// Returns `true` if the rectangle intersects the given bounds. Two bounds
5953  	// intersect if they have at least one point in common.
5954  	intersects: function (bounds) { // (Bounds) -> Boolean
5955  		bounds = toBounds(bounds);
5956
5957  		var min = this.min,
5958  		    max = this.max,
5959  		    min2 = bounds.min,
5960  		    max2 = bounds.max,
5961  		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
5962  		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
5963
5964  		return xIntersects && yIntersects;
5965  	},
5966
5967  	// @method overlaps(otherBounds: Bounds): Boolean
5968  	// Returns `true` if the rectangle overlaps the given bounds. Two bounds
5969  	// overlap if their intersection is an area.
5970  	overlaps: function (bounds) { // (Bounds) -> Boolean
5971  		bounds = toBounds(bounds);
5972
5973  		var min = this.min,
5974  		    max = this.max,
5975  		    min2 = bounds.min,
5976  		    max2 = bounds.max,
5977  		    xOverlaps = (max2.x > min.x) && (min2.x < max.x),
5978  		    yOverlaps = (max2.y > min.y) && (min2.y < max.y);
5979
5980  		return xOverlaps && yOverlaps;
5981  	},
5982
5983  	// @method isValid(): Boolean
5984  	// Returns `true` if the bounds are properly initialized.
5985  	isValid: function () {
5986  		return !!(this.min && this.max);
5987  	},
5988
5989
5990  	// @method pad(bufferRatio: Number): Bounds
5991  	// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
5992  	// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
5993  	// Negative values will retract the bounds.
5994  	pad: function (bufferRatio) {
5995  		var min = this.min,
5996  		max = this.max,
5997  		heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
5998  		widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
5999
6000
6001  		return toBounds(
6002  			toPoint(min.x - heightBuffer, min.y - widthBuffer),
6003  			toPoint(max.x + heightBuffer, max.y + widthBuffer));
6004  	},
6005
6006
6007  	// @method equals(otherBounds: Bounds): Boolean
6008  	// Returns `true` if the rectangle is equivalent to the given bounds.
6009  	equals: function (bounds) {
6010  		if (!bounds) { return false; }
6011
6012  		bounds = toBounds(bounds);
6013
6014  		return this.min.equals(bounds.getTopLeft()) &&
6015  			this.max.equals(bounds.getBottomRight());
6016  	},
6017  };
6018
6019
6020  // @factory L.bounds(corner1: Point, corner2: Point)
6021  // Creates a Bounds object from two corners coordinate pairs.
6022  // @alternative
6023  // @factory L.bounds(points: Point[])
6024  // Creates a Bounds object from the given array of points.
6025  function toBounds(a, b) {
6026  	if (!a || a instanceof Bounds) {
6027  		return a;
6028  	}
6029  	return new Bounds(a, b);
6030  }
6031
6032  /*
6033   * @class LatLngBounds
6034   * @aka L.LatLngBounds
6035   *
6036   * Represents a rectangular geographical area on a map.
6037   *
6038   * @example
6039   *
6040   * ```js
6041   * var corner1 = L.latLng(40.712, -74.227),
6042   * corner2 = L.latLng(40.774, -74.125),
6043   * bounds = L.latLngBounds(corner1, corner2);
6044   * ```
6045   *
6046   * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
6047   *
6048   * ```js
6049   * map.fitBounds([
6050   * 	[40.712, -74.227],
6051   * 	[40.774, -74.125]
6052   * ]);
6053   * ```
6054   *
6055   * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
6056   *
6057   * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
6058   * which means new classes can't inherit from it, and new methods
6059   * can't be added to it with the `include` function.
6060   */
6061
6062  function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
6063  	if (!corner1) { return; }
6064
6065  	var latlngs = corner2 ? [corner1, corner2] : corner1;
6066
6067  	for (var i = 0, len = latlngs.length; i < len; i++) {
6068  		this.extend(latlngs[i]);
6069  	}
6070  }
6071
6072  LatLngBounds.prototype = {
6073
6074  	// @method extend(latlng: LatLng): this
6075  	// Extend the bounds to contain the given point
6076
6077  	// @alternative
6078  	// @method extend(otherBounds: LatLngBounds): this
6079  	// Extend the bounds to contain the given bounds
6080  	extend: function (obj) {
6081  		var sw = this._southWest,
6082  		    ne = this._northEast,
6083  		    sw2, ne2;
6084
6085  		if (obj instanceof LatLng) {
6086  			sw2 = obj;
6087  			ne2 = obj;
6088
6089  		} else if (obj instanceof LatLngBounds) {
6090  			sw2 = obj._southWest;
6091  			ne2 = obj._northEast;
6092
6093  			if (!sw2 || !ne2) { return this; }
6094
6095  		} else {
6096  			return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
6097  		}
6098
6099  		if (!sw && !ne) {
6100  			this._southWest = new LatLng(sw2.lat, sw2.lng);
6101  			this._northEast = new LatLng(ne2.lat, ne2.lng);
6102  		} else {
6103  			sw.lat = Math.min(sw2.lat, sw.lat);
6104  			sw.lng = Math.min(sw2.lng, sw.lng);
6105  			ne.lat = Math.max(ne2.lat, ne.lat);
6106  			ne.lng = Math.max(ne2.lng, ne.lng);
6107  		}
6108
6109  		return this;
6110  	},
6111
6112  	// @method pad(bufferRatio: Number): LatLngBounds
6113  	// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
6114  	// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
6115  	// Negative values will retract the bounds.
6116  	pad: function (bufferRatio) {
6117  		var sw = this._southWest,
6118  		    ne = this._northEast,
6119  		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
6120  		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
6121
6122  		return new LatLngBounds(
6123  		        new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
6124  		        new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
6125  	},
6126
6127  	// @method getCenter(): LatLng
6128  	// Returns the center point of the bounds.
6129  	getCenter: function () {
6130  		return new LatLng(
6131  		        (this._southWest.lat + this._northEast.lat) / 2,
6132  		        (this._southWest.lng + this._northEast.lng) / 2);
6133  	},
6134
6135  	// @method getSouthWest(): LatLng
6136  	// Returns the south-west point of the bounds.
6137  	getSouthWest: function () {
6138  		return this._southWest;
6139  	},
6140
6141  	// @method getNorthEast(): LatLng
6142  	// Returns the north-east point of the bounds.
6143  	getNorthEast: function () {
6144  		return this._northEast;
6145  	},
6146
6147  	// @method getNorthWest(): LatLng
6148  	// Returns the north-west point of the bounds.
6149  	getNorthWest: function () {
6150  		return new LatLng(this.getNorth(), this.getWest());
6151  	},
6152
6153  	// @method getSouthEast(): LatLng
6154  	// Returns the south-east point of the bounds.
6155  	getSouthEast: function () {
6156  		return new LatLng(this.getSouth(), this.getEast());
6157  	},
6158
6159  	// @method getWest(): Number
6160  	// Returns the west longitude of the bounds
6161  	getWest: function () {
6162  		return this._southWest.lng;
6163  	},
6164
6165  	// @method getSouth(): Number
6166  	// Returns the south latitude of the bounds
6167  	getSouth: function () {
6168  		return this._southWest.lat;
6169  	},
6170
6171  	// @method getEast(): Number
6172  	// Returns the east longitude of the bounds
6173  	getEast: function () {
6174  		return this._northEast.lng;
6175  	},
6176
6177  	// @method getNorth(): Number
6178  	// Returns the north latitude of the bounds
6179  	getNorth: function () {
6180  		return this._northEast.lat;
6181  	},
6182
6183  	// @method contains(otherBounds: LatLngBounds): Boolean
6184  	// Returns `true` if the rectangle contains the given one.
6185
6186  	// @alternative
6187  	// @method contains (latlng: LatLng): Boolean
6188  	// Returns `true` if the rectangle contains the given point.
6189  	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
6190  		if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
6191  			obj = toLatLng(obj);
6192  		} else {
6193  			obj = toLatLngBounds(obj);
6194  		}
6195
6196  		var sw = this._southWest,
6197  		    ne = this._northEast,
6198  		    sw2, ne2;
6199
6200  		if (obj instanceof LatLngBounds) {
6201  			sw2 = obj.getSouthWest();
6202  			ne2 = obj.getNorthEast();
6203  		} else {
6204  			sw2 = ne2 = obj;
6205  		}
6206
6207  		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
6208  		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
6209  	},
6210
6211  	// @method intersects(otherBounds: LatLngBounds): Boolean
6212  	// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
6213  	intersects: function (bounds) {
6214  		bounds = toLatLngBounds(bounds);
6215
6216  		var sw = this._southWest,
6217  		    ne = this._northEast,
6218  		    sw2 = bounds.getSouthWest(),
6219  		    ne2 = bounds.getNorthEast(),
6220
6221  		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
6222  		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
6223
6224  		return latIntersects && lngIntersects;
6225  	},
6226
6227  	// @method overlaps(otherBounds: LatLngBounds): Boolean
6228  	// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
6229  	overlaps: function (bounds) {
6230  		bounds = toLatLngBounds(bounds);
6231
6232  		var sw = this._southWest,
6233  		    ne = this._northEast,
6234  		    sw2 = bounds.getSouthWest(),
6235  		    ne2 = bounds.getNorthEast(),
6236
6237  		    latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
6238  		    lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
6239
6240  		return latOverlaps && lngOverlaps;
6241  	},
6242
6243  	// @method toBBoxString(): String
6244  	// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
6245  	toBBoxString: function () {
6246  		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
6247  	},
6248
6249  	// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
6250  	// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
6251  	equals: function (bounds, maxMargin) {
6252  		if (!bounds) { return false; }
6253
6254  		bounds = toLatLngBounds(bounds);
6255
6256  		return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
6257  		       this._northEast.equals(bounds.getNorthEast(), maxMargin);
6258  	},
6259
6260  	// @method isValid(): Boolean
6261  	// Returns `true` if the bounds are properly initialized.
6262  	isValid: function () {
6263  		return !!(this._southWest && this._northEast);
6264  	}
6265  };
6266
6267  // TODO International date line?
6268
6269  // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
6270  // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
6271
6272  // @alternative
6273  // @factory L.latLngBounds(latlngs: LatLng[])
6274  // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
6275  function toLatLngBounds(a, b) {
6276  	if (a instanceof LatLngBounds) {
6277  		return a;
6278  	}
6279  	return new LatLngBounds(a, b);
6280  }
6281
6282  /* @class LatLng
6283   * @aka L.LatLng
6284   *
6285   * Represents a geographical point with a certain latitude and longitude.
6286   *
6287   * @example
6288   *
6289   * ```
6290   * var latlng = L.latLng(50.5, 30.5);
6291   * ```
6292   *
6293   * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
6294   *
6295   * ```
6296   * map.panTo([50, 30]);
6297   * map.panTo({lon: 30, lat: 50});
6298   * map.panTo({lat: 50, lng: 30});
6299   * map.panTo(L.latLng(50, 30));
6300   * ```
6301   *
6302   * Note that `LatLng` does not inherit from Leaflet's `Class` object,
6303   * which means new classes can't inherit from it, and new methods
6304   * can't be added to it with the `include` function.
6305   */
6306
6307  function LatLng(lat, lng, alt) {
6308  	if (isNaN(lat) || isNaN(lng)) {
6309  		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
6310  	}
6311
6312  	// @property lat: Number
6313  	// Latitude in degrees
6314  	this.lat = +lat;
6315
6316  	// @property lng: Number
6317  	// Longitude in degrees
6318  	this.lng = +lng;
6319
6320  	// @property alt: Number
6321  	// Altitude in meters (optional)
6322  	if (alt !== undefined) {
6323  		this.alt = +alt;
6324  	}
6325  }
6326
6327  LatLng.prototype = {
6328  	// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
6329  	// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
6330  	equals: function (obj, maxMargin) {
6331  		if (!obj) { return false; }
6332
6333  		obj = toLatLng(obj);
6334
6335  		var margin = Math.max(
6336  		        Math.abs(this.lat - obj.lat),
6337  		        Math.abs(this.lng - obj.lng));
6338
6339  		return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
6340  	},
6341
6342  	// @method toString(): String
6343  	// Returns a string representation of the point (for debugging purposes).
6344  	toString: function (precision) {
6345  		return 'LatLng(' +
6346  		        formatNum(this.lat, precision) + ', ' +
6347  		        formatNum(this.lng, precision) + ')';
6348  	},
6349
6350  	// @method distanceTo(otherLatLng: LatLng): Number
6351  	// Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
6352  	distanceTo: function (other) {
6353  		return Earth.distance(this, toLatLng(other));
6354  	},
6355
6356  	// @method wrap(): LatLng
6357  	// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
6358  	wrap: function () {
6359  		return Earth.wrapLatLng(this);
6360  	},
6361
6362  	// @method toBounds(sizeInMeters: Number): LatLngBounds
6363  	// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
6364  	toBounds: function (sizeInMeters) {
6365  		var latAccuracy = 180 * sizeInMeters / 40075017,
6366  		    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
6367
6368  		return toLatLngBounds(
6369  		        [this.lat - latAccuracy, this.lng - lngAccuracy],
6370  		        [this.lat + latAccuracy, this.lng + lngAccuracy]);
6371  	},
6372
6373  	clone: function () {
6374  		return new LatLng(this.lat, this.lng, this.alt);
6375  	}
6376  };
6377
6378
6379
6380  // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
6381  // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
6382
6383  // @alternative
6384  // @factory L.latLng(coords: Array): LatLng
6385  // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
6386
6387  // @alternative
6388  // @factory L.latLng(coords: Object): LatLng
6389  // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
6390
6391  function toLatLng(a, b, c) {
6392  	if (a instanceof LatLng) {
6393  		return a;
6394  	}
6395  	if (isArray(a) && typeof a[0] !== 'object') {
6396  		if (a.length === 3) {
6397  			return new LatLng(a[0], a[1], a[2]);
6398  		}
6399  		if (a.length === 2) {
6400  			return new LatLng(a[0], a[1]);
6401  		}
6402  		return null;
6403  	}
6404  	if (a === undefined || a === null) {
6405  		return a;
6406  	}
6407  	if (typeof a === 'object' && 'lat' in a) {
6408  		return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
6409  	}
6410  	if (b === undefined) {
6411  		return null;
6412  	}
6413  	return new LatLng(a, b, c);
6414  }
6415
6416  /*
6417   * @namespace CRS
6418   * @crs L.CRS.Base
6419   * Object that defines coordinate reference systems for projecting
6420   * geographical points into pixel (screen) coordinates and back (and to
6421   * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
6422   * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
6423   *
6424   * Leaflet defines the most usual CRSs by default. If you want to use a
6425   * CRS not defined by default, take a look at the
6426   * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
6427   *
6428   * Note that the CRS instances do not inherit from Leaflet's `Class` object,
6429   * and can't be instantiated. Also, new classes can't inherit from them,
6430   * and methods can't be added to them with the `include` function.
6431   */
6432
6433  var CRS = {
6434  	// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
6435  	// Projects geographical coordinates into pixel coordinates for a given zoom.
6436  	latLngToPoint: function (latlng, zoom) {
6437  		var projectedPoint = this.projection.project(latlng),
6438  		    scale = this.scale(zoom);
6439
6440  		return this.transformation._transform(projectedPoint, scale);
6441  	},
6442
6443  	// @method pointToLatLng(point: Point, zoom: Number): LatLng
6444  	// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
6445  	// zoom into geographical coordinates.
6446  	pointToLatLng: function (point, zoom) {
6447  		var scale = this.scale(zoom),
6448  		    untransformedPoint = this.transformation.untransform(point, scale);
6449
6450  		return this.projection.unproject(untransformedPoint);
6451  	},
6452
6453  	// @method project(latlng: LatLng): Point
6454  	// Projects geographical coordinates into coordinates in units accepted for
6455  	// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
6456  	project: function (latlng) {
6457  		return this.projection.project(latlng);
6458  	},
6459
6460  	// @method unproject(point: Point): LatLng
6461  	// Given a projected coordinate returns the corresponding LatLng.
6462  	// The inverse of `project`.
6463  	unproject: function (point) {
6464  		return this.projection.unproject(point);
6465  	},
6466
6467  	// @method scale(zoom: Number): Number
6468  	// Returns the scale used when transforming projected coordinates into
6469  	// pixel coordinates for a particular zoom. For example, it returns
6470  	// `256 * 2^zoom` for Mercator-based CRS.
6471  	scale: function (zoom) {
6472  		return 256 * Math.pow(2, zoom);
6473  	},
6474
6475  	// @method zoom(scale: Number): Number
6476  	// Inverse of `scale()`, returns the zoom level corresponding to a scale
6477  	// factor of `scale`.
6478  	zoom: function (scale) {
6479  		return Math.log(scale / 256) / Math.LN2;
6480  	},
6481
6482  	// @method getProjectedBounds(zoom: Number): Bounds
6483  	// Returns the projection's bounds scaled and transformed for the provided `zoom`.
6484  	getProjectedBounds: function (zoom) {
6485  		if (this.infinite) { return null; }
6486
6487  		var b = this.projection.bounds,
6488  		    s = this.scale(zoom),
6489  		    min = this.transformation.transform(b.min, s),
6490  		    max = this.transformation.transform(b.max, s);
6491
6492  		return new Bounds(min, max);
6493  	},
6494
6495  	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
6496  	// Returns the distance between two geographical coordinates.
6497
6498  	// @property code: String
6499  	// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
6500  	//
6501  	// @property wrapLng: Number[]
6502  	// An array of two numbers defining whether the longitude (horizontal) coordinate
6503  	// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
6504  	// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
6505  	//
6506  	// @property wrapLat: Number[]
6507  	// Like `wrapLng`, but for the latitude (vertical) axis.
6508
6509  	// wrapLng: [min, max],
6510  	// wrapLat: [min, max],
6511
6512  	// @property infinite: Boolean
6513  	// If true, the coordinate space will be unbounded (infinite in both axes)
6514  	infinite: false,
6515
6516  	// @method wrapLatLng(latlng: LatLng): LatLng
6517  	// Returns a `LatLng` where lat and lng has been wrapped according to the
6518  	// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
6519  	wrapLatLng: function (latlng) {
6520  		var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
6521  		    lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
6522  		    alt = latlng.alt;
6523
6524  		return new LatLng(lat, lng, alt);
6525  	},
6526
6527  	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
6528  	// Returns a `LatLngBounds` with the same size as the given one, ensuring
6529  	// that its center is within the CRS's bounds.
6530  	// Only accepts actual `L.LatLngBounds` instances, not arrays.
6531  	wrapLatLngBounds: function (bounds) {
6532  		var center = bounds.getCenter(),
6533  		    newCenter = this.wrapLatLng(center),
6534  		    latShift = center.lat - newCenter.lat,
6535  		    lngShift = center.lng - newCenter.lng;
6536
6537  		if (latShift === 0 && lngShift === 0) {
6538  			return bounds;
6539  		}
6540
6541  		var sw = bounds.getSouthWest(),
6542  		    ne = bounds.getNorthEast(),
6543  		    newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
6544  		    newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
6545
6546  		return new LatLngBounds(newSw, newNe);
6547  	}
6548  };
6549
6550  /*
6551   * @namespace CRS
6552   * @crs L.CRS.Earth
6553   *
6554   * Serves as the base for CRS that are global such that they cover the earth.
6555   * Can only be used as the base for other CRS and cannot be used directly,
6556   * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
6557   * meters.
6558   */
6559
6560  var Earth = extend({}, CRS, {
6561  	wrapLng: [-180, 180],
6562
6563  	// Mean Earth Radius, as recommended for use by
6564  	// the International Union of Geodesy and Geophysics,
6565  	// see https://rosettacode.org/wiki/Haversine_formula
6566  	R: 6371000,
6567
6568  	// distance between two geographical points using spherical law of cosines approximation
6569  	distance: function (latlng1, latlng2) {
6570  		var rad = Math.PI / 180,
6571  		    lat1 = latlng1.lat * rad,
6572  		    lat2 = latlng2.lat * rad,
6573  		    sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
6574  		    sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
6575  		    a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
6576  		    c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
6577  		return this.R * c;
6578  	}
6579  });
6580
6581  /*
6582   * @namespace Projection
6583   * @projection L.Projection.SphericalMercator
6584   *
6585   * Spherical Mercator projection — the most common projection for online maps,
6586   * used by almost all free and commercial tile providers. Assumes that Earth is
6587   * a sphere. Used by the `EPSG:3857` CRS.
6588   */
6589
6590  var earthRadius = 6378137;
6591
6592  var SphericalMercator = {
6593
6594  	R: earthRadius,
6595  	MAX_LATITUDE: 85.0511287798,
6596
6597  	project: function (latlng) {
6598  		var d = Math.PI / 180,
6599  		    max = this.MAX_LATITUDE,
6600  		    lat = Math.max(Math.min(max, latlng.lat), -max),
6601  		    sin = Math.sin(lat * d);
6602
6603  		return new Point(
6604  			this.R * latlng.lng * d,
6605  			this.R * Math.log((1 + sin) / (1 - sin)) / 2);
6606  	},
6607
6608  	unproject: function (point) {
6609  		var d = 180 / Math.PI;
6610
6611  		return new LatLng(
6612  			(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
6613  			point.x * d / this.R);
6614  	},
6615
6616  	bounds: (function () {
6617  		var d = earthRadius * Math.PI;
6618  		return new Bounds([-d, -d], [d, d]);
6619  	})()
6620  };
6621
6622  /*
6623   * @class Transformation
6624   * @aka L.Transformation
6625   *
6626   * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
6627   * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
6628   * the reverse. Used by Leaflet in its projections code.
6629   *
6630   * @example
6631   *
6632   * ```js
6633   * var transformation = L.transformation(2, 5, -1, 10),
6634   * 	p = L.point(1, 2),
6635   * 	p2 = transformation.transform(p), //  L.point(7, 8)
6636   * 	p3 = transformation.untransform(p2); //  L.point(1, 2)
6637   * ```
6638   */
6639
6640
6641  // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
6642  // Creates a `Transformation` object with the given coefficients.
6643  function Transformation(a, b, c, d) {
6644  	if (isArray(a)) {
6645  		// use array properties
6646  		this._a = a[0];
6647  		this._b = a[1];
6648  		this._c = a[2];
6649  		this._d = a[3];
6650  		return;
6651  	}
6652  	this._a = a;
6653  	this._b = b;
6654  	this._c = c;
6655  	this._d = d;
6656  }
6657
6658  Transformation.prototype = {
6659  	// @method transform(point: Point, scale?: Number): Point
6660  	// Returns a transformed point, optionally multiplied by the given scale.
6661  	// Only accepts actual `L.Point` instances, not arrays.
6662  	transform: function (point, scale) { // (Point, Number) -> Point
6663  		return this._transform(point.clone(), scale);
6664  	},
6665
6666  	// destructive transform (faster)
6667  	_transform: function (point, scale) {
6668  		scale = scale || 1;
6669  		point.x = scale * (this._a * point.x + this._b);
6670  		point.y = scale * (this._c * point.y + this._d);
6671  		return point;
6672  	},
6673
6674  	// @method untransform(point: Point, scale?: Number): Point
6675  	// Returns the reverse transformation of the given point, optionally divided
6676  	// by the given scale. Only accepts actual `L.Point` instances, not arrays.
6677  	untransform: function (point, scale) {
6678  		scale = scale || 1;
6679  		return new Point(
6680  		        (point.x / scale - this._b) / this._a,
6681  		        (point.y / scale - this._d) / this._c);
6682  	}
6683  };
6684
6685  // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
6686
6687  // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
6688  // Instantiates a Transformation object with the given coefficients.
6689
6690  // @alternative
6691  // @factory L.transformation(coefficients: Array): Transformation
6692  // Expects an coefficients array of the form
6693  // `[a: Number, b: Number, c: Number, d: Number]`.
6694
6695  function toTransformation(a, b, c, d) {
6696  	return new Transformation(a, b, c, d);
6697  }
6698
6699  /*
6700   * @namespace CRS
6701   * @crs L.CRS.EPSG3857
6702   *
6703   * The most common CRS for online maps, used by almost all free and commercial
6704   * tile providers. Uses Spherical Mercator projection. Set in by default in
6705   * Map's `crs` option.
6706   */
6707
6708  var EPSG3857 = extend({}, Earth, {
6709  	code: 'EPSG:3857',
6710  	projection: SphericalMercator,
6711
6712  	transformation: (function () {
6713  		var scale = 0.5 / (Math.PI * SphericalMercator.R);
6714  		return toTransformation(scale, 0.5, -scale, 0.5);
6715  	}())
6716  });
6717
6718  var EPSG900913 = extend({}, EPSG3857, {
6719  	code: 'EPSG:900913'
6720  });
6721
6722  // @namespace SVG; @section
6723  // There are several static functions which can be called without instantiating L.SVG:
6724
6725  // @function create(name: String): SVGElement
6726  // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
6727  // corresponding to the class name passed. For example, using 'line' will return
6728  // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
6729  function svgCreate(name) {
6730  	return document.createElementNS('http://www.w3.org/2000/svg', name);
6731  }
6732
6733  // @function pointsToPath(rings: Point[], closed: Boolean): String
6734  // Generates a SVG path string for multiple rings, with each ring turning
6735  // into "M..L..L.." instructions
6736  function pointsToPath(rings, closed) {
6737  	var str = '',
6738  	i, j, len, len2, points, p;
6739
6740  	for (i = 0, len = rings.length; i < len; i++) {
6741  		points = rings[i];
6742
6743  		for (j = 0, len2 = points.length; j < len2; j++) {
6744  			p = points[j];
6745  			str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
6746  		}
6747
6748  		// closes the ring for polygons; "x" is VML syntax
6749  		str += closed ? (Browser.svg ? 'z' : 'x') : '';
6750  	}
6751
6752  	// SVG complains about empty path strings
6753  	return str || 'M0 0';
6754  }
6755
6756  /*
6757   * @namespace Browser
6758   * @aka L.Browser
6759   *
6760   * A namespace with static properties for browser/feature detection used by Leaflet internally.
6761   *
6762   * @example
6763   *
6764   * ```js
6765   * if (L.Browser.ielt9) {
6766   *   alert('Upgrade your browser, dude!');
6767   * }
6768   * ```
6769   */
6770
6771  var style = document.documentElement.style;
6772
6773  // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
6774  var ie = 'ActiveXObject' in window;
6775
6776  // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
6777  var ielt9 = ie && !document.addEventListener;
6778
6779  // @property edge: Boolean; `true` for the Edge web browser.
6780  var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
6781
6782  // @property webkit: Boolean;
6783  // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
6784  var webkit = userAgentContains('webkit');
6785
6786  // @property android: Boolean
6787  // **Deprecated.** `true` for any browser running on an Android platform.
6788  var android = userAgentContains('android');
6789
6790  // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
6791  var android23 = userAgentContains('android 2') || userAgentContains('android 3');
6792
6793  /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
6794  var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
6795  // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
6796  var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
6797
6798  // @property opera: Boolean; `true` for the Opera browser
6799  var opera = !!window.opera;
6800
6801  // @property chrome: Boolean; `true` for the Chrome browser.
6802  var chrome = !edge && userAgentContains('chrome');
6803
6804  // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
6805  var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
6806
6807  // @property safari: Boolean; `true` for the Safari browser.
6808  var safari = !chrome && userAgentContains('safari');
6809
6810  var phantom = userAgentContains('phantom');
6811
6812  // @property opera12: Boolean
6813  // `true` for the Opera browser supporting CSS transforms (version 12 or later).
6814  var opera12 = 'OTransition' in style;
6815
6816  // @property win: Boolean; `true` when the browser is running in a Windows platform
6817  var win = navigator.platform.indexOf('Win') === 0;
6818
6819  // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
6820  var ie3d = ie && ('transition' in style);
6821
6822  // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
6823  var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
6824
6825  // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
6826  var gecko3d = 'MozPerspective' in style;
6827
6828  // @property any3d: Boolean
6829  // `true` for all browsers supporting CSS transforms.
6830  var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
6831
6832  // @property mobile: Boolean; `true` for all browsers running in a mobile device.
6833  var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
6834
6835  // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
6836  var mobileWebkit = mobile && webkit;
6837
6838  // @property mobileWebkit3d: Boolean
6839  // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
6840  var mobileWebkit3d = mobile && webkit3d;
6841
6842  // @property msPointer: Boolean
6843  // `true` for browsers implementing the Microsoft touch events model (notably IE10).
6844  var msPointer = !window.PointerEvent && window.MSPointerEvent;
6845
6846  // @property pointer: Boolean
6847  // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
6848  var pointer = !!(window.PointerEvent || msPointer);
6849
6850  // @property touchNative: Boolean
6851  // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
6852  // **This does not necessarily mean** that the browser is running in a computer with
6853  // a touchscreen, it only means that the browser is capable of understanding
6854  // touch events.
6855  var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
6856
6857  // @property touch: Boolean
6858  // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
6859  // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
6860  var touch = !window.L_NO_TOUCH && (touchNative || pointer);
6861
6862  // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
6863  var mobileOpera = mobile && opera;
6864
6865  // @property mobileGecko: Boolean
6866  // `true` for gecko-based browsers running in a mobile device.
6867  var mobileGecko = mobile && gecko;
6868
6869  // @property retina: Boolean
6870  // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
6871  var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
6872
6873  // @property passiveEvents: Boolean
6874  // `true` for browsers that support passive events.
6875  var passiveEvents = (function () {
6876  	var supportsPassiveOption = false;
6877  	try {
6878  		var opts = Object.defineProperty({}, 'passive', {
6879  			get: function () { // eslint-disable-line getter-return
6880  				supportsPassiveOption = true;
6881  			}
6882  		});
6883  		window.addEventListener('testPassiveEventSupport', falseFn, opts);
6884  		window.removeEventListener('testPassiveEventSupport', falseFn, opts);
6885  	} catch (e) {
6886  		// Errors can safely be ignored since this is only a browser support test.
6887  	}
6888  	return supportsPassiveOption;
6889  }());
6890
6891  // @property canvas: Boolean
6892  // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
6893  var canvas$1 = (function () {
6894  	return !!document.createElement('canvas').getContext;
6895  }());
6896
6897  // @property svg: Boolean
6898  // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
6899  var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
6900
6901  var inlineSvg = !!svg$1 && (function () {
6902  	var div = document.createElement('div');
6903  	div.innerHTML = '<svg/>';
6904  	return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
6905  })();
6906
6907  // @property vml: Boolean
6908  // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
6909  var vml = !svg$1 && (function () {
6910  	try {
6911  		var div = document.createElement('div');
6912  		div.innerHTML = '<v:shape adj="1"/>';
6913
6914  		var shape = div.firstChild;
6915  		shape.style.behavior = 'url(#default#VML)';
6916
6917  		return shape && (typeof shape.adj === 'object');
6918
6919  	} catch (e) {
6920  		return false;
6921  	}
6922  }());
6923
6924
6925  // @property mac: Boolean; `true` when the browser is running in a Mac platform
6926  var mac = navigator.platform.indexOf('Mac') === 0;
6927
6928  // @property mac: Boolean; `true` when the browser is running in a Linux platform
6929  var linux = navigator.platform.indexOf('Linux') === 0;
6930
6931  function userAgentContains(str) {
6932  	return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
6933  }
6934
6935
6936  var Browser = {
6937  	ie: ie,
6938  	ielt9: ielt9,
6939  	edge: edge,
6940  	webkit: webkit,
6941  	android: android,
6942  	android23: android23,
6943  	androidStock: androidStock,
6944  	opera: opera,
6945  	chrome: chrome,
6946  	gecko: gecko,
6947  	safari: safari,
6948  	phantom: phantom,
6949  	opera12: opera12,
6950  	win: win,
6951  	ie3d: ie3d,
6952  	webkit3d: webkit3d,
6953  	gecko3d: gecko3d,
6954  	any3d: any3d,
6955  	mobile: mobile,
6956  	mobileWebkit: mobileWebkit,
6957  	mobileWebkit3d: mobileWebkit3d,
6958  	msPointer: msPointer,
6959  	pointer: pointer,
6960  	touch: touch,
6961  	touchNative: touchNative,
6962  	mobileOpera: mobileOpera,
6963  	mobileGecko: mobileGecko,
6964  	retina: retina,
6965  	passiveEvents: passiveEvents,
6966  	canvas: canvas$1,
6967  	svg: svg$1,
6968  	vml: vml,
6969  	inlineSvg: inlineSvg,
6970  	mac: mac,
6971  	linux: linux
6972  };
6973
6974  /*
6975   * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
6976   */
6977
6978  var POINTER_DOWN =   Browser.msPointer ? 'MSPointerDown'   : 'pointerdown';
6979  var POINTER_MOVE =   Browser.msPointer ? 'MSPointerMove'   : 'pointermove';
6980  var POINTER_UP =     Browser.msPointer ? 'MSPointerUp'     : 'pointerup';
6981  var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
6982  var pEvent = {
6983  	touchstart  : POINTER_DOWN,
6984  	touchmove   : POINTER_MOVE,
6985  	touchend    : POINTER_UP,
6986  	touchcancel : POINTER_CANCEL
6987  };
6988  var handle = {
6989  	touchstart  : _onPointerStart,
6990  	touchmove   : _handlePointer,
6991  	touchend    : _handlePointer,
6992  	touchcancel : _handlePointer
6993  };
6994  var _pointers = {};
6995  var _pointerDocListener = false;
6996
6997  // Provides a touch events wrapper for (ms)pointer events.
6998  // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
6999
7000  function addPointerListener(obj, type, handler) {
7001  	if (type === 'touchstart') {
7002  		_addPointerDocListener();
7003  	}
7004  	if (!handle[type]) {
7005  		console.warn('wrong event specified:', type);
7006  		return falseFn;
7007  	}
7008  	handler = handle[type].bind(this, handler);
7009  	obj.addEventListener(pEvent[type], handler, false);
7010  	return handler;
7011  }
7012
7013  function removePointerListener(obj, type, handler) {
7014  	if (!pEvent[type]) {
7015  		console.warn('wrong event specified:', type);
7016  		return;
7017  	}
7018  	obj.removeEventListener(pEvent[type], handler, false);
7019  }
7020
7021  function _globalPointerDown(e) {
7022  	_pointers[e.pointerId] = e;
7023  }
7024
7025  function _globalPointerMove(e) {
7026  	if (_pointers[e.pointerId]) {
7027  		_pointers[e.pointerId] = e;
7028  	}
7029  }
7030
7031  function _globalPointerUp(e) {
7032  	delete _pointers[e.pointerId];
7033  }
7034
7035  function _addPointerDocListener() {
7036  	// need to keep track of what pointers and how many are active to provide e.touches emulation
7037  	if (!_pointerDocListener) {
7038  		// we listen document as any drags that end by moving the touch off the screen get fired there
7039  		document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
7040  		document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
7041  		document.addEventListener(POINTER_UP, _globalPointerUp, true);
7042  		document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
7043
7044  		_pointerDocListener = true;
7045  	}
7046  }
7047
7048  function _handlePointer(handler, e) {
7049  	if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
7050
7051  	e.touches = [];
7052  	for (var i in _pointers) {
7053  		e.touches.push(_pointers[i]);
7054  	}
7055  	e.changedTouches = [e];
7056
7057  	handler(e);
7058  }
7059
7060  function _onPointerStart(handler, e) {
7061  	// IE10 specific: MsTouch needs preventDefault. See #2000
7062  	if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
7063  		preventDefault(e);
7064  	}
7065  	_handlePointer(handler, e);
7066  }
7067
7068  /*
7069   * Extends the event handling code with double tap support for mobile browsers.
7070   *
7071   * Note: currently most browsers fire native dblclick, with only a few exceptions
7072   * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
7073   */
7074
7075  function makeDblclick(event) {
7076  	// in modern browsers `type` cannot be just overridden:
7077  	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
7078  	var newEvent = {},
7079  	    prop, i;
7080  	for (i in event) {
7081  		prop = event[i];
7082  		newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
7083  	}
7084  	event = newEvent;
7085  	newEvent.type = 'dblclick';
7086  	newEvent.detail = 2;
7087  	newEvent.isTrusted = false;
7088  	newEvent._simulated = true; // for debug purposes
7089  	return newEvent;
7090  }
7091
7092  var delay = 200;
7093  function addDoubleTapListener(obj, handler) {
7094  	// Most browsers handle double tap natively
7095  	obj.addEventListener('dblclick', handler);
7096
7097  	// On some platforms the browser doesn't fire native dblclicks for touch events.
7098  	// It seems that in all such cases `detail` property of `click` event is always `1`.
7099  	// So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
7100  	var last = 0,
7101  	    detail;
7102  	function simDblclick(e) {
7103  		if (e.detail !== 1) {
7104  			detail = e.detail; // keep in sync to avoid false dblclick in some cases
7105  			return;
7106  		}
7107
7108  		if (e.pointerType === 'mouse' ||
7109  			(e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
7110
7111  			return;
7112  		}
7113
7114  		// When clicking on an <input>, the browser generates a click on its
7115  		// <label> (and vice versa) triggering two clicks in quick succession.
7116  		// This ignores clicks on elements which are a label with a 'for'
7117  		// attribute (or children of such a label), but not children of
7118  		// a <input>.
7119  		var path = getPropagationPath(e);
7120  		if (path.some(function (el) {
7121  			return el instanceof HTMLLabelElement && el.attributes.for;
7122  		}) &&
7123  			!path.some(function (el) {
7124  				return (
7125  					el instanceof HTMLInputElement ||
7126  					el instanceof HTMLSelectElement
7127  				);
7128  			})
7129  		) {
7130  			return;
7131  		}
7132
7133  		var now = Date.now();
7134  		if (now - last <= delay) {
7135  			detail++;
7136  			if (detail === 2) {
7137  				handler(makeDblclick(e));
7138  			}
7139  		} else {
7140  			detail = 1;
7141  		}
7142  		last = now;
7143  	}
7144
7145  	obj.addEventListener('click', simDblclick);
7146
7147  	return {
7148  		dblclick: handler,
7149  		simDblclick: simDblclick
7150  	};
7151  }
7152
7153  function removeDoubleTapListener(obj, handlers) {
7154  	obj.removeEventListener('dblclick', handlers.dblclick);
7155  	obj.removeEventListener('click', handlers.simDblclick);
7156  }
7157
7158  /*
7159   * @namespace DomUtil
7160   *
7161   * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
7162   * tree, used by Leaflet internally.
7163   *
7164   * Most functions expecting or returning a `HTMLElement` also work for
7165   * SVG elements. The only difference is that classes refer to CSS classes
7166   * in HTML and SVG classes in SVG.
7167   */
7168
7169
7170  // @property TRANSFORM: String
7171  // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
7172  var TRANSFORM = testProp(
7173  	['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
7174
7175  // webkitTransition comes first because some browser versions that drop vendor prefix don't do
7176  // the same for the transitionend event, in particular the Android 4.1 stock browser
7177
7178  // @property TRANSITION: String
7179  // Vendor-prefixed transition style name.
7180  var TRANSITION = testProp(
7181  	['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
7182
7183  // @property TRANSITION_END: String
7184  // Vendor-prefixed transitionend event name.
7185  var TRANSITION_END =
7186  	TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
7187
7188
7189  // @function get(id: String|HTMLElement): HTMLElement
7190  // Returns an element given its DOM id, or returns the element itself
7191  // if it was passed directly.
7192  function get(id) {
7193  	return typeof id === 'string' ? document.getElementById(id) : id;
7194  }
7195
7196  // @function getStyle(el: HTMLElement, styleAttrib: String): String
7197  // Returns the value for a certain style attribute on an element,
7198  // including computed values or values set through CSS.
7199  function getStyle(el, style) {
7200  	var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
7201
7202  	if ((!value || value === 'auto') && document.defaultView) {
7203  		var css = document.defaultView.getComputedStyle(el, null);
7204  		value = css ? css[style] : null;
7205  	}
7206  	return value === 'auto' ? null : value;
7207  }
7208
7209  // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
7210  // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
7211  function create$1(tagName, className, container) {
7212  	var el = document.createElement(tagName);
7213  	el.className = className || '';
7214
7215  	if (container) {
7216  		container.appendChild(el);
7217  	}
7218  	return el;
7219  }
7220
7221  // @function remove(el: HTMLElement)
7222  // Removes `el` from its parent element
7223  function remove(el) {
7224  	var parent = el.parentNode;
7225  	if (parent) {
7226  		parent.removeChild(el);
7227  	}
7228  }
7229
7230  // @function empty(el: HTMLElement)
7231  // Removes all of `el`'s children elements from `el`
7232  function empty(el) {
7233  	while (el.firstChild) {
7234  		el.removeChild(el.firstChild);
7235  	}
7236  }
7237
7238  // @function toFront(el: HTMLElement)
7239  // Makes `el` the last child of its parent, so it renders in front of the other children.
7240  function toFront(el) {
7241  	var parent = el.parentNode;
7242  	if (parent && parent.lastChild !== el) {
7243  		parent.appendChild(el);
7244  	}
7245  }
7246
7247  // @function toBack(el: HTMLElement)
7248  // Makes `el` the first child of its parent, so it renders behind the other children.
7249  function toBack(el) {
7250  	var parent = el.parentNode;
7251  	if (parent && parent.firstChild !== el) {
7252  		parent.insertBefore(el, parent.firstChild);
7253  	}
7254  }
7255
7256  // @function hasClass(el: HTMLElement, name: String): Boolean
7257  // Returns `true` if the element's class attribute contains `name`.
7258  function hasClass(el, name) {
7259  	if (el.classList !== undefined) {
7260  		return el.classList.contains(name);
7261  	}
7262  	var className = getClass(el);
7263  	return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
7264  }
7265
7266  // @function addClass(el: HTMLElement, name: String)
7267  // Adds `name` to the element's class attribute.
7268  function addClass(el, name) {
7269  	if (el.classList !== undefined) {
7270  		var classes = splitWords(name);
7271  		for (var i = 0, len = classes.length; i < len; i++) {
7272  			el.classList.add(classes[i]);
7273  		}
7274  	} else if (!hasClass(el, name)) {
7275  		var className = getClass(el);
7276  		setClass(el, (className ? className + ' ' : '') + name);
7277  	}
7278  }
7279
7280  // @function removeClass(el: HTMLElement, name: String)
7281  // Removes `name` from the element's class attribute.
7282  function removeClass(el, name) {
7283  	if (el.classList !== undefined) {
7284  		el.classList.remove(name);
7285  	} else {
7286  		setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
7287  	}
7288  }
7289
7290  // @function setClass(el: HTMLElement, name: String)
7291  // Sets the element's class.
7292  function setClass(el, name) {
7293  	if (el.className.baseVal === undefined) {
7294  		el.className = name;
7295  	} else {
7296  		// in case of SVG element
7297  		el.className.baseVal = name;
7298  	}
7299  }
7300
7301  // @function getClass(el: HTMLElement): String
7302  // Returns the element's class.
7303  function getClass(el) {
7304  	// Check if the element is an SVGElementInstance and use the correspondingElement instead
7305  	// (Required for linked SVG elements in IE11.)
7306  	if (el.correspondingElement) {
7307  		el = el.correspondingElement;
7308  	}
7309  	return el.className.baseVal === undefined ? el.className : el.className.baseVal;
7310  }
7311
7312  // @function setOpacity(el: HTMLElement, opacity: Number)
7313  // Set the opacity of an element (including old IE support).
7314  // `opacity` must be a number from `0` to `1`.
7315  function setOpacity(el, value) {
7316  	if ('opacity' in el.style) {
7317  		el.style.opacity = value;
7318  	} else if ('filter' in el.style) {
7319  		_setOpacityIE(el, value);
7320  	}
7321  }
7322
7323  function _setOpacityIE(el, value) {
7324  	var filter = false,
7325  	    filterName = 'DXImageTransform.Microsoft.Alpha';
7326
7327  	// filters collection throws an error if we try to retrieve a filter that doesn't exist
7328  	try {
7329  		filter = el.filters.item(filterName);
7330  	} catch (e) {
7331  		// don't set opacity to 1 if we haven't already set an opacity,
7332  		// it isn't needed and breaks transparent pngs.
7333  		if (value === 1) { return; }
7334  	}
7335
7336  	value = Math.round(value * 100);
7337
7338  	if (filter) {
7339  		filter.Enabled = (value !== 100);
7340  		filter.Opacity = value;
7341  	} else {
7342  		el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
7343  	}
7344  }
7345
7346  // @function testProp(props: String[]): String|false
7347  // Goes through the array of style names and returns the first name
7348  // that is a valid style name for an element. If no such name is found,
7349  // it returns false. Useful for vendor-prefixed styles like `transform`.
7350  function testProp(props) {
7351  	var style = document.documentElement.style;
7352
7353  	for (var i = 0; i < props.length; i++) {
7354  		if (props[i] in style) {
7355  			return props[i];
7356  		}
7357  	}
7358  	return false;
7359  }
7360
7361  // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
7362  // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
7363  // and optionally scaled by `scale`. Does not have an effect if the
7364  // browser doesn't support 3D CSS transforms.
7365  function setTransform(el, offset, scale) {
7366  	var pos = offset || new Point(0, 0);
7367
7368  	el.style[TRANSFORM] =
7369  		(Browser.ie3d ?
7370  			'translate(' + pos.x + 'px,' + pos.y + 'px)' :
7371  			'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
7372  		(scale ? ' scale(' + scale + ')' : '');
7373  }
7374
7375  // @function setPosition(el: HTMLElement, position: Point)
7376  // Sets the position of `el` to coordinates specified by `position`,
7377  // using CSS translate or top/left positioning depending on the browser
7378  // (used by Leaflet internally to position its layers).
7379  function setPosition(el, point) {
7380
7381  	/*eslint-disable */
7382  	el._leaflet_pos = point;
7383  	/* eslint-enable */
7384
7385  	if (Browser.any3d) {
7386  		setTransform(el, point);
7387  	} else {
7388  		el.style.left = point.x + 'px';
7389  		el.style.top = point.y + 'px';
7390  	}
7391  }
7392
7393  // @function getPosition(el: HTMLElement): Point
7394  // Returns the coordinates of an element previously positioned with setPosition.
7395  function getPosition(el) {
7396  	// this method is only used for elements previously positioned using setPosition,
7397  	// so it's safe to cache the position for performance
7398
7399  	return el._leaflet_pos || new Point(0, 0);
7400  }
7401
7402  // @function disableTextSelection()
7403  // Prevents the user from generating `selectstart` DOM events, usually generated
7404  // when the user drags the mouse through a page with text. Used internally
7405  // by Leaflet to override the behaviour of any click-and-drag interaction on
7406  // the map. Affects drag interactions on the whole document.
7407
7408  // @function enableTextSelection()
7409  // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
7410  var disableTextSelection;
7411  var enableTextSelection;
7412  var _userSelect;
7413  if ('onselectstart' in document) {
7414  	disableTextSelection = function () {
7415  		on(window, 'selectstart', preventDefault);
7416  	};
7417  	enableTextSelection = function () {
7418  		off(window, 'selectstart', preventDefault);
7419  	};
7420  } else {
7421  	var userSelectProperty = testProp(
7422  		['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
7423
7424  	disableTextSelection = function () {
7425  		if (userSelectProperty) {
7426  			var style = document.documentElement.style;
7427  			_userSelect = style[userSelectProperty];
7428  			style[userSelectProperty] = 'none';
7429  		}
7430  	};
7431  	enableTextSelection = function () {
7432  		if (userSelectProperty) {
7433  			document.documentElement.style[userSelectProperty] = _userSelect;
7434  			_userSelect = undefined;
7435  		}
7436  	};
7437  }
7438
7439  // @function disableImageDrag()
7440  // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
7441  // for `dragstart` DOM events, usually generated when the user drags an image.
7442  function disableImageDrag() {
7443  	on(window, 'dragstart', preventDefault);
7444  }
7445
7446  // @function enableImageDrag()
7447  // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
7448  function enableImageDrag() {
7449  	off(window, 'dragstart', preventDefault);
7450  }
7451
7452  var _outlineElement, _outlineStyle;
7453  // @function preventOutline(el: HTMLElement)
7454  // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
7455  // of the element `el` invisible. Used internally by Leaflet to prevent
7456  // focusable elements from displaying an outline when the user performs a
7457  // drag interaction on them.
7458  function preventOutline(element) {
7459  	while (element.tabIndex === -1) {
7460  		element = element.parentNode;
7461  	}
7462  	if (!element.style) { return; }
7463  	restoreOutline();
7464  	_outlineElement = element;
7465  	_outlineStyle = element.style.outlineStyle;
7466  	element.style.outlineStyle = 'none';
7467  	on(window, 'keydown', restoreOutline);
7468  }
7469
7470  // @function restoreOutline()
7471  // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
7472  function restoreOutline() {
7473  	if (!_outlineElement) { return; }
7474  	_outlineElement.style.outlineStyle = _outlineStyle;
7475  	_outlineElement = undefined;
7476  	_outlineStyle = undefined;
7477  	off(window, 'keydown', restoreOutline);
7478  }
7479
7480  // @function getSizedParentNode(el: HTMLElement): HTMLElement
7481  // Finds the closest parent node which size (width and height) is not null.
7482  function getSizedParentNode(element) {
7483  	do {
7484  		element = element.parentNode;
7485  	} while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
7486  	return element;
7487  }
7488
7489  // @function getScale(el: HTMLElement): Object
7490  // Computes the CSS scale currently applied on the element.
7491  // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
7492  // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
7493  function getScale(element) {
7494  	var rect = element.getBoundingClientRect(); // Read-only in old browsers.
7495
7496  	return {
7497  		x: rect.width / element.offsetWidth || 1,
7498  		y: rect.height / element.offsetHeight || 1,
7499  		boundingClientRect: rect
7500  	};
7501  }
7502
7503  var DomUtil = {
7504    __proto__: null,
7505    TRANSFORM: TRANSFORM,
7506    TRANSITION: TRANSITION,
7507    TRANSITION_END: TRANSITION_END,
7508    get: get,
7509    getStyle: getStyle,
7510    create: create$1,
7511    remove: remove,
7512    empty: empty,
7513    toFront: toFront,
7514    toBack: toBack,
7515    hasClass: hasClass,
7516    addClass: addClass,
7517    removeClass: removeClass,
7518    setClass: setClass,
7519    getClass: getClass,
7520    setOpacity: setOpacity,
7521    testProp: testProp,
7522    setTransform: setTransform,
7523    setPosition: setPosition,
7524    getPosition: getPosition,
7525    get disableTextSelection () { return disableTextSelection; },
7526    get enableTextSelection () { return enableTextSelection; },
7527    disableImageDrag: disableImageDrag,
7528    enableImageDrag: enableImageDrag,
7529    preventOutline: preventOutline,
7530    restoreOutline: restoreOutline,
7531    getSizedParentNode: getSizedParentNode,
7532    getScale: getScale
7533  };
7534
7535  /*
7536   * @namespace DomEvent
7537   * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
7538   */
7539
7540  // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
7541
7542  // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
7543  // Adds a listener function (`fn`) to a particular DOM event type of the
7544  // element `el`. You can optionally specify the context of the listener
7545  // (object the `this` keyword will point to). You can also pass several
7546  // space-separated types (e.g. `'click dblclick'`).
7547
7548  // @alternative
7549  // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
7550  // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
7551  function on(obj, types, fn, context) {
7552
7553  	if (types && typeof types === 'object') {
7554  		for (var type in types) {
7555  			addOne(obj, type, types[type], fn);
7556  		}
7557  	} else {
7558  		types = splitWords(types);
7559
7560  		for (var i = 0, len = types.length; i < len; i++) {
7561  			addOne(obj, types[i], fn, context);
7562  		}
7563  	}
7564
7565  	return this;
7566  }
7567
7568  var eventsKey = '_leaflet_events';
7569
7570  // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
7571  // Removes a previously added listener function.
7572  // Note that if you passed a custom context to on, you must pass the same
7573  // context to `off` in order to remove the listener.
7574
7575  // @alternative
7576  // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
7577  // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
7578
7579  // @alternative
7580  // @function off(el: HTMLElement, types: String): this
7581  // Removes all previously added listeners of given types.
7582
7583  // @alternative
7584  // @function off(el: HTMLElement): this
7585  // Removes all previously added listeners from given HTMLElement
7586  function off(obj, types, fn, context) {
7587
7588  	if (arguments.length === 1) {
7589  		batchRemove(obj);
7590  		delete obj[eventsKey];
7591
7592  	} else if (types && typeof types === 'object') {
7593  		for (var type in types) {
7594  			removeOne(obj, type, types[type], fn);
7595  		}
7596
7597  	} else {
7598  		types = splitWords(types);
7599
7600  		if (arguments.length === 2) {
7601  			batchRemove(obj, function (type) {
7602  				return indexOf(types, type) !== -1;
7603  			});
7604  		} else {
7605  			for (var i = 0, len = types.length; i < len; i++) {
7606  				removeOne(obj, types[i], fn, context);
7607  			}
7608  		}
7609  	}
7610
7611  	return this;
7612  }
7613
7614  function batchRemove(obj, filterFn) {
7615  	for (var id in obj[eventsKey]) {
7616  		var type = id.split(/\d/)[0];
7617  		if (!filterFn || filterFn(type)) {
7618  			removeOne(obj, type, null, null, id);
7619  		}
7620  	}
7621  }
7622
7623  var mouseSubst = {
7624  	mouseenter: 'mouseover',
7625  	mouseleave: 'mouseout',
7626  	wheel: !('onwheel' in window) && 'mousewheel'
7627  };
7628
7629  function addOne(obj, type, fn, context) {
7630  	var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
7631
7632  	if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
7633
7634  	var handler = function (e) {
7635  		return fn.call(context || obj, e || window.event);
7636  	};
7637
7638  	var originalHandler = handler;
7639
7640  	if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
7641  		// Needs DomEvent.Pointer.js
7642  		handler = addPointerListener(obj, type, handler);
7643
7644  	} else if (Browser.touch && (type === 'dblclick')) {
7645  		handler = addDoubleTapListener(obj, handler);
7646
7647  	} else if ('addEventListener' in obj) {
7648
7649  		if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' ||  type === 'mousewheel') {
7650  			obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
7651
7652  		} else if (type === 'mouseenter' || type === 'mouseleave') {
7653  			handler = function (e) {
7654  				e = e || window.event;
7655  				if (isExternalTarget(obj, e)) {
7656  					originalHandler(e);
7657  				}
7658  			};
7659  			obj.addEventListener(mouseSubst[type], handler, false);
7660
7661  		} else {
7662  			obj.addEventListener(type, originalHandler, false);
7663  		}
7664
7665  	} else {
7666  		obj.attachEvent('on' + type, handler);
7667  	}
7668
7669  	obj[eventsKey] = obj[eventsKey] || {};
7670  	obj[eventsKey][id] = handler;
7671  }
7672
7673  function removeOne(obj, type, fn, context, id) {
7674  	id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
7675  	var handler = obj[eventsKey] && obj[eventsKey][id];
7676
7677  	if (!handler) { return this; }
7678
7679  	if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
7680  		removePointerListener(obj, type, handler);
7681
7682  	} else if (Browser.touch && (type === 'dblclick')) {
7683  		removeDoubleTapListener(obj, handler);
7684
7685  	} else if ('removeEventListener' in obj) {
7686
7687  		obj.removeEventListener(mouseSubst[type] || type, handler, false);
7688
7689  	} else {
7690  		obj.detachEvent('on' + type, handler);
7691  	}
7692
7693  	obj[eventsKey][id] = null;
7694  }
7695
7696  // @function stopPropagation(ev: DOMEvent): this
7697  // Stop the given event from propagation to parent elements. Used inside the listener functions:
7698  // ```js
7699  // L.DomEvent.on(div, 'click', function (ev) {
7700  // 	L.DomEvent.stopPropagation(ev);
7701  // });
7702  // ```
7703  function stopPropagation(e) {
7704
7705  	if (e.stopPropagation) {
7706  		e.stopPropagation();
7707  	} else if (e.originalEvent) {  // In case of Leaflet event.
7708  		e.originalEvent._stopped = true;
7709  	} else {
7710  		e.cancelBubble = true;
7711  	}
7712
7713  	return this;
7714  }
7715
7716  // @function disableScrollPropagation(el: HTMLElement): this
7717  // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
7718  function disableScrollPropagation(el) {
7719  	addOne(el, 'wheel', stopPropagation);
7720  	return this;
7721  }
7722
7723  // @function disableClickPropagation(el: HTMLElement): this
7724  // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
7725  // `'mousedown'` and `'touchstart'` events (plus browser variants).
7726  function disableClickPropagation(el) {
7727  	on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
7728  	el['_leaflet_disable_click'] = true;
7729  	return this;
7730  }
7731
7732  // @function preventDefault(ev: DOMEvent): this
7733  // Prevents the default action of the DOM Event `ev` from happening (such as
7734  // following a link in the href of the a element, or doing a POST request
7735  // with page reload when a `<form>` is submitted).
7736  // Use it inside listener functions.
7737  function preventDefault(e) {
7738  	if (e.preventDefault) {
7739  		e.preventDefault();
7740  	} else {
7741  		e.returnValue = false;
7742  	}
7743  	return this;
7744  }
7745
7746  // @function stop(ev: DOMEvent): this
7747  // Does `stopPropagation` and `preventDefault` at the same time.
7748  function stop(e) {
7749  	preventDefault(e);
7750  	stopPropagation(e);
7751  	return this;
7752  }
7753
7754  // @function getPropagationPath(ev: DOMEvent): Array
7755  // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
7756  // Returns an array containing the `HTMLElement`s that the given DOM event
7757  // should propagate to (if not stopped).
7758  function getPropagationPath(ev) {
7759  	if (ev.composedPath) {
7760  		return ev.composedPath();
7761  	}
7762
7763  	var path = [];
7764  	var el = ev.target;
7765
7766  	while (el) {
7767  		path.push(el);
7768  		el = el.parentNode;
7769  	}
7770  	return path;
7771  }
7772
7773
7774  // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
7775  // Gets normalized mouse position from a DOM event relative to the
7776  // `container` (border excluded) or to the whole page if not specified.
7777  function getMousePosition(e, container) {
7778  	if (!container) {
7779  		return new Point(e.clientX, e.clientY);
7780  	}
7781
7782  	var scale = getScale(container),
7783  	    offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
7784
7785  	return new Point(
7786  		// offset.left/top values are in page scale (like clientX/Y),
7787  		// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
7788  		(e.clientX - offset.left) / scale.x - container.clientLeft,
7789  		(e.clientY - offset.top) / scale.y - container.clientTop
7790  	);
7791  }
7792
7793
7794  //  except , Safari and
7795  // We need double the scroll pixels (see #7403 and #4538) for all Browsers
7796  // except OSX (Mac) -> 3x, Chrome running on Linux 1x
7797
7798  var wheelPxFactor =
7799  	(Browser.linux && Browser.chrome) ? window.devicePixelRatio :
7800  	Browser.mac ? window.devicePixelRatio * 3 :
7801  	window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
7802  // @function getWheelDelta(ev: DOMEvent): Number
7803  // Gets normalized wheel delta from a wheel DOM event, in vertical
7804  // pixels scrolled (negative if scrolling down).
7805  // Events from pointing devices without precise scrolling are mapped to
7806  // a best guess of 60 pixels.
7807  function getWheelDelta(e) {
7808  	return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
7809  	       (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
7810  	       (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
7811  	       (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
7812  	       (e.deltaX || e.deltaZ) ? 0 :	// Skip horizontal/depth wheel events
7813  	       e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
7814  	       (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
7815  	       e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
7816  	       0;
7817  }
7818
7819  // check if element really left/entered the event target (for mouseenter/mouseleave)
7820  function isExternalTarget(el, e) {
7821
7822  	var related = e.relatedTarget;
7823
7824  	if (!related) { return true; }
7825
7826  	try {
7827  		while (related && (related !== el)) {
7828  			related = related.parentNode;
7829  		}
7830  	} catch (err) {
7831  		return false;
7832  	}
7833  	return (related !== el);
7834  }
7835
7836  var DomEvent = {
7837    __proto__: null,
7838    on: on,
7839    off: off,
7840    stopPropagation: stopPropagation,
7841    disableScrollPropagation: disableScrollPropagation,
7842    disableClickPropagation: disableClickPropagation,
7843    preventDefault: preventDefault,
7844    stop: stop,
7845    getPropagationPath: getPropagationPath,
7846    getMousePosition: getMousePosition,
7847    getWheelDelta: getWheelDelta,
7848    isExternalTarget: isExternalTarget,
7849    addListener: on,
7850    removeListener: off
7851  };
7852
7853  /*
7854   * @class PosAnimation
7855   * @aka L.PosAnimation
7856   * @inherits Evented
7857   * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
7858   *
7859   * @example
7860   * ```js
7861   * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
7862   *
7863   * myPositionMarker.on("click", function() {
7864   * 	var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
7865   * 	pos.y -= 25;
7866   * 	var fx = new L.PosAnimation();
7867   *
7868   * 	fx.once('end',function() {
7869   * 		pos.y += 25;
7870   * 		fx.run(myPositionMarker._icon, pos, 0.8);
7871   * 	});
7872   *
7873   * 	fx.run(myPositionMarker._icon, pos, 0.3);
7874   * });
7875   *
7876   * ```
7877   *
7878   * @constructor L.PosAnimation()
7879   * Creates a `PosAnimation` object.
7880   *
7881   */
7882
7883  var PosAnimation = Evented.extend({
7884
7885  	// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
7886  	// Run an animation of a given element to a new position, optionally setting
7887  	// duration in seconds (`0.25` by default) and easing linearity factor (3rd
7888  	// argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
7889  	// `0.5` by default).
7890  	run: function (el, newPos, duration, easeLinearity) {
7891  		this.stop();
7892
7893  		this._el = el;
7894  		this._inProgress = true;
7895  		this._duration = duration || 0.25;
7896  		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
7897
7898  		this._startPos = getPosition(el);
7899  		this._offset = newPos.subtract(this._startPos);
7900  		this._startTime = +new Date();
7901
7902  		// @event start: Event
7903  		// Fired when the animation starts
7904  		this.fire('start');
7905
7906  		this._animate();
7907  	},
7908
7909  	// @method stop()
7910  	// Stops the animation (if currently running).
7911  	stop: function () {
7912  		if (!this._inProgress) { return; }
7913
7914  		this._step(true);
7915  		this._complete();
7916  	},
7917
7918  	_animate: function () {
7919  		// animation loop
7920  		this._animId = requestAnimFrame(this._animate, this);
7921  		this._step();
7922  	},
7923
7924  	_step: function (round) {
7925  		var elapsed = (+new Date()) - this._startTime,
7926  		    duration = this._duration * 1000;
7927
7928  		if (elapsed < duration) {
7929  			this._runFrame(this._easeOut(elapsed / duration), round);
7930  		} else {
7931  			this._runFrame(1);
7932  			this._complete();
7933  		}
7934  	},
7935
7936  	_runFrame: function (progress, round) {
7937  		var pos = this._startPos.add(this._offset.multiplyBy(progress));
7938  		if (round) {
7939  			pos._round();
7940  		}
7941  		setPosition(this._el, pos);
7942
7943  		// @event step: Event
7944  		// Fired continuously during the animation.
7945  		this.fire('step');
7946  	},
7947
7948  	_complete: function () {
7949  		cancelAnimFrame(this._animId);
7950
7951  		this._inProgress = false;
7952  		// @event end: Event
7953  		// Fired when the animation ends.
7954  		this.fire('end');
7955  	},
7956
7957  	_easeOut: function (t) {
7958  		return 1 - Math.pow(1 - t, this._easeOutPower);
7959  	}
7960  });
7961
7962  /*
7963   * @class Map
7964   * @aka L.Map
7965   * @inherits Evented
7966   *
7967   * The central class of the API — it is used to create a map on a page and manipulate it.
7968   *
7969   * @example
7970   *
7971   * ```js
7972   * // initialize the map on the "map" div with a given center and zoom
7973   * var map = L.map('map', {
7974   * 	center: [51.505, -0.09],
7975   * 	zoom: 13
7976   * });
7977   * ```
7978   *
7979   */
7980
7981  var Map = Evented.extend({
7982
7983  	options: {
7984  		// @section Map State Options
7985  		// @option crs: CRS = L.CRS.EPSG3857
7986  		// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
7987  		// sure what it means.
7988  		crs: EPSG3857,
7989
7990  		// @option center: LatLng = undefined
7991  		// Initial geographic center of the map
7992  		center: undefined,
7993
7994  		// @option zoom: Number = undefined
7995  		// Initial map zoom level
7996  		zoom: undefined,
7997
7998  		// @option minZoom: Number = *
7999  		// Minimum zoom level of the map.
8000  		// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
8001  		// the lowest of their `minZoom` options will be used instead.
8002  		minZoom: undefined,
8003
8004  		// @option maxZoom: Number = *
8005  		// Maximum zoom level of the map.
8006  		// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
8007  		// the highest of their `maxZoom` options will be used instead.
8008  		maxZoom: undefined,
8009
8010  		// @option layers: Layer[] = []
8011  		// Array of layers that will be added to the map initially
8012  		layers: [],
8013
8014  		// @option maxBounds: LatLngBounds = null
8015  		// When this option is set, the map restricts the view to the given
8016  		// geographical bounds, bouncing the user back if the user tries to pan
8017  		// outside the view. To set the restriction dynamically, use
8018  		// [`setMaxBounds`](#map-setmaxbounds) method.
8019  		maxBounds: undefined,
8020
8021  		// @option renderer: Renderer = *
8022  		// The default method for drawing vector layers on the map. `L.SVG`
8023  		// or `L.Canvas` by default depending on browser support.
8024  		renderer: undefined,
8025
8026
8027  		// @section Animation Options
8028  		// @option zoomAnimation: Boolean = true
8029  		// Whether the map zoom animation is enabled. By default it's enabled
8030  		// in all browsers that support CSS3 Transitions except Android.
8031  		zoomAnimation: true,
8032
8033  		// @option zoomAnimationThreshold: Number = 4
8034  		// Won't animate zoom if the zoom difference exceeds this value.
8035  		zoomAnimationThreshold: 4,
8036
8037  		// @option fadeAnimation: Boolean = true
8038  		// Whether the tile fade animation is enabled. By default it's enabled
8039  		// in all browsers that support CSS3 Transitions except Android.
8040  		fadeAnimation: true,
8041
8042  		// @option markerZoomAnimation: Boolean = true
8043  		// Whether markers animate their zoom with the zoom animation, if disabled
8044  		// they will disappear for the length of the animation. By default it's
8045  		// enabled in all browsers that support CSS3 Transitions except Android.
8046  		markerZoomAnimation: true,
8047
8048  		// @option transform3DLimit: Number = 2^23
8049  		// Defines the maximum size of a CSS translation transform. The default
8050  		// value should not be changed unless a web browser positions layers in
8051  		// the wrong place after doing a large `panBy`.
8052  		transform3DLimit: 8388608, // Precision limit of a 32-bit float
8053
8054  		// @section Interaction Options
8055  		// @option zoomSnap: Number = 1
8056  		// Forces the map's zoom level to always be a multiple of this, particularly
8057  		// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
8058  		// By default, the zoom level snaps to the nearest integer; lower values
8059  		// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
8060  		// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
8061  		zoomSnap: 1,
8062
8063  		// @option zoomDelta: Number = 1
8064  		// Controls how much the map's zoom level will change after a
8065  		// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
8066  		// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
8067  		// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
8068  		zoomDelta: 1,
8069
8070  		// @option trackResize: Boolean = true
8071  		// Whether the map automatically handles browser window resize to update itself.
8072  		trackResize: true
8073  	},
8074
8075  	initialize: function (id, options) { // (HTMLElement or String, Object)
8076  		options = setOptions(this, options);
8077
8078  		// Make sure to assign internal flags at the beginning,
8079  		// to avoid inconsistent state in some edge cases.
8080  		this._handlers = [];
8081  		this._layers = {};
8082  		this._zoomBoundLayers = {};
8083  		this._sizeChanged = true;
8084
8085  		this._initContainer(id);
8086  		this._initLayout();
8087
8088  		// hack for https://github.com/Leaflet/Leaflet/issues/1980
8089  		this._onResize = bind(this._onResize, this);
8090
8091  		this._initEvents();
8092
8093  		if (options.maxBounds) {
8094  			this.setMaxBounds(options.maxBounds);
8095  		}
8096
8097  		if (options.zoom !== undefined) {
8098  			this._zoom = this._limitZoom(options.zoom);
8099  		}
8100
8101  		if (options.center && options.zoom !== undefined) {
8102  			this.setView(toLatLng(options.center), options.zoom, {reset: true});
8103  		}
8104
8105  		this.callInitHooks();
8106
8107  		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
8108  		this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
8109  				this.options.zoomAnimation;
8110
8111  		// zoom transitions run with the same duration for all layers, so if one of transitionend events
8112  		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
8113  		if (this._zoomAnimated) {
8114  			this._createAnimProxy();
8115  			on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
8116  		}
8117
8118  		this._addLayers(this.options.layers);
8119  	},
8120
8121
8122  	// @section Methods for modifying map state
8123
8124  	// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
8125  	// Sets the view of the map (geographical center and zoom) with the given
8126  	// animation options.
8127  	setView: function (center, zoom, options) {
8128
8129  		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
8130  		center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
8131  		options = options || {};
8132
8133  		this._stop();
8134
8135  		if (this._loaded && !options.reset && options !== true) {
8136
8137  			if (options.animate !== undefined) {
8138  				options.zoom = extend({animate: options.animate}, options.zoom);
8139  				options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
8140  			}
8141
8142  			// try animating pan or zoom
8143  			var moved = (this._zoom !== zoom) ?
8144  				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
8145  				this._tryAnimatedPan(center, options.pan);
8146
8147  			if (moved) {
8148  				// prevent resize handler call, the view will refresh after animation anyway
8149  				clearTimeout(this._sizeTimer);
8150  				return this;
8151  			}
8152  		}
8153
8154  		// animation didn't start, just reset the map view
8155  		this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
8156
8157  		return this;
8158  	},
8159
8160  	// @method setZoom(zoom: Number, options?: Zoom/pan options): this
8161  	// Sets the zoom of the map.
8162  	setZoom: function (zoom, options) {
8163  		if (!this._loaded) {
8164  			this._zoom = zoom;
8165  			return this;
8166  		}
8167  		return this.setView(this.getCenter(), zoom, {zoom: options});
8168  	},
8169
8170  	// @method zoomIn(delta?: Number, options?: Zoom options): this
8171  	// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
8172  	zoomIn: function (delta, options) {
8173  		delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
8174  		return this.setZoom(this._zoom + delta, options);
8175  	},
8176
8177  	// @method zoomOut(delta?: Number, options?: Zoom options): this
8178  	// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
8179  	zoomOut: function (delta, options) {
8180  		delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
8181  		return this.setZoom(this._zoom - delta, options);
8182  	},
8183
8184  	// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
8185  	// Zooms the map while keeping a specified geographical point on the map
8186  	// stationary (e.g. used internally for scroll zoom and double-click zoom).
8187  	// @alternative
8188  	// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
8189  	// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
8190  	setZoomAround: function (latlng, zoom, options) {
8191  		var scale = this.getZoomScale(zoom),
8192  		    viewHalf = this.getSize().divideBy(2),
8193  		    containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
8194
8195  		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
8196  		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
8197
8198  		return this.setView(newCenter, zoom, {zoom: options});
8199  	},
8200
8201  	_getBoundsCenterZoom: function (bounds, options) {
8202
8203  		options = options || {};
8204  		bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
8205
8206  		var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
8207  		    paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
8208
8209  		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
8210
8211  		zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
8212
8213  		if (zoom === Infinity) {
8214  			return {
8215  				center: bounds.getCenter(),
8216  				zoom: zoom
8217  			};
8218  		}
8219
8220  		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
8221
8222  		    swPoint = this.project(bounds.getSouthWest(), zoom),
8223  		    nePoint = this.project(bounds.getNorthEast(), zoom),
8224  		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
8225
8226  		return {
8227  			center: center,
8228  			zoom: zoom
8229  		};
8230  	},
8231
8232  	// @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
8233  	// Sets a map view that contains the given geographical bounds with the
8234  	// maximum zoom level possible.
8235  	fitBounds: function (bounds, options) {
8236
8237  		bounds = toLatLngBounds(bounds);
8238
8239  		if (!bounds.isValid()) {
8240  			throw new Error('Bounds are not valid.');
8241  		}
8242
8243  		var target = this._getBoundsCenterZoom(bounds, options);
8244  		return this.setView(target.center, target.zoom, options);
8245  	},
8246
8247  	// @method fitWorld(options?: fitBounds options): this
8248  	// Sets a map view that mostly contains the whole world with the maximum
8249  	// zoom level possible.
8250  	fitWorld: function (options) {
8251  		return this.fitBounds([[-90, -180], [90, 180]], options);
8252  	},
8253
8254  	// @method panTo(latlng: LatLng, options?: Pan options): this
8255  	// Pans the map to a given center.
8256  	panTo: function (center, options) { // (LatLng)
8257  		return this.setView(center, this._zoom, {pan: options});
8258  	},
8259
8260  	// @method panBy(offset: Point, options?: Pan options): this
8261  	// Pans the map by a given number of pixels (animated).
8262  	panBy: function (offset, options) {
8263  		offset = toPoint(offset).round();
8264  		options = options || {};
8265
8266  		if (!offset.x && !offset.y) {
8267  			return this.fire('moveend');
8268  		}
8269  		// If we pan too far, Chrome gets issues with tiles
8270  		// and makes them disappear or appear in the wrong place (slightly offset) #2602
8271  		if (options.animate !== true && !this.getSize().contains(offset)) {
8272  			this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
8273  			return this;
8274  		}
8275
8276  		if (!this._panAnim) {
8277  			this._panAnim = new PosAnimation();
8278
8279  			this._panAnim.on({
8280  				'step': this._onPanTransitionStep,
8281  				'end': this._onPanTransitionEnd
8282  			}, this);
8283  		}
8284
8285  		// don't fire movestart if animating inertia
8286  		if (!options.noMoveStart) {
8287  			this.fire('movestart');
8288  		}
8289
8290  		// animate pan unless animate: false specified
8291  		if (options.animate !== false) {
8292  			addClass(this._mapPane, 'leaflet-pan-anim');
8293
8294  			var newPos = this._getMapPanePos().subtract(offset).round();
8295  			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
8296  		} else {
8297  			this._rawPanBy(offset);
8298  			this.fire('move').fire('moveend');
8299  		}
8300
8301  		return this;
8302  	},
8303
8304  	// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
8305  	// Sets the view of the map (geographical center and zoom) performing a smooth
8306  	// pan-zoom animation.
8307  	flyTo: function (targetCenter, targetZoom, options) {
8308
8309  		options = options || {};
8310  		if (options.animate === false || !Browser.any3d) {
8311  			return this.setView(targetCenter, targetZoom, options);
8312  		}
8313
8314  		this._stop();
8315
8316  		var from = this.project(this.getCenter()),
8317  		    to = this.project(targetCenter),
8318  		    size = this.getSize(),
8319  		    startZoom = this._zoom;
8320
8321  		targetCenter = toLatLng(targetCenter);
8322  		targetZoom = targetZoom === undefined ? startZoom : targetZoom;
8323
8324  		var w0 = Math.max(size.x, size.y),
8325  		    w1 = w0 * this.getZoomScale(startZoom, targetZoom),
8326  		    u1 = (to.distanceTo(from)) || 1,
8327  		    rho = 1.42,
8328  		    rho2 = rho * rho;
8329
8330  		function r(i) {
8331  			var s1 = i ? -1 : 1,
8332  			    s2 = i ? w1 : w0,
8333  			    t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
8334  			    b1 = 2 * s2 * rho2 * u1,
8335  			    b = t1 / b1,
8336  			    sq = Math.sqrt(b * b + 1) - b;
8337
8338  			    // workaround for floating point precision bug when sq = 0, log = -Infinite,
8339  			    // thus triggering an infinite loop in flyTo
8340  			    var log = sq < 0.000000001 ? -18 : Math.log(sq);
8341
8342  			return log;
8343  		}
8344
8345  		function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
8346  		function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
8347  		function tanh(n) { return sinh(n) / cosh(n); }
8348
8349  		var r0 = r(0);
8350
8351  		function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
8352  		function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
8353
8354  		function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
8355
8356  		var start = Date.now(),
8357  		    S = (r(1) - r0) / rho,
8358  		    duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
8359
8360  		function frame() {
8361  			var t = (Date.now() - start) / duration,
8362  			    s = easeOut(t) * S;
8363
8364  			if (t <= 1) {
8365  				this._flyToFrame = requestAnimFrame(frame, this);
8366
8367  				this._move(
8368  					this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
8369  					this.getScaleZoom(w0 / w(s), startZoom),
8370  					{flyTo: true});
8371
8372  			} else {
8373  				this
8374  					._move(targetCenter, targetZoom)
8375  					._moveEnd(true);
8376  			}
8377  		}
8378
8379  		this._moveStart(true, options.noMoveStart);
8380
8381  		frame.call(this);
8382  		return this;
8383  	},
8384
8385  	// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
8386  	// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
8387  	// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
8388  	flyToBounds: function (bounds, options) {
8389  		var target = this._getBoundsCenterZoom(bounds, options);
8390  		return this.flyTo(target.center, target.zoom, options);
8391  	},
8392
8393  	// @method setMaxBounds(bounds: LatLngBounds): this
8394  	// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
8395  	setMaxBounds: function (bounds) {
8396  		bounds = toLatLngBounds(bounds);
8397
8398  		if (this.listens('moveend', this._panInsideMaxBounds)) {
8399  			this.off('moveend', this._panInsideMaxBounds);
8400  		}
8401
8402  		if (!bounds.isValid()) {
8403  			this.options.maxBounds = null;
8404  			return this;
8405  		}
8406
8407  		this.options.maxBounds = bounds;
8408
8409  		if (this._loaded) {
8410  			this._panInsideMaxBounds();
8411  		}
8412
8413  		return this.on('moveend', this._panInsideMaxBounds);
8414  	},
8415
8416  	// @method setMinZoom(zoom: Number): this
8417  	// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
8418  	setMinZoom: function (zoom) {
8419  		var oldZoom = this.options.minZoom;
8420  		this.options.minZoom = zoom;
8421
8422  		if (this._loaded && oldZoom !== zoom) {
8423  			this.fire('zoomlevelschange');
8424
8425  			if (this.getZoom() < this.options.minZoom) {
8426  				return this.setZoom(zoom);
8427  			}
8428  		}
8429
8430  		return this;
8431  	},
8432
8433  	// @method setMaxZoom(zoom: Number): this
8434  	// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
8435  	setMaxZoom: function (zoom) {
8436  		var oldZoom = this.options.maxZoom;
8437  		this.options.maxZoom = zoom;
8438
8439  		if (this._loaded && oldZoom !== zoom) {
8440  			this.fire('zoomlevelschange');
8441
8442  			if (this.getZoom() > this.options.maxZoom) {
8443  				return this.setZoom(zoom);
8444  			}
8445  		}
8446
8447  		return this;
8448  	},
8449
8450  	// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
8451  	// Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
8452  	panInsideBounds: function (bounds, options) {
8453  		this._enforcingBounds = true;
8454  		var center = this.getCenter(),
8455  		    newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
8456
8457  		if (!center.equals(newCenter)) {
8458  			this.panTo(newCenter, options);
8459  		}
8460
8461  		this._enforcingBounds = false;
8462  		return this;
8463  	},
8464
8465  	// @method panInside(latlng: LatLng, options?: padding options): this
8466  	// Pans the map the minimum amount to make the `latlng` visible. Use
8467  	// padding options to fit the display to more restricted bounds.
8468  	// If `latlng` is already within the (optionally padded) display bounds,
8469  	// the map will not be panned.
8470  	panInside: function (latlng, options) {
8471  		options = options || {};
8472
8473  		var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
8474  		    paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
8475  		    pixelCenter = this.project(this.getCenter()),
8476  		    pixelPoint = this.project(latlng),
8477  		    pixelBounds = this.getPixelBounds(),
8478  		    paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
8479  		    paddedSize = paddedBounds.getSize();
8480
8481  		if (!paddedBounds.contains(pixelPoint)) {
8482  			this._enforcingBounds = true;
8483  			var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
8484  			var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
8485  			pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
8486  			pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
8487  			this.panTo(this.unproject(pixelCenter), options);
8488  			this._enforcingBounds = false;
8489  		}
8490  		return this;
8491  	},
8492
8493  	// @method invalidateSize(options: Zoom/pan options): this
8494  	// Checks if the map container size changed and updates the map if so —
8495  	// call it after you've changed the map size dynamically, also animating
8496  	// pan by default. If `options.pan` is `false`, panning will not occur.
8497  	// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
8498  	// that it doesn't happen often even if the method is called many
8499  	// times in a row.
8500
8501  	// @alternative
8502  	// @method invalidateSize(animate: Boolean): this
8503  	// Checks if the map container size changed and updates the map if so —
8504  	// call it after you've changed the map size dynamically, also animating
8505  	// pan by default.
8506  	invalidateSize: function (options) {
8507  		if (!this._loaded) { return this; }
8508
8509  		options = extend({
8510  			animate: false,
8511  			pan: true
8512  		}, options === true ? {animate: true} : options);
8513
8514  		var oldSize = this.getSize();
8515  		this._sizeChanged = true;
8516  		this._lastCenter = null;
8517
8518  		var newSize = this.getSize(),
8519  		    oldCenter = oldSize.divideBy(2).round(),
8520  		    newCenter = newSize.divideBy(2).round(),
8521  		    offset = oldCenter.subtract(newCenter);
8522
8523  		if (!offset.x && !offset.y) { return this; }
8524
8525  		if (options.animate && options.pan) {
8526  			this.panBy(offset);
8527
8528  		} else {
8529  			if (options.pan) {
8530  				this._rawPanBy(offset);
8531  			}
8532
8533  			this.fire('move');
8534
8535  			if (options.debounceMoveend) {
8536  				clearTimeout(this._sizeTimer);
8537  				this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
8538  			} else {
8539  				this.fire('moveend');
8540  			}
8541  		}
8542
8543  		// @section Map state change events
8544  		// @event resize: ResizeEvent
8545  		// Fired when the map is resized.
8546  		return this.fire('resize', {
8547  			oldSize: oldSize,
8548  			newSize: newSize
8549  		});
8550  	},
8551
8552  	// @section Methods for modifying map state
8553  	// @method stop(): this
8554  	// Stops the currently running `panTo` or `flyTo` animation, if any.
8555  	stop: function () {
8556  		this.setZoom(this._limitZoom(this._zoom));
8557  		if (!this.options.zoomSnap) {
8558  			this.fire('viewreset');
8559  		}
8560  		return this._stop();
8561  	},
8562
8563  	// @section Geolocation methods
8564  	// @method locate(options?: Locate options): this
8565  	// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
8566  	// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
8567  	// and optionally sets the map view to the user's location with respect to
8568  	// detection accuracy (or to the world view if geolocation failed).
8569  	// Note that, if your page doesn't use HTTPS, this method will fail in
8570  	// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
8571  	// See `Locate options` for more details.
8572  	locate: function (options) {
8573
8574  		options = this._locateOptions = extend({
8575  			timeout: 10000,
8576  			watch: false
8577  			// setView: false
8578  			// maxZoom: <Number>
8579  			// maximumAge: 0
8580  			// enableHighAccuracy: false
8581  		}, options);
8582
8583  		if (!('geolocation' in navigator)) {
8584  			this._handleGeolocationError({
8585  				code: 0,
8586  				message: 'Geolocation not supported.'
8587  			});
8588  			return this;
8589  		}
8590
8591  		var onResponse = bind(this._handleGeolocationResponse, this),
8592  		    onError = bind(this._handleGeolocationError, this);
8593
8594  		if (options.watch) {
8595  			this._locationWatchId =
8596  			        navigator.geolocation.watchPosition(onResponse, onError, options);
8597  		} else {
8598  			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
8599  		}
8600  		return this;
8601  	},
8602
8603  	// @method stopLocate(): this
8604  	// Stops watching location previously initiated by `map.locate({watch: true})`
8605  	// and aborts resetting the map view if map.locate was called with
8606  	// `{setView: true}`.
8607  	stopLocate: function () {
8608  		if (navigator.geolocation && navigator.geolocation.clearWatch) {
8609  			navigator.geolocation.clearWatch(this._locationWatchId);
8610  		}
8611  		if (this._locateOptions) {
8612  			this._locateOptions.setView = false;
8613  		}
8614  		return this;
8615  	},
8616
8617  	_handleGeolocationError: function (error) {
8618  		if (!this._container._leaflet_id) { return; }
8619
8620  		var c = error.code,
8621  		    message = error.message ||
8622  		            (c === 1 ? 'permission denied' :
8623  		            (c === 2 ? 'position unavailable' : 'timeout'));
8624
8625  		if (this._locateOptions.setView && !this._loaded) {
8626  			this.fitWorld();
8627  		}
8628
8629  		// @section Location events
8630  		// @event locationerror: ErrorEvent
8631  		// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
8632  		this.fire('locationerror', {
8633  			code: c,
8634  			message: 'Geolocation error: ' + message + '.'
8635  		});
8636  	},
8637
8638  	_handleGeolocationResponse: function (pos) {
8639  		if (!this._container._leaflet_id) { return; }
8640
8641  		var lat = pos.coords.latitude,
8642  		    lng = pos.coords.longitude,
8643  		    latlng = new LatLng(lat, lng),
8644  		    bounds = latlng.toBounds(pos.coords.accuracy * 2),
8645  		    options = this._locateOptions;
8646
8647  		if (options.setView) {
8648  			var zoom = this.getBoundsZoom(bounds);
8649  			this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
8650  		}
8651
8652  		var data = {
8653  			latlng: latlng,
8654  			bounds: bounds,
8655  			timestamp: pos.timestamp
8656  		};
8657
8658  		for (var i in pos.coords) {
8659  			if (typeof pos.coords[i] === 'number') {
8660  				data[i] = pos.coords[i];
8661  			}
8662  		}
8663
8664  		// @event locationfound: LocationEvent
8665  		// Fired when geolocation (using the [`locate`](#map-locate) method)
8666  		// went successfully.
8667  		this.fire('locationfound', data);
8668  	},
8669
8670  	// TODO Appropriate docs section?
8671  	// @section Other Methods
8672  	// @method addHandler(name: String, HandlerClass: Function): this
8673  	// Adds a new `Handler` to the map, given its name and constructor function.
8674  	addHandler: function (name, HandlerClass) {
8675  		if (!HandlerClass) { return this; }
8676
8677  		var handler = this[name] = new HandlerClass(this);
8678
8679  		this._handlers.push(handler);
8680
8681  		if (this.options[name]) {
8682  			handler.enable();
8683  		}
8684
8685  		return this;
8686  	},
8687
8688  	// @method remove(): this
8689  	// Destroys the map and clears all related event listeners.
8690  	remove: function () {
8691
8692  		this._initEvents(true);
8693  		if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
8694
8695  		if (this._containerId !== this._container._leaflet_id) {
8696  			throw new Error('Map container is being reused by another instance');
8697  		}
8698
8699  		try {
8700  			// throws error in IE6-8
8701  			delete this._container._leaflet_id;
8702  			delete this._containerId;
8703  		} catch (e) {
8704  			/*eslint-disable */
8705  			this._container._leaflet_id = undefined;
8706  			/* eslint-enable */
8707  			this._containerId = undefined;
8708  		}
8709
8710  		if (this._locationWatchId !== undefined) {
8711  			this.stopLocate();
8712  		}
8713
8714  		this._stop();
8715
8716  		remove(this._mapPane);
8717
8718  		if (this._clearControlPos) {
8719  			this._clearControlPos();
8720  		}
8721  		if (this._resizeRequest) {
8722  			cancelAnimFrame(this._resizeRequest);
8723  			this._resizeRequest = null;
8724  		}
8725
8726  		this._clearHandlers();
8727
8728  		if (this._loaded) {
8729  			// @section Map state change events
8730  			// @event unload: Event
8731  			// Fired when the map is destroyed with [remove](#map-remove) method.
8732  			this.fire('unload');
8733  		}
8734
8735  		var i;
8736  		for (i in this._layers) {
8737  			this._layers[i].remove();
8738  		}
8739  		for (i in this._panes) {
8740  			remove(this._panes[i]);
8741  		}
8742
8743  		this._layers = [];
8744  		this._panes = [];
8745  		delete this._mapPane;
8746  		delete this._renderer;
8747
8748  		return this;
8749  	},
8750
8751  	// @section Other Methods
8752  	// @method createPane(name: String, container?: HTMLElement): HTMLElement
8753  	// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
8754  	// then returns it. The pane is created as a child of `container`, or
8755  	// as a child of the main map pane if not set.
8756  	createPane: function (name, container) {
8757  		var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
8758  		    pane = create$1('div', className, container || this._mapPane);
8759
8760  		if (name) {
8761  			this._panes[name] = pane;
8762  		}
8763  		return pane;
8764  	},
8765
8766  	// @section Methods for Getting Map State
8767
8768  	// @method getCenter(): LatLng
8769  	// Returns the geographical center of the map view
8770  	getCenter: function () {
8771  		this._checkIfLoaded();
8772
8773  		if (this._lastCenter && !this._moved()) {
8774  			return this._lastCenter.clone();
8775  		}
8776  		return this.layerPointToLatLng(this._getCenterLayerPoint());
8777  	},
8778
8779  	// @method getZoom(): Number
8780  	// Returns the current zoom level of the map view
8781  	getZoom: function () {
8782  		return this._zoom;
8783  	},
8784
8785  	// @method getBounds(): LatLngBounds
8786  	// Returns the geographical bounds visible in the current map view
8787  	getBounds: function () {
8788  		var bounds = this.getPixelBounds(),
8789  		    sw = this.unproject(bounds.getBottomLeft()),
8790  		    ne = this.unproject(bounds.getTopRight());
8791
8792  		return new LatLngBounds(sw, ne);
8793  	},
8794
8795  	// @method getMinZoom(): Number
8796  	// Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
8797  	getMinZoom: function () {
8798  		return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
8799  	},
8800
8801  	// @method getMaxZoom(): Number
8802  	// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
8803  	getMaxZoom: function () {
8804  		return this.options.maxZoom === undefined ?
8805  			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
8806  			this.options.maxZoom;
8807  	},
8808
8809  	// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
8810  	// Returns the maximum zoom level on which the given bounds fit to the map
8811  	// view in its entirety. If `inside` (optional) is set to `true`, the method
8812  	// instead returns the minimum zoom level on which the map view fits into
8813  	// the given bounds in its entirety.
8814  	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
8815  		bounds = toLatLngBounds(bounds);
8816  		padding = toPoint(padding || [0, 0]);
8817
8818  		var zoom = this.getZoom() || 0,
8819  		    min = this.getMinZoom(),
8820  		    max = this.getMaxZoom(),
8821  		    nw = bounds.getNorthWest(),
8822  		    se = bounds.getSouthEast(),
8823  		    size = this.getSize().subtract(padding),
8824  		    boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
8825  		    snap = Browser.any3d ? this.options.zoomSnap : 1,
8826  		    scalex = size.x / boundsSize.x,
8827  		    scaley = size.y / boundsSize.y,
8828  		    scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
8829
8830  		zoom = this.getScaleZoom(scale, zoom);
8831
8832  		if (snap) {
8833  			zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
8834  			zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
8835  		}
8836
8837  		return Math.max(min, Math.min(max, zoom));
8838  	},
8839
8840  	// @method getSize(): Point
8841  	// Returns the current size of the map container (in pixels).
8842  	getSize: function () {
8843  		if (!this._size || this._sizeChanged) {
8844  			this._size = new Point(
8845  				this._container.clientWidth || 0,
8846  				this._container.clientHeight || 0);
8847
8848  			this._sizeChanged = false;
8849  		}
8850  		return this._size.clone();
8851  	},
8852
8853  	// @method getPixelBounds(): Bounds
8854  	// Returns the bounds of the current map view in projected pixel
8855  	// coordinates (sometimes useful in layer and overlay implementations).
8856  	getPixelBounds: function (center, zoom) {
8857  		var topLeftPoint = this._getTopLeftPoint(center, zoom);
8858  		return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
8859  	},
8860
8861  	// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
8862  	// the map pane? "left point of the map layer" can be confusing, specially
8863  	// since there can be negative offsets.
8864  	// @method getPixelOrigin(): Point
8865  	// Returns the projected pixel coordinates of the top left point of
8866  	// the map layer (useful in custom layer and overlay implementations).
8867  	getPixelOrigin: function () {
8868  		this._checkIfLoaded();
8869  		return this._pixelOrigin;
8870  	},
8871
8872  	// @method getPixelWorldBounds(zoom?: Number): Bounds
8873  	// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
8874  	// If `zoom` is omitted, the map's current zoom level is used.
8875  	getPixelWorldBounds: function (zoom) {
8876  		return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
8877  	},
8878
8879  	// @section Other Methods
8880
8881  	// @method getPane(pane: String|HTMLElement): HTMLElement
8882  	// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
8883  	getPane: function (pane) {
8884  		return typeof pane === 'string' ? this._panes[pane] : pane;
8885  	},
8886
8887  	// @method getPanes(): Object
8888  	// Returns a plain object containing the names of all [panes](#map-pane) as keys and
8889  	// the panes as values.
8890  	getPanes: function () {
8891  		return this._panes;
8892  	},
8893
8894  	// @method getContainer: HTMLElement
8895  	// Returns the HTML element that contains the map.
8896  	getContainer: function () {
8897  		return this._container;
8898  	},
8899
8900
8901  	// @section Conversion Methods
8902
8903  	// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
8904  	// Returns the scale factor to be applied to a map transition from zoom level
8905  	// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
8906  	getZoomScale: function (toZoom, fromZoom) {
8907  		// TODO replace with universal implementation after refactoring projections
8908  		var crs = this.options.crs;
8909  		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
8910  		return crs.scale(toZoom) / crs.scale(fromZoom);
8911  	},
8912
8913  	// @method getScaleZoom(scale: Number, fromZoom: Number): Number
8914  	// Returns the zoom level that the map would end up at, if it is at `fromZoom`
8915  	// level and everything is scaled by a factor of `scale`. Inverse of
8916  	// [`getZoomScale`](#map-getZoomScale).
8917  	getScaleZoom: function (scale, fromZoom) {
8918  		var crs = this.options.crs;
8919  		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
8920  		var zoom = crs.zoom(scale * crs.scale(fromZoom));
8921  		return isNaN(zoom) ? Infinity : zoom;
8922  	},
8923
8924  	// @method project(latlng: LatLng, zoom: Number): Point
8925  	// Projects a geographical coordinate `LatLng` according to the projection
8926  	// of the map's CRS, then scales it according to `zoom` and the CRS's
8927  	// `Transformation`. The result is pixel coordinate relative to
8928  	// the CRS origin.
8929  	project: function (latlng, zoom) {
8930  		zoom = zoom === undefined ? this._zoom : zoom;
8931  		return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
8932  	},
8933
8934  	// @method unproject(point: Point, zoom: Number): LatLng
8935  	// Inverse of [`project`](#map-project).
8936  	unproject: function (point, zoom) {
8937  		zoom = zoom === undefined ? this._zoom : zoom;
8938  		return this.options.crs.pointToLatLng(toPoint(point), zoom);
8939  	},
8940
8941  	// @method layerPointToLatLng(point: Point): LatLng
8942  	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
8943  	// returns the corresponding geographical coordinate (for the current zoom level).
8944  	layerPointToLatLng: function (point) {
8945  		var projectedPoint = toPoint(point).add(this.getPixelOrigin());
8946  		return this.unproject(projectedPoint);
8947  	},
8948
8949  	// @method latLngToLayerPoint(latlng: LatLng): Point
8950  	// Given a geographical coordinate, returns the corresponding pixel coordinate
8951  	// relative to the [origin pixel](#map-getpixelorigin).
8952  	latLngToLayerPoint: function (latlng) {
8953  		var projectedPoint = this.project(toLatLng(latlng))._round();
8954  		return projectedPoint._subtract(this.getPixelOrigin());
8955  	},
8956
8957  	// @method wrapLatLng(latlng: LatLng): LatLng
8958  	// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
8959  	// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
8960  	// CRS's bounds.
8961  	// By default this means longitude is wrapped around the dateline so its
8962  	// value is between -180 and +180 degrees.
8963  	wrapLatLng: function (latlng) {
8964  		return this.options.crs.wrapLatLng(toLatLng(latlng));
8965  	},
8966
8967  	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
8968  	// Returns a `LatLngBounds` with the same size as the given one, ensuring that
8969  	// its center is within the CRS's bounds.
8970  	// By default this means the center longitude is wrapped around the dateline so its
8971  	// value is between -180 and +180 degrees, and the majority of the bounds
8972  	// overlaps the CRS's bounds.
8973  	wrapLatLngBounds: function (latlng) {
8974  		return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
8975  	},
8976
8977  	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
8978  	// Returns the distance between two geographical coordinates according to
8979  	// the map's CRS. By default this measures distance in meters.
8980  	distance: function (latlng1, latlng2) {
8981  		return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
8982  	},
8983
8984  	// @method containerPointToLayerPoint(point: Point): Point
8985  	// Given a pixel coordinate relative to the map container, returns the corresponding
8986  	// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
8987  	containerPointToLayerPoint: function (point) { // (Point)
8988  		return toPoint(point).subtract(this._getMapPanePos());
8989  	},
8990
8991  	// @method layerPointToContainerPoint(point: Point): Point
8992  	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
8993  	// returns the corresponding pixel coordinate relative to the map container.
8994  	layerPointToContainerPoint: function (point) { // (Point)
8995  		return toPoint(point).add(this._getMapPanePos());
8996  	},
8997
8998  	// @method containerPointToLatLng(point: Point): LatLng
8999  	// Given a pixel coordinate relative to the map container, returns
9000  	// the corresponding geographical coordinate (for the current zoom level).
9001  	containerPointToLatLng: function (point) {
9002  		var layerPoint = this.containerPointToLayerPoint(toPoint(point));
9003  		return this.layerPointToLatLng(layerPoint);
9004  	},
9005
9006  	// @method latLngToContainerPoint(latlng: LatLng): Point
9007  	// Given a geographical coordinate, returns the corresponding pixel coordinate
9008  	// relative to the map container.
9009  	latLngToContainerPoint: function (latlng) {
9010  		return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
9011  	},
9012
9013  	// @method mouseEventToContainerPoint(ev: MouseEvent): Point
9014  	// Given a MouseEvent object, returns the pixel coordinate relative to the
9015  	// map container where the event took place.
9016  	mouseEventToContainerPoint: function (e) {
9017  		return getMousePosition(e, this._container);
9018  	},
9019
9020  	// @method mouseEventToLayerPoint(ev: MouseEvent): Point
9021  	// Given a MouseEvent object, returns the pixel coordinate relative to
9022  	// the [origin pixel](#map-getpixelorigin) where the event took place.
9023  	mouseEventToLayerPoint: function (e) {
9024  		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
9025  	},
9026
9027  	// @method mouseEventToLatLng(ev: MouseEvent): LatLng
9028  	// Given a MouseEvent object, returns geographical coordinate where the
9029  	// event took place.
9030  	mouseEventToLatLng: function (e) { // (MouseEvent)
9031  		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
9032  	},
9033
9034
9035  	// map initialization methods
9036
9037  	_initContainer: function (id) {
9038  		var container = this._container = get(id);
9039
9040  		if (!container) {
9041  			throw new Error('Map container not found.');
9042  		} else if (container._leaflet_id) {
9043  			throw new Error('Map container is already initialized.');
9044  		}
9045
9046  		on(container, 'scroll', this._onScroll, this);
9047  		this._containerId = stamp(container);
9048  	},
9049
9050  	_initLayout: function () {
9051  		var container = this._container;
9052
9053  		this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
9054
9055  		addClass(container, 'leaflet-container' +
9056  			(Browser.touch ? ' leaflet-touch' : '') +
9057  			(Browser.retina ? ' leaflet-retina' : '') +
9058  			(Browser.ielt9 ? ' leaflet-oldie' : '') +
9059  			(Browser.safari ? ' leaflet-safari' : '') +
9060  			(this._fadeAnimated ? ' leaflet-fade-anim' : ''));
9061
9062  		var position = getStyle(container, 'position');
9063
9064  		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
9065  			container.style.position = 'relative';
9066  		}
9067
9068  		this._initPanes();
9069
9070  		if (this._initControlPos) {
9071  			this._initControlPos();
9072  		}
9073  	},
9074
9075  	_initPanes: function () {
9076  		var panes = this._panes = {};
9077  		this._paneRenderers = {};
9078
9079  		// @section
9080  		//
9081  		// Panes are DOM elements used to control the ordering of layers on the map. You
9082  		// can access panes with [`map.getPane`](#map-getpane) or
9083  		// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
9084  		// [`map.createPane`](#map-createpane) method.
9085  		//
9086  		// Every map has the following default panes that differ only in zIndex.
9087  		//
9088  		// @pane mapPane: HTMLElement = 'auto'
9089  		// Pane that contains all other map panes
9090
9091  		this._mapPane = this.createPane('mapPane', this._container);
9092  		setPosition(this._mapPane, new Point(0, 0));
9093
9094  		// @pane tilePane: HTMLElement = 200
9095  		// Pane for `GridLayer`s and `TileLayer`s
9096  		this.createPane('tilePane');
9097  		// @pane overlayPane: HTMLElement = 400
9098  		// Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
9099  		this.createPane('overlayPane');
9100  		// @pane shadowPane: HTMLElement = 500
9101  		// Pane for overlay shadows (e.g. `Marker` shadows)
9102  		this.createPane('shadowPane');
9103  		// @pane markerPane: HTMLElement = 600
9104  		// Pane for `Icon`s of `Marker`s
9105  		this.createPane('markerPane');
9106  		// @pane tooltipPane: HTMLElement = 650
9107  		// Pane for `Tooltip`s.
9108  		this.createPane('tooltipPane');
9109  		// @pane popupPane: HTMLElement = 700
9110  		// Pane for `Popup`s.
9111  		this.createPane('popupPane');
9112
9113  		if (!this.options.markerZoomAnimation) {
9114  			addClass(panes.markerPane, 'leaflet-zoom-hide');
9115  			addClass(panes.shadowPane, 'leaflet-zoom-hide');
9116  		}
9117  	},
9118
9119
9120  	// private methods that modify map state
9121
9122  	// @section Map state change events
9123  	_resetView: function (center, zoom, noMoveStart) {
9124  		setPosition(this._mapPane, new Point(0, 0));
9125
9126  		var loading = !this._loaded;
9127  		this._loaded = true;
9128  		zoom = this._limitZoom(zoom);
9129
9130  		this.fire('viewprereset');
9131
9132  		var zoomChanged = this._zoom !== zoom;
9133  		this
9134  			._moveStart(zoomChanged, noMoveStart)
9135  			._move(center, zoom)
9136  			._moveEnd(zoomChanged);
9137
9138  		// @event viewreset: Event
9139  		// Fired when the map needs to redraw its content (this usually happens
9140  		// on map zoom or load). Very useful for creating custom overlays.
9141  		this.fire('viewreset');
9142
9143  		// @event load: Event
9144  		// Fired when the map is initialized (when its center and zoom are set
9145  		// for the first time).
9146  		if (loading) {
9147  			this.fire('load');
9148  		}
9149  	},
9150
9151  	_moveStart: function (zoomChanged, noMoveStart) {
9152  		// @event zoomstart: Event
9153  		// Fired when the map zoom is about to change (e.g. before zoom animation).
9154  		// @event movestart: Event
9155  		// Fired when the view of the map starts changing (e.g. user starts dragging the map).
9156  		if (zoomChanged) {
9157  			this.fire('zoomstart');
9158  		}
9159  		if (!noMoveStart) {
9160  			this.fire('movestart');
9161  		}
9162  		return this;
9163  	},
9164
9165  	_move: function (center, zoom, data, supressEvent) {
9166  		if (zoom === undefined) {
9167  			zoom = this._zoom;
9168  		}
9169  		var zoomChanged = this._zoom !== zoom;
9170
9171  		this._zoom = zoom;
9172  		this._lastCenter = center;
9173  		this._pixelOrigin = this._getNewPixelOrigin(center);
9174
9175  		if (!supressEvent) {
9176  			// @event zoom: Event
9177  			// Fired repeatedly during any change in zoom level,
9178  			// including zoom and fly animations.
9179  			if (zoomChanged || (data && data.pinch)) {	// Always fire 'zoom' if pinching because #3530
9180  				this.fire('zoom', data);
9181  			}
9182
9183  			// @event move: Event
9184  			// Fired repeatedly during any movement of the map,
9185  			// including pan and fly animations.
9186  			this.fire('move', data);
9187  		} else if (data && data.pinch) {	// Always fire 'zoom' if pinching because #3530
9188  			this.fire('zoom', data);
9189  		}
9190  		return this;
9191  	},
9192
9193  	_moveEnd: function (zoomChanged) {
9194  		// @event zoomend: Event
9195  		// Fired when the map zoom changed, after any animations.
9196  		if (zoomChanged) {
9197  			this.fire('zoomend');
9198  		}
9199
9200  		// @event moveend: Event
9201  		// Fired when the center of the map stops changing
9202  		// (e.g. user stopped dragging the map or after non-centered zoom).
9203  		return this.fire('moveend');
9204  	},
9205
9206  	_stop: function () {
9207  		cancelAnimFrame(this._flyToFrame);
9208  		if (this._panAnim) {
9209  			this._panAnim.stop();
9210  		}
9211  		return this;
9212  	},
9213
9214  	_rawPanBy: function (offset) {
9215  		setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
9216  	},
9217
9218  	_getZoomSpan: function () {
9219  		return this.getMaxZoom() - this.getMinZoom();
9220  	},
9221
9222  	_panInsideMaxBounds: function () {
9223  		if (!this._enforcingBounds) {
9224  			this.panInsideBounds(this.options.maxBounds);
9225  		}
9226  	},
9227
9228  	_checkIfLoaded: function () {
9229  		if (!this._loaded) {
9230  			throw new Error('Set map center and zoom first.');
9231  		}
9232  	},
9233
9234  	// DOM event handling
9235
9236  	// @section Interaction events
9237  	_initEvents: function (remove) {
9238  		this._targets = {};
9239  		this._targets[stamp(this._container)] = this;
9240
9241  		var onOff = remove ? off : on;
9242
9243  		// @event click: MouseEvent
9244  		// Fired when the user clicks (or taps) the map.
9245  		// @event dblclick: MouseEvent
9246  		// Fired when the user double-clicks (or double-taps) the map.
9247  		// @event mousedown: MouseEvent
9248  		// Fired when the user pushes the mouse button on the map.
9249  		// @event mouseup: MouseEvent
9250  		// Fired when the user releases the mouse button on the map.
9251  		// @event mouseover: MouseEvent
9252  		// Fired when the mouse enters the map.
9253  		// @event mouseout: MouseEvent
9254  		// Fired when the mouse leaves the map.
9255  		// @event mousemove: MouseEvent
9256  		// Fired while the mouse moves over the map.
9257  		// @event contextmenu: MouseEvent
9258  		// Fired when the user pushes the right mouse button on the map, prevents
9259  		// default browser context menu from showing if there are listeners on
9260  		// this event. Also fired on mobile when the user holds a single touch
9261  		// for a second (also called long press).
9262  		// @event keypress: KeyboardEvent
9263  		// Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
9264  		// @event keydown: KeyboardEvent
9265  		// Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
9266  		// the `keydown` event is fired for keys that produce a character value and for keys
9267  		// that do not produce a character value.
9268  		// @event keyup: KeyboardEvent
9269  		// Fired when the user releases a key from the keyboard while the map is focused.
9270  		onOff(this._container, 'click dblclick mousedown mouseup ' +
9271  			'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
9272
9273  		if (this.options.trackResize) {
9274  			onOff(window, 'resize', this._onResize, this);
9275  		}
9276
9277  		if (Browser.any3d && this.options.transform3DLimit) {
9278  			(remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
9279  		}
9280  	},
9281
9282  	_onResize: function () {
9283  		cancelAnimFrame(this._resizeRequest);
9284  		this._resizeRequest = requestAnimFrame(
9285  		        function () { this.invalidateSize({debounceMoveend: true}); }, this);
9286  	},
9287
9288  	_onScroll: function () {
9289  		this._container.scrollTop  = 0;
9290  		this._container.scrollLeft = 0;
9291  	},
9292
9293  	_onMoveEnd: function () {
9294  		var pos = this._getMapPanePos();
9295  		if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
9296  			// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
9297  			// a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
9298  			this._resetView(this.getCenter(), this.getZoom());
9299  		}
9300  	},
9301
9302  	_findEventTargets: function (e, type) {
9303  		var targets = [],
9304  		    target,
9305  		    isHover = type === 'mouseout' || type === 'mouseover',
9306  		    src = e.target || e.srcElement,
9307  		    dragging = false;
9308
9309  		while (src) {
9310  			target = this._targets[stamp(src)];
9311  			if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
9312  				// Prevent firing click after you just dragged an object.
9313  				dragging = true;
9314  				break;
9315  			}
9316  			if (target && target.listens(type, true)) {
9317  				if (isHover && !isExternalTarget(src, e)) { break; }
9318  				targets.push(target);
9319  				if (isHover) { break; }
9320  			}
9321  			if (src === this._container) { break; }
9322  			src = src.parentNode;
9323  		}
9324  		if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
9325  			targets = [this];
9326  		}
9327  		return targets;
9328  	},
9329
9330  	_isClickDisabled: function (el) {
9331  		while (el && el !== this._container) {
9332  			if (el['_leaflet_disable_click']) { return true; }
9333  			el = el.parentNode;
9334  		}
9335  	},
9336
9337  	_handleDOMEvent: function (e) {
9338  		var el = (e.target || e.srcElement);
9339  		if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
9340  			return;
9341  		}
9342
9343  		var type = e.type;
9344
9345  		if (type === 'mousedown') {
9346  			// prevents outline when clicking on keyboard-focusable element
9347  			preventOutline(el);
9348  		}
9349
9350  		this._fireDOMEvent(e, type);
9351  	},
9352
9353  	_mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
9354
9355  	_fireDOMEvent: function (e, type, canvasTargets) {
9356
9357  		if (e.type === 'click') {
9358  			// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
9359  			// @event preclick: MouseEvent
9360  			// Fired before mouse click on the map (sometimes useful when you
9361  			// want something to happen on click before any existing click
9362  			// handlers start running).
9363  			var synth = extend({}, e);
9364  			synth.type = 'preclick';
9365  			this._fireDOMEvent(synth, synth.type, canvasTargets);
9366  		}
9367
9368  		// Find the layer the event is propagating from and its parents.
9369  		var targets = this._findEventTargets(e, type);
9370
9371  		if (canvasTargets) {
9372  			var filtered = []; // pick only targets with listeners
9373  			for (var i = 0; i < canvasTargets.length; i++) {
9374  				if (canvasTargets[i].listens(type, true)) {
9375  					filtered.push(canvasTargets[i]);
9376  				}
9377  			}
9378  			targets = filtered.concat(targets);
9379  		}
9380
9381  		if (!targets.length) { return; }
9382
9383  		if (type === 'contextmenu') {
9384  			preventDefault(e);
9385  		}
9386
9387  		var target = targets[0];
9388  		var data = {
9389  			originalEvent: e
9390  		};
9391
9392  		if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
9393  			var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
9394  			data.containerPoint = isMarker ?
9395  				this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
9396  			data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
9397  			data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
9398  		}
9399
9400  		for (i = 0; i < targets.length; i++) {
9401  			targets[i].fire(type, data, true);
9402  			if (data.originalEvent._stopped ||
9403  				(targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
9404  		}
9405  	},
9406
9407  	_draggableMoved: function (obj) {
9408  		obj = obj.dragging && obj.dragging.enabled() ? obj : this;
9409  		return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
9410  	},
9411
9412  	_clearHandlers: function () {
9413  		for (var i = 0, len = this._handlers.length; i < len; i++) {
9414  			this._handlers[i].disable();
9415  		}
9416  	},
9417
9418  	// @section Other Methods
9419
9420  	// @method whenReady(fn: Function, context?: Object): this
9421  	// Runs the given function `fn` when the map gets initialized with
9422  	// a view (center and zoom) and at least one layer, or immediately
9423  	// if it's already initialized, optionally passing a function context.
9424  	whenReady: function (callback, context) {
9425  		if (this._loaded) {
9426  			callback.call(context || this, {target: this});
9427  		} else {
9428  			this.on('load', callback, context);
9429  		}
9430  		return this;
9431  	},
9432
9433
9434  	// private methods for getting map state
9435
9436  	_getMapPanePos: function () {
9437  		return getPosition(this._mapPane) || new Point(0, 0);
9438  	},
9439
9440  	_moved: function () {
9441  		var pos = this._getMapPanePos();
9442  		return pos && !pos.equals([0, 0]);
9443  	},
9444
9445  	_getTopLeftPoint: function (center, zoom) {
9446  		var pixelOrigin = center && zoom !== undefined ?
9447  			this._getNewPixelOrigin(center, zoom) :
9448  			this.getPixelOrigin();
9449  		return pixelOrigin.subtract(this._getMapPanePos());
9450  	},
9451
9452  	_getNewPixelOrigin: function (center, zoom) {
9453  		var viewHalf = this.getSize()._divideBy(2);
9454  		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
9455  	},
9456
9457  	_latLngToNewLayerPoint: function (latlng, zoom, center) {
9458  		var topLeft = this._getNewPixelOrigin(center, zoom);
9459  		return this.project(latlng, zoom)._subtract(topLeft);
9460  	},
9461
9462  	_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
9463  		var topLeft = this._getNewPixelOrigin(center, zoom);
9464  		return toBounds([
9465  			this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
9466  			this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
9467  			this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
9468  			this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
9469  		]);
9470  	},
9471
9472  	// layer point of the current center
9473  	_getCenterLayerPoint: function () {
9474  		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
9475  	},
9476
9477  	// offset of the specified place to the current center in pixels
9478  	_getCenterOffset: function (latlng) {
9479  		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
9480  	},
9481
9482  	// adjust center for view to get inside bounds
9483  	_limitCenter: function (center, zoom, bounds) {
9484
9485  		if (!bounds) { return center; }
9486
9487  		var centerPoint = this.project(center, zoom),
9488  		    viewHalf = this.getSize().divideBy(2),
9489  		    viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
9490  		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);
9491
9492  		// If offset is less than a pixel, ignore.
9493  		// This prevents unstable projections from getting into
9494  		// an infinite loop of tiny offsets.
9495  		if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
9496  			return center;
9497  		}
9498
9499  		return this.unproject(centerPoint.add(offset), zoom);
9500  	},
9501
9502  	// adjust offset for view to get inside bounds
9503  	_limitOffset: function (offset, bounds) {
9504  		if (!bounds) { return offset; }
9505
9506  		var viewBounds = this.getPixelBounds(),
9507  		    newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
9508
9509  		return offset.add(this._getBoundsOffset(newBounds, bounds));
9510  	},
9511
9512  	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
9513  	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
9514  		var projectedMaxBounds = toBounds(
9515  		        this.project(maxBounds.getNorthEast(), zoom),
9516  		        this.project(maxBounds.getSouthWest(), zoom)
9517  		    ),
9518  		    minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
9519  		    maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
9520
9521  		    dx = this._rebound(minOffset.x, -maxOffset.x),
9522  		    dy = this._rebound(minOffset.y, -maxOffset.y);
9523
9524  		return new Point(dx, dy);
9525  	},
9526
9527  	_rebound: function (left, right) {
9528  		return left + right > 0 ?
9529  			Math.round(left - right) / 2 :
9530  			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
9531  	},
9532
9533  	_limitZoom: function (zoom) {
9534  		var min = this.getMinZoom(),
9535  		    max = this.getMaxZoom(),
9536  		    snap = Browser.any3d ? this.options.zoomSnap : 1;
9537  		if (snap) {
9538  			zoom = Math.round(zoom / snap) * snap;
9539  		}
9540  		return Math.max(min, Math.min(max, zoom));
9541  	},
9542
9543  	_onPanTransitionStep: function () {
9544  		this.fire('move');
9545  	},
9546
9547  	_onPanTransitionEnd: function () {
9548  		removeClass(this._mapPane, 'leaflet-pan-anim');
9549  		this.fire('moveend');
9550  	},
9551
9552  	_tryAnimatedPan: function (center, options) {
9553  		// difference between the new and current centers in pixels
9554  		var offset = this._getCenterOffset(center)._trunc();
9555
9556  		// don't animate too far unless animate: true specified in options
9557  		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
9558
9559  		this.panBy(offset, options);
9560
9561  		return true;
9562  	},
9563
9564  	_createAnimProxy: function () {
9565
9566  		var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
9567  		this._panes.mapPane.appendChild(proxy);
9568
9569  		this.on('zoomanim', function (e) {
9570  			var prop = TRANSFORM,
9571  			    transform = this._proxy.style[prop];
9572
9573  			setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
9574
9575  			// workaround for case when transform is the same and so transitionend event is not fired
9576  			if (transform === this._proxy.style[prop] && this._animatingZoom) {
9577  				this._onZoomTransitionEnd();
9578  			}
9579  		}, this);
9580
9581  		this.on('load moveend', this._animMoveEnd, this);
9582
9583  		this._on('unload', this._destroyAnimProxy, this);
9584  	},
9585
9586  	_destroyAnimProxy: function () {
9587  		remove(this._proxy);
9588  		this.off('load moveend', this._animMoveEnd, this);
9589  		delete this._proxy;
9590  	},
9591
9592  	_animMoveEnd: function () {
9593  		var c = this.getCenter(),
9594  		    z = this.getZoom();
9595  		setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
9596  	},
9597
9598  	_catchTransitionEnd: function (e) {
9599  		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
9600  			this._onZoomTransitionEnd();
9601  		}
9602  	},
9603
9604  	_nothingToAnimate: function () {
9605  		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
9606  	},
9607
9608  	_tryAnimatedZoom: function (center, zoom, options) {
9609
9610  		if (this._animatingZoom) { return true; }
9611
9612  		options = options || {};
9613
9614  		// don't animate if disabled, not supported or zoom difference is too large
9615  		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
9616  		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
9617
9618  		// offset is the pixel coords of the zoom origin relative to the current center
9619  		var scale = this.getZoomScale(zoom),
9620  		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
9621
9622  		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
9623  		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
9624
9625  		requestAnimFrame(function () {
9626  			this
9627  			    ._moveStart(true, options.noMoveStart || false)
9628  			    ._animateZoom(center, zoom, true);
9629  		}, this);
9630
9631  		return true;
9632  	},
9633
9634  	_animateZoom: function (center, zoom, startAnim, noUpdate) {
9635  		if (!this._mapPane) { return; }
9636
9637  		if (startAnim) {
9638  			this._animatingZoom = true;
9639
9640  			// remember what center/zoom to set after animation
9641  			this._animateToCenter = center;
9642  			this._animateToZoom = zoom;
9643
9644  			addClass(this._mapPane, 'leaflet-zoom-anim');
9645  		}
9646
9647  		// @section Other Events
9648  		// @event zoomanim: ZoomAnimEvent
9649  		// Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
9650  		this.fire('zoomanim', {
9651  			center: center,
9652  			zoom: zoom,
9653  			noUpdate: noUpdate
9654  		});
9655
9656  		if (!this._tempFireZoomEvent) {
9657  			this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
9658  		}
9659
9660  		this._move(this._animateToCenter, this._animateToZoom, undefined, true);
9661
9662  		// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
9663  		setTimeout(bind(this._onZoomTransitionEnd, this), 250);
9664  	},
9665
9666  	_onZoomTransitionEnd: function () {
9667  		if (!this._animatingZoom) { return; }
9668
9669  		if (this._mapPane) {
9670  			removeClass(this._mapPane, 'leaflet-zoom-anim');
9671  		}
9672
9673  		this._animatingZoom = false;
9674
9675  		this._move(this._animateToCenter, this._animateToZoom, undefined, true);
9676
9677  		if (this._tempFireZoomEvent) {
9678  			this.fire('zoom');
9679  		}
9680  		delete this._tempFireZoomEvent;
9681
9682  		this.fire('move');
9683
9684  		this._moveEnd(true);
9685  	}
9686  });
9687
9688  // @section
9689
9690  // @factory L.map(id: String, options?: Map options)
9691  // Instantiates a map object given the DOM ID of a `<div>` element
9692  // and optionally an object literal with `Map options`.
9693  //
9694  // @alternative
9695  // @factory L.map(el: HTMLElement, options?: Map options)
9696  // Instantiates a map object given an instance of a `<div>` HTML element
9697  // and optionally an object literal with `Map options`.
9698  function createMap(id, options) {
9699  	return new Map(id, options);
9700  }
9701
9702  /*
9703   * @class Control
9704   * @aka L.Control
9705   * @inherits Class
9706   *
9707   * L.Control is a base class for implementing map controls. Handles positioning.
9708   * All other controls extend from this class.
9709   */
9710
9711  var Control = Class.extend({
9712  	// @section
9713  	// @aka Control Options
9714  	options: {
9715  		// @option position: String = 'topright'
9716  		// The position of the control (one of the map corners). Possible values are `'topleft'`,
9717  		// `'topright'`, `'bottomleft'` or `'bottomright'`
9718  		position: 'topright'
9719  	},
9720
9721  	initialize: function (options) {
9722  		setOptions(this, options);
9723  	},
9724
9725  	/* @section
9726  	 * Classes extending L.Control will inherit the following methods:
9727  	 *
9728  	 * @method getPosition: string
9729  	 * Returns the position of the control.
9730  	 */
9731  	getPosition: function () {
9732  		return this.options.position;
9733  	},
9734
9735  	// @method setPosition(position: string): this
9736  	// Sets the position of the control.
9737  	setPosition: function (position) {
9738  		var map = this._map;
9739
9740  		if (map) {
9741  			map.removeControl(this);
9742  		}
9743
9744  		this.options.position = position;
9745
9746  		if (map) {
9747  			map.addControl(this);
9748  		}
9749
9750  		return this;
9751  	},
9752
9753  	// @method getContainer: HTMLElement
9754  	// Returns the HTMLElement that contains the control.
9755  	getContainer: function () {
9756  		return this._container;
9757  	},
9758
9759  	// @method addTo(map: Map): this
9760  	// Adds the control to the given map.
9761  	addTo: function (map) {
9762  		this.remove();
9763  		this._map = map;
9764
9765  		var container = this._container = this.onAdd(map),
9766  		    pos = this.getPosition(),
9767  		    corner = map._controlCorners[pos];
9768
9769  		addClass(container, 'leaflet-control');
9770
9771  		if (pos.indexOf('bottom') !== -1) {
9772  			corner.insertBefore(container, corner.firstChild);
9773  		} else {
9774  			corner.appendChild(container);
9775  		}
9776
9777  		this._map.on('unload', this.remove, this);
9778
9779  		return this;
9780  	},
9781
9782  	// @method remove: this
9783  	// Removes the control from the map it is currently active on.
9784  	remove: function () {
9785  		if (!this._map) {
9786  			return this;
9787  		}
9788
9789  		remove(this._container);
9790
9791  		if (this.onRemove) {
9792  			this.onRemove(this._map);
9793  		}
9794
9795  		this._map.off('unload', this.remove, this);
9796  		this._map = null;
9797
9798  		return this;
9799  	},
9800
9801  	_refocusOnMap: function (e) {
9802  		// if map exists and event is not a keyboard event
9803  		if (this._map && e && e.screenX > 0 && e.screenY > 0) {
9804  			this._map.getContainer().focus();
9805  		}
9806  	}
9807  });
9808
9809  var control = function (options) {
9810  	return new Control(options);
9811  };
9812
9813  /* @section Extension methods
9814   * @uninheritable
9815   *
9816   * Every control should extend from `L.Control` and (re-)implement the following methods.
9817   *
9818   * @method onAdd(map: Map): HTMLElement
9819   * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
9820   *
9821   * @method onRemove(map: Map)
9822   * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
9823   */
9824
9825  /* @namespace Map
9826   * @section Methods for Layers and Controls
9827   */
9828  Map.include({
9829  	// @method addControl(control: Control): this
9830  	// Adds the given control to the map
9831  	addControl: function (control) {
9832  		control.addTo(this);
9833  		return this;
9834  	},
9835
9836  	// @method removeControl(control: Control): this
9837  	// Removes the given control from the map
9838  	removeControl: function (control) {
9839  		control.remove();
9840  		return this;
9841  	},
9842
9843  	_initControlPos: function () {
9844  		var corners = this._controlCorners = {},
9845  		    l = 'leaflet-',
9846  		    container = this._controlContainer =
9847  		            create$1('div', l + 'control-container', this._container);
9848
9849  		function createCorner(vSide, hSide) {
9850  			var className = l + vSide + ' ' + l + hSide;
9851
9852  			corners[vSide + hSide] = create$1('div', className, container);
9853  		}
9854
9855  		createCorner('top', 'left');
9856  		createCorner('top', 'right');
9857  		createCorner('bottom', 'left');
9858  		createCorner('bottom', 'right');
9859  	},
9860
9861  	_clearControlPos: function () {
9862  		for (var i in this._controlCorners) {
9863  			remove(this._controlCorners[i]);
9864  		}
9865  		remove(this._controlContainer);
9866  		delete this._controlCorners;
9867  		delete this._controlContainer;
9868  	}
9869  });
9870
9871  /*
9872   * @class Control.Layers
9873   * @aka L.Control.Layers
9874   * @inherits Control
9875   *
9876   * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
9877   *
9878   * @example
9879   *
9880   * ```js
9881   * var baseLayers = {
9882   * 	"Mapbox": mapbox,
9883   * 	"OpenStreetMap": osm
9884   * };
9885   *
9886   * var overlays = {
9887   * 	"Marker": marker,
9888   * 	"Roads": roadsLayer
9889   * };
9890   *
9891   * L.control.layers(baseLayers, overlays).addTo(map);
9892   * ```
9893   *
9894   * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
9895   *
9896   * ```js
9897   * {
9898   *     "<someName1>": layer1,
9899   *     "<someName2>": layer2
9900   * }
9901   * ```
9902   *
9903   * The layer names can contain HTML, which allows you to add additional styling to the items:
9904   *
9905   * ```js
9906   * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
9907   * ```
9908   */
9909
9910  var Layers = Control.extend({
9911  	// @section
9912  	// @aka Control.Layers options
9913  	options: {
9914  		// @option collapsed: Boolean = true
9915  		// If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
9916  		collapsed: true,
9917  		position: 'topright',
9918
9919  		// @option autoZIndex: Boolean = true
9920  		// If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
9921  		autoZIndex: true,
9922
9923  		// @option hideSingleBase: Boolean = false
9924  		// If `true`, the base layers in the control will be hidden when there is only one.
9925  		hideSingleBase: false,
9926
9927  		// @option sortLayers: Boolean = false
9928  		// Whether to sort the layers. When `false`, layers will keep the order
9929  		// in which they were added to the control.
9930  		sortLayers: false,
9931
9932  		// @option sortFunction: Function = *
9933  		// A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
9934  		// that will be used for sorting the layers, when `sortLayers` is `true`.
9935  		// The function receives both the `L.Layer` instances and their names, as in
9936  		// `sortFunction(layerA, layerB, nameA, nameB)`.
9937  		// By default, it sorts layers alphabetically by their name.
9938  		sortFunction: function (layerA, layerB, nameA, nameB) {
9939  			return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
9940  		}
9941  	},
9942
9943  	initialize: function (baseLayers, overlays, options) {
9944  		setOptions(this, options);
9945
9946  		this._layerControlInputs = [];
9947  		this._layers = [];
9948  		this._lastZIndex = 0;
9949  		this._handlingClick = false;
9950  		this._preventClick = false;
9951
9952  		for (var i in baseLayers) {
9953  			this._addLayer(baseLayers[i], i);
9954  		}
9955
9956  		for (i in overlays) {
9957  			this._addLayer(overlays[i], i, true);
9958  		}
9959  	},
9960
9961  	onAdd: function (map) {
9962  		this._initLayout();
9963  		this._update();
9964
9965  		this._map = map;
9966  		map.on('zoomend', this._checkDisabledLayers, this);
9967
9968  		for (var i = 0; i < this._layers.length; i++) {
9969  			this._layers[i].layer.on('add remove', this._onLayerChange, this);
9970  		}
9971
9972  		return this._container;
9973  	},
9974
9975  	addTo: function (map) {
9976  		Control.prototype.addTo.call(this, map);
9977  		// Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
9978  		return this._expandIfNotCollapsed();
9979  	},
9980
9981  	onRemove: function () {
9982  		this._map.off('zoomend', this._checkDisabledLayers, this);
9983
9984  		for (var i = 0; i < this._layers.length; i++) {
9985  			this._layers[i].layer.off('add remove', this._onLayerChange, this);
9986  		}
9987  	},
9988
9989  	// @method addBaseLayer(layer: Layer, name: String): this
9990  	// Adds a base layer (radio button entry) with the given name to the control.
9991  	addBaseLayer: function (layer, name) {
9992  		this._addLayer(layer, name);
9993  		return (this._map) ? this._update() : this;
9994  	},
9995
9996  	// @method addOverlay(layer: Layer, name: String): this
9997  	// Adds an overlay (checkbox entry) with the given name to the control.
9998  	addOverlay: function (layer, name) {
9999  		this._addLayer(layer, name, true);
10000  		return (this._map) ? this._update() : this;
10001  	},
10002
10003  	// @method removeLayer(layer: Layer): this
10004  	// Remove the given layer from the control.
10005  	removeLayer: function (layer) {
10006  		layer.off('add remove', this._onLayerChange, this);
10007
10008  		var obj = this._getLayer(stamp(layer));
10009  		if (obj) {
10010  			this._layers.splice(this._layers.indexOf(obj), 1);
10011  		}
10012  		return (this._map) ? this._update() : this;
10013  	},
10014
10015  	// @method expand(): this
10016  	// Expand the control container if collapsed.
10017  	expand: function () {
10018  		addClass(this._container, 'leaflet-control-layers-expanded');
10019  		this._section.style.height = null;
10020  		var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
10021  		if (acceptableHeight < this._section.clientHeight) {
10022  			addClass(this._section, 'leaflet-control-layers-scrollbar');
10023  			this._section.style.height = acceptableHeight + 'px';
10024  		} else {
10025  			removeClass(this._section, 'leaflet-control-layers-scrollbar');
10026  		}
10027  		this._checkDisabledLayers();
10028  		return this;
10029  	},
10030
10031  	// @method collapse(): this
10032  	// Collapse the control container if expanded.
10033  	collapse: function () {
10034  		removeClass(this._container, 'leaflet-control-layers-expanded');
10035  		return this;
10036  	},
10037
10038  	_initLayout: function () {
10039  		var className = 'leaflet-control-layers',
10040  		    container = this._container = create$1('div', className),
10041  		    collapsed = this.options.collapsed;
10042
10043  		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
10044  		container.setAttribute('aria-haspopup', true);
10045
10046  		disableClickPropagation(container);
10047  		disableScrollPropagation(container);
10048
10049  		var section = this._section = create$1('section', className + '-list');
10050
10051  		if (collapsed) {
10052  			this._map.on('click', this.collapse, this);
10053
10054  			on(container, {
10055  				mouseenter: this._expandSafely,
10056  				mouseleave: this.collapse
10057  			}, this);
10058  		}
10059
10060  		var link = this._layersLink = create$1('a', className + '-toggle', container);
10061  		link.href = '#';
10062  		link.title = 'Layers';
10063  		link.setAttribute('role', 'button');
10064
10065  		on(link, {
10066  			keydown: function (e) {
10067  				if (e.keyCode === 13) {
10068  					this._expandSafely();
10069  				}
10070  			},
10071  			// Certain screen readers intercept the key event and instead send a click event
10072  			click: function (e) {
10073  				preventDefault(e);
10074  				this._expandSafely();
10075  			}
10076  		}, this);
10077
10078  		if (!collapsed) {
10079  			this.expand();
10080  		}
10081
10082  		this._baseLayersList = create$1('div', className + '-base', section);
10083  		this._separator = create$1('div', className + '-separator', section);
10084  		this._overlaysList = create$1('div', className + '-overlays', section);
10085
10086  		container.appendChild(section);
10087  	},
10088
10089  	_getLayer: function (id) {
10090  		for (var i = 0; i < this._layers.length; i++) {
10091
10092  			if (this._layers[i] && stamp(this._layers[i].layer) === id) {
10093  				return this._layers[i];
10094  			}
10095  		}
10096  	},
10097
10098  	_addLayer: function (layer, name, overlay) {
10099  		if (this._map) {
10100  			layer.on('add remove', this._onLayerChange, this);
10101  		}
10102
10103  		this._layers.push({
10104  			layer: layer,
10105  			name: name,
10106  			overlay: overlay
10107  		});
10108
10109  		if (this.options.sortLayers) {
10110  			this._layers.sort(bind(function (a, b) {
10111  				return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
10112  			}, this));
10113  		}
10114
10115  		if (this.options.autoZIndex && layer.setZIndex) {
10116  			this._lastZIndex++;
10117  			layer.setZIndex(this._lastZIndex);
10118  		}
10119
10120  		this._expandIfNotCollapsed();
10121  	},
10122
10123  	_update: function () {
10124  		if (!this._container) { return this; }
10125
10126  		empty(this._baseLayersList);
10127  		empty(this._overlaysList);
10128
10129  		this._layerControlInputs = [];
10130  		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
10131
10132  		for (i = 0; i < this._layers.length; i++) {
10133  			obj = this._layers[i];
10134  			this._addItem(obj);
10135  			overlaysPresent = overlaysPresent || obj.overlay;
10136  			baseLayersPresent = baseLayersPresent || !obj.overlay;
10137  			baseLayersCount += !obj.overlay ? 1 : 0;
10138  		}
10139
10140  		// Hide base layers section if there's only one layer.
10141  		if (this.options.hideSingleBase) {
10142  			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
10143  			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
10144  		}
10145
10146  		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
10147
10148  		return this;
10149  	},
10150
10151  	_onLayerChange: function (e) {
10152  		if (!this._handlingClick) {
10153  			this._update();
10154  		}
10155
10156  		var obj = this._getLayer(stamp(e.target));
10157
10158  		// @namespace Map
10159  		// @section Layer events
10160  		// @event baselayerchange: LayersControlEvent
10161  		// Fired when the base layer is changed through the [layers control](#control-layers).
10162  		// @event overlayadd: LayersControlEvent
10163  		// Fired when an overlay is selected through the [layers control](#control-layers).
10164  		// @event overlayremove: LayersControlEvent
10165  		// Fired when an overlay is deselected through the [layers control](#control-layers).
10166  		// @namespace Control.Layers
10167  		var type = obj.overlay ?
10168  			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
10169  			(e.type === 'add' ? 'baselayerchange' : null);
10170
10171  		if (type) {
10172  			this._map.fire(type, obj);
10173  		}
10174  	},
10175
10176  	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
10177  	_createRadioElement: function (name, checked) {
10178
10179  		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
10180  				name + '"' + (checked ? ' checked="checked"' : '') + '/>';
10181
10182  		var radioFragment = document.createElement('div');
10183  		radioFragment.innerHTML = radioHtml;
10184
10185  		return radioFragment.firstChild;
10186  	},
10187
10188  	_addItem: function (obj) {
10189  		var label = document.createElement('label'),
10190  		    checked = this._map.hasLayer(obj.layer),
10191  		    input;
10192
10193  		if (obj.overlay) {
10194  			input = document.createElement('input');
10195  			input.type = 'checkbox';
10196  			input.className = 'leaflet-control-layers-selector';
10197  			input.defaultChecked = checked;
10198  		} else {
10199  			input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
10200  		}
10201
10202  		this._layerControlInputs.push(input);
10203  		input.layerId = stamp(obj.layer);
10204
10205  		on(input, 'click', this._onInputClick, this);
10206
10207  		var name = document.createElement('span');
10208  		name.innerHTML = ' ' + obj.name;
10209
10210  		// Helps from preventing layer control flicker when checkboxes are disabled
10211  		// https://github.com/Leaflet/Leaflet/issues/2771
10212  		var holder = document.createElement('span');
10213
10214  		label.appendChild(holder);
10215  		holder.appendChild(input);
10216  		holder.appendChild(name);
10217
10218  		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
10219  		container.appendChild(label);
10220
10221  		this._checkDisabledLayers();
10222  		return label;
10223  	},
10224
10225  	_onInputClick: function () {
10226  		// expanding the control on mobile with a click can cause adding a layer - we don't want this
10227  		if (this._preventClick) {
10228  			return;
10229  		}
10230
10231  		var inputs = this._layerControlInputs,
10232  		    input, layer;
10233  		var addedLayers = [],
10234  		    removedLayers = [];
10235
10236  		this._handlingClick = true;
10237
10238  		for (var i = inputs.length - 1; i >= 0; i--) {
10239  			input = inputs[i];
10240  			layer = this._getLayer(input.layerId).layer;
10241
10242  			if (input.checked) {
10243  				addedLayers.push(layer);
10244  			} else if (!input.checked) {
10245  				removedLayers.push(layer);
10246  			}
10247  		}
10248
10249  		// Bugfix issue 2318: Should remove all old layers before readding new ones
10250  		for (i = 0; i < removedLayers.length; i++) {
10251  			if (this._map.hasLayer(removedLayers[i])) {
10252  				this._map.removeLayer(removedLayers[i]);
10253  			}
10254  		}
10255  		for (i = 0; i < addedLayers.length; i++) {
10256  			if (!this._map.hasLayer(addedLayers[i])) {
10257  				this._map.addLayer(addedLayers[i]);
10258  			}
10259  		}
10260
10261  		this._handlingClick = false;
10262
10263  		this._refocusOnMap();
10264  	},
10265
10266  	_checkDisabledLayers: function () {
10267  		var inputs = this._layerControlInputs,
10268  		    input,
10269  		    layer,
10270  		    zoom = this._map.getZoom();
10271
10272  		for (var i = inputs.length - 1; i >= 0; i--) {
10273  			input = inputs[i];
10274  			layer = this._getLayer(input.layerId).layer;
10275  			input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
10276  			                 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
10277
10278  		}
10279  	},
10280
10281  	_expandIfNotCollapsed: function () {
10282  		if (this._map && !this.options.collapsed) {
10283  			this.expand();
10284  		}
10285  		return this;
10286  	},
10287
10288  	_expandSafely: function () {
10289  		var section = this._section;
10290  		this._preventClick = true;
10291  		on(section, 'click', preventDefault);
10292  		this.expand();
10293  		var that = this;
10294  		setTimeout(function () {
10295  			off(section, 'click', preventDefault);
10296  			that._preventClick = false;
10297  		});
10298  	}
10299
10300  });
10301
10302
10303  // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
10304  // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
10305  var layers = function (baseLayers, overlays, options) {
10306  	return new Layers(baseLayers, overlays, options);
10307  };
10308
10309  /*
10310   * @class Control.Zoom
10311   * @aka L.Control.Zoom
10312   * @inherits Control
10313   *
10314   * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
10315   */
10316
10317  var Zoom = Control.extend({
10318  	// @section
10319  	// @aka Control.Zoom options
10320  	options: {
10321  		position: 'topleft',
10322
10323  		// @option zoomInText: String = '<span aria-hidden="true">+</span>'
10324  		// The text set on the 'zoom in' button.
10325  		zoomInText: '<span aria-hidden="true">+</span>',
10326
10327  		// @option zoomInTitle: String = 'Zoom in'
10328  		// The title set on the 'zoom in' button.
10329  		zoomInTitle: 'Zoom in',
10330
10331  		// @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
10332  		// The text set on the 'zoom out' button.
10333  		zoomOutText: '<span aria-hidden="true">&#x2212;</span>',
10334
10335  		// @option zoomOutTitle: String = 'Zoom out'
10336  		// The title set on the 'zoom out' button.
10337  		zoomOutTitle: 'Zoom out'
10338  	},
10339
10340  	onAdd: function (map) {
10341  		var zoomName = 'leaflet-control-zoom',
10342  		    container = create$1('div', zoomName + ' leaflet-bar'),
10343  		    options = this.options;
10344
10345  		this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
10346  		        zoomName + '-in',  container, this._zoomIn);
10347  		this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
10348  		        zoomName + '-out', container, this._zoomOut);
10349
10350  		this._updateDisabled();
10351  		map.on('zoomend zoomlevelschange', this._updateDisabled, this);
10352
10353  		return container;
10354  	},
10355
10356  	onRemove: function (map) {
10357  		map.off('zoomend zoomlevelschange', this._updateDisabled, this);
10358  	},
10359
10360  	disable: function () {
10361  		this._disabled = true;
10362  		this._updateDisabled();
10363  		return this;
10364  	},
10365
10366  	enable: function () {
10367  		this._disabled = false;
10368  		this._updateDisabled();
10369  		return this;
10370  	},
10371
10372  	_zoomIn: function (e) {
10373  		if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
10374  			this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
10375  		}
10376  	},
10377
10378  	_zoomOut: function (e) {
10379  		if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
10380  			this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
10381  		}
10382  	},
10383
10384  	_createButton: function (html, title, className, container, fn) {
10385  		var link = create$1('a', className, container);
10386  		link.innerHTML = html;
10387  		link.href = '#';
10388  		link.title = title;
10389
10390  		/*
10391  		 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
10392  		 */
10393  		link.setAttribute('role', 'button');
10394  		link.setAttribute('aria-label', title);
10395
10396  		disableClickPropagation(link);
10397  		on(link, 'click', stop);
10398  		on(link, 'click', fn, this);
10399  		on(link, 'click', this._refocusOnMap, this);
10400
10401  		return link;
10402  	},
10403
10404  	_updateDisabled: function () {
10405  		var map = this._map,
10406  		    className = 'leaflet-disabled';
10407
10408  		removeClass(this._zoomInButton, className);
10409  		removeClass(this._zoomOutButton, className);
10410  		this._zoomInButton.setAttribute('aria-disabled', 'false');
10411  		this._zoomOutButton.setAttribute('aria-disabled', 'false');
10412
10413  		if (this._disabled || map._zoom === map.getMinZoom()) {
10414  			addClass(this._zoomOutButton, className);
10415  			this._zoomOutButton.setAttribute('aria-disabled', 'true');
10416  		}
10417  		if (this._disabled || map._zoom === map.getMaxZoom()) {
10418  			addClass(this._zoomInButton, className);
10419  			this._zoomInButton.setAttribute('aria-disabled', 'true');
10420  		}
10421  	}
10422  });
10423
10424  // @namespace Map
10425  // @section Control options
10426  // @option zoomControl: Boolean = true
10427  // Whether a [zoom control](#control-zoom) is added to the map by default.
10428  Map.mergeOptions({
10429  	zoomControl: true
10430  });
10431
10432  Map.addInitHook(function () {
10433  	if (this.options.zoomControl) {
10434  		// @section Controls
10435  		// @property zoomControl: Control.Zoom
10436  		// The default zoom control (only available if the
10437  		// [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
10438  		this.zoomControl = new Zoom();
10439  		this.addControl(this.zoomControl);
10440  	}
10441  });
10442
10443  // @namespace Control.Zoom
10444  // @factory L.control.zoom(options: Control.Zoom options)
10445  // Creates a zoom control
10446  var zoom = function (options) {
10447  	return new Zoom(options);
10448  };
10449
10450  /*
10451   * @class Control.Scale
10452   * @aka L.Control.Scale
10453   * @inherits Control
10454   *
10455   * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
10456   *
10457   * @example
10458   *
10459   * ```js
10460   * L.control.scale().addTo(map);
10461   * ```
10462   */
10463
10464  var Scale = Control.extend({
10465  	// @section
10466  	// @aka Control.Scale options
10467  	options: {
10468  		position: 'bottomleft',
10469
10470  		// @option maxWidth: Number = 100
10471  		// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
10472  		maxWidth: 100,
10473
10474  		// @option metric: Boolean = True
10475  		// Whether to show the metric scale line (m/km).
10476  		metric: true,
10477
10478  		// @option imperial: Boolean = True
10479  		// Whether to show the imperial scale line (mi/ft).
10480  		imperial: true
10481
10482  		// @option updateWhenIdle: Boolean = false
10483  		// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
10484  	},
10485
10486  	onAdd: function (map) {
10487  		var className = 'leaflet-control-scale',
10488  		    container = create$1('div', className),
10489  		    options = this.options;
10490
10491  		this._addScales(options, className + '-line', container);
10492
10493  		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
10494  		map.whenReady(this._update, this);
10495
10496  		return container;
10497  	},
10498
10499  	onRemove: function (map) {
10500  		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
10501  	},
10502
10503  	_addScales: function (options, className, container) {
10504  		if (options.metric) {
10505  			this._mScale = create$1('div', className, container);
10506  		}
10507  		if (options.imperial) {
10508  			this._iScale = create$1('div', className, container);
10509  		}
10510  	},
10511
10512  	_update: function () {
10513  		var map = this._map,
10514  		    y = map.getSize().y / 2;
10515
10516  		var maxMeters = map.distance(
10517  			map.containerPointToLatLng([0, y]),
10518  			map.containerPointToLatLng([this.options.maxWidth, y]));
10519
10520  		this._updateScales(maxMeters);
10521  	},
10522
10523  	_updateScales: function (maxMeters) {
10524  		if (this.options.metric && maxMeters) {
10525  			this._updateMetric(maxMeters);
10526  		}
10527  		if (this.options.imperial && maxMeters) {
10528  			this._updateImperial(maxMeters);
10529  		}
10530  	},
10531
10532  	_updateMetric: function (maxMeters) {
10533  		var meters = this._getRoundNum(maxMeters),
10534  		    label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
10535
10536  		this._updateScale(this._mScale, label, meters / maxMeters);
10537  	},
10538
10539  	_updateImperial: function (maxMeters) {
10540  		var maxFeet = maxMeters * 3.2808399,
10541  		    maxMiles, miles, feet;
10542
10543  		if (maxFeet > 5280) {
10544  			maxMiles = maxFeet / 5280;
10545  			miles = this._getRoundNum(maxMiles);
10546  			this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
10547
10548  		} else {
10549  			feet = this._getRoundNum(maxFeet);
10550  			this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
10551  		}
10552  	},
10553
10554  	_updateScale: function (scale, text, ratio) {
10555  		scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
10556  		scale.innerHTML = text;
10557  	},
10558
10559  	_getRoundNum: function (num) {
10560  		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
10561  		    d = num / pow10;
10562
10563  		d = d >= 10 ? 10 :
10564  		    d >= 5 ? 5 :
10565  		    d >= 3 ? 3 :
10566  		    d >= 2 ? 2 : 1;
10567
10568  		return pow10 * d;
10569  	}
10570  });
10571
10572
10573  // @factory L.control.scale(options?: Control.Scale options)
10574  // Creates an scale control with the given options.
10575  var scale = function (options) {
10576  	return new Scale(options);
10577  };
10578
10579  var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';
10580
10581
10582  /*
10583   * @class Control.Attribution
10584   * @aka L.Control.Attribution
10585   * @inherits Control
10586   *
10587   * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
10588   */
10589
10590  var Attribution = Control.extend({
10591  	// @section
10592  	// @aka Control.Attribution options
10593  	options: {
10594  		position: 'bottomright',
10595
10596  		// @option prefix: String|false = 'Leaflet'
10597  		// The HTML text shown before the attributions. Pass `false` to disable.
10598  		prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
10599  	},
10600
10601  	initialize: function (options) {
10602  		setOptions(this, options);
10603
10604  		this._attributions = {};
10605  	},
10606
10607  	onAdd: function (map) {
10608  		map.attributionControl = this;
10609  		this._container = create$1('div', 'leaflet-control-attribution');
10610  		disableClickPropagation(this._container);
10611
10612  		// TODO ugly, refactor
10613  		for (var i in map._layers) {
10614  			if (map._layers[i].getAttribution) {
10615  				this.addAttribution(map._layers[i].getAttribution());
10616  			}
10617  		}
10618
10619  		this._update();
10620
10621  		map.on('layeradd', this._addAttribution, this);
10622
10623  		return this._container;
10624  	},
10625
10626  	onRemove: function (map) {
10627  		map.off('layeradd', this._addAttribution, this);
10628  	},
10629
10630  	_addAttribution: function (ev) {
10631  		if (ev.layer.getAttribution) {
10632  			this.addAttribution(ev.layer.getAttribution());
10633  			ev.layer.once('remove', function () {
10634  				this.removeAttribution(ev.layer.getAttribution());
10635  			}, this);
10636  		}
10637  	},
10638
10639  	// @method setPrefix(prefix: String|false): this
10640  	// The HTML text shown before the attributions. Pass `false` to disable.
10641  	setPrefix: function (prefix) {
10642  		this.options.prefix = prefix;
10643  		this._update();
10644  		return this;
10645  	},
10646
10647  	// @method addAttribution(text: String): this
10648  	// Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).
10649  	addAttribution: function (text) {
10650  		if (!text) { return this; }
10651
10652  		if (!this._attributions[text]) {
10653  			this._attributions[text] = 0;
10654  		}
10655  		this._attributions[text]++;
10656
10657  		this._update();
10658
10659  		return this;
10660  	},
10661
10662  	// @method removeAttribution(text: String): this
10663  	// Removes an attribution text.
10664  	removeAttribution: function (text) {
10665  		if (!text) { return this; }
10666
10667  		if (this._attributions[text]) {
10668  			this._attributions[text]--;
10669  			this._update();
10670  		}
10671
10672  		return this;
10673  	},
10674
10675  	_update: function () {
10676  		if (!this._map) { return; }
10677
10678  		var attribs = [];
10679
10680  		for (var i in this._attributions) {
10681  			if (this._attributions[i]) {
10682  				attribs.push(i);
10683  			}
10684  		}
10685
10686  		var prefixAndAttribs = [];
10687
10688  		if (this.options.prefix) {
10689  			prefixAndAttribs.push(this.options.prefix);
10690  		}
10691  		if (attribs.length) {
10692  			prefixAndAttribs.push(attribs.join(', '));
10693  		}
10694
10695  		this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
10696  	}
10697  });
10698
10699  // @namespace Map
10700  // @section Control options
10701  // @option attributionControl: Boolean = true
10702  // Whether a [attribution control](#control-attribution) is added to the map by default.
10703  Map.mergeOptions({
10704  	attributionControl: true
10705  });
10706
10707  Map.addInitHook(function () {
10708  	if (this.options.attributionControl) {
10709  		new Attribution().addTo(this);
10710  	}
10711  });
10712
10713  // @namespace Control.Attribution
10714  // @factory L.control.attribution(options: Control.Attribution options)
10715  // Creates an attribution control.
10716  var attribution = function (options) {
10717  	return new Attribution(options);
10718  };
10719
10720  Control.Layers = Layers;
10721  Control.Zoom = Zoom;
10722  Control.Scale = Scale;
10723  Control.Attribution = Attribution;
10724
10725  control.layers = layers;
10726  control.zoom = zoom;
10727  control.scale = scale;
10728  control.attribution = attribution;
10729
10730  /*
10731  	L.Handler is a base class for handler classes that are used internally to inject
10732  	interaction features like dragging to classes like Map and Marker.
10733  */
10734
10735  // @class Handler
10736  // @aka L.Handler
10737  // Abstract class for map interaction handlers
10738
10739  var Handler = Class.extend({
10740  	initialize: function (map) {
10741  		this._map = map;
10742  	},
10743
10744  	// @method enable(): this
10745  	// Enables the handler
10746  	enable: function () {
10747  		if (this._enabled) { return this; }
10748
10749  		this._enabled = true;
10750  		this.addHooks();
10751  		return this;
10752  	},
10753
10754  	// @method disable(): this
10755  	// Disables the handler
10756  	disable: function () {
10757  		if (!this._enabled) { return this; }
10758
10759  		this._enabled = false;
10760  		this.removeHooks();
10761  		return this;
10762  	},
10763
10764  	// @method enabled(): Boolean
10765  	// Returns `true` if the handler is enabled
10766  	enabled: function () {
10767  		return !!this._enabled;
10768  	}
10769
10770  	// @section Extension methods
10771  	// Classes inheriting from `Handler` must implement the two following methods:
10772  	// @method addHooks()
10773  	// Called when the handler is enabled, should add event hooks.
10774  	// @method removeHooks()
10775  	// Called when the handler is disabled, should remove the event hooks added previously.
10776  });
10777
10778  // @section There is static function which can be called without instantiating L.Handler:
10779  // @function addTo(map: Map, name: String): this
10780  // Adds a new Handler to the given map with the given name.
10781  Handler.addTo = function (map, name) {
10782  	map.addHandler(name, this);
10783  	return this;
10784  };
10785
10786  var Mixin = {Events: Events};
10787
10788  /*
10789   * @class Draggable
10790   * @aka L.Draggable
10791   * @inherits Evented
10792   *
10793   * A class for making DOM elements draggable (including touch support).
10794   * Used internally for map and marker dragging. Only works for elements
10795   * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10796   *
10797   * @example
10798   * ```js
10799   * var draggable = new L.Draggable(elementToDrag);
10800   * draggable.enable();
10801   * ```
10802   */
10803
10804  var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
10805
10806  var Draggable = Evented.extend({
10807
10808  	options: {
10809  		// @section
10810  		// @aka Draggable options
10811  		// @option clickTolerance: Number = 3
10812  		// The max number of pixels a user can shift the mouse pointer during a click
10813  		// for it to be considered a valid click (as opposed to a mouse drag).
10814  		clickTolerance: 3
10815  	},
10816
10817  	// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
10818  	// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10819  	initialize: function (element, dragStartTarget, preventOutline, options) {
10820  		setOptions(this, options);
10821
10822  		this._element = element;
10823  		this._dragStartTarget = dragStartTarget || element;
10824  		this._preventOutline = preventOutline;
10825  	},
10826
10827  	// @method enable()
10828  	// Enables the dragging ability
10829  	enable: function () {
10830  		if (this._enabled) { return; }
10831
10832  		on(this._dragStartTarget, START, this._onDown, this);
10833
10834  		this._enabled = true;
10835  	},
10836
10837  	// @method disable()
10838  	// Disables the dragging ability
10839  	disable: function () {
10840  		if (!this._enabled) { return; }
10841
10842  		// If we're currently dragging this draggable,
10843  		// disabling it counts as first ending the drag.
10844  		if (Draggable._dragging === this) {
10845  			this.finishDrag(true);
10846  		}
10847
10848  		off(this._dragStartTarget, START, this._onDown, this);
10849
10850  		this._enabled = false;
10851  		this._moved = false;
10852  	},
10853
10854  	_onDown: function (e) {
10855  		// Ignore the event if disabled; this happens in IE11
10856  		// under some circumstances, see #3666.
10857  		if (!this._enabled) { return; }
10858
10859  		this._moved = false;
10860
10861  		if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10862
10863  		if (e.touches && e.touches.length !== 1) {
10864  			// Finish dragging to avoid conflict with touchZoom
10865  			if (Draggable._dragging === this) {
10866  				this.finishDrag();
10867  			}
10868  			return;
10869  		}
10870
10871  		if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
10872  		Draggable._dragging = this;  // Prevent dragging multiple objects at once.
10873
10874  		if (this._preventOutline) {
10875  			preventOutline(this._element);
10876  		}
10877
10878  		disableImageDrag();
10879  		disableTextSelection();
10880
10881  		if (this._moving) { return; }
10882
10883  		// @event down: Event
10884  		// Fired when a drag is about to start.
10885  		this.fire('down');
10886
10887  		var first = e.touches ? e.touches[0] : e,
10888  		    sizedParent = getSizedParentNode(this._element);
10889
10890  		this._startPoint = new Point(first.clientX, first.clientY);
10891  		this._startPos = getPosition(this._element);
10892
10893  		// Cache the scale, so that we can continuously compensate for it during drag (_onMove).
10894  		this._parentScale = getScale(sizedParent);
10895
10896  		var mouseevent = e.type === 'mousedown';
10897  		on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
10898  		on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
10899  	},
10900
10901  	_onMove: function (e) {
10902  		// Ignore the event if disabled; this happens in IE11
10903  		// under some circumstances, see #3666.
10904  		if (!this._enabled) { return; }
10905
10906  		if (e.touches && e.touches.length > 1) {
10907  			this._moved = true;
10908  			return;
10909  		}
10910
10911  		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10912  		    offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
10913
10914  		if (!offset.x && !offset.y) { return; }
10915  		if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10916
10917  		// We assume that the parent container's position, border and scale do not change for the duration of the drag.
10918  		// Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
10919  		// and we can use the cached value for the scale.
10920  		offset.x /= this._parentScale.x;
10921  		offset.y /= this._parentScale.y;
10922
10923  		preventDefault(e);
10924
10925  		if (!this._moved) {
10926  			// @event dragstart: Event
10927  			// Fired when a drag starts
10928  			this.fire('dragstart');
10929
10930  			this._moved = true;
10931
10932  			addClass(document.body, 'leaflet-dragging');
10933
10934  			this._lastTarget = e.target || e.srcElement;
10935  			// IE and Edge do not give the <use> element, so fetch it
10936  			// if necessary
10937  			if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
10938  				this._lastTarget = this._lastTarget.correspondingUseElement;
10939  			}
10940  			addClass(this._lastTarget, 'leaflet-drag-target');
10941  		}
10942
10943  		this._newPos = this._startPos.add(offset);
10944  		this._moving = true;
10945
10946  		this._lastEvent = e;
10947  		this._updatePosition();
10948  	},
10949
10950  	_updatePosition: function () {
10951  		var e = {originalEvent: this._lastEvent};
10952
10953  		// @event predrag: Event
10954  		// Fired continuously during dragging *before* each corresponding
10955  		// update of the element's position.
10956  		this.fire('predrag', e);
10957  		setPosition(this._element, this._newPos);
10958
10959  		// @event drag: Event
10960  		// Fired continuously during dragging.
10961  		this.fire('drag', e);
10962  	},
10963
10964  	_onUp: function () {
10965  		// Ignore the event if disabled; this happens in IE11
10966  		// under some circumstances, see #3666.
10967  		if (!this._enabled) { return; }
10968  		this.finishDrag();
10969  	},
10970
10971  	finishDrag: function (noInertia) {
10972  		removeClass(document.body, 'leaflet-dragging');
10973
10974  		if (this._lastTarget) {
10975  			removeClass(this._lastTarget, 'leaflet-drag-target');
10976  			this._lastTarget = null;
10977  		}
10978
10979  		off(document, 'mousemove touchmove', this._onMove, this);
10980  		off(document, 'mouseup touchend touchcancel', this._onUp, this);
10981
10982  		enableImageDrag();
10983  		enableTextSelection();
10984
10985  		var fireDragend = this._moved && this._moving;
10986
10987  		this._moving = false;
10988  		Draggable._dragging = false;
10989
10990  		if (fireDragend) {
10991  			// @event dragend: DragEndEvent
10992  			// Fired when the drag ends.
10993  			this.fire('dragend', {
10994  				noInertia: noInertia,
10995  				distance: this._newPos.distanceTo(this._startPos)
10996  			});
10997  		}
10998  	}
10999
11000  });
11001
11002  /*
11003   * @namespace PolyUtil
11004   * Various utility functions for polygon geometries.
11005   */
11006
11007  /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
11008   * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
11009   * Used by Leaflet to only show polygon points that are on the screen or near, increasing
11010   * performance. Note that polygon points needs different algorithm for clipping
11011   * than polyline, so there's a separate method for it.
11012   */
11013  function clipPolygon(points, bounds, round) {
11014  	var clippedPoints,
11015  	    edges = [1, 4, 2, 8],
11016  	    i, j, k,
11017  	    a, b,
11018  	    len, edge, p;
11019
11020  	for (i = 0, len = points.length; i < len; i++) {
11021  		points[i]._code = _getBitCode(points[i], bounds);
11022  	}
11023
11024  	// for each edge (left, bottom, right, top)
11025  	for (k = 0; k < 4; k++) {
11026  		edge = edges[k];
11027  		clippedPoints = [];
11028
11029  		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
11030  			a = points[i];
11031  			b = points[j];
11032
11033  			// if a is inside the clip window
11034  			if (!(a._code & edge)) {
11035  				// if b is outside the clip window (a->b goes out of screen)
11036  				if (b._code & edge) {
11037  					p = _getEdgeIntersection(b, a, edge, bounds, round);
11038  					p._code = _getBitCode(p, bounds);
11039  					clippedPoints.push(p);
11040  				}
11041  				clippedPoints.push(a);
11042
11043  			// else if b is inside the clip window (a->b enters the screen)
11044  			} else if (!(b._code & edge)) {
11045  				p = _getEdgeIntersection(b, a, edge, bounds, round);
11046  				p._code = _getBitCode(p, bounds);
11047  				clippedPoints.push(p);
11048  			}
11049  		}
11050  		points = clippedPoints;
11051  	}
11052
11053  	return points;
11054  }
11055
11056  /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
11057   * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
11058   */
11059  function polygonCenter(latlngs, crs) {
11060  	var i, j, p1, p2, f, area, x, y, center;
11061
11062  	if (!latlngs || latlngs.length === 0) {
11063  		throw new Error('latlngs not passed');
11064  	}
11065
11066  	if (!isFlat(latlngs)) {
11067  		console.warn('latlngs are not flat! Only the first ring will be used');
11068  		latlngs = latlngs[0];
11069  	}
11070
11071  	var centroidLatLng = toLatLng([0, 0]);
11072
11073  	var bounds = toLatLngBounds(latlngs);
11074  	var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
11075  	// tests showed that below 1700 rounding errors are happening
11076  	if (areaBounds < 1700) {
11077  		// getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
11078  		centroidLatLng = centroid(latlngs);
11079  	}
11080
11081  	var len = latlngs.length;
11082  	var points = [];
11083  	for (i = 0; i < len; i++) {
11084  		var latlng = toLatLng(latlngs[i]);
11085  		points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
11086  	}
11087
11088  	area = x = y = 0;
11089
11090  	// polygon centroid algorithm;
11091  	for (i = 0, j = len - 1; i < len; j = i++) {
11092  		p1 = points[i];
11093  		p2 = points[j];
11094
11095  		f = p1.y * p2.x - p2.y * p1.x;
11096  		x += (p1.x + p2.x) * f;
11097  		y += (p1.y + p2.y) * f;
11098  		area += f * 3;
11099  	}
11100
11101  	if (area === 0) {
11102  		// Polygon is so small that all points are on same pixel.
11103  		center = points[0];
11104  	} else {
11105  		center = [x / area, y / area];
11106  	}
11107
11108  	var latlngCenter = crs.unproject(toPoint(center));
11109  	return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
11110  }
11111
11112  /* @function centroid(latlngs: LatLng[]): LatLng
11113   * Returns the 'center of mass' of the passed LatLngs.
11114   */
11115  function centroid(coords) {
11116  	var latSum = 0;
11117  	var lngSum = 0;
11118  	var len = 0;
11119  	for (var i = 0; i < coords.length; i++) {
11120  		var latlng = toLatLng(coords[i]);
11121  		latSum += latlng.lat;
11122  		lngSum += latlng.lng;
11123  		len++;
11124  	}
11125  	return toLatLng([latSum / len, lngSum / len]);
11126  }
11127
11128  var PolyUtil = {
11129    __proto__: null,
11130    clipPolygon: clipPolygon,
11131    polygonCenter: polygonCenter,
11132    centroid: centroid
11133  };
11134
11135  /*
11136   * @namespace LineUtil
11137   *
11138   * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
11139   */
11140
11141  // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
11142  // Improves rendering performance dramatically by lessening the number of points to draw.
11143
11144  // @function simplify(points: Point[], tolerance: Number): Point[]
11145  // Dramatically reduces the number of points in a polyline while retaining
11146  // its shape and returns a new array of simplified points, using the
11147  // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
11148  // Used for a huge performance boost when processing/displaying Leaflet polylines for
11149  // each zoom level and also reducing visual noise. tolerance affects the amount of
11150  // simplification (lesser value means higher quality but slower and with more points).
11151  // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
11152  function simplify(points, tolerance) {
11153  	if (!tolerance || !points.length) {
11154  		return points.slice();
11155  	}
11156
11157  	var sqTolerance = tolerance * tolerance;
11158
11159  	    // stage 1: vertex reduction
11160  	    points = _reducePoints(points, sqTolerance);
11161
11162  	    // stage 2: Douglas-Peucker simplification
11163  	    points = _simplifyDP(points, sqTolerance);
11164
11165  	return points;
11166  }
11167
11168  // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
11169  // Returns the distance between point `p` and segment `p1` to `p2`.
11170  function pointToSegmentDistance(p, p1, p2) {
11171  	return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
11172  }
11173
11174  // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
11175  // Returns the closest point from a point `p` on a segment `p1` to `p2`.
11176  function closestPointOnSegment(p, p1, p2) {
11177  	return _sqClosestPointOnSegment(p, p1, p2);
11178  }
11179
11180  // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
11181  function _simplifyDP(points, sqTolerance) {
11182
11183  	var len = points.length,
11184  	    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
11185  	    markers = new ArrayConstructor(len);
11186
11187  	    markers[0] = markers[len - 1] = 1;
11188
11189  	_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
11190
11191  	var i,
11192  	    newPoints = [];
11193
11194  	for (i = 0; i < len; i++) {
11195  		if (markers[i]) {
11196  			newPoints.push(points[i]);
11197  		}
11198  	}
11199
11200  	return newPoints;
11201  }
11202
11203  function _simplifyDPStep(points, markers, sqTolerance, first, last) {
11204
11205  	var maxSqDist = 0,
11206  	index, i, sqDist;
11207
11208  	for (i = first + 1; i <= last - 1; i++) {
11209  		sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
11210
11211  		if (sqDist > maxSqDist) {
11212  			index = i;
11213  			maxSqDist = sqDist;
11214  		}
11215  	}
11216
11217  	if (maxSqDist > sqTolerance) {
11218  		markers[index] = 1;
11219
11220  		_simplifyDPStep(points, markers, sqTolerance, first, index);
11221  		_simplifyDPStep(points, markers, sqTolerance, index, last);
11222  	}
11223  }
11224
11225  // reduce points that are too close to each other to a single point
11226  function _reducePoints(points, sqTolerance) {
11227  	var reducedPoints = [points[0]];
11228
11229  	for (var i = 1, prev = 0, len = points.length; i < len; i++) {
11230  		if (_sqDist(points[i], points[prev]) > sqTolerance) {
11231  			reducedPoints.push(points[i]);
11232  			prev = i;
11233  		}
11234  	}
11235  	if (prev < len - 1) {
11236  		reducedPoints.push(points[len - 1]);
11237  	}
11238  	return reducedPoints;
11239  }
11240
11241  var _lastCode;
11242
11243  // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
11244  // Clips the segment a to b by rectangular bounds with the
11245  // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
11246  // (modifying the segment points directly!). Used by Leaflet to only show polyline
11247  // points that are on the screen or near, increasing performance.
11248  function clipSegment(a, b, bounds, useLastCode, round) {
11249  	var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
11250  	    codeB = _getBitCode(b, bounds),
11251
11252  	    codeOut, p, newCode;
11253
11254  	    // save 2nd code to avoid calculating it on the next segment
11255  	    _lastCode = codeB;
11256
11257  	while (true) {
11258  		// if a,b is inside the clip window (trivial accept)
11259  		if (!(codeA | codeB)) {
11260  			return [a, b];
11261  		}
11262
11263  		// if a,b is outside the clip window (trivial reject)
11264  		if (codeA & codeB) {
11265  			return false;
11266  		}
11267
11268  		// other cases
11269  		codeOut = codeA || codeB;
11270  		p = _getEdgeIntersection(a, b, codeOut, bounds, round);
11271  		newCode = _getBitCode(p, bounds);
11272
11273  		if (codeOut === codeA) {
11274  			a = p;
11275  			codeA = newCode;
11276  		} else {
11277  			b = p;
11278  			codeB = newCode;
11279  		}
11280  	}
11281  }
11282
11283  function _getEdgeIntersection(a, b, code, bounds, round) {
11284  	var dx = b.x - a.x,
11285  	    dy = b.y - a.y,
11286  	    min = bounds.min,
11287  	    max = bounds.max,
11288  	    x, y;
11289
11290  	if (code & 8) { // top
11291  		x = a.x + dx * (max.y - a.y) / dy;
11292  		y = max.y;
11293
11294  	} else if (code & 4) { // bottom
11295  		x = a.x + dx * (min.y - a.y) / dy;
11296  		y = min.y;
11297
11298  	} else if (code & 2) { // right
11299  		x = max.x;
11300  		y = a.y + dy * (max.x - a.x) / dx;
11301
11302  	} else if (code & 1) { // left
11303  		x = min.x;
11304  		y = a.y + dy * (min.x - a.x) / dx;
11305  	}
11306
11307  	return new Point(x, y, round);
11308  }
11309
11310  function _getBitCode(p, bounds) {
11311  	var code = 0;
11312
11313  	if (p.x < bounds.min.x) { // left
11314  		code |= 1;
11315  	} else if (p.x > bounds.max.x) { // right
11316  		code |= 2;
11317  	}
11318
11319  	if (p.y < bounds.min.y) { // bottom
11320  		code |= 4;
11321  	} else if (p.y > bounds.max.y) { // top
11322  		code |= 8;
11323  	}
11324
11325  	return code;
11326  }
11327
11328  // square distance (to avoid unnecessary Math.sqrt calls)
11329  function _sqDist(p1, p2) {
11330  	var dx = p2.x - p1.x,
11331  	    dy = p2.y - p1.y;
11332  	return dx * dx + dy * dy;
11333  }
11334
11335  // return closest point on segment or distance to that point
11336  function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
11337  	var x = p1.x,
11338  	    y = p1.y,
11339  	    dx = p2.x - x,
11340  	    dy = p2.y - y,
11341  	    dot = dx * dx + dy * dy,
11342  	    t;
11343
11344  	if (dot > 0) {
11345  		t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
11346
11347  		if (t > 1) {
11348  			x = p2.x;
11349  			y = p2.y;
11350  		} else if (t > 0) {
11351  			x += dx * t;
11352  			y += dy * t;
11353  		}
11354  	}
11355
11356  	dx = p.x - x;
11357  	dy = p.y - y;
11358
11359  	return sqDist ? dx * dx + dy * dy : new Point(x, y);
11360  }
11361
11362
11363  // @function isFlat(latlngs: LatLng[]): Boolean
11364  // Returns true if `latlngs` is a flat array, false is nested.
11365  function isFlat(latlngs) {
11366  	return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
11367  }
11368
11369  function _flat(latlngs) {
11370  	console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
11371  	return isFlat(latlngs);
11372  }
11373
11374  /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
11375   * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
11376   */
11377  function polylineCenter(latlngs, crs) {
11378  	var i, halfDist, segDist, dist, p1, p2, ratio, center;
11379
11380  	if (!latlngs || latlngs.length === 0) {
11381  		throw new Error('latlngs not passed');
11382  	}
11383
11384  	if (!isFlat(latlngs)) {
11385  		console.warn('latlngs are not flat! Only the first ring will be used');
11386  		latlngs = latlngs[0];
11387  	}
11388
11389  	var centroidLatLng = toLatLng([0, 0]);
11390
11391  	var bounds = toLatLngBounds(latlngs);
11392  	var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
11393  	// tests showed that below 1700 rounding errors are happening
11394  	if (areaBounds < 1700) {
11395  		// getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
11396  		centroidLatLng = centroid(latlngs);
11397  	}
11398
11399  	var len = latlngs.length;
11400  	var points = [];
11401  	for (i = 0; i < len; i++) {
11402  		var latlng = toLatLng(latlngs[i]);
11403  		points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
11404  	}
11405
11406  	for (i = 0, halfDist = 0; i < len - 1; i++) {
11407  		halfDist += points[i].distanceTo(points[i + 1]) / 2;
11408  	}
11409
11410  	// The line is so small in the current view that all points are on the same pixel.
11411  	if (halfDist === 0) {
11412  		center = points[0];
11413  	} else {
11414  		for (i = 0, dist = 0; i < len - 1; i++) {
11415  			p1 = points[i];
11416  			p2 = points[i + 1];
11417  			segDist = p1.distanceTo(p2);
11418  			dist += segDist;
11419
11420  			if (dist > halfDist) {
11421  				ratio = (dist - halfDist) / segDist;
11422  				center = [
11423  					p2.x - ratio * (p2.x - p1.x),
11424  					p2.y - ratio * (p2.y - p1.y)
11425  				];
11426  				break;
11427  			}
11428  		}
11429  	}
11430
11431  	var latlngCenter = crs.unproject(toPoint(center));
11432  	return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
11433  }
11434
11435  var LineUtil = {
11436    __proto__: null,
11437    simplify: simplify,
11438    pointToSegmentDistance: pointToSegmentDistance,
11439    closestPointOnSegment: closestPointOnSegment,
11440    clipSegment: clipSegment,
11441    _getEdgeIntersection: _getEdgeIntersection,
11442    _getBitCode: _getBitCode,
11443    _sqClosestPointOnSegment: _sqClosestPointOnSegment,
11444    isFlat: isFlat,
11445    _flat: _flat,
11446    polylineCenter: polylineCenter
11447  };
11448
11449  /*
11450   * @namespace Projection
11451   * @section
11452   * Leaflet comes with a set of already defined Projections out of the box:
11453   *
11454   * @projection L.Projection.LonLat
11455   *
11456   * Equirectangular, or Plate Carree projection — the most simple projection,
11457   * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
11458   * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
11459   * `EPSG:4326` and `Simple` CRS.
11460   */
11461
11462  var LonLat = {
11463  	project: function (latlng) {
11464  		return new Point(latlng.lng, latlng.lat);
11465  	},
11466
11467  	unproject: function (point) {
11468  		return new LatLng(point.y, point.x);
11469  	},
11470
11471  	bounds: new Bounds([-180, -90], [180, 90])
11472  };
11473
11474  /*
11475   * @namespace Projection
11476   * @projection L.Projection.Mercator
11477   *
11478   * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
11479   */
11480
11481  var Mercator = {
11482  	R: 6378137,
11483  	R_MINOR: 6356752.314245179,
11484
11485  	bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
11486
11487  	project: function (latlng) {
11488  		var d = Math.PI / 180,
11489  		    r = this.R,
11490  		    y = latlng.lat * d,
11491  		    tmp = this.R_MINOR / r,
11492  		    e = Math.sqrt(1 - tmp * tmp),
11493  		    con = e * Math.sin(y);
11494
11495  		var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
11496  		y = -r * Math.log(Math.max(ts, 1E-10));
11497
11498  		return new Point(latlng.lng * d * r, y);
11499  	},
11500
11501  	unproject: function (point) {
11502  		var d = 180 / Math.PI,
11503  		    r = this.R,
11504  		    tmp = this.R_MINOR / r,
11505  		    e = Math.sqrt(1 - tmp * tmp),
11506  		    ts = Math.exp(-point.y / r),
11507  		    phi = Math.PI / 2 - 2 * Math.atan(ts);
11508
11509  		for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
11510  			con = e * Math.sin(phi);
11511  			con = Math.pow((1 - con) / (1 + con), e / 2);
11512  			dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
11513  			phi += dphi;
11514  		}
11515
11516  		return new LatLng(phi * d, point.x * d / r);
11517  	}
11518  };
11519
11520  /*
11521   * @class Projection
11522
11523   * An object with methods for projecting geographical coordinates of the world onto
11524   * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
11525
11526   * @property bounds: Bounds
11527   * The bounds (specified in CRS units) where the projection is valid
11528
11529   * @method project(latlng: LatLng): Point
11530   * Projects geographical coordinates into a 2D point.
11531   * Only accepts actual `L.LatLng` instances, not arrays.
11532
11533   * @method unproject(point: Point): LatLng
11534   * The inverse of `project`. Projects a 2D point into a geographical location.
11535   * Only accepts actual `L.Point` instances, not arrays.
11536
11537   * Note that the projection instances do not inherit from Leaflet's `Class` object,
11538   * and can't be instantiated. Also, new classes can't inherit from them,
11539   * and methods can't be added to them with the `include` function.
11540
11541   */
11542
11543  var index = {
11544    __proto__: null,
11545    LonLat: LonLat,
11546    Mercator: Mercator,
11547    SphericalMercator: SphericalMercator
11548  };
11549
11550  /*
11551   * @namespace CRS
11552   * @crs L.CRS.EPSG3395
11553   *
11554   * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
11555   */
11556  var EPSG3395 = extend({}, Earth, {
11557  	code: 'EPSG:3395',
11558  	projection: Mercator,
11559
11560  	transformation: (function () {
11561  		var scale = 0.5 / (Math.PI * Mercator.R);
11562  		return toTransformation(scale, 0.5, -scale, 0.5);
11563  	}())
11564  });
11565
11566  /*
11567   * @namespace CRS
11568   * @crs L.CRS.EPSG4326
11569   *
11570   * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
11571   *
11572   * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
11573   * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
11574   * with this CRS, ensure that there are two 256x256 pixel tiles covering the
11575   * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
11576   * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
11577   */
11578
11579  var EPSG4326 = extend({}, Earth, {
11580  	code: 'EPSG:4326',
11581  	projection: LonLat,
11582  	transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
11583  });
11584
11585  /*
11586   * @namespace CRS
11587   * @crs L.CRS.Simple
11588   *
11589   * A simple CRS that maps longitude and latitude into `x` and `y` directly.
11590   * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
11591   * axis should still be inverted (going from bottom to top). `distance()` returns
11592   * simple euclidean distance.
11593   */
11594
11595  var Simple = extend({}, CRS, {
11596  	projection: LonLat,
11597  	transformation: toTransformation(1, 0, -1, 0),
11598
11599  	scale: function (zoom) {
11600  		return Math.pow(2, zoom);
11601  	},
11602
11603  	zoom: function (scale) {
11604  		return Math.log(scale) / Math.LN2;
11605  	},
11606
11607  	distance: function (latlng1, latlng2) {
11608  		var dx = latlng2.lng - latlng1.lng,
11609  		    dy = latlng2.lat - latlng1.lat;
11610
11611  		return Math.sqrt(dx * dx + dy * dy);
11612  	},
11613
11614  	infinite: true
11615  });
11616
11617  CRS.Earth = Earth;
11618  CRS.EPSG3395 = EPSG3395;
11619  CRS.EPSG3857 = EPSG3857;
11620  CRS.EPSG900913 = EPSG900913;
11621  CRS.EPSG4326 = EPSG4326;
11622  CRS.Simple = Simple;
11623
11624  /*
11625   * @class Layer
11626   * @inherits Evented
11627   * @aka L.Layer
11628   * @aka ILayer
11629   *
11630   * A set of methods from the Layer base class that all Leaflet layers use.
11631   * Inherits all methods, options and events from `L.Evented`.
11632   *
11633   * @example
11634   *
11635   * ```js
11636   * var layer = L.marker(latlng).addTo(map);
11637   * layer.addTo(map);
11638   * layer.remove();
11639   * ```
11640   *
11641   * @event add: Event
11642   * Fired after the layer is added to a map
11643   *
11644   * @event remove: Event
11645   * Fired after the layer is removed from a map
11646   */
11647
11648
11649  var Layer = Evented.extend({
11650
11651  	// Classes extending `L.Layer` will inherit the following options:
11652  	options: {
11653  		// @option pane: String = 'overlayPane'
11654  		// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
11655  		pane: 'overlayPane',
11656
11657  		// @option attribution: String = null
11658  		// String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
11659  		attribution: null,
11660
11661  		bubblingMouseEvents: true
11662  	},
11663
11664  	/* @section
11665  	 * Classes extending `L.Layer` will inherit the following methods:
11666  	 *
11667  	 * @method addTo(map: Map|LayerGroup): this
11668  	 * Adds the layer to the given map or layer group.
11669  	 */
11670  	addTo: function (map) {
11671  		map.addLayer(this);
11672  		return this;
11673  	},
11674
11675  	// @method remove: this
11676  	// Removes the layer from the map it is currently active on.
11677  	remove: function () {
11678  		return this.removeFrom(this._map || this._mapToAdd);
11679  	},
11680
11681  	// @method removeFrom(map: Map): this
11682  	// Removes the layer from the given map
11683  	//
11684  	// @alternative
11685  	// @method removeFrom(group: LayerGroup): this
11686  	// Removes the layer from the given `LayerGroup`
11687  	removeFrom: function (obj) {
11688  		if (obj) {
11689  			obj.removeLayer(this);
11690  		}
11691  		return this;
11692  	},
11693
11694  	// @method getPane(name? : String): HTMLElement
11695  	// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
11696  	getPane: function (name) {
11697  		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
11698  	},
11699
11700  	addInteractiveTarget: function (targetEl) {
11701  		this._map._targets[stamp(targetEl)] = this;
11702  		return this;
11703  	},
11704
11705  	removeInteractiveTarget: function (targetEl) {
11706  		delete this._map._targets[stamp(targetEl)];
11707  		return this;
11708  	},
11709
11710  	// @method getAttribution: String
11711  	// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
11712  	getAttribution: function () {
11713  		return this.options.attribution;
11714  	},
11715
11716  	_layerAdd: function (e) {
11717  		var map = e.target;
11718
11719  		// check in case layer gets added and then removed before the map is ready
11720  		if (!map.hasLayer(this)) { return; }
11721
11722  		this._map = map;
11723  		this._zoomAnimated = map._zoomAnimated;
11724
11725  		if (this.getEvents) {
11726  			var events = this.getEvents();
11727  			map.on(events, this);
11728  			this.once('remove', function () {
11729  				map.off(events, this);
11730  			}, this);
11731  		}
11732
11733  		this.onAdd(map);
11734
11735  		this.fire('add');
11736  		map.fire('layeradd', {layer: this});
11737  	}
11738  });
11739
11740  /* @section Extension methods
11741   * @uninheritable
11742   *
11743   * Every layer should extend from `L.Layer` and (re-)implement the following methods.
11744   *
11745   * @method onAdd(map: Map): this
11746   * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
11747   *
11748   * @method onRemove(map: Map): this
11749   * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
11750   *
11751   * @method getEvents(): Object
11752   * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
11753   *
11754   * @method getAttribution(): String
11755   * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
11756   *
11757   * @method beforeAdd(map: Map): this
11758   * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
11759   */
11760
11761
11762  /* @namespace Map
11763   * @section Layer events
11764   *
11765   * @event layeradd: LayerEvent
11766   * Fired when a new layer is added to the map.
11767   *
11768   * @event layerremove: LayerEvent
11769   * Fired when some layer is removed from the map
11770   *
11771   * @section Methods for Layers and Controls
11772   */
11773  Map.include({
11774  	// @method addLayer(layer: Layer): this
11775  	// Adds the given layer to the map
11776  	addLayer: function (layer) {
11777  		if (!layer._layerAdd) {
11778  			throw new Error('The provided object is not a Layer.');
11779  		}
11780
11781  		var id = stamp(layer);
11782  		if (this._layers[id]) { return this; }
11783  		this._layers[id] = layer;
11784
11785  		layer._mapToAdd = this;
11786
11787  		if (layer.beforeAdd) {
11788  			layer.beforeAdd(this);
11789  		}
11790
11791  		this.whenReady(layer._layerAdd, layer);
11792
11793  		return this;
11794  	},
11795
11796  	// @method removeLayer(layer: Layer): this
11797  	// Removes the given layer from the map.
11798  	removeLayer: function (layer) {
11799  		var id = stamp(layer);
11800
11801  		if (!this._layers[id]) { return this; }
11802
11803  		if (this._loaded) {
11804  			layer.onRemove(this);
11805  		}
11806
11807  		delete this._layers[id];
11808
11809  		if (this._loaded) {
11810  			this.fire('layerremove', {layer: layer});
11811  			layer.fire('remove');
11812  		}
11813
11814  		layer._map = layer._mapToAdd = null;
11815
11816  		return this;
11817  	},
11818
11819  	// @method hasLayer(layer: Layer): Boolean
11820  	// Returns `true` if the given layer is currently added to the map
11821  	hasLayer: function (layer) {
11822  		return stamp(layer) in this._layers;
11823  	},
11824
11825  	/* @method eachLayer(fn: Function, context?: Object): this
11826  	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
11827  	 * ```
11828  	 * map.eachLayer(function(layer){
11829  	 *     layer.bindPopup('Hello');
11830  	 * });
11831  	 * ```
11832  	 */
11833  	eachLayer: function (method, context) {
11834  		for (var i in this._layers) {
11835  			method.call(context, this._layers[i]);
11836  		}
11837  		return this;
11838  	},
11839
11840  	_addLayers: function (layers) {
11841  		layers = layers ? (isArray(layers) ? layers : [layers]) : [];
11842
11843  		for (var i = 0, len = layers.length; i < len; i++) {
11844  			this.addLayer(layers[i]);
11845  		}
11846  	},
11847
11848  	_addZoomLimit: function (layer) {
11849  		if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
11850  			this._zoomBoundLayers[stamp(layer)] = layer;
11851  			this._updateZoomLevels();
11852  		}
11853  	},
11854
11855  	_removeZoomLimit: function (layer) {
11856  		var id = stamp(layer);
11857
11858  		if (this._zoomBoundLayers[id]) {
11859  			delete this._zoomBoundLayers[id];
11860  			this._updateZoomLevels();
11861  		}
11862  	},
11863
11864  	_updateZoomLevels: function () {
11865  		var minZoom = Infinity,
11866  		    maxZoom = -Infinity,
11867  		    oldZoomSpan = this._getZoomSpan();
11868
11869  		for (var i in this._zoomBoundLayers) {
11870  			var options = this._zoomBoundLayers[i].options;
11871
11872  			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
11873  			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
11874  		}
11875
11876  		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
11877  		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
11878
11879  		// @section Map state change events
11880  		// @event zoomlevelschange: Event
11881  		// Fired when the number of zoomlevels on the map is changed due
11882  		// to adding or removing a layer.
11883  		if (oldZoomSpan !== this._getZoomSpan()) {
11884  			this.fire('zoomlevelschange');
11885  		}
11886
11887  		if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
11888  			this.setZoom(this._layersMaxZoom);
11889  		}
11890  		if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
11891  			this.setZoom(this._layersMinZoom);
11892  		}
11893  	}
11894  });
11895
11896  /*
11897   * @class LayerGroup
11898   * @aka L.LayerGroup
11899   * @inherits Interactive layer
11900   *
11901   * Used to group several layers and handle them as one. If you add it to the map,
11902   * any layers added or removed from the group will be added/removed on the map as
11903   * well. Extends `Layer`.
11904   *
11905   * @example
11906   *
11907   * ```js
11908   * L.layerGroup([marker1, marker2])
11909   * 	.addLayer(polyline)
11910   * 	.addTo(map);
11911   * ```
11912   */
11913
11914  var LayerGroup = Layer.extend({
11915
11916  	initialize: function (layers, options) {
11917  		setOptions(this, options);
11918
11919  		this._layers = {};
11920
11921  		var i, len;
11922
11923  		if (layers) {
11924  			for (i = 0, len = layers.length; i < len; i++) {
11925  				this.addLayer(layers[i]);
11926  			}
11927  		}
11928  	},
11929
11930  	// @method addLayer(layer: Layer): this
11931  	// Adds the given layer to the group.
11932  	addLayer: function (layer) {
11933  		var id = this.getLayerId(layer);
11934
11935  		this._layers[id] = layer;
11936
11937  		if (this._map) {
11938  			this._map.addLayer(layer);
11939  		}
11940
11941  		return this;
11942  	},
11943
11944  	// @method removeLayer(layer: Layer): this
11945  	// Removes the given layer from the group.
11946  	// @alternative
11947  	// @method removeLayer(id: Number): this
11948  	// Removes the layer with the given internal ID from the group.
11949  	removeLayer: function (layer) {
11950  		var id = layer in this._layers ? layer : this.getLayerId(layer);
11951
11952  		if (this._map && this._layers[id]) {
11953  			this._map.removeLayer(this._layers[id]);
11954  		}
11955
11956  		delete this._layers[id];
11957
11958  		return this;
11959  	},
11960
11961  	// @method hasLayer(layer: Layer): Boolean
11962  	// Returns `true` if the given layer is currently added to the group.
11963  	// @alternative
11964  	// @method hasLayer(id: Number): Boolean
11965  	// Returns `true` if the given internal ID is currently added to the group.
11966  	hasLayer: function (layer) {
11967  		var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
11968  		return layerId in this._layers;
11969  	},
11970
11971  	// @method clearLayers(): this
11972  	// Removes all the layers from the group.
11973  	clearLayers: function () {
11974  		return this.eachLayer(this.removeLayer, this);
11975  	},
11976
11977  	// @method invoke(methodName: String, …): this
11978  	// Calls `methodName` on every layer contained in this group, passing any
11979  	// additional parameters. Has no effect if the layers contained do not
11980  	// implement `methodName`.
11981  	invoke: function (methodName) {
11982  		var args = Array.prototype.slice.call(arguments, 1),
11983  		    i, layer;
11984
11985  		for (i in this._layers) {
11986  			layer = this._layers[i];
11987
11988  			if (layer[methodName]) {
11989  				layer[methodName].apply(layer, args);
11990  			}
11991  		}
11992
11993  		return this;
11994  	},
11995
11996  	onAdd: function (map) {
11997  		this.eachLayer(map.addLayer, map);
11998  	},
11999
12000  	onRemove: function (map) {
12001  		this.eachLayer(map.removeLayer, map);
12002  	},
12003
12004  	// @method eachLayer(fn: Function, context?: Object): this
12005  	// Iterates over the layers of the group, optionally specifying context of the iterator function.
12006  	// ```js
12007  	// group.eachLayer(function (layer) {
12008  	// 	layer.bindPopup('Hello');
12009  	// });
12010  	// ```
12011  	eachLayer: function (method, context) {
12012  		for (var i in this._layers) {
12013  			method.call(context, this._layers[i]);
12014  		}
12015  		return this;
12016  	},
12017
12018  	// @method getLayer(id: Number): Layer
12019  	// Returns the layer with the given internal ID.
12020  	getLayer: function (id) {
12021  		return this._layers[id];
12022  	},
12023
12024  	// @method getLayers(): Layer[]
12025  	// Returns an array of all the layers added to the group.
12026  	getLayers: function () {
12027  		var layers = [];
12028  		this.eachLayer(layers.push, layers);
12029  		return layers;
12030  	},
12031
12032  	// @method setZIndex(zIndex: Number): this
12033  	// Calls `setZIndex` on every layer contained in this group, passing the z-index.
12034  	setZIndex: function (zIndex) {
12035  		return this.invoke('setZIndex', zIndex);
12036  	},
12037
12038  	// @method getLayerId(layer: Layer): Number
12039  	// Returns the internal ID for a layer
12040  	getLayerId: function (layer) {
12041  		return stamp(layer);
12042  	}
12043  });
12044
12045
12046  // @factory L.layerGroup(layers?: Layer[], options?: Object)
12047  // Create a layer group, optionally given an initial set of layers and an `options` object.
12048  var layerGroup = function (layers, options) {
12049  	return new LayerGroup(layers, options);
12050  };
12051
12052  /*
12053   * @class FeatureGroup
12054   * @aka L.FeatureGroup
12055   * @inherits LayerGroup
12056   *
12057   * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
12058   *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
12059   *  * Events are propagated to the `FeatureGroup`, so if the group has an event
12060   * handler, it will handle events from any of the layers. This includes mouse events
12061   * and custom events.
12062   *  * Has `layeradd` and `layerremove` events
12063   *
12064   * @example
12065   *
12066   * ```js
12067   * L.featureGroup([marker1, marker2, polyline])
12068   * 	.bindPopup('Hello world!')
12069   * 	.on('click', function() { alert('Clicked on a member of the group!'); })
12070   * 	.addTo(map);
12071   * ```
12072   */
12073
12074  var FeatureGroup = LayerGroup.extend({
12075
12076  	addLayer: function (layer) {
12077  		if (this.hasLayer(layer)) {
12078  			return this;
12079  		}
12080
12081  		layer.addEventParent(this);
12082
12083  		LayerGroup.prototype.addLayer.call(this, layer);
12084
12085  		// @event layeradd: LayerEvent
12086  		// Fired when a layer is added to this `FeatureGroup`
12087  		return this.fire('layeradd', {layer: layer});
12088  	},
12089
12090  	removeLayer: function (layer) {
12091  		if (!this.hasLayer(layer)) {
12092  			return this;
12093  		}
12094  		if (layer in this._layers) {
12095  			layer = this._layers[layer];
12096  		}
12097
12098  		layer.removeEventParent(this);
12099
12100  		LayerGroup.prototype.removeLayer.call(this, layer);
12101
12102  		// @event layerremove: LayerEvent
12103  		// Fired when a layer is removed from this `FeatureGroup`
12104  		return this.fire('layerremove', {layer: layer});
12105  	},
12106
12107  	// @method setStyle(style: Path options): this
12108  	// Sets the given path options to each layer of the group that has a `setStyle` method.
12109  	setStyle: function (style) {
12110  		return this.invoke('setStyle', style);
12111  	},
12112
12113  	// @method bringToFront(): this
12114  	// Brings the layer group to the top of all other layers
12115  	bringToFront: function () {
12116  		return this.invoke('bringToFront');
12117  	},
12118
12119  	// @method bringToBack(): this
12120  	// Brings the layer group to the back of all other layers
12121  	bringToBack: function () {
12122  		return this.invoke('bringToBack');
12123  	},
12124
12125  	// @method getBounds(): LatLngBounds
12126  	// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
12127  	getBounds: function () {
12128  		var bounds = new LatLngBounds();
12129
12130  		for (var id in this._layers) {
12131  			var layer = this._layers[id];
12132  			bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
12133  		}
12134  		return bounds;
12135  	}
12136  });
12137
12138  // @factory L.featureGroup(layers?: Layer[], options?: Object)
12139  // Create a feature group, optionally given an initial set of layers and an `options` object.
12140  var featureGroup = function (layers, options) {
12141  	return new FeatureGroup(layers, options);
12142  };
12143
12144  /*
12145   * @class Icon
12146   * @aka L.Icon
12147   *
12148   * Represents an icon to provide when creating a marker.
12149   *
12150   * @example
12151   *
12152   * ```js
12153   * var myIcon = L.icon({
12154   *     iconUrl: 'my-icon.png',
12155   *     iconRetinaUrl: '[email protected]',
12156   *     iconSize: [38, 95],
12157   *     iconAnchor: [22, 94],
12158   *     popupAnchor: [-3, -76],
12159   *     shadowUrl: 'my-icon-shadow.png',
12160   *     shadowRetinaUrl: '[email protected]',
12161   *     shadowSize: [68, 95],
12162   *     shadowAnchor: [22, 94]
12163   * });
12164   *
12165   * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
12166   * ```
12167   *
12168   * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
12169   *
12170   */
12171
12172  var Icon = Class.extend({
12173
12174  	/* @section
12175  	 * @aka Icon options
12176  	 *
12177  	 * @option iconUrl: String = null
12178  	 * **(required)** The URL to the icon image (absolute or relative to your script path).
12179  	 *
12180  	 * @option iconRetinaUrl: String = null
12181  	 * The URL to a retina sized version of the icon image (absolute or relative to your
12182  	 * script path). Used for Retina screen devices.
12183  	 *
12184  	 * @option iconSize: Point = null
12185  	 * Size of the icon image in pixels.
12186  	 *
12187  	 * @option iconAnchor: Point = null
12188  	 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
12189  	 * will be aligned so that this point is at the marker's geographical location. Centered
12190  	 * by default if size is specified, also can be set in CSS with negative margins.
12191  	 *
12192  	 * @option popupAnchor: Point = [0, 0]
12193  	 * The coordinates of the point from which popups will "open", relative to the icon anchor.
12194  	 *
12195  	 * @option tooltipAnchor: Point = [0, 0]
12196  	 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
12197  	 *
12198  	 * @option shadowUrl: String = null
12199  	 * The URL to the icon shadow image. If not specified, no shadow image will be created.
12200  	 *
12201  	 * @option shadowRetinaUrl: String = null
12202  	 *
12203  	 * @option shadowSize: Point = null
12204  	 * Size of the shadow image in pixels.
12205  	 *
12206  	 * @option shadowAnchor: Point = null
12207  	 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
12208  	 * as iconAnchor if not specified).
12209  	 *
12210  	 * @option className: String = ''
12211  	 * A custom class name to assign to both icon and shadow images. Empty by default.
12212  	 */
12213
12214  	options: {
12215  		popupAnchor: [0, 0],
12216  		tooltipAnchor: [0, 0],
12217
12218  		// @option crossOrigin: Boolean|String = false
12219  		// Whether the crossOrigin attribute will be added to the tiles.
12220  		// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
12221  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
12222  		crossOrigin: false
12223  	},
12224
12225  	initialize: function (options) {
12226  		setOptions(this, options);
12227  	},
12228
12229  	// @method createIcon(oldIcon?: HTMLElement): HTMLElement
12230  	// Called internally when the icon has to be shown, returns a `<img>` HTML element
12231  	// styled according to the options.
12232  	createIcon: function (oldIcon) {
12233  		return this._createIcon('icon', oldIcon);
12234  	},
12235
12236  	// @method createShadow(oldIcon?: HTMLElement): HTMLElement
12237  	// As `createIcon`, but for the shadow beneath it.
12238  	createShadow: function (oldIcon) {
12239  		return this._createIcon('shadow', oldIcon);
12240  	},
12241
12242  	_createIcon: function (name, oldIcon) {
12243  		var src = this._getIconUrl(name);
12244
12245  		if (!src) {
12246  			if (name === 'icon') {
12247  				throw new Error('iconUrl not set in Icon options (see the docs).');
12248  			}
12249  			return null;
12250  		}
12251
12252  		var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
12253  		this._setIconStyles(img, name);
12254
12255  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
12256  			img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
12257  		}
12258
12259  		return img;
12260  	},
12261
12262  	_setIconStyles: function (img, name) {
12263  		var options = this.options;
12264  		var sizeOption = options[name + 'Size'];
12265
12266  		if (typeof sizeOption === 'number') {
12267  			sizeOption = [sizeOption, sizeOption];
12268  		}
12269
12270  		var size = toPoint(sizeOption),
12271  		    anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
12272  		            size && size.divideBy(2, true));
12273
12274  		img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
12275
12276  		if (anchor) {
12277  			img.style.marginLeft = (-anchor.x) + 'px';
12278  			img.style.marginTop  = (-anchor.y) + 'px';
12279  		}
12280
12281  		if (size) {
12282  			img.style.width  = size.x + 'px';
12283  			img.style.height = size.y + 'px';
12284  		}
12285  	},
12286
12287  	_createImg: function (src, el) {
12288  		el = el || document.createElement('img');
12289  		el.src = src;
12290  		return el;
12291  	},
12292
12293  	_getIconUrl: function (name) {
12294  		return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
12295  	}
12296  });
12297
12298
12299  // @factory L.icon(options: Icon options)
12300  // Creates an icon instance with the given options.
12301  function icon(options) {
12302  	return new Icon(options);
12303  }
12304
12305  /*
12306   * @miniclass Icon.Default (Icon)
12307   * @aka L.Icon.Default
12308   * @section
12309   *
12310   * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
12311   * no icon is specified. Points to the blue marker image distributed with Leaflet
12312   * releases.
12313   *
12314   * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
12315   * (which is a set of `Icon options`).
12316   *
12317   * If you want to _completely_ replace the default icon, override the
12318   * `L.Marker.prototype.options.icon` with your own icon instead.
12319   */
12320
12321  var IconDefault = Icon.extend({
12322
12323  	options: {
12324  		iconUrl:       'marker-icon.png',
12325  		iconRetinaUrl: 'marker-icon-2x.png',
12326  		shadowUrl:     'marker-shadow.png',
12327  		iconSize:    [25, 41],
12328  		iconAnchor:  [12, 41],
12329  		popupAnchor: [1, -34],
12330  		tooltipAnchor: [16, -28],
12331  		shadowSize:  [41, 41]
12332  	},
12333
12334  	_getIconUrl: function (name) {
12335  		if (typeof IconDefault.imagePath !== 'string') {	// Deprecated, backwards-compatibility only
12336  			IconDefault.imagePath = this._detectIconPath();
12337  		}
12338
12339  		// @option imagePath: String
12340  		// `Icon.Default` will try to auto-detect the location of the
12341  		// blue icon images. If you are placing these images in a non-standard
12342  		// way, set this option to point to the right path.
12343  		return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
12344  	},
12345
12346  	_stripUrl: function (path) {	// separate function to use in tests
12347  		var strip = function (str, re, idx) {
12348  			var match = re.exec(str);
12349  			return match && match[idx];
12350  		};
12351  		path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
12352  		return path && strip(path, /^(.*)marker-icon\.png$/, 1);
12353  	},
12354
12355  	_detectIconPath: function () {
12356  		var el = create$1('div',  'leaflet-default-icon-path', document.body);
12357  		var path = getStyle(el, 'background-image') ||
12358  		           getStyle(el, 'backgroundImage');	// IE8
12359
12360  		document.body.removeChild(el);
12361  		path = this._stripUrl(path);
12362  		if (path) { return path; }
12363  		var link = document.querySelector('link[href$="leaflet.css"]');
12364  		if (!link) { return ''; }
12365  		return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
12366  	}
12367  });
12368
12369  /*
12370   * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
12371   */
12372
12373
12374  /* @namespace Marker
12375   * @section Interaction handlers
12376   *
12377   * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
12378   *
12379   * ```js
12380   * marker.dragging.disable();
12381   * ```
12382   *
12383   * @property dragging: Handler
12384   * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
12385   */
12386
12387  var MarkerDrag = Handler.extend({
12388  	initialize: function (marker) {
12389  		this._marker = marker;
12390  	},
12391
12392  	addHooks: function () {
12393  		var icon = this._marker._icon;
12394
12395  		if (!this._draggable) {
12396  			this._draggable = new Draggable(icon, icon, true);
12397  		}
12398
12399  		this._draggable.on({
12400  			dragstart: this._onDragStart,
12401  			predrag: this._onPreDrag,
12402  			drag: this._onDrag,
12403  			dragend: this._onDragEnd
12404  		}, this).enable();
12405
12406  		addClass(icon, 'leaflet-marker-draggable');
12407  	},
12408
12409  	removeHooks: function () {
12410  		this._draggable.off({
12411  			dragstart: this._onDragStart,
12412  			predrag: this._onPreDrag,
12413  			drag: this._onDrag,
12414  			dragend: this._onDragEnd
12415  		}, this).disable();
12416
12417  		if (this._marker._icon) {
12418  			removeClass(this._marker._icon, 'leaflet-marker-draggable');
12419  		}
12420  	},
12421
12422  	moved: function () {
12423  		return this._draggable && this._draggable._moved;
12424  	},
12425
12426  	_adjustPan: function (e) {
12427  		var marker = this._marker,
12428  		    map = marker._map,
12429  		    speed = this._marker.options.autoPanSpeed,
12430  		    padding = this._marker.options.autoPanPadding,
12431  		    iconPos = getPosition(marker._icon),
12432  		    bounds = map.getPixelBounds(),
12433  		    origin = map.getPixelOrigin();
12434
12435  		var panBounds = toBounds(
12436  			bounds.min._subtract(origin).add(padding),
12437  			bounds.max._subtract(origin).subtract(padding)
12438  		);
12439
12440  		if (!panBounds.contains(iconPos)) {
12441  			// Compute incremental movement
12442  			var movement = toPoint(
12443  				(Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
12444  				(Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
12445
12446  				(Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
12447  				(Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
12448  			).multiplyBy(speed);
12449
12450  			map.panBy(movement, {animate: false});
12451
12452  			this._draggable._newPos._add(movement);
12453  			this._draggable._startPos._add(movement);
12454
12455  			setPosition(marker._icon, this._draggable._newPos);
12456  			this._onDrag(e);
12457
12458  			this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
12459  		}
12460  	},
12461
12462  	_onDragStart: function () {
12463  		// @section Dragging events
12464  		// @event dragstart: Event
12465  		// Fired when the user starts dragging the marker.
12466
12467  		// @event movestart: Event
12468  		// Fired when the marker starts moving (because of dragging).
12469
12470  		this._oldLatLng = this._marker.getLatLng();
12471
12472  		// When using ES6 imports it could not be set when `Popup` was not imported as well
12473  		this._marker.closePopup && this._marker.closePopup();
12474
12475  		this._marker
12476  			.fire('movestart')
12477  			.fire('dragstart');
12478  	},
12479
12480  	_onPreDrag: function (e) {
12481  		if (this._marker.options.autoPan) {
12482  			cancelAnimFrame(this._panRequest);
12483  			this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
12484  		}
12485  	},
12486
12487  	_onDrag: function (e) {
12488  		var marker = this._marker,
12489  		    shadow = marker._shadow,
12490  		    iconPos = getPosition(marker._icon),
12491  		    latlng = marker._map.layerPointToLatLng(iconPos);
12492
12493  		// update shadow position
12494  		if (shadow) {
12495  			setPosition(shadow, iconPos);
12496  		}
12497
12498  		marker._latlng = latlng;
12499  		e.latlng = latlng;
12500  		e.oldLatLng = this._oldLatLng;
12501
12502  		// @event drag: Event
12503  		// Fired repeatedly while the user drags the marker.
12504  		marker
12505  		    .fire('move', e)
12506  		    .fire('drag', e);
12507  	},
12508
12509  	_onDragEnd: function (e) {
12510  		// @event dragend: DragEndEvent
12511  		// Fired when the user stops dragging the marker.
12512
12513  		 cancelAnimFrame(this._panRequest);
12514
12515  		// @event moveend: Event
12516  		// Fired when the marker stops moving (because of dragging).
12517  		delete this._oldLatLng;
12518  		this._marker
12519  		    .fire('moveend')
12520  		    .fire('dragend', e);
12521  	}
12522  });
12523
12524  /*
12525   * @class Marker
12526   * @inherits Interactive layer
12527   * @aka L.Marker
12528   * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
12529   *
12530   * @example
12531   *
12532   * ```js
12533   * L.marker([50.5, 30.5]).addTo(map);
12534   * ```
12535   */
12536
12537  var Marker = Layer.extend({
12538
12539  	// @section
12540  	// @aka Marker options
12541  	options: {
12542  		// @option icon: Icon = *
12543  		// Icon instance to use for rendering the marker.
12544  		// See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
12545  		// If not specified, a common instance of `L.Icon.Default` is used.
12546  		icon: new IconDefault(),
12547
12548  		// Option inherited from "Interactive layer" abstract class
12549  		interactive: true,
12550
12551  		// @option keyboard: Boolean = true
12552  		// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
12553  		keyboard: true,
12554
12555  		// @option title: String = ''
12556  		// Text for the browser tooltip that appear on marker hover (no tooltip by default).
12557  		// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
12558  		title: '',
12559
12560  		// @option alt: String = 'Marker'
12561  		// Text for the `alt` attribute of the icon image.
12562  		// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
12563  		alt: 'Marker',
12564
12565  		// @option zIndexOffset: Number = 0
12566  		// By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
12567  		zIndexOffset: 0,
12568
12569  		// @option opacity: Number = 1.0
12570  		// The opacity of the marker.
12571  		opacity: 1,
12572
12573  		// @option riseOnHover: Boolean = false
12574  		// If `true`, the marker will get on top of others when you hover the mouse over it.
12575  		riseOnHover: false,
12576
12577  		// @option riseOffset: Number = 250
12578  		// The z-index offset used for the `riseOnHover` feature.
12579  		riseOffset: 250,
12580
12581  		// @option pane: String = 'markerPane'
12582  		// `Map pane` where the markers icon will be added.
12583  		pane: 'markerPane',
12584
12585  		// @option shadowPane: String = 'shadowPane'
12586  		// `Map pane` where the markers shadow will be added.
12587  		shadowPane: 'shadowPane',
12588
12589  		// @option bubblingMouseEvents: Boolean = false
12590  		// When `true`, a mouse event on this marker will trigger the same event on the map
12591  		// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
12592  		bubblingMouseEvents: false,
12593
12594  		// @option autoPanOnFocus: Boolean = true
12595  		// When `true`, the map will pan whenever the marker is focused (via
12596  		// e.g. pressing `tab` on the keyboard) to ensure the marker is
12597  		// visible within the map's bounds
12598  		autoPanOnFocus: true,
12599
12600  		// @section Draggable marker options
12601  		// @option draggable: Boolean = false
12602  		// Whether the marker is draggable with mouse/touch or not.
12603  		draggable: false,
12604
12605  		// @option autoPan: Boolean = false
12606  		// Whether to pan the map when dragging this marker near its edge or not.
12607  		autoPan: false,
12608
12609  		// @option autoPanPadding: Point = Point(50, 50)
12610  		// Distance (in pixels to the left/right and to the top/bottom) of the
12611  		// map edge to start panning the map.
12612  		autoPanPadding: [50, 50],
12613
12614  		// @option autoPanSpeed: Number = 10
12615  		// Number of pixels the map should pan by.
12616  		autoPanSpeed: 10
12617  	},
12618
12619  	/* @section
12620  	 *
12621  	 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
12622  	 */
12623
12624  	initialize: function (latlng, options) {
12625  		setOptions(this, options);
12626  		this._latlng = toLatLng(latlng);
12627  	},
12628
12629  	onAdd: function (map) {
12630  		this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
12631
12632  		if (this._zoomAnimated) {
12633  			map.on('zoomanim', this._animateZoom, this);
12634  		}
12635
12636  		this._initIcon();
12637  		this.update();
12638  	},
12639
12640  	onRemove: function (map) {
12641  		if (this.dragging && this.dragging.enabled()) {
12642  			this.options.draggable = true;
12643  			this.dragging.removeHooks();
12644  		}
12645  		delete this.dragging;
12646
12647  		if (this._zoomAnimated) {
12648  			map.off('zoomanim', this._animateZoom, this);
12649  		}
12650
12651  		this._removeIcon();
12652  		this._removeShadow();
12653  	},
12654
12655  	getEvents: function () {
12656  		return {
12657  			zoom: this.update,
12658  			viewreset: this.update
12659  		};
12660  	},
12661
12662  	// @method getLatLng: LatLng
12663  	// Returns the current geographical position of the marker.
12664  	getLatLng: function () {
12665  		return this._latlng;
12666  	},
12667
12668  	// @method setLatLng(latlng: LatLng): this
12669  	// Changes the marker position to the given point.
12670  	setLatLng: function (latlng) {
12671  		var oldLatLng = this._latlng;
12672  		this._latlng = toLatLng(latlng);
12673  		this.update();
12674
12675  		// @event move: Event
12676  		// Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
12677  		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
12678  	},
12679
12680  	// @method setZIndexOffset(offset: Number): this
12681  	// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
12682  	setZIndexOffset: function (offset) {
12683  		this.options.zIndexOffset = offset;
12684  		return this.update();
12685  	},
12686
12687  	// @method getIcon: Icon
12688  	// Returns the current icon used by the marker
12689  	getIcon: function () {
12690  		return this.options.icon;
12691  	},
12692
12693  	// @method setIcon(icon: Icon): this
12694  	// Changes the marker icon.
12695  	setIcon: function (icon) {
12696
12697  		this.options.icon = icon;
12698
12699  		if (this._map) {
12700  			this._initIcon();
12701  			this.update();
12702  		}
12703
12704  		if (this._popup) {
12705  			this.bindPopup(this._popup, this._popup.options);
12706  		}
12707
12708  		return this;
12709  	},
12710
12711  	getElement: function () {
12712  		return this._icon;
12713  	},
12714
12715  	update: function () {
12716
12717  		if (this._icon && this._map) {
12718  			var pos = this._map.latLngToLayerPoint(this._latlng).round();
12719  			this._setPos(pos);
12720  		}
12721
12722  		return this;
12723  	},
12724
12725  	_initIcon: function () {
12726  		var options = this.options,
12727  		    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
12728
12729  		var icon = options.icon.createIcon(this._icon),
12730  		    addIcon = false;
12731
12732  		// if we're not reusing the icon, remove the old one and init new one
12733  		if (icon !== this._icon) {
12734  			if (this._icon) {
12735  				this._removeIcon();
12736  			}
12737  			addIcon = true;
12738
12739  			if (options.title) {
12740  				icon.title = options.title;
12741  			}
12742
12743  			if (icon.tagName === 'IMG') {
12744  				icon.alt = options.alt || '';
12745  			}
12746  		}
12747
12748  		addClass(icon, classToAdd);
12749
12750  		if (options.keyboard) {
12751  			icon.tabIndex = '0';
12752  			icon.setAttribute('role', 'button');
12753  		}
12754
12755  		this._icon = icon;
12756
12757  		if (options.riseOnHover) {
12758  			this.on({
12759  				mouseover: this._bringToFront,
12760  				mouseout: this._resetZIndex
12761  			});
12762  		}
12763
12764  		if (this.options.autoPanOnFocus) {
12765  			on(icon, 'focus', this._panOnFocus, this);
12766  		}
12767
12768  		var newShadow = options.icon.createShadow(this._shadow),
12769  		    addShadow = false;
12770
12771  		if (newShadow !== this._shadow) {
12772  			this._removeShadow();
12773  			addShadow = true;
12774  		}
12775
12776  		if (newShadow) {
12777  			addClass(newShadow, classToAdd);
12778  			newShadow.alt = '';
12779  		}
12780  		this._shadow = newShadow;
12781
12782
12783  		if (options.opacity < 1) {
12784  			this._updateOpacity();
12785  		}
12786
12787
12788  		if (addIcon) {
12789  			this.getPane().appendChild(this._icon);
12790  		}
12791  		this._initInteraction();
12792  		if (newShadow && addShadow) {
12793  			this.getPane(options.shadowPane).appendChild(this._shadow);
12794  		}
12795  	},
12796
12797  	_removeIcon: function () {
12798  		if (this.options.riseOnHover) {
12799  			this.off({
12800  				mouseover: this._bringToFront,
12801  				mouseout: this._resetZIndex
12802  			});
12803  		}
12804
12805  		if (this.options.autoPanOnFocus) {
12806  			off(this._icon, 'focus', this._panOnFocus, this);
12807  		}
12808
12809  		remove(this._icon);
12810  		this.removeInteractiveTarget(this._icon);
12811
12812  		this._icon = null;
12813  	},
12814
12815  	_removeShadow: function () {
12816  		if (this._shadow) {
12817  			remove(this._shadow);
12818  		}
12819  		this._shadow = null;
12820  	},
12821
12822  	_setPos: function (pos) {
12823
12824  		if (this._icon) {
12825  			setPosition(this._icon, pos);
12826  		}
12827
12828  		if (this._shadow) {
12829  			setPosition(this._shadow, pos);
12830  		}
12831
12832  		this._zIndex = pos.y + this.options.zIndexOffset;
12833
12834  		this._resetZIndex();
12835  	},
12836
12837  	_updateZIndex: function (offset) {
12838  		if (this._icon) {
12839  			this._icon.style.zIndex = this._zIndex + offset;
12840  		}
12841  	},
12842
12843  	_animateZoom: function (opt) {
12844  		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
12845
12846  		this._setPos(pos);
12847  	},
12848
12849  	_initInteraction: function () {
12850
12851  		if (!this.options.interactive) { return; }
12852
12853  		addClass(this._icon, 'leaflet-interactive');
12854
12855  		this.addInteractiveTarget(this._icon);
12856
12857  		if (MarkerDrag) {
12858  			var draggable = this.options.draggable;
12859  			if (this.dragging) {
12860  				draggable = this.dragging.enabled();
12861  				this.dragging.disable();
12862  			}
12863
12864  			this.dragging = new MarkerDrag(this);
12865
12866  			if (draggable) {
12867  				this.dragging.enable();
12868  			}
12869  		}
12870  	},
12871
12872  	// @method setOpacity(opacity: Number): this
12873  	// Changes the opacity of the marker.
12874  	setOpacity: function (opacity) {
12875  		this.options.opacity = opacity;
12876  		if (this._map) {
12877  			this._updateOpacity();
12878  		}
12879
12880  		return this;
12881  	},
12882
12883  	_updateOpacity: function () {
12884  		var opacity = this.options.opacity;
12885
12886  		if (this._icon) {
12887  			setOpacity(this._icon, opacity);
12888  		}
12889
12890  		if (this._shadow) {
12891  			setOpacity(this._shadow, opacity);
12892  		}
12893  	},
12894
12895  	_bringToFront: function () {
12896  		this._updateZIndex(this.options.riseOffset);
12897  	},
12898
12899  	_resetZIndex: function () {
12900  		this._updateZIndex(0);
12901  	},
12902
12903  	_panOnFocus: function () {
12904  		var map = this._map;
12905  		if (!map) { return; }
12906
12907  		var iconOpts = this.options.icon.options;
12908  		var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
12909  		var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
12910
12911  		map.panInside(this._latlng, {
12912  			paddingTopLeft: anchor,
12913  			paddingBottomRight: size.subtract(anchor)
12914  		});
12915  	},
12916
12917  	_getPopupAnchor: function () {
12918  		return this.options.icon.options.popupAnchor;
12919  	},
12920
12921  	_getTooltipAnchor: function () {
12922  		return this.options.icon.options.tooltipAnchor;
12923  	}
12924  });
12925
12926
12927  // factory L.marker(latlng: LatLng, options? : Marker options)
12928
12929  // @factory L.marker(latlng: LatLng, options? : Marker options)
12930  // Instantiates a Marker object given a geographical point and optionally an options object.
12931  function marker(latlng, options) {
12932  	return new Marker(latlng, options);
12933  }
12934
12935  /*
12936   * @class Path
12937   * @aka L.Path
12938   * @inherits Interactive layer
12939   *
12940   * An abstract class that contains options and constants shared between vector
12941   * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
12942   */
12943
12944  var Path = Layer.extend({
12945
12946  	// @section
12947  	// @aka Path options
12948  	options: {
12949  		// @option stroke: Boolean = true
12950  		// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
12951  		stroke: true,
12952
12953  		// @option color: String = '#3388ff'
12954  		// Stroke color
12955  		color: '#3388ff',
12956
12957  		// @option weight: Number = 3
12958  		// Stroke width in pixels
12959  		weight: 3,
12960
12961  		// @option opacity: Number = 1.0
12962  		// Stroke opacity
12963  		opacity: 1,
12964
12965  		// @option lineCap: String= 'round'
12966  		// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
12967  		lineCap: 'round',
12968
12969  		// @option lineJoin: String = 'round'
12970  		// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
12971  		lineJoin: 'round',
12972
12973  		// @option dashArray: String = null
12974  		// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
12975  		dashArray: null,
12976
12977  		// @option dashOffset: String = null
12978  		// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
12979  		dashOffset: null,
12980
12981  		// @option fill: Boolean = depends
12982  		// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
12983  		fill: false,
12984
12985  		// @option fillColor: String = *
12986  		// Fill color. Defaults to the value of the [`color`](#path-color) option
12987  		fillColor: null,
12988
12989  		// @option fillOpacity: Number = 0.2
12990  		// Fill opacity.
12991  		fillOpacity: 0.2,
12992
12993  		// @option fillRule: String = 'evenodd'
12994  		// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
12995  		fillRule: 'evenodd',
12996
12997  		// className: '',
12998
12999  		// Option inherited from "Interactive layer" abstract class
13000  		interactive: true,
13001
13002  		// @option bubblingMouseEvents: Boolean = true
13003  		// When `true`, a mouse event on this path will trigger the same event on the map
13004  		// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
13005  		bubblingMouseEvents: true
13006  	},
13007
13008  	beforeAdd: function (map) {
13009  		// Renderer is set here because we need to call renderer.getEvents
13010  		// before this.getEvents.
13011  		this._renderer = map.getRenderer(this);
13012  	},
13013
13014  	onAdd: function () {
13015  		this._renderer._initPath(this);
13016  		this._reset();
13017  		this._renderer._addPath(this);
13018  	},
13019
13020  	onRemove: function () {
13021  		this._renderer._removePath(this);
13022  	},
13023
13024  	// @method redraw(): this
13025  	// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
13026  	redraw: function () {
13027  		if (this._map) {
13028  			this._renderer._updatePath(this);
13029  		}
13030  		return this;
13031  	},
13032
13033  	// @method setStyle(style: Path options): this
13034  	// Changes the appearance of a Path based on the options in the `Path options` object.
13035  	setStyle: function (style) {
13036  		setOptions(this, style);
13037  		if (this._renderer) {
13038  			this._renderer._updateStyle(this);
13039  			if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
13040  				this._updateBounds();
13041  			}
13042  		}
13043  		return this;
13044  	},
13045
13046  	// @method bringToFront(): this
13047  	// Brings the layer to the top of all path layers.
13048  	bringToFront: function () {
13049  		if (this._renderer) {
13050  			this._renderer._bringToFront(this);
13051  		}
13052  		return this;
13053  	},
13054
13055  	// @method bringToBack(): this
13056  	// Brings the layer to the bottom of all path layers.
13057  	bringToBack: function () {
13058  		if (this._renderer) {
13059  			this._renderer._bringToBack(this);
13060  		}
13061  		return this;
13062  	},
13063
13064  	getElement: function () {
13065  		return this._path;
13066  	},
13067
13068  	_reset: function () {
13069  		// defined in child classes
13070  		this._project();
13071  		this._update();
13072  	},
13073
13074  	_clickTolerance: function () {
13075  		// used when doing hit detection for Canvas layers
13076  		return (this.options.stroke ? this.options.weight / 2 : 0) +
13077  		  (this._renderer.options.tolerance || 0);
13078  	}
13079  });
13080
13081  /*
13082   * @class CircleMarker
13083   * @aka L.CircleMarker
13084   * @inherits Path
13085   *
13086   * A circle of a fixed size with radius specified in pixels. Extends `Path`.
13087   */
13088
13089  var CircleMarker = Path.extend({
13090
13091  	// @section
13092  	// @aka CircleMarker options
13093  	options: {
13094  		fill: true,
13095
13096  		// @option radius: Number = 10
13097  		// Radius of the circle marker, in pixels
13098  		radius: 10
13099  	},
13100
13101  	initialize: function (latlng, options) {
13102  		setOptions(this, options);
13103  		this._latlng = toLatLng(latlng);
13104  		this._radius = this.options.radius;
13105  	},
13106
13107  	// @method setLatLng(latLng: LatLng): this
13108  	// Sets the position of a circle marker to a new location.
13109  	setLatLng: function (latlng) {
13110  		var oldLatLng = this._latlng;
13111  		this._latlng = toLatLng(latlng);
13112  		this.redraw();
13113
13114  		// @event move: Event
13115  		// Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
13116  		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
13117  	},
13118
13119  	// @method getLatLng(): LatLng
13120  	// Returns the current geographical position of the circle marker
13121  	getLatLng: function () {
13122  		return this._latlng;
13123  	},
13124
13125  	// @method setRadius(radius: Number): this
13126  	// Sets the radius of a circle marker. Units are in pixels.
13127  	setRadius: function (radius) {
13128  		this.options.radius = this._radius = radius;
13129  		return this.redraw();
13130  	},
13131
13132  	// @method getRadius(): Number
13133  	// Returns the current radius of the circle
13134  	getRadius: function () {
13135  		return this._radius;
13136  	},
13137
13138  	setStyle : function (options) {
13139  		var radius = options && options.radius || this._radius;
13140  		Path.prototype.setStyle.call(this, options);
13141  		this.setRadius(radius);
13142  		return this;
13143  	},
13144
13145  	_project: function () {
13146  		this._point = this._map.latLngToLayerPoint(this._latlng);
13147  		this._updateBounds();
13148  	},
13149
13150  	_updateBounds: function () {
13151  		var r = this._radius,
13152  		    r2 = this._radiusY || r,
13153  		    w = this._clickTolerance(),
13154  		    p = [r + w, r2 + w];
13155  		this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
13156  	},
13157
13158  	_update: function () {
13159  		if (this._map) {
13160  			this._updatePath();
13161  		}
13162  	},
13163
13164  	_updatePath: function () {
13165  		this._renderer._updateCircle(this);
13166  	},
13167
13168  	_empty: function () {
13169  		return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
13170  	},
13171
13172  	// Needed by the `Canvas` renderer for interactivity
13173  	_containsPoint: function (p) {
13174  		return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
13175  	}
13176  });
13177
13178
13179  // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
13180  // Instantiates a circle marker object given a geographical point, and an optional options object.
13181  function circleMarker(latlng, options) {
13182  	return new CircleMarker(latlng, options);
13183  }
13184
13185  /*
13186   * @class Circle
13187   * @aka L.Circle
13188   * @inherits CircleMarker
13189   *
13190   * A class for drawing circle overlays on a map. Extends `CircleMarker`.
13191   *
13192   * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
13193   *
13194   * @example
13195   *
13196   * ```js
13197   * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
13198   * ```
13199   */
13200
13201  var Circle = CircleMarker.extend({
13202
13203  	initialize: function (latlng, options, legacyOptions) {
13204  		if (typeof options === 'number') {
13205  			// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
13206  			options = extend({}, legacyOptions, {radius: options});
13207  		}
13208  		setOptions(this, options);
13209  		this._latlng = toLatLng(latlng);
13210
13211  		if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
13212
13213  		// @section
13214  		// @aka Circle options
13215  		// @option radius: Number; Radius of the circle, in meters.
13216  		this._mRadius = this.options.radius;
13217  	},
13218
13219  	// @method setRadius(radius: Number): this
13220  	// Sets the radius of a circle. Units are in meters.
13221  	setRadius: function (radius) {
13222  		this._mRadius = radius;
13223  		return this.redraw();
13224  	},
13225
13226  	// @method getRadius(): Number
13227  	// Returns the current radius of a circle. Units are in meters.
13228  	getRadius: function () {
13229  		return this._mRadius;
13230  	},
13231
13232  	// @method getBounds(): LatLngBounds
13233  	// Returns the `LatLngBounds` of the path.
13234  	getBounds: function () {
13235  		var half = [this._radius, this._radiusY || this._radius];
13236
13237  		return new LatLngBounds(
13238  			this._map.layerPointToLatLng(this._point.subtract(half)),
13239  			this._map.layerPointToLatLng(this._point.add(half)));
13240  	},
13241
13242  	setStyle: Path.prototype.setStyle,
13243
13244  	_project: function () {
13245
13246  		var lng = this._latlng.lng,
13247  		    lat = this._latlng.lat,
13248  		    map = this._map,
13249  		    crs = map.options.crs;
13250
13251  		if (crs.distance === Earth.distance) {
13252  			var d = Math.PI / 180,
13253  			    latR = (this._mRadius / Earth.R) / d,
13254  			    top = map.project([lat + latR, lng]),
13255  			    bottom = map.project([lat - latR, lng]),
13256  			    p = top.add(bottom).divideBy(2),
13257  			    lat2 = map.unproject(p).lat,
13258  			    lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
13259  			            (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
13260
13261  			if (isNaN(lngR) || lngR === 0) {
13262  				lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
13263  			}
13264
13265  			this._point = p.subtract(map.getPixelOrigin());
13266  			this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
13267  			this._radiusY = p.y - top.y;
13268
13269  		} else {
13270  			var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
13271
13272  			this._point = map.latLngToLayerPoint(this._latlng);
13273  			this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
13274  		}
13275
13276  		this._updateBounds();
13277  	}
13278  });
13279
13280  // @factory L.circle(latlng: LatLng, options?: Circle options)
13281  // Instantiates a circle object given a geographical point, and an options object
13282  // which contains the circle radius.
13283  // @alternative
13284  // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
13285  // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
13286  // Do not use in new applications or plugins.
13287  function circle(latlng, options, legacyOptions) {
13288  	return new Circle(latlng, options, legacyOptions);
13289  }
13290
13291  /*
13292   * @class Polyline
13293   * @aka L.Polyline
13294   * @inherits Path
13295   *
13296   * A class for drawing polyline overlays on a map. Extends `Path`.
13297   *
13298   * @example
13299   *
13300   * ```js
13301   * // create a red polyline from an array of LatLng points
13302   * var latlngs = [
13303   * 	[45.51, -122.68],
13304   * 	[37.77, -122.43],
13305   * 	[34.04, -118.2]
13306   * ];
13307   *
13308   * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
13309   *
13310   * // zoom the map to the polyline
13311   * map.fitBounds(polyline.getBounds());
13312   * ```
13313   *
13314   * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
13315   *
13316   * ```js
13317   * // create a red polyline from an array of arrays of LatLng points
13318   * var latlngs = [
13319   * 	[[45.51, -122.68],
13320   * 	 [37.77, -122.43],
13321   * 	 [34.04, -118.2]],
13322   * 	[[40.78, -73.91],
13323   * 	 [41.83, -87.62],
13324   * 	 [32.76, -96.72]]
13325   * ];
13326   * ```
13327   */
13328
13329
13330  var Polyline = Path.extend({
13331
13332  	// @section
13333  	// @aka Polyline options
13334  	options: {
13335  		// @option smoothFactor: Number = 1.0
13336  		// How much to simplify the polyline on each zoom level. More means
13337  		// better performance and smoother look, and less means more accurate representation.
13338  		smoothFactor: 1.0,
13339
13340  		// @option noClip: Boolean = false
13341  		// Disable polyline clipping.
13342  		noClip: false
13343  	},
13344
13345  	initialize: function (latlngs, options) {
13346  		setOptions(this, options);
13347  		this._setLatLngs(latlngs);
13348  	},
13349
13350  	// @method getLatLngs(): LatLng[]
13351  	// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
13352  	getLatLngs: function () {
13353  		return this._latlngs;
13354  	},
13355
13356  	// @method setLatLngs(latlngs: LatLng[]): this
13357  	// Replaces all the points in the polyline with the given array of geographical points.
13358  	setLatLngs: function (latlngs) {
13359  		this._setLatLngs(latlngs);
13360  		return this.redraw();
13361  	},
13362
13363  	// @method isEmpty(): Boolean
13364  	// Returns `true` if the Polyline has no LatLngs.
13365  	isEmpty: function () {
13366  		return !this._latlngs.length;
13367  	},
13368
13369  	// @method closestLayerPoint(p: Point): Point
13370  	// Returns the point closest to `p` on the Polyline.
13371  	closestLayerPoint: function (p) {
13372  		var minDistance = Infinity,
13373  		    minPoint = null,
13374  		    closest = _sqClosestPointOnSegment,
13375  		    p1, p2;
13376
13377  		for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
13378  			var points = this._parts[j];
13379
13380  			for (var i = 1, len = points.length; i < len; i++) {
13381  				p1 = points[i - 1];
13382  				p2 = points[i];
13383
13384  				var sqDist = closest(p, p1, p2, true);
13385
13386  				if (sqDist < minDistance) {
13387  					minDistance = sqDist;
13388  					minPoint = closest(p, p1, p2);
13389  				}
13390  			}
13391  		}
13392  		if (minPoint) {
13393  			minPoint.distance = Math.sqrt(minDistance);
13394  		}
13395  		return minPoint;
13396  	},
13397
13398  	// @method getCenter(): LatLng
13399  	// Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
13400  	getCenter: function () {
13401  		// throws error when not yet added to map as this center calculation requires projected coordinates
13402  		if (!this._map) {
13403  			throw new Error('Must add layer to map before using getCenter()');
13404  		}
13405  		return polylineCenter(this._defaultShape(), this._map.options.crs);
13406  	},
13407
13408  	// @method getBounds(): LatLngBounds
13409  	// Returns the `LatLngBounds` of the path.
13410  	getBounds: function () {
13411  		return this._bounds;
13412  	},
13413
13414  	// @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
13415  	// Adds a given point to the polyline. By default, adds to the first ring of
13416  	// the polyline in case of a multi-polyline, but can be overridden by passing
13417  	// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
13418  	addLatLng: function (latlng, latlngs) {
13419  		latlngs = latlngs || this._defaultShape();
13420  		latlng = toLatLng(latlng);
13421  		latlngs.push(latlng);
13422  		this._bounds.extend(latlng);
13423  		return this.redraw();
13424  	},
13425
13426  	_setLatLngs: function (latlngs) {
13427  		this._bounds = new LatLngBounds();
13428  		this._latlngs = this._convertLatLngs(latlngs);
13429  	},
13430
13431  	_defaultShape: function () {
13432  		return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
13433  	},
13434
13435  	// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
13436  	_convertLatLngs: function (latlngs) {
13437  		var result = [],
13438  		    flat = isFlat(latlngs);
13439
13440  		for (var i = 0, len = latlngs.length; i < len; i++) {
13441  			if (flat) {
13442  				result[i] = toLatLng(latlngs[i]);
13443  				this._bounds.extend(result[i]);
13444  			} else {
13445  				result[i] = this._convertLatLngs(latlngs[i]);
13446  			}
13447  		}
13448
13449  		return result;
13450  	},
13451
13452  	_project: function () {
13453  		var pxBounds = new Bounds();
13454  		this._rings = [];
13455  		this._projectLatlngs(this._latlngs, this._rings, pxBounds);
13456
13457  		if (this._bounds.isValid() && pxBounds.isValid()) {
13458  			this._rawPxBounds = pxBounds;
13459  			this._updateBounds();
13460  		}
13461  	},
13462
13463  	_updateBounds: function () {
13464  		var w = this._clickTolerance(),
13465  		    p = new Point(w, w);
13466
13467  		if (!this._rawPxBounds) {
13468  			return;
13469  		}
13470
13471  		this._pxBounds = new Bounds([
13472  			this._rawPxBounds.min.subtract(p),
13473  			this._rawPxBounds.max.add(p)
13474  		]);
13475  	},
13476
13477  	// recursively turns latlngs into a set of rings with projected coordinates
13478  	_projectLatlngs: function (latlngs, result, projectedBounds) {
13479  		var flat = latlngs[0] instanceof LatLng,
13480  		    len = latlngs.length,
13481  		    i, ring;
13482
13483  		if (flat) {
13484  			ring = [];
13485  			for (i = 0; i < len; i++) {
13486  				ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
13487  				projectedBounds.extend(ring[i]);
13488  			}
13489  			result.push(ring);
13490  		} else {
13491  			for (i = 0; i < len; i++) {
13492  				this._projectLatlngs(latlngs[i], result, projectedBounds);
13493  			}
13494  		}
13495  	},
13496
13497  	// clip polyline by renderer bounds so that we have less to render for performance
13498  	_clipPoints: function () {
13499  		var bounds = this._renderer._bounds;
13500
13501  		this._parts = [];
13502  		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
13503  			return;
13504  		}
13505
13506  		if (this.options.noClip) {
13507  			this._parts = this._rings;
13508  			return;
13509  		}
13510
13511  		var parts = this._parts,
13512  		    i, j, k, len, len2, segment, points;
13513
13514  		for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
13515  			points = this._rings[i];
13516
13517  			for (j = 0, len2 = points.length; j < len2 - 1; j++) {
13518  				segment = clipSegment(points[j], points[j + 1], bounds, j, true);
13519
13520  				if (!segment) { continue; }
13521
13522  				parts[k] = parts[k] || [];
13523  				parts[k].push(segment[0]);
13524
13525  				// if segment goes out of screen, or it's the last one, it's the end of the line part
13526  				if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
13527  					parts[k].push(segment[1]);
13528  					k++;
13529  				}
13530  			}
13531  		}
13532  	},
13533
13534  	// simplify each clipped part of the polyline for performance
13535  	_simplifyPoints: function () {
13536  		var parts = this._parts,
13537  		    tolerance = this.options.smoothFactor;
13538
13539  		for (var i = 0, len = parts.length; i < len; i++) {
13540  			parts[i] = simplify(parts[i], tolerance);
13541  		}
13542  	},
13543
13544  	_update: function () {
13545  		if (!this._map) { return; }
13546
13547  		this._clipPoints();
13548  		this._simplifyPoints();
13549  		this._updatePath();
13550  	},
13551
13552  	_updatePath: function () {
13553  		this._renderer._updatePoly(this);
13554  	},
13555
13556  	// Needed by the `Canvas` renderer for interactivity
13557  	_containsPoint: function (p, closed) {
13558  		var i, j, k, len, len2, part,
13559  		    w = this._clickTolerance();
13560
13561  		if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
13562
13563  		// hit detection for polylines
13564  		for (i = 0, len = this._parts.length; i < len; i++) {
13565  			part = this._parts[i];
13566
13567  			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
13568  				if (!closed && (j === 0)) { continue; }
13569
13570  				if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
13571  					return true;
13572  				}
13573  			}
13574  		}
13575  		return false;
13576  	}
13577  });
13578
13579  // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
13580  // Instantiates a polyline object given an array of geographical points and
13581  // optionally an options object. You can create a `Polyline` object with
13582  // multiple separate lines (`MultiPolyline`) by passing an array of arrays
13583  // of geographic points.
13584  function polyline(latlngs, options) {
13585  	return new Polyline(latlngs, options);
13586  }
13587
13588  // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
13589  Polyline._flat = _flat;
13590
13591  /*
13592   * @class Polygon
13593   * @aka L.Polygon
13594   * @inherits Polyline
13595   *
13596   * A class for drawing polygon overlays on a map. Extends `Polyline`.
13597   *
13598   * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
13599   *
13600   *
13601   * @example
13602   *
13603   * ```js
13604   * // create a red polygon from an array of LatLng points
13605   * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
13606   *
13607   * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
13608   *
13609   * // zoom the map to the polygon
13610   * map.fitBounds(polygon.getBounds());
13611   * ```
13612   *
13613   * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
13614   *
13615   * ```js
13616   * var latlngs = [
13617   *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
13618   *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
13619   * ];
13620   * ```
13621   *
13622   * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
13623   *
13624   * ```js
13625   * var latlngs = [
13626   *   [ // first polygon
13627   *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
13628   *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
13629   *   ],
13630   *   [ // second polygon
13631   *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
13632   *   ]
13633   * ];
13634   * ```
13635   */
13636
13637  var Polygon = Polyline.extend({
13638
13639  	options: {
13640  		fill: true
13641  	},
13642
13643  	isEmpty: function () {
13644  		return !this._latlngs.length || !this._latlngs[0].length;
13645  	},
13646
13647  	// @method getCenter(): LatLng
13648  	// Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
13649  	getCenter: function () {
13650  		// throws error when not yet added to map as this center calculation requires projected coordinates
13651  		if (!this._map) {
13652  			throw new Error('Must add layer to map before using getCenter()');
13653  		}
13654  		return polygonCenter(this._defaultShape(), this._map.options.crs);
13655  	},
13656
13657  	_convertLatLngs: function (latlngs) {
13658  		var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
13659  		    len = result.length;
13660
13661  		// remove last point if it equals first one
13662  		if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
13663  			result.pop();
13664  		}
13665  		return result;
13666  	},
13667
13668  	_setLatLngs: function (latlngs) {
13669  		Polyline.prototype._setLatLngs.call(this, latlngs);
13670  		if (isFlat(this._latlngs)) {
13671  			this._latlngs = [this._latlngs];
13672  		}
13673  	},
13674
13675  	_defaultShape: function () {
13676  		return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
13677  	},
13678
13679  	_clipPoints: function () {
13680  		// polygons need a different clipping algorithm so we redefine that
13681
13682  		var bounds = this._renderer._bounds,
13683  		    w = this.options.weight,
13684  		    p = new Point(w, w);
13685
13686  		// increase clip padding by stroke width to avoid stroke on clip edges
13687  		bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
13688
13689  		this._parts = [];
13690  		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
13691  			return;
13692  		}
13693
13694  		if (this.options.noClip) {
13695  			this._parts = this._rings;
13696  			return;
13697  		}
13698
13699  		for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
13700  			clipped = clipPolygon(this._rings[i], bounds, true);
13701  			if (clipped.length) {
13702  				this._parts.push(clipped);
13703  			}
13704  		}
13705  	},
13706
13707  	_updatePath: function () {
13708  		this._renderer._updatePoly(this, true);
13709  	},
13710
13711  	// Needed by the `Canvas` renderer for interactivity
13712  	_containsPoint: function (p) {
13713  		var inside = false,
13714  		    part, p1, p2, i, j, k, len, len2;
13715
13716  		if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
13717
13718  		// ray casting algorithm for detecting if point is in polygon
13719  		for (i = 0, len = this._parts.length; i < len; i++) {
13720  			part = this._parts[i];
13721
13722  			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
13723  				p1 = part[j];
13724  				p2 = part[k];
13725
13726  				if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
13727  					inside = !inside;
13728  				}
13729  			}
13730  		}
13731
13732  		// also check if it's on polygon stroke
13733  		return inside || Polyline.prototype._containsPoint.call(this, p, true);
13734  	}
13735
13736  });
13737
13738
13739  // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
13740  function polygon(latlngs, options) {
13741  	return new Polygon(latlngs, options);
13742  }
13743
13744  /*
13745   * @class GeoJSON
13746   * @aka L.GeoJSON
13747   * @inherits FeatureGroup
13748   *
13749   * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
13750   * GeoJSON data and display it on the map. Extends `FeatureGroup`.
13751   *
13752   * @example
13753   *
13754   * ```js
13755   * L.geoJSON(data, {
13756   * 	style: function (feature) {
13757   * 		return {color: feature.properties.color};
13758   * 	}
13759   * }).bindPopup(function (layer) {
13760   * 	return layer.feature.properties.description;
13761   * }).addTo(map);
13762   * ```
13763   */
13764
13765  var GeoJSON = FeatureGroup.extend({
13766
13767  	/* @section
13768  	 * @aka GeoJSON options
13769  	 *
13770  	 * @option pointToLayer: Function = *
13771  	 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
13772  	 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
13773  	 * The default is to spawn a default `Marker`:
13774  	 * ```js
13775  	 * function(geoJsonPoint, latlng) {
13776  	 * 	return L.marker(latlng);
13777  	 * }
13778  	 * ```
13779  	 *
13780  	 * @option style: Function = *
13781  	 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
13782  	 * called internally when data is added.
13783  	 * The default value is to not override any defaults:
13784  	 * ```js
13785  	 * function (geoJsonFeature) {
13786  	 * 	return {}
13787  	 * }
13788  	 * ```
13789  	 *
13790  	 * @option onEachFeature: Function = *
13791  	 * A `Function` that will be called once for each created `Feature`, after it has
13792  	 * been created and styled. Useful for attaching events and popups to features.
13793  	 * The default is to do nothing with the newly created layers:
13794  	 * ```js
13795  	 * function (feature, layer) {}
13796  	 * ```
13797  	 *
13798  	 * @option filter: Function = *
13799  	 * A `Function` that will be used to decide whether to include a feature or not.
13800  	 * The default is to include all features:
13801  	 * ```js
13802  	 * function (geoJsonFeature) {
13803  	 * 	return true;
13804  	 * }
13805  	 * ```
13806  	 * Note: dynamically changing the `filter` option will have effect only on newly
13807  	 * added data. It will _not_ re-evaluate already included features.
13808  	 *
13809  	 * @option coordsToLatLng: Function = *
13810  	 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
13811  	 * The default is the `coordsToLatLng` static method.
13812  	 *
13813  	 * @option markersInheritOptions: Boolean = false
13814  	 * Whether default Markers for "Point" type Features inherit from group options.
13815  	 */
13816
13817  	initialize: function (geojson, options) {
13818  		setOptions(this, options);
13819
13820  		this._layers = {};
13821
13822  		if (geojson) {
13823  			this.addData(geojson);
13824  		}
13825  	},
13826
13827  	// @method addData( <GeoJSON> data ): this
13828  	// Adds a GeoJSON object to the layer.
13829  	addData: function (geojson) {
13830  		var features = isArray(geojson) ? geojson : geojson.features,
13831  		    i, len, feature;
13832
13833  		if (features) {
13834  			for (i = 0, len = features.length; i < len; i++) {
13835  				// only add this if geometry or geometries are set and not null
13836  				feature = features[i];
13837  				if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
13838  					this.addData(feature);
13839  				}
13840  			}
13841  			return this;
13842  		}
13843
13844  		var options = this.options;
13845
13846  		if (options.filter && !options.filter(geojson)) { return this; }
13847
13848  		var layer = geometryToLayer(geojson, options);
13849  		if (!layer) {
13850  			return this;
13851  		}
13852  		layer.feature = asFeature(geojson);
13853
13854  		layer.defaultOptions = layer.options;
13855  		this.resetStyle(layer);
13856
13857  		if (options.onEachFeature) {
13858  			options.onEachFeature(geojson, layer);
13859  		}
13860
13861  		return this.addLayer(layer);
13862  	},
13863
13864  	// @method resetStyle( <Path> layer? ): this
13865  	// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
13866  	// If `layer` is omitted, the style of all features in the current layer is reset.
13867  	resetStyle: function (layer) {
13868  		if (layer === undefined) {
13869  			return this.eachLayer(this.resetStyle, this);
13870  		}
13871  		// reset any custom styles
13872  		layer.options = extend({}, layer.defaultOptions);
13873  		this._setLayerStyle(layer, this.options.style);
13874  		return this;
13875  	},
13876
13877  	// @method setStyle( <Function> style ): this
13878  	// Changes styles of GeoJSON vector layers with the given style function.
13879  	setStyle: function (style) {
13880  		return this.eachLayer(function (layer) {
13881  			this._setLayerStyle(layer, style);
13882  		}, this);
13883  	},
13884
13885  	_setLayerStyle: function (layer, style) {
13886  		if (layer.setStyle) {
13887  			if (typeof style === 'function') {
13888  				style = style(layer.feature);
13889  			}
13890  			layer.setStyle(style);
13891  		}
13892  	}
13893  });
13894
13895  // @section
13896  // There are several static functions which can be called without instantiating L.GeoJSON:
13897
13898  // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
13899  // Creates a `Layer` from a given GeoJSON feature. Can use a custom
13900  // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
13901  // functions if provided as options.
13902  function geometryToLayer(geojson, options) {
13903
13904  	var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
13905  	    coords = geometry ? geometry.coordinates : null,
13906  	    layers = [],
13907  	    pointToLayer = options && options.pointToLayer,
13908  	    _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
13909  	    latlng, latlngs, i, len;
13910
13911  	if (!coords && !geometry) {
13912  		return null;
13913  	}
13914
13915  	switch (geometry.type) {
13916  	case 'Point':
13917  		latlng = _coordsToLatLng(coords);
13918  		return _pointToLayer(pointToLayer, geojson, latlng, options);
13919
13920  	case 'MultiPoint':
13921  		for (i = 0, len = coords.length; i < len; i++) {
13922  			latlng = _coordsToLatLng(coords[i]);
13923  			layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
13924  		}
13925  		return new FeatureGroup(layers);
13926
13927  	case 'LineString':
13928  	case 'MultiLineString':
13929  		latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
13930  		return new Polyline(latlngs, options);
13931
13932  	case 'Polygon':
13933  	case 'MultiPolygon':
13934  		latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
13935  		return new Polygon(latlngs, options);
13936
13937  	case 'GeometryCollection':
13938  		for (i = 0, len = geometry.geometries.length; i < len; i++) {
13939  			var geoLayer = geometryToLayer({
13940  				geometry: geometry.geometries[i],
13941  				type: 'Feature',
13942  				properties: geojson.properties
13943  			}, options);
13944
13945  			if (geoLayer) {
13946  				layers.push(geoLayer);
13947  			}
13948  		}
13949  		return new FeatureGroup(layers);
13950
13951  	case 'FeatureCollection':
13952  		for (i = 0, len = geometry.features.length; i < len; i++) {
13953  			var featureLayer = geometryToLayer(geometry.features[i], options);
13954
13955  			if (featureLayer) {
13956  				layers.push(featureLayer);
13957  			}
13958  		}
13959  		return new FeatureGroup(layers);
13960
13961  	default:
13962  		throw new Error('Invalid GeoJSON object.');
13963  	}
13964  }
13965
13966  function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
13967  	return pointToLayerFn ?
13968  		pointToLayerFn(geojson, latlng) :
13969  		new Marker(latlng, options && options.markersInheritOptions && options);
13970  }
13971
13972  // @function coordsToLatLng(coords: Array): LatLng
13973  // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
13974  // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
13975  function coordsToLatLng(coords) {
13976  	return new LatLng(coords[1], coords[0], coords[2]);
13977  }
13978
13979  // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
13980  // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
13981  // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
13982  // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
13983  function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
13984  	var latlngs = [];
13985
13986  	for (var i = 0, len = coords.length, latlng; i < len; i++) {
13987  		latlng = levelsDeep ?
13988  			coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
13989  			(_coordsToLatLng || coordsToLatLng)(coords[i]);
13990
13991  		latlngs.push(latlng);
13992  	}
13993
13994  	return latlngs;
13995  }
13996
13997  // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
13998  // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
13999  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
14000  function latLngToCoords(latlng, precision) {
14001  	latlng = toLatLng(latlng);
14002  	return latlng.alt !== undefined ?
14003  		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
14004  		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
14005  }
14006
14007  // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
14008  // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
14009  // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
14010  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
14011  function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
14012  	var coords = [];
14013
14014  	for (var i = 0, len = latlngs.length; i < len; i++) {
14015  		// Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
14016  		coords.push(levelsDeep ?
14017  			latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
14018  			latLngToCoords(latlngs[i], precision));
14019  	}
14020
14021  	if (!levelsDeep && closed && coords.length > 0) {
14022  		coords.push(coords[0].slice());
14023  	}
14024
14025  	return coords;
14026  }
14027
14028  function getFeature(layer, newGeometry) {
14029  	return layer.feature ?
14030  		extend({}, layer.feature, {geometry: newGeometry}) :
14031  		asFeature(newGeometry);
14032  }
14033
14034  // @function asFeature(geojson: Object): Object
14035  // Normalize GeoJSON geometries/features into GeoJSON features.
14036  function asFeature(geojson) {
14037  	if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
14038  		return geojson;
14039  	}
14040
14041  	return {
14042  		type: 'Feature',
14043  		properties: {},
14044  		geometry: geojson
14045  	};
14046  }
14047
14048  var PointToGeoJSON = {
14049  	toGeoJSON: function (precision) {
14050  		return getFeature(this, {
14051  			type: 'Point',
14052  			coordinates: latLngToCoords(this.getLatLng(), precision)
14053  		});
14054  	}
14055  };
14056
14057  // @namespace Marker
14058  // @section Other methods
14059  // @method toGeoJSON(precision?: Number|false): Object
14060  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
14061  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
14062  Marker.include(PointToGeoJSON);
14063
14064  // @namespace CircleMarker
14065  // @method toGeoJSON(precision?: Number|false): Object
14066  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
14067  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
14068  Circle.include(PointToGeoJSON);
14069  CircleMarker.include(PointToGeoJSON);
14070
14071
14072  // @namespace Polyline
14073  // @method toGeoJSON(precision?: Number|false): Object
14074  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
14075  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
14076  Polyline.include({
14077  	toGeoJSON: function (precision) {
14078  		var multi = !isFlat(this._latlngs);
14079
14080  		var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
14081
14082  		return getFeature(this, {
14083  			type: (multi ? 'Multi' : '') + 'LineString',
14084  			coordinates: coords
14085  		});
14086  	}
14087  });
14088
14089  // @namespace Polygon
14090  // @method toGeoJSON(precision?: Number|false): Object
14091  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
14092  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
14093  Polygon.include({
14094  	toGeoJSON: function (precision) {
14095  		var holes = !isFlat(this._latlngs),
14096  		    multi = holes && !isFlat(this._latlngs[0]);
14097
14098  		var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
14099
14100  		if (!holes) {
14101  			coords = [coords];
14102  		}
14103
14104  		return getFeature(this, {
14105  			type: (multi ? 'Multi' : '') + 'Polygon',
14106  			coordinates: coords
14107  		});
14108  	}
14109  });
14110
14111
14112  // @namespace LayerGroup
14113  LayerGroup.include({
14114  	toMultiPoint: function (precision) {
14115  		var coords = [];
14116
14117  		this.eachLayer(function (layer) {
14118  			coords.push(layer.toGeoJSON(precision).geometry.coordinates);
14119  		});
14120
14121  		return getFeature(this, {
14122  			type: 'MultiPoint',
14123  			coordinates: coords
14124  		});
14125  	},
14126
14127  	// @method toGeoJSON(precision?: Number|false): Object
14128  	// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
14129  	// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
14130  	toGeoJSON: function (precision) {
14131
14132  		var type = this.feature && this.feature.geometry && this.feature.geometry.type;
14133
14134  		if (type === 'MultiPoint') {
14135  			return this.toMultiPoint(precision);
14136  		}
14137
14138  		var isGeometryCollection = type === 'GeometryCollection',
14139  		    jsons = [];
14140
14141  		this.eachLayer(function (layer) {
14142  			if (layer.toGeoJSON) {
14143  				var json = layer.toGeoJSON(precision);
14144  				if (isGeometryCollection) {
14145  					jsons.push(json.geometry);
14146  				} else {
14147  					var feature = asFeature(json);
14148  					// Squash nested feature collections
14149  					if (feature.type === 'FeatureCollection') {
14150  						jsons.push.apply(jsons, feature.features);
14151  					} else {
14152  						jsons.push(feature);
14153  					}
14154  				}
14155  			}
14156  		});
14157
14158  		if (isGeometryCollection) {
14159  			return getFeature(this, {
14160  				geometries: jsons,
14161  				type: 'GeometryCollection'
14162  			});
14163  		}
14164
14165  		return {
14166  			type: 'FeatureCollection',
14167  			features: jsons
14168  		};
14169  	}
14170  });
14171
14172  // @namespace GeoJSON
14173  // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
14174  // Creates a GeoJSON layer. Optionally accepts an object in
14175  // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
14176  // (you can alternatively add it later with `addData` method) and an `options` object.
14177  function geoJSON(geojson, options) {
14178  	return new GeoJSON(geojson, options);
14179  }
14180
14181  // Backward compatibility.
14182  var geoJson = geoJSON;
14183
14184  /*
14185   * @class ImageOverlay
14186   * @aka L.ImageOverlay
14187   * @inherits Interactive layer
14188   *
14189   * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
14190   *
14191   * @example
14192   *
14193   * ```js
14194   * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
14195   * 	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
14196   * L.imageOverlay(imageUrl, imageBounds).addTo(map);
14197   * ```
14198   */
14199
14200  var ImageOverlay = Layer.extend({
14201
14202  	// @section
14203  	// @aka ImageOverlay options
14204  	options: {
14205  		// @option opacity: Number = 1.0
14206  		// The opacity of the image overlay.
14207  		opacity: 1,
14208
14209  		// @option alt: String = ''
14210  		// Text for the `alt` attribute of the image (useful for accessibility).
14211  		alt: '',
14212
14213  		// @option interactive: Boolean = false
14214  		// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
14215  		interactive: false,
14216
14217  		// @option crossOrigin: Boolean|String = false
14218  		// Whether the crossOrigin attribute will be added to the image.
14219  		// If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
14220  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
14221  		crossOrigin: false,
14222
14223  		// @option errorOverlayUrl: String = ''
14224  		// URL to the overlay image to show in place of the overlay that failed to load.
14225  		errorOverlayUrl: '',
14226
14227  		// @option zIndex: Number = 1
14228  		// The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
14229  		zIndex: 1,
14230
14231  		// @option className: String = ''
14232  		// A custom class name to assign to the image. Empty by default.
14233  		className: ''
14234  	},
14235
14236  	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
14237  		this._url = url;
14238  		this._bounds = toLatLngBounds(bounds);
14239
14240  		setOptions(this, options);
14241  	},
14242
14243  	onAdd: function () {
14244  		if (!this._image) {
14245  			this._initImage();
14246
14247  			if (this.options.opacity < 1) {
14248  				this._updateOpacity();
14249  			}
14250  		}
14251
14252  		if (this.options.interactive) {
14253  			addClass(this._image, 'leaflet-interactive');
14254  			this.addInteractiveTarget(this._image);
14255  		}
14256
14257  		this.getPane().appendChild(this._image);
14258  		this._reset();
14259  	},
14260
14261  	onRemove: function () {
14262  		remove(this._image);
14263  		if (this.options.interactive) {
14264  			this.removeInteractiveTarget(this._image);
14265  		}
14266  	},
14267
14268  	// @method setOpacity(opacity: Number): this
14269  	// Sets the opacity of the overlay.
14270  	setOpacity: function (opacity) {
14271  		this.options.opacity = opacity;
14272
14273  		if (this._image) {
14274  			this._updateOpacity();
14275  		}
14276  		return this;
14277  	},
14278
14279  	setStyle: function (styleOpts) {
14280  		if (styleOpts.opacity) {
14281  			this.setOpacity(styleOpts.opacity);
14282  		}
14283  		return this;
14284  	},
14285
14286  	// @method bringToFront(): this
14287  	// Brings the layer to the top of all overlays.
14288  	bringToFront: function () {
14289  		if (this._map) {
14290  			toFront(this._image);
14291  		}
14292  		return this;
14293  	},
14294
14295  	// @method bringToBack(): this
14296  	// Brings the layer to the bottom of all overlays.
14297  	bringToBack: function () {
14298  		if (this._map) {
14299  			toBack(this._image);
14300  		}
14301  		return this;
14302  	},
14303
14304  	// @method setUrl(url: String): this
14305  	// Changes the URL of the image.
14306  	setUrl: function (url) {
14307  		this._url = url;
14308
14309  		if (this._image) {
14310  			this._image.src = url;
14311  		}
14312  		return this;
14313  	},
14314
14315  	// @method setBounds(bounds: LatLngBounds): this
14316  	// Update the bounds that this ImageOverlay covers
14317  	setBounds: function (bounds) {
14318  		this._bounds = toLatLngBounds(bounds);
14319
14320  		if (this._map) {
14321  			this._reset();
14322  		}
14323  		return this;
14324  	},
14325
14326  	getEvents: function () {
14327  		var events = {
14328  			zoom: this._reset,
14329  			viewreset: this._reset
14330  		};
14331
14332  		if (this._zoomAnimated) {
14333  			events.zoomanim = this._animateZoom;
14334  		}
14335
14336  		return events;
14337  	},
14338
14339  	// @method setZIndex(value: Number): this
14340  	// Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
14341  	setZIndex: function (value) {
14342  		this.options.zIndex = value;
14343  		this._updateZIndex();
14344  		return this;
14345  	},
14346
14347  	// @method getBounds(): LatLngBounds
14348  	// Get the bounds that this ImageOverlay covers
14349  	getBounds: function () {
14350  		return this._bounds;
14351  	},
14352
14353  	// @method getElement(): HTMLElement
14354  	// Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
14355  	// used by this overlay.
14356  	getElement: function () {
14357  		return this._image;
14358  	},
14359
14360  	_initImage: function () {
14361  		var wasElementSupplied = this._url.tagName === 'IMG';
14362  		var img = this._image = wasElementSupplied ? this._url : create$1('img');
14363
14364  		addClass(img, 'leaflet-image-layer');
14365  		if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
14366  		if (this.options.className) { addClass(img, this.options.className); }
14367
14368  		img.onselectstart = falseFn;
14369  		img.onmousemove = falseFn;
14370
14371  		// @event load: Event
14372  		// Fired when the ImageOverlay layer has loaded its image
14373  		img.onload = bind(this.fire, this, 'load');
14374  		img.onerror = bind(this._overlayOnError, this, 'error');
14375
14376  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
14377  			img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
14378  		}
14379
14380  		if (this.options.zIndex) {
14381  			this._updateZIndex();
14382  		}
14383
14384  		if (wasElementSupplied) {
14385  			this._url = img.src;
14386  			return;
14387  		}
14388
14389  		img.src = this._url;
14390  		img.alt = this.options.alt;
14391  	},
14392
14393  	_animateZoom: function (e) {
14394  		var scale = this._map.getZoomScale(e.zoom),
14395  		    offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
14396
14397  		setTransform(this._image, offset, scale);
14398  	},
14399
14400  	_reset: function () {
14401  		var image = this._image,
14402  		    bounds = new Bounds(
14403  		        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
14404  		        this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
14405  		    size = bounds.getSize();
14406
14407  		setPosition(image, bounds.min);
14408
14409  		image.style.width  = size.x + 'px';
14410  		image.style.height = size.y + 'px';
14411  	},
14412
14413  	_updateOpacity: function () {
14414  		setOpacity(this._image, this.options.opacity);
14415  	},
14416
14417  	_updateZIndex: function () {
14418  		if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
14419  			this._image.style.zIndex = this.options.zIndex;
14420  		}
14421  	},
14422
14423  	_overlayOnError: function () {
14424  		// @event error: Event
14425  		// Fired when the ImageOverlay layer fails to load its image
14426  		this.fire('error');
14427
14428  		var errorUrl = this.options.errorOverlayUrl;
14429  		if (errorUrl && this._url !== errorUrl) {
14430  			this._url = errorUrl;
14431  			this._image.src = errorUrl;
14432  		}
14433  	},
14434
14435  	// @method getCenter(): LatLng
14436  	// Returns the center of the ImageOverlay.
14437  	getCenter: function () {
14438  		return this._bounds.getCenter();
14439  	}
14440  });
14441
14442  // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
14443  // Instantiates an image overlay object given the URL of the image and the
14444  // geographical bounds it is tied to.
14445  var imageOverlay = function (url, bounds, options) {
14446  	return new ImageOverlay(url, bounds, options);
14447  };
14448
14449  /*
14450   * @class VideoOverlay
14451   * @aka L.VideoOverlay
14452   * @inherits ImageOverlay
14453   *
14454   * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
14455   *
14456   * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
14457   * HTML5 element.
14458   *
14459   * @example
14460   *
14461   * ```js
14462   * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
14463   * 	videoBounds = [[ 32, -130], [ 13, -100]];
14464   * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
14465   * ```
14466   */
14467
14468  var VideoOverlay = ImageOverlay.extend({
14469
14470  	// @section
14471  	// @aka VideoOverlay options
14472  	options: {
14473  		// @option autoplay: Boolean = true
14474  		// Whether the video starts playing automatically when loaded.
14475  		// On some browsers autoplay will only work with `muted: true`
14476  		autoplay: true,
14477
14478  		// @option loop: Boolean = true
14479  		// Whether the video will loop back to the beginning when played.
14480  		loop: true,
14481
14482  		// @option keepAspectRatio: Boolean = true
14483  		// Whether the video will save aspect ratio after the projection.
14484  		// Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
14485  		keepAspectRatio: true,
14486
14487  		// @option muted: Boolean = false
14488  		// Whether the video starts on mute when loaded.
14489  		muted: false,
14490
14491  		// @option playsInline: Boolean = true
14492  		// Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
14493  		playsInline: true
14494  	},
14495
14496  	_initImage: function () {
14497  		var wasElementSupplied = this._url.tagName === 'VIDEO';
14498  		var vid = this._image = wasElementSupplied ? this._url : create$1('video');
14499
14500  		addClass(vid, 'leaflet-image-layer');
14501  		if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
14502  		if (this.options.className) { addClass(vid, this.options.className); }
14503
14504  		vid.onselectstart = falseFn;
14505  		vid.onmousemove = falseFn;
14506
14507  		// @event load: Event
14508  		// Fired when the video has finished loading the first frame
14509  		vid.onloadeddata = bind(this.fire, this, 'load');
14510
14511  		if (wasElementSupplied) {
14512  			var sourceElements = vid.getElementsByTagName('source');
14513  			var sources = [];
14514  			for (var j = 0; j < sourceElements.length; j++) {
14515  				sources.push(sourceElements[j].src);
14516  			}
14517
14518  			this._url = (sourceElements.length > 0) ? sources : [vid.src];
14519  			return;
14520  		}
14521
14522  		if (!isArray(this._url)) { this._url = [this._url]; }
14523
14524  		if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
14525  			vid.style['objectFit'] = 'fill';
14526  		}
14527  		vid.autoplay = !!this.options.autoplay;
14528  		vid.loop = !!this.options.loop;
14529  		vid.muted = !!this.options.muted;
14530  		vid.playsInline = !!this.options.playsInline;
14531  		for (var i = 0; i < this._url.length; i++) {
14532  			var source = create$1('source');
14533  			source.src = this._url[i];
14534  			vid.appendChild(source);
14535  		}
14536  	}
14537
14538  	// @method getElement(): HTMLVideoElement
14539  	// Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
14540  	// used by this overlay.
14541  });
14542
14543
14544  // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
14545  // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
14546  // geographical bounds it is tied to.
14547
14548  function videoOverlay(video, bounds, options) {
14549  	return new VideoOverlay(video, bounds, options);
14550  }
14551
14552  /*
14553   * @class SVGOverlay
14554   * @aka L.SVGOverlay
14555   * @inherits ImageOverlay
14556   *
14557   * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
14558   *
14559   * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
14560   *
14561   * @example
14562   *
14563   * ```js
14564   * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
14565   * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
14566   * svgElement.setAttribute('viewBox', "0 0 200 200");
14567   * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
14568   * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
14569   * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
14570   * ```
14571   */
14572
14573  var SVGOverlay = ImageOverlay.extend({
14574  	_initImage: function () {
14575  		var el = this._image = this._url;
14576
14577  		addClass(el, 'leaflet-image-layer');
14578  		if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
14579  		if (this.options.className) { addClass(el, this.options.className); }
14580
14581  		el.onselectstart = falseFn;
14582  		el.onmousemove = falseFn;
14583  	}
14584
14585  	// @method getElement(): SVGElement
14586  	// Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
14587  	// used by this overlay.
14588  });
14589
14590
14591  // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
14592  // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
14593  // A viewBox attribute is required on the SVG element to zoom in and out properly.
14594
14595  function svgOverlay(el, bounds, options) {
14596  	return new SVGOverlay(el, bounds, options);
14597  }
14598
14599  /*
14600   * @class DivOverlay
14601   * @inherits Interactive layer
14602   * @aka L.DivOverlay
14603   * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
14604   */
14605
14606  // @namespace DivOverlay
14607  var DivOverlay = Layer.extend({
14608
14609  	// @section
14610  	// @aka DivOverlay options
14611  	options: {
14612  		// @option interactive: Boolean = false
14613  		// If true, the popup/tooltip will listen to the mouse events.
14614  		interactive: false,
14615
14616  		// @option offset: Point = Point(0, 0)
14617  		// The offset of the overlay position.
14618  		offset: [0, 0],
14619
14620  		// @option className: String = ''
14621  		// A custom CSS class name to assign to the overlay.
14622  		className: '',
14623
14624  		// @option pane: String = undefined
14625  		// `Map pane` where the overlay will be added.
14626  		pane: undefined,
14627
14628  		// @option content: String|HTMLElement|Function = ''
14629  		// Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
14630  		// passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
14631  		content: ''
14632  	},
14633
14634  	initialize: function (options, source) {
14635  		if (options && (options instanceof LatLng || isArray(options))) {
14636  			this._latlng = toLatLng(options);
14637  			setOptions(this, source);
14638  		} else {
14639  			setOptions(this, options);
14640  			this._source = source;
14641  		}
14642  		if (this.options.content) {
14643  			this._content = this.options.content;
14644  		}
14645  	},
14646
14647  	// @method openOn(map: Map): this
14648  	// Adds the overlay to the map.
14649  	// Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
14650  	openOn: function (map) {
14651  		map = arguments.length ? map : this._source._map; // experimental, not the part of public api
14652  		if (!map.hasLayer(this)) {
14653  			map.addLayer(this);
14654  		}
14655  		return this;
14656  	},
14657
14658  	// @method close(): this
14659  	// Closes the overlay.
14660  	// Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
14661  	// and `layer.closePopup()`/`.closeTooltip()`.
14662  	close: function () {
14663  		if (this._map) {
14664  			this._map.removeLayer(this);
14665  		}
14666  		return this;
14667  	},
14668
14669  	// @method toggle(layer?: Layer): this
14670  	// Opens or closes the overlay bound to layer depending on its current state.
14671  	// Argument may be omitted only for overlay bound to layer.
14672  	// Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
14673  	toggle: function (layer) {
14674  		if (this._map) {
14675  			this.close();
14676  		} else {
14677  			if (arguments.length) {
14678  				this._source = layer;
14679  			} else {
14680  				layer = this._source;
14681  			}
14682  			this._prepareOpen();
14683
14684  			// open the overlay on the map
14685  			this.openOn(layer._map);
14686  		}
14687  		return this;
14688  	},
14689
14690  	onAdd: function (map) {
14691  		this._zoomAnimated = map._zoomAnimated;
14692
14693  		if (!this._container) {
14694  			this._initLayout();
14695  		}
14696
14697  		if (map._fadeAnimated) {
14698  			setOpacity(this._container, 0);
14699  		}
14700
14701  		clearTimeout(this._removeTimeout);
14702  		this.getPane().appendChild(this._container);
14703  		this.update();
14704
14705  		if (map._fadeAnimated) {
14706  			setOpacity(this._container, 1);
14707  		}
14708
14709  		this.bringToFront();
14710
14711  		if (this.options.interactive) {
14712  			addClass(this._container, 'leaflet-interactive');
14713  			this.addInteractiveTarget(this._container);
14714  		}
14715  	},
14716
14717  	onRemove: function (map) {
14718  		if (map._fadeAnimated) {
14719  			setOpacity(this._container, 0);
14720  			this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
14721  		} else {
14722  			remove(this._container);
14723  		}
14724
14725  		if (this.options.interactive) {
14726  			removeClass(this._container, 'leaflet-interactive');
14727  			this.removeInteractiveTarget(this._container);
14728  		}
14729  	},
14730
14731  	// @namespace DivOverlay
14732  	// @method getLatLng: LatLng
14733  	// Returns the geographical point of the overlay.
14734  	getLatLng: function () {
14735  		return this._latlng;
14736  	},
14737
14738  	// @method setLatLng(latlng: LatLng): this
14739  	// Sets the geographical point where the overlay will open.
14740  	setLatLng: function (latlng) {
14741  		this._latlng = toLatLng(latlng);
14742  		if (this._map) {
14743  			this._updatePosition();
14744  			this._adjustPan();
14745  		}
14746  		return this;
14747  	},
14748
14749  	// @method getContent: String|HTMLElement
14750  	// Returns the content of the overlay.
14751  	getContent: function () {
14752  		return this._content;
14753  	},
14754
14755  	// @method setContent(htmlContent: String|HTMLElement|Function): this
14756  	// Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
14757  	// The function should return a `String` or `HTMLElement` to be used in the overlay.
14758  	setContent: function (content) {
14759  		this._content = content;
14760  		this.update();
14761  		return this;
14762  	},
14763
14764  	// @method getElement: String|HTMLElement
14765  	// Returns the HTML container of the overlay.
14766  	getElement: function () {
14767  		return this._container;
14768  	},
14769
14770  	// @method update: null
14771  	// Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
14772  	update: function () {
14773  		if (!this._map) { return; }
14774
14775  		this._container.style.visibility = 'hidden';
14776
14777  		this._updateContent();
14778  		this._updateLayout();
14779  		this._updatePosition();
14780
14781  		this._container.style.visibility = '';
14782
14783  		this._adjustPan();
14784  	},
14785
14786  	getEvents: function () {
14787  		var events = {
14788  			zoom: this._updatePosition,
14789  			viewreset: this._updatePosition
14790  		};
14791
14792  		if (this._zoomAnimated) {
14793  			events.zoomanim = this._animateZoom;
14794  		}
14795  		return events;
14796  	},
14797
14798  	// @method isOpen: Boolean
14799  	// Returns `true` when the overlay is visible on the map.
14800  	isOpen: function () {
14801  		return !!this._map && this._map.hasLayer(this);
14802  	},
14803
14804  	// @method bringToFront: this
14805  	// Brings this overlay in front of other overlays (in the same map pane).
14806  	bringToFront: function () {
14807  		if (this._map) {
14808  			toFront(this._container);
14809  		}
14810  		return this;
14811  	},
14812
14813  	// @method bringToBack: this
14814  	// Brings this overlay to the back of other overlays (in the same map pane).
14815  	bringToBack: function () {
14816  		if (this._map) {
14817  			toBack(this._container);
14818  		}
14819  		return this;
14820  	},
14821
14822  	// prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
14823  	_prepareOpen: function (latlng) {
14824  		var source = this._source;
14825  		if (!source._map) { return false; }
14826
14827  		if (source instanceof FeatureGroup) {
14828  			source = null;
14829  			var layers = this._source._layers;
14830  			for (var id in layers) {
14831  				if (layers[id]._map) {
14832  					source = layers[id];
14833  					break;
14834  				}
14835  			}
14836  			if (!source) { return false; } // Unable to get source layer.
14837
14838  			// set overlay source to this layer
14839  			this._source = source;
14840  		}
14841
14842  		if (!latlng) {
14843  			if (source.getCenter) {
14844  				latlng = source.getCenter();
14845  			} else if (source.getLatLng) {
14846  				latlng = source.getLatLng();
14847  			} else if (source.getBounds) {
14848  				latlng = source.getBounds().getCenter();
14849  			} else {
14850  				throw new Error('Unable to get source layer LatLng.');
14851  			}
14852  		}
14853  		this.setLatLng(latlng);
14854
14855  		if (this._map) {
14856  			// update the overlay (content, layout, etc...)
14857  			this.update();
14858  		}
14859
14860  		return true;
14861  	},
14862
14863  	_updateContent: function () {
14864  		if (!this._content) { return; }
14865
14866  		var node = this._contentNode;
14867  		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
14868
14869  		if (typeof content === 'string') {
14870  			node.innerHTML = content;
14871  		} else {
14872  			while (node.hasChildNodes()) {
14873  				node.removeChild(node.firstChild);
14874  			}
14875  			node.appendChild(content);
14876  		}
14877
14878  		// @namespace DivOverlay
14879  		// @section DivOverlay events
14880  		// @event contentupdate: Event
14881  		// Fired when the content of the overlay is updated
14882  		this.fire('contentupdate');
14883  	},
14884
14885  	_updatePosition: function () {
14886  		if (!this._map) { return; }
14887
14888  		var pos = this._map.latLngToLayerPoint(this._latlng),
14889  		    offset = toPoint(this.options.offset),
14890  		    anchor = this._getAnchor();
14891
14892  		if (this._zoomAnimated) {
14893  			setPosition(this._container, pos.add(anchor));
14894  		} else {
14895  			offset = offset.add(pos).add(anchor);
14896  		}
14897
14898  		var bottom = this._containerBottom = -offset.y,
14899  		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
14900
14901  		// bottom position the overlay in case the height of the overlay changes (images loading etc)
14902  		this._container.style.bottom = bottom + 'px';
14903  		this._container.style.left = left + 'px';
14904  	},
14905
14906  	_getAnchor: function () {
14907  		return [0, 0];
14908  	}
14909
14910  });
14911
14912  Map.include({
14913  	_initOverlay: function (OverlayClass, content, latlng, options) {
14914  		var overlay = content;
14915  		if (!(overlay instanceof OverlayClass)) {
14916  			overlay = new OverlayClass(options).setContent(content);
14917  		}
14918  		if (latlng) {
14919  			overlay.setLatLng(latlng);
14920  		}
14921  		return overlay;
14922  	}
14923  });
14924
14925
14926  Layer.include({
14927  	_initOverlay: function (OverlayClass, old, content, options) {
14928  		var overlay = content;
14929  		if (overlay instanceof OverlayClass) {
14930  			setOptions(overlay, options);
14931  			overlay._source = this;
14932  		} else {
14933  			overlay = (old && !options) ? old : new OverlayClass(options, this);
14934  			overlay.setContent(content);
14935  		}
14936  		return overlay;
14937  	}
14938  });
14939
14940  /*
14941   * @class Popup
14942   * @inherits DivOverlay
14943   * @aka L.Popup
14944   * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
14945   * open popups while making sure that only one popup is open at one time
14946   * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
14947   *
14948   * @example
14949   *
14950   * If you want to just bind a popup to marker click and then open it, it's really easy:
14951   *
14952   * ```js
14953   * marker.bindPopup(popupContent).openPopup();
14954   * ```
14955   * Path overlays like polylines also have a `bindPopup` method.
14956   *
14957   * A popup can be also standalone:
14958   *
14959   * ```js
14960   * var popup = L.popup()
14961   * 	.setLatLng(latlng)
14962   * 	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
14963   * 	.openOn(map);
14964   * ```
14965   * or
14966   * ```js
14967   * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
14968   * 	.openOn(map);
14969   * ```
14970   */
14971
14972
14973  // @namespace Popup
14974  var Popup = DivOverlay.extend({
14975
14976  	// @section
14977  	// @aka Popup options
14978  	options: {
14979  		// @option pane: String = 'popupPane'
14980  		// `Map pane` where the popup will be added.
14981  		pane: 'popupPane',
14982
14983  		// @option offset: Point = Point(0, 7)
14984  		// The offset of the popup position.
14985  		offset: [0, 7],
14986
14987  		// @option maxWidth: Number = 300
14988  		// Max width of the popup, in pixels.
14989  		maxWidth: 300,
14990
14991  		// @option minWidth: Number = 50
14992  		// Min width of the popup, in pixels.
14993  		minWidth: 50,
14994
14995  		// @option maxHeight: Number = null
14996  		// If set, creates a scrollable container of the given height
14997  		// inside a popup if its content exceeds it.
14998  		// The scrollable container can be styled using the
14999  		// `leaflet-popup-scrolled` CSS class selector.
15000  		maxHeight: null,
15001
15002  		// @option autoPan: Boolean = true
15003  		// Set it to `false` if you don't want the map to do panning animation
15004  		// to fit the opened popup.
15005  		autoPan: true,
15006
15007  		// @option autoPanPaddingTopLeft: Point = null
15008  		// The margin between the popup and the top left corner of the map
15009  		// view after autopanning was performed.
15010  		autoPanPaddingTopLeft: null,
15011
15012  		// @option autoPanPaddingBottomRight: Point = null
15013  		// The margin between the popup and the bottom right corner of the map
15014  		// view after autopanning was performed.
15015  		autoPanPaddingBottomRight: null,
15016
15017  		// @option autoPanPadding: Point = Point(5, 5)
15018  		// Equivalent of setting both top left and bottom right autopan padding to the same value.
15019  		autoPanPadding: [5, 5],
15020
15021  		// @option keepInView: Boolean = false
15022  		// Set it to `true` if you want to prevent users from panning the popup
15023  		// off of the screen while it is open.
15024  		keepInView: false,
15025
15026  		// @option closeButton: Boolean = true
15027  		// Controls the presence of a close button in the popup.
15028  		closeButton: true,
15029
15030  		// @option autoClose: Boolean = true
15031  		// Set it to `false` if you want to override the default behavior of
15032  		// the popup closing when another popup is opened.
15033  		autoClose: true,
15034
15035  		// @option closeOnEscapeKey: Boolean = true
15036  		// Set it to `false` if you want to override the default behavior of
15037  		// the ESC key for closing of the popup.
15038  		closeOnEscapeKey: true,
15039
15040  		// @option closeOnClick: Boolean = *
15041  		// Set it if you want to override the default behavior of the popup closing when user clicks
15042  		// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
15043
15044  		// @option className: String = ''
15045  		// A custom CSS class name to assign to the popup.
15046  		className: ''
15047  	},
15048
15049  	// @namespace Popup
15050  	// @method openOn(map: Map): this
15051  	// Alternative to `map.openPopup(popup)`.
15052  	// Adds the popup to the map and closes the previous one.
15053  	openOn: function (map) {
15054  		map = arguments.length ? map : this._source._map; // experimental, not the part of public api
15055
15056  		if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
15057  			map.removeLayer(map._popup);
15058  		}
15059  		map._popup = this;
15060
15061  		return DivOverlay.prototype.openOn.call(this, map);
15062  	},
15063
15064  	onAdd: function (map) {
15065  		DivOverlay.prototype.onAdd.call(this, map);
15066
15067  		// @namespace Map
15068  		// @section Popup events
15069  		// @event popupopen: PopupEvent
15070  		// Fired when a popup is opened in the map
15071  		map.fire('popupopen', {popup: this});
15072
15073  		if (this._source) {
15074  			// @namespace Layer
15075  			// @section Popup events
15076  			// @event popupopen: PopupEvent
15077  			// Fired when a popup bound to this layer is opened
15078  			this._source.fire('popupopen', {popup: this}, true);
15079  			// For non-path layers, we toggle the popup when clicking
15080  			// again the layer, so prevent the map to reopen it.
15081  			if (!(this._source instanceof Path)) {
15082  				this._source.on('preclick', stopPropagation);
15083  			}
15084  		}
15085  	},
15086
15087  	onRemove: function (map) {
15088  		DivOverlay.prototype.onRemove.call(this, map);
15089
15090  		// @namespace Map
15091  		// @section Popup events
15092  		// @event popupclose: PopupEvent
15093  		// Fired when a popup in the map is closed
15094  		map.fire('popupclose', {popup: this});
15095
15096  		if (this._source) {
15097  			// @namespace Layer
15098  			// @section Popup events
15099  			// @event popupclose: PopupEvent
15100  			// Fired when a popup bound to this layer is closed
15101  			this._source.fire('popupclose', {popup: this}, true);
15102  			if (!(this._source instanceof Path)) {
15103  				this._source.off('preclick', stopPropagation);
15104  			}
15105  		}
15106  	},
15107
15108  	getEvents: function () {
15109  		var events = DivOverlay.prototype.getEvents.call(this);
15110
15111  		if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
15112  			events.preclick = this.close;
15113  		}
15114
15115  		if (this.options.keepInView) {
15116  			events.moveend = this._adjustPan;
15117  		}
15118
15119  		return events;
15120  	},
15121
15122  	_initLayout: function () {
15123  		var prefix = 'leaflet-popup',
15124  		    container = this._container = create$1('div',
15125  			prefix + ' ' + (this.options.className || '') +
15126  			' leaflet-zoom-animated');
15127
15128  		var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
15129  		this._contentNode = create$1('div', prefix + '-content', wrapper);
15130
15131  		disableClickPropagation(container);
15132  		disableScrollPropagation(this._contentNode);
15133  		on(container, 'contextmenu', stopPropagation);
15134
15135  		this._tipContainer = create$1('div', prefix + '-tip-container', container);
15136  		this._tip = create$1('div', prefix + '-tip', this._tipContainer);
15137
15138  		if (this.options.closeButton) {
15139  			var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
15140  			closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
15141  			closeButton.setAttribute('aria-label', 'Close popup');
15142  			closeButton.href = '#close';
15143  			closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';
15144
15145  			on(closeButton, 'click', function (ev) {
15146  				preventDefault(ev);
15147  				this.close();
15148  			}, this);
15149  		}
15150  	},
15151
15152  	_updateLayout: function () {
15153  		var container = this._contentNode,
15154  		    style = container.style;
15155
15156  		style.width = '';
15157  		style.whiteSpace = 'nowrap';
15158
15159  		var width = container.offsetWidth;
15160  		width = Math.min(width, this.options.maxWidth);
15161  		width = Math.max(width, this.options.minWidth);
15162
15163  		style.width = (width + 1) + 'px';
15164  		style.whiteSpace = '';
15165
15166  		style.height = '';
15167
15168  		var height = container.offsetHeight,
15169  		    maxHeight = this.options.maxHeight,
15170  		    scrolledClass = 'leaflet-popup-scrolled';
15171
15172  		if (maxHeight && height > maxHeight) {
15173  			style.height = maxHeight + 'px';
15174  			addClass(container, scrolledClass);
15175  		} else {
15176  			removeClass(container, scrolledClass);
15177  		}
15178
15179  		this._containerWidth = this._container.offsetWidth;
15180  	},
15181
15182  	_animateZoom: function (e) {
15183  		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
15184  		    anchor = this._getAnchor();
15185  		setPosition(this._container, pos.add(anchor));
15186  	},
15187
15188  	_adjustPan: function () {
15189  		if (!this.options.autoPan) { return; }
15190  		if (this._map._panAnim) { this._map._panAnim.stop(); }
15191
15192  		// We can endlessly recurse if keepInView is set and the view resets.
15193  		// Let's guard against that by exiting early if we're responding to our own autopan.
15194  		if (this._autopanning) {
15195  			this._autopanning = false;
15196  			return;
15197  		}
15198
15199  		var map = this._map,
15200  		    marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
15201  		    containerHeight = this._container.offsetHeight + marginBottom,
15202  		    containerWidth = this._containerWidth,
15203  		    layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
15204
15205  		layerPos._add(getPosition(this._container));
15206
15207  		var containerPos = map.layerPointToContainerPoint(layerPos),
15208  		    padding = toPoint(this.options.autoPanPadding),
15209  		    paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
15210  		    paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
15211  		    size = map.getSize(),
15212  		    dx = 0,
15213  		    dy = 0;
15214
15215  		if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
15216  			dx = containerPos.x + containerWidth - size.x + paddingBR.x;
15217  		}
15218  		if (containerPos.x - dx - paddingTL.x < 0) { // left
15219  			dx = containerPos.x - paddingTL.x;
15220  		}
15221  		if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
15222  			dy = containerPos.y + containerHeight - size.y + paddingBR.y;
15223  		}
15224  		if (containerPos.y - dy - paddingTL.y < 0) { // top
15225  			dy = containerPos.y - paddingTL.y;
15226  		}
15227
15228  		// @namespace Map
15229  		// @section Popup events
15230  		// @event autopanstart: Event
15231  		// Fired when the map starts autopanning when opening a popup.
15232  		if (dx || dy) {
15233  			// Track that we're autopanning, as this function will be re-ran on moveend
15234  			if (this.options.keepInView) {
15235  				this._autopanning = true;
15236  			}
15237
15238  			map
15239  			    .fire('autopanstart')
15240  			    .panBy([dx, dy]);
15241  		}
15242  	},
15243
15244  	_getAnchor: function () {
15245  		// Where should we anchor the popup on the source layer?
15246  		return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
15247  	}
15248
15249  });
15250
15251  // @namespace Popup
15252  // @factory L.popup(options?: Popup options, source?: Layer)
15253  // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
15254  // @alternative
15255  // @factory L.popup(latlng: LatLng, options?: Popup options)
15256  // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
15257  var popup = function (options, source) {
15258  	return new Popup(options, source);
15259  };
15260
15261
15262  /* @namespace Map
15263   * @section Interaction Options
15264   * @option closePopupOnClick: Boolean = true
15265   * Set it to `false` if you don't want popups to close when user clicks the map.
15266   */
15267  Map.mergeOptions({
15268  	closePopupOnClick: true
15269  });
15270
15271
15272  // @namespace Map
15273  // @section Methods for Layers and Controls
15274  Map.include({
15275  	// @method openPopup(popup: Popup): this
15276  	// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
15277  	// @alternative
15278  	// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
15279  	// Creates a popup with the specified content and options and opens it in the given point on a map.
15280  	openPopup: function (popup, latlng, options) {
15281  		this._initOverlay(Popup, popup, latlng, options)
15282  		  .openOn(this);
15283
15284  		return this;
15285  	},
15286
15287  	// @method closePopup(popup?: Popup): this
15288  	// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
15289  	closePopup: function (popup) {
15290  		popup = arguments.length ? popup : this._popup;
15291  		if (popup) {
15292  			popup.close();
15293  		}
15294  		return this;
15295  	}
15296  });
15297
15298  /*
15299   * @namespace Layer
15300   * @section Popup methods example
15301   *
15302   * All layers share a set of methods convenient for binding popups to it.
15303   *
15304   * ```js
15305   * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
15306   * layer.openPopup();
15307   * layer.closePopup();
15308   * ```
15309   *
15310   * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
15311   */
15312
15313  // @section Popup methods
15314  Layer.include({
15315
15316  	// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
15317  	// Binds a popup to the layer with the passed `content` and sets up the
15318  	// necessary event listeners. If a `Function` is passed it will receive
15319  	// the layer as the first argument and should return a `String` or `HTMLElement`.
15320  	bindPopup: function (content, options) {
15321  		this._popup = this._initOverlay(Popup, this._popup, content, options);
15322  		if (!this._popupHandlersAdded) {
15323  			this.on({
15324  				click: this._openPopup,
15325  				keypress: this._onKeyPress,
15326  				remove: this.closePopup,
15327  				move: this._movePopup
15328  			});
15329  			this._popupHandlersAdded = true;
15330  		}
15331
15332  		return this;
15333  	},
15334
15335  	// @method unbindPopup(): this
15336  	// Removes the popup previously bound with `bindPopup`.
15337  	unbindPopup: function () {
15338  		if (this._popup) {
15339  			this.off({
15340  				click: this._openPopup,
15341  				keypress: this._onKeyPress,
15342  				remove: this.closePopup,
15343  				move: this._movePopup
15344  			});
15345  			this._popupHandlersAdded = false;
15346  			this._popup = null;
15347  		}
15348  		return this;
15349  	},
15350
15351  	// @method openPopup(latlng?: LatLng): this
15352  	// Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
15353  	openPopup: function (latlng) {
15354  		if (this._popup) {
15355  			if (!(this instanceof FeatureGroup)) {
15356  				this._popup._source = this;
15357  			}
15358  			if (this._popup._prepareOpen(latlng || this._latlng)) {
15359  				// open the popup on the map
15360  				this._popup.openOn(this._map);
15361  			}
15362  		}
15363  		return this;
15364  	},
15365
15366  	// @method closePopup(): this
15367  	// Closes the popup bound to this layer if it is open.
15368  	closePopup: function () {
15369  		if (this._popup) {
15370  			this._popup.close();
15371  		}
15372  		return this;
15373  	},
15374
15375  	// @method togglePopup(): this
15376  	// Opens or closes the popup bound to this layer depending on its current state.
15377  	togglePopup: function () {
15378  		if (this._popup) {
15379  			this._popup.toggle(this);
15380  		}
15381  		return this;
15382  	},
15383
15384  	// @method isPopupOpen(): boolean
15385  	// Returns `true` if the popup bound to this layer is currently open.
15386  	isPopupOpen: function () {
15387  		return (this._popup ? this._popup.isOpen() : false);
15388  	},
15389
15390  	// @method setPopupContent(content: String|HTMLElement|Popup): this
15391  	// Sets the content of the popup bound to this layer.
15392  	setPopupContent: function (content) {
15393  		if (this._popup) {
15394  			this._popup.setContent(content);
15395  		}
15396  		return this;
15397  	},
15398
15399  	// @method getPopup(): Popup
15400  	// Returns the popup bound to this layer.
15401  	getPopup: function () {
15402  		return this._popup;
15403  	},
15404
15405  	_openPopup: function (e) {
15406  		if (!this._popup || !this._map) {
15407  			return;
15408  		}
15409  		// prevent map click
15410  		stop(e);
15411
15412  		var target = e.layer || e.target;
15413  		if (this._popup._source === target && !(target instanceof Path)) {
15414  			// treat it like a marker and figure out
15415  			// if we should toggle it open/closed
15416  			if (this._map.hasLayer(this._popup)) {
15417  				this.closePopup();
15418  			} else {
15419  				this.openPopup(e.latlng);
15420  			}
15421  			return;
15422  		}
15423  		this._popup._source = target;
15424  		this.openPopup(e.latlng);
15425  	},
15426
15427  	_movePopup: function (e) {
15428  		this._popup.setLatLng(e.latlng);
15429  	},
15430
15431  	_onKeyPress: function (e) {
15432  		if (e.originalEvent.keyCode === 13) {
15433  			this._openPopup(e);
15434  		}
15435  	}
15436  });
15437
15438  /*
15439   * @class Tooltip
15440   * @inherits DivOverlay
15441   * @aka L.Tooltip
15442   * Used to display small texts on top of map layers.
15443   *
15444   * @example
15445   * If you want to just bind a tooltip to marker:
15446   *
15447   * ```js
15448   * marker.bindTooltip("my tooltip text").openTooltip();
15449   * ```
15450   * Path overlays like polylines also have a `bindTooltip` method.
15451   *
15452   * A tooltip can be also standalone:
15453   *
15454   * ```js
15455   * var tooltip = L.tooltip()
15456   * 	.setLatLng(latlng)
15457   * 	.setContent('Hello world!<br />This is a nice tooltip.')
15458   * 	.addTo(map);
15459   * ```
15460   * or
15461   * ```js
15462   * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
15463   * 	.addTo(map);
15464   * ```
15465   *
15466   *
15467   * Note about tooltip offset. Leaflet takes two options in consideration
15468   * for computing tooltip offsetting:
15469   * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
15470   *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
15471   *   move it to the bottom. Negatives will move to the left and top.
15472   * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
15473   *   should adapt this value if you use a custom icon.
15474   */
15475
15476
15477  // @namespace Tooltip
15478  var Tooltip = DivOverlay.extend({
15479
15480  	// @section
15481  	// @aka Tooltip options
15482  	options: {
15483  		// @option pane: String = 'tooltipPane'
15484  		// `Map pane` where the tooltip will be added.
15485  		pane: 'tooltipPane',
15486
15487  		// @option offset: Point = Point(0, 0)
15488  		// Optional offset of the tooltip position.
15489  		offset: [0, 0],
15490
15491  		// @option direction: String = 'auto'
15492  		// Direction where to open the tooltip. Possible values are: `right`, `left`,
15493  		// `top`, `bottom`, `center`, `auto`.
15494  		// `auto` will dynamically switch between `right` and `left` according to the tooltip
15495  		// position on the map.
15496  		direction: 'auto',
15497
15498  		// @option permanent: Boolean = false
15499  		// Whether to open the tooltip permanently or only on mouseover.
15500  		permanent: false,
15501
15502  		// @option sticky: Boolean = false
15503  		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
15504  		sticky: false,
15505
15506  		// @option opacity: Number = 0.9
15507  		// Tooltip container opacity.
15508  		opacity: 0.9
15509  	},
15510
15511  	onAdd: function (map) {
15512  		DivOverlay.prototype.onAdd.call(this, map);
15513  		this.setOpacity(this.options.opacity);
15514
15515  		// @namespace Map
15516  		// @section Tooltip events
15517  		// @event tooltipopen: TooltipEvent
15518  		// Fired when a tooltip is opened in the map.
15519  		map.fire('tooltipopen', {tooltip: this});
15520
15521  		if (this._source) {
15522  			this.addEventParent(this._source);
15523
15524  			// @namespace Layer
15525  			// @section Tooltip events
15526  			// @event tooltipopen: TooltipEvent
15527  			// Fired when a tooltip bound to this layer is opened.
15528  			this._source.fire('tooltipopen', {tooltip: this}, true);
15529  		}
15530  	},
15531
15532  	onRemove: function (map) {
15533  		DivOverlay.prototype.onRemove.call(this, map);
15534
15535  		// @namespace Map
15536  		// @section Tooltip events
15537  		// @event tooltipclose: TooltipEvent
15538  		// Fired when a tooltip in the map is closed.
15539  		map.fire('tooltipclose', {tooltip: this});
15540
15541  		if (this._source) {
15542  			this.removeEventParent(this._source);
15543
15544  			// @namespace Layer
15545  			// @section Tooltip events
15546  			// @event tooltipclose: TooltipEvent
15547  			// Fired when a tooltip bound to this layer is closed.
15548  			this._source.fire('tooltipclose', {tooltip: this}, true);
15549  		}
15550  	},
15551
15552  	getEvents: function () {
15553  		var events = DivOverlay.prototype.getEvents.call(this);
15554
15555  		if (!this.options.permanent) {
15556  			events.preclick = this.close;
15557  		}
15558
15559  		return events;
15560  	},
15561
15562  	_initLayout: function () {
15563  		var prefix = 'leaflet-tooltip',
15564  		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
15565
15566  		this._contentNode = this._container = create$1('div', className);
15567
15568  		this._container.setAttribute('role', 'tooltip');
15569  		this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
15570  	},
15571
15572  	_updateLayout: function () {},
15573
15574  	_adjustPan: function () {},
15575
15576  	_setPosition: function (pos) {
15577  		var subX, subY,
15578  		    map = this._map,
15579  		    container = this._container,
15580  		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
15581  		    tooltipPoint = map.layerPointToContainerPoint(pos),
15582  		    direction = this.options.direction,
15583  		    tooltipWidth = container.offsetWidth,
15584  		    tooltipHeight = container.offsetHeight,
15585  		    offset = toPoint(this.options.offset),
15586  		    anchor = this._getAnchor();
15587
15588  		if (direction === 'top') {
15589  			subX = tooltipWidth / 2;
15590  			subY = tooltipHeight;
15591  		} else if (direction === 'bottom') {
15592  			subX = tooltipWidth / 2;
15593  			subY = 0;
15594  		} else if (direction === 'center') {
15595  			subX = tooltipWidth / 2;
15596  			subY = tooltipHeight / 2;
15597  		} else if (direction === 'right') {
15598  			subX = 0;
15599  			subY = tooltipHeight / 2;
15600  		} else if (direction === 'left') {
15601  			subX = tooltipWidth;
15602  			subY = tooltipHeight / 2;
15603  		} else if (tooltipPoint.x < centerPoint.x) {
15604  			direction = 'right';
15605  			subX = 0;
15606  			subY = tooltipHeight / 2;
15607  		} else {
15608  			direction = 'left';
15609  			subX = tooltipWidth + (offset.x + anchor.x) * 2;
15610  			subY = tooltipHeight / 2;
15611  		}
15612
15613  		pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
15614
15615  		removeClass(container, 'leaflet-tooltip-right');
15616  		removeClass(container, 'leaflet-tooltip-left');
15617  		removeClass(container, 'leaflet-tooltip-top');
15618  		removeClass(container, 'leaflet-tooltip-bottom');
15619  		addClass(container, 'leaflet-tooltip-' + direction);
15620  		setPosition(container, pos);
15621  	},
15622
15623  	_updatePosition: function () {
15624  		var pos = this._map.latLngToLayerPoint(this._latlng);
15625  		this._setPosition(pos);
15626  	},
15627
15628  	setOpacity: function (opacity) {
15629  		this.options.opacity = opacity;
15630
15631  		if (this._container) {
15632  			setOpacity(this._container, opacity);
15633  		}
15634  	},
15635
15636  	_animateZoom: function (e) {
15637  		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
15638  		this._setPosition(pos);
15639  	},
15640
15641  	_getAnchor: function () {
15642  		// Where should we anchor the tooltip on the source layer?
15643  		return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
15644  	}
15645
15646  });
15647
15648  // @namespace Tooltip
15649  // @factory L.tooltip(options?: Tooltip options, source?: Layer)
15650  // Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
15651  // @alternative
15652  // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
15653  // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
15654  var tooltip = function (options, source) {
15655  	return new Tooltip(options, source);
15656  };
15657
15658  // @namespace Map
15659  // @section Methods for Layers and Controls
15660  Map.include({
15661
15662  	// @method openTooltip(tooltip: Tooltip): this
15663  	// Opens the specified tooltip.
15664  	// @alternative
15665  	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
15666  	// Creates a tooltip with the specified content and options and open it.
15667  	openTooltip: function (tooltip, latlng, options) {
15668  		this._initOverlay(Tooltip, tooltip, latlng, options)
15669  		  .openOn(this);
15670
15671  		return this;
15672  	},
15673
15674  	// @method closeTooltip(tooltip: Tooltip): this
15675  	// Closes the tooltip given as parameter.
15676  	closeTooltip: function (tooltip) {
15677  		tooltip.close();
15678  		return this;
15679  	}
15680
15681  });
15682
15683  /*
15684   * @namespace Layer
15685   * @section Tooltip methods example
15686   *
15687   * All layers share a set of methods convenient for binding tooltips to it.
15688   *
15689   * ```js
15690   * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
15691   * layer.openTooltip();
15692   * layer.closeTooltip();
15693   * ```
15694   */
15695
15696  // @section Tooltip methods
15697  Layer.include({
15698
15699  	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
15700  	// Binds a tooltip to the layer with the passed `content` and sets up the
15701  	// necessary event listeners. If a `Function` is passed it will receive
15702  	// the layer as the first argument and should return a `String` or `HTMLElement`.
15703  	bindTooltip: function (content, options) {
15704
15705  		if (this._tooltip && this.isTooltipOpen()) {
15706  			this.unbindTooltip();
15707  		}
15708
15709  		this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
15710  		this._initTooltipInteractions();
15711
15712  		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
15713  			this.openTooltip();
15714  		}
15715
15716  		return this;
15717  	},
15718
15719  	// @method unbindTooltip(): this
15720  	// Removes the tooltip previously bound with `bindTooltip`.
15721  	unbindTooltip: function () {
15722  		if (this._tooltip) {
15723  			this._initTooltipInteractions(true);
15724  			this.closeTooltip();
15725  			this._tooltip = null;
15726  		}
15727  		return this;
15728  	},
15729
15730  	_initTooltipInteractions: function (remove) {
15731  		if (!remove && this._tooltipHandlersAdded) { return; }
15732  		var onOff = remove ? 'off' : 'on',
15733  		    events = {
15734  			remove: this.closeTooltip,
15735  			move: this._moveTooltip
15736  		    };
15737  		if (!this._tooltip.options.permanent) {
15738  			events.mouseover = this._openTooltip;
15739  			events.mouseout = this.closeTooltip;
15740  			events.click = this._openTooltip;
15741  			if (this._map) {
15742  				this._addFocusListeners();
15743  			} else {
15744  				events.add = this._addFocusListeners;
15745  			}
15746  		} else {
15747  			events.add = this._openTooltip;
15748  		}
15749  		if (this._tooltip.options.sticky) {
15750  			events.mousemove = this._moveTooltip;
15751  		}
15752  		this[onOff](events);
15753  		this._tooltipHandlersAdded = !remove;
15754  	},
15755
15756  	// @method openTooltip(latlng?: LatLng): this
15757  	// Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
15758  	openTooltip: function (latlng) {
15759  		if (this._tooltip) {
15760  			if (!(this instanceof FeatureGroup)) {
15761  				this._tooltip._source = this;
15762  			}
15763  			if (this._tooltip._prepareOpen(latlng)) {
15764  				// open the tooltip on the map
15765  				this._tooltip.openOn(this._map);
15766
15767  				if (this.getElement) {
15768  					this._setAriaDescribedByOnLayer(this);
15769  				} else if (this.eachLayer) {
15770  					this.eachLayer(this._setAriaDescribedByOnLayer, this);
15771  				}
15772  			}
15773  		}
15774  		return this;
15775  	},
15776
15777  	// @method closeTooltip(): this
15778  	// Closes the tooltip bound to this layer if it is open.
15779  	closeTooltip: function () {
15780  		if (this._tooltip) {
15781  			return this._tooltip.close();
15782  		}
15783  	},
15784
15785  	// @method toggleTooltip(): this
15786  	// Opens or closes the tooltip bound to this layer depending on its current state.
15787  	toggleTooltip: function () {
15788  		if (this._tooltip) {
15789  			this._tooltip.toggle(this);
15790  		}
15791  		return this;
15792  	},
15793
15794  	// @method isTooltipOpen(): boolean
15795  	// Returns `true` if the tooltip bound to this layer is currently open.
15796  	isTooltipOpen: function () {
15797  		return this._tooltip.isOpen();
15798  	},
15799
15800  	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
15801  	// Sets the content of the tooltip bound to this layer.
15802  	setTooltipContent: function (content) {
15803  		if (this._tooltip) {
15804  			this._tooltip.setContent(content);
15805  		}
15806  		return this;
15807  	},
15808
15809  	// @method getTooltip(): Tooltip
15810  	// Returns the tooltip bound to this layer.
15811  	getTooltip: function () {
15812  		return this._tooltip;
15813  	},
15814
15815  	_addFocusListeners: function () {
15816  		if (this.getElement) {
15817  			this._addFocusListenersOnLayer(this);
15818  		} else if (this.eachLayer) {
15819  			this.eachLayer(this._addFocusListenersOnLayer, this);
15820  		}
15821  	},
15822
15823  	_addFocusListenersOnLayer: function (layer) {
15824  		var el = typeof layer.getElement === 'function' && layer.getElement();
15825  		if (el) {
15826  			on(el, 'focus', function () {
15827  				this._tooltip._source = layer;
15828  				this.openTooltip();
15829  			}, this);
15830  			on(el, 'blur', this.closeTooltip, this);
15831  		}
15832  	},
15833
15834  	_setAriaDescribedByOnLayer: function (layer) {
15835  		var el = typeof layer.getElement === 'function' && layer.getElement();
15836  		if (el) {
15837  			el.setAttribute('aria-describedby', this._tooltip._container.id);
15838  		}
15839  	},
15840
15841
15842  	_openTooltip: function (e) {
15843  		if (!this._tooltip || !this._map) {
15844  			return;
15845  		}
15846
15847  		// If the map is moving, we will show the tooltip after it's done.
15848  		if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
15849  			this._openOnceFlag = true;
15850  			var that = this;
15851  			this._map.once('moveend', function () {
15852  				that._openOnceFlag = false;
15853  				that._openTooltip(e);
15854  			});
15855  			return;
15856  		}
15857
15858  		this._tooltip._source = e.layer || e.target;
15859
15860  		this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
15861  	},
15862
15863  	_moveTooltip: function (e) {
15864  		var latlng = e.latlng, containerPoint, layerPoint;
15865  		if (this._tooltip.options.sticky && e.originalEvent) {
15866  			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
15867  			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
15868  			latlng = this._map.layerPointToLatLng(layerPoint);
15869  		}
15870  		this._tooltip.setLatLng(latlng);
15871  	}
15872  });
15873
15874  /*
15875   * @class DivIcon
15876   * @aka L.DivIcon
15877   * @inherits Icon
15878   *
15879   * Represents a lightweight icon for markers that uses a simple `<div>`
15880   * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
15881   *
15882   * @example
15883   * ```js
15884   * var myIcon = L.divIcon({className: 'my-div-icon'});
15885   * // you can set .my-div-icon styles in CSS
15886   *
15887   * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
15888   * ```
15889   *
15890   * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
15891   */
15892
15893  var DivIcon = Icon.extend({
15894  	options: {
15895  		// @section
15896  		// @aka DivIcon options
15897  		iconSize: [12, 12], // also can be set through CSS
15898
15899  		// iconAnchor: (Point),
15900  		// popupAnchor: (Point),
15901
15902  		// @option html: String|HTMLElement = ''
15903  		// Custom HTML code to put inside the div element, empty by default. Alternatively,
15904  		// an instance of `HTMLElement`.
15905  		html: false,
15906
15907  		// @option bgPos: Point = [0, 0]
15908  		// Optional relative position of the background, in pixels
15909  		bgPos: null,
15910
15911  		className: 'leaflet-div-icon'
15912  	},
15913
15914  	createIcon: function (oldIcon) {
15915  		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
15916  		    options = this.options;
15917
15918  		if (options.html instanceof Element) {
15919  			empty(div);
15920  			div.appendChild(options.html);
15921  		} else {
15922  			div.innerHTML = options.html !== false ? options.html : '';
15923  		}
15924
15925  		if (options.bgPos) {
15926  			var bgPos = toPoint(options.bgPos);
15927  			div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
15928  		}
15929  		this._setIconStyles(div, 'icon');
15930
15931  		return div;
15932  	},
15933
15934  	createShadow: function () {
15935  		return null;
15936  	}
15937  });
15938
15939  // @factory L.divIcon(options: DivIcon options)
15940  // Creates a `DivIcon` instance with the given options.
15941  function divIcon(options) {
15942  	return new DivIcon(options);
15943  }
15944
15945  Icon.Default = IconDefault;
15946
15947  /*
15948   * @class GridLayer
15949   * @inherits Layer
15950   * @aka L.GridLayer
15951   *
15952   * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
15953   * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
15954   *
15955   *
15956   * @section Synchronous usage
15957   * @example
15958   *
15959   * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
15960   *
15961   * ```js
15962   * var CanvasLayer = L.GridLayer.extend({
15963   *     createTile: function(coords){
15964   *         // create a <canvas> element for drawing
15965   *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
15966   *
15967   *         // setup tile width and height according to the options
15968   *         var size = this.getTileSize();
15969   *         tile.width = size.x;
15970   *         tile.height = size.y;
15971   *
15972   *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
15973   *         var ctx = tile.getContext('2d');
15974   *
15975   *         // return the tile so it can be rendered on screen
15976   *         return tile;
15977   *     }
15978   * });
15979   * ```
15980   *
15981   * @section Asynchronous usage
15982   * @example
15983   *
15984   * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
15985   *
15986   * ```js
15987   * var CanvasLayer = L.GridLayer.extend({
15988   *     createTile: function(coords, done){
15989   *         var error;
15990   *
15991   *         // create a <canvas> element for drawing
15992   *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
15993   *
15994   *         // setup tile width and height according to the options
15995   *         var size = this.getTileSize();
15996   *         tile.width = size.x;
15997   *         tile.height = size.y;
15998   *
15999   *         // draw something asynchronously and pass the tile to the done() callback
16000   *         setTimeout(function() {
16001   *             done(error, tile);
16002   *         }, 1000);
16003   *
16004   *         return tile;
16005   *     }
16006   * });
16007   * ```
16008   *
16009   * @section
16010   */
16011
16012
16013  var GridLayer = Layer.extend({
16014
16015  	// @section
16016  	// @aka GridLayer options
16017  	options: {
16018  		// @option tileSize: Number|Point = 256
16019  		// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
16020  		tileSize: 256,
16021
16022  		// @option opacity: Number = 1.0
16023  		// Opacity of the tiles. Can be used in the `createTile()` function.
16024  		opacity: 1,
16025
16026  		// @option updateWhenIdle: Boolean = (depends)
16027  		// Load new tiles only when panning ends.
16028  		// `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
16029  		// `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
16030  		// [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
16031  		updateWhenIdle: Browser.mobile,
16032
16033  		// @option updateWhenZooming: Boolean = true
16034  		// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
16035  		updateWhenZooming: true,
16036
16037  		// @option updateInterval: Number = 200
16038  		// Tiles will not update more than once every `updateInterval` milliseconds when panning.
16039  		updateInterval: 200,
16040
16041  		// @option zIndex: Number = 1
16042  		// The explicit zIndex of the tile layer.
16043  		zIndex: 1,
16044
16045  		// @option bounds: LatLngBounds = undefined
16046  		// If set, tiles will only be loaded inside the set `LatLngBounds`.
16047  		bounds: null,
16048
16049  		// @option minZoom: Number = 0
16050  		// The minimum zoom level down to which this layer will be displayed (inclusive).
16051  		minZoom: 0,
16052
16053  		// @option maxZoom: Number = undefined
16054  		// The maximum zoom level up to which this layer will be displayed (inclusive).
16055  		maxZoom: undefined,
16056
16057  		// @option maxNativeZoom: Number = undefined
16058  		// Maximum zoom number the tile source has available. If it is specified,
16059  		// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
16060  		// from `maxNativeZoom` level and auto-scaled.
16061  		maxNativeZoom: undefined,
16062
16063  		// @option minNativeZoom: Number = undefined
16064  		// Minimum zoom number the tile source has available. If it is specified,
16065  		// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
16066  		// from `minNativeZoom` level and auto-scaled.
16067  		minNativeZoom: undefined,
16068
16069  		// @option noWrap: Boolean = false
16070  		// Whether the layer is wrapped around the antimeridian. If `true`, the
16071  		// GridLayer will only be displayed once at low zoom levels. Has no
16072  		// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
16073  		// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
16074  		// tiles outside the CRS limits.
16075  		noWrap: false,
16076
16077  		// @option pane: String = 'tilePane'
16078  		// `Map pane` where the grid layer will be added.
16079  		pane: 'tilePane',
16080
16081  		// @option className: String = ''
16082  		// A custom class name to assign to the tile layer. Empty by default.
16083  		className: '',
16084
16085  		// @option keepBuffer: Number = 2
16086  		// When panning the map, keep this many rows and columns of tiles before unloading them.
16087  		keepBuffer: 2
16088  	},
16089
16090  	initialize: function (options) {
16091  		setOptions(this, options);
16092  	},
16093
16094  	onAdd: function () {
16095  		this._initContainer();
16096
16097  		this._levels = {};
16098  		this._tiles = {};
16099
16100  		this._resetView(); // implicit _update() call
16101  	},
16102
16103  	beforeAdd: function (map) {
16104  		map._addZoomLimit(this);
16105  	},
16106
16107  	onRemove: function (map) {
16108  		this._removeAllTiles();
16109  		remove(this._container);
16110  		map._removeZoomLimit(this);
16111  		this._container = null;
16112  		this._tileZoom = undefined;
16113  	},
16114
16115  	// @method bringToFront: this
16116  	// Brings the tile layer to the top of all tile layers.
16117  	bringToFront: function () {
16118  		if (this._map) {
16119  			toFront(this._container);
16120  			this._setAutoZIndex(Math.max);
16121  		}
16122  		return this;
16123  	},
16124
16125  	// @method bringToBack: this
16126  	// Brings the tile layer to the bottom of all tile layers.
16127  	bringToBack: function () {
16128  		if (this._map) {
16129  			toBack(this._container);
16130  			this._setAutoZIndex(Math.min);
16131  		}
16132  		return this;
16133  	},
16134
16135  	// @method getContainer: HTMLElement
16136  	// Returns the HTML element that contains the tiles for this layer.
16137  	getContainer: function () {
16138  		return this._container;
16139  	},
16140
16141  	// @method setOpacity(opacity: Number): this
16142  	// Changes the [opacity](#gridlayer-opacity) of the grid layer.
16143  	setOpacity: function (opacity) {
16144  		this.options.opacity = opacity;
16145  		this._updateOpacity();
16146  		return this;
16147  	},
16148
16149  	// @method setZIndex(zIndex: Number): this
16150  	// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
16151  	setZIndex: function (zIndex) {
16152  		this.options.zIndex = zIndex;
16153  		this._updateZIndex();
16154
16155  		return this;
16156  	},
16157
16158  	// @method isLoading: Boolean
16159  	// Returns `true` if any tile in the grid layer has not finished loading.
16160  	isLoading: function () {
16161  		return this._loading;
16162  	},
16163
16164  	// @method redraw: this
16165  	// Causes the layer to clear all the tiles and request them again.
16166  	redraw: function () {
16167  		if (this._map) {
16168  			this._removeAllTiles();
16169  			var tileZoom = this._clampZoom(this._map.getZoom());
16170  			if (tileZoom !== this._tileZoom) {
16171  				this._tileZoom = tileZoom;
16172  				this._updateLevels();
16173  			}
16174  			this._update();
16175  		}
16176  		return this;
16177  	},
16178
16179  	getEvents: function () {
16180  		var events = {
16181  			viewprereset: this._invalidateAll,
16182  			viewreset: this._resetView,
16183  			zoom: this._resetView,
16184  			moveend: this._onMoveEnd
16185  		};
16186
16187  		if (!this.options.updateWhenIdle) {
16188  			// update tiles on move, but not more often than once per given interval
16189  			if (!this._onMove) {
16190  				this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
16191  			}
16192
16193  			events.move = this._onMove;
16194  		}
16195
16196  		if (this._zoomAnimated) {
16197  			events.zoomanim = this._animateZoom;
16198  		}
16199
16200  		return events;
16201  	},
16202
16203  	// @section Extension methods
16204  	// Layers extending `GridLayer` shall reimplement the following method.
16205  	// @method createTile(coords: Object, done?: Function): HTMLElement
16206  	// Called only internally, must be overridden by classes extending `GridLayer`.
16207  	// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
16208  	// is specified, it must be called when the tile has finished loading and drawing.
16209  	createTile: function () {
16210  		return document.createElement('div');
16211  	},
16212
16213  	// @section
16214  	// @method getTileSize: Point
16215  	// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
16216  	getTileSize: function () {
16217  		var s = this.options.tileSize;
16218  		return s instanceof Point ? s : new Point(s, s);
16219  	},
16220
16221  	_updateZIndex: function () {
16222  		if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
16223  			this._container.style.zIndex = this.options.zIndex;
16224  		}
16225  	},
16226
16227  	_setAutoZIndex: function (compare) {
16228  		// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
16229
16230  		var layers = this.getPane().children,
16231  		    edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
16232
16233  		for (var i = 0, len = layers.length, zIndex; i < len; i++) {
16234
16235  			zIndex = layers[i].style.zIndex;
16236
16237  			if (layers[i] !== this._container && zIndex) {
16238  				edgeZIndex = compare(edgeZIndex, +zIndex);
16239  			}
16240  		}
16241
16242  		if (isFinite(edgeZIndex)) {
16243  			this.options.zIndex = edgeZIndex + compare(-1, 1);
16244  			this._updateZIndex();
16245  		}
16246  	},
16247
16248  	_updateOpacity: function () {
16249  		if (!this._map) { return; }
16250
16251  		// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
16252  		if (Browser.ielt9) { return; }
16253
16254  		setOpacity(this._container, this.options.opacity);
16255
16256  		var now = +new Date(),
16257  		    nextFrame = false,
16258  		    willPrune = false;
16259
16260  		for (var key in this._tiles) {
16261  			var tile = this._tiles[key];
16262  			if (!tile.current || !tile.loaded) { continue; }
16263
16264  			var fade = Math.min(1, (now - tile.loaded) / 200);
16265
16266  			setOpacity(tile.el, fade);
16267  			if (fade < 1) {
16268  				nextFrame = true;
16269  			} else {
16270  				if (tile.active) {
16271  					willPrune = true;
16272  				} else {
16273  					this._onOpaqueTile(tile);
16274  				}
16275  				tile.active = true;
16276  			}
16277  		}
16278
16279  		if (willPrune && !this._noPrune) { this._pruneTiles(); }
16280
16281  		if (nextFrame) {
16282  			cancelAnimFrame(this._fadeFrame);
16283  			this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
16284  		}
16285  	},
16286
16287  	_onOpaqueTile: falseFn,
16288
16289  	_initContainer: function () {
16290  		if (this._container) { return; }
16291
16292  		this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
16293  		this._updateZIndex();
16294
16295  		if (this.options.opacity < 1) {
16296  			this._updateOpacity();
16297  		}
16298
16299  		this.getPane().appendChild(this._container);
16300  	},
16301
16302  	_updateLevels: function () {
16303
16304  		var zoom = this._tileZoom,
16305  		    maxZoom = this.options.maxZoom;
16306
16307  		if (zoom === undefined) { return undefined; }
16308
16309  		for (var z in this._levels) {
16310  			z = Number(z);
16311  			if (this._levels[z].el.children.length || z === zoom) {
16312  				this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
16313  				this._onUpdateLevel(z);
16314  			} else {
16315  				remove(this._levels[z].el);
16316  				this._removeTilesAtZoom(z);
16317  				this._onRemoveLevel(z);
16318  				delete this._levels[z];
16319  			}
16320  		}
16321
16322  		var level = this._levels[zoom],
16323  		    map = this._map;
16324
16325  		if (!level) {
16326  			level = this._levels[zoom] = {};
16327
16328  			level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
16329  			level.el.style.zIndex = maxZoom;
16330
16331  			level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
16332  			level.zoom = zoom;
16333
16334  			this._setZoomTransform(level, map.getCenter(), map.getZoom());
16335
16336  			// force the browser to consider the newly added element for transition
16337  			falseFn(level.el.offsetWidth);
16338
16339  			this._onCreateLevel(level);
16340  		}
16341
16342  		this._level = level;
16343
16344  		return level;
16345  	},
16346
16347  	_onUpdateLevel: falseFn,
16348
16349  	_onRemoveLevel: falseFn,
16350
16351  	_onCreateLevel: falseFn,
16352
16353  	_pruneTiles: function () {
16354  		if (!this._map) {
16355  			return;
16356  		}
16357
16358  		var key, tile;
16359
16360  		var zoom = this._map.getZoom();
16361  		if (zoom > this.options.maxZoom ||
16362  			zoom < this.options.minZoom) {
16363  			this._removeAllTiles();
16364  			return;
16365  		}
16366
16367  		for (key in this._tiles) {
16368  			tile = this._tiles[key];
16369  			tile.retain = tile.current;
16370  		}
16371
16372  		for (key in this._tiles) {
16373  			tile = this._tiles[key];
16374  			if (tile.current && !tile.active) {
16375  				var coords = tile.coords;
16376  				if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
16377  					this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
16378  				}
16379  			}
16380  		}
16381
16382  		for (key in this._tiles) {
16383  			if (!this._tiles[key].retain) {
16384  				this._removeTile(key);
16385  			}
16386  		}
16387  	},
16388
16389  	_removeTilesAtZoom: function (zoom) {
16390  		for (var key in this._tiles) {
16391  			if (this._tiles[key].coords.z !== zoom) {
16392  				continue;
16393  			}
16394  			this._removeTile(key);
16395  		}
16396  	},
16397
16398  	_removeAllTiles: function () {
16399  		for (var key in this._tiles) {
16400  			this._removeTile(key);
16401  		}
16402  	},
16403
16404  	_invalidateAll: function () {
16405  		for (var z in this._levels) {
16406  			remove(this._levels[z].el);
16407  			this._onRemoveLevel(Number(z));
16408  			delete this._levels[z];
16409  		}
16410  		this._removeAllTiles();
16411
16412  		this._tileZoom = undefined;
16413  	},
16414
16415  	_retainParent: function (x, y, z, minZoom) {
16416  		var x2 = Math.floor(x / 2),
16417  		    y2 = Math.floor(y / 2),
16418  		    z2 = z - 1,
16419  		    coords2 = new Point(+x2, +y2);
16420  		coords2.z = +z2;
16421
16422  		var key = this._tileCoordsToKey(coords2),
16423  		    tile = this._tiles[key];
16424
16425  		if (tile && tile.active) {
16426  			tile.retain = true;
16427  			return true;
16428
16429  		} else if (tile && tile.loaded) {
16430  			tile.retain = true;
16431  		}
16432
16433  		if (z2 > minZoom) {
16434  			return this._retainParent(x2, y2, z2, minZoom);
16435  		}
16436
16437  		return false;
16438  	},
16439
16440  	_retainChildren: function (x, y, z, maxZoom) {
16441
16442  		for (var i = 2 * x; i < 2 * x + 2; i++) {
16443  			for (var j = 2 * y; j < 2 * y + 2; j++) {
16444
16445  				var coords = new Point(i, j);
16446  				coords.z = z + 1;
16447
16448  				var key = this._tileCoordsToKey(coords),
16449  				    tile = this._tiles[key];
16450
16451  				if (tile && tile.active) {
16452  					tile.retain = true;
16453  					continue;
16454
16455  				} else if (tile && tile.loaded) {
16456  					tile.retain = true;
16457  				}
16458
16459  				if (z + 1 < maxZoom) {
16460  					this._retainChildren(i, j, z + 1, maxZoom);
16461  				}
16462  			}
16463  		}
16464  	},
16465
16466  	_resetView: function (e) {
16467  		var animating = e && (e.pinch || e.flyTo);
16468  		this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
16469  	},
16470
16471  	_animateZoom: function (e) {
16472  		this._setView(e.center, e.zoom, true, e.noUpdate);
16473  	},
16474
16475  	_clampZoom: function (zoom) {
16476  		var options = this.options;
16477
16478  		if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
16479  			return options.minNativeZoom;
16480  		}
16481
16482  		if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
16483  			return options.maxNativeZoom;
16484  		}
16485
16486  		return zoom;
16487  	},
16488
16489  	_setView: function (center, zoom, noPrune, noUpdate) {
16490  		var tileZoom = Math.round(zoom);
16491  		if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
16492  		    (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
16493  			tileZoom = undefined;
16494  		} else {
16495  			tileZoom = this._clampZoom(tileZoom);
16496  		}
16497
16498  		var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
16499
16500  		if (!noUpdate || tileZoomChanged) {
16501
16502  			this._tileZoom = tileZoom;
16503
16504  			if (this._abortLoading) {
16505  				this._abortLoading();
16506  			}
16507
16508  			this._updateLevels();
16509  			this._resetGrid();
16510
16511  			if (tileZoom !== undefined) {
16512  				this._update(center);
16513  			}
16514
16515  			if (!noPrune) {
16516  				this._pruneTiles();
16517  			}
16518
16519  			// Flag to prevent _updateOpacity from pruning tiles during
16520  			// a zoom anim or a pinch gesture
16521  			this._noPrune = !!noPrune;
16522  		}
16523
16524  		this._setZoomTransforms(center, zoom);
16525  	},
16526
16527  	_setZoomTransforms: function (center, zoom) {
16528  		for (var i in this._levels) {
16529  			this._setZoomTransform(this._levels[i], center, zoom);
16530  		}
16531  	},
16532
16533  	_setZoomTransform: function (level, center, zoom) {
16534  		var scale = this._map.getZoomScale(zoom, level.zoom),
16535  		    translate = level.origin.multiplyBy(scale)
16536  		        .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
16537
16538  		if (Browser.any3d) {
16539  			setTransform(level.el, translate, scale);
16540  		} else {
16541  			setPosition(level.el, translate);
16542  		}
16543  	},
16544
16545  	_resetGrid: function () {
16546  		var map = this._map,
16547  		    crs = map.options.crs,
16548  		    tileSize = this._tileSize = this.getTileSize(),
16549  		    tileZoom = this._tileZoom;
16550
16551  		var bounds = this._map.getPixelWorldBounds(this._tileZoom);
16552  		if (bounds) {
16553  			this._globalTileRange = this._pxBoundsToTileRange(bounds);
16554  		}
16555
16556  		this._wrapX = crs.wrapLng && !this.options.noWrap && [
16557  			Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
16558  			Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
16559  		];
16560  		this._wrapY = crs.wrapLat && !this.options.noWrap && [
16561  			Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
16562  			Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
16563  		];
16564  	},
16565
16566  	_onMoveEnd: function () {
16567  		if (!this._map || this._map._animatingZoom) { return; }
16568
16569  		this._update();
16570  	},
16571
16572  	_getTiledPixelBounds: function (center) {
16573  		var map = this._map,
16574  		    mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
16575  		    scale = map.getZoomScale(mapZoom, this._tileZoom),
16576  		    pixelCenter = map.project(center, this._tileZoom).floor(),
16577  		    halfSize = map.getSize().divideBy(scale * 2);
16578
16579  		return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
16580  	},
16581
16582  	// Private method to load tiles in the grid's active zoom level according to map bounds
16583  	_update: function (center) {
16584  		var map = this._map;
16585  		if (!map) { return; }
16586  		var zoom = this._clampZoom(map.getZoom());
16587
16588  		if (center === undefined) { center = map.getCenter(); }
16589  		if (this._tileZoom === undefined) { return; }	// if out of minzoom/maxzoom
16590
16591  		var pixelBounds = this._getTiledPixelBounds(center),
16592  		    tileRange = this._pxBoundsToTileRange(pixelBounds),
16593  		    tileCenter = tileRange.getCenter(),
16594  		    queue = [],
16595  		    margin = this.options.keepBuffer,
16596  		    noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
16597  		                              tileRange.getTopRight().add([margin, -margin]));
16598
16599  		// Sanity check: panic if the tile range contains Infinity somewhere.
16600  		if (!(isFinite(tileRange.min.x) &&
16601  		      isFinite(tileRange.min.y) &&
16602  		      isFinite(tileRange.max.x) &&
16603  		      isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
16604
16605  		for (var key in this._tiles) {
16606  			var c = this._tiles[key].coords;
16607  			if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
16608  				this._tiles[key].current = false;
16609  			}
16610  		}
16611
16612  		// _update just loads more tiles. If the tile zoom level differs too much
16613  		// from the map's, let _setView reset levels and prune old tiles.
16614  		if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
16615
16616  		// create a queue of coordinates to load tiles from
16617  		for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
16618  			for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
16619  				var coords = new Point(i, j);
16620  				coords.z = this._tileZoom;
16621
16622  				if (!this._isValidTile(coords)) { continue; }
16623
16624  				var tile = this._tiles[this._tileCoordsToKey(coords)];
16625  				if (tile) {
16626  					tile.current = true;
16627  				} else {
16628  					queue.push(coords);
16629  				}
16630  			}
16631  		}
16632
16633  		// sort tile queue to load tiles in order of their distance to center
16634  		queue.sort(function (a, b) {
16635  			return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
16636  		});
16637
16638  		if (queue.length !== 0) {
16639  			// if it's the first batch of tiles to load
16640  			if (!this._loading) {
16641  				this._loading = true;
16642  				// @event loading: Event
16643  				// Fired when the grid layer starts loading tiles.
16644  				this.fire('loading');
16645  			}
16646
16647  			// create DOM fragment to append tiles in one batch
16648  			var fragment = document.createDocumentFragment();
16649
16650  			for (i = 0; i < queue.length; i++) {
16651  				this._addTile(queue[i], fragment);
16652  			}
16653
16654  			this._level.el.appendChild(fragment);
16655  		}
16656  	},
16657
16658  	_isValidTile: function (coords) {
16659  		var crs = this._map.options.crs;
16660
16661  		if (!crs.infinite) {
16662  			// don't load tile if it's out of bounds and not wrapped
16663  			var bounds = this._globalTileRange;
16664  			if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
16665  			    (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
16666  		}
16667
16668  		if (!this.options.bounds) { return true; }
16669
16670  		// don't load tile if it doesn't intersect the bounds in options
16671  		var tileBounds = this._tileCoordsToBounds(coords);
16672  		return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
16673  	},
16674
16675  	_keyToBounds: function (key) {
16676  		return this._tileCoordsToBounds(this._keyToTileCoords(key));
16677  	},
16678
16679  	_tileCoordsToNwSe: function (coords) {
16680  		var map = this._map,
16681  		    tileSize = this.getTileSize(),
16682  		    nwPoint = coords.scaleBy(tileSize),
16683  		    sePoint = nwPoint.add(tileSize),
16684  		    nw = map.unproject(nwPoint, coords.z),
16685  		    se = map.unproject(sePoint, coords.z);
16686  		return [nw, se];
16687  	},
16688
16689  	// converts tile coordinates to its geographical bounds
16690  	_tileCoordsToBounds: function (coords) {
16691  		var bp = this._tileCoordsToNwSe(coords),
16692  		    bounds = new LatLngBounds(bp[0], bp[1]);
16693
16694  		if (!this.options.noWrap) {
16695  			bounds = this._map.wrapLatLngBounds(bounds);
16696  		}
16697  		return bounds;
16698  	},
16699  	// converts tile coordinates to key for the tile cache
16700  	_tileCoordsToKey: function (coords) {
16701  		return coords.x + ':' + coords.y + ':' + coords.z;
16702  	},
16703
16704  	// converts tile cache key to coordinates
16705  	_keyToTileCoords: function (key) {
16706  		var k = key.split(':'),
16707  		    coords = new Point(+k[0], +k[1]);
16708  		coords.z = +k[2];
16709  		return coords;
16710  	},
16711
16712  	_removeTile: function (key) {
16713  		var tile = this._tiles[key];
16714  		if (!tile) { return; }
16715
16716  		remove(tile.el);
16717
16718  		delete this._tiles[key];
16719
16720  		// @event tileunload: TileEvent
16721  		// Fired when a tile is removed (e.g. when a tile goes off the screen).
16722  		this.fire('tileunload', {
16723  			tile: tile.el,
16724  			coords: this._keyToTileCoords(key)
16725  		});
16726  	},
16727
16728  	_initTile: function (tile) {
16729  		addClass(tile, 'leaflet-tile');
16730
16731  		var tileSize = this.getTileSize();
16732  		tile.style.width = tileSize.x + 'px';
16733  		tile.style.height = tileSize.y + 'px';
16734
16735  		tile.onselectstart = falseFn;
16736  		tile.onmousemove = falseFn;
16737
16738  		// update opacity on tiles in IE7-8 because of filter inheritance problems
16739  		if (Browser.ielt9 && this.options.opacity < 1) {
16740  			setOpacity(tile, this.options.opacity);
16741  		}
16742  	},
16743
16744  	_addTile: function (coords, container) {
16745  		var tilePos = this._getTilePos(coords),
16746  		    key = this._tileCoordsToKey(coords);
16747
16748  		var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
16749
16750  		this._initTile(tile);
16751
16752  		// if createTile is defined with a second argument ("done" callback),
16753  		// we know that tile is async and will be ready later; otherwise
16754  		if (this.createTile.length < 2) {
16755  			// mark tile as ready, but delay one frame for opacity animation to happen
16756  			requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
16757  		}
16758
16759  		setPosition(tile, tilePos);
16760
16761  		// save tile in cache
16762  		this._tiles[key] = {
16763  			el: tile,
16764  			coords: coords,
16765  			current: true
16766  		};
16767
16768  		container.appendChild(tile);
16769  		// @event tileloadstart: TileEvent
16770  		// Fired when a tile is requested and starts loading.
16771  		this.fire('tileloadstart', {
16772  			tile: tile,
16773  			coords: coords
16774  		});
16775  	},
16776
16777  	_tileReady: function (coords, err, tile) {
16778  		if (err) {
16779  			// @event tileerror: TileErrorEvent
16780  			// Fired when there is an error loading a tile.
16781  			this.fire('tileerror', {
16782  				error: err,
16783  				tile: tile,
16784  				coords: coords
16785  			});
16786  		}
16787
16788  		var key = this._tileCoordsToKey(coords);
16789
16790  		tile = this._tiles[key];
16791  		if (!tile) { return; }
16792
16793  		tile.loaded = +new Date();
16794  		if (this._map._fadeAnimated) {
16795  			setOpacity(tile.el, 0);
16796  			cancelAnimFrame(this._fadeFrame);
16797  			this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
16798  		} else {
16799  			tile.active = true;
16800  			this._pruneTiles();
16801  		}
16802
16803  		if (!err) {
16804  			addClass(tile.el, 'leaflet-tile-loaded');
16805
16806  			// @event tileload: TileEvent
16807  			// Fired when a tile loads.
16808  			this.fire('tileload', {
16809  				tile: tile.el,
16810  				coords: coords
16811  			});
16812  		}
16813
16814  		if (this._noTilesToLoad()) {
16815  			this._loading = false;
16816  			// @event load: Event
16817  			// Fired when the grid layer loaded all visible tiles.
16818  			this.fire('load');
16819
16820  			if (Browser.ielt9 || !this._map._fadeAnimated) {
16821  				requestAnimFrame(this._pruneTiles, this);
16822  			} else {
16823  				// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
16824  				// to trigger a pruning.
16825  				setTimeout(bind(this._pruneTiles, this), 250);
16826  			}
16827  		}
16828  	},
16829
16830  	_getTilePos: function (coords) {
16831  		return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
16832  	},
16833
16834  	_wrapCoords: function (coords) {
16835  		var newCoords = new Point(
16836  			this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
16837  			this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
16838  		newCoords.z = coords.z;
16839  		return newCoords;
16840  	},
16841
16842  	_pxBoundsToTileRange: function (bounds) {
16843  		var tileSize = this.getTileSize();
16844  		return new Bounds(
16845  			bounds.min.unscaleBy(tileSize).floor(),
16846  			bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
16847  	},
16848
16849  	_noTilesToLoad: function () {
16850  		for (var key in this._tiles) {
16851  			if (!this._tiles[key].loaded) { return false; }
16852  		}
16853  		return true;
16854  	}
16855  });
16856
16857  // @factory L.gridLayer(options?: GridLayer options)
16858  // Creates a new instance of GridLayer with the supplied options.
16859  function gridLayer(options) {
16860  	return new GridLayer(options);
16861  }
16862
16863  /*
16864   * @class TileLayer
16865   * @inherits GridLayer
16866   * @aka L.TileLayer
16867   * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
16868   *
16869   * @example
16870   *
16871   * ```js
16872   * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
16873   * ```
16874   *
16875   * @section URL template
16876   * @example
16877   *
16878   * A string of the following form:
16879   *
16880   * ```
16881   * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
16882   * ```
16883   *
16884   * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "&commat;2x" to the URL to load retina tiles.
16885   *
16886   * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
16887   *
16888   * ```
16889   * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
16890   * ```
16891   */
16892
16893
16894  var TileLayer = GridLayer.extend({
16895
16896  	// @section
16897  	// @aka TileLayer options
16898  	options: {
16899  		// @option minZoom: Number = 0
16900  		// The minimum zoom level down to which this layer will be displayed (inclusive).
16901  		minZoom: 0,
16902
16903  		// @option maxZoom: Number = 18
16904  		// The maximum zoom level up to which this layer will be displayed (inclusive).
16905  		maxZoom: 18,
16906
16907  		// @option subdomains: String|String[] = 'abc'
16908  		// Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
16909  		subdomains: 'abc',
16910
16911  		// @option errorTileUrl: String = ''
16912  		// URL to the tile image to show in place of the tile that failed to load.
16913  		errorTileUrl: '',
16914
16915  		// @option zoomOffset: Number = 0
16916  		// The zoom number used in tile URLs will be offset with this value.
16917  		zoomOffset: 0,
16918
16919  		// @option tms: Boolean = false
16920  		// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
16921  		tms: false,
16922
16923  		// @option zoomReverse: Boolean = false
16924  		// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
16925  		zoomReverse: false,
16926
16927  		// @option detectRetina: Boolean = false
16928  		// If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
16929  		detectRetina: false,
16930
16931  		// @option crossOrigin: Boolean|String = false
16932  		// Whether the crossOrigin attribute will be added to the tiles.
16933  		// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
16934  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
16935  		crossOrigin: false,
16936
16937  		// @option referrerPolicy: Boolean|String = false
16938  		// Whether the referrerPolicy attribute will be added to the tiles.
16939  		// If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
16940  		// This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
16941  		// (e.g. to validate an API token).
16942  		// Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
16943  		referrerPolicy: false
16944  	},
16945
16946  	initialize: function (url, options) {
16947
16948  		this._url = url;
16949
16950  		options = setOptions(this, options);
16951
16952  		// detecting retina displays, adjusting tileSize and zoom levels
16953  		if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
16954
16955  			options.tileSize = Math.floor(options.tileSize / 2);
16956
16957  			if (!options.zoomReverse) {
16958  				options.zoomOffset++;
16959  				options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
16960  			} else {
16961  				options.zoomOffset--;
16962  				options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
16963  			}
16964
16965  			options.minZoom = Math.max(0, options.minZoom);
16966  		} else if (!options.zoomReverse) {
16967  			// make sure maxZoom is gte minZoom
16968  			options.maxZoom = Math.max(options.minZoom, options.maxZoom);
16969  		} else {
16970  			// make sure minZoom is lte maxZoom
16971  			options.minZoom = Math.min(options.maxZoom, options.minZoom);
16972  		}
16973
16974  		if (typeof options.subdomains === 'string') {
16975  			options.subdomains = options.subdomains.split('');
16976  		}
16977
16978  		this.on('tileunload', this._onTileRemove);
16979  	},
16980
16981  	// @method setUrl(url: String, noRedraw?: Boolean): this
16982  	// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
16983  	// If the URL does not change, the layer will not be redrawn unless
16984  	// the noRedraw parameter is set to false.
16985  	setUrl: function (url, noRedraw) {
16986  		if (this._url === url && noRedraw === undefined) {
16987  			noRedraw = true;
16988  		}
16989
16990  		this._url = url;
16991
16992  		if (!noRedraw) {
16993  			this.redraw();
16994  		}
16995  		return this;
16996  	},
16997
16998  	// @method createTile(coords: Object, done?: Function): HTMLElement
16999  	// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
17000  	// to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
17001  	// callback is called when the tile has been loaded.
17002  	createTile: function (coords, done) {
17003  		var tile = document.createElement('img');
17004
17005  		on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
17006  		on(tile, 'error', bind(this._tileOnError, this, done, tile));
17007
17008  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
17009  			tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
17010  		}
17011
17012  		// for this new option we follow the documented behavior
17013  		// more closely by only setting the property when string
17014  		if (typeof this.options.referrerPolicy === 'string') {
17015  			tile.referrerPolicy = this.options.referrerPolicy;
17016  		}
17017
17018  		// The alt attribute is set to the empty string,
17019  		// allowing screen readers to ignore the decorative image tiles.
17020  		// https://www.w3.org/WAI/tutorials/images/decorative/
17021  		// https://www.w3.org/TR/html-aria/#el-img-empty-alt
17022  		tile.alt = '';
17023
17024  		tile.src = this.getTileUrl(coords);
17025
17026  		return tile;
17027  	},
17028
17029  	// @section Extension methods
17030  	// @uninheritable
17031  	// Layers extending `TileLayer` might reimplement the following method.
17032  	// @method getTileUrl(coords: Object): String
17033  	// Called only internally, returns the URL for a tile given its coordinates.
17034  	// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
17035  	getTileUrl: function (coords) {
17036  		var data = {
17037  			r: Browser.retina ? '@2x' : '',
17038  			s: this._getSubdomain(coords),
17039  			x: coords.x,
17040  			y: coords.y,
17041  			z: this._getZoomForUrl()
17042  		};
17043  		if (this._map && !this._map.options.crs.infinite) {
17044  			var invertedY = this._globalTileRange.max.y - coords.y;
17045  			if (this.options.tms) {
17046  				data['y'] = invertedY;
17047  			}
17048  			data['-y'] = invertedY;
17049  		}
17050
17051  		return template(this._url, extend(data, this.options));
17052  	},
17053
17054  	_tileOnLoad: function (done, tile) {
17055  		// For https://github.com/Leaflet/Leaflet/issues/3332
17056  		if (Browser.ielt9) {
17057  			setTimeout(bind(done, this, null, tile), 0);
17058  		} else {
17059  			done(null, tile);
17060  		}
17061  	},
17062
17063  	_tileOnError: function (done, tile, e) {
17064  		var errorUrl = this.options.errorTileUrl;
17065  		if (errorUrl && tile.getAttribute('src') !== errorUrl) {
17066  			tile.src = errorUrl;
17067  		}
17068  		done(e, tile);
17069  	},
17070
17071  	_onTileRemove: function (e) {
17072  		e.tile.onload = null;
17073  	},
17074
17075  	_getZoomForUrl: function () {
17076  		var zoom = this._tileZoom,
17077  		maxZoom = this.options.maxZoom,
17078  		zoomReverse = this.options.zoomReverse,
17079  		zoomOffset = this.options.zoomOffset;
17080
17081  		if (zoomReverse) {
17082  			zoom = maxZoom - zoom;
17083  		}
17084
17085  		return zoom + zoomOffset;
17086  	},
17087
17088  	_getSubdomain: function (tilePoint) {
17089  		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
17090  		return this.options.subdomains[index];
17091  	},
17092
17093  	// stops loading all tiles in the background layer
17094  	_abortLoading: function () {
17095  		var i, tile;
17096  		for (i in this._tiles) {
17097  			if (this._tiles[i].coords.z !== this._tileZoom) {
17098  				tile = this._tiles[i].el;
17099
17100  				tile.onload = falseFn;
17101  				tile.onerror = falseFn;
17102
17103  				if (!tile.complete) {
17104  					tile.src = emptyImageUrl;
17105  					var coords = this._tiles[i].coords;
17106  					remove(tile);
17107  					delete this._tiles[i];
17108  					// @event tileabort: TileEvent
17109  					// Fired when a tile was loading but is now not wanted.
17110  					this.fire('tileabort', {
17111  						tile: tile,
17112  						coords: coords
17113  					});
17114  				}
17115  			}
17116  		}
17117  	},
17118
17119  	_removeTile: function (key) {
17120  		var tile = this._tiles[key];
17121  		if (!tile) { return; }
17122
17123  		// Cancels any pending http requests associated with the tile
17124  		tile.el.setAttribute('src', emptyImageUrl);
17125
17126  		return GridLayer.prototype._removeTile.call(this, key);
17127  	},
17128
17129  	_tileReady: function (coords, err, tile) {
17130  		if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
17131  			return;
17132  		}
17133
17134  		return GridLayer.prototype._tileReady.call(this, coords, err, tile);
17135  	}
17136  });
17137
17138
17139  // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
17140  // Instantiates a tile layer object given a `URL template` and optionally an options object.
17141
17142  function tileLayer(url, options) {
17143  	return new TileLayer(url, options);
17144  }
17145
17146  /*
17147   * @class TileLayer.WMS
17148   * @inherits TileLayer
17149   * @aka L.TileLayer.WMS
17150   * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
17151   *
17152   * @example
17153   *
17154   * ```js
17155   * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
17156   * 	layers: 'nexrad-n0r-900913',
17157   * 	format: 'image/png',
17158   * 	transparent: true,
17159   * 	attribution: "Weather data © 2012 IEM Nexrad"
17160   * });
17161   * ```
17162   */
17163
17164  var TileLayerWMS = TileLayer.extend({
17165
17166  	// @section
17167  	// @aka TileLayer.WMS options
17168  	// If any custom options not documented here are used, they will be sent to the
17169  	// WMS server as extra parameters in each request URL. This can be useful for
17170  	// [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
17171  	defaultWmsParams: {
17172  		service: 'WMS',
17173  		request: 'GetMap',
17174
17175  		// @option layers: String = ''
17176  		// **(required)** Comma-separated list of WMS layers to show.
17177  		layers: '',
17178
17179  		// @option styles: String = ''
17180  		// Comma-separated list of WMS styles.
17181  		styles: '',
17182
17183  		// @option format: String = 'image/jpeg'
17184  		// WMS image format (use `'image/png'` for layers with transparency).
17185  		format: 'image/jpeg',
17186
17187  		// @option transparent: Boolean = false
17188  		// If `true`, the WMS service will return images with transparency.
17189  		transparent: false,
17190
17191  		// @option version: String = '1.1.1'
17192  		// Version of the WMS service to use
17193  		version: '1.1.1'
17194  	},
17195
17196  	options: {
17197  		// @option crs: CRS = null
17198  		// Coordinate Reference System to use for the WMS requests, defaults to
17199  		// map CRS. Don't change this if you're not sure what it means.
17200  		crs: null,
17201
17202  		// @option uppercase: Boolean = false
17203  		// If `true`, WMS request parameter keys will be uppercase.
17204  		uppercase: false
17205  	},
17206
17207  	initialize: function (url, options) {
17208
17209  		this._url = url;
17210
17211  		var wmsParams = extend({}, this.defaultWmsParams);
17212
17213  		// all keys that are not TileLayer options go to WMS params
17214  		for (var i in options) {
17215  			if (!(i in this.options)) {
17216  				wmsParams[i] = options[i];
17217  			}
17218  		}
17219
17220  		options = setOptions(this, options);
17221
17222  		var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
17223  		var tileSize = this.getTileSize();
17224  		wmsParams.width = tileSize.x * realRetina;
17225  		wmsParams.height = tileSize.y * realRetina;
17226
17227  		this.wmsParams = wmsParams;
17228  	},
17229
17230  	onAdd: function (map) {
17231
17232  		this._crs = this.options.crs || map.options.crs;
17233  		this._wmsVersion = parseFloat(this.wmsParams.version);
17234
17235  		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
17236  		this.wmsParams[projectionKey] = this._crs.code;
17237
17238  		TileLayer.prototype.onAdd.call(this, map);
17239  	},
17240
17241  	getTileUrl: function (coords) {
17242
17243  		var tileBounds = this._tileCoordsToNwSe(coords),
17244  		    crs = this._crs,
17245  		    bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
17246  		    min = bounds.min,
17247  		    max = bounds.max,
17248  		    bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
17249  		    [min.y, min.x, max.y, max.x] :
17250  		    [min.x, min.y, max.x, max.y]).join(','),
17251  		    url = TileLayer.prototype.getTileUrl.call(this, coords);
17252  		return url +
17253  			getParamString(this.wmsParams, url, this.options.uppercase) +
17254  			(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
17255  	},
17256
17257  	// @method setParams(params: Object, noRedraw?: Boolean): this
17258  	// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
17259  	setParams: function (params, noRedraw) {
17260
17261  		extend(this.wmsParams, params);
17262
17263  		if (!noRedraw) {
17264  			this.redraw();
17265  		}
17266
17267  		return this;
17268  	}
17269  });
17270
17271
17272  // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
17273  // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
17274  function tileLayerWMS(url, options) {
17275  	return new TileLayerWMS(url, options);
17276  }
17277
17278  TileLayer.WMS = TileLayerWMS;
17279  tileLayer.wms = tileLayerWMS;
17280
17281  /*
17282   * @class Renderer
17283   * @inherits Layer
17284   * @aka L.Renderer
17285   *
17286   * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
17287   * DOM container of the renderer, its bounds, and its zoom animation.
17288   *
17289   * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
17290   * itself can be added or removed to the map. All paths use a renderer, which can
17291   * be implicit (the map will decide the type of renderer and use it automatically)
17292   * or explicit (using the [`renderer`](#path-renderer) option of the path).
17293   *
17294   * Do not use this class directly, use `SVG` and `Canvas` instead.
17295   *
17296   * @event update: Event
17297   * Fired when the renderer updates its bounds, center and zoom, for example when
17298   * its map has moved
17299   */
17300
17301  var Renderer = Layer.extend({
17302
17303  	// @section
17304  	// @aka Renderer options
17305  	options: {
17306  		// @option padding: Number = 0.1
17307  		// How much to extend the clip area around the map view (relative to its size)
17308  		// e.g. 0.1 would be 10% of map view in each direction
17309  		padding: 0.1
17310  	},
17311
17312  	initialize: function (options) {
17313  		setOptions(this, options);
17314  		stamp(this);
17315  		this._layers = this._layers || {};
17316  	},
17317
17318  	onAdd: function () {
17319  		if (!this._container) {
17320  			this._initContainer(); // defined by renderer implementations
17321
17322  			// always keep transform-origin as 0 0
17323  			addClass(this._container, 'leaflet-zoom-animated');
17324  		}
17325
17326  		this.getPane().appendChild(this._container);
17327  		this._update();
17328  		this.on('update', this._updatePaths, this);
17329  	},
17330
17331  	onRemove: function () {
17332  		this.off('update', this._updatePaths, this);
17333  		this._destroyContainer();
17334  	},
17335
17336  	getEvents: function () {
17337  		var events = {
17338  			viewreset: this._reset,
17339  			zoom: this._onZoom,
17340  			moveend: this._update,
17341  			zoomend: this._onZoomEnd
17342  		};
17343  		if (this._zoomAnimated) {
17344  			events.zoomanim = this._onAnimZoom;
17345  		}
17346  		return events;
17347  	},
17348
17349  	_onAnimZoom: function (ev) {
17350  		this._updateTransform(ev.center, ev.zoom);
17351  	},
17352
17353  	_onZoom: function () {
17354  		this._updateTransform(this._map.getCenter(), this._map.getZoom());
17355  	},
17356
17357  	_updateTransform: function (center, zoom) {
17358  		var scale = this._map.getZoomScale(zoom, this._zoom),
17359  		    viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
17360  		    currentCenterPoint = this._map.project(this._center, zoom),
17361
17362  		    topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
17363  				  .subtract(this._map._getNewPixelOrigin(center, zoom));
17364
17365  		if (Browser.any3d) {
17366  			setTransform(this._container, topLeftOffset, scale);
17367  		} else {
17368  			setPosition(this._container, topLeftOffset);
17369  		}
17370  	},
17371
17372  	_reset: function () {
17373  		this._update();
17374  		this._updateTransform(this._center, this._zoom);
17375
17376  		for (var id in this._layers) {
17377  			this._layers[id]._reset();
17378  		}
17379  	},
17380
17381  	_onZoomEnd: function () {
17382  		for (var id in this._layers) {
17383  			this._layers[id]._project();
17384  		}
17385  	},
17386
17387  	_updatePaths: function () {
17388  		for (var id in this._layers) {
17389  			this._layers[id]._update();
17390  		}
17391  	},
17392
17393  	_update: function () {
17394  		// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
17395  		// Subclasses are responsible of firing the 'update' event.
17396  		var p = this.options.padding,
17397  		    size = this._map.getSize(),
17398  		    min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
17399
17400  		this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
17401
17402  		this._center = this._map.getCenter();
17403  		this._zoom = this._map.getZoom();
17404  	}
17405  });
17406
17407  /*
17408   * @class Canvas
17409   * @inherits Renderer
17410   * @aka L.Canvas
17411   *
17412   * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
17413   * Inherits `Renderer`.
17414   *
17415   * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
17416   * available in all web browsers, notably IE8, and overlapping geometries might
17417   * not display properly in some edge cases.
17418   *
17419   * @example
17420   *
17421   * Use Canvas by default for all paths in the map:
17422   *
17423   * ```js
17424   * var map = L.map('map', {
17425   * 	renderer: L.canvas()
17426   * });
17427   * ```
17428   *
17429   * Use a Canvas renderer with extra padding for specific vector geometries:
17430   *
17431   * ```js
17432   * var map = L.map('map');
17433   * var myRenderer = L.canvas({ padding: 0.5 });
17434   * var line = L.polyline( coordinates, { renderer: myRenderer } );
17435   * var circle = L.circle( center, { renderer: myRenderer } );
17436   * ```
17437   */
17438
17439  var Canvas = Renderer.extend({
17440
17441  	// @section
17442  	// @aka Canvas options
17443  	options: {
17444  		// @option tolerance: Number = 0
17445  		// How much to extend the click tolerance around a path/object on the map.
17446  		tolerance: 0
17447  	},
17448
17449  	getEvents: function () {
17450  		var events = Renderer.prototype.getEvents.call(this);
17451  		events.viewprereset = this._onViewPreReset;
17452  		return events;
17453  	},
17454
17455  	_onViewPreReset: function () {
17456  		// Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
17457  		this._postponeUpdatePaths = true;
17458  	},
17459
17460  	onAdd: function () {
17461  		Renderer.prototype.onAdd.call(this);
17462
17463  		// Redraw vectors since canvas is cleared upon removal,
17464  		// in case of removing the renderer itself from the map.
17465  		this._draw();
17466  	},
17467
17468  	_initContainer: function () {
17469  		var container = this._container = document.createElement('canvas');
17470
17471  		on(container, 'mousemove', this._onMouseMove, this);
17472  		on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
17473  		on(container, 'mouseout', this._handleMouseOut, this);
17474  		container['_leaflet_disable_events'] = true;
17475
17476  		this._ctx = container.getContext('2d');
17477  	},
17478
17479  	_destroyContainer: function () {
17480  		cancelAnimFrame(this._redrawRequest);
17481  		delete this._ctx;
17482  		remove(this._container);
17483  		off(this._container);
17484  		delete this._container;
17485  	},
17486
17487  	_updatePaths: function () {
17488  		if (this._postponeUpdatePaths) { return; }
17489
17490  		var layer;
17491  		this._redrawBounds = null;
17492  		for (var id in this._layers) {
17493  			layer = this._layers[id];
17494  			layer._update();
17495  		}
17496  		this._redraw();
17497  	},
17498
17499  	_update: function () {
17500  		if (this._map._animatingZoom && this._bounds) { return; }
17501
17502  		Renderer.prototype._update.call(this);
17503
17504  		var b = this._bounds,
17505  		    container = this._container,
17506  		    size = b.getSize(),
17507  		    m = Browser.retina ? 2 : 1;
17508
17509  		setPosition(container, b.min);
17510
17511  		// set canvas size (also clearing it); use double size on retina
17512  		container.width = m * size.x;
17513  		container.height = m * size.y;
17514  		container.style.width = size.x + 'px';
17515  		container.style.height = size.y + 'px';
17516
17517  		if (Browser.retina) {
17518  			this._ctx.scale(2, 2);
17519  		}
17520
17521  		// translate so we use the same path coordinates after canvas element moves
17522  		this._ctx.translate(-b.min.x, -b.min.y);
17523
17524  		// Tell paths to redraw themselves
17525  		this.fire('update');
17526  	},
17527
17528  	_reset: function () {
17529  		Renderer.prototype._reset.call(this);
17530
17531  		if (this._postponeUpdatePaths) {
17532  			this._postponeUpdatePaths = false;
17533  			this._updatePaths();
17534  		}
17535  	},
17536
17537  	_initPath: function (layer) {
17538  		this._updateDashArray(layer);
17539  		this._layers[stamp(layer)] = layer;
17540
17541  		var order = layer._order = {
17542  			layer: layer,
17543  			prev: this._drawLast,
17544  			next: null
17545  		};
17546  		if (this._drawLast) { this._drawLast.next = order; }
17547  		this._drawLast = order;
17548  		this._drawFirst = this._drawFirst || this._drawLast;
17549  	},
17550
17551  	_addPath: function (layer) {
17552  		this._requestRedraw(layer);
17553  	},
17554
17555  	_removePath: function (layer) {
17556  		var order = layer._order;
17557  		var next = order.next;
17558  		var prev = order.prev;
17559
17560  		if (next) {
17561  			next.prev = prev;
17562  		} else {
17563  			this._drawLast = prev;
17564  		}
17565  		if (prev) {
17566  			prev.next = next;
17567  		} else {
17568  			this._drawFirst = next;
17569  		}
17570
17571  		delete layer._order;
17572
17573  		delete this._layers[stamp(layer)];
17574
17575  		this._requestRedraw(layer);
17576  	},
17577
17578  	_updatePath: function (layer) {
17579  		// Redraw the union of the layer's old pixel
17580  		// bounds and the new pixel bounds.
17581  		this._extendRedrawBounds(layer);
17582  		layer._project();
17583  		layer._update();
17584  		// The redraw will extend the redraw bounds
17585  		// with the new pixel bounds.
17586  		this._requestRedraw(layer);
17587  	},
17588
17589  	_updateStyle: function (layer) {
17590  		this._updateDashArray(layer);
17591  		this._requestRedraw(layer);
17592  	},
17593
17594  	_updateDashArray: function (layer) {
17595  		if (typeof layer.options.dashArray === 'string') {
17596  			var parts = layer.options.dashArray.split(/[, ]+/),
17597  			    dashArray = [],
17598  			    dashValue,
17599  			    i;
17600  			for (i = 0; i < parts.length; i++) {
17601  				dashValue = Number(parts[i]);
17602  				// Ignore dash array containing invalid lengths
17603  				if (isNaN(dashValue)) { return; }
17604  				dashArray.push(dashValue);
17605  			}
17606  			layer.options._dashArray = dashArray;
17607  		} else {
17608  			layer.options._dashArray = layer.options.dashArray;
17609  		}
17610  	},
17611
17612  	_requestRedraw: function (layer) {
17613  		if (!this._map) { return; }
17614
17615  		this._extendRedrawBounds(layer);
17616  		this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
17617  	},
17618
17619  	_extendRedrawBounds: function (layer) {
17620  		if (layer._pxBounds) {
17621  			var padding = (layer.options.weight || 0) + 1;
17622  			this._redrawBounds = this._redrawBounds || new Bounds();
17623  			this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
17624  			this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
17625  		}
17626  	},
17627
17628  	_redraw: function () {
17629  		this._redrawRequest = null;
17630
17631  		if (this._redrawBounds) {
17632  			this._redrawBounds.min._floor();
17633  			this._redrawBounds.max._ceil();
17634  		}
17635
17636  		this._clear(); // clear layers in redraw bounds
17637  		this._draw(); // draw layers
17638
17639  		this._redrawBounds = null;
17640  	},
17641
17642  	_clear: function () {
17643  		var bounds = this._redrawBounds;
17644  		if (bounds) {
17645  			var size = bounds.getSize();
17646  			this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
17647  		} else {
17648  			this._ctx.save();
17649  			this._ctx.setTransform(1, 0, 0, 1, 0, 0);
17650  			this._ctx.clearRect(0, 0, this._container.width, this._container.height);
17651  			this._ctx.restore();
17652  		}
17653  	},
17654
17655  	_draw: function () {
17656  		var layer, bounds = this._redrawBounds;
17657  		this._ctx.save();
17658  		if (bounds) {
17659  			var size = bounds.getSize();
17660  			this._ctx.beginPath();
17661  			this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
17662  			this._ctx.clip();
17663  		}
17664
17665  		this._drawing = true;
17666
17667  		for (var order = this._drawFirst; order; order = order.next) {
17668  			layer = order.layer;
17669  			if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
17670  				layer._updatePath();
17671  			}
17672  		}
17673
17674  		this._drawing = false;
17675
17676  		this._ctx.restore();  // Restore state before clipping.
17677  	},
17678
17679  	_updatePoly: function (layer, closed) {
17680  		if (!this._drawing) { return; }
17681
17682  		var i, j, len2, p,
17683  		    parts = layer._parts,
17684  		    len = parts.length,
17685  		    ctx = this._ctx;
17686
17687  		if (!len) { return; }
17688
17689  		ctx.beginPath();
17690
17691  		for (i = 0; i < len; i++) {
17692  			for (j = 0, len2 = parts[i].length; j < len2; j++) {
17693  				p = parts[i][j];
17694  				ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
17695  			}
17696  			if (closed) {
17697  				ctx.closePath();
17698  			}
17699  		}
17700
17701  		this._fillStroke(ctx, layer);
17702
17703  		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
17704  	},
17705
17706  	_updateCircle: function (layer) {
17707
17708  		if (!this._drawing || layer._empty()) { return; }
17709
17710  		var p = layer._point,
17711  		    ctx = this._ctx,
17712  		    r = Math.max(Math.round(layer._radius), 1),
17713  		    s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
17714
17715  		if (s !== 1) {
17716  			ctx.save();
17717  			ctx.scale(1, s);
17718  		}
17719
17720  		ctx.beginPath();
17721  		ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
17722
17723  		if (s !== 1) {
17724  			ctx.restore();
17725  		}
17726
17727  		this._fillStroke(ctx, layer);
17728  	},
17729
17730  	_fillStroke: function (ctx, layer) {
17731  		var options = layer.options;
17732
17733  		if (options.fill) {
17734  			ctx.globalAlpha = options.fillOpacity;
17735  			ctx.fillStyle = options.fillColor || options.color;
17736  			ctx.fill(options.fillRule || 'evenodd');
17737  		}
17738
17739  		if (options.stroke && options.weight !== 0) {
17740  			if (ctx.setLineDash) {
17741  				ctx.setLineDash(layer.options && layer.options._dashArray || []);
17742  			}
17743  			ctx.globalAlpha = options.opacity;
17744  			ctx.lineWidth = options.weight;
17745  			ctx.strokeStyle = options.color;
17746  			ctx.lineCap = options.lineCap;
17747  			ctx.lineJoin = options.lineJoin;
17748  			ctx.stroke();
17749  		}
17750  	},
17751
17752  	// Canvas obviously doesn't have mouse events for individual drawn objects,
17753  	// so we emulate that by calculating what's under the mouse on mousemove/click manually
17754
17755  	_onClick: function (e) {
17756  		var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
17757
17758  		for (var order = this._drawFirst; order; order = order.next) {
17759  			layer = order.layer;
17760  			if (layer.options.interactive && layer._containsPoint(point)) {
17761  				if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
17762  					clickedLayer = layer;
17763  				}
17764  			}
17765  		}
17766  		this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
17767  	},
17768
17769  	_onMouseMove: function (e) {
17770  		if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
17771
17772  		var point = this._map.mouseEventToLayerPoint(e);
17773  		this._handleMouseHover(e, point);
17774  	},
17775
17776
17777  	_handleMouseOut: function (e) {
17778  		var layer = this._hoveredLayer;
17779  		if (layer) {
17780  			// if we're leaving the layer, fire mouseout
17781  			removeClass(this._container, 'leaflet-interactive');
17782  			this._fireEvent([layer], e, 'mouseout');
17783  			this._hoveredLayer = null;
17784  			this._mouseHoverThrottled = false;
17785  		}
17786  	},
17787
17788  	_handleMouseHover: function (e, point) {
17789  		if (this._mouseHoverThrottled) {
17790  			return;
17791  		}
17792
17793  		var layer, candidateHoveredLayer;
17794
17795  		for (var order = this._drawFirst; order; order = order.next) {
17796  			layer = order.layer;
17797  			if (layer.options.interactive && layer._containsPoint(point)) {
17798  				candidateHoveredLayer = layer;
17799  			}
17800  		}
17801
17802  		if (candidateHoveredLayer !== this._hoveredLayer) {
17803  			this._handleMouseOut(e);
17804
17805  			if (candidateHoveredLayer) {
17806  				addClass(this._container, 'leaflet-interactive'); // change cursor
17807  				this._fireEvent([candidateHoveredLayer], e, 'mouseover');
17808  				this._hoveredLayer = candidateHoveredLayer;
17809  			}
17810  		}
17811
17812  		this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
17813
17814  		this._mouseHoverThrottled = true;
17815  		setTimeout(bind(function () {
17816  			this._mouseHoverThrottled = false;
17817  		}, this), 32);
17818  	},
17819
17820  	_fireEvent: function (layers, e, type) {
17821  		this._map._fireDOMEvent(e, type || e.type, layers);
17822  	},
17823
17824  	_bringToFront: function (layer) {
17825  		var order = layer._order;
17826
17827  		if (!order) { return; }
17828
17829  		var next = order.next;
17830  		var prev = order.prev;
17831
17832  		if (next) {
17833  			next.prev = prev;
17834  		} else {
17835  			// Already last
17836  			return;
17837  		}
17838  		if (prev) {
17839  			prev.next = next;
17840  		} else if (next) {
17841  			// Update first entry unless this is the
17842  			// single entry
17843  			this._drawFirst = next;
17844  		}
17845
17846  		order.prev = this._drawLast;
17847  		this._drawLast.next = order;
17848
17849  		order.next = null;
17850  		this._drawLast = order;
17851
17852  		this._requestRedraw(layer);
17853  	},
17854
17855  	_bringToBack: function (layer) {
17856  		var order = layer._order;
17857
17858  		if (!order) { return; }
17859
17860  		var next = order.next;
17861  		var prev = order.prev;
17862
17863  		if (prev) {
17864  			prev.next = next;
17865  		} else {
17866  			// Already first
17867  			return;
17868  		}
17869  		if (next) {
17870  			next.prev = prev;
17871  		} else if (prev) {
17872  			// Update last entry unless this is the
17873  			// single entry
17874  			this._drawLast = prev;
17875  		}
17876
17877  		order.prev = null;
17878
17879  		order.next = this._drawFirst;
17880  		this._drawFirst.prev = order;
17881  		this._drawFirst = order;
17882
17883  		this._requestRedraw(layer);
17884  	}
17885  });
17886
17887  // @factory L.canvas(options?: Renderer options)
17888  // Creates a Canvas renderer with the given options.
17889  function canvas(options) {
17890  	return Browser.canvas ? new Canvas(options) : null;
17891  }
17892
17893  /*
17894   * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
17895   */
17896
17897
17898  var vmlCreate = (function () {
17899  	try {
17900  		document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
17901  		return function (name) {
17902  			return document.createElement('<lvml:' + name + ' class="lvml">');
17903  		};
17904  	} catch (e) {
17905  		// Do not return fn from catch block so `e` can be garbage collected
17906  		// See https://github.com/Leaflet/Leaflet/pull/7279
17907  	}
17908  	return function (name) {
17909  		return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
17910  	};
17911  })();
17912
17913
17914  /*
17915   * @class SVG
17916   *
17917   *
17918   * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
17919   * with old versions of Internet Explorer.
17920   */
17921
17922  // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
17923  var vmlMixin = {
17924
17925  	_initContainer: function () {
17926  		this._container = create$1('div', 'leaflet-vml-container');
17927  	},
17928
17929  	_update: function () {
17930  		if (this._map._animatingZoom) { return; }
17931  		Renderer.prototype._update.call(this);
17932  		this.fire('update');
17933  	},
17934
17935  	_initPath: function (layer) {
17936  		var container = layer._container = vmlCreate('shape');
17937
17938  		addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
17939
17940  		container.coordsize = '1 1';
17941
17942  		layer._path = vmlCreate('path');
17943  		container.appendChild(layer._path);
17944
17945  		this._updateStyle(layer);
17946  		this._layers[stamp(layer)] = layer;
17947  	},
17948
17949  	_addPath: function (layer) {
17950  		var container = layer._container;
17951  		this._container.appendChild(container);
17952
17953  		if (layer.options.interactive) {
17954  			layer.addInteractiveTarget(container);
17955  		}
17956  	},
17957
17958  	_removePath: function (layer) {
17959  		var container = layer._container;
17960  		remove(container);
17961  		layer.removeInteractiveTarget(container);
17962  		delete this._layers[stamp(layer)];
17963  	},
17964
17965  	_updateStyle: function (layer) {
17966  		var stroke = layer._stroke,
17967  		    fill = layer._fill,
17968  		    options = layer.options,
17969  		    container = layer._container;
17970
17971  		container.stroked = !!options.stroke;
17972  		container.filled = !!options.fill;
17973
17974  		if (options.stroke) {
17975  			if (!stroke) {
17976  				stroke = layer._stroke = vmlCreate('stroke');
17977  			}
17978  			container.appendChild(stroke);
17979  			stroke.weight = options.weight + 'px';
17980  			stroke.color = options.color;
17981  			stroke.opacity = options.opacity;
17982
17983  			if (options.dashArray) {
17984  				stroke.dashStyle = isArray(options.dashArray) ?
17985  				    options.dashArray.join(' ') :
17986  				    options.dashArray.replace(/( *, *)/g, ' ');
17987  			} else {
17988  				stroke.dashStyle = '';
17989  			}
17990  			stroke.endcap = options.lineCap.replace('butt', 'flat');
17991  			stroke.joinstyle = options.lineJoin;
17992
17993  		} else if (stroke) {
17994  			container.removeChild(stroke);
17995  			layer._stroke = null;
17996  		}
17997
17998  		if (options.fill) {
17999  			if (!fill) {
18000  				fill = layer._fill = vmlCreate('fill');
18001  			}
18002  			container.appendChild(fill);
18003  			fill.color = options.fillColor || options.color;
18004  			fill.opacity = options.fillOpacity;
18005
18006  		} else if (fill) {
18007  			container.removeChild(fill);
18008  			layer._fill = null;
18009  		}
18010  	},
18011
18012  	_updateCircle: function (layer) {
18013  		var p = layer._point.round(),
18014  		    r = Math.round(layer._radius),
18015  		    r2 = Math.round(layer._radiusY || r);
18016
18017  		this._setPath(layer, layer._empty() ? 'M0 0' :
18018  			'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
18019  	},
18020
18021  	_setPath: function (layer, path) {
18022  		layer._path.v = path;
18023  	},
18024
18025  	_bringToFront: function (layer) {
18026  		toFront(layer._container);
18027  	},
18028
18029  	_bringToBack: function (layer) {
18030  		toBack(layer._container);
18031  	}
18032  };
18033
18034  var create = Browser.vml ? vmlCreate : svgCreate;
18035
18036  /*
18037   * @class SVG
18038   * @inherits Renderer
18039   * @aka L.SVG
18040   *
18041   * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
18042   * Inherits `Renderer`.
18043   *
18044   * Due to [technical limitations](https://caniuse.com/svg), SVG is not
18045   * available in all web browsers, notably Android 2.x and 3.x.
18046   *
18047   * Although SVG is not available on IE7 and IE8, these browsers support
18048   * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
18049   * (a now deprecated technology), and the SVG renderer will fall back to VML in
18050   * this case.
18051   *
18052   * @example
18053   *
18054   * Use SVG by default for all paths in the map:
18055   *
18056   * ```js
18057   * var map = L.map('map', {
18058   * 	renderer: L.svg()
18059   * });
18060   * ```
18061   *
18062   * Use a SVG renderer with extra padding for specific vector geometries:
18063   *
18064   * ```js
18065   * var map = L.map('map');
18066   * var myRenderer = L.svg({ padding: 0.5 });
18067   * var line = L.polyline( coordinates, { renderer: myRenderer } );
18068   * var circle = L.circle( center, { renderer: myRenderer } );
18069   * ```
18070   */
18071
18072  var SVG = Renderer.extend({
18073
18074  	_initContainer: function () {
18075  		this._container = create('svg');
18076
18077  		// makes it possible to click through svg root; we'll reset it back in individual paths
18078  		this._container.setAttribute('pointer-events', 'none');
18079
18080  		this._rootGroup = create('g');
18081  		this._container.appendChild(this._rootGroup);
18082  	},
18083
18084  	_destroyContainer: function () {
18085  		remove(this._container);
18086  		off(this._container);
18087  		delete this._container;
18088  		delete this._rootGroup;
18089  		delete this._svgSize;
18090  	},
18091
18092  	_update: function () {
18093  		if (this._map._animatingZoom && this._bounds) { return; }
18094
18095  		Renderer.prototype._update.call(this);
18096
18097  		var b = this._bounds,
18098  		    size = b.getSize(),
18099  		    container = this._container;
18100
18101  		// set size of svg-container if changed
18102  		if (!this._svgSize || !this._svgSize.equals(size)) {
18103  			this._svgSize = size;
18104  			container.setAttribute('width', size.x);
18105  			container.setAttribute('height', size.y);
18106  		}
18107
18108  		// movement: update container viewBox so that we don't have to change coordinates of individual layers
18109  		setPosition(container, b.min);
18110  		container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
18111
18112  		this.fire('update');
18113  	},
18114
18115  	// methods below are called by vector layers implementations
18116
18117  	_initPath: function (layer) {
18118  		var path = layer._path = create('path');
18119
18120  		// @namespace Path
18121  		// @option className: String = null
18122  		// Custom class name set on an element. Only for SVG renderer.
18123  		if (layer.options.className) {
18124  			addClass(path, layer.options.className);
18125  		}
18126
18127  		if (layer.options.interactive) {
18128  			addClass(path, 'leaflet-interactive');
18129  		}
18130
18131  		this._updateStyle(layer);
18132  		this._layers[stamp(layer)] = layer;
18133  	},
18134
18135  	_addPath: function (layer) {
18136  		if (!this._rootGroup) { this._initContainer(); }
18137  		this._rootGroup.appendChild(layer._path);
18138  		layer.addInteractiveTarget(layer._path);
18139  	},
18140
18141  	_removePath: function (layer) {
18142  		remove(layer._path);
18143  		layer.removeInteractiveTarget(layer._path);
18144  		delete this._layers[stamp(layer)];
18145  	},
18146
18147  	_updatePath: function (layer) {
18148  		layer._project();
18149  		layer._update();
18150  	},
18151
18152  	_updateStyle: function (layer) {
18153  		var path = layer._path,
18154  		    options = layer.options;
18155
18156  		if (!path) { return; }
18157
18158  		if (options.stroke) {
18159  			path.setAttribute('stroke', options.color);
18160  			path.setAttribute('stroke-opacity', options.opacity);
18161  			path.setAttribute('stroke-width', options.weight);
18162  			path.setAttribute('stroke-linecap', options.lineCap);
18163  			path.setAttribute('stroke-linejoin', options.lineJoin);
18164
18165  			if (options.dashArray) {
18166  				path.setAttribute('stroke-dasharray', options.dashArray);
18167  			} else {
18168  				path.removeAttribute('stroke-dasharray');
18169  			}
18170
18171  			if (options.dashOffset) {
18172  				path.setAttribute('stroke-dashoffset', options.dashOffset);
18173  			} else {
18174  				path.removeAttribute('stroke-dashoffset');
18175  			}
18176  		} else {
18177  			path.setAttribute('stroke', 'none');
18178  		}
18179
18180  		if (options.fill) {
18181  			path.setAttribute('fill', options.fillColor || options.color);
18182  			path.setAttribute('fill-opacity', options.fillOpacity);
18183  			path.setAttribute('fill-rule', options.fillRule || 'evenodd');
18184  		} else {
18185  			path.setAttribute('fill', 'none');
18186  		}
18187  	},
18188
18189  	_updatePoly: function (layer, closed) {
18190  		this._setPath(layer, pointsToPath(layer._parts, closed));
18191  	},
18192
18193  	_updateCircle: function (layer) {
18194  		var p = layer._point,
18195  		    r = Math.max(Math.round(layer._radius), 1),
18196  		    r2 = Math.max(Math.round(layer._radiusY), 1) || r,
18197  		    arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
18198
18199  		// drawing a circle with two half-arcs
18200  		var d = layer._empty() ? 'M0 0' :
18201  			'M' + (p.x - r) + ',' + p.y +
18202  			arc + (r * 2) + ',0 ' +
18203  			arc + (-r * 2) + ',0 ';
18204
18205  		this._setPath(layer, d);
18206  	},
18207
18208  	_setPath: function (layer, path) {
18209  		layer._path.setAttribute('d', path);
18210  	},
18211
18212  	// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
18213  	_bringToFront: function (layer) {
18214  		toFront(layer._path);
18215  	},
18216
18217  	_bringToBack: function (layer) {
18218  		toBack(layer._path);
18219  	}
18220  });
18221
18222  if (Browser.vml) {
18223  	SVG.include(vmlMixin);
18224  }
18225
18226  // @namespace SVG
18227  // @factory L.svg(options?: Renderer options)
18228  // Creates a SVG renderer with the given options.
18229  function svg(options) {
18230  	return Browser.svg || Browser.vml ? new SVG(options) : null;
18231  }
18232
18233  Map.include({
18234  	// @namespace Map; @method getRenderer(layer: Path): Renderer
18235  	// Returns the instance of `Renderer` that should be used to render the given
18236  	// `Path`. It will ensure that the `renderer` options of the map and paths
18237  	// are respected, and that the renderers do exist on the map.
18238  	getRenderer: function (layer) {
18239  		// @namespace Path; @option renderer: Renderer
18240  		// Use this specific instance of `Renderer` for this path. Takes
18241  		// precedence over the map's [default renderer](#map-renderer).
18242  		var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
18243
18244  		if (!renderer) {
18245  			renderer = this._renderer = this._createRenderer();
18246  		}
18247
18248  		if (!this.hasLayer(renderer)) {
18249  			this.addLayer(renderer);
18250  		}
18251  		return renderer;
18252  	},
18253
18254  	_getPaneRenderer: function (name) {
18255  		if (name === 'overlayPane' || name === undefined) {
18256  			return false;
18257  		}
18258
18259  		var renderer = this._paneRenderers[name];
18260  		if (renderer === undefined) {
18261  			renderer = this._createRenderer({pane: name});
18262  			this._paneRenderers[name] = renderer;
18263  		}
18264  		return renderer;
18265  	},
18266
18267  	_createRenderer: function (options) {
18268  		// @namespace Map; @option preferCanvas: Boolean = false
18269  		// Whether `Path`s should be rendered on a `Canvas` renderer.
18270  		// By default, all `Path`s are rendered in a `SVG` renderer.
18271  		return (this.options.preferCanvas && canvas(options)) || svg(options);
18272  	}
18273  });
18274
18275  /*
18276   * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
18277   */
18278
18279  /*
18280   * @class Rectangle
18281   * @aka L.Rectangle
18282   * @inherits Polygon
18283   *
18284   * A class for drawing rectangle overlays on a map. Extends `Polygon`.
18285   *
18286   * @example
18287   *
18288   * ```js
18289   * // define rectangle geographical bounds
18290   * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
18291   *
18292   * // create an orange rectangle
18293   * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
18294   *
18295   * // zoom the map to the rectangle bounds
18296   * map.fitBounds(bounds);
18297   * ```
18298   *
18299   */
18300
18301
18302  var Rectangle = Polygon.extend({
18303  	initialize: function (latLngBounds, options) {
18304  		Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
18305  	},
18306
18307  	// @method setBounds(latLngBounds: LatLngBounds): this
18308  	// Redraws the rectangle with the passed bounds.
18309  	setBounds: function (latLngBounds) {
18310  		return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
18311  	},
18312
18313  	_boundsToLatLngs: function (latLngBounds) {
18314  		latLngBounds = toLatLngBounds(latLngBounds);
18315  		return [
18316  			latLngBounds.getSouthWest(),
18317  			latLngBounds.getNorthWest(),
18318  			latLngBounds.getNorthEast(),
18319  			latLngBounds.getSouthEast()
18320  		];
18321  	}
18322  });
18323
18324
18325  // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
18326  function rectangle(latLngBounds, options) {
18327  	return new Rectangle(latLngBounds, options);
18328  }
18329
18330  SVG.create = create;
18331  SVG.pointsToPath = pointsToPath;
18332
18333  GeoJSON.geometryToLayer = geometryToLayer;
18334  GeoJSON.coordsToLatLng = coordsToLatLng;
18335  GeoJSON.coordsToLatLngs = coordsToLatLngs;
18336  GeoJSON.latLngToCoords = latLngToCoords;
18337  GeoJSON.latLngsToCoords = latLngsToCoords;
18338  GeoJSON.getFeature = getFeature;
18339  GeoJSON.asFeature = asFeature;
18340
18341  /*
18342   * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
18343   * (zoom to a selected bounding box), enabled by default.
18344   */
18345
18346  // @namespace Map
18347  // @section Interaction Options
18348  Map.mergeOptions({
18349  	// @option boxZoom: Boolean = true
18350  	// Whether the map can be zoomed to a rectangular area specified by
18351  	// dragging the mouse while pressing the shift key.
18352  	boxZoom: true
18353  });
18354
18355  var BoxZoom = Handler.extend({
18356  	initialize: function (map) {
18357  		this._map = map;
18358  		this._container = map._container;
18359  		this._pane = map._panes.overlayPane;
18360  		this._resetStateTimeout = 0;
18361  		map.on('unload', this._destroy, this);
18362  	},
18363
18364  	addHooks: function () {
18365  		on(this._container, 'mousedown', this._onMouseDown, this);
18366  	},
18367
18368  	removeHooks: function () {
18369  		off(this._container, 'mousedown', this._onMouseDown, this);
18370  	},
18371
18372  	moved: function () {
18373  		return this._moved;
18374  	},
18375
18376  	_destroy: function () {
18377  		remove(this._pane);
18378  		delete this._pane;
18379  	},
18380
18381  	_resetState: function () {
18382  		this._resetStateTimeout = 0;
18383  		this._moved = false;
18384  	},
18385
18386  	_clearDeferredResetState: function () {
18387  		if (this._resetStateTimeout !== 0) {
18388  			clearTimeout(this._resetStateTimeout);
18389  			this._resetStateTimeout = 0;
18390  		}
18391  	},
18392
18393  	_onMouseDown: function (e) {
18394  		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
18395
18396  		// Clear the deferred resetState if it hasn't executed yet, otherwise it
18397  		// will interrupt the interaction and orphan a box element in the container.
18398  		this._clearDeferredResetState();
18399  		this._resetState();
18400
18401  		disableTextSelection();
18402  		disableImageDrag();
18403
18404  		this._startPoint = this._map.mouseEventToContainerPoint(e);
18405
18406  		on(document, {
18407  			contextmenu: stop,
18408  			mousemove: this._onMouseMove,
18409  			mouseup: this._onMouseUp,
18410  			keydown: this._onKeyDown
18411  		}, this);
18412  	},
18413
18414  	_onMouseMove: function (e) {
18415  		if (!this._moved) {
18416  			this._moved = true;
18417
18418  			this._box = create$1('div', 'leaflet-zoom-box', this._container);
18419  			addClass(this._container, 'leaflet-crosshair');
18420
18421  			this._map.fire('boxzoomstart');
18422  		}
18423
18424  		this._point = this._map.mouseEventToContainerPoint(e);
18425
18426  		var bounds = new Bounds(this._point, this._startPoint),
18427  		    size = bounds.getSize();
18428
18429  		setPosition(this._box, bounds.min);
18430
18431  		this._box.style.width  = size.x + 'px';
18432  		this._box.style.height = size.y + 'px';
18433  	},
18434
18435  	_finish: function () {
18436  		if (this._moved) {
18437  			remove(this._box);
18438  			removeClass(this._container, 'leaflet-crosshair');
18439  		}
18440
18441  		enableTextSelection();
18442  		enableImageDrag();
18443
18444  		off(document, {
18445  			contextmenu: stop,
18446  			mousemove: this._onMouseMove,
18447  			mouseup: this._onMouseUp,
18448  			keydown: this._onKeyDown
18449  		}, this);
18450  	},
18451
18452  	_onMouseUp: function (e) {
18453  		if ((e.which !== 1) && (e.button !== 1)) { return; }
18454
18455  		this._finish();
18456
18457  		if (!this._moved) { return; }
18458  		// Postpone to next JS tick so internal click event handling
18459  		// still see it as "moved".
18460  		this._clearDeferredResetState();
18461  		this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
18462
18463  		var bounds = new LatLngBounds(
18464  		        this._map.containerPointToLatLng(this._startPoint),
18465  		        this._map.containerPointToLatLng(this._point));
18466
18467  		this._map
18468  			.fitBounds(bounds)
18469  			.fire('boxzoomend', {boxZoomBounds: bounds});
18470  	},
18471
18472  	_onKeyDown: function (e) {
18473  		if (e.keyCode === 27) {
18474  			this._finish();
18475  			this._clearDeferredResetState();
18476  			this._resetState();
18477  		}
18478  	}
18479  });
18480
18481  // @section Handlers
18482  // @property boxZoom: Handler
18483  // Box (shift-drag with mouse) zoom handler.
18484  Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
18485
18486  /*
18487   * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
18488   */
18489
18490  // @namespace Map
18491  // @section Interaction Options
18492
18493  Map.mergeOptions({
18494  	// @option doubleClickZoom: Boolean|String = true
18495  	// Whether the map can be zoomed in by double clicking on it and
18496  	// zoomed out by double clicking while holding shift. If passed
18497  	// `'center'`, double-click zoom will zoom to the center of the
18498  	//  view regardless of where the mouse was.
18499  	doubleClickZoom: true
18500  });
18501
18502  var DoubleClickZoom = Handler.extend({
18503  	addHooks: function () {
18504  		this._map.on('dblclick', this._onDoubleClick, this);
18505  	},
18506
18507  	removeHooks: function () {
18508  		this._map.off('dblclick', this._onDoubleClick, this);
18509  	},
18510
18511  	_onDoubleClick: function (e) {
18512  		var map = this._map,
18513  		    oldZoom = map.getZoom(),
18514  		    delta = map.options.zoomDelta,
18515  		    zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
18516
18517  		if (map.options.doubleClickZoom === 'center') {
18518  			map.setZoom(zoom);
18519  		} else {
18520  			map.setZoomAround(e.containerPoint, zoom);
18521  		}
18522  	}
18523  });
18524
18525  // @section Handlers
18526  //
18527  // Map properties include interaction handlers that allow you to control
18528  // interaction behavior in runtime, enabling or disabling certain features such
18529  // as dragging or touch zoom (see `Handler` methods). For example:
18530  //
18531  // ```js
18532  // map.doubleClickZoom.disable();
18533  // ```
18534  //
18535  // @property doubleClickZoom: Handler
18536  // Double click zoom handler.
18537  Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
18538
18539  /*
18540   * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
18541   */
18542
18543  // @namespace Map
18544  // @section Interaction Options
18545  Map.mergeOptions({
18546  	// @option dragging: Boolean = true
18547  	// Whether the map is draggable with mouse/touch or not.
18548  	dragging: true,
18549
18550  	// @section Panning Inertia Options
18551  	// @option inertia: Boolean = *
18552  	// If enabled, panning of the map will have an inertia effect where
18553  	// the map builds momentum while dragging and continues moving in
18554  	// the same direction for some time. Feels especially nice on touch
18555  	// devices. Enabled by default.
18556  	inertia: true,
18557
18558  	// @option inertiaDeceleration: Number = 3000
18559  	// The rate with which the inertial movement slows down, in pixels/second².
18560  	inertiaDeceleration: 3400, // px/s^2
18561
18562  	// @option inertiaMaxSpeed: Number = Infinity
18563  	// Max speed of the inertial movement, in pixels/second.
18564  	inertiaMaxSpeed: Infinity, // px/s
18565
18566  	// @option easeLinearity: Number = 0.2
18567  	easeLinearity: 0.2,
18568
18569  	// TODO refactor, move to CRS
18570  	// @option worldCopyJump: Boolean = false
18571  	// With this option enabled, the map tracks when you pan to another "copy"
18572  	// of the world and seamlessly jumps to the original one so that all overlays
18573  	// like markers and vector layers are still visible.
18574  	worldCopyJump: false,
18575
18576  	// @option maxBoundsViscosity: Number = 0.0
18577  	// If `maxBounds` is set, this option will control how solid the bounds
18578  	// are when dragging the map around. The default value of `0.0` allows the
18579  	// user to drag outside the bounds at normal speed, higher values will
18580  	// slow down map dragging outside bounds, and `1.0` makes the bounds fully
18581  	// solid, preventing the user from dragging outside the bounds.
18582  	maxBoundsViscosity: 0.0
18583  });
18584
18585  var Drag = Handler.extend({
18586  	addHooks: function () {
18587  		if (!this._draggable) {
18588  			var map = this._map;
18589
18590  			this._draggable = new Draggable(map._mapPane, map._container);
18591
18592  			this._draggable.on({
18593  				dragstart: this._onDragStart,
18594  				drag: this._onDrag,
18595  				dragend: this._onDragEnd
18596  			}, this);
18597
18598  			this._draggable.on('predrag', this._onPreDragLimit, this);
18599  			if (map.options.worldCopyJump) {
18600  				this._draggable.on('predrag', this._onPreDragWrap, this);
18601  				map.on('zoomend', this._onZoomEnd, this);
18602
18603  				map.whenReady(this._onZoomEnd, this);
18604  			}
18605  		}
18606  		addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
18607  		this._draggable.enable();
18608  		this._positions = [];
18609  		this._times = [];
18610  	},
18611
18612  	removeHooks: function () {
18613  		removeClass(this._map._container, 'leaflet-grab');
18614  		removeClass(this._map._container, 'leaflet-touch-drag');
18615  		this._draggable.disable();
18616  	},
18617
18618  	moved: function () {
18619  		return this._draggable && this._draggable._moved;
18620  	},
18621
18622  	moving: function () {
18623  		return this._draggable && this._draggable._moving;
18624  	},
18625
18626  	_onDragStart: function () {
18627  		var map = this._map;
18628
18629  		map._stop();
18630  		if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
18631  			var bounds = toLatLngBounds(this._map.options.maxBounds);
18632
18633  			this._offsetLimit = toBounds(
18634  				this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
18635  				this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
18636  					.add(this._map.getSize()));
18637
18638  			this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
18639  		} else {
18640  			this._offsetLimit = null;
18641  		}
18642
18643  		map
18644  		    .fire('movestart')
18645  		    .fire('dragstart');
18646
18647  		if (map.options.inertia) {
18648  			this._positions = [];
18649  			this._times = [];
18650  		}
18651  	},
18652
18653  	_onDrag: function (e) {
18654  		if (this._map.options.inertia) {
18655  			var time = this._lastTime = +new Date(),
18656  			    pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
18657
18658  			this._positions.push(pos);
18659  			this._times.push(time);
18660
18661  			this._prunePositions(time);
18662  		}
18663
18664  		this._map
18665  		    .fire('move', e)
18666  		    .fire('drag', e);
18667  	},
18668
18669  	_prunePositions: function (time) {
18670  		while (this._positions.length > 1 && time - this._times[0] > 50) {
18671  			this._positions.shift();
18672  			this._times.shift();
18673  		}
18674  	},
18675
18676  	_onZoomEnd: function () {
18677  		var pxCenter = this._map.getSize().divideBy(2),
18678  		    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
18679
18680  		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
18681  		this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
18682  	},
18683
18684  	_viscousLimit: function (value, threshold) {
18685  		return value - (value - threshold) * this._viscosity;
18686  	},
18687
18688  	_onPreDragLimit: function () {
18689  		if (!this._viscosity || !this._offsetLimit) { return; }
18690
18691  		var offset = this._draggable._newPos.subtract(this._draggable._startPos);
18692
18693  		var limit = this._offsetLimit;
18694  		if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
18695  		if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
18696  		if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
18697  		if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
18698
18699  		this._draggable._newPos = this._draggable._startPos.add(offset);
18700  	},
18701
18702  	_onPreDragWrap: function () {
18703  		// TODO refactor to be able to adjust map pane position after zoom
18704  		var worldWidth = this._worldWidth,
18705  		    halfWidth = Math.round(worldWidth / 2),
18706  		    dx = this._initialWorldOffset,
18707  		    x = this._draggable._newPos.x,
18708  		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
18709  		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
18710  		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
18711
18712  		this._draggable._absPos = this._draggable._newPos.clone();
18713  		this._draggable._newPos.x = newX;
18714  	},
18715
18716  	_onDragEnd: function (e) {
18717  		var map = this._map,
18718  		    options = map.options,
18719
18720  		    noInertia = !options.inertia || e.noInertia || this._times.length < 2;
18721
18722  		map.fire('dragend', e);
18723
18724  		if (noInertia) {
18725  			map.fire('moveend');
18726
18727  		} else {
18728  			this._prunePositions(+new Date());
18729
18730  			var direction = this._lastPos.subtract(this._positions[0]),
18731  			    duration = (this._lastTime - this._times[0]) / 1000,
18732  			    ease = options.easeLinearity,
18733
18734  			    speedVector = direction.multiplyBy(ease / duration),
18735  			    speed = speedVector.distanceTo([0, 0]),
18736
18737  			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
18738  			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
18739
18740  			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
18741  			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
18742
18743  			if (!offset.x && !offset.y) {
18744  				map.fire('moveend');
18745
18746  			} else {
18747  				offset = map._limitOffset(offset, map.options.maxBounds);
18748
18749  				requestAnimFrame(function () {
18750  					map.panBy(offset, {
18751  						duration: decelerationDuration,
18752  						easeLinearity: ease,
18753  						noMoveStart: true,
18754  						animate: true
18755  					});
18756  				});
18757  			}
18758  		}
18759  	}
18760  });
18761
18762  // @section Handlers
18763  // @property dragging: Handler
18764  // Map dragging handler (by both mouse and touch).
18765  Map.addInitHook('addHandler', 'dragging', Drag);
18766
18767  /*
18768   * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
18769   */
18770
18771  // @namespace Map
18772  // @section Keyboard Navigation Options
18773  Map.mergeOptions({
18774  	// @option keyboard: Boolean = true
18775  	// Makes the map focusable and allows users to navigate the map with keyboard
18776  	// arrows and `+`/`-` keys.
18777  	keyboard: true,
18778
18779  	// @option keyboardPanDelta: Number = 80
18780  	// Amount of pixels to pan when pressing an arrow key.
18781  	keyboardPanDelta: 80
18782  });
18783
18784  var Keyboard = Handler.extend({
18785
18786  	keyCodes: {
18787  		left:    [37],
18788  		right:   [39],
18789  		down:    [40],
18790  		up:      [38],
18791  		zoomIn:  [187, 107, 61, 171],
18792  		zoomOut: [189, 109, 54, 173]
18793  	},
18794
18795  	initialize: function (map) {
18796  		this._map = map;
18797
18798  		this._setPanDelta(map.options.keyboardPanDelta);
18799  		this._setZoomDelta(map.options.zoomDelta);
18800  	},
18801
18802  	addHooks: function () {
18803  		var container = this._map._container;
18804
18805  		// make the container focusable by tabbing
18806  		if (container.tabIndex <= 0) {
18807  			container.tabIndex = '0';
18808  		}
18809
18810  		on(container, {
18811  			focus: this._onFocus,
18812  			blur: this._onBlur,
18813  			mousedown: this._onMouseDown
18814  		}, this);
18815
18816  		this._map.on({
18817  			focus: this._addHooks,
18818  			blur: this._removeHooks
18819  		}, this);
18820  	},
18821
18822  	removeHooks: function () {
18823  		this._removeHooks();
18824
18825  		off(this._map._container, {
18826  			focus: this._onFocus,
18827  			blur: this._onBlur,
18828  			mousedown: this._onMouseDown
18829  		}, this);
18830
18831  		this._map.off({
18832  			focus: this._addHooks,
18833  			blur: this._removeHooks
18834  		}, this);
18835  	},
18836
18837  	_onMouseDown: function () {
18838  		if (this._focused) { return; }
18839
18840  		var body = document.body,
18841  		    docEl = document.documentElement,
18842  		    top = body.scrollTop || docEl.scrollTop,
18843  		    left = body.scrollLeft || docEl.scrollLeft;
18844
18845  		this._map._container.focus();
18846
18847  		window.scrollTo(left, top);
18848  	},
18849
18850  	_onFocus: function () {
18851  		this._focused = true;
18852  		this._map.fire('focus');
18853  	},
18854
18855  	_onBlur: function () {
18856  		this._focused = false;
18857  		this._map.fire('blur');
18858  	},
18859
18860  	_setPanDelta: function (panDelta) {
18861  		var keys = this._panKeys = {},
18862  		    codes = this.keyCodes,
18863  		    i, len;
18864
18865  		for (i = 0, len = codes.left.length; i < len; i++) {
18866  			keys[codes.left[i]] = [-1 * panDelta, 0];
18867  		}
18868  		for (i = 0, len = codes.right.length; i < len; i++) {
18869  			keys[codes.right[i]] = [panDelta, 0];
18870  		}
18871  		for (i = 0, len = codes.down.length; i < len; i++) {
18872  			keys[codes.down[i]] = [0, panDelta];
18873  		}
18874  		for (i = 0, len = codes.up.length; i < len; i++) {
18875  			keys[codes.up[i]] = [0, -1 * panDelta];
18876  		}
18877  	},
18878
18879  	_setZoomDelta: function (zoomDelta) {
18880  		var keys = this._zoomKeys = {},
18881  		    codes = this.keyCodes,
18882  		    i, len;
18883
18884  		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
18885  			keys[codes.zoomIn[i]] = zoomDelta;
18886  		}
18887  		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
18888  			keys[codes.zoomOut[i]] = -zoomDelta;
18889  		}
18890  	},
18891
18892  	_addHooks: function () {
18893  		on(document, 'keydown', this._onKeyDown, this);
18894  	},
18895
18896  	_removeHooks: function () {
18897  		off(document, 'keydown', this._onKeyDown, this);
18898  	},
18899
18900  	_onKeyDown: function (e) {
18901  		if (e.altKey || e.ctrlKey || e.metaKey) { return; }
18902
18903  		var key = e.keyCode,
18904  		    map = this._map,
18905  		    offset;
18906
18907  		if (key in this._panKeys) {
18908  			if (!map._panAnim || !map._panAnim._inProgress) {
18909  				offset = this._panKeys[key];
18910  				if (e.shiftKey) {
18911  					offset = toPoint(offset).multiplyBy(3);
18912  				}
18913
18914  				if (map.options.maxBounds) {
18915  					offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
18916  				}
18917
18918  				if (map.options.worldCopyJump) {
18919  					var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
18920  					map.panTo(newLatLng);
18921  				} else {
18922  					map.panBy(offset);
18923  				}
18924  			}
18925  		} else if (key in this._zoomKeys) {
18926  			map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
18927
18928  		} else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
18929  			map.closePopup();
18930
18931  		} else {
18932  			return;
18933  		}
18934
18935  		stop(e);
18936  	}
18937  });
18938
18939  // @section Handlers
18940  // @section Handlers
18941  // @property keyboard: Handler
18942  // Keyboard navigation handler.
18943  Map.addInitHook('addHandler', 'keyboard', Keyboard);
18944
18945  /*
18946   * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
18947   */
18948
18949  // @namespace Map
18950  // @section Interaction Options
18951  Map.mergeOptions({
18952  	// @section Mouse wheel options
18953  	// @option scrollWheelZoom: Boolean|String = true
18954  	// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
18955  	// it will zoom to the center of the view regardless of where the mouse was.
18956  	scrollWheelZoom: true,
18957
18958  	// @option wheelDebounceTime: Number = 40
18959  	// Limits the rate at which a wheel can fire (in milliseconds). By default
18960  	// user can't zoom via wheel more often than once per 40 ms.
18961  	wheelDebounceTime: 40,
18962
18963  	// @option wheelPxPerZoomLevel: Number = 60
18964  	// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
18965  	// mean a change of one full zoom level. Smaller values will make wheel-zooming
18966  	// faster (and vice versa).
18967  	wheelPxPerZoomLevel: 60
18968  });
18969
18970  var ScrollWheelZoom = Handler.extend({
18971  	addHooks: function () {
18972  		on(this._map._container, 'wheel', this._onWheelScroll, this);
18973
18974  		this._delta = 0;
18975  	},
18976
18977  	removeHooks: function () {
18978  		off(this._map._container, 'wheel', this._onWheelScroll, this);
18979  	},
18980
18981  	_onWheelScroll: function (e) {
18982  		var delta = getWheelDelta(e);
18983
18984  		var debounce = this._map.options.wheelDebounceTime;
18985
18986  		this._delta += delta;
18987  		this._lastMousePos = this._map.mouseEventToContainerPoint(e);
18988
18989  		if (!this._startTime) {
18990  			this._startTime = +new Date();
18991  		}
18992
18993  		var left = Math.max(debounce - (+new Date() - this._startTime), 0);
18994
18995  		clearTimeout(this._timer);
18996  		this._timer = setTimeout(bind(this._performZoom, this), left);
18997
18998  		stop(e);
18999  	},
19000
19001  	_performZoom: function () {
19002  		var map = this._map,
19003  		    zoom = map.getZoom(),
19004  		    snap = this._map.options.zoomSnap || 0;
19005
19006  		map._stop(); // stop panning and fly animations if any
19007
19008  		// map the delta with a sigmoid function to -4..4 range leaning on -1..1
19009  		var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
19010  		    d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
19011  		    d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
19012  		    delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
19013
19014  		this._delta = 0;
19015  		this._startTime = null;
19016
19017  		if (!delta) { return; }
19018
19019  		if (map.options.scrollWheelZoom === 'center') {
19020  			map.setZoom(zoom + delta);
19021  		} else {
19022  			map.setZoomAround(this._lastMousePos, zoom + delta);
19023  		}
19024  	}
19025  });
19026
19027  // @section Handlers
19028  // @property scrollWheelZoom: Handler
19029  // Scroll wheel zoom handler.
19030  Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
19031
19032  /*
19033   * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
19034   * which otherwise is not fired by mobile Safari.
19035   */
19036
19037  var tapHoldDelay = 600;
19038
19039  // @namespace Map
19040  // @section Interaction Options
19041  Map.mergeOptions({
19042  	// @section Touch interaction options
19043  	// @option tapHold: Boolean
19044  	// Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
19045  	tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
19046
19047  	// @option tapTolerance: Number = 15
19048  	// The max number of pixels a user can shift his finger during touch
19049  	// for it to be considered a valid tap.
19050  	tapTolerance: 15
19051  });
19052
19053  var TapHold = Handler.extend({
19054  	addHooks: function () {
19055  		on(this._map._container, 'touchstart', this._onDown, this);
19056  	},
19057
19058  	removeHooks: function () {
19059  		off(this._map._container, 'touchstart', this._onDown, this);
19060  	},
19061
19062  	_onDown: function (e) {
19063  		clearTimeout(this._holdTimeout);
19064  		if (e.touches.length !== 1) { return; }
19065
19066  		var first = e.touches[0];
19067  		this._startPos = this._newPos = new Point(first.clientX, first.clientY);
19068
19069  		this._holdTimeout = setTimeout(bind(function () {
19070  			this._cancel();
19071  			if (!this._isTapValid()) { return; }
19072
19073  			// prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
19074  			on(document, 'touchend', preventDefault);
19075  			on(document, 'touchend touchcancel', this._cancelClickPrevent);
19076  			this._simulateEvent('contextmenu', first);
19077  		}, this), tapHoldDelay);
19078
19079  		on(document, 'touchend touchcancel contextmenu', this._cancel, this);
19080  		on(document, 'touchmove', this._onMove, this);
19081  	},
19082
19083  	_cancelClickPrevent: function cancelClickPrevent() {
19084  		off(document, 'touchend', preventDefault);
19085  		off(document, 'touchend touchcancel', cancelClickPrevent);
19086  	},
19087
19088  	_cancel: function () {
19089  		clearTimeout(this._holdTimeout);
19090  		off(document, 'touchend touchcancel contextmenu', this._cancel, this);
19091  		off(document, 'touchmove', this._onMove, this);
19092  	},
19093
19094  	_onMove: function (e) {
19095  		var first = e.touches[0];
19096  		this._newPos = new Point(first.clientX, first.clientY);
19097  	},
19098
19099  	_isTapValid: function () {
19100  		return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
19101  	},
19102
19103  	_simulateEvent: function (type, e) {
19104  		var simulatedEvent = new MouseEvent(type, {
19105  			bubbles: true,
19106  			cancelable: true,
19107  			view: window,
19108  			// detail: 1,
19109  			screenX: e.screenX,
19110  			screenY: e.screenY,
19111  			clientX: e.clientX,
19112  			clientY: e.clientY,
19113  			// button: 2,
19114  			// buttons: 2
19115  		});
19116
19117  		simulatedEvent._simulated = true;
19118
19119  		e.target.dispatchEvent(simulatedEvent);
19120  	}
19121  });
19122
19123  // @section Handlers
19124  // @property tapHold: Handler
19125  // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
19126  Map.addInitHook('addHandler', 'tapHold', TapHold);
19127
19128  /*
19129   * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
19130   */
19131
19132  // @namespace Map
19133  // @section Interaction Options
19134  Map.mergeOptions({
19135  	// @section Touch interaction options
19136  	// @option touchZoom: Boolean|String = *
19137  	// Whether the map can be zoomed by touch-dragging with two fingers. If
19138  	// passed `'center'`, it will zoom to the center of the view regardless of
19139  	// where the touch events (fingers) were. Enabled for touch-capable web
19140  	// browsers.
19141  	touchZoom: Browser.touch,
19142
19143  	// @option bounceAtZoomLimits: Boolean = true
19144  	// Set it to false if you don't want the map to zoom beyond min/max zoom
19145  	// and then bounce back when pinch-zooming.
19146  	bounceAtZoomLimits: true
19147  });
19148
19149  var TouchZoom = Handler.extend({
19150  	addHooks: function () {
19151  		addClass(this._map._container, 'leaflet-touch-zoom');
19152  		on(this._map._container, 'touchstart', this._onTouchStart, this);
19153  	},
19154
19155  	removeHooks: function () {
19156  		removeClass(this._map._container, 'leaflet-touch-zoom');
19157  		off(this._map._container, 'touchstart', this._onTouchStart, this);
19158  	},
19159
19160  	_onTouchStart: function (e) {
19161  		var map = this._map;
19162  		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
19163
19164  		var p1 = map.mouseEventToContainerPoint(e.touches[0]),
19165  		    p2 = map.mouseEventToContainerPoint(e.touches[1]);
19166
19167  		this._centerPoint = map.getSize()._divideBy(2);
19168  		this._startLatLng = map.containerPointToLatLng(this._centerPoint);
19169  		if (map.options.touchZoom !== 'center') {
19170  			this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
19171  		}
19172
19173  		this._startDist = p1.distanceTo(p2);
19174  		this._startZoom = map.getZoom();
19175
19176  		this._moved = false;
19177  		this._zooming = true;
19178
19179  		map._stop();
19180
19181  		on(document, 'touchmove', this._onTouchMove, this);
19182  		on(document, 'touchend touchcancel', this._onTouchEnd, this);
19183
19184  		preventDefault(e);
19185  	},
19186
19187  	_onTouchMove: function (e) {
19188  		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
19189
19190  		var map = this._map,
19191  		    p1 = map.mouseEventToContainerPoint(e.touches[0]),
19192  		    p2 = map.mouseEventToContainerPoint(e.touches[1]),
19193  		    scale = p1.distanceTo(p2) / this._startDist;
19194
19195  		this._zoom = map.getScaleZoom(scale, this._startZoom);
19196
19197  		if (!map.options.bounceAtZoomLimits && (
19198  			(this._zoom < map.getMinZoom() && scale < 1) ||
19199  			(this._zoom > map.getMaxZoom() && scale > 1))) {
19200  			this._zoom = map._limitZoom(this._zoom);
19201  		}
19202
19203  		if (map.options.touchZoom === 'center') {
19204  			this._center = this._startLatLng;
19205  			if (scale === 1) { return; }
19206  		} else {
19207  			// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
19208  			var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
19209  			if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
19210  			this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
19211  		}
19212
19213  		if (!this._moved) {
19214  			map._moveStart(true, false);
19215  			this._moved = true;
19216  		}
19217
19218  		cancelAnimFrame(this._animRequest);
19219
19220  		var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
19221  		this._animRequest = requestAnimFrame(moveFn, this, true);
19222
19223  		preventDefault(e);
19224  	},
19225
19226  	_onTouchEnd: function () {
19227  		if (!this._moved || !this._zooming) {
19228  			this._zooming = false;
19229  			return;
19230  		}
19231
19232  		this._zooming = false;
19233  		cancelAnimFrame(this._animRequest);
19234
19235  		off(document, 'touchmove', this._onTouchMove, this);
19236  		off(document, 'touchend touchcancel', this._onTouchEnd, this);
19237
19238  		// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
19239  		if (this._map.options.zoomAnimation) {
19240  			this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
19241  		} else {
19242  			this._map._resetView(this._center, this._map._limitZoom(this._zoom));
19243  		}
19244  	}
19245  });
19246
19247  // @section Handlers
19248  // @property touchZoom: Handler
19249  // Touch zoom handler.
19250  Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
19251
19252  Map.BoxZoom = BoxZoom;
19253  Map.DoubleClickZoom = DoubleClickZoom;
19254  Map.Drag = Drag;
19255  Map.Keyboard = Keyboard;
19256  Map.ScrollWheelZoom = ScrollWheelZoom;
19257  Map.TapHold = TapHold;
19258  Map.TouchZoom = TouchZoom;
19259
19260  exports.Bounds = Bounds;
19261  exports.Browser = Browser;
19262  exports.CRS = CRS;
19263  exports.Canvas = Canvas;
19264  exports.Circle = Circle;
19265  exports.CircleMarker = CircleMarker;
19266  exports.Class = Class;
19267  exports.Control = Control;
19268  exports.DivIcon = DivIcon;
19269  exports.DivOverlay = DivOverlay;
19270  exports.DomEvent = DomEvent;
19271  exports.DomUtil = DomUtil;
19272  exports.Draggable = Draggable;
19273  exports.Evented = Evented;
19274  exports.FeatureGroup = FeatureGroup;
19275  exports.GeoJSON = GeoJSON;
19276  exports.GridLayer = GridLayer;
19277  exports.Handler = Handler;
19278  exports.Icon = Icon;
19279  exports.ImageOverlay = ImageOverlay;
19280  exports.LatLng = LatLng;
19281  exports.LatLngBounds = LatLngBounds;
19282  exports.Layer = Layer;
19283  exports.LayerGroup = LayerGroup;
19284  exports.LineUtil = LineUtil;
19285  exports.Map = Map;
19286  exports.Marker = Marker;
19287  exports.Mixin = Mixin;
19288  exports.Path = Path;
19289  exports.Point = Point;
19290  exports.PolyUtil = PolyUtil;
19291  exports.Polygon = Polygon;
19292  exports.Polyline = Polyline;
19293  exports.Popup = Popup;
19294  exports.PosAnimation = PosAnimation;
19295  exports.Projection = index;
19296  exports.Rectangle = Rectangle;
19297  exports.Renderer = Renderer;
19298  exports.SVG = SVG;
19299  exports.SVGOverlay = SVGOverlay;
19300  exports.TileLayer = TileLayer;
19301  exports.Tooltip = Tooltip;
19302  exports.Transformation = Transformation;
19303  exports.Util = Util;
19304  exports.VideoOverlay = VideoOverlay;
19305  exports.bind = bind;
19306  exports.bounds = toBounds;
19307  exports.canvas = canvas;
19308  exports.circle = circle;
19309  exports.circleMarker = circleMarker;
19310  exports.control = control;
19311  exports.divIcon = divIcon;
19312  exports.extend = extend;
19313  exports.featureGroup = featureGroup;
19314  exports.geoJSON = geoJSON;
19315  exports.geoJson = geoJson;
19316  exports.gridLayer = gridLayer;
19317  exports.icon = icon;
19318  exports.imageOverlay = imageOverlay;
19319  exports.latLng = toLatLng;
19320  exports.latLngBounds = toLatLngBounds;
19321  exports.layerGroup = layerGroup;
19322  exports.map = createMap;
19323  exports.marker = marker;
19324  exports.point = toPoint;
19325  exports.polygon = polygon;
19326  exports.polyline = polyline;
19327  exports.popup = popup;
19328  exports.rectangle = rectangle;
19329  exports.setOptions = setOptions;
19330  exports.stamp = stamp;
19331  exports.svg = svg;
19332  exports.svgOverlay = svgOverlay;
19333  exports.tileLayer = tileLayer;
19334  exports.tooltip = tooltip;
19335  exports.transformation = toTransformation;
19336  exports.version = version;
19337  exports.videoOverlay = videoOverlay;
19338
19339  var oldL = window.L;
19340  exports.noConflict = function() {
19341  	window.L = oldL;
19342  	return this;
19343  }
19344  // Always export us to window global (see #2364)
19345  window.L = exports;
19346
19347}));
19348
19349
19350},{}]},{},[1]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.