UNPKG

8.07 kBJavaScriptView Raw
1import {Evented} from '../core/Events';
2import {Map} from '../map/Map';
3import * as Util from '../core/Util';
4
5/*
6 * @class Layer
7 * @inherits Evented
8 * @aka L.Layer
9 * @aka ILayer
10 *
11 * A set of methods from the Layer base class that all Leaflet layers use.
12 * Inherits all methods, options and events from `L.Evented`.
13 *
14 * @example
15 *
16 * ```js
17 * var layer = L.marker(latlng).addTo(map);
18 * layer.addTo(map);
19 * layer.remove();
20 * ```
21 *
22 * @event add: Event
23 * Fired after the layer is added to a map
24 *
25 * @event remove: Event
26 * Fired after the layer is removed from a map
27 */
28
29
30export var Layer = Evented.extend({
31
32 // Classes extending `L.Layer` will inherit the following options:
33 options: {
34 // @option pane: String = 'overlayPane'
35 // 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.
36 pane: 'overlayPane',
37
38 // @option attribution: String = null
39 // 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.
40 attribution: null,
41
42 bubblingMouseEvents: true
43 },
44
45 /* @section
46 * Classes extending `L.Layer` will inherit the following methods:
47 *
48 * @method addTo(map: Map|LayerGroup): this
49 * Adds the layer to the given map or layer group.
50 */
51 addTo: function (map) {
52 map.addLayer(this);
53 return this;
54 },
55
56 // @method remove: this
57 // Removes the layer from the map it is currently active on.
58 remove: function () {
59 return this.removeFrom(this._map || this._mapToAdd);
60 },
61
62 // @method removeFrom(map: Map): this
63 // Removes the layer from the given map
64 removeFrom: function (obj) {
65 if (obj) {
66 obj.removeLayer(this);
67 }
68 return this;
69 },
70
71 // @method getPane(name? : String): HTMLElement
72 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
73 getPane: function (name) {
74 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
75 },
76
77 addInteractiveTarget: function (targetEl) {
78 this._map._targets[Util.stamp(targetEl)] = this;
79 return this;
80 },
81
82 removeInteractiveTarget: function (targetEl) {
83 delete this._map._targets[Util.stamp(targetEl)];
84 return this;
85 },
86
87 // @method getAttribution: String
88 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
89 getAttribution: function () {
90 return this.options.attribution;
91 },
92
93 _layerAdd: function (e) {
94 var map = e.target;
95
96 // check in case layer gets added and then removed before the map is ready
97 if (!map.hasLayer(this)) { return; }
98
99 this._map = map;
100 this._zoomAnimated = map._zoomAnimated;
101
102 if (this.getEvents) {
103 var events = this.getEvents();
104 map.on(events, this);
105 this.once('remove', function () {
106 map.off(events, this);
107 }, this);
108 }
109
110 this.onAdd(map);
111
112 if (this.getAttribution && map.attributionControl) {
113 map.attributionControl.addAttribution(this.getAttribution());
114 }
115
116 this.fire('add');
117 map.fire('layeradd', {layer: this});
118 }
119});
120
121/* @section Extension methods
122 * @uninheritable
123 *
124 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
125 *
126 * @method onAdd(map: Map): this
127 * 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).
128 *
129 * @method onRemove(map: Map): this
130 * 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).
131 *
132 * @method getEvents(): Object
133 * 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.
134 *
135 * @method getAttribution(): String
136 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
137 *
138 * @method beforeAdd(map: Map): this
139 * 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.
140 */
141
142
143/* @namespace Map
144 * @section Layer events
145 *
146 * @event layeradd: LayerEvent
147 * Fired when a new layer is added to the map.
148 *
149 * @event layerremove: LayerEvent
150 * Fired when some layer is removed from the map
151 *
152 * @section Methods for Layers and Controls
153 */
154Map.include({
155 // @method addLayer(layer: Layer): this
156 // Adds the given layer to the map
157 addLayer: function (layer) {
158 if (!layer._layerAdd) {
159 throw new Error('The provided object is not a Layer.');
160 }
161
162 var id = Util.stamp(layer);
163 if (this._layers[id]) { return this; }
164 this._layers[id] = layer;
165
166 layer._mapToAdd = this;
167
168 if (layer.beforeAdd) {
169 layer.beforeAdd(this);
170 }
171
172 this.whenReady(layer._layerAdd, layer);
173
174 return this;
175 },
176
177 // @method removeLayer(layer: Layer): this
178 // Removes the given layer from the map.
179 removeLayer: function (layer) {
180 var id = Util.stamp(layer);
181
182 if (!this._layers[id]) { return this; }
183
184 if (this._loaded) {
185 layer.onRemove(this);
186 }
187
188 if (layer.getAttribution && this.attributionControl) {
189 this.attributionControl.removeAttribution(layer.getAttribution());
190 }
191
192 delete this._layers[id];
193
194 if (this._loaded) {
195 this.fire('layerremove', {layer: layer});
196 layer.fire('remove');
197 }
198
199 layer._map = layer._mapToAdd = null;
200
201 return this;
202 },
203
204 // @method hasLayer(layer: Layer): Boolean
205 // Returns `true` if the given layer is currently added to the map
206 hasLayer: function (layer) {
207 return !!layer && (Util.stamp(layer) in this._layers);
208 },
209
210 /* @method eachLayer(fn: Function, context?: Object): this
211 * Iterates over the layers of the map, optionally specifying context of the iterator function.
212 * ```
213 * map.eachLayer(function(layer){
214 * layer.bindPopup('Hello');
215 * });
216 * ```
217 */
218 eachLayer: function (method, context) {
219 for (var i in this._layers) {
220 method.call(context, this._layers[i]);
221 }
222 return this;
223 },
224
225 _addLayers: function (layers) {
226 layers = layers ? (Util.isArray(layers) ? layers : [layers]) : [];
227
228 for (var i = 0, len = layers.length; i < len; i++) {
229 this.addLayer(layers[i]);
230 }
231 },
232
233 _addZoomLimit: function (layer) {
234 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
235 this._zoomBoundLayers[Util.stamp(layer)] = layer;
236 this._updateZoomLevels();
237 }
238 },
239
240 _removeZoomLimit: function (layer) {
241 var id = Util.stamp(layer);
242
243 if (this._zoomBoundLayers[id]) {
244 delete this._zoomBoundLayers[id];
245 this._updateZoomLevels();
246 }
247 },
248
249 _updateZoomLevels: function () {
250 var minZoom = Infinity,
251 maxZoom = -Infinity,
252 oldZoomSpan = this._getZoomSpan();
253
254 for (var i in this._zoomBoundLayers) {
255 var options = this._zoomBoundLayers[i].options;
256
257 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
258 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
259 }
260
261 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
262 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
263
264 // @section Map state change events
265 // @event zoomlevelschange: Event
266 // Fired when the number of zoomlevels on the map is changed due
267 // to adding or removing a layer.
268 if (oldZoomSpan !== this._getZoomSpan()) {
269 this.fire('zoomlevelschange');
270 }
271
272 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
273 this.setZoom(this._layersMaxZoom);
274 }
275 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
276 this.setZoom(this._layersMinZoom);
277 }
278 }
279});