+ 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 pane: String = 'shadowPane'
+ // `Map pane` where the markers shadow will be added.
+ shadowPane: 'shadowPane',
+
+ // @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 getIcon: Icon
+ // Returns the current icon used by the marker
+ getIcon: function () {
+ return this.options.icon;
+ },
+
+ // @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 || '';
+ }
+ }
+
+ addClass(icon, classToAdd);
+
+ if (options.keyboard) {
+ icon.tabIndex = '0';
+ }
+
+ this._icon = icon;
+
+ if (options.riseOnHover) {
+ this.on({
+ mouseover: this._bringToFront,
+ mouseout: this._resetZIndex
+ });
+ }
+
+ var newShadow = options.icon.createShadow(this._shadow),
+ addShadow = false;
+
+ if (newShadow !== this._shadow) {
+ this._removeShadow();
+ addShadow = true;
+ }
+
+ if (newShadow) {
+ addClass(newShadow, classToAdd);
+ newShadow.alt = '';
+ }
+ this._shadow = newShadow;
+
+
+ if (options.opacity < 1) {
+ this._updateOpacity();
+ }
+
+
+ if (addIcon) {
+ this.getPane().appendChild(this._icon);
+ }
+ this._initInteraction();
+ if (newShadow && addShadow) {
+ this.getPane(options.shadowPane).appendChild(this._shadow);
+ }
+ },
+
+ _removeIcon: function () {
+ if (this.options.riseOnHover) {
+ this.off({
+ mouseover: this._bringToFront,
+ mouseout: this._resetZIndex
+ });
+ }
+
+ remove(this._icon);
+ this.removeInteractiveTarget(this._icon);
+
+ this._icon = null;
+ },
+
+ _removeShadow: function () {
+ if (this._shadow) {
+ remove(this._shadow);
+ }
+ this._shadow = null;
+ },
+
+ _setPos: function (pos) {
+
+ if (this._icon) {
+ setPosition(this._icon, pos);
+ }
+
+ if (this._shadow) {
+ setPosition(this._shadow, pos);
+ }
+
+ this._zIndex = pos.y + this.options.zIndexOffset;
+
+ this._resetZIndex();
+ },
+
+ _updateZIndex: function (offset) {
+ if (this._icon) {
+ this._icon.style.zIndex = this._zIndex + offset;
+ }
+ },
+
+ _animateZoom: function (opt) {
+ var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
+
+ this._setPos(pos);
+ },
+
+ _initInteraction: function () {
+
+ if (!this.options.interactive) { return; }
+
+ addClass(this._icon, 'leaflet-interactive');
+
+ this.addInteractiveTarget(this._icon);
+
+ if (MarkerDrag) {
+ var draggable = this.options.draggable;
+ if (this.dragging) {
+ draggable = this.dragging.enabled();
+ this.dragging.disable();
+ }
+
+ this.dragging = new MarkerDrag(this);
+
+ if (draggable) {
+ this.dragging.enable();
+ }
+ }
+ },
+
+ // @method setOpacity(opacity: Number): this
+ // Changes the opacity of the marker.
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+ if (this._map) {
+ this._updateOpacity();
+ }
+
+ return this;
+ },
+
+ _updateOpacity: function () {
+ var opacity = this.options.opacity;
+
+ if (this._icon) {
+ setOpacity(this._icon, opacity);
+ }
+
+ if (this._shadow) {
+ setOpacity(this._shadow, opacity);
+ }
+ },
+
+ _bringToFront: function () {
+ this._updateZIndex(this.options.riseOffset);
+ },
+
+ _resetZIndex: function () {
+ this._updateZIndex(0);
+ },
+
+ _getPopupAnchor: function () {
+ return this.options.icon.options.popupAnchor;
+ },
+
+ _getTooltipAnchor: function () {
+ return this.options.icon.options.tooltipAnchor;
+ }
+});
+
+
+// factory L.marker(latlng: LatLng, options? : Marker options)
+
+// @factory L.marker(latlng: LatLng, options? : Marker options)
+// Instantiates a Marker object given a geographical point and optionally an options object.
+function marker(latlng, options) {
+ return new Marker(latlng, options);
+}
+
+/*
+ * @class Path
+ * @aka L.Path
+ * @inherits Interactive layer
+ *
+ * An abstract class that contains options and constants shared between vector
+ * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
+ */
+
+var Path = Layer.extend({
+
+ // @section
+ // @aka Path options
+ options: {
+ // @option stroke: Boolean = true
+ // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
+ stroke: true,
+
+ // @option color: String = '#3388ff'
+ // Stroke color
+ color: '#3388ff',
+
+ // @option weight: Number = 3
+ // Stroke width in pixels
+ weight: 3,
+
+ // @option opacity: Number = 1.0
+ // Stroke opacity
+ opacity: 1,
+
+ // @option lineCap: String= 'round'
+ // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
+ lineCap: 'round',
+
+ // @option lineJoin: String = 'round'
+ // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
+ lineJoin: 'round',
+
+ // @option dashArray: String = null
+ // 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).
+ dashArray: null,
+
+ // @option dashOffset: String = null
+ // 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).
+ dashOffset: null,
+
+ // @option fill: Boolean = depends
+ // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
+ fill: false,
+
+ // @option fillColor: String = *
+ // Fill color. Defaults to the value of the [`color`](#path-color) option
+ fillColor: null,
+
+ // @option fillOpacity: Number = 0.2
+ // Fill opacity.
+ fillOpacity: 0.2,
+
+ // @option fillRule: String = 'evenodd'
+ // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
+ fillRule: 'evenodd',
+
+ // className: '',
+
+ // Option inherited from "Interactive layer" abstract class
+ interactive: true,
+
+ // @option bubblingMouseEvents: Boolean = true
+ // When `true`, a mouse event on this path will trigger the same event on the map
+ // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
+ bubblingMouseEvents: true
+ },
+
+ beforeAdd: function (map) {
+ // Renderer is set here because we need to call renderer.getEvents
+ // before this.getEvents.
+ this._renderer = map.getRenderer(this);
+ },
+
+ onAdd: function () {
+ this._renderer._initPath(this);
+ this._reset();
+ this._renderer._addPath(this);
+ },
+
+ onRemove: function () {
+ this._renderer._removePath(this);
+ },
+
+ // @method redraw(): this
+ // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
+ redraw: function () {
+ if (this._map) {
+ this._renderer._updatePath(this);
+ }
+ return this;
+ },
+
+ // @method setStyle(style: Path options): this
+ // Changes the appearance of a Path based on the options in the `Path options` object.
+ setStyle: function (style) {
+ setOptions(this, style);
+ if (this._renderer) {
+ this._renderer._updateStyle(this);
+ if (this.options.stroke && style && style.hasOwnProperty('weight')) {
+ this._updateBounds();
+ }
+ }
+ return this;
+ },
+
+ // @method bringToFront(): this
+ // Brings the layer to the top of all path layers.
+ bringToFront: function () {
+ if (this._renderer) {
+ this._renderer._bringToFront(this);
+ }
+ return this;
+ },
+
+ // @method bringToBack(): this
+ // Brings the layer to the bottom of all path layers.
+ bringToBack: function () {
+ if (this._renderer) {
+ this._renderer._bringToBack(this);
+ }
+ return this;
+ },
+
+ getElement: function () {
+ return this._path;
+ },
+
+ _reset: function () {
+ // defined in child classes
+ this._project();
+ this._update();
+ },
+
+ _clickTolerance: function () {
+ // used when doing hit detection for Canvas layers
+ return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
+ }
+});
+
+/*
+ * @class CircleMarker
+ * @aka L.CircleMarker
+ * @inherits Path
+ *
+ * A circle of a fixed size with radius specified in pixels. Extends `Path`.
+ */
+
+var CircleMarker = Path.extend({
+
+ // @section
+ // @aka CircleMarker options
+ options: {
+ fill: true,
+
+ // @option radius: Number = 10
+ // Radius of the circle marker, in pixels
+ radius: 10
+ },
+
+ initialize: function (latlng, options) {
+ setOptions(this, options);
+ this._latlng = toLatLng(latlng);
+ this._radius = this.options.radius;
+ },
+
+ // @method setLatLng(latLng: LatLng): this
+ // Sets the position of a circle marker to a new location.
+ setLatLng: function (latlng) {
+ var oldLatLng = this._latlng;
+ this._latlng = toLatLng(latlng);
+ this.redraw();
+
+ // @event move: Event
+ // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
+ return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
+ },
+
+ // @method getLatLng(): LatLng
+ // Returns the current geographical position of the circle marker
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ // @method setRadius(radius: Number): this
+ // Sets the radius of a circle marker. Units are in pixels.
+ setRadius: function (radius) {
+ this.options.radius = this._radius = radius;
+ return this.redraw();
+ },
+
+ // @method getRadius(): Number
+ // Returns the current radius of the circle
+ getRadius: function () {
+ return this._radius;
+ },
+
+ setStyle : function (options) {
+ var radius = options && options.radius || this._radius;
+ Path.prototype.setStyle.call(this, options);
+ this.setRadius(radius);
+ return this;
+ },
+
+ _project: function () {
+ this._point = this._map.latLngToLayerPoint(this._latlng);
+ this._updateBounds();
+ },
+
+ _updateBounds: function () {
+ var r = this._radius,
+ r2 = this._radiusY || r,
+ w = this._clickTolerance(),
+ p = [r + w, r2 + w];
+ this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
+ },
+
+ _update: function () {
+ if (this._map) {
+ this._updatePath();
+ }
+ },
+
+ _updatePath: function () {
+ this._renderer._updateCircle(this);
+ },
+
+ _empty: function () {
+ return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
+ },
+
+ // Needed by the `Canvas` renderer for interactivity
+ _containsPoint: function (p) {
+ return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
+ }
+});
+
+
+// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
+// Instantiates a circle marker object given a geographical point, and an optional options object.
+function circleMarker(latlng, options) {
+ return new CircleMarker(latlng, options);
+}
+
+/*
+ * @class Circle
+ * @aka L.Circle
+ * @inherits CircleMarker
+ *
+ * A class for drawing circle overlays on a map. Extends `CircleMarker`.
+ *
+ * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
+ *
+ * @example
+ *
+ * ```js
+ * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
+ * ```
+ */
+
+var Circle = CircleMarker.extend({
+
+ initialize: function (latlng, options, legacyOptions) {
+ if (typeof options === 'number') {
+ // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
+ options = extend({}, legacyOptions, {radius: options});
+ }
+ setOptions(this, options);
+ this._latlng = toLatLng(latlng);
+
+ if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
+
+ // @section
+ // @aka Circle options
+ // @option radius: Number; Radius of the circle, in meters.
+ this._mRadius = this.options.radius;
+ },
+
+ // @method setRadius(radius: Number): this
+ // Sets the radius of a circle. Units are in meters.
+ setRadius: function (radius) {
+ this._mRadius = radius;
+ return this.redraw();
+ },
+
+ // @method getRadius(): Number
+ // Returns the current radius of a circle. Units are in meters.
+ getRadius: function () {
+ return this._mRadius;
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the `LatLngBounds` of the path.
+ getBounds: function () {
+ var half = [this._radius, this._radiusY || this._radius];
+
+ return new LatLngBounds(
+ this._map.layerPointToLatLng(this._point.subtract(half)),
+ this._map.layerPointToLatLng(this._point.add(half)));
+ },
+
+ setStyle: Path.prototype.setStyle,
+
+ _project: function () {
+
+ var lng = this._latlng.lng,
+ lat = this._latlng.lat,
+ map = this._map,
+ crs = map.options.crs;
+
+ if (crs.distance === Earth.distance) {
+ var d = Math.PI / 180,
+ latR = (this._mRadius / Earth.R) / d,
+ top = map.project([lat + latR, lng]),
+ bottom = map.project([lat - latR, lng]),
+ p = top.add(bottom).divideBy(2),
+ lat2 = map.unproject(p).lat,
+ lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
+ (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
+
+ if (isNaN(lngR) || lngR === 0) {
+ lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
+ }
+
+ this._point = p.subtract(map.getPixelOrigin());
+ this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
+ this._radiusY = p.y - top.y;
+
+ } else {
+ var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
+
+ this._point = map.latLngToLayerPoint(this._latlng);
+ this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
+ }
+
+ this._updateBounds();
+ }
+});
+
+// @factory L.circle(latlng: LatLng, options?: Circle options)
+// Instantiates a circle object given a geographical point, and an options object
+// which contains the circle radius.
+// @alternative
+// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
+// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
+// Do not use in new applications or plugins.
+function circle(latlng, options, legacyOptions) {
+ return new Circle(latlng, options, legacyOptions);
+}
+
+/*
+ * @class Polyline
+ * @aka L.Polyline
+ * @inherits Path
+ *
+ * A class for drawing polyline overlays on a map. Extends `Path`.
+ *
+ * @example
+ *
+ * ```js
+ * // create a red polyline from an array of LatLng points
+ * var latlngs = [
+ * [45.51, -122.68],
+ * [37.77, -122.43],
+ * [34.04, -118.2]
+ * ];
+ *
+ * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
+ *
+ * // zoom the map to the polyline
+ * map.fitBounds(polyline.getBounds());
+ * ```
+ *
+ * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
+ *
+ * ```js
+ * // create a red polyline from an array of arrays of LatLng points
+ * var latlngs = [
+ * [[45.51, -122.68],
+ * [37.77, -122.43],
+ * [34.04, -118.2]],
+ * [[40.78, -73.91],
+ * [41.83, -87.62],
+ * [32.76, -96.72]]
+ * ];
+ * ```
+ */
+
+
+var Polyline = Path.extend({
+
+ // @section
+ // @aka Polyline options
+ options: {
+ // @option smoothFactor: Number = 1.0
+ // How much to simplify the polyline on each zoom level. More means
+ // better performance and smoother look, and less means more accurate representation.
+ smoothFactor: 1.0,
+
+ // @option noClip: Boolean = false
+ // Disable polyline clipping.
+ noClip: false
+ },
+
+ initialize: function (latlngs, options) {
+ setOptions(this, options);
+ this._setLatLngs(latlngs);
+ },
+
+ // @method getLatLngs(): LatLng[]
+ // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
+ getLatLngs: function () {
+ return this._latlngs;
+ },
+
+ // @method setLatLngs(latlngs: LatLng[]): this
+ // Replaces all the points in the polyline with the given array of geographical points.
+ setLatLngs: function (latlngs) {
+ this._setLatLngs(latlngs);
+ return this.redraw();
+ },
+
+ // @method isEmpty(): Boolean
+ // Returns `true` if the Polyline has no LatLngs.
+ isEmpty: function () {
+ return !this._latlngs.length;
+ },
+
+ // @method closestLayerPoint(p: Point): Point
+ // Returns the point closest to `p` on the Polyline.
+ closestLayerPoint: function (p) {
+ var minDistance = Infinity,
+ minPoint = null,
+ closest = _sqClosestPointOnSegment,
+ p1, p2;
+
+ for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
+ var points = this._parts[j];
+
+ for (var i = 1, len = points.length; i < len; i++) {
+ p1 = points[i - 1];
+ p2 = points[i];
+
+ var sqDist = closest(p, p1, p2, true);
+
+ if (sqDist < minDistance) {
+ minDistance = sqDist;
+ minPoint = closest(p, p1, p2);
+ }
+ }
+ }
+ if (minPoint) {
+ minPoint.distance = Math.sqrt(minDistance);
+ }
+ return minPoint;
+ },
+
+ // @method getCenter(): LatLng
+ // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
+ getCenter: function () {
+ // throws error when not yet added to map as this center calculation requires projected coordinates
+ if (!this._map) {
+ throw new Error('Must add layer to map before using getCenter()');
+ }
+
+ var i, halfDist, segDist, dist, p1, p2, ratio,
+ points = this._rings[0],
+ len = points.length;
+
+ if (!len) { return null; }
+
+ // polyline centroid algorithm; only uses the first ring if there are multiple
+
+ for (i = 0, halfDist = 0; i < len - 1; i++) {
+ halfDist += points[i].distanceTo(points[i + 1]) / 2;
+ }
+
+ // The line is so small in the current view that all points are on the same pixel.
+ if (halfDist === 0) {
+ return this._map.layerPointToLatLng(points[0]);
+ }
+
+ for (i = 0, dist = 0; i < len - 1; i++) {
+ p1 = points[i];
+ p2 = points[i + 1];
+ segDist = p1.distanceTo(p2);
+ dist += segDist;
+
+ if (dist > halfDist) {
+ ratio = (dist - halfDist) / segDist;
+ return this._map.layerPointToLatLng([
+ p2.x - ratio * (p2.x - p1.x),
+ p2.y - ratio * (p2.y - p1.y)
+ ]);
+ }
+ }
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the `LatLngBounds` of the path.
+ getBounds: function () {
+ return this._bounds;
+ },
+
+ // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
+ // Adds a given point to the polyline. By default, adds to the first ring of
+ // the polyline in case of a multi-polyline, but can be overridden by passing
+ // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
+ addLatLng: function (latlng, latlngs) {
+ latlngs = latlngs || this._defaultShape();
+ latlng = toLatLng(latlng);
+ latlngs.push(latlng);
+ this._bounds.extend(latlng);
+ return this.redraw();
+ },
+
+ _setLatLngs: function (latlngs) {
+ this._bounds = new LatLngBounds();
+ this._latlngs = this._convertLatLngs(latlngs);
+ },
+
+ _defaultShape: function () {
+ return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
+ },
+
+ // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
+ _convertLatLngs: function (latlngs) {
+ var result = [],
+ flat = isFlat(latlngs);
+
+ for (var i = 0, len = latlngs.length; i < len; i++) {
+ if (flat) {
+ result[i] = toLatLng(latlngs[i]);
+ this._bounds.extend(result[i]);
+ } else {
+ result[i] = this._convertLatLngs(latlngs[i]);
+ }
+ }
+
+ return result;
+ },
+
+ _project: function () {
+ var pxBounds = new Bounds();
+ this._rings = [];
+ this._projectLatlngs(this._latlngs, this._rings, pxBounds);
+
+ if (this._bounds.isValid() && pxBounds.isValid()) {
+ this._rawPxBounds = pxBounds;
+ this._updateBounds();
+ }
+ },
+
+ _updateBounds: function () {
+ var w = this._clickTolerance(),
+ p = new Point(w, w);
+ this._pxBounds = new Bounds([
+ this._rawPxBounds.min.subtract(p),
+ this._rawPxBounds.max.add(p)
+ ]);
+ },
+
+ // recursively turns latlngs into a set of rings with projected coordinates
+ _projectLatlngs: function (latlngs, result, projectedBounds) {
+ var flat = latlngs[0] instanceof LatLng,
+ len = latlngs.length,
+ i, ring;
+
+ if (flat) {
+ ring = [];
+ for (i = 0; i < len; i++) {
+ ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
+ projectedBounds.extend(ring[i]);
+ }
+ result.push(ring);
+ } else {
+ for (i = 0; i < len; i++) {
+ this._projectLatlngs(latlngs[i], result, projectedBounds);
+ }
+ }
+ },
+
+ // clip polyline by renderer bounds so that we have less to render for performance
+ _clipPoints: function () {
+ var bounds = this._renderer._bounds;
+
+ this._parts = [];
+ if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
+ return;
+ }
+
+ if (this.options.noClip) {
+ this._parts = this._rings;
+ return;
+ }
+
+ var parts = this._parts,
+ i, j, k, len, len2, segment, points;
+
+ for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
+ points = this._rings[i];
+
+ for (j = 0, len2 = points.length; j < len2 - 1; j++) {
+ segment = clipSegment(points[j], points[j + 1], bounds, j, true);
+
+ if (!segment) { continue; }
+
+ parts[k] = parts[k] || [];
+ parts[k].push(segment[0]);
+
+ // if segment goes out of screen, or it's the last one, it's the end of the line part
+ if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
+ parts[k].push(segment[1]);
+ k++;
+ }
+ }
+ }
+ },
+
+ // simplify each clipped part of the polyline for performance
+ _simplifyPoints: function () {
+ var parts = this._parts,
+ tolerance = this.options.smoothFactor;
+
+ for (var i = 0, len = parts.length; i < len; i++) {
+ parts[i] = simplify(parts[i], tolerance);
+ }
+ },
+
+ _update: function () {
+ if (!this._map) { return; }
+
+ this._clipPoints();
+ this._simplifyPoints();
+ this._updatePath();
+ },
+
+ _updatePath: function () {
+ this._renderer._updatePoly(this);
+ },
+
+ // Needed by the `Canvas` renderer for interactivity
+ _containsPoint: function (p, closed) {
+ var i, j, k, len, len2, part,
+ w = this._clickTolerance();
+
+ if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
+
+ // hit detection for polylines
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ part = this._parts[i];
+
+ for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+ if (!closed && (j === 0)) { continue; }
+
+ if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+});
+
+// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
+// Instantiates a polyline object given an array of geographical points and
+// optionally an options object. You can create a `Polyline` object with
+// multiple separate lines (`MultiPolyline`) by passing an array of arrays
+// of geographic points.
+function polyline(latlngs, options) {
+ return new Polyline(latlngs, options);
+}
+
+// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
+Polyline._flat = _flat;
+
+/*
+ * @class Polygon
+ * @aka L.Polygon
+ * @inherits Polyline
+ *
+ * A class for drawing polygon overlays on a map. Extends `Polyline`.
+ *
+ * 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.
+ *
+ *
+ * @example
+ *
+ * ```js
+ * // create a red polygon from an array of LatLng points
+ * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
+ *
+ * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
+ *
+ * // zoom the map to the polygon
+ * map.fitBounds(polygon.getBounds());
+ * ```
+ *
+ * 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:
+ *
+ * ```js
+ * var latlngs = [
+ * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
+ * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
+ * ];
+ * ```
+ *
+ * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
+ *
+ * ```js
+ * var latlngs = [
+ * [ // first polygon
+ * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
+ * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
+ * ],
+ * [ // second polygon
+ * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
+ * ]
+ * ];
+ * ```
+ */
+
+var Polygon = Polyline.extend({
+
+ options: {
+ fill: true
+ },
+
+ isEmpty: function () {
+ return !this._latlngs.length || !this._latlngs[0].length;
+ },
+
+ getCenter: function () {
+ // throws error when not yet added to map as this center calculation requires projected coordinates
+ if (!this._map) {
+ throw new Error('Must add layer to map before using getCenter()');
+ }
+
+ var i, j, p1, p2, f, area, x, y, center,
+ points = this._rings[0],
+ len = points.length;
+
+ if (!len) { return null; }
+
+ // polygon centroid algorithm; only uses the first ring if there are multiple
+
+ area = x = y = 0;
+
+ for (i = 0, j = len - 1; i < len; j = i++) {
+ p1 = points[i];
+ p2 = points[j];
+
+ f = p1.y * p2.x - p2.y * p1.x;
+ x += (p1.x + p2.x) * f;
+ y += (p1.y + p2.y) * f;
+ area += f * 3;
+ }
+
+ if (area === 0) {
+ // Polygon is so small that all points are on same pixel.
+ center = points[0];
+ } else {
+ center = [x / area, y / area];
+ }
+ return this._map.layerPointToLatLng(center);
+ },
+
+ _convertLatLngs: function (latlngs) {
+ var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
+ len = result.length;
+
+ // remove last point if it equals first one
+ if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
+ result.pop();
+ }
+ return result;
+ },
+
+ _setLatLngs: function (latlngs) {
+ Polyline.prototype._setLatLngs.call(this, latlngs);
+ if (isFlat(this._latlngs)) {
+ this._latlngs = [this._latlngs];
+ }
+ },
+
+ _defaultShape: function () {
+ return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
+ },
+
+ _clipPoints: function () {
+ // polygons need a different clipping algorithm so we redefine that
+
+ var bounds = this._renderer._bounds,
+ w = this.options.weight,
+ p = new Point(w, w);
+
+ // increase clip padding by stroke width to avoid stroke on clip edges
+ bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
+
+ this._parts = [];
+ if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
+ return;
+ }
+
+ if (this.options.noClip) {
+ this._parts = this._rings;
+ return;
+ }
+
+ for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
+ clipped = clipPolygon(this._rings[i], bounds, true);
+ if (clipped.length) {
+ this._parts.push(clipped);
+ }
+ }
+ },
+
+ _updatePath: function () {
+ this._renderer._updatePoly(this, true);
+ },
+
+ // Needed by the `Canvas` renderer for interactivity
+ _containsPoint: function (p) {
+ var inside = false,
+ part, p1, p2, i, j, k, len, len2;
+
+ if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
+
+ // ray casting algorithm for detecting if point is in polygon
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ part = this._parts[i];
+
+ for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+ p1 = part[j];
+ p2 = part[k];
+
+ 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)) {
+ inside = !inside;
+ }
+ }
+ }
+
+ // also check if it's on polygon stroke
+ return inside || Polyline.prototype._containsPoint.call(this, p, true);
+ }
+
+});
+
+
+// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
+function polygon(latlngs, options) {
+ return new Polygon(latlngs, options);
+}
+
+/*
+ * @class GeoJSON
+ * @aka L.GeoJSON
+ * @inherits FeatureGroup
+ *
+ * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
+ * GeoJSON data and display it on the map. Extends `FeatureGroup`.
+ *
+ * @example
+ *
+ * ```js
+ * L.geoJSON(data, {
+ * style: function (feature) {
+ * return {color: feature.properties.color};
+ * }
+ * }).bindPopup(function (layer) {
+ * return layer.feature.properties.description;
+ * }).addTo(map);
+ * ```
+ */
+
+var GeoJSON = FeatureGroup.extend({
+
+ /* @section
+ * @aka GeoJSON options
+ *
+ * @option pointToLayer: Function = *
+ * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
+ * called when data is added, passing the GeoJSON point feature and its `LatLng`.
+ * The default is to spawn a default `Marker`:
+ * ```js
+ * function(geoJsonPoint, latlng) {
+ * return L.marker(latlng);
+ * }
+ * ```
+ *
+ * @option style: Function = *
+ * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
+ * called internally when data is added.
+ * The default value is to not override any defaults:
+ * ```js
+ * function (geoJsonFeature) {
+ * return {}
+ * }
+ * ```
+ *
+ * @option onEachFeature: Function = *
+ * A `Function` that will be called once for each created `Feature`, after it has
+ * been created and styled. Useful for attaching events and popups to features.
+ * The default is to do nothing with the newly created layers:
+ * ```js
+ * function (feature, layer) {}
+ * ```
+ *
+ * @option filter: Function = *
+ * A `Function` that will be used to decide whether to include a feature or not.
+ * The default is to include all features:
+ * ```js
+ * function (geoJsonFeature) {
+ * return true;
+ * }
+ * ```
+ * Note: dynamically changing the `filter` option will have effect only on newly
+ * added data. It will _not_ re-evaluate already included features.
+ *
+ * @option coordsToLatLng: Function = *
+ * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
+ * The default is the `coordsToLatLng` static method.
+ *
+ * @option markersInheritOptions: Boolean = false
+ * Whether default Markers for "Point" type Features inherit from group options.
+ */
+
+ initialize: function (geojson, options) {
+ setOptions(this, options);
+
+ this._layers = {};
+
+ if (geojson) {
+ this.addData(geojson);
+ }
+ },
+
+ // @method addData( <GeoJSON> data ): this
+ // Adds a GeoJSON object to the layer.
+ addData: function (geojson) {
+ var features = isArray(geojson) ? geojson : geojson.features,
+ i, len, feature;
+
+ if (features) {
+ for (i = 0, len = features.length; i < len; i++) {
+ // only add this if geometry or geometries are set and not null
+ feature = features[i];
+ if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
+ this.addData(feature);
+ }
+ }
+ return this;
+ }
+
+ var options = this.options;
+
+ if (options.filter && !options.filter(geojson)) { return this; }
+
+ var layer = geometryToLayer(geojson, options);
+ if (!layer) {
+ return this;
+ }
+ layer.feature = asFeature(geojson);
+
+ layer.defaultOptions = layer.options;
+ this.resetStyle(layer);
+
+ if (options.onEachFeature) {
+ options.onEachFeature(geojson, layer);
+ }
+
+ return this.addLayer(layer);
+ },
+
+ // @method resetStyle( <Path> layer? ): this
+ // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
+ // If `layer` is omitted, the style of all features in the current layer is reset.
+ resetStyle: function (layer) {
+ if (layer === undefined) {
+ return this.eachLayer(this.resetStyle, this);
+ }
+ // reset any custom styles
+ layer.options = extend({}, layer.defaultOptions);
+ this._setLayerStyle(layer, this.options.style);
+ return this;
+ },
+
+ // @method setStyle( <Function> style ): this
+ // Changes styles of GeoJSON vector layers with the given style function.
+ setStyle: function (style) {
+ return this.eachLayer(function (layer) {
+ this._setLayerStyle(layer, style);
+ }, this);
+ },
+
+ _setLayerStyle: function (layer, style) {
+ if (layer.setStyle) {
+ if (typeof style === 'function') {
+ style = style(layer.feature);
+ }
+ layer.setStyle(style);
+ }
+ }
+});
+
+// @section
+// There are several static functions which can be called without instantiating L.GeoJSON:
+
+// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
+// Creates a `Layer` from a given GeoJSON feature. Can use a custom
+// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
+// functions if provided as options.
+function geometryToLayer(geojson, options) {
+
+ var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
+ coords = geometry ? geometry.coordinates : null,
+ layers = [],
+ pointToLayer = options && options.pointToLayer,
+ _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
+ latlng, latlngs, i, len;
+
+ if (!coords && !geometry) {
+ return null;
+ }
+
+ switch (geometry.type) {
+ case 'Point':
+ latlng = _coordsToLatLng(coords);
+ return _pointToLayer(pointToLayer, geojson, latlng, options);
+
+ case 'MultiPoint':
+ for (i = 0, len = coords.length; i < len; i++) {
+ latlng = _coordsToLatLng(coords[i]);
+ layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
+ }
+ return new FeatureGroup(layers);
+
+ case 'LineString':
+ case 'MultiLineString':
+ latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
+ return new Polyline(latlngs, options);
+
+ case 'Polygon':
+ case 'MultiPolygon':
+ latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
+ return new Polygon(latlngs, options);
+
+ case 'GeometryCollection':
+ for (i = 0, len = geometry.geometries.length; i < len; i++) {
+ var layer = geometryToLayer({
+ geometry: geometry.geometries[i],
+ type: 'Feature',
+ properties: geojson.properties
+ }, options);
+
+ if (layer) {
+ layers.push(layer);
+ }
+ }
+ return new FeatureGroup(layers);
+
+ default:
+ throw new Error('Invalid GeoJSON object.');
+ }
+}
+
+function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
+ return pointToLayerFn ?
+ pointToLayerFn(geojson, latlng) :
+ new Marker(latlng, options && options.markersInheritOptions && options);
+}
+
+// @function coordsToLatLng(coords: Array): LatLng
+// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
+// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
+function coordsToLatLng(coords) {
+ return new LatLng(coords[1], coords[0], coords[2]);
+}
+
+// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
+// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
+// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
+// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
+function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
+ var latlngs = [];
+
+ for (var i = 0, len = coords.length, latlng; i < len; i++) {
+ latlng = levelsDeep ?
+ coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
+ (_coordsToLatLng || coordsToLatLng)(coords[i]);
+
+ latlngs.push(latlng);
+ }
+
+ return latlngs;
+}
+
+// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
+// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
+function latLngToCoords(latlng, precision) {
+ precision = typeof precision === 'number' ? precision : 6;
+ return latlng.alt !== undefined ?
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
+}
+
+// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
+// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
+// `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.
+function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
+ var coords = [];
+
+ for (var i = 0, len = latlngs.length; i < len; i++) {
+ coords.push(levelsDeep ?
+ latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
+ latLngToCoords(latlngs[i], precision));
+ }
+
+ if (!levelsDeep && closed) {
+ coords.push(coords[0]);
+ }
+
+ return coords;
+}
+
+function getFeature(layer, newGeometry) {
+ return layer.feature ?
+ extend({}, layer.feature, {geometry: newGeometry}) :
+ asFeature(newGeometry);
+}
+
+// @function asFeature(geojson: Object): Object
+// Normalize GeoJSON geometries/features into GeoJSON features.
+function asFeature(geojson) {
+ if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
+ return geojson;
+ }
+
+ return {
+ type: 'Feature',
+ properties: {},
+ geometry: geojson
+ };
+}
+
+var PointToGeoJSON = {
+ toGeoJSON: function (precision) {
+ return getFeature(this, {
+ type: 'Point',
+ coordinates: latLngToCoords(this.getLatLng(), precision)
+ });
+ }
+};
+
+// @namespace Marker
+// @section Other methods
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
+Marker.include(PointToGeoJSON);
+
+// @namespace CircleMarker
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
+Circle.include(PointToGeoJSON);
+CircleMarker.include(PointToGeoJSON);
+
+
+// @namespace Polyline
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
+Polyline.include({
+ toGeoJSON: function (precision) {
+ var multi = !isFlat(this._latlngs);
+
+ var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
+
+ return getFeature(this, {
+ type: (multi ? 'Multi' : '') + 'LineString',
+ coordinates: coords
+ });
+ }
+});
+
+// @namespace Polygon
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
+Polygon.include({
+ toGeoJSON: function (precision) {
+ var holes = !isFlat(this._latlngs),
+ multi = holes && !isFlat(this._latlngs[0]);
+
+ var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
+
+ if (!holes) {
+ coords = [coords];
+ }
+
+ return getFeature(this, {
+ type: (multi ? 'Multi' : '') + 'Polygon',
+ coordinates: coords
+ });
+ }
+});
+
+
+// @namespace LayerGroup
+LayerGroup.include({
+ toMultiPoint: function (precision) {
+ var coords = [];
+
+ this.eachLayer(function (layer) {
+ coords.push(layer.toGeoJSON(precision).geometry.coordinates);
+ });
+
+ return getFeature(this, {
+ type: 'MultiPoint',
+ coordinates: coords
+ });
+ },
+
+ // @method toGeoJSON(precision?: Number): Object
+ // `precision` is the number of decimal places for coordinates.
+ // The default value is 6 places.
+ // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
+ toGeoJSON: function (precision) {
+
+ var type = this.feature && this.feature.geometry && this.feature.geometry.type;
+
+ if (type === 'MultiPoint') {
+ return this.toMultiPoint(precision);
+ }
+
+ var isGeometryCollection = type === 'GeometryCollection',
+ jsons = [];
+
+ this.eachLayer(function (layer) {
+ if (layer.toGeoJSON) {
+ var json = layer.toGeoJSON(precision);
+ if (isGeometryCollection) {
+ jsons.push(json.geometry);
+ } else {
+ var feature = asFeature(json);
+ // Squash nested feature collections
+ if (feature.type === 'FeatureCollection') {
+ jsons.push.apply(jsons, feature.features);
+ } else {
+ jsons.push(feature);
+ }
+ }
+ }
+ });
+
+ if (isGeometryCollection) {
+ return getFeature(this, {
+ geometries: jsons,
+ type: 'GeometryCollection'
+ });
+ }
+
+ return {
+ type: 'FeatureCollection',
+ features: jsons
+ };
+ }
+});
+
+// @namespace GeoJSON
+// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
+// Creates a GeoJSON layer. Optionally accepts an object in
+// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
+// (you can alternatively add it later with `addData` method) and an `options` object.
+function geoJSON(geojson, options) {
+ return new GeoJSON(geojson, options);
+}
+
+// Backward compatibility.
+var geoJson = geoJSON;
+
+/*
+ * @class ImageOverlay
+ * @aka L.ImageOverlay
+ * @inherits Interactive layer
+ *
+ * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
+ *
+ * @example
+ *
+ * ```js
+ * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
+ * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
+ * L.imageOverlay(imageUrl, imageBounds).addTo(map);
+ * ```
+ */
+
+var ImageOverlay = Layer.extend({
+
+ // @section
+ // @aka ImageOverlay options
+ options: {
+ // @option opacity: Number = 1.0
+ // The opacity of the image overlay.
+ opacity: 1,
+
+ // @option alt: String = ''
+ // Text for the `alt` attribute of the image (useful for accessibility).
+ alt: '',
+
+ // @option interactive: Boolean = false
+ // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
+ interactive: false,
+
+ // @option crossOrigin: Boolean|String = false
+ // Whether the crossOrigin attribute will be added to the image.
+ // 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.
+ // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
+ crossOrigin: false,
+
+ // @option errorOverlayUrl: String = ''
+ // URL to the overlay image to show in place of the overlay that failed to load.
+ errorOverlayUrl: '',
+
+ // @option zIndex: Number = 1
+ // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
+ zIndex: 1,
+
+ // @option className: String = ''
+ // A custom class name to assign to the image. Empty by default.
+ className: ''
+ },
+
+ initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
+ this._url = url;
+ this._bounds = toLatLngBounds(bounds);
+
+ setOptions(this, options);
+ },
+
+ onAdd: function () {
+ if (!this._image) {
+ this._initImage();
+
+ if (this.options.opacity < 1) {
+ this._updateOpacity();
+ }
+ }
+
+ if (this.options.interactive) {
+ addClass(this._image, 'leaflet-interactive');
+ this.addInteractiveTarget(this._image);
+ }
+
+ this.getPane().appendChild(this._image);
+ this._reset();
+ },
+
+ onRemove: function () {
+ remove(this._image);
+ if (this.options.interactive) {
+ this.removeInteractiveTarget(this._image);
+ }
+ },
+
+ // @method setOpacity(opacity: Number): this
+ // Sets the opacity of the overlay.
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+
+ if (this._image) {
+ this._updateOpacity();
+ }
+ return this;
+ },
+
+ setStyle: function (styleOpts) {
+ if (styleOpts.opacity) {
+ this.setOpacity(styleOpts.opacity);
+ }
+ return this;
+ },
+
+ // @method bringToFront(): this
+ // Brings the layer to the top of all overlays.
+ bringToFront: function () {
+ if (this._map) {
+ toFront(this._image);
+ }
+ return this;
+ },
+
+ // @method bringToBack(): this
+ // Brings the layer to the bottom of all overlays.
+ bringToBack: function () {
+ if (this._map) {
+ toBack(this._image);
+ }
+ return this;
+ },
+
+ // @method setUrl(url: String): this
+ // Changes the URL of the image.
+ setUrl: function (url) {
+ this._url = url;
+
+ if (this._image) {
+ this._image.src = url;
+ }
+ return this;
+ },
+
+ // @method setBounds(bounds: LatLngBounds): this
+ // Update the bounds that this ImageOverlay covers
+ setBounds: function (bounds) {
+ this._bounds = toLatLngBounds(bounds);
+
+ if (this._map) {
+ this._reset();
+ }
+ return this;
+ },
+
+ getEvents: function () {
+ var events = {
+ zoom: this._reset,
+ viewreset: this._reset
+ };
+
+ if (this._zoomAnimated) {
+ events.zoomanim = this._animateZoom;
+ }
+
+ return events;
+ },
+
+ // @method setZIndex(value: Number): this
+ // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
+ setZIndex: function (value) {
+ this.options.zIndex = value;
+ this._updateZIndex();
+ return this;
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Get the bounds that this ImageOverlay covers
+ getBounds: function () {
+ return this._bounds;
+ },
+
+ // @method getElement(): HTMLElement
+ // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
+ // used by this overlay.
+ getElement: function () {
+ return this._image;
+ },
+
+ _initImage: function () {
+ var wasElementSupplied = this._url.tagName === 'IMG';
+ var img = this._image = wasElementSupplied ? this._url : create$1('img');
+
+ addClass(img, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(img, this.options.className); }
+
+ img.onselectstart = falseFn;
+ img.onmousemove = falseFn;
+
+ // @event load: Event
+ // Fired when the ImageOverlay layer has loaded its image
+ img.onload = bind(this.fire, this, 'load');
+ img.onerror = bind(this._overlayOnError, this, 'error');
+
+ if (this.options.crossOrigin || this.options.crossOrigin === '') {
+ img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
+ }
+
+ if (this.options.zIndex) {
+ this._updateZIndex();
+ }
+
+ if (wasElementSupplied) {
+ this._url = img.src;
+ return;
+ }
+
+ img.src = this._url;
+ img.alt = this.options.alt;
+ },
+
+ _animateZoom: function (e) {
+ var scale = this._map.getZoomScale(e.zoom),
+ offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
+
+ setTransform(this._image, offset, scale);
+ },
+
+ _reset: function () {
+ var image = this._image,
+ bounds = new Bounds(
+ this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
+ this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
+ size = bounds.getSize();
+
+ setPosition(image, bounds.min);
+
+ image.style.width = size.x + 'px';
+ image.style.height = size.y + 'px';
+ },
+
+ _updateOpacity: function () {
+ setOpacity(this._image, this.options.opacity);
+ },
+
+ _updateZIndex: function () {
+ if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
+ this._image.style.zIndex = this.options.zIndex;
+ }
+ },
+
+ _overlayOnError: function () {
+ // @event error: Event
+ // Fired when the ImageOverlay layer fails to load its image
+ this.fire('error');
+
+ var errorUrl = this.options.errorOverlayUrl;
+ if (errorUrl && this._url !== errorUrl) {
+ this._url = errorUrl;
+ this._image.src = errorUrl;
+ }
+ }
+});
+
+// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
+// Instantiates an image overlay object given the URL of the image and the
+// geographical bounds it is tied to.
+var imageOverlay = function (url, bounds, options) {
+ return new ImageOverlay(url, bounds, options);
+};
+
+/*
+ * @class VideoOverlay
+ * @aka L.VideoOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
+ * HTML5 element.
+ *
+ * @example
+ *
+ * ```js
+ * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
+ * videoBounds = [[ 32, -130], [ 13, -100]];
+ * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
+ * ```
+ */
+
+var VideoOverlay = ImageOverlay.extend({
+
+ // @section
+ // @aka VideoOverlay options
+ options: {
+ // @option autoplay: Boolean = true
+ // Whether the video starts playing automatically when loaded.
+ autoplay: true,
+
+ // @option loop: Boolean = true
+ // Whether the video will loop back to the beginning when played.
+ loop: true,
+
+ // @option keepAspectRatio: Boolean = true
+ // Whether the video will save aspect ratio after the projection.
+ // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
+ keepAspectRatio: true
+ },
+
+ _initImage: function () {
+ var wasElementSupplied = this._url.tagName === 'VIDEO';
+ var vid = this._image = wasElementSupplied ? this._url : create$1('video');
+
+ addClass(vid, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(vid, this.options.className); }
+
+ vid.onselectstart = falseFn;
+ vid.onmousemove = falseFn;
+
+ // @event load: Event
+ // Fired when the video has finished loading the first frame
+ vid.onloadeddata = bind(this.fire, this, 'load');
+
+ if (wasElementSupplied) {
+ var sourceElements = vid.getElementsByTagName('source');
+ var sources = [];
+ for (var j = 0; j < sourceElements.length; j++) {
+ sources.push(sourceElements[j].src);
+ }
+
+ this._url = (sourceElements.length > 0) ? sources : [vid.src];
+ return;
+ }
+
+ if (!isArray(this._url)) { this._url = [this._url]; }
+
+ if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
+ vid.autoplay = !!this.options.autoplay;
+ vid.loop = !!this.options.loop;
+ for (var i = 0; i < this._url.length; i++) {
+ var source = create$1('source');
+ source.src = this._url[i];
+ vid.appendChild(source);
+ }
+ }
+
+ // @method getElement(): HTMLVideoElement
+ // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
+ // used by this overlay.
+});
+
+
+// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
+// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
+// geographical bounds it is tied to.
+
+function videoOverlay(video, bounds, options) {
+ return new VideoOverlay(video, bounds, options);
+}
+
+/*
+ * @class SVGOverlay
+ * @aka L.SVGOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
+ *
+ * @example
+ *
+ * ```js
+ * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
+ * svgElement.setAttribute('viewBox', "0 0 200 200");
+ * 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"/>';
+ * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
+ * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
+ * ```
+ */
+
+var SVGOverlay = ImageOverlay.extend({
+ _initImage: function () {
+ var el = this._image = this._url;
+
+ addClass(el, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(el, this.options.className); }
+
+ el.onselectstart = falseFn;
+ el.onmousemove = falseFn;
+ }
+
+ // @method getElement(): SVGElement
+ // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
+ // used by this overlay.
+});
+
+
+// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
+// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
+// A viewBox attribute is required on the SVG element to zoom in and out properly.
+
+function svgOverlay(el, bounds, options) {
+ return new SVGOverlay(el, bounds, options);
+}
+
+/*
+ * @class DivOverlay
+ * @inherits Layer
+ * @aka L.DivOverlay
+ * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
+ */
+
+// @namespace DivOverlay
+var DivOverlay = Layer.extend({
+
+ // @section
+ // @aka DivOverlay options
+ options: {
+ // @option offset: Point = Point(0, 7)
+ // The offset of the popup position. Useful to control the anchor
+ // of the popup when opening it on some overlays.
+ offset: [0, 7],
+
+ // @option className: String = ''
+ // A custom CSS class name to assign to the popup.
+ className: '',
+
+ // @option pane: String = 'popupPane'
+ // `Map pane` where the popup will be added.
+ pane: 'popupPane'
+ },
+
+ initialize: function (options, source) {
+ setOptions(this, options);
+
+ this._source = source;
+ },
+
+ onAdd: function (map) {
+ this._zoomAnimated = map._zoomAnimated;
+
+ if (!this._container) {
+ this._initLayout();
+ }
+
+ if (map._fadeAnimated) {
+ setOpacity(this._container, 0);
+ }
+
+ clearTimeout(this._removeTimeout);
+ this.getPane().appendChild(this._container);
+ this.update();
+
+ if (map._fadeAnimated) {
+ setOpacity(this._container, 1);
+ }
+
+ this.bringToFront();
+ },
+
+ onRemove: function (map) {
+ if (map._fadeAnimated) {
+ setOpacity(this._container, 0);
+ this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
+ } else {
+ remove(this._container);
+ }
+ },
+
+ // @namespace Popup
+ // @method getLatLng: LatLng
+ // Returns the geographical point of popup.
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ // @method setLatLng(latlng: LatLng): this
+ // Sets the geographical point where the popup will open.
+ setLatLng: function (latlng) {
+ this._latlng = toLatLng(latlng);
+ if (this._map) {
+ this._updatePosition();
+ this._adjustPan();
+ }
+ return this;
+ },
+
+ // @method getContent: String|HTMLElement
+ // Returns the content of the popup.
+ getContent: function () {
+ return this._content;
+ },
+
+ // @method setContent(htmlContent: String|HTMLElement|Function): this
+ // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
+ setContent: function (content) {
+ this._content = content;
+ this.update();
+ return this;
+ },
+
+ // @method getElement: String|HTMLElement
+ // Alias for [getContent()](#popup-getcontent)
+ getElement: function () {
+ return this._container;
+ },
+
+ // @method update: null
+ // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
+ update: function () {
+ if (!this._map) { return; }
+
+ this._container.style.visibility = 'hidden';
+
+ this._updateContent();
+ this._updateLayout();
+ this._updatePosition();
+
+ this._container.style.visibility = '';
+
+ this._adjustPan();
+ },
+
+ getEvents: function () {
+ var events = {
+ zoom: this._updatePosition,
+ viewreset: this._updatePosition
+ };
+
+ if (this._zoomAnimated) {
+ events.zoomanim = this._animateZoom;
+ }
+ return events;
+ },
+
+ // @method isOpen: Boolean
+ // Returns `true` when the popup is visible on the map.
+ isOpen: function () {
+ return !!this._map && this._map.hasLayer(this);
+ },
+
+ // @method bringToFront: this
+ // Brings this popup in front of other popups (in the same map pane).
+ bringToFront: function () {
+ if (this._map) {
+ toFront(this._container);
+ }
+ return this;
+ },
+
+ // @method bringToBack: this
+ // Brings this popup to the back of other popups (in the same map pane).
+ bringToBack: function () {
+ if (this._map) {
+ toBack(this._container);
+ }
+ return this;
+ },