Merge pull request #2266 from Leaflet/layer

Layer refactoring
This commit is contained in:
Vladimir Agafonkin 2013-12-06 05:12:00 -08:00
commit 90ab59b94a
21 changed files with 299 additions and 361 deletions

View File

@ -7,9 +7,26 @@ Leaflet Changelog
An in-progress version being developed on the `master` branch. Includes `stable` branch fixes. An in-progress version being developed on the `master` branch. Includes `stable` branch fixes.
This version contains a lot of beneficial but potentially breaking changes (especially if you're a plugin author), so please read through the changes carefully before upgrading.
### Layers refactoring
All Leaflet layers (including markers, popups, tile and vector layers) have been refactored to have a common parent, `Layer` class, that shares the basic logic of adding and removing. The leads to the following changes (documented in PR [#2266](https://github.com/Leaflet/Leaflet/pull/2266)):
* Added `Layer` class which all layers added to a map should inherit from.
* Added `add` and `remove` events to all layers.
* Added `pane` option to all layers that can be changed (e.g. you can set `pane: 'overlayPane'` to a tile layer).
* Added `shadowPane` option to markers as well.
* Added `getEvents` method to all layers that returns an `{event: listener, ...}` hash; layers now manage its listeners automatically without having to do this in `onAdd`/`onRemove`.
* Improved performance of adding/removing layers with layers control present (instead of listening to any layer add/remove, the control only listens to layers added in configuration).
* Fixed `FeatureGroup` `getBounds` to work correctly when containing circle markers.
* Removed `Map` `tilelayersload` event.
* Removed `Popup` `open` and `close` events in favor of `add` and `remove` for consistency.
* Moved all layer-related logic in `Map.js` to `Layer.js`.
### TileLayer & Projections refactoring ### TileLayer & Projections refactoring
TileLayer code and everything projections-related has undergone a major refactoring, documented in [#2247](https://github.com/Leaflet/Leaflet/pull/2247). It includes the following changes (in addition to much cleaner and simpler code): TileLayer code and everything projections-related has undergone a major refactoring, documented in PR [#2247](https://github.com/Leaflet/Leaflet/pull/2247). It includes the following changes (in addition to much cleaner and simpler code):
#### TileLayer-related changes #### TileLayer-related changes

View File

@ -17,7 +17,9 @@ var deps = {
'geo/crs/CRS.Simple.js', 'geo/crs/CRS.Simple.js',
'geo/crs/CRS.EPSG3857.js', 'geo/crs/CRS.EPSG3857.js',
'geo/crs/CRS.EPSG4326.js', 'geo/crs/CRS.EPSG4326.js',
'map/Map.js'], 'map/Map.js',
'layer/Layer.js'
],
desc: 'The core of the library, including OOP, events, DOM facilities, basic units, projections (EPSG:3857 and EPSG:4326) and the base Map class.' desc: 'The core of the library, including OOP, events, DOM facilities, basic units, projections (EPSG:3857 and EPSG:4326) and the base Map class.'
}, },

View File

@ -199,16 +199,24 @@ describe("Map", function () {
}); });
}); });
function layerSpy() {
var layer = new L.Layer();
layer.onAdd = sinon.spy();
layer.onRemove = sinon.spy();
return layer;
}
describe("#addLayer", function () { describe("#addLayer", function () {
it("calls layer.onAdd immediately if the map is ready", function () { it("calls layer.onAdd immediately if the map is ready", function () {
var layer = { onAdd: sinon.spy() }; var layer = layerSpy();
map.setView([0, 0], 0); map.setView([0, 0], 0);
map.addLayer(layer); map.addLayer(layer);
expect(layer.onAdd.called).to.be.ok(); expect(layer.onAdd.called).to.be.ok();
}); });
it("calls layer.onAdd when the map becomes ready", function () { it("calls layer.onAdd when the map becomes ready", function () {
var layer = { onAdd: sinon.spy() }; var layer = layerSpy();
map.addLayer(layer); map.addLayer(layer);
expect(layer.onAdd.called).not.to.be.ok(); expect(layer.onAdd.called).not.to.be.ok();
map.setView([0, 0], 0); map.setView([0, 0], 0);
@ -216,7 +224,7 @@ describe("Map", function () {
}); });
it("does not call layer.onAdd if the layer is removed before the map becomes ready", function () { it("does not call layer.onAdd if the layer is removed before the map becomes ready", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }; var layer = layerSpy();
map.addLayer(layer); map.addLayer(layer);
map.removeLayer(layer); map.removeLayer(layer);
map.setView([0, 0], 0); map.setView([0, 0], 0);
@ -224,7 +232,7 @@ describe("Map", function () {
}); });
it("fires a layeradd event immediately if the map is ready", function () { it("fires a layeradd event immediately if the map is ready", function () {
var layer = { onAdd: sinon.spy() }, var layer = layerSpy(),
spy = sinon.spy(); spy = sinon.spy();
map.on('layeradd', spy); map.on('layeradd', spy);
map.setView([0, 0], 0); map.setView([0, 0], 0);
@ -233,7 +241,7 @@ describe("Map", function () {
}); });
it("fires a layeradd event when the map becomes ready", function () { it("fires a layeradd event when the map becomes ready", function () {
var layer = { onAdd: sinon.spy() }, var layer = layerSpy(),
spy = sinon.spy(); spy = sinon.spy();
map.on('layeradd', spy); map.on('layeradd', spy);
map.addLayer(layer); map.addLayer(layer);
@ -243,7 +251,7 @@ describe("Map", function () {
}); });
it("does not fire a layeradd event if the layer is removed before the map becomes ready", function () { it("does not fire a layeradd event if the layer is removed before the map becomes ready", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }, var layer = layerSpy(),
spy = sinon.spy(); spy = sinon.spy();
map.on('layeradd', spy); map.on('layeradd', spy);
map.addLayer(layer); map.addLayer(layer);
@ -253,7 +261,7 @@ describe("Map", function () {
}); });
it("adds the layer before firing layeradd", function (done) { it("adds the layer before firing layeradd", function (done) {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }; var layer = layerSpy();
map.on('layeradd', function () { map.on('layeradd', function () {
expect(map.hasLayer(layer)).to.be.ok(); expect(map.hasLayer(layer)).to.be.ok();
done(); done();
@ -299,7 +307,7 @@ describe("Map", function () {
describe("#removeLayer", function () { describe("#removeLayer", function () {
it("calls layer.onRemove if the map is ready", function () { it("calls layer.onRemove if the map is ready", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }; var layer = layerSpy();
map.setView([0, 0], 0); map.setView([0, 0], 0);
map.addLayer(layer); map.addLayer(layer);
map.removeLayer(layer); map.removeLayer(layer);
@ -307,21 +315,21 @@ describe("Map", function () {
}); });
it("does not call layer.onRemove if the layer was not added", function () { it("does not call layer.onRemove if the layer was not added", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }; var layer = layerSpy();
map.setView([0, 0], 0); map.setView([0, 0], 0);
map.removeLayer(layer); map.removeLayer(layer);
expect(layer.onRemove.called).not.to.be.ok(); expect(layer.onRemove.called).not.to.be.ok();
}); });
it("does not call layer.onRemove if the map is not ready", function () { it("does not call layer.onRemove if the map is not ready", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }; var layer = layerSpy();
map.addLayer(layer); map.addLayer(layer);
map.removeLayer(layer); map.removeLayer(layer);
expect(layer.onRemove.called).not.to.be.ok(); expect(layer.onRemove.called).not.to.be.ok();
}); });
it("fires a layerremove event if the map is ready", function () { it("fires a layerremove event if the map is ready", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }, var layer = layerSpy(),
spy = sinon.spy(); spy = sinon.spy();
map.on('layerremove', spy); map.on('layerremove', spy);
map.setView([0, 0], 0); map.setView([0, 0], 0);
@ -331,7 +339,7 @@ describe("Map", function () {
}); });
it("does not fire a layerremove if the layer was not added", function () { it("does not fire a layerremove if the layer was not added", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }, var layer = layerSpy(),
spy = sinon.spy(); spy = sinon.spy();
map.on('layerremove', spy); map.on('layerremove', spy);
map.setView([0, 0], 0); map.setView([0, 0], 0);
@ -340,7 +348,7 @@ describe("Map", function () {
}); });
it("does not fire a layerremove if the map is not ready", function () { it("does not fire a layerremove if the map is not ready", function () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }, var layer = layerSpy(),
spy = sinon.spy(); spy = sinon.spy();
map.on('layerremove', spy); map.on('layerremove', spy);
map.addLayer(layer); map.addLayer(layer);
@ -349,7 +357,7 @@ describe("Map", function () {
}); });
it("removes the layer before firing layerremove", function (done) { it("removes the layer before firing layerremove", function (done) {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() }; var layer = layerSpy();
map.on('layerremove', function () { map.on('layerremove', function () {
expect(map.hasLayer(layer)).not.to.be.ok(); expect(map.hasLayer(layer)).not.to.be.ok();
done(); done();

View File

@ -25,19 +25,13 @@ L.Control.Layers = L.Control.extend({
} }
}, },
onAdd: function (map) { onAdd: function () {
this._initLayout(); this._initLayout();
this._update(); this._update();
map.on('layeradd layerremove', this._onLayerChange, this);
return this._container; return this._container;
}, },
onRemove: function (map) {
map.off('layeradd layerremove', this._onLayerChange, this);
},
addBaseLayer: function (layer, name) { addBaseLayer: function (layer, name) {
this._addLayer(layer, name); this._addLayer(layer, name);
return this._update(); return this._update();
@ -49,6 +43,8 @@ L.Control.Layers = L.Control.extend({
}, },
removeLayer: function (layer) { removeLayer: function (layer) {
layer.off('add remove', this._onLayerChange, this);
delete this._layers[L.stamp(layer)]; delete this._layers[L.stamp(layer)];
return this._update(); return this._update();
}, },
@ -108,6 +104,8 @@ L.Control.Layers = L.Control.extend({
}, },
_addLayer: function (layer, name, overlay) { _addLayer: function (layer, name, overlay) {
layer.on('add remove', this._onLayerChange, this);
var id = L.stamp(layer); var id = L.stamp(layer);
this._layers[id] = { this._layers[id] = {
@ -143,20 +141,16 @@ L.Control.Layers = L.Control.extend({
}, },
_onLayerChange: function (e) { _onLayerChange: function (e) {
var obj = this._layers[L.stamp(e.layer)];
if (!obj) { return; }
if (!this._handlingClick) { if (!this._handlingClick) {
this._update(); this._update();
} }
var type = obj.overlay ? var type = e.target.overlay ?
(e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') : (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
(e.type === 'layeradd' ? 'baselayerchange' : null); (e.type === 'add' ? 'baselayerchange' : null);
if (type) { if (type) {
this._map.fire(type, obj); this._map.fire(type, e.target);
} }
}, },

View File

@ -7,13 +7,11 @@ L.Control.Scale = L.Control.extend({
position: 'bottomleft', position: 'bottomleft',
maxWidth: 100, maxWidth: 100,
metric: true, metric: true,
imperial: true, imperial: true
updateWhenIdle: false // updateWhenIdle: false
}, },
onAdd: function (map) { onAdd: function (map) {
this._map = map;
var className = 'leaflet-control-scale', var className = 'leaflet-control-scale',
container = L.DomUtil.create('div', className), container = L.DomUtil.create('div', className),
options = this.options; options = this.options;

View File

@ -16,8 +16,6 @@ L.Control.Zoom = L.Control.extend({
container = L.DomUtil.create('div', zoomName + ' leaflet-bar'), container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
options = this.options; options = this.options;
this._map = map;
this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
zoomName + '-in', container, this._zoomIn, this); zoomName + '-in', container, this._zoomIn, this);
this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,

View File

@ -4,7 +4,6 @@
*/ */
L.FeatureGroup = L.LayerGroup.extend({ L.FeatureGroup = L.LayerGroup.extend({
includes: L.Mixin.Events,
statics: { statics: {
EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose' EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
@ -15,9 +14,7 @@ L.FeatureGroup = L.LayerGroup.extend({
return this; return this;
} }
if ('on' in layer) { layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
}
L.LayerGroup.prototype.addLayer.call(this, layer); L.LayerGroup.prototype.addLayer.call(this, layer);
@ -78,7 +75,7 @@ L.FeatureGroup = L.LayerGroup.extend({
var bounds = new L.LatLngBounds(); var bounds = new L.LatLngBounds();
this.eachLayer(function (layer) { this.eachLayer(function (layer) {
bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds()); bounds.extend((layer.getBounds || layer.getLatLng)());
}); });
return bounds; return bounds;

View File

@ -2,8 +2,7 @@
* L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
*/ */
L.ImageOverlay = L.Class.extend({ L.ImageOverlay = L.Layer.extend({
includes: L.Mixin.Events,
options: { options: {
opacity: 1 opacity: 1
@ -16,30 +15,18 @@ L.ImageOverlay = L.Class.extend({
L.setOptions(this, options); L.setOptions(this, options);
}, },
onAdd: function (map) { onAdd: function () {
this._map = map;
this._animated = this._map.options.zoomAnimation && L.Browser.any3d;
if (!this._image) { if (!this._image) {
this._initImage(); this._initImage();
} }
this._getPane().appendChild(this._image); this.getPane().appendChild(this._image);
map.on(this._getEvents(), this);
this._reset(); this._reset();
}, },
onRemove: function (map) { onRemove: function () {
L.DomUtil.remove(this._image); L.DomUtil.remove(this._image);
map.off(this._getEvents(), this);
},
addTo: function (map) {
map.addLayer(this);
return this;
}, },
setOpacity: function (opacity) { setOpacity: function (opacity) {
@ -51,13 +38,13 @@ L.ImageOverlay = L.Class.extend({
// TODO remove bringToFront/bringToBack duplication from TileLayer/Path // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
bringToFront: function () { bringToFront: function () {
if (this._image) { if (this._image) {
this._getPane().appendChild(this._image); this.getPane().appendChild(this._image);
} }
return this; return this;
}, },
bringToBack: function () { bringToBack: function () {
var pane = this._getPane(); var pane = this.getPane();
if (this._image) { if (this._image) {
pane.insertBefore(this._image, pane.firstChild); pane.insertBefore(this._image, pane.firstChild);
} }
@ -73,14 +60,10 @@ L.ImageOverlay = L.Class.extend({
return this.options.attribution; return this.options.attribution;
}, },
_getPane: function () { getEvents: function () {
return this._map._panes.overlayPane;
},
_getEvents: function () {
var events = {viewreset: this._reset}; var events = {viewreset: this._reset};
if (this._animated) { if (this._zoomAnimated) {
events.zoomanim = this._animateZoom; events.zoomanim = this._animateZoom;
} }
@ -89,8 +72,7 @@ L.ImageOverlay = L.Class.extend({
_initImage: function () { _initImage: function () {
this._image = L.DomUtil.create('img', this._image = L.DomUtil.create('img',
'leaflet-image-layer ' + 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
'leaflet-zoom-' + (this._animated ? 'animated' : 'hide'));
this._updateOpacity(); this._updateOpacity();

145
src/layer/Layer.js Normal file
View File

@ -0,0 +1,145 @@
L.Layer = L.Class.extend({
includes: L.Mixin.Events,
options: {
pane: 'overlayPane'
},
addTo: function (map) {
this._map = map;
var id = L.stamp(this);
if (map._layers[id]) { return this; }
map._layers[id] = this;
this._zoomAnimated = map._zoomAnimated;
if (this.beforeAdd) {
this.beforeAdd(map);
}
map.whenReady(this._layerAdd, this);
return this;
},
_layerAdd: function () {
var map = this._map;
// check in case layer gets added and then removed before the map is ready
if (!map) { return; }
this.onAdd(map);
if (this.getEvents) {
map.on(this.getEvents(), this);
}
this.fire('add');
map.fire('layeradd', {layer: this});
},
removeFrom: function (map) {
var id = L.stamp(this);
if (!map._layers[id]) { return this; }
if (map._loaded) {
this.onRemove(map);
}
if (this.getEvents) {
map.off(this.getEvents(), this);
}
delete map._layers[id];
if (map._loaded) {
map.fire('layerremove', {layer: this});
this.fire('remove');
}
this._map = null;
},
getPane: function (name) {
// TODO make pane if not present
var paneName = name ? this.options[name] || name : this.options.pane;
return this._map._panes[paneName];
}
});
L.Map.addInitHook(function () {
this._layers = {};
this._zoomBoundLayers = {};
this._addLayers(this.options.layers);
});
L.Map.include({
addLayer: function (layer) {
layer.addTo(this);
return this;
},
removeLayer: function (layer) {
layer.removeFrom(this);
return this;
},
hasLayer: function (layer) {
return !layer || L.stamp(layer) in this._layers;
},
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
_addLayers: function (layers) {
layers = layers ? (L.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[L.stamp(layer)] = layer;
this._updateZoomLevels();
}
},
_removeZoomLimit: function (layer) {
var id = L.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;
if (oldZoomSpan !== this._getZoomSpan()) {
this.fire('zoomlevelschange');
}
}
});

View File

@ -3,7 +3,8 @@
* you can manipulate the group (e.g. add/remove it) as one layer. * you can manipulate the group (e.g. add/remove it) as one layer.
*/ */
L.LayerGroup = L.Class.extend({ L.LayerGroup = L.Layer.extend({
initialize: function (layers) { initialize: function (layers) {
this._layers = {}; this._layers = {};
@ -41,9 +42,7 @@ L.LayerGroup = L.Class.extend({
}, },
hasLayer: function (layer) { hasLayer: function (layer) {
if (!layer) { return false; } return !layer || layer in this._layers || this.getLayerId(layer) in this._layers;
return (layer in this._layers || this.getLayerId(layer) in this._layers);
}, },
clearLayers: function () { clearLayers: function () {
@ -67,18 +66,11 @@ L.LayerGroup = L.Class.extend({
}, },
onAdd: function (map) { onAdd: function (map) {
this._map = map;
this.eachLayer(map.addLayer, map); this.eachLayer(map.addLayer, map);
}, },
onRemove: function (map) { onRemove: function (map) {
this.eachLayer(map.removeLayer, map); this.eachLayer(map.removeLayer, map);
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
}, },
eachLayer: function (method, context) { eachLayer: function (method, context) {

View File

@ -6,10 +6,11 @@ L.Map.mergeOptions({
closePopupOnClick: true closePopupOnClick: true
}); });
L.Popup = L.Class.extend({ L.Popup = L.Layer.extend({
includes: L.Mixin.Events,
options: { options: {
pane: 'popupPane',
minWidth: 50, minWidth: 50,
maxWidth: 300, maxWidth: 300,
// maxHeight: <Number>, // maxHeight: <Number>,
@ -21,8 +22,8 @@ L.Popup = L.Class.extend({
// autoPanPaddingBottomRight: <Point>, // autoPanPaddingBottomRight: <Point>,
closeButton: true, closeButton: true,
keepInView: false, // keepInView: false,
className: '', // className: '',
zoomAnimation: true zoomAnimation: true
}, },
@ -30,32 +31,26 @@ L.Popup = L.Class.extend({
L.setOptions(this, options); L.setOptions(this, options);
this._source = source; this._source = source;
this._animated = L.Browser.any3d && this.options.zoomAnimation;
}, },
onAdd: function (map) { onAdd: function (map) {
this._map = map; this._zoomAnimated = this._zoomAnimated && this.options.zoomAnimation;
if (!this._container) { if (!this._container) {
this._initLayout(); this._initLayout();
} }
var animFade = map.options.fadeAnimation; if (map._fadeAnimated) {
if (animFade) {
L.DomUtil.setOpacity(this._container, 0); L.DomUtil.setOpacity(this._container, 0);
} }
this._getPane().appendChild(this._container); this.getPane().appendChild(this._container);
map.on(this._getEvents(), this);
this.update(); this.update();
if (animFade) { if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 1); L.DomUtil.setOpacity(this._container, 1);
} }
this.fire('open');
map.fire('popupopen', {popup: this}); map.fire('popupopen', {popup: this});
if (this._source) { if (this._source) {
@ -63,29 +58,19 @@ L.Popup = L.Class.extend({
} }
}, },
addTo: function (map) {
map.addLayer(this);
return this;
},
openOn: function (map) { openOn: function (map) {
map.openPopup(this); map.openPopup(this);
return this; return this;
}, },
onRemove: function (map) { onRemove: function (map) {
if (map.options.fadeAnimation) { if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0); L.DomUtil.setOpacity(this._container, 0);
setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
} else { } else {
L.DomUtil.remove(this._container); L.DomUtil.remove(this._container);
} }
map.off(this._getEvents(), this);
this._map = null;
this.fire('close');
map.fire('popupclose', {popup: this}); map.fire('popupclose', {popup: this});
if (this._source) { if (this._source) {
@ -130,16 +115,12 @@ L.Popup = L.Class.extend({
this._adjustPan(); this._adjustPan();
}, },
_getPane: function () { getEvents: function () {
return this._map._panes.popupPane;
},
_getEvents: function () {
var events = {viewreset: this._updatePosition}, var events = {viewreset: this._updatePosition},
options = this.options; options = this.options;
if (this._animated) { if (this._zoomAnimated) {
events.zoomanim = this._zoomAnimation; events.zoomanim = this._animateZoom;
} }
if ('closeOnClick' in options ? options.closeOnClick : this._map.options.closePopupOnClick) { if ('closeOnClick' in options ? options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this._close; events.preclick = this._close;
@ -159,7 +140,8 @@ L.Popup = L.Class.extend({
_initLayout: function () { _initLayout: function () {
var prefix = 'leaflet-popup', var prefix = 'leaflet-popup',
container = this._container = L.DomUtil.create('div', container = this._container = L.DomUtil.create('div',
prefix + ' ' + this.options.className + ' leaflet-zoom-' + (this._animated ? 'animated' : 'hide')); prefix + ' ' + (this.options.className || '') +
' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'));
if (this.options.closeButton) { if (this.options.closeButton) {
var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container); var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
@ -233,10 +215,9 @@ L.Popup = L.Class.extend({
if (!this._map) { return; } if (!this._map) { return; }
var pos = this._map.latLngToLayerPoint(this._latlng), var pos = this._map.latLngToLayerPoint(this._latlng),
animated = this._animated,
offset = L.point(this.options.offset); offset = L.point(this.options.offset);
if (animated) { if (this._zoomAnimated) {
L.DomUtil.setPosition(this._container, pos); L.DomUtil.setPosition(this._container, pos);
} else { } else {
offset = offset.add(pos); offset = offset.add(pos);
@ -250,7 +231,7 @@ L.Popup = L.Class.extend({
this._container.style.left = left + 'px'; this._container.style.left = left + 'px';
}, },
_zoomAnimation: function (e) { _animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
L.DomUtil.setPosition(this._container, pos); L.DomUtil.setPosition(this._container, pos);
}, },
@ -263,7 +244,7 @@ L.Popup = L.Class.extend({
containerWidth = this._containerWidth, containerWidth = this._containerWidth,
layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
if (this._animated) { if (this._zoomAnimated) {
layerPos._add(L.DomUtil.getPosition(this._container)); layerPos._add(L.DomUtil.getPosition(this._container));
} }

View File

@ -3,8 +3,8 @@
*/ */
L.Icon = L.Class.extend({ L.Icon = L.Class.extend({
/*
options: { options: {
/*
iconUrl: (String) (required) iconUrl: (String) (required)
iconRetinaUrl: (String) (optional, used for retina devices if detected) iconRetinaUrl: (String) (optional, used for retina devices if detected)
iconSize: (Point) (can be set through CSS) iconSize: (Point) (can be set through CSS)
@ -14,9 +14,9 @@ L.Icon = L.Class.extend({
shadowRetinaUrl: (String) (optional, used for retina devices if detected) shadowRetinaUrl: (String) (optional, used for retina devices if detected)
shadowSize: (Point) shadowSize: (Point)
shadowAnchor: (Point) shadowAnchor: (Point)
*/ className: (String)
className: ''
}, },
*/
initialize: function (options) { initialize: function (options) {
L.setOptions(this, options); L.setOptions(this, options);
@ -52,7 +52,7 @@ L.Icon = L.Class.extend({
anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
size && size.divideBy(2, true)); size && size.divideBy(2, true));
img.className = 'leaflet-marker-' + name + ' ' + options.className; img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
if (anchor) { if (anchor) {
img.style.marginLeft = (-anchor.x) + 'px'; img.style.marginLeft = (-anchor.x) + 'px';

View File

@ -2,11 +2,11 @@
* L.Marker is used to display clickable/draggable icons on the map. * L.Marker is used to display clickable/draggable icons on the map.
*/ */
L.Marker = L.Class.extend({ L.Marker = L.Layer.extend({
includes: L.Mixin.Events,
options: { options: {
pane: 'markerPane',
icon: new L.Icon.Default(), icon: new L.Icon.Default(),
// title: '', // title: '',
// alt: '', // alt: '',
@ -25,42 +25,29 @@ L.Marker = L.Class.extend({
}, },
onAdd: function (map) { onAdd: function (map) {
this._map = map; this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
this._animated = map.options.zoomAnimation && map.options.markerZoomAnimation;
map.on('viewreset', this.update, this);
this._initIcon(); this._initIcon();
this.update(); this.update();
this.fire('add');
if (this._animated) {
map.on('zoomanim', this._animateZoom, this);
}
}, },
addTo: function (map) { onRemove: function () {
map.addLayer(this);
return this;
},
onRemove: function (map) {
if (this.dragging) { if (this.dragging) {
this.dragging.disable(); this.dragging.disable();
} }
this._removeIcon(); this._removeIcon();
this._removeShadow(); this._removeShadow();
},
this.fire('remove'); getEvents: function () {
var events = {viewreset: this.update};
map.off({ if (this._zoomAnimated) {
'viewreset': this.update, events.zoomanim = this._animateZoom;
'zoomanim': this._animateZoom }
}, this);
this._map = null; return events;
}, },
getLatLng: function () { getLatLng: function () {
@ -97,6 +84,7 @@ L.Marker = L.Class.extend({
}, },
update: function () { update: function () {
if (this._icon) { if (this._icon) {
var pos = this._map.latLngToLayerPoint(this._latlng).round(); var pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos); this._setPos(pos);
@ -107,7 +95,7 @@ L.Marker = L.Class.extend({
_initIcon: function () { _initIcon: function () {
var options = this.options, var options = this.options,
classToAdd = 'leaflet-zoom-' + (this._animated ? 'animated' : 'hide'); classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
var icon = options.icon.createIcon(this._icon), var icon = options.icon.createIcon(this._icon),
addIcon = false; addIcon = false;
@ -161,13 +149,11 @@ L.Marker = L.Class.extend({
} }
var panes = this._map._panes;
if (addIcon) { if (addIcon) {
panes.markerPane.appendChild(this._icon); this.getPane().appendChild(this._icon);
} }
if (newShadow && addShadow) { if (newShadow && addShadow) {
panes.shadowPane.appendChild(this._shadow); this.getPane('shadowPane').appendChild(this._shadow);
} }
}, },

View File

@ -2,11 +2,11 @@
* L.GridLayer is used as base class for grid-like layers like TileLayer. * L.GridLayer is used as base class for grid-like layers like TileLayer.
*/ */
L.GridLayer = L.Class.extend({ L.GridLayer = L.Layer.extend({
includes: L.Mixin.Events,
options: { options: {
pane: 'tilePane',
tileSize: 256, tileSize: 256,
opacity: 1, opacity: 1,
@ -27,10 +27,7 @@ L.GridLayer = L.Class.extend({
options = L.setOptions(this, options); options = L.setOptions(this, options);
}, },
onAdd: function (map) { onAdd: function () {
this._map = map;
this._animated = map._zoomAnimated;
this._initContainer(); this._initContainer();
if (!this.options.updateWhenIdle) { if (!this.options.updateWhenIdle) {
@ -38,29 +35,26 @@ L.GridLayer = L.Class.extend({
this._update = L.Util.limitExecByInterval(this._update, this.options.updateInterval, this); this._update = L.Util.limitExecByInterval(this._update, this.options.updateInterval, this);
} }
map.on(this._getEvents(), this);
this._reset(); this._reset();
this._update(); this._update();
}, },
beforeAdd: function (map) {
map._addZoomLimit(this);
},
onRemove: function (map) { onRemove: function (map) {
this._clearBgBuffer(); this._clearBgBuffer();
L.DomUtil.remove(this._container); L.DomUtil.remove(this._container);
map.off(this._getEvents(), this); map._removeZoomLimit(this);
this._container = this._map = null; this._container = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
}, },
bringToFront: function () { bringToFront: function () {
if (this._map) { if (this._map) {
var pane = this._getPane(); var pane = this.getPane();
pane.appendChild(this._container); pane.appendChild(this._container);
this._setAutoZIndex(pane, Math.max); this._setAutoZIndex(pane, Math.max);
} }
@ -69,7 +63,7 @@ L.GridLayer = L.Class.extend({
bringToBack: function () { bringToBack: function () {
if (this._map) { if (this._map) {
var pane = this._getPane(); var pane = this.getPane();
pane.insertBefore(this._container, pane.firstChild); pane.insertBefore(this._container, pane.firstChild);
this._setAutoZIndex(pane, Math.min); this._setAutoZIndex(pane, Math.min);
} }
@ -108,12 +102,7 @@ L.GridLayer = L.Class.extend({
return this; return this;
}, },
_getPane: function () { getEvents: function () {
// TODO pane in options?
return this._map._panes.tilePane;
},
_getEvents: function () {
var events = { var events = {
viewreset: this._reset, viewreset: this._reset,
moveend: this._update moveend: this._update
@ -123,7 +112,7 @@ L.GridLayer = L.Class.extend({
events.move = this._update; events.move = this._update;
} }
if (this._animated) { if (this._zoomAnimated) {
events.zoomanim = this._animateZoom; events.zoomanim = this._animateZoom;
events.zoomend = this._endZoomAnim; events.zoomend = this._endZoomAnim;
} }
@ -177,7 +166,7 @@ L.GridLayer = L.Class.extend({
this._container = L.DomUtil.create('div', 'leaflet-layer'); this._container = L.DomUtil.create('div', 'leaflet-layer');
this._updateZIndex(); this._updateZIndex();
if (this._animated) { if (this._zoomAnimated) {
var className = 'leaflet-tile-container leaflet-zoom-animated'; var className = 'leaflet-tile-container leaflet-zoom-animated';
this._bgBuffer = L.DomUtil.create('div', className, this._container); this._bgBuffer = L.DomUtil.create('div', className, this._container);
@ -191,7 +180,7 @@ L.GridLayer = L.Class.extend({
this._updateOpacity(); this._updateOpacity();
} }
this._getPane().appendChild(this._container); this.getPane().appendChild(this._container);
}, },
_reset: function (e) { _reset: function (e) {
@ -206,7 +195,7 @@ L.GridLayer = L.Class.extend({
this._tileContainer.innerHTML = ''; this._tileContainer.innerHTML = '';
if (this._animated && e && e.hard) { if (this._zoomAnimated && e && e.hard) {
this._clearBgBuffer(); this._clearBgBuffer();
} }
@ -449,7 +438,7 @@ L.GridLayer = L.Class.extend({
_visibleTilesReady: function () { _visibleTilesReady: function () {
this.fire('load'); this.fire('load');
if (this._animated) { if (this._zoomAnimated) {
// clear scaled tiles after all new tiles are loaded (for performance) // clear scaled tiles after all new tiles are loaded (for performance)
clearTimeout(this._clearBgBufferTimer); clearTimeout(this._clearBgBufferTimer);
this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 300); this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 300);

View File

@ -9,7 +9,7 @@ L.TileLayer = L.GridLayer.extend({
maxZoom: 18, maxZoom: 18,
subdomains: 'abc', subdomains: 'abc',
errorTileUrl: '', // errorTileUrl: '',
zoomOffset: 0, zoomOffset: 0,
/* /*

View File

@ -169,11 +169,8 @@ L.Map.include({
var root = this._pathRoot = L.Path.prototype._createElement('svg'); var root = this._pathRoot = L.Path.prototype._createElement('svg');
this._panes.overlayPane.appendChild(root); this._panes.overlayPane.appendChild(root);
var animated = this.options.zoomAnimation && L.Browser.any3d; if (this._zoomAnimated) {
L.DomUtil.addClass(root, 'leaflet-zoom-animated');
L.DomUtil.addClass(root, 'leaflet-zoom-' + (animated ? 'animated' : 'hide'));
if (animated) {
this.on({ this.on({
'zoomanim': this._animatePathZoom, 'zoomanim': this._animatePathZoom,
'zoomend': this._endPathZoom 'zoomend': this._endPathZoom

View File

@ -2,8 +2,7 @@
* L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc. * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
*/ */
L.Path = L.Class.extend({ L.Path = L.Layer.extend({
includes: [L.Mixin.Events],
statics: { statics: {
// how much to extend the clip area around the map view // how much to extend the clip area around the map view
@ -38,9 +37,7 @@ L.Path = L.Class.extend({
L.setOptions(this, options); L.setOptions(this, options);
}, },
onAdd: function (map) { onAdd: function () {
this._map = map;
if (!this._container) { if (!this._container) {
this._initElements(); this._initElements();
@ -55,39 +52,24 @@ L.Path = L.Class.extend({
if (this._container) { if (this._container) {
this._map._pathRoot.appendChild(this._container); this._map._pathRoot.appendChild(this._container);
} }
this.fire('add');
map.on({
viewreset: this.projectLatlngs,
moveend: this._updatePath
}, this);
}, },
addTo: function (map) { onRemove: function () {
map.addLayer(this);
return this;
},
onRemove: function (map) {
L.DomUtil.remove(this._container); L.DomUtil.remove(this._container);
// Need to fire remove event before we set _map to null as the event hooks might need the object
this.fire('remove');
this._map = null;
// TODO move to Path.VML // TODO move to Path.VML
if (L.Browser.vml) { if (L.Browser.vml) {
this._container = null; this._container = null;
this._stroke = null; this._stroke = null;
this._fill = null; this._fill = null;
} }
},
map.off({ getEvents: function () {
return {
viewreset: this.projectLatlngs, viewreset: this.projectLatlngs,
moveend: this._updatePath moveend: this._updatePath
}, this); };
}, },
/* /*

View File

@ -42,8 +42,6 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path :
} }
this._requestUpdate(); this._requestUpdate();
this._map = null;
}, },
_requestUpdate: function () { _requestUpdate: function () {
@ -166,7 +164,7 @@ L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {}
this._panes.overlayPane.appendChild(root); this._panes.overlayPane.appendChild(root);
if (this.options.zoomAnimation) { if (this._zoomAnimated) {
this._pathRoot.className = 'leaflet-zoom-animated'; this._pathRoot.className = 'leaflet-zoom-animated';
this.on('zoomanim', this._animatePathZoom); this.on('zoomanim', this._animatePathZoom);
this.on('zoomend', this._endPathZoom); this.on('zoomend', this._endPathZoom);

View File

@ -15,9 +15,9 @@ L.Map = L.Class.extend({
layers: Array, layers: Array,
*/ */
fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, fadeAnimation: true,
trackResize: true, trackResize: true,
markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d markerZoomAnimation: true
}, },
initialize: function (id, options) { // (HTMLElement or String, Object) initialize: function (id, options) { // (HTMLElement or String, Object)
@ -42,13 +42,7 @@ L.Map = L.Class.extend({
this._handlers = []; this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._tileLayersNum = 0;
this.callInitHooks(); this.callInitHooks();
this._addLayers(options.layers);
}, },
@ -151,78 +145,6 @@ L.Map = L.Class.extend({
return this.panTo(newCenter, options); return this.panTo(newCenter, options);
}, },
addLayer: function (layer) {
// TODO method is too big, refactor
var id = L.stamp(layer);
if (this._layers[id]) { return this; }
this._layers[id] = layer;
// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
this._zoomBoundLayers[id] = layer;
this._updateZoomLevels();
}
// TODO looks ugly, refactor!!!
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum++;
this._tileLayersToLoad++;
layer.on('load', this._onTileLayerLoad, this);
}
if (this._loaded) {
this._layerAdd(layer);
}
return this;
},
removeLayer: function (layer) {
var id = L.stamp(layer);
if (!this._layers[id]) { return this; }
if (this._loaded) {
layer.onRemove(this);
}
delete this._layers[id];
if (this._loaded) {
this.fire('layerremove', {layer: layer});
}
if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id];
this._updateZoomLevels();
}
// TODO looks ugly, refactor
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum--;
this._tileLayersToLoad--;
layer.off('load', this._onTileLayerLoad, this);
}
return this;
},
hasLayer: function (layer) {
if (!layer) { return false; }
return (L.stamp(layer) in this._layers);
},
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
invalidateSize: function (options) { invalidateSize: function (options) {
if (!this._loaded) { return this; } if (!this._loaded) { return this; }
@ -331,9 +253,7 @@ L.Map = L.Class.extend({
}, },
getMinZoom: function () { getMinZoom: function () {
return this.options.minZoom === undefined ? return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
(this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
this.options.minZoom;
}, },
getMaxZoom: function () { getMaxZoom: function () {
@ -490,11 +410,13 @@ L.Map = L.Class.extend({
_initLayout: function () { _initLayout: function () {
var container = this._container; var container = this._container;
this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
L.DomUtil.addClass(container, 'leaflet-container' + L.DomUtil.addClass(container, 'leaflet-container' +
(L.Browser.touch ? ' leaflet-touch' : '') + (L.Browser.touch ? ' leaflet-touch' : '') +
(L.Browser.retina ? ' leaflet-retina' : '') + (L.Browser.retina ? ' leaflet-retina' : '') +
(L.Browser.ielt9 ? ' leaflet-oldie' : '') + (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
(this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
var position = L.DomUtil.getStyle(container, 'position'); var position = L.DomUtil.getStyle(container, 'position');
@ -534,14 +456,6 @@ L.Map = L.Class.extend({
return L.DomUtil.create('div', className, container || this._panes.objectsPane); return L.DomUtil.create('div', className, container || this._panes.objectsPane);
}, },
_addLayers: function (layers) {
layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
},
// private methods that modify map state // private methods that modify map state
@ -568,14 +482,11 @@ L.Map = L.Class.extend({
this._initialTopLeftPoint._add(this._getMapPanePos()); this._initialTopLeftPoint._add(this._getMapPanePos());
} }
this._tileLayersToLoad = this._tileLayersNum;
var loading = !this._loaded; var loading = !this._loaded;
this._loaded = true; this._loaded = true;
if (loading) { if (loading) {
this.fire('load'); this.fire('load');
this.eachLayer(this._layerAdd, this);
} }
this.fire('viewreset', {hard: !preserveMapOffset}); this.fire('viewreset', {hard: !preserveMapOffset});
@ -597,34 +508,6 @@ L.Map = L.Class.extend({
return this.getMaxZoom() - this.getMinZoom(); return this.getMaxZoom() - this.getMinZoom();
}, },
_updateZoomLevels: function () {
var i,
minZoom = Infinity,
maxZoom = -Infinity,
oldZoomSpan = this._getZoomSpan();
for (i in this._zoomBoundLayers) {
var layer = this._zoomBoundLayers[i];
if (!isNaN(layer.options.minZoom)) {
minZoom = Math.min(minZoom, layer.options.minZoom);
}
if (!isNaN(layer.options.maxZoom)) {
maxZoom = Math.max(maxZoom, layer.options.maxZoom);
}
}
if (i === undefined) { // we have no tilelayers
this._layersMaxZoom = this._layersMinZoom = undefined;
} else {
this._layersMaxZoom = maxZoom;
this._layersMinZoom = minZoom;
}
if (oldZoomSpan !== this._getZoomSpan()) {
this.fire('zoomlevelschange');
}
},
_panInsideMaxBounds: function () { _panInsideMaxBounds: function () {
this.panInsideBounds(this.options.maxBounds); this.panInsideBounds(this.options.maxBounds);
}, },
@ -698,13 +581,6 @@ L.Map = L.Class.extend({
}); });
}, },
_onTileLayerLoad: function () {
this._tileLayersToLoad--;
if (this._tileLayersNum && !this._tileLayersToLoad) {
this.fire('tilelayersload');
}
},
_clearHandlers: function () { _clearHandlers: function () {
for (var i = 0, len = this._handlers.length; i < len; i++) { for (var i = 0, len = this._handlers.length; i < len; i++) {
this._handlers[i].disable(); this._handlers[i].disable();
@ -720,11 +596,6 @@ L.Map = L.Class.extend({
return this; return this;
}, },
_layerAdd: function (layer) {
layer.onAdd(this);
this.fire('layeradd', {layer: layer});
},
// private methods for getting map state // private methods for getting map state

View File

@ -7,12 +7,13 @@ L.Map.mergeOptions({
zoomAnimationThreshold: 4 zoomAnimationThreshold: 4
}); });
if (L.DomUtil.TRANSITION) { var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
if (zoomAnimated) {
L.Map.addInitHook(function () { L.Map.addInitHook(function () {
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION && this._zoomAnimated = this.options.zoomAnimation;
L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
// zoom transitions run with the same duration for all layers, so if one of transitionend events // zoom transitions run with the same duration for all layers, so if one of transitionend events
// happens after starting zoom animation (propagating to the map pane), we know that it ended globally // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
@ -22,7 +23,7 @@ if (L.DomUtil.TRANSITION) {
}); });
} }
L.Map.include(!L.DomUtil.TRANSITION ? {} : { L.Map.include(!zoomAnimated ? {} : {
_catchTransitionEnd: function (e) { _catchTransitionEnd: function (e) {
if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {

View File

@ -4,12 +4,12 @@
L.Map.include({ L.Map.include({
_defaultLocateOptions: { _defaultLocateOptions: {
watch: false, timeout: 10000
setView: false, // watch: false
maxZoom: Infinity, // setView: false
timeout: 10000, // maxZoom: <Number>
maximumAge: 0, // maximumAge: 0
enableHighAccuracy: false // enableHighAccuracy: false
}, },
locate: function (/*Object*/ options) { locate: function (/*Object*/ options) {
@ -77,8 +77,8 @@ L.Map.include({
options = this._locateOptions; options = this._locateOptions;
if (options.setView) { if (options.setView) {
var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom); var zoom = this.getBoundsZoom(bounds);
this.setView(latlng, zoom); this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
} }
var data = { var data = {