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.
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 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

View File

@ -17,7 +17,9 @@ var deps = {
'geo/crs/CRS.Simple.js',
'geo/crs/CRS.EPSG3857.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.'
},

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 () {
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.addLayer(layer);
expect(layer.onAdd.called).to.be.ok();
});
it("calls layer.onAdd when the map becomes ready", function () {
var layer = { onAdd: sinon.spy() };
var layer = layerSpy();
map.addLayer(layer);
expect(layer.onAdd.called).not.to.be.ok();
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 () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
var layer = layerSpy();
map.addLayer(layer);
map.removeLayer(layer);
map.setView([0, 0], 0);
@ -224,7 +232,7 @@ describe("Map", function () {
});
it("fires a layeradd event immediately if the map is ready", function () {
var layer = { onAdd: sinon.spy() },
var layer = layerSpy(),
spy = sinon.spy();
map.on('layeradd', spy);
map.setView([0, 0], 0);
@ -233,7 +241,7 @@ describe("Map", function () {
});
it("fires a layeradd event when the map becomes ready", function () {
var layer = { onAdd: sinon.spy() },
var layer = layerSpy(),
spy = sinon.spy();
map.on('layeradd', spy);
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 () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
var layer = layerSpy(),
spy = sinon.spy();
map.on('layeradd', spy);
map.addLayer(layer);
@ -253,7 +261,7 @@ describe("Map", function () {
});
it("adds the layer before firing layeradd", function (done) {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
var layer = layerSpy();
map.on('layeradd', function () {
expect(map.hasLayer(layer)).to.be.ok();
done();
@ -299,7 +307,7 @@ describe("Map", function () {
describe("#removeLayer", 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.addLayer(layer);
map.removeLayer(layer);
@ -307,21 +315,21 @@ describe("Map", 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.removeLayer(layer);
expect(layer.onRemove.called).not.to.be.ok();
});
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.removeLayer(layer);
expect(layer.onRemove.called).not.to.be.ok();
});
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();
map.on('layerremove', spy);
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 () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
var layer = layerSpy(),
spy = sinon.spy();
map.on('layerremove', spy);
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 () {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() },
var layer = layerSpy(),
spy = sinon.spy();
map.on('layerremove', spy);
map.addLayer(layer);
@ -349,7 +357,7 @@ describe("Map", function () {
});
it("removes the layer before firing layerremove", function (done) {
var layer = { onAdd: sinon.spy(), onRemove: sinon.spy() };
var layer = layerSpy();
map.on('layerremove', function () {
expect(map.hasLayer(layer)).not.to.be.ok();
done();

View File

@ -25,19 +25,13 @@ L.Control.Layers = L.Control.extend({
}
},
onAdd: function (map) {
onAdd: function () {
this._initLayout();
this._update();
map.on('layeradd layerremove', this._onLayerChange, this);
return this._container;
},
onRemove: function (map) {
map.off('layeradd layerremove', this._onLayerChange, this);
},
addBaseLayer: function (layer, name) {
this._addLayer(layer, name);
return this._update();
@ -49,6 +43,8 @@ L.Control.Layers = L.Control.extend({
},
removeLayer: function (layer) {
layer.off('add remove', this._onLayerChange, this);
delete this._layers[L.stamp(layer)];
return this._update();
},
@ -108,6 +104,8 @@ L.Control.Layers = L.Control.extend({
},
_addLayer: function (layer, name, overlay) {
layer.on('add remove', this._onLayerChange, this);
var id = L.stamp(layer);
this._layers[id] = {
@ -143,20 +141,16 @@ L.Control.Layers = L.Control.extend({
},
_onLayerChange: function (e) {
var obj = this._layers[L.stamp(e.layer)];
if (!obj) { return; }
if (!this._handlingClick) {
this._update();
}
var type = obj.overlay ?
(e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
(e.type === 'layeradd' ? 'baselayerchange' : null);
var type = e.target.overlay ?
(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
(e.type === 'add' ? 'baselayerchange' : null);
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',
maxWidth: 100,
metric: true,
imperial: true,
updateWhenIdle: false
imperial: true
// updateWhenIdle: false
},
onAdd: function (map) {
this._map = map;
var className = 'leaflet-control-scale',
container = L.DomUtil.create('div', className),
options = this.options;

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@
*/
L.Icon = L.Class.extend({
/*
options: {
/*
iconUrl: (String) (required)
iconRetinaUrl: (String) (optional, used for retina devices if detected)
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)
shadowSize: (Point)
shadowAnchor: (Point)
*/
className: ''
className: (String)
},
*/
initialize: function (options) {
L.setOptions(this, options);
@ -52,7 +52,7 @@ L.Icon = L.Class.extend({
anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
size && size.divideBy(2, true));
img.className = 'leaflet-marker-' + name + ' ' + options.className;
img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
if (anchor) {
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 = L.Class.extend({
includes: L.Mixin.Events,
L.Marker = L.Layer.extend({
options: {
pane: 'markerPane',
icon: new L.Icon.Default(),
// title: '',
// alt: '',
@ -25,42 +25,29 @@ L.Marker = L.Class.extend({
},
onAdd: function (map) {
this._map = map;
this._animated = map.options.zoomAnimation && map.options.markerZoomAnimation;
map.on('viewreset', this.update, this);
this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
this._initIcon();
this.update();
this.fire('add');
if (this._animated) {
map.on('zoomanim', this._animateZoom, this);
}
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
onRemove: function () {
if (this.dragging) {
this.dragging.disable();
}
this._removeIcon();
this._removeShadow();
},
this.fire('remove');
getEvents: function () {
var events = {viewreset: this.update};
map.off({
'viewreset': this.update,
'zoomanim': this._animateZoom
}, this);
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
this._map = null;
return events;
},
getLatLng: function () {
@ -97,6 +84,7 @@ L.Marker = L.Class.extend({
},
update: function () {
if (this._icon) {
var pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos);
@ -107,7 +95,7 @@ L.Marker = L.Class.extend({
_initIcon: function () {
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),
addIcon = false;
@ -161,13 +149,11 @@ L.Marker = L.Class.extend({
}
var panes = this._map._panes;
if (addIcon) {
panes.markerPane.appendChild(this._icon);
this.getPane().appendChild(this._icon);
}
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 = L.Class.extend({
includes: L.Mixin.Events,
L.GridLayer = L.Layer.extend({
options: {
pane: 'tilePane',
tileSize: 256,
opacity: 1,
@ -27,10 +27,7 @@ L.GridLayer = L.Class.extend({
options = L.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
this._animated = map._zoomAnimated;
onAdd: function () {
this._initContainer();
if (!this.options.updateWhenIdle) {
@ -38,29 +35,26 @@ L.GridLayer = L.Class.extend({
this._update = L.Util.limitExecByInterval(this._update, this.options.updateInterval, this);
}
map.on(this._getEvents(), this);
this._reset();
this._update();
},
beforeAdd: function (map) {
map._addZoomLimit(this);
},
onRemove: function (map) {
this._clearBgBuffer();
L.DomUtil.remove(this._container);
map.off(this._getEvents(), this);
map._removeZoomLimit(this);
this._container = this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
this._container = null;
},
bringToFront: function () {
if (this._map) {
var pane = this._getPane();
var pane = this.getPane();
pane.appendChild(this._container);
this._setAutoZIndex(pane, Math.max);
}
@ -69,7 +63,7 @@ L.GridLayer = L.Class.extend({
bringToBack: function () {
if (this._map) {
var pane = this._getPane();
var pane = this.getPane();
pane.insertBefore(this._container, pane.firstChild);
this._setAutoZIndex(pane, Math.min);
}
@ -108,12 +102,7 @@ L.GridLayer = L.Class.extend({
return this;
},
_getPane: function () {
// TODO pane in options?
return this._map._panes.tilePane;
},
_getEvents: function () {
getEvents: function () {
var events = {
viewreset: this._reset,
moveend: this._update
@ -123,7 +112,7 @@ L.GridLayer = L.Class.extend({
events.move = this._update;
}
if (this._animated) {
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
events.zoomend = this._endZoomAnim;
}
@ -177,7 +166,7 @@ L.GridLayer = L.Class.extend({
this._container = L.DomUtil.create('div', 'leaflet-layer');
this._updateZIndex();
if (this._animated) {
if (this._zoomAnimated) {
var className = 'leaflet-tile-container leaflet-zoom-animated';
this._bgBuffer = L.DomUtil.create('div', className, this._container);
@ -191,7 +180,7 @@ L.GridLayer = L.Class.extend({
this._updateOpacity();
}
this._getPane().appendChild(this._container);
this.getPane().appendChild(this._container);
},
_reset: function (e) {
@ -206,7 +195,7 @@ L.GridLayer = L.Class.extend({
this._tileContainer.innerHTML = '';
if (this._animated && e && e.hard) {
if (this._zoomAnimated && e && e.hard) {
this._clearBgBuffer();
}
@ -449,7 +438,7 @@ L.GridLayer = L.Class.extend({
_visibleTilesReady: function () {
this.fire('load');
if (this._animated) {
if (this._zoomAnimated) {
// clear scaled tiles after all new tiles are loaded (for performance)
clearTimeout(this._clearBgBufferTimer);
this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 300);

View File

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

View File

@ -169,11 +169,8 @@ L.Map.include({
var root = this._pathRoot = L.Path.prototype._createElement('svg');
this._panes.overlayPane.appendChild(root);
var animated = this.options.zoomAnimation && L.Browser.any3d;
L.DomUtil.addClass(root, 'leaflet-zoom-' + (animated ? 'animated' : 'hide'));
if (animated) {
if (this._zoomAnimated) {
L.DomUtil.addClass(root, 'leaflet-zoom-animated');
this.on({
'zoomanim': this._animatePathZoom,
'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 = L.Class.extend({
includes: [L.Mixin.Events],
L.Path = L.Layer.extend({
statics: {
// how much to extend the clip area around the map view
@ -38,9 +37,7 @@ L.Path = L.Class.extend({
L.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
onAdd: function () {
if (!this._container) {
this._initElements();
@ -55,39 +52,24 @@ L.Path = L.Class.extend({
if (this._container) {
this._map._pathRoot.appendChild(this._container);
}
this.fire('add');
map.on({
viewreset: this.projectLatlngs,
moveend: this._updatePath
}, this);
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onRemove: function (map) {
onRemove: function () {
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
if (L.Browser.vml) {
this._container = null;
this._stroke = null;
this._fill = null;
}
},
map.off({
getEvents: function () {
return {
viewreset: this.projectLatlngs,
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._map = null;
},
_requestUpdate: function () {
@ -166,7 +164,7 @@ L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {}
this._panes.overlayPane.appendChild(root);
if (this.options.zoomAnimation) {
if (this._zoomAnimated) {
this._pathRoot.className = 'leaflet-zoom-animated';
this.on('zoomanim', this._animatePathZoom);
this.on('zoomend', this._endPathZoom);

View File

@ -15,9 +15,9 @@ L.Map = L.Class.extend({
layers: Array,
*/
fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
fadeAnimation: true,
trackResize: true,
markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
markerZoomAnimation: true
},
initialize: function (id, options) { // (HTMLElement or String, Object)
@ -42,13 +42,7 @@ L.Map = L.Class.extend({
this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._tileLayersNum = 0;
this.callInitHooks();
this._addLayers(options.layers);
},
@ -151,78 +145,6 @@ L.Map = L.Class.extend({
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) {
if (!this._loaded) { return this; }
@ -331,9 +253,7 @@ L.Map = L.Class.extend({
},
getMinZoom: function () {
return this.options.minZoom === undefined ?
(this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
this.options.minZoom;
return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
},
getMaxZoom: function () {
@ -490,11 +410,13 @@ L.Map = L.Class.extend({
_initLayout: function () {
var container = this._container;
this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
L.DomUtil.addClass(container, 'leaflet-container' +
(L.Browser.touch ? ' leaflet-touch' : '') +
(L.Browser.retina ? ' leaflet-retina' : '') +
(L.Browser.ielt9 ? ' leaflet-oldie' : '') +
(this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
(this._fadeAnimated ? ' leaflet-fade-anim' : ''));
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);
},
_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
@ -568,14 +482,11 @@ L.Map = L.Class.extend({
this._initialTopLeftPoint._add(this._getMapPanePos());
}
this._tileLayersToLoad = this._tileLayersNum;
var loading = !this._loaded;
this._loaded = true;
if (loading) {
this.fire('load');
this.eachLayer(this._layerAdd, this);
}
this.fire('viewreset', {hard: !preserveMapOffset});
@ -597,34 +508,6 @@ L.Map = L.Class.extend({
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 () {
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 () {
for (var i = 0, len = this._handlers.length; i < len; i++) {
this._handlers[i].disable();
@ -720,11 +596,6 @@ L.Map = L.Class.extend({
return this;
},
_layerAdd: function (layer) {
layer.onAdd(this);
this.fire('layeradd', {layer: layer});
},
// private methods for getting map state

View File

@ -7,12 +7,13 @@ L.Map.mergeOptions({
zoomAnimationThreshold: 4
});
if (L.DomUtil.TRANSITION) {
var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
if (zoomAnimated) {
L.Map.addInitHook(function () {
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
this._zoomAnimated = this.options.zoomAnimation;
// 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
@ -22,7 +23,7 @@ if (L.DomUtil.TRANSITION) {
});
}
L.Map.include(!L.DomUtil.TRANSITION ? {} : {
L.Map.include(!zoomAnimated ? {} : {
_catchTransitionEnd: function (e) {
if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {

View File

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