+ // adjust offset for view to get inside bounds
+ _limitOffset: function (offset, bounds) {
+ if (!bounds) { return offset; }
+
+ var viewBounds = this.getPixelBounds(),
+ newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
+
+ return offset.add(this._getBoundsOffset(newBounds, bounds));
+ },
+
+ // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
+ _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
+ var projectedMaxBounds = toBounds(
+ this.project(maxBounds.getNorthEast(), zoom),
+ this.project(maxBounds.getSouthWest(), zoom)
+ ),
+ minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
+ maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
+
+ dx = this._rebound(minOffset.x, -maxOffset.x),
+ dy = this._rebound(minOffset.y, -maxOffset.y);
+
+ return new Point(dx, dy);
+ },
+
+ _rebound: function (left, right) {
+ return left + right > 0 ?
+ Math.round(left - right) / 2 :
+ Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
+ },
+
+ _limitZoom: function (zoom) {
+ var min = this.getMinZoom(),
+ max = this.getMaxZoom(),
+ snap = any3d ? this.options.zoomSnap : 1;
+ if (snap) {
+ zoom = Math.round(zoom / snap) * snap;
+ }
+ return Math.max(min, Math.min(max, zoom));
+ },
+
+ _onPanTransitionStep: function () {
+ this.fire('move');
+ },
+
+ _onPanTransitionEnd: function () {
+ removeClass(this._mapPane, 'leaflet-pan-anim');
+ this.fire('moveend');
+ },
+
+ _tryAnimatedPan: function (center, options) {
+ // difference between the new and current centers in pixels
+ var offset = this._getCenterOffset(center)._trunc();
+
+ // don't animate too far unless animate: true specified in options
+ if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+
+ this.panBy(offset, options);
+
+ return true;
+ },
+
+ _createAnimProxy: function () {
+
+ var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
+ this._panes.mapPane.appendChild(proxy);
+
+ this.on('zoomanim', function (e) {
+ var prop = TRANSFORM,
+ transform = this._proxy.style[prop];
+
+ setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
+
+ // workaround for case when transform is the same and so transitionend event is not fired
+ if (transform === this._proxy.style[prop] && this._animatingZoom) {
+ this._onZoomTransitionEnd();
+ }
+ }, this);
+
+ this.on('load moveend', function () {
+ var c = this.getCenter(),
+ z = this.getZoom();
+ setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
+ }, this);
+
+ this._on('unload', this._destroyAnimProxy, this);
+ },
+
+ _destroyAnimProxy: function () {
+ remove(this._proxy);
+ delete this._proxy;
+ },
+
+ _catchTransitionEnd: function (e) {
+ if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
+ this._onZoomTransitionEnd();
+ }
+ },
+
+ _nothingToAnimate: function () {
+ return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
+ },
+
+ _tryAnimatedZoom: function (center, zoom, options) {
+
+ if (this._animatingZoom) { return true; }
+
+ options = options || {};
+
+ // don't animate if disabled, not supported or zoom difference is too large
+ if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
+ Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
+
+ // offset is the pixel coords of the zoom origin relative to the current center
+ var scale = this.getZoomScale(zoom),
+ offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
+
+ // don't animate if the zoom origin isn't within one screen from the current center, unless forced
+ if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
+
+ requestAnimFrame(function () {
+ this
+ ._moveStart(true, false)
+ ._animateZoom(center, zoom, true);
+ }, this);
+
+ return true;
+ },
+
+ _animateZoom: function (center, zoom, startAnim, noUpdate) {
+ if (!this._mapPane) { return; }
+
+ if (startAnim) {
+ this._animatingZoom = true;
+
+ // remember what center/zoom to set after animation
+ this._animateToCenter = center;
+ this._animateToZoom = zoom;
+
+ addClass(this._mapPane, 'leaflet-zoom-anim');
+ }
+
+ // @event zoomanim: ZoomAnimEvent
+ // Fired at least once per zoom animation. For continous zoom, like pinch zooming, fired once per frame during zoom.
+ this.fire('zoomanim', {
+ center: center,
+ zoom: zoom,
+ noUpdate: noUpdate
+ });
+
+ // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
+ setTimeout(bind(this._onZoomTransitionEnd, this), 250);
+ },
+
+ _onZoomTransitionEnd: function () {
+ if (!this._animatingZoom) { return; }
+
+ if (this._mapPane) {
+ removeClass(this._mapPane, 'leaflet-zoom-anim');
+ }
+
+ this._animatingZoom = false;
+
+ this._move(this._animateToCenter, this._animateToZoom);
+
+ // This anim frame should prevent an obscure iOS webkit tile loading race condition.
+ requestAnimFrame(function () {
+ this._moveEnd(true);
+ }, this);
+ }
+});
+
+// @section
+
+// @factory L.map(id: String, options?: Map options)
+// Instantiates a map object given the DOM ID of a `<div>` element
+// and optionally an object literal with `Map options`.
+//
+// @alternative
+// @factory L.map(el: HTMLElement, options?: Map options)
+// Instantiates a map object given an instance of a `<div>` HTML element
+// and optionally an object literal with `Map options`.
+function createMap(id, options) {
+ return new Map(id, options);
+}
+
+/*
+ * @class Control
+ * @aka L.Control
+ * @inherits Class
+ *
+ * L.Control is a base class for implementing map controls. Handles positioning.
+ * All other controls extend from this class.
+ */
+
+var Control = Class.extend({
+ // @section
+ // @aka Control options
+ options: {
+ // @option position: String = 'topright'
+ // The position of the control (one of the map corners). Possible values are `'topleft'`,
+ // `'topright'`, `'bottomleft'` or `'bottomright'`
+ position: 'topright'
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+ },
+
+ /* @section
+ * Classes extending L.Control will inherit the following methods:
+ *
+ * @method getPosition: string
+ * Returns the position of the control.
+ */
+ getPosition: function () {
+ return this.options.position;
+ },
+
+ // @method setPosition(position: string): this
+ // Sets the position of the control.
+ setPosition: function (position) {
+ var map = this._map;
+
+ if (map) {
+ map.removeControl(this);
+ }
+
+ this.options.position = position;
+
+ if (map) {
+ map.addControl(this);
+ }
+
+ return this;
+ },
+
+ // @method getContainer: HTMLElement
+ // Returns the HTMLElement that contains the control.
+ getContainer: function () {
+ return this._container;
+ },
+
+ // @method addTo(map: Map): this
+ // Adds the control to the given map.
+ addTo: function (map) {
+ this.remove();
+ this._map = map;
+
+ var container = this._container = this.onAdd(map),
+ pos = this.getPosition(),
+ corner = map._controlCorners[pos];
+
+ addClass(container, 'leaflet-control');
+
+ if (pos.indexOf('bottom') !== -1) {
+ corner.insertBefore(container, corner.firstChild);
+ } else {
+ corner.appendChild(container);
+ }
+
+ return this;
+ },
+
+ // @method remove: this
+ // Removes the control from the map it is currently active on.
+ remove: function () {
+ if (!this._map) {
+ return this;
+ }
+
+ remove(this._container);
+
+ if (this.onRemove) {
+ this.onRemove(this._map);
+ }
+
+ this._map = null;
+
+ return this;
+ },
+
+ _refocusOnMap: function (e) {
+ // if map exists and event is not a keyboard event
+ if (this._map && e && e.screenX > 0 && e.screenY > 0) {
+ this._map.getContainer().focus();
+ }
+ }
+});
+
+var control = function (options) {
+ return new Control(options);
+};
+
+/* @section Extension methods
+ * @uninheritable
+ *
+ * Every control should extend from `L.Control` and (re-)implement the following methods.
+ *
+ * @method onAdd(map: Map): HTMLElement
+ * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
+ *
+ * @method onRemove(map: Map)
+ * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
+ */
+
+/* @namespace Map
+ * @section Methods for Layers and Controls
+ */
+Map.include({
+ // @method addControl(control: Control): this
+ // Adds the given control to the map
+ addControl: function (control) {
+ control.addTo(this);
+ return this;
+ },
+
+ // @method removeControl(control: Control): this
+ // Removes the given control from the map
+ removeControl: function (control) {
+ control.remove();
+ return this;
+ },
+
+ _initControlPos: function () {
+ var corners = this._controlCorners = {},
+ l = 'leaflet-',
+ container = this._controlContainer =
+ create$1('div', l + 'control-container', this._container);
+
+ function createCorner(vSide, hSide) {
+ var className = l + vSide + ' ' + l + hSide;
+
+ corners[vSide + hSide] = create$1('div', className, container);
+ }
+
+ createCorner('top', 'left');
+ createCorner('top', 'right');
+ createCorner('bottom', 'left');
+ createCorner('bottom', 'right');
+ },
+
+ _clearControlPos: function () {
+ for (var i in this._controlCorners) {
+ remove(this._controlCorners[i]);
+ }
+ remove(this._controlContainer);
+ delete this._controlCorners;
+ delete this._controlContainer;
+ }
+});
+
+/*
+ * @class Control.Layers
+ * @aka L.Control.Layers
+ * @inherits Control
+ *
+ * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.
+ *
+ * @example
+ *
+ * ```js
+ * var baseLayers = {
+ * "Mapbox": mapbox,
+ * "OpenStreetMap": osm
+ * };
+ *
+ * var overlays = {
+ * "Marker": marker,
+ * "Roads": roadsLayer
+ * };
+ *
+ * L.control.layers(baseLayers, overlays).addTo(map);
+ * ```
+ *
+ * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
+ *
+ * ```js
+ * {
+ * "<someName1>": layer1,
+ * "<someName2>": layer2
+ * }
+ * ```
+ *
+ * The layer names can contain HTML, which allows you to add additional styling to the items:
+ *
+ * ```js
+ * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
+ * ```
+ */
+
+var Layers = Control.extend({
+ // @section
+ // @aka Control.Layers options
+ options: {
+ // @option collapsed: Boolean = true
+ // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
+ collapsed: true,
+ position: 'topright',
+
+ // @option autoZIndex: Boolean = true
+ // 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.
+ autoZIndex: true,
+
+ // @option hideSingleBase: Boolean = false
+ // If `true`, the base layers in the control will be hidden when there is only one.
+ hideSingleBase: false,
+
+ // @option sortLayers: Boolean = false
+ // Whether to sort the layers. When `false`, layers will keep the order
+ // in which they were added to the control.
+ sortLayers: false,
+
+ // @option sortFunction: Function = *
+ // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
+ // that will be used for sorting the layers, when `sortLayers` is `true`.
+ // The function receives both the `L.Layer` instances and their names, as in
+ // `sortFunction(layerA, layerB, nameA, nameB)`.
+ // By default, it sorts layers alphabetically by their name.
+ sortFunction: function (layerA, layerB, nameA, nameB) {
+ return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
+ }
+ },
+
+ initialize: function (baseLayers, overlays, options) {
+ setOptions(this, options);
+
+ this._layerControlInputs = [];
+ this._layers = [];
+ this._lastZIndex = 0;
+ this._handlingClick = false;
+
+ for (var i in baseLayers) {
+ this._addLayer(baseLayers[i], i);
+ }
+
+ for (i in overlays) {
+ this._addLayer(overlays[i], i, true);
+ }
+ },
+
+ onAdd: function (map) {
+ this._initLayout();
+ this._update();
+
+ this._map = map;
+ map.on('zoomend', this._checkDisabledLayers, this);
+
+ for (var i = 0; i < this._layers.length; i++) {
+ this._layers[i].layer.on('add remove', this._onLayerChange, this);
+ }
+
+ return this._container;
+ },
+
+ addTo: function (map) {
+ Control.prototype.addTo.call(this, map);
+ // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
+ return this._expandIfNotCollapsed();
+ },
+
+ onRemove: function () {
+ this._map.off('zoomend', this._checkDisabledLayers, this);
+
+ for (var i = 0; i < this._layers.length; i++) {
+ this._layers[i].layer.off('add remove', this._onLayerChange, this);
+ }
+ },
+
+ // @method addBaseLayer(layer: Layer, name: String): this
+ // Adds a base layer (radio button entry) with the given name to the control.
+ addBaseLayer: function (layer, name) {
+ this._addLayer(layer, name);
+ return (this._map) ? this._update() : this;
+ },
+
+ // @method addOverlay(layer: Layer, name: String): this
+ // Adds an overlay (checkbox entry) with the given name to the control.
+ addOverlay: function (layer, name) {
+ this._addLayer(layer, name, true);
+ return (this._map) ? this._update() : this;
+ },
+
+ // @method removeLayer(layer: Layer): this
+ // Remove the given layer from the control.
+ removeLayer: function (layer) {
+ layer.off('add remove', this._onLayerChange, this);
+
+ var obj = this._getLayer(stamp(layer));
+ if (obj) {
+ this._layers.splice(this._layers.indexOf(obj), 1);
+ }
+ return (this._map) ? this._update() : this;
+ },
+
+ // @method expand(): this
+ // Expand the control container if collapsed.
+ expand: function () {
+ addClass(this._container, 'leaflet-control-layers-expanded');
+ this._section.style.height = null;
+ var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
+ if (acceptableHeight < this._section.clientHeight) {
+ addClass(this._section, 'leaflet-control-layers-scrollbar');
+ this._section.style.height = acceptableHeight + 'px';
+ } else {
+ removeClass(this._section, 'leaflet-control-layers-scrollbar');
+ }
+ this._checkDisabledLayers();
+ return this;
+ },
+
+ // @method collapse(): this
+ // Collapse the control container if expanded.
+ collapse: function () {
+ removeClass(this._container, 'leaflet-control-layers-expanded');
+ return this;
+ },
+
+ _initLayout: function () {
+ var className = 'leaflet-control-layers',
+ container = this._container = create$1('div', className),
+ collapsed = this.options.collapsed;
+
+ // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
+ container.setAttribute('aria-haspopup', true);
+
+ disableClickPropagation(container);
+ disableScrollPropagation(container);
+
+ var section = this._section = create$1('section', className + '-list');
+
+ if (collapsed) {
+ this._map.on('click', this.collapse, this);
+
+ if (!android) {
+ on(container, {
+ mouseenter: this.expand,
+ mouseleave: this.collapse
+ }, this);
+ }
+ }
+
+ var link = this._layersLink = create$1('a', className + '-toggle', container);
+ link.href = '#';
+ link.title = 'Layers';
+
+ if (touch) {
+ on(link, 'click', stop);
+ on(link, 'click', this.expand, this);
+ } else {
+ on(link, 'focus', this.expand, this);
+ }
+
+ if (!collapsed) {
+ this.expand();
+ }
+
+ this._baseLayersList = create$1('div', className + '-base', section);
+ this._separator = create$1('div', className + '-separator', section);
+ this._overlaysList = create$1('div', className + '-overlays', section);
+
+ container.appendChild(section);
+ },
+
+ _getLayer: function (id) {
+ for (var i = 0; i < this._layers.length; i++) {
+
+ if (this._layers[i] && stamp(this._layers[i].layer) === id) {
+ return this._layers[i];
+ }
+ }
+ },
+
+ _addLayer: function (layer, name, overlay) {
+ if (this._map) {
+ layer.on('add remove', this._onLayerChange, this);
+ }
+
+ this._layers.push({
+ layer: layer,
+ name: name,
+ overlay: overlay
+ });
+
+ if (this.options.sortLayers) {
+ this._layers.sort(bind(function (a, b) {
+ return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
+ }, this));
+ }
+
+ if (this.options.autoZIndex && layer.setZIndex) {
+ this._lastZIndex++;
+ layer.setZIndex(this._lastZIndex);
+ }
+
+ this._expandIfNotCollapsed();
+ },
+
+ _update: function () {
+ if (!this._container) { return this; }
+
+ empty(this._baseLayersList);
+ empty(this._overlaysList);
+
+ this._layerControlInputs = [];
+ var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
+
+ for (i = 0; i < this._layers.length; i++) {
+ obj = this._layers[i];
+ this._addItem(obj);
+ overlaysPresent = overlaysPresent || obj.overlay;
+ baseLayersPresent = baseLayersPresent || !obj.overlay;
+ baseLayersCount += !obj.overlay ? 1 : 0;
+ }
+
+ // Hide base layers section if there's only one layer.
+ if (this.options.hideSingleBase) {
+ baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
+ this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
+ }
+
+ this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
+
+ return this;
+ },
+
+ _onLayerChange: function (e) {
+ if (!this._handlingClick) {
+ this._update();
+ }
+
+ var obj = this._getLayer(stamp(e.target));
+
+ // @namespace Map
+ // @section Layer events
+ // @event baselayerchange: LayersControlEvent
+ // Fired when the base layer is changed through the [layer control](#control-layers).
+ // @event overlayadd: LayersControlEvent
+ // Fired when an overlay is selected through the [layer control](#control-layers).
+ // @event overlayremove: LayersControlEvent
+ // Fired when an overlay is deselected through the [layer control](#control-layers).
+ // @namespace Control.Layers
+ var type = obj.overlay ?
+ (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
+ (e.type === 'add' ? 'baselayerchange' : null);
+
+ if (type) {
+ this._map.fire(type, obj);
+ }
+ },
+
+ // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
+ _createRadioElement: function (name, checked) {
+
+ var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
+ name + '"' + (checked ? ' checked="checked"' : '') + '/>';
+
+ var radioFragment = document.createElement('div');
+ radioFragment.innerHTML = radioHtml;
+
+ return radioFragment.firstChild;
+ },
+
+ _addItem: function (obj) {
+ var label = document.createElement('label'),
+ checked = this._map.hasLayer(obj.layer),
+ input;
+
+ if (obj.overlay) {
+ input = document.createElement('input');
+ input.type = 'checkbox';
+ input.className = 'leaflet-control-layers-selector';
+ input.defaultChecked = checked;
+ } else {
+ input = this._createRadioElement('leaflet-base-layers', checked);
+ }
+
+ this._layerControlInputs.push(input);
+ input.layerId = stamp(obj.layer);
+
+ on(input, 'click', this._onInputClick, this);
+
+ var name = document.createElement('span');
+ name.innerHTML = ' ' + obj.name;
+
+ // Helps from preventing layer control flicker when checkboxes are disabled
+ // https://github.com/Leaflet/Leaflet/issues/2771
+ var holder = document.createElement('div');
+
+ label.appendChild(holder);
+ holder.appendChild(input);
+ holder.appendChild(name);
+
+ var container = obj.overlay ? this._overlaysList : this._baseLayersList;
+ container.appendChild(label);
+
+ this._checkDisabledLayers();
+ return label;
+ },
+
+ _onInputClick: function () {
+ var inputs = this._layerControlInputs,
+ input, layer;
+ var addedLayers = [],
+ removedLayers = [];
+
+ this._handlingClick = true;
+
+ for (var i = inputs.length - 1; i >= 0; i--) {
+ input = inputs[i];
+ layer = this._getLayer(input.layerId).layer;
+
+ if (input.checked) {
+ addedLayers.push(layer);
+ } else if (!input.checked) {
+ removedLayers.push(layer);
+ }
+ }
+
+ // Bugfix issue 2318: Should remove all old layers before readding new ones
+ for (i = 0; i < removedLayers.length; i++) {
+ if (this._map.hasLayer(removedLayers[i])) {
+ this._map.removeLayer(removedLayers[i]);
+ }
+ }
+ for (i = 0; i < addedLayers.length; i++) {
+ if (!this._map.hasLayer(addedLayers[i])) {
+ this._map.addLayer(addedLayers[i]);
+ }
+ }
+
+ this._handlingClick = false;
+
+ this._refocusOnMap();
+ },
+
+ _checkDisabledLayers: function () {
+ var inputs = this._layerControlInputs,
+ input,
+ layer,
+ zoom = this._map.getZoom();
+
+ for (var i = inputs.length - 1; i >= 0; i--) {
+ input = inputs[i];
+ layer = this._getLayer(input.layerId).layer;
+ input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
+ (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
+
+ }
+ },
+
+ _expandIfNotCollapsed: function () {
+ if (this._map && !this.options.collapsed) {
+ this.expand();
+ }
+ return this;
+ },
+
+ _expand: function () {
+ // Backward compatibility, remove me in 1.1.
+ return this.expand();
+ },
+
+ _collapse: function () {
+ // Backward compatibility, remove me in 1.1.
+ return this.collapse();
+ }
+
+});
+
+
+// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
+// Creates an attribution 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.
+var layers = function (baseLayers, overlays, options) {
+ return new Layers(baseLayers, overlays, options);
+};
+
+/*
+ * @class Control.Zoom
+ * @aka L.Control.Zoom
+ * @inherits Control
+ *
+ * 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`.
+ */
+
+var Zoom = Control.extend({
+ // @section
+ // @aka Control.Zoom options
+ options: {
+ position: 'topleft',
+
+ // @option zoomInText: String = '+'
+ // The text set on the 'zoom in' button.
+ zoomInText: '+',
+
+ // @option zoomInTitle: String = 'Zoom in'
+ // The title set on the 'zoom in' button.
+ zoomInTitle: 'Zoom in',
+
+ // @option zoomOutText: String = '−'
+ // The text set on the 'zoom out' button.
+ zoomOutText: '−',
+
+ // @option zoomOutTitle: String = 'Zoom out'
+ // The title set on the 'zoom out' button.
+ zoomOutTitle: 'Zoom out'
+ },
+
+ onAdd: function (map) {
+ var zoomName = 'leaflet-control-zoom',
+ container = create$1('div', zoomName + ' leaflet-bar'),
+ options = this.options;
+
+ this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
+ zoomName + '-in', container, this._zoomIn);
+ this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
+ zoomName + '-out', container, this._zoomOut);
+
+ this._updateDisabled();
+ map.on('zoomend zoomlevelschange', this._updateDisabled, this);
+
+ return container;
+ },
+
+ onRemove: function (map) {
+ map.off('zoomend zoomlevelschange', this._updateDisabled, this);
+ },
+
+ disable: function () {
+ this._disabled = true;
+ this._updateDisabled();
+ return this;
+ },
+
+ enable: function () {
+ this._disabled = false;
+ this._updateDisabled();
+ return this;
+ },
+
+ _zoomIn: function (e) {
+ if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
+ this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
+ }
+ },
+
+ _zoomOut: function (e) {
+ if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
+ this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
+ }
+ },
+
+ _createButton: function (html, title, className, container, fn) {
+ var link = create$1('a', className, container);
+ link.innerHTML = html;
+ link.href = '#';
+ link.title = title;
+
+ /*
+ * Will force screen readers like VoiceOver to read this as "Zoom in - button"
+ */
+ link.setAttribute('role', 'button');
+ link.setAttribute('aria-label', title);
+
+ disableClickPropagation(link);
+ on(link, 'click', stop);
+ on(link, 'click', fn, this);
+ on(link, 'click', this._refocusOnMap, this);
+
+ return link;
+ },
+
+ _updateDisabled: function () {
+ var map = this._map,
+ className = 'leaflet-disabled';
+
+ removeClass(this._zoomInButton, className);
+ removeClass(this._zoomOutButton, className);
+
+ if (this._disabled || map._zoom === map.getMinZoom()) {
+ addClass(this._zoomOutButton, className);
+ }
+ if (this._disabled || map._zoom === map.getMaxZoom()) {
+ addClass(this._zoomInButton, className);
+ }
+ }
+});
+
+// @namespace Map
+// @section Control options
+// @option zoomControl: Boolean = true
+// Whether a [zoom control](#control-zoom) is added to the map by default.
+Map.mergeOptions({
+ zoomControl: true
+});
+
+Map.addInitHook(function () {
+ if (this.options.zoomControl) {
+ // @section Controls
+ // @property zoomControl: Control.Zoom
+ // The default zoom control (only available if the
+ // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
+ this.zoomControl = new Zoom();
+ this.addControl(this.zoomControl);
+ }
+});
+
+// @namespace Control.Zoom
+// @factory L.control.zoom(options: Control.Zoom options)
+// Creates a zoom control
+var zoom = function (options) {
+ return new Zoom(options);
+};
+
+/*
+ * @class Control.Scale
+ * @aka L.Control.Scale
+ * @inherits Control
+ *
+ * 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`.
+ *
+ * @example
+ *
+ * ```js
+ * L.control.scale().addTo(map);
+ * ```
+ */
+
+var Scale = Control.extend({
+ // @section
+ // @aka Control.Scale options
+ options: {
+ position: 'bottomleft',
+
+ // @option maxWidth: Number = 100
+ // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
+ maxWidth: 100,
+
+ // @option metric: Boolean = True
+ // Whether to show the metric scale line (m/km).
+ metric: true,
+
+ // @option imperial: Boolean = True
+ // Whether to show the imperial scale line (mi/ft).
+ imperial: true
+
+ // @option updateWhenIdle: Boolean = false
+ // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
+ },
+
+ onAdd: function (map) {
+ var className = 'leaflet-control-scale',
+ container = create$1('div', className),
+ options = this.options;
+
+ this._addScales(options, className + '-line', container);
+
+ map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+ map.whenReady(this._update, this);
+
+ return container;
+ },
+
+ onRemove: function (map) {
+ map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+ },
+
+ _addScales: function (options, className, container) {
+ if (options.metric) {
+ this._mScale = create$1('div', className, container);
+ }
+ if (options.imperial) {
+ this._iScale = create$1('div', className, container);
+ }
+ },
+
+ _update: function () {
+ var map = this._map,
+ y = map.getSize().y / 2;
+
+ var maxMeters = map.distance(
+ map.containerPointToLatLng([0, y]),
+ map.containerPointToLatLng([this.options.maxWidth, y]));
+
+ this._updateScales(maxMeters);
+ },
+
+ _updateScales: function (maxMeters) {
+ if (this.options.metric && maxMeters) {
+ this._updateMetric(maxMeters);
+ }
+ if (this.options.imperial && maxMeters) {
+ this._updateImperial(maxMeters);
+ }
+ },
+
+ _updateMetric: function (maxMeters) {
+ var meters = this._getRoundNum(maxMeters),
+ label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
+
+ this._updateScale(this._mScale, label, meters / maxMeters);
+ },
+
+ _updateImperial: function (maxMeters) {
+ var maxFeet = maxMeters * 3.2808399,
+ maxMiles, miles, feet;
+
+ if (maxFeet > 5280) {
+ maxMiles = maxFeet / 5280;
+ miles = this._getRoundNum(maxMiles);
+ this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
+
+ } else {
+ feet = this._getRoundNum(maxFeet);
+ this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
+ }
+ },
+
+ _updateScale: function (scale, text, ratio) {
+ scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
+ scale.innerHTML = text;
+ },
+
+ _getRoundNum: function (num) {
+ var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
+ d = num / pow10;
+
+ d = d >= 10 ? 10 :
+ d >= 5 ? 5 :
+ d >= 3 ? 3 :
+ d >= 2 ? 2 : 1;
+
+ return pow10 * d;
+ }
+});
+
+
+// @factory L.control.scale(options?: Control.Scale options)
+// Creates an scale control with the given options.
+var scale = function (options) {
+ return new Scale(options);
+};
+
+/*
+ * @class Control.Attribution
+ * @aka L.Control.Attribution
+ * @inherits Control
+ *
+ * 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.
+ */
+
+var Attribution = Control.extend({
+ // @section
+ // @aka Control.Attribution options
+ options: {
+ position: 'bottomright',
+
+ // @option prefix: String = 'Leaflet'
+ // The HTML text shown before the attributions. Pass `false` to disable.
+ prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+
+ this._attributions = {};
+ },
+
+ onAdd: function (map) {
+ map.attributionControl = this;
+ this._container = create$1('div', 'leaflet-control-attribution');
+ disableClickPropagation(this._container);
+
+ // TODO ugly, refactor
+ for (var i in map._layers) {
+ if (map._layers[i].getAttribution) {
+ this.addAttribution(map._layers[i].getAttribution());
+ }
+ }
+
+ this._update();
+
+ return this._container;
+ },
+
+ // @method setPrefix(prefix: String): this
+ // Sets the text before the attributions.
+ setPrefix: function (prefix) {
+ this.options.prefix = prefix;
+ this._update();
+ return this;
+ },
+
+ // @method addAttribution(text: String): this
+ // Adds an attribution text (e.g. `'Vector data © Mapbox'`).
+ addAttribution: function (text) {
+ if (!text) { return this; }
+
+ if (!this._attributions[text]) {
+ this._attributions[text] = 0;
+ }
+ this._attributions[text]++;
+
+ this._update();
+
+ return this;
+ },
+
+ // @method removeAttribution(text: String): this
+ // Removes an attribution text.
+ removeAttribution: function (text) {
+ if (!text) { return this; }
+
+ if (this._attributions[text]) {
+ this._attributions[text]--;
+ this._update();
+ }
+
+ return this;
+ },
+
+ _update: function () {
+ if (!this._map) { return; }
+
+ var attribs = [];
+
+ for (var i in this._attributions) {
+ if (this._attributions[i]) {
+ attribs.push(i);
+ }
+ }
+
+ var prefixAndAttribs = [];
+
+ if (this.options.prefix) {
+ prefixAndAttribs.push(this.options.prefix);
+ }
+ if (attribs.length) {
+ prefixAndAttribs.push(attribs.join(', '));
+ }
+
+ this._container.innerHTML = prefixAndAttribs.join(' | ');
+ }
+});
+
+// @namespace Map
+// @section Control options
+// @option attributionControl: Boolean = true
+// Whether a [attribution control](#control-attribution) is added to the map by default.
+Map.mergeOptions({
+ attributionControl: true
+});
+
+Map.addInitHook(function () {
+ if (this.options.attributionControl) {
+ new Attribution().addTo(this);
+ }
+});
+
+// @namespace Control.Attribution
+// @factory L.control.attribution(options: Control.Attribution options)
+// Creates an attribution control.
+var attribution = function (options) {
+ return new Attribution(options);
+};
+
+Control.Layers = Layers;
+Control.Zoom = Zoom;
+Control.Scale = Scale;
+Control.Attribution = Attribution;
+
+control.layers = layers;
+control.zoom = zoom;
+control.scale = scale;
+control.attribution = attribution;
+
+/*
+ L.Handler is a base class for handler classes that are used internally to inject
+ interaction features like dragging to classes like Map and Marker.
+*/
+
+// @class Handler
+// @aka L.Handler
+// Abstract class for map interaction handlers
+
+var Handler = Class.extend({
+ initialize: function (map) {
+ this._map = map;
+ },
+
+ // @method enable(): this
+ // Enables the handler
+ enable: function () {
+ if (this._enabled) { return this; }
+
+ this._enabled = true;
+ this.addHooks();
+ return this;
+ },
+
+ // @method disable(): this
+ // Disables the handler
+ disable: function () {
+ if (!this._enabled) { return this; }
+
+ this._enabled = false;
+ this.removeHooks();
+ return this;
+ },
+
+ // @method enabled(): Boolean
+ // Returns `true` if the handler is enabled
+ enabled: function () {
+ return !!this._enabled;
+ }
+
+ // @section Extension methods
+ // Classes inheriting from `Handler` must implement the two following methods:
+ // @method addHooks()
+ // Called when the handler is enabled, should add event hooks.
+ // @method removeHooks()
+ // Called when the handler is disabled, should remove the event hooks added previously.
+});
+
+// @section There is static function which can be called without instantiating L.Handler:
+// @function addTo(map: Map, name: String): this
+// Adds a new Handler to the given map with the given name.
+Handler.addTo = function (map, name) {
+ map.addHandler(name, this);
+ return this;
+};
+
+var Mixin = {Events: Events};
+
+/*
+ * @class Draggable
+ * @aka L.Draggable
+ * @inherits Evented
+ *
+ * A class for making DOM elements draggable (including touch support).
+ * Used internally for map and marker dragging. Only works for elements
+ * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
+ *
+ * @example
+ * ```js
+ * var draggable = new L.Draggable(elementToDrag);
+ * draggable.enable();
+ * ```
+ */
+
+var START = touch ? 'touchstart mousedown' : 'mousedown';
+var END = {
+ mousedown: 'mouseup',
+ touchstart: 'touchend',
+ pointerdown: 'touchend',
+ MSPointerDown: 'touchend'
+};
+var MOVE = {
+ mousedown: 'mousemove',
+ touchstart: 'touchmove',
+ pointerdown: 'touchmove',
+ MSPointerDown: 'touchmove'
+};
+
+
+var Draggable = Evented.extend({
+
+ options: {
+ // @section
+ // @aka Draggable options
+ // @option clickTolerance: Number = 3
+ // The max number of pixels a user can shift the mouse pointer during a click
+ // for it to be considered a valid click (as opposed to a mouse drag).
+ clickTolerance: 3
+ },
+
+ // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
+ // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
+ initialize: function (element, dragStartTarget, preventOutline$$1, options) {
+ setOptions(this, options);
+
+ this._element = element;
+ this._dragStartTarget = dragStartTarget || element;
+ this._preventOutline = preventOutline$$1;
+ },
+
+ // @method enable()
+ // Enables the dragging ability
+ enable: function () {
+ if (this._enabled) { return; }
+
+ on(this._dragStartTarget, START, this._onDown, this);
+
+ this._enabled = true;
+ },
+
+ // @method disable()
+ // Disables the dragging ability
+ disable: function () {
+ if (!this._enabled) { return; }
+
+ // If we're currently dragging this draggable,
+ // disabling it counts as first ending the drag.
+ if (Draggable._dragging === this) {
+ this.finishDrag();
+ }
+
+ off(this._dragStartTarget, START, this._onDown, this);
+
+ this._enabled = false;
+ this._moved = false;
+ },
+
+ _onDown: function (e) {
+ // Ignore simulated events, since we handle both touch and
+ // mouse explicitly; otherwise we risk getting duplicates of
+ // touch events, see #4315.
+ // Also ignore the event if disabled; this happens in IE11
+ // under some circumstances, see #3666.
+ if (e._simulated || !this._enabled) { return; }
+
+ this._moved = false;
+
+ if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
+
+ if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
+ Draggable._dragging = this; // Prevent dragging multiple objects at once.
+
+ if (this._preventOutline) {
+ preventOutline(this._element);
+ }
+
+ disableImageDrag();
+ disableTextSelection();
+
+ if (this._moving) { return; }
+
+ // @event down: Event
+ // Fired when a drag is about to start.
+ this.fire('down');
+
+ var first = e.touches ? e.touches[0] : e,
+ sizedParent = getSizedParentNode(this._element);
+
+ this._startPoint = new Point(first.clientX, first.clientY);
+
+ // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
+ this._parentScale = getScale(sizedParent);
+
+ on(document, MOVE[e.type], this._onMove, this);
+ on(document, END[e.type], this._onUp, this);
+ },
+
+ _onMove: function (e) {
+ // Ignore simulated events, since we handle both touch and
+ // mouse explicitly; otherwise we risk getting duplicates of
+ // touch events, see #4315.
+ // Also ignore the event if disabled; this happens in IE11
+ // under some circumstances, see #3666.
+ if (e._simulated || !this._enabled) { return; }
+
+ if (e.touches && e.touches.length > 1) {
+ this._moved = true;
+ return;
+ }
+
+ var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
+ offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
+
+ if (!offset.x && !offset.y) { return; }
+ if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
+
+ // We assume that the parent container's position, border and scale do not change for the duration of the drag.
+ // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
+ // and we can use the cached value for the scale.
+ offset.x /= this._parentScale.x;
+ offset.y /= this._parentScale.y;
+
+ preventDefault(e);
+
+ if (!this._moved) {
+ // @event dragstart: Event
+ // Fired when a drag starts
+ this.fire('dragstart');
+
+ this._moved = true;
+ this._startPos = getPosition(this._element).subtract(offset);
+
+ addClass(document.body, 'leaflet-dragging');
+
+ this._lastTarget = e.target || e.srcElement;
+ // IE and Edge do not give the <use> element, so fetch it
+ // if necessary
+ if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
+ this._lastTarget = this._lastTarget.correspondingUseElement;
+ }
+ addClass(this._lastTarget, 'leaflet-drag-target');
+ }
+
+ this._newPos = this._startPos.add(offset);
+ this._moving = true;
+
+ cancelAnimFrame(this._animRequest);
+ this._lastEvent = e;
+ this._animRequest = requestAnimFrame(this._updatePosition, this, true);
+ },
+
+ _updatePosition: function () {
+ var e = {originalEvent: this._lastEvent};
+
+ // @event predrag: Event
+ // Fired continuously during dragging *before* each corresponding
+ // update of the element's position.
+ this.fire('predrag', e);
+ setPosition(this._element, this._newPos);
+
+ // @event drag: Event
+ // Fired continuously during dragging.
+ this.fire('drag', e);
+ },
+
+ _onUp: function (e) {
+ // Ignore simulated events, since we handle both touch and
+ // mouse explicitly; otherwise we risk getting duplicates of
+ // touch events, see #4315.
+ // Also ignore the event if disabled; this happens in IE11
+ // under some circumstances, see #3666.
+ if (e._simulated || !this._enabled) { return; }
+ this.finishDrag();
+ },
+
+ finishDrag: function () {
+ removeClass(document.body, 'leaflet-dragging');
+
+ if (this._lastTarget) {
+ removeClass(this._lastTarget, 'leaflet-drag-target');
+ this._lastTarget = null;
+ }
+
+ for (var i in MOVE) {
+ off(document, MOVE[i], this._onMove, this);
+ off(document, END[i], this._onUp, this);
+ }
+
+ enableImageDrag();
+ enableTextSelection();
+
+ if (this._moved && this._moving) {
+ // ensure drag is not fired after dragend
+ cancelAnimFrame(this._animRequest);
+
+ // @event dragend: DragEndEvent
+ // Fired when the drag ends.
+ this.fire('dragend', {
+ distance: this._newPos.distanceTo(this._startPos)
+ });
+ }
+
+ this._moving = false;
+ Draggable._dragging = false;
+ }
+
+});
+
+/*
+ * @namespace LineUtil
+ *
+ * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
+ */
+
+// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
+// Improves rendering performance dramatically by lessening the number of points to draw.
+
+// @function simplify(points: Point[], tolerance: Number): Point[]
+// Dramatically reduces the number of points in a polyline while retaining
+// its shape and returns a new array of simplified points, using the
+// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
+// Used for a huge performance boost when processing/displaying Leaflet polylines for
+// each zoom level and also reducing visual noise. tolerance affects the amount of
+// simplification (lesser value means higher quality but slower and with more points).
+// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
+function simplify(points, tolerance) {
+ if (!tolerance || !points.length) {
+ return points.slice();
+ }
+
+ var sqTolerance = tolerance * tolerance;
+
+ // stage 1: vertex reduction
+ points = _reducePoints(points, sqTolerance);
+
+ // stage 2: Douglas-Peucker simplification
+ points = _simplifyDP(points, sqTolerance);
+
+ return points;
+}
+
+// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
+// Returns the distance between point `p` and segment `p1` to `p2`.
+function pointToSegmentDistance(p, p1, p2) {
+ return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
+}
+
+// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
+// Returns the closest point from a point `p` on a segment `p1` to `p2`.
+function closestPointOnSegment(p, p1, p2) {
+ return _sqClosestPointOnSegment(p, p1, p2);
+}
+
+// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
+function _simplifyDP(points, sqTolerance) {
+
+ var len = points.length,
+ ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
+ markers = new ArrayConstructor(len);
+
+ markers[0] = markers[len - 1] = 1;
+
+ _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
+
+ var i,
+ newPoints = [];
+
+ for (i = 0; i < len; i++) {
+ if (markers[i]) {
+ newPoints.push(points[i]);
+ }
+ }
+
+ return newPoints;
+}
+
+function _simplifyDPStep(points, markers, sqTolerance, first, last) {
+
+ var maxSqDist = 0,
+ index, i, sqDist;
+
+ for (i = first + 1; i <= last - 1; i++) {
+ sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
+
+ if (sqDist > maxSqDist) {
+ index = i;
+ maxSqDist = sqDist;
+ }
+ }
+
+ if (maxSqDist > sqTolerance) {
+ markers[index] = 1;
+
+ _simplifyDPStep(points, markers, sqTolerance, first, index);
+ _simplifyDPStep(points, markers, sqTolerance, index, last);
+ }
+}
+
+// reduce points that are too close to each other to a single point
+function _reducePoints(points, sqTolerance) {
+ var reducedPoints = [points[0]];
+
+ for (var i = 1, prev = 0, len = points.length; i < len; i++) {
+ if (_sqDist(points[i], points[prev]) > sqTolerance) {
+ reducedPoints.push(points[i]);
+ prev = i;
+ }
+ }
+ if (prev < len - 1) {
+ reducedPoints.push(points[len - 1]);
+ }
+ return reducedPoints;
+}
+
+var _lastCode;
+
+// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
+// Clips the segment a to b by rectangular bounds with the
+// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
+// (modifying the segment points directly!). Used by Leaflet to only show polyline
+// points that are on the screen or near, increasing performance.
+function clipSegment(a, b, bounds, useLastCode, round) {
+ var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
+ codeB = _getBitCode(b, bounds),
+
+ codeOut, p, newCode;
+
+ // save 2nd code to avoid calculating it on the next segment
+ _lastCode = codeB;
+
+ while (true) {
+ // if a,b is inside the clip window (trivial accept)
+ if (!(codeA | codeB)) {
+ return [a, b];
+ }
+
+ // if a,b is outside the clip window (trivial reject)
+ if (codeA & codeB) {
+ return false;
+ }
+
+ // other cases
+ codeOut = codeA || codeB;
+ p = _getEdgeIntersection(a, b, codeOut, bounds, round);
+ newCode = _getBitCode(p, bounds);
+
+ if (codeOut === codeA) {
+ a = p;
+ codeA = newCode;
+ } else {
+ b = p;
+ codeB = newCode;
+ }
+ }
+}
+
+function _getEdgeIntersection(a, b, code, bounds, round) {
+ var dx = b.x - a.x,
+ dy = b.y - a.y,
+ min = bounds.min,
+ max = bounds.max,
+ x, y;
+
+ if (code & 8) { // top
+ x = a.x + dx * (max.y - a.y) / dy;
+ y = max.y;
+
+ } else if (code & 4) { // bottom
+ x = a.x + dx * (min.y - a.y) / dy;
+ y = min.y;
+
+ } else if (code & 2) { // right
+ x = max.x;
+ y = a.y + dy * (max.x - a.x) / dx;
+
+ } else if (code & 1) { // left
+ x = min.x;
+ y = a.y + dy * (min.x - a.x) / dx;
+ }
+
+ return new Point(x, y, round);
+}
+
+function _getBitCode(p, bounds) {
+ var code = 0;
+
+ if (p.x < bounds.min.x) { // left
+ code |= 1;
+ } else if (p.x > bounds.max.x) { // right
+ code |= 2;
+ }
+
+ if (p.y < bounds.min.y) { // bottom
+ code |= 4;
+ } else if (p.y > bounds.max.y) { // top
+ code |= 8;
+ }
+
+ return code;
+}
+
+// square distance (to avoid unnecessary Math.sqrt calls)
+function _sqDist(p1, p2) {
+ var dx = p2.x - p1.x,
+ dy = p2.y - p1.y;
+ return dx * dx + dy * dy;
+}
+
+// return closest point on segment or distance to that point
+function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
+ var x = p1.x,
+ y = p1.y,
+ dx = p2.x - x,
+ dy = p2.y - y,
+ dot = dx * dx + dy * dy,
+ t;
+
+ if (dot > 0) {
+ t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
+
+ if (t > 1) {
+ x = p2.x;
+ y = p2.y;
+ } else if (t > 0) {
+ x += dx * t;
+ y += dy * t;
+ }
+ }
+
+ dx = p.x - x;
+ dy = p.y - y;
+
+ return sqDist ? dx * dx + dy * dy : new Point(x, y);
+}
+
+
+// @function isFlat(latlngs: LatLng[]): Boolean
+// Returns true if `latlngs` is a flat array, false is nested.
+function isFlat(latlngs) {
+ return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
+}
+
+function _flat(latlngs) {
+ console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
+ return isFlat(latlngs);
+}
+
+
+var LineUtil = (Object.freeze || Object)({
+ simplify: simplify,
+ pointToSegmentDistance: pointToSegmentDistance,
+ closestPointOnSegment: closestPointOnSegment,
+ clipSegment: clipSegment,
+ _getEdgeIntersection: _getEdgeIntersection,
+ _getBitCode: _getBitCode,
+ _sqClosestPointOnSegment: _sqClosestPointOnSegment,
+ isFlat: isFlat,
+ _flat: _flat
+});
+
+/*
+ * @namespace PolyUtil
+ * Various utility functions for polygon geometries.
+ */
+
+/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
+ * 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)).
+ * Used by Leaflet to only show polygon points that are on the screen or near, increasing
+ * performance. Note that polygon points needs different algorithm for clipping
+ * than polyline, so there's a separate method for it.
+ */
+function clipPolygon(points, bounds, round) {
+ var clippedPoints,
+ edges = [1, 4, 2, 8],
+ i, j, k,
+ a, b,
+ len, edge, p;
+
+ for (i = 0, len = points.length; i < len; i++) {
+ points[i]._code = _getBitCode(points[i], bounds);
+ }
+
+ // for each edge (left, bottom, right, top)
+ for (k = 0; k < 4; k++) {
+ edge = edges[k];
+ clippedPoints = [];
+
+ for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+ a = points[i];
+ b = points[j];
+
+ // if a is inside the clip window
+ if (!(a._code & edge)) {
+ // if b is outside the clip window (a->b goes out of screen)
+ if (b._code & edge) {
+ p = _getEdgeIntersection(b, a, edge, bounds, round);
+ p._code = _getBitCode(p, bounds);
+ clippedPoints.push(p);
+ }
+ clippedPoints.push(a);
+
+ // else if b is inside the clip window (a->b enters the screen)
+ } else if (!(b._code & edge)) {
+ p = _getEdgeIntersection(b, a, edge, bounds, round);
+ p._code = _getBitCode(p, bounds);
+ clippedPoints.push(p);
+ }
+ }
+ points = clippedPoints;
+ }
+
+ return points;
+}
+
+
+var PolyUtil = (Object.freeze || Object)({
+ clipPolygon: clipPolygon
+});
+
+/*
+ * @namespace Projection
+ * @section
+ * Leaflet comes with a set of already defined Projections out of the box:
+ *
+ * @projection L.Projection.LonLat
+ *
+ * Equirectangular, or Plate Carree projection — the most simple projection,
+ * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
+ * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
+ * `EPSG:4326` and `Simple` CRS.
+ */
+
+var LonLat = {
+ project: function (latlng) {
+ return new Point(latlng.lng, latlng.lat);
+ },
+
+ unproject: function (point) {
+ return new LatLng(point.y, point.x);
+ },
+
+ bounds: new Bounds([-180, -90], [180, 90])
+};
+
+/*
+ * @namespace Projection
+ * @projection L.Projection.Mercator
+ *
+ * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
+ */
+
+var Mercator = {
+ R: 6378137,
+ R_MINOR: 6356752.314245179,
+
+ bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
+
+ project: function (latlng) {
+ var d = Math.PI / 180,
+ r = this.R,
+ y = latlng.lat * d,
+ tmp = this.R_MINOR / r,
+ e = Math.sqrt(1 - tmp * tmp),
+ con = e * Math.sin(y);
+
+ var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
+ y = -r * Math.log(Math.max(ts, 1E-10));
+
+ return new Point(latlng.lng * d * r, y);
+ },
+
+ unproject: function (point) {
+ var d = 180 / Math.PI,
+ r = this.R,
+ tmp = this.R_MINOR / r,
+ e = Math.sqrt(1 - tmp * tmp),
+ ts = Math.exp(-point.y / r),
+ phi = Math.PI / 2 - 2 * Math.atan(ts);
+
+ for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
+ con = e * Math.sin(phi);
+ con = Math.pow((1 - con) / (1 + con), e / 2);
+ dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
+ phi += dphi;
+ }
+
+ return new LatLng(phi * d, point.x * d / r);
+ }
+};
+
+/*
+ * @class Projection
+
+ * An object with methods for projecting geographical coordinates of the world onto
+ * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
+
+ * @property bounds: Bounds
+ * The bounds (specified in CRS units) where the projection is valid
+
+ * @method project(latlng: LatLng): Point
+ * Projects geographical coordinates into a 2D point.
+ * Only accepts actual `L.LatLng` instances, not arrays.
+
+ * @method unproject(point: Point): LatLng
+ * The inverse of `project`. Projects a 2D point into a geographical location.
+ * Only accepts actual `L.Point` instances, not arrays.
+
+ * Note that the projection instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
+
+ */
+
+
+
+
+var index = (Object.freeze || Object)({
+ LonLat: LonLat,
+ Mercator: Mercator,
+ SphericalMercator: SphericalMercator
+});
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.EPSG3395
+ *
+ * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
+ */
+var EPSG3395 = extend({}, Earth, {
+ code: 'EPSG:3395',
+ projection: Mercator,
+
+ transformation: (function () {
+ var scale = 0.5 / (Math.PI * Mercator.R);
+ return toTransformation(scale, 0.5, -scale, 0.5);
+ }())
+});
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.EPSG4326
+ *
+ * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
+ *
+ * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
+ * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
+ * with this CRS, ensure that there are two 256x256 pixel tiles covering the
+ * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
+ * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
+ */
+
+var EPSG4326 = extend({}, Earth, {
+ code: 'EPSG:4326',
+ projection: LonLat,
+ transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
+});
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.Simple
+ *
+ * A simple CRS that maps longitude and latitude into `x` and `y` directly.
+ * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
+ * axis should still be inverted (going from bottom to top). `distance()` returns
+ * simple euclidean distance.
+ */
+
+var Simple = extend({}, CRS, {
+ projection: LonLat,
+ transformation: toTransformation(1, 0, -1, 0),
+
+ scale: function (zoom) {
+ return Math.pow(2, zoom);
+ },
+
+ zoom: function (scale) {
+ return Math.log(scale) / Math.LN2;
+ },
+
+ distance: function (latlng1, latlng2) {
+ var dx = latlng2.lng - latlng1.lng,
+ dy = latlng2.lat - latlng1.lat;
+
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+
+ infinite: true
+});
+
+CRS.Earth = Earth;
+CRS.EPSG3395 = EPSG3395;
+CRS.EPSG3857 = EPSG3857;
+CRS.EPSG900913 = EPSG900913;
+CRS.EPSG4326 = EPSG4326;
+CRS.Simple = Simple;
+
+/*
+ * @class Layer
+ * @inherits Evented
+ * @aka L.Layer
+ * @aka ILayer
+ *
+ * A set of methods from the Layer base class that all Leaflet layers use.
+ * Inherits all methods, options and events from `L.Evented`.
+ *
+ * @example
+ *
+ * ```js
+ * var layer = L.Marker(latlng).addTo(map);
+ * layer.addTo(map);
+ * layer.remove();
+ * ```
+ *
+ * @event add: Event
+ * Fired after the layer is added to a map
+ *
+ * @event remove: Event
+ * Fired after the layer is removed from a map
+ */
+
+
+var Layer = Evented.extend({
+
+ // Classes extending `L.Layer` will inherit the following options:
+ options: {
+ // @option pane: String = 'overlayPane'
+ // 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.
+ pane: 'overlayPane',
+
+ // @option attribution: String = null
+ // 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.
+ attribution: null,
+
+ bubblingMouseEvents: true
+ },
+
+ /* @section
+ * Classes extending `L.Layer` will inherit the following methods:
+ *
+ * @method addTo(map: Map|LayerGroup): this
+ * Adds the layer to the given map or layer group.
+ */
+ addTo: function (map) {
+ map.addLayer(this);
+ return this;
+ },
+
+ // @method remove: this
+ // Removes the layer from the map it is currently active on.
+ remove: function () {
+ return this.removeFrom(this._map || this._mapToAdd);
+ },
+
+ // @method removeFrom(map: Map): this
+ // Removes the layer from the given map
+ removeFrom: function (obj) {
+ if (obj) {
+ obj.removeLayer(this);
+ }
+ return this;
+ },
+
+ // @method getPane(name? : String): HTMLElement
+ // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
+ getPane: function (name) {
+ return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
+ },
+
+ addInteractiveTarget: function (targetEl) {
+ this._map._targets[stamp(targetEl)] = this;
+ return this;
+ },
+
+ removeInteractiveTarget: function (targetEl) {
+ delete this._map._targets[stamp(targetEl)];
+ return this;
+ },
+
+ // @method getAttribution: String
+ // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
+ getAttribution: function () {
+ return this.options.attribution;
+ },
+
+ _layerAdd: function (e) {
+ var map = e.target;
+
+ // check in case layer gets added and then removed before the map is ready
+ if (!map.hasLayer(this)) { return; }
+
+ this._map = map;
+ this._zoomAnimated = map._zoomAnimated;
+
+ if (this.getEvents) {
+ var events = this.getEvents();
+ map.on(events, this);
+ this.once('remove', function () {
+ map.off(events, this);
+ }, this);
+ }
+
+ this.onAdd(map);
+
+ if (this.getAttribution && map.attributionControl) {
+ map.attributionControl.addAttribution(this.getAttribution());
+ }
+
+ this.fire('add');
+ map.fire('layeradd', {layer: this});
+ }
+});
+
+/* @section Extension methods
+ * @uninheritable
+ *
+ * Every layer should extend from `L.Layer` and (re-)implement the following methods.
+ *
+ * @method onAdd(map: Map): this
+ * 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).
+ *
+ * @method onRemove(map: Map): this
+ * 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).
+ *
+ * @method getEvents(): Object
+ * 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.
+ *
+ * @method getAttribution(): String
+ * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
+ *
+ * @method beforeAdd(map: Map): this
+ * 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.
+ */
+
+
+/* @namespace Map
+ * @section Layer events
+ *
+ * @event layeradd: LayerEvent
+ * Fired when a new layer is added to the map.
+ *
+ * @event layerremove: LayerEvent
+ * Fired when some layer is removed from the map
+ *
+ * @section Methods for Layers and Controls
+ */
+Map.include({
+ // @method addLayer(layer: Layer): this
+ // Adds the given layer to the map
+ addLayer: function (layer) {
+ if (!layer._layerAdd) {
+ throw new Error('The provided object is not a Layer.');
+ }
+
+ var id = stamp(layer);
+ if (this._layers[id]) { return this; }
+ this._layers[id] = layer;
+
+ layer._mapToAdd = this;
+
+ if (layer.beforeAdd) {
+ layer.beforeAdd(this);
+ }
+
+ this.whenReady(layer._layerAdd, layer);
+
+ return this;
+ },
+
+ // @method removeLayer(layer: Layer): this
+ // Removes the given layer from the map.
+ removeLayer: function (layer) {
+ var id = stamp(layer);
+
+ if (!this._layers[id]) { return this; }
+
+ if (this._loaded) {
+ layer.onRemove(this);
+ }
+
+ if (layer.getAttribution && this.attributionControl) {
+ this.attributionControl.removeAttribution(layer.getAttribution());
+ }
+
+ delete this._layers[id];
+
+ if (this._loaded) {
+ this.fire('layerremove', {layer: layer});
+ layer.fire('remove');
+ }
+
+ layer._map = layer._mapToAdd = null;
+
+ return this;
+ },
+
+ // @method hasLayer(layer: Layer): Boolean
+ // Returns `true` if the given layer is currently added to the map
+ hasLayer: function (layer) {
+ return !!layer && (stamp(layer) in this._layers);
+ },
+
+ /* @method eachLayer(fn: Function, context?: Object): this
+ * Iterates over the layers of the map, optionally specifying context of the iterator function.
+ * ```
+ * map.eachLayer(function(layer){
+ * layer.bindPopup('Hello');
+ * });
+ * ```
+ */
+ eachLayer: function (method, context) {
+ for (var i in this._layers) {
+ method.call(context, this._layers[i]);
+ }
+ return this;
+ },
+
+ _addLayers: function (layers) {
+ layers = layers ? (isArray(layers) ? layers : [layers]) : [];
+
+ for (var i = 0, len = layers.length; i < len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ _addZoomLimit: function (layer) {
+ if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
+ this._zoomBoundLayers[stamp(layer)] = layer;
+ this._updateZoomLevels();
+ }
+ },
+
+ _removeZoomLimit: function (layer) {
+ var id = stamp(layer);
+
+ if (this._zoomBoundLayers[id]) {
+ delete this._zoomBoundLayers[id];
+ this._updateZoomLevels();
+ }
+ },
+
+ _updateZoomLevels: function () {
+ var minZoom = Infinity,
+ maxZoom = -Infinity,
+ oldZoomSpan = this._getZoomSpan();
+
+ for (var i in this._zoomBoundLayers) {
+ var options = this._zoomBoundLayers[i].options;
+
+ minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
+ maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
+ }
+
+ this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
+ this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
+
+ // @section Map state change events
+ // @event zoomlevelschange: Event
+ // Fired when the number of zoomlevels on the map is changed due
+ // to adding or removing a layer.
+ if (oldZoomSpan !== this._getZoomSpan()) {
+ this.fire('zoomlevelschange');
+ }
+
+ if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
+ this.setZoom(this._layersMaxZoom);
+ }
+ if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
+ this.setZoom(this._layersMinZoom);
+ }
+ }
+});
+
+/*
+ * @class LayerGroup
+ * @aka L.LayerGroup
+ * @inherits Layer
+ *
+ * Used to group several layers and handle them as one. If you add it to the map,
+ * any layers added or removed from the group will be added/removed on the map as
+ * well. Extends `Layer`.
+ *
+ * @example
+ *
+ * ```js
+ * L.layerGroup([marker1, marker2])
+ * .addLayer(polyline)
+ * .addTo(map);
+ * ```
+ */
+
+var LayerGroup = Layer.extend({
+
+ initialize: function (layers, options) {
+ setOptions(this, options);
+
+ this._layers = {};
+
+ var i, len;
+
+ if (layers) {
+ for (i = 0, len = layers.length; i < len; i++) {
+ this.addLayer(layers[i]);
+ }
+ }
+ },
+
+ // @method addLayer(layer: Layer): this
+ // Adds the given layer to the group.
+ addLayer: function (layer) {
+ var id = this.getLayerId(layer);
+
+ this._layers[id] = layer;
+
+ if (this._map) {
+ this._map.addLayer(layer);
+ }
+
+ return this;
+ },
+
+ // @method removeLayer(layer: Layer): this
+ // Removes the given layer from the group.
+ // @alternative
+ // @method removeLayer(id: Number): this
+ // Removes the layer with the given internal ID from the group.
+ removeLayer: function (layer) {
+ var id = layer in this._layers ? layer : this.getLayerId(layer);
+
+ if (this._map && this._layers[id]) {
+ this._map.removeLayer(this._layers[id]);
+ }
+
+ delete this._layers[id];
+
+ return this;
+ },
+
+ // @method hasLayer(layer: Layer): Boolean
+ // Returns `true` if the given layer is currently added to the group.
+ // @alternative
+ // @method hasLayer(id: Number): Boolean
+ // Returns `true` if the given internal ID is currently added to the group.
+ hasLayer: function (layer) {
+ return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
+ },
+
+ // @method clearLayers(): this
+ // Removes all the layers from the group.
+ clearLayers: function () {
+ return this.eachLayer(this.removeLayer, this);
+ },
+
+ // @method invoke(methodName: String, …): this
+ // Calls `methodName` on every layer contained in this group, passing any
+ // additional parameters. Has no effect if the layers contained do not
+ // implement `methodName`.
+ invoke: function (methodName) {
+ var args = Array.prototype.slice.call(arguments, 1),
+ i, layer;
+
+ for (i in this._layers) {
+ layer = this._layers[i];
+
+ if (layer[methodName]) {
+ layer[methodName].apply(layer, args);
+ }
+ }
+
+ return this;
+ },
+
+ onAdd: function (map) {
+ this.eachLayer(map.addLayer, map);
+ },
+
+ onRemove: function (map) {
+ this.eachLayer(map.removeLayer, map);
+ },
+
+ // @method eachLayer(fn: Function, context?: Object): this
+ // Iterates over the layers of the group, optionally specifying context of the iterator function.
+ // ```js
+ // group.eachLayer(function (layer) {
+ // layer.bindPopup('Hello');
+ // });
+ // ```
+ eachLayer: function (method, context) {
+ for (var i in this._layers) {
+ method.call(context, this._layers[i]);
+ }
+ return this;
+ },
+
+ // @method getLayer(id: Number): Layer
+ // Returns the layer with the given internal ID.
+ getLayer: function (id) {
+ return this._layers[id];
+ },
+
+ // @method getLayers(): Layer[]
+ // Returns an array of all the layers added to the group.
+ getLayers: function () {
+ var layers = [];
+ this.eachLayer(layers.push, layers);
+ return layers;
+ },
+
+ // @method setZIndex(zIndex: Number): this
+ // Calls `setZIndex` on every layer contained in this group, passing the z-index.
+ setZIndex: function (zIndex) {
+ return this.invoke('setZIndex', zIndex);
+ },
+
+ // @method getLayerId(layer: Layer): Number
+ // Returns the internal ID for a layer
+ getLayerId: function (layer) {
+ return stamp(layer);
+ }
+});
+
+
+// @factory L.layerGroup(layers?: Layer[], options?: Object)
+// Create a layer group, optionally given an initial set of layers and an `options` object.
+var layerGroup = function (layers, options) {
+ return new LayerGroup(layers, options);
+};
+
+/*
+ * @class FeatureGroup
+ * @aka L.FeatureGroup
+ * @inherits LayerGroup
+ *
+ * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
+ * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
+ * * Events are propagated to the `FeatureGroup`, so if the group has an event
+ * handler, it will handle events from any of the layers. This includes mouse events
+ * and custom events.
+ * * Has `layeradd` and `layerremove` events
+ *
+ * @example
+ *
+ * ```js
+ * L.featureGroup([marker1, marker2, polyline])
+ * .bindPopup('Hello world!')
+ * .on('click', function() { alert('Clicked on a member of the group!'); })
+ * .addTo(map);
+ * ```
+ */
+
+var FeatureGroup = LayerGroup.extend({
+
+ addLayer: function (layer) {
+ if (this.hasLayer(layer)) {
+ return this;
+ }
+
+ layer.addEventParent(this);
+
+ LayerGroup.prototype.addLayer.call(this, layer);
+
+ // @event layeradd: LayerEvent
+ // Fired when a layer is added to this `FeatureGroup`
+ return this.fire('layeradd', {layer: layer});
+ },
+
+ removeLayer: function (layer) {
+ if (!this.hasLayer(layer)) {
+ return this;
+ }
+ if (layer in this._layers) {
+ layer = this._layers[layer];
+ }
+
+ layer.removeEventParent(this);
+
+ LayerGroup.prototype.removeLayer.call(this, layer);
+
+ // @event layerremove: LayerEvent
+ // Fired when a layer is removed from this `FeatureGroup`
+ return this.fire('layerremove', {layer: layer});
+ },
+
+ // @method setStyle(style: Path options): this
+ // Sets the given path options to each layer of the group that has a `setStyle` method.
+ setStyle: function (style) {
+ return this.invoke('setStyle', style);
+ },
+
+ // @method bringToFront(): this
+ // Brings the layer group to the top of all other layers
+ bringToFront: function () {
+ return this.invoke('bringToFront');
+ },
+
+ // @method bringToBack(): this
+ // Brings the layer group to the back of all other layers
+ bringToBack: function () {
+ return this.invoke('bringToBack');
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
+ getBounds: function () {
+ var bounds = new LatLngBounds();
+
+ for (var id in this._layers) {
+ var layer = this._layers[id];
+ bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
+ }
+ return bounds;
+ }
+});
+
+// @factory L.featureGroup(layers: Layer[])
+// Create a feature group, optionally given an initial set of layers.
+var featureGroup = function (layers) {
+ return new FeatureGroup(layers);
+};
+
+/*
+ * @class Icon
+ * @aka L.Icon
+ *
+ * Represents an icon to provide when creating a marker.
+ *
+ * @example
+ *
+ * ```js
+ * var myIcon = L.icon({
+ * iconUrl: 'my-icon.png',
+ * iconRetinaUrl: 'my-icon@2x.png',
+ * iconSize: [38, 95],
+ * iconAnchor: [22, 94],
+ * popupAnchor: [-3, -76],
+ * shadowUrl: 'my-icon-shadow.png',
+ * shadowRetinaUrl: 'my-icon-shadow@2x.png',
+ * shadowSize: [68, 95],
+ * shadowAnchor: [22, 94]
+ * });
+ *
+ * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
+ * ```
+ *
+ * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
+ *
+ */
+
+var Icon = Class.extend({
+
+ /* @section
+ * @aka Icon options
+ *
+ * @option iconUrl: String = null
+ * **(required)** The URL to the icon image (absolute or relative to your script path).
+ *
+ * @option iconRetinaUrl: String = null
+ * The URL to a retina sized version of the icon image (absolute or relative to your
+ * script path). Used for Retina screen devices.
+ *
+ * @option iconSize: Point = null
+ * Size of the icon image in pixels.
+ *
+ * @option iconAnchor: Point = null
+ * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
+ * will be aligned so that this point is at the marker's geographical location. Centered
+ * by default if size is specified, also can be set in CSS with negative margins.
+ *
+ * @option popupAnchor: Point = [0, 0]
+ * The coordinates of the point from which popups will "open", relative to the icon anchor.
+ *
+ * @option tooltipAnchor: Point = [0, 0]
+ * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
+ *
+ * @option shadowUrl: String = null
+ * The URL to the icon shadow image. If not specified, no shadow image will be created.
+ *
+ * @option shadowRetinaUrl: String = null
+ *
+ * @option shadowSize: Point = null
+ * Size of the shadow image in pixels.
+ *
+ * @option shadowAnchor: Point = null
+ * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
+ * as iconAnchor if not specified).
+ *
+ * @option className: String = ''
+ * A custom class name to assign to both icon and shadow images. Empty by default.
+ */
+
+ options: {
+ popupAnchor: [0, 0],
+ tooltipAnchor: [0, 0]
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+ },
+
+ // @method createIcon(oldIcon?: HTMLElement): HTMLElement
+ // Called internally when the icon has to be shown, returns a `<img>` HTML element
+ // styled according to the options.
+ createIcon: function (oldIcon) {
+ return this._createIcon('icon', oldIcon);
+ },
+
+ // @method createShadow(oldIcon?: HTMLElement): HTMLElement
+ // As `createIcon`, but for the shadow beneath it.
+ createShadow: function (oldIcon) {
+ return this._createIcon('shadow', oldIcon);
+ },
+
+ _createIcon: function (name, oldIcon) {
+ var src = this._getIconUrl(name);
+
+ if (!src) {
+ if (name === 'icon') {
+ throw new Error('iconUrl not set in Icon options (see the docs).');
+ }
+ return null;
+ }
+
+ var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
+ this._setIconStyles(img, name);
+
+ return img;
+ },
+
+ _setIconStyles: function (img, name) {
+ var options = this.options;
+ var sizeOption = options[name + 'Size'];
+
+ if (typeof sizeOption === 'number') {
+ sizeOption = [sizeOption, sizeOption];
+ }
+
+ var size = toPoint(sizeOption),
+ anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
+ size && size.divideBy(2, true));
+
+ img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
+
+ if (anchor) {
+ img.style.marginLeft = (-anchor.x) + 'px';
+ img.style.marginTop = (-anchor.y) + 'px';
+ }
+
+ if (size) {
+ img.style.width = size.x + 'px';
+ img.style.height = size.y + 'px';
+ }
+ },
+
+ _createImg: function (src, el) {
+ el = el || document.createElement('img');
+ el.src = src;
+ return el;
+ },
+
+ _getIconUrl: function (name) {
+ return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
+ }
+});
+
+
+// @factory L.icon(options: Icon options)
+// Creates an icon instance with the given options.
+function icon(options) {
+ return new Icon(options);
+}
+
+/*
+ * @miniclass Icon.Default (Icon)
+ * @aka L.Icon.Default
+ * @section
+ *
+ * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
+ * no icon is specified. Points to the blue marker image distributed with Leaflet
+ * releases.
+ *
+ * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
+ * (which is a set of `Icon options`).
+ *
+ * If you want to _completely_ replace the default icon, override the
+ * `L.Marker.prototype.options.icon` with your own icon instead.
+ */
+
+var IconDefault = Icon.extend({
+
+ options: {
+ iconUrl: 'marker-icon.png',
+ iconRetinaUrl: 'marker-icon-2x.png',
+ shadowUrl: 'marker-shadow.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ tooltipAnchor: [16, -28],
+ shadowSize: [41, 41]
+ },
+
+ _getIconUrl: function (name) {
+ if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
+ IconDefault.imagePath = this._detectIconPath();
+ }
+
+ // @option imagePath: String
+ // `Icon.Default` will try to auto-detect the location of the
+ // blue icon images. If you are placing these images in a non-standard
+ // way, set this option to point to the right path.
+ return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
+ },
+
+ _detectIconPath: function () {
+ var el = create$1('div', 'leaflet-default-icon-path', document.body);
+ var path = getStyle(el, 'background-image') ||
+ getStyle(el, 'backgroundImage'); // IE8
+
+ document.body.removeChild(el);
+
+ if (path === null || path.indexOf('url') !== 0) {
+ path = '';
+ } else {
+ path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
+ }
+
+ return path;
+ }
+});
+
+/*
+ * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
+ */
+
+
+/* @namespace Marker
+ * @section Interaction handlers
+ *
+ * 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:
+ *
+ * ```js
+ * marker.dragging.disable();
+ * ```
+ *
+ * @property dragging: Handler
+ * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
+ */
+
+var MarkerDrag = Handler.extend({
+ initialize: function (marker) {
+ this._marker = marker;
+ },
+
+ addHooks: function () {
+ var icon = this._marker._icon;
+
+ if (!this._draggable) {
+ this._draggable = new Draggable(icon, icon, true);
+ }
+
+ this._draggable.on({
+ dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
+ drag: this._onDrag,
+ dragend: this._onDragEnd
+ }, this).enable();
+
+ addClass(icon, 'leaflet-marker-draggable');
+ },
+
+ removeHooks: function () {
+ this._draggable.off({
+ dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
+ drag: this._onDrag,
+ dragend: this._onDragEnd
+ }, this).disable();
+
+ if (this._marker._icon) {
+ removeClass(this._marker._icon, 'leaflet-marker-draggable');
+ }
+ },
+
+ moved: function () {
+ return this._draggable && this._draggable._moved;
+ },
+
+ _adjustPan: function (e) {
+ var marker = this._marker,
+ map = marker._map,
+ speed = this._marker.options.autoPanSpeed,
+ padding = this._marker.options.autoPanPadding,
+ iconPos = getPosition(marker._icon),
+ bounds = map.getPixelBounds(),
+ origin = map.getPixelOrigin();
+
+ var panBounds = toBounds(
+ bounds.min._subtract(origin).add(padding),
+ bounds.max._subtract(origin).subtract(padding)
+ );
+
+ if (!panBounds.contains(iconPos)) {
+ // Compute incremental movement
+ var movement = toPoint(
+ (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
+ (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
+
+ (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
+ (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
+ ).multiplyBy(speed);
+
+ map.panBy(movement, {animate: false});
+
+ this._draggable._newPos._add(movement);
+ this._draggable._startPos._add(movement);
+
+ setPosition(marker._icon, this._draggable._newPos);
+ this._onDrag(e);
+
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
+ _onDragStart: function () {
+ // @section Dragging events
+ // @event dragstart: Event
+ // Fired when the user starts dragging the marker.
+
+ // @event movestart: Event
+ // Fired when the marker starts moving (because of dragging).
+
+ this._oldLatLng = this._marker.getLatLng();
+ this._marker
+ .closePopup()
+ .fire('movestart')
+ .fire('dragstart');
+ },
+
+ _onPreDrag: function (e) {
+ if (this._marker.options.autoPan) {
+ cancelAnimFrame(this._panRequest);
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
+ _onDrag: function (e) {
+ var marker = this._marker,
+ shadow = marker._shadow,
+ iconPos = getPosition(marker._icon),
+ latlng = marker._map.layerPointToLatLng(iconPos);
+
+ // update shadow position
+ if (shadow) {
+ setPosition(shadow, iconPos);
+ }
+
+ marker._latlng = latlng;
+ e.latlng = latlng;
+ e.oldLatLng = this._oldLatLng;
+
+ // @event drag: Event
+ // Fired repeatedly while the user drags the marker.
+ marker
+ .fire('move', e)
+ .fire('drag', e);
+ },
+
+ _onDragEnd: function (e) {
+ // @event dragend: DragEndEvent
+ // Fired when the user stops dragging the marker.
+
+ cancelAnimFrame(this._panRequest);
+
+ // @event moveend: Event
+ // Fired when the marker stops moving (because of dragging).
+ delete this._oldLatLng;
+ this._marker
+ .fire('moveend')
+ .fire('dragend', e);
+ }
+});
+
+/*
+ * @class Marker
+ * @inherits Interactive layer
+ * @aka L.Marker
+ * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
+ *
+ * @example
+ *
+ * ```js
+ * L.marker([50.5, 30.5]).addTo(map);
+ * ```
+ */
+
+var Marker = Layer.extend({
+
+ // @section
+ // @aka Marker options
+ options: {
+ // @option icon: Icon = *
+ // Icon instance to use for rendering the marker.
+ // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
+ // If not specified, a common instance of `L.Icon.Default` is used.
+ icon: new IconDefault(),
+
+ // Option inherited from "Interactive layer" abstract class
+ interactive: true,
+
+ // @option keyboard: Boolean = true
+ // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
+ keyboard: true,
+
+ // @option title: String = ''
+ // Text for the browser tooltip that appear on marker hover (no tooltip by default).
+ title: '',
+
+ // @option alt: String = ''
+ // Text for the `alt` attribute of the icon image (useful for accessibility).
+ alt: '',
+
+ // @option zIndexOffset: Number = 0
+ // 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).
+ zIndexOffset: 0,
+
+ // @option opacity: Number = 1.0
+ // The opacity of the marker.
+ opacity: 1,
+
+ // @option riseOnHover: Boolean = false
+ // If `true`, the marker will get on top of others when you hover the mouse over it.
+ riseOnHover: false,
+
+ // @option riseOffset: Number = 250
+ // The z-index offset used for the `riseOnHover` feature.
+ riseOffset: 250,
+
+ // @option pane: String = 'markerPane'
+ // `Map pane` where the markers icon will be added.
+ pane: 'markerPane',
+
+ // @option bubblingMouseEvents: Boolean = false
+ // When `true`, a mouse event on this marker will trigger the same event on the map
+ // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
+ bubblingMouseEvents: false,
+
+ // @section Draggable marker options
+ // @option draggable: Boolean = false
+ // Whether the marker is draggable with mouse/touch or not.
+ draggable: false,
+
+ // @option autoPan: Boolean = false
+ // Whether to pan the map when dragging this marker near its edge or not.
+ autoPan: false,
+
+ // @option autoPanPadding: Point = Point(50, 50)
+ // Distance (in pixels to the left/right and to the top/bottom) of the
+ // map edge to start panning the map.
+ autoPanPadding: [50, 50],
+
+ // @option autoPanSpeed: Number = 10
+ // Number of pixels the map should pan by.
+ autoPanSpeed: 10
+ },
+
+ /* @section
+ *
+ * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
+ */
+
+ initialize: function (latlng, options) {
+ setOptions(this, options);
+ this._latlng = toLatLng(latlng);
+ },
+
+ onAdd: function (map) {
+ this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
+
+ if (this._zoomAnimated) {
+ map.on('zoomanim', this._animateZoom, this);
+ }
+
+ this._initIcon();
+ this.update();
+ },
+
+ onRemove: function (map) {
+ if (this.dragging && this.dragging.enabled()) {
+ this.options.draggable = true;
+ this.dragging.removeHooks();
+ }
+ delete this.dragging;
+
+ if (this._zoomAnimated) {
+ map.off('zoomanim', this._animateZoom, this);
+ }
+
+ this._removeIcon();
+ this._removeShadow();
+ },
+
+ getEvents: function () {
+ return {
+ zoom: this.update,
+ viewreset: this.update
+ };
+ },
+
+ // @method getLatLng: LatLng
+ // Returns the current geographical position of the marker.
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ // @method setLatLng(latlng: LatLng): this
+ // Changes the marker position to the given point.
+ setLatLng: function (latlng) {
+ var oldLatLng = this._latlng;
+ this._latlng = toLatLng(latlng);
+ this.update();
+
+ // @event move: Event
+ // 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`.
+ return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
+ },
+
+ // @method setZIndexOffset(offset: Number): this
+ // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
+ setZIndexOffset: function (offset) {
+ this.options.zIndexOffset = offset;
+ return this.update();
+ },
+
+ // @method setIcon(icon: Icon): this
+ // Changes the marker icon.
+ setIcon: function (icon) {
+
+ this.options.icon = icon;
+
+ if (this._map) {
+ this._initIcon();
+ this.update();
+ }
+
+ if (this._popup) {
+ this.bindPopup(this._popup, this._popup.options);
+ }
+
+ return this;
+ },
+
+ getElement: function () {
+ return this._icon;
+ },
+
+ update: function () {
+
+ if (this._icon && this._map) {
+ var pos = this._map.latLngToLayerPoint(this._latlng).round();
+ this._setPos(pos);
+ }
+
+ return this;
+ },
+
+ _initIcon: function () {
+ var options = this.options,
+ classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
+
+ var icon = options.icon.createIcon(this._icon),
+ addIcon = false;
+
+ // if we're not reusing the icon, remove the old one and init new one
+ if (icon !== this._icon) {
+ if (this._icon) {
+ this._removeIcon();
+ }
+ addIcon = true;
+
+ if (options.title) {
+ icon.title = options.title;
+ }
+
+ if (icon.tagName === 'IMG') {
+ icon.alt = options.alt || '';