import {Evented} from '../core/Events'; import {Map} from '../map/Map'; import * as Util from '../core/Util'; /* * @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 */ export 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, describes the layer data, e.g. "© Mapbox". 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[Util.stamp(targetEl)] = this; return this; }, removeInteractiveTarget: function (targetEl) { delete this._map._targets[Util.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 = Util.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 = Util.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 && (Util.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 ? (Util.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[Util.stamp(layer)] = layer; this._updateZoomLevels(); } }, _removeZoomLimit: function (layer) { var id = Util.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); } } });