/*
- Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin
- Leaflet is an open-source JavaScript library for mobile-friendly interactive maps.
- http://leafletjs.com
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin, CloudMade
*/
-(function (window, undefined) {
+(function (window, document, undefined) {/*
+ * The L namespace contains all Leaflet classes and functions.
+ * This code allows you to handle any possible namespace conflicts.
+ */
var L, originalL;
window.L = L;
}
-L.version = '0.4.4';
+L.version = '0.5';
/*
- * L.Util is a namespace for various utility functions.
+ * L.Util contains various utility functions used throughout Leaflet code.
*/
L.Util = {
return obj.options;
},
- getParamString: function (obj) {
+ getParamString: function (obj, existingUrl) {
var params = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
params.push(i + '=' + obj[i]);
}
}
- return '?' + params.join('&');
+ return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
},
template: function (str, data) {
});
},
+ isArray: function (obj) {
+ return (Object.prototype.toString.call(obj) === '[object Array]');
+ },
+
emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
};
/*
- * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
+ * L.Class powers the OOP facilities of the library.
+ * Thanks to John Resig and Dean Edwards for inspiration!
*/
L.Class = function () {};
-L.Class.extend = function (/*Object*/ props) /*-> Class*/ {
+L.Class.extend = function (props) {
// extended class with the new prototype
var NewClass = function () {
+
+ // call the constructor
if (this.initialize) {
this.initialize.apply(this, arguments);
}
+
+ // call all constructor hooks
+ if (this._initHooks) {
+ this.callInitHooks();
+ }
};
// instantiate class without calling constructor
// mix given properties into the prototype
L.extend(proto, props);
+ proto._initHooks = [];
+
+ var parent = this;
+ // add method for calling all hooks
+ proto.callInitHooks = function () {
+
+ if (this._initHooksCalled) { return; }
+
+ if (parent.prototype.callInitHooks) {
+ parent.prototype.callInitHooks.call(this);
+ }
+
+ this._initHooksCalled = true;
+
+ for (var i = 0, len = proto._initHooks.length; i < len; i++) {
+ proto._initHooks[i].call(this);
+ }
+ };
+
return NewClass;
};
L.extend(this.prototype, props);
};
+// merge new default options to the Class
L.Class.mergeOptions = function (options) {
L.extend(this.prototype.options, options);
};
+// add a constructor hook
+L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ var init = typeof fn === 'function' ? fn : function () {
+ this[fn].apply(this, args);
+ };
+
+ this.prototype._initHooks = this.prototype._initHooks || [];
+ this.prototype._initHooks.push(init);
+};
+
/*
- * L.Mixin.Events adds custom events functionality to Leaflet classes
+ * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
*/
var key = '_leaflet_events';
L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
+/*
+ * L.Browser handles different browser and feature detections for internal Leaflet use.
+ */
+
(function () {
var ie = !!window.ActiveXObject,
- // http://tanalin.com/en/articles/ie-version-js/
ie6 = ie && !window.XMLHttpRequest,
ie7 = ie && !document.querySelector,
// terrible browser detection to work around Safari / iOS / Android browser bugs
- // see TileLayer._addTile and debug/hacks/jitter.html
-
ua = navigator.userAgent.toLowerCase(),
- webkit = ua.indexOf("webkit") !== -1,
- chrome = ua.indexOf("chrome") !== -1,
- android = ua.indexOf("android") !== -1,
- android23 = ua.search("android [23]") !== -1,
+ webkit = ua.indexOf('webkit') !== -1,
+ chrome = ua.indexOf('chrome') !== -1,
+ android = ua.indexOf('android') !== -1,
+ android23 = ua.search('android [23]') !== -1,
mobile = typeof orientation !== undefined + '',
- msTouch = (window.navigator && window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints),
- retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
- ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches)),
+ msTouch = window.navigator && window.navigator.msPointerEnabled &&
+ window.navigator.msMaxTouchPoints,
+ retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
+ ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
+ window.matchMedia('(min-resolution:144dpi)').matches),
doc = document.documentElement,
ie3d = ie && ('transition' in doc.style),
L.Browser = {
+ ie: ie,
ie6: ie6,
ie7: ie7,
webkit: webkit,
return Math.sqrt(x * x + y * y);
},
+ equals: function (point) {
+ return point.x === this.x &&
+ point.y === this.y;
+ },
+
toString: function () {
return 'Point(' +
L.Util.formatNum(this.x) + ', ' +
if (x instanceof L.Point) {
return x;
}
- if (x instanceof Array) {
+ if (L.Util.isArray(x)) {
return new L.Point(x[0], x[1]);
}
if (isNaN(x)) {
* L.Bounds represents a rectangular area on the screen in pixel coordinates.
*/
-L.Bounds = L.Class.extend({
-
- initialize: function (a, b) { //(Point, Point) or Point[]
- if (!a) { return; }
+L.Bounds = function (a, b) { //(Point, Point) or Point[]
+ if (!a) { return; }
- var points = b ? [a, b] : a;
+ var points = b ? [a, b] : a;
- for (var i = 0, len = points.length; i < len; i++) {
- this.extend(points[i]);
- }
- },
+ for (var i = 0, len = points.length; i < len; i++) {
+ this.extend(points[i]);
+ }
+};
+L.Bounds.prototype = {
// extend the bounds to contain the given point
extend: function (point) { // (Point)
point = L.point(point);
return new L.Point(this.max.x, this.min.y);
},
+ getSize: function () {
+ return this.max.subtract(this.min);
+ },
+
contains: function (obj) { // (Bounds) or (Point) -> Boolean
var min, max;
isValid: function () {
return !!(this.min && this.max);
}
-});
+};
L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
if (!a || a instanceof L.Bounds) {
* L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
*/
-L.Transformation = L.Class.extend({
- initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
- this._a = a;
- this._b = b;
- this._c = c;
- this._d = d;
- },
+L.Transformation = function (a, b, c, d) {
+ this._a = a;
+ this._b = b;
+ this._c = c;
+ this._d = d;
+};
- transform: function (point, scale) {
+L.Transformation.prototype = {
+ transform: function (point, scale) { // (Point, Number) -> Point
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
- _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+ _transform: function (point, scale) {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
- untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
+ untransform: function (point, scale) {
scale = scale || 1;
return new L.Point(
(point.x / scale - this._b) / this._a,
(point.y / scale - this._d) / this._c);
}
-});
+};
/*
do {
top += el.offsetTop || 0;
left += el.offsetLeft || 0;
+
+ //add borders
+ top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0;
+ left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0;
+
pos = L.DomUtil.getStyle(el, 'position');
if (el.offsetParent === docBody && pos === 'absolute') { break; }
document.selection.empty();
}
if (!this._onselectstart) {
- this._onselectstart = document.onselectstart;
+ this._onselectstart = document.onselectstart || null;
document.onselectstart = L.Util.falseFn;
}
},
L.DomUtil.TRANSFORM = L.DomUtil.testProp(
['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
+
L.DomUtil.TRANSITION = L.DomUtil.testProp(
- ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']);
+ ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
L.DomUtil.TRANSITION_END =
L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
/*
- CM.LatLng represents a geographical point with latitude and longtitude coordinates.
-*/
+ * L.LatLng represents a geographical point with latitude and longitude coordinates.
+ */
-L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean])
+L.LatLng = function (rawLat, rawLng) { // (Number, Number)
var lat = parseFloat(rawLat),
lng = parseFloat(rawLng);
throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
}
- if (noWrap !== true) {
- lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
- lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
- }
-
this.lat = lat;
this.lng = lng;
};
obj = L.latLng(obj);
- var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
+ var margin = Math.max(
+ Math.abs(this.lat - obj.lat),
+ Math.abs(this.lng - obj.lng));
+
return margin <= L.LatLng.MAX_MARGIN;
},
- toString: function (precision) { // -> String
+ toString: function (precision) { // (Number) -> String
return 'LatLng(' +
L.Util.formatNum(this.lat, precision) + ', ' +
L.Util.formatNum(this.lng, precision) + ')';
},
// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
+ // TODO move to projection code, LatLng shouldn't know about Earth
distanceTo: function (other) { // (LatLng) -> Number
other = L.latLng(other);
var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ },
+
+ wrap: function (a, b) { // (Number, Number) -> LatLng
+ var lng = this.lng;
+
+ a = a || -180;
+ b = b || 180;
+
+ lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
+
+ return new L.LatLng(this.lat, lng);
}
};
-L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean)
+L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
if (a instanceof L.LatLng) {
return a;
}
- if (a instanceof Array) {
+ if (L.Util.isArray(a)) {
return new L.LatLng(a[0], a[1]);
}
if (isNaN(a)) {
return a;
}
- return new L.LatLng(a, b, c);
+ return new L.LatLng(a, b);
};
* L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
*/
-L.LatLngBounds = L.Class.extend({
- initialize: function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
- if (!southWest) { return; }
+L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
+ if (!southWest) { return; }
- var latlngs = northEast ? [southWest, northEast] : southWest;
+ var latlngs = northEast ? [southWest, northEast] : southWest;
- for (var i = 0, len = latlngs.length; i < len; i++) {
- this.extend(latlngs[i]);
- }
- },
+ for (var i = 0, len = latlngs.length; i < len; i++) {
+ this.extend(latlngs[i]);
+ }
+};
+L.LatLngBounds.prototype = {
// extend the bounds to contain the given point or bounds
extend: function (obj) { // (LatLng) or (LatLngBounds)
- if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+ if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) {
obj = L.latLng(obj);
} else {
obj = L.latLngBounds(obj);
if (obj instanceof L.LatLng) {
if (!this._southWest && !this._northEast) {
- this._southWest = new L.LatLng(obj.lat, obj.lng, true);
- this._northEast = new L.LatLng(obj.lat, obj.lng, true);
+ this._southWest = new L.LatLng(obj.lat, obj.lng);
+ this._northEast = new L.LatLng(obj.lat, obj.lng);
} else {
this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
},
getNorthWest: function () {
- return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
+ return new L.LatLng(this._northEast.lat, this._southWest.lng);
},
getSouthEast: function () {
- return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
+ return new L.LatLng(this._southWest.lat, this._northEast.lng);
},
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
isValid: function () {
return !!(this._southWest && this._northEast);
}
-});
+};
//TODO International date line?
L.Projection = {};
+/*
+ * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
+ */
L.Projection.SphericalMercator = {
MAX_LATITUDE: 85.0511287798,
lng = point.x * d,
lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
- // TODO refactor LatLng wrapping
- return new L.LatLng(lat, lng, true);
+ return new L.LatLng(lat, lng);
}
};
+/*
+ * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
+ */
L.Projection.LonLat = {
project: function (latlng) {
},
unproject: function (point) {
- return new L.LatLng(point.y, point.x, true);
+ return new L.LatLng(point.y, point.x);
}
};
+/*
+ * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
+ */
L.CRS = {
latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
};
+/*
+ * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
+ */
L.CRS.Simple = L.extend({}, L.CRS, {
projection: L.Projection.LonLat,
- transformation: new L.Transformation(1, 0, 1, 0)
+ transformation: new L.Transformation(1, 0, -1, 0),
+
+ scale: function (zoom) {
+ return Math.pow(2, zoom);
+ }
});
+/*
+ * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
+ * and is used by Leaflet by default.
+ */
L.CRS.EPSG3857 = L.extend({}, L.CRS, {
code: 'EPSG:3857',
});
+/*
+ * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
+ */
L.CRS.EPSG4326 = L.extend({}, L.CRS, {
code: 'EPSG:4326',
this._initContainer(id);
this._initLayout();
- this._initHooks();
+ this.callInitHooks();
this._initEvents();
if (options.maxBounds) {
this._layers[id] = layer;
// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
- if (layer.options && !isNaN(layer.options.maxZoom)) {
- this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
- }
- if (layer.options && !isNaN(layer.options.minZoom)) {
- this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
+ if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
+ this._zoomBoundLayers[id] = layer;
+ this._updateZoomLevels();
}
// TODO looks ugly, refactor!!!
layer.onRemove(this);
delete this._layers[id];
+ if (this._zoomBoundLayers[id]) {
+ delete this._zoomBoundLayers[id];
+ this._updateZoomLevels();
+ }
// TODO looks ugly, refactor
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
_initLayout: function () {
var container = this._container;
- container.innerHTML = '';
L.DomUtil.addClass(container, 'leaflet-container');
if (L.Browser.touch) {
return L.DomUtil.create('div', className, container || this._panes.objectsPane);
},
- _initializers: [],
-
- _initHooks: function () {
- var i, len;
- for (i = 0, len = this._initializers.length; i < len; i++) {
- this._initializers[i].call(this);
- }
- },
-
_initLayers: function (layers) {
- layers = layers ? (layers instanceof Array ? layers : [layers]) : [];
+ layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
this._layers = {};
+ this._zoomBoundLayers = {};
this._tileLayersNum = 0;
var i, len;
L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
},
+ _updateZoomLevels: function () {
+ var i,
+ minZoom = Infinity,
+ maxZoom = -Infinity;
+
+ for (i in this._zoomBoundLayers) {
+ if (this._zoomBoundLayers.hasOwnProperty(i)) {
+ 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;
+ }
+ },
// map events
}
});
-L.Map.addInitHook = function (fn) {
- var args = Array.prototype.slice.call(arguments, 1);
-
- var init = typeof fn === 'function' ? fn : function () {
- this[fn].apply(this, args);
- };
-
- this.prototype._initializers.push(init);
-};
-
L.map = function (id, options) {
return new L.Map(id, options);
};
+/*
+ * Mercator projection that takes into account that the Earth is not a perfect sphere.
+ * Less popular than spherical mercator; used by projections like EPSG:3395.
+ */
L.Projection.Mercator = {
MAX_LATITUDE: 85.0840591556,
phi += dphi;
}
- return new L.LatLng(phi * d, lng, true);
+ return new L.LatLng(phi * d, lng);
}
};
},
onRemove: function (map) {
- map._panes.tilePane.removeChild(this._container);
+ this._container.parentNode.removeChild(this._container);
map.off({
'viewreset': this._resetCallback,
_setAutoZIndex: function (pane, compare) {
- var layers = pane.getElementsByClassName('leaflet-layer'),
- edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min
+ var layers = pane.children,
+ edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
zIndex, i, len;
for (i = 0, len = layers.length; i < len; i++) {
this._initContainer();
},
- _update: function (e) {
+ _update: function () {
if (!this._map) { return; }
return this._createTile();
},
- _resetTile: function (tile) {
- // Override if data stored on a tile needs to be cleaned up before reuse
- },
+ // Override if data stored on a tile needs to be cleaned up before reuse
+ _resetTile: function (/*tile*/) {},
_createTile: function () {
var tile = this._tileImg.cloneNode(false);
}
},
- _tileOnLoad: function (e) {
+ _tileOnLoad: function () {
var layer = this._layer;
//Only if we are loading an actual image
layer._tileLoaded();
},
- _tileOnError: function (e) {
+ _tileOnError: function () {
var layer = this._layer;
layer.fire('tileerror', {
};
+/*
+ * L.TileLayer.WMS is used for putting WMS tile layers on the map.
+ */
+
L.TileLayer.WMS = L.TileLayer.extend({
defaultWmsParams: {
getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String
+ this._adjustTilePoint(tilePoint);
+
var map = this._map,
crs = map.options.crs,
tileSize = this.options.tileSize,
url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
- return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
+ return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox;
},
setParams: function (params, noRedraw) {
};
+/*
+ * L.TileLayer.Canvas is a class that you can use as a base for creating
+ * dynamically drawn Canvas-based tile layers.
+ */
+
L.TileLayer.Canvas = L.TileLayer.extend({
options: {
async: false
}
},
- drawTile: function (tile, tilePoint) {
+ drawTile: function (/*tile, tilePoint*/) {
// override with rendering code
},
};
+/*
+ * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
+ */
+
L.ImageOverlay = L.Class.extend({
includes: L.Mixin.Events,
topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
- currentSize = map.latLngToLayerPoint(se)._subtract(map.latLngToLayerPoint(nw)),
- origin = topLeft._add(size._subtract(currentSize)._divideBy(2));
+ origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
image.style[L.DomUtil.TRANSFORM] =
L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
_reset: function () {
var image = this._image,
topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
- size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
+ size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
L.DomUtil.setPosition(image, topLeft);
};
+/*
+ * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
+ */
+
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)
iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
popupAnchor: (Point) (if not specified, popup opens in the anchor point)
shadowUrl: (Point) (no shadow by default)
+ shadowRetinaUrl: (String) (optional, used for retina devices if detected)
shadowSize: (Point)
shadowAnchor: (Point)
*/
},
_getIconUrl: function (name) {
+ if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
+ return this.options[name + 'RetinaUrl'];
+ }
return this.options[name + 'Url'];
}
});
};
+/*
+ * L.Icon.Default is the blue marker icon used by default in Leaflet.
+ */
L.Icon.Default = L.Icon.extend({
return this.options[key];
}
+ if (L.Browser.retina && name === 'icon') {
+ name += '@2x';
+ }
+
var path = L.Icon.Default.imagePath;
if (!path) {
this.update();
- this.fire('move', { latlng: this._latlng });
+ return this.fire('move', { latlng: this._latlng });
},
setZIndexOffset: function (offset) {
this.options.zIndexOffset = offset;
this.update();
+
+ return this;
},
setIcon: function (icon) {
this._initIcon();
this.update();
}
+
+ return this;
},
update: function () {
- if (!this._icon) { return; }
+ if (this._icon) {
+ var pos = this._map.latLngToLayerPoint(this._latlng).round();
+ this._setPos(pos);
+ }
- var pos = this._map.latLngToLayerPoint(this._latlng).round();
- this._setPos(pos);
+ return this;
},
_initIcon: function () {
},
_initInteraction: function () {
- if (!this.options.clickable) {
- return;
- }
+
+ if (!this.options.clickable) { return; }
+
+ // TODO refactor into something shared with Map/Path/etc. to DRY it up
var icon = this._icon,
- events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
+ events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
L.DomUtil.addClass(icon, 'leaflet-clickable');
L.DomEvent.on(icon, 'click', this._onMouseClick, this);
_onMouseClick: function (e) {
var wasDragged = this.dragging && this.dragging.moved();
+
if (this.hasEventListeners(e.type) || wasDragged) {
L.DomEvent.stopPropagation(e);
}
+
if (wasDragged) { return; }
- if (this._map.dragging && this._map.dragging.moved()) { return; }
+
+ if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
+
this.fire(e.type, {
originalEvent: e
});
},
_fireMouseEvent: function (e) {
+
this.fire(e.type, {
originalEvent: e
});
+
+ // TODO proper custom event propagation
+ // this line will always be called if marker is in a FeatureGroup
+ if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
+ L.DomEvent.preventDefault(e);
+ }
if (e.type !== 'mousedown') {
L.DomEvent.stopPropagation(e);
}
};
+/*
+ * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
+ * to use with L.Marker.
+ */
+
L.DivIcon = L.Icon.extend({
options: {
iconSize: new L.Point(12, 12), // also can be set through CSS
};
+/*
+ * L.Popup is used for displaying popups on the map.
+ */
L.Map.mergeOptions({
closePopupOnClick: true
closeButton: true,
offset: new L.Point(0, 6),
autoPanPadding: new L.Point(5, 5),
- className: ''
+ className: '',
+ zoomAnimation: true
},
initialize: function (options, source) {
L.setOptions(this, options);
this._source = source;
+ this._animated = L.Browser.any3d && this.options.zoomAnimation;
},
onAdd: function (map) {
map.on('viewreset', this._updatePosition, this);
- if (L.Browser.any3d) {
+ if (this._animated) {
map.on('zoomanim', this._zoomAnimation, this);
}
_initLayout: function () {
var prefix = 'leaflet-popup',
- containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-animated',
+ containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
+ (this._animated ? 'animated' : 'hide'),
container = this._container = L.DomUtil.create('div', containerClass),
closeButton;
if (!this._map) { return; }
var pos = this._map.latLngToLayerPoint(this._latlng),
- is3d = L.Browser.any3d,
+ animated = this._animated,
offset = this.options.offset;
- if (is3d) {
+ if (animated) {
L.DomUtil.setPosition(this._container, pos);
}
- this._containerBottom = -offset.y - (is3d ? 0 : pos.y);
- this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x);
+ this._containerBottom = -offset.y - (animated ? 0 : pos.y);
+ this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
//Bottom position the popup in case the height of the popup changes (images loading etc)
this._container.style.bottom = this._containerBottom + 'px';
layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
- if (L.Browser.any3d) {
+ if (this._animated) {
layerPos._add(L.DomUtil.getPosition(this._container));
}
/*
- * Popup extension to L.Marker, adding openPopup & bindPopup methods.
+ * Popup extension to L.Marker, adding popup-related methods.
*/
L.Marker.include({
});
+/*
+ * Adds popup-related methods to L.Map.
+ */
L.Map.include({
openPopup: function (popup) {
/*
- * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
+ * L.LayerGroup is a class to combine several layers into one so that
+ * you can manipulate the group (e.g. add/remove it) as one layer.
*/
L.LayerGroup = L.Class.extend({
method.call(context, this._layers[i]);
}
}
+ },
+
+ setZIndex: function (zIndex) {
+ return this.invoke('setZIndex', zIndex);
}
});
/*
- * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
+ * shared between a group of interactive layers (like vectors or markers).
*/
L.FeatureGroup = L.LayerGroup.extend({
L.LayerGroup.prototype.addLayer.call(this, layer);
if (this._popupContent && layer.bindPopup) {
- layer.bindPopup(this._popupContent);
+ layer.bindPopup(this._popupContent, this._popupOptions);
}
return this.fire('layeradd', {layer: layer});
return this.fire('layerremove', {layer: layer});
},
- bindPopup: function (content) {
+ bindPopup: function (content, options) {
this._popupContent = content;
- return this.invoke('bindPopup', content);
+ this._popupOptions = options;
+ return this.invoke('bindPopup', content, options);
},
setStyle: function (style) {
this._map._pathRoot.appendChild(this._container);
}
+ this.fire('add');
+
map.on({
'viewreset': this.projectLatlngs,
'moveend': this._updatePath
onRemove: function (map) {
map._pathRoot.removeChild(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;
if (L.Browser.vml) {
this._fill = null;
}
- this.fire('remove');
-
map.off({
'viewreset': this.projectLatlngs,
'moveend': this._updatePath
});
+/*
+ * Extends L.Path with SVG-specific rendering code.
+ */
+
L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
}
},
- _animatePathZoom: function (opt) {
- var scale = this.getZoomScale(opt.zoom),
- offset = this._getCenterOffset(opt.center),
- translate = offset.multiplyBy(-scale)._add(this._pathViewport.min);
+ _animatePathZoom: function (e) {
+ var scale = this.getZoomScale(e.zoom),
+ offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
this._pathRoot.style[L.DomUtil.TRANSFORM] =
- L.DomUtil.getTranslateString(translate) + ' scale(' + scale + ') ';
+ L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
this._pathZooming = true;
},
/*
- * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
+ * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
*/
L.Path.include({
if (this._popup) {
this._popup = null;
this
- .off('click', this.openPopup)
+ .off('click', this._openPopup)
.off('remove', this.closePopup);
this._popupHandlersAdded = false;
stroke.weight = options.weight + 'px';
stroke.color = options.color;
stroke.opacity = options.opacity;
+
if (options.dashArray) {
- stroke.dashStyle = options.dashArray.replace(/ *, */g, ' ');
+ stroke.dashStyle = options.dashArray instanceof Array ?
+ options.dashArray.join(' ') :
+ options.dashArray.replace(/ *, */g, ' ');
} else {
stroke.dashStyle = '';
}
+
} else if (stroke) {
container.removeChild(stroke);
this._stroke = null;
}
fill.color = options.fillColor || options.color;
fill.opacity = options.fillOpacity;
+
} else if (fill) {
container.removeChild(fill);
this._fill = null;
.off('viewreset', this.projectLatlngs, this)
.off('moveend', this._updatePath, this);
+ if (this.options.clickable) {
+ this._map.off('click', this._onClick, this);
+ }
+
this._requestUpdate();
this._map = null;
this._updateStyle();
if (options.fill) {
- if (options.fillOpacity < 1) {
- ctx.globalAlpha = options.fillOpacity;
- }
+ ctx.globalAlpha = options.fillOpacity;
ctx.fill();
}
if (options.stroke) {
- if (options.opacity < 1) {
- ctx.globalAlpha = options.opacity;
- }
+ ctx.globalAlpha = options.opacity;
ctx.stroke();
}
_onClick: function (e) {
if (this._containsPoint(e.layerPoint)) {
- this.fire('click', e);
+ this.fire('click', {
+ latlng: e.latlng,
+ layerPoint: e.layerPoint,
+ containerPoint: e.containerPoint,
+ originalEvent: e
+ });
}
}
});
* and polylines (clipping, simplification, distances, etc.)
*/
+/*jshint bitwise:false */ // allow bitwise oprations for this file
+
L.LineUtil = {
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
return reducedPoints;
},
- /*jshint bitwise:false */ // temporarily allow bitwise oprations
-
// Cohen-Sutherland line clipping algorithm.
// Used to avoid rendering parts of a polyline that are not currently visible.
clipSegment: function (a, b, bounds, useLastCode) {
- var min = bounds.min,
- max = bounds.max,
-
- codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
+ var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
codeB = this._getBitCode(b, bounds),
codeOut, p, newCode;
return code;
},
- /*jshint bitwise:true */
-
// square distance (to avoid unnecessary Math.sqrt calls)
_sqDist: function (p1, p2) {
var dx = p2.x - p1.x,
};
+/*
+ * L.Polygon is used to display polylines on a map.
+ */
+
L.Polyline = L.Path.extend({
initialize: function (latlngs, options) {
L.Path.prototype.initialize.call(this, options);
this._latlngs = this._convertLatLngs(latlngs);
-
- // TODO refactor: move to Polyline.Edit.js
- if (L.Handler.PolyEdit) {
- this.editing = new L.Handler.PolyEdit(this);
-
- if (this.options.editable) {
- this.editing.enable();
- }
- }
},
options: {
return this.redraw();
},
- spliceLatLngs: function (index, howMany) {
+ spliceLatLngs: function () { // (Number index, Number howMany)
var removed = [].splice.apply(this._latlngs, arguments);
this._convertLatLngs(this._latlngs);
this.redraw();
return bounds;
},
- // TODO refactor: move to Polyline.Edit.js
- onAdd: function (map) {
- L.Path.prototype.onAdd.call(this, map);
-
- if (this.editing && this.editing.enabled()) {
- this.editing.addHooks();
- }
- },
-
- onRemove: function (map) {
- if (this.editing && this.editing.enabled()) {
- this.editing.removeHooks();
- }
-
- L.Path.prototype.onRemove.call(this, map);
- },
-
_convertLatLngs: function (latlngs) {
var i, len;
for (i = 0, len = latlngs.length; i < len; i++) {
- if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') {
+ if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
return;
}
latlngs[i] = L.latLng(latlngs[i]);
/*
- * L.PolyUtil contains utilify functions for polygons (clipping, etc.).
+ * L.PolyUtil contains utility functions for polygons (clipping, etc.).
*/
-/*jshint bitwise:false */ // allow bitwise oprations here
+/*jshint bitwise:false */ // allow bitwise operations here
L.PolyUtil = {};
* Used to avoid rendering parts of a polygon that are not currently visible.
*/
L.PolyUtil.clipPolygon = function (points, bounds) {
- var min = bounds.min,
- max = bounds.max,
- clippedPoints,
+ var clippedPoints,
edges = [1, 4, 2, 8],
i, j, k,
a, b,
return points;
};
-/*jshint bitwise:true */
-
/*
* L.Polygon is used to display polygons on a map.
initialize: function (latlngs, options) {
L.Polyline.prototype.initialize.call(this, latlngs, options);
- if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) {
+ if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
this._latlngs = this._convertLatLngs(latlngs[0]);
this._holes = latlngs.slice(1);
}
if (!this._holes) { return; }
- var i, j, len, len2, hole;
+ var i, j, len, len2;
for (i = 0, len = this._holes.length; i < len; i++) {
this._holePoints[i] = [];
/*
- * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
*/
L.Rectangle = L.Polygon.extend({
latLngBounds.getSouthWest(),
latLngBounds.getNorthWest(),
latLngBounds.getNorthEast(),
- latLngBounds.getSouthEast(),
- latLngBounds.getSouthWest()
+ latLngBounds.getSouthEast()
];
}
});
projectLatlngs: function () {
var lngRadius = this._getLngRadius(),
- latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true),
+ latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius),
point2 = this._map.latLngToLayerPoint(latlng2);
this._point = this._map.latLngToLayerPoint(this._latlng);
projectLatlngs: function () {
this._point = this._map.latLngToLayerPoint(this._latlng);
},
+
+ _updateStyle : function () {
+ L.Circle.prototype._updateStyle.call(this);
+ this.setRadius(this.options.radius);
+ },
setRadius: function (radius) {
this._radius = radius;
};
+/*
+ * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
+ */
L.Polyline.include(!L.Path.CANVAS ? {} : {
_containsPoint: function (p, closed) {
});
+/*
+ * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
+ */
L.Polygon.include(!L.Path.CANVAS ? {} : {
_containsPoint: function (p) {
/*
- * Circle canvas specific drawing parts.
+ * Extends L.Circle with Canvas-specific code.
*/
L.Circle.include(!L.Path.CANVAS ? {} : {
});
+/*
+ * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
+ */
+
L.GeoJSON = L.FeatureGroup.extend({
+
initialize: function (geojson, options) {
L.setOptions(this, options);
},
addData: function (geojson) {
- var features = geojson instanceof Array ? geojson : geojson.features,
+ var features = L.Util.isArray(geojson) ? geojson : geojson.features,
i, len;
if (features) {
for (i = 0, len = features.length; i < len; i++) {
- this.addData(features[i]);
+ // Only add this if geometry or geometries are set and not null
+ if (features[i].geometries || features[i].geometry) {
+ this.addData(features[i]);
+ }
}
return this;
}
var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer);
layer.feature = geojson;
+ layer.defaultOptions = layer.options;
this.resetStyle(layer);
if (options.onEachFeature) {
resetStyle: function (layer) {
var style = this.options.style;
if (style) {
+ // reset any custom styles
+ L.Util.extend(layer.options, layer.defaultOptions);
+
this._setLayerStyle(layer, style);
}
},
latlngs = this.coordsToLatLngs(coords, 1);
return new L.MultiPolyline(latlngs);
- case "MultiPolygon":
+ case 'MultiPolygon':
latlngs = this.coordsToLatLngs(coords, 2);
return new L.MultiPolygon(latlngs);
- case "GeometryCollection":
+ case 'GeometryCollection':
for (i = 0, len = geometry.geometries.length; i < len; i++) {
- layer = this.geometryToLayer(geometry.geometries[i], pointToLayer);
+ layer = this.geometryToLayer({
+ geometry: geometry.geometries[i],
+ type: 'Feature',
+ properties: geojson.properties
+ }, pointToLayer);
layers.push(layer);
}
return new L.FeatureGroup(layers);
var lat = parseFloat(coords[reverse ? 0 : 1]),
lng = parseFloat(coords[reverse ? 1 : 0]);
- return new L.LatLng(lat, lng, true);
+ return new L.LatLng(lat, lng);
},
coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array
*/
L.DomEvent = {
- /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
+ /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
var id = L.stamp(fn),
if (L.Browser.msTouch && type.indexOf('touch') === 0) {
return this.addMsTouchListener(obj, type, handler, id);
- } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
- return this.addDoubleTapListener(obj, handler, id);
+ }
+ if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+ this.addDoubleTapListener(obj, handler, id);
+ }
- } else if ('addEventListener' in obj) {
+ if ('addEventListener' in obj) {
if (type === 'mousewheel') {
obj.addEventListener('DOMMouseScroll', handler, false);
var stop = L.DomEvent.stopPropagation;
+ for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+ L.DomEvent.addListener(el, L.Draggable.START[i], stop);
+ }
+
return L.DomEvent
- .addListener(el, L.Draggable.START, stop)
.addListener(el, 'click', stop)
.addListener(el, 'dblclick', stop);
},
return (related !== el);
},
- /*jshint noarg:false */
_getEvent: function () { // evil magic for IE
-
+ /*jshint noarg:false */
var e = window.event;
if (!e) {
var caller = arguments.callee.caller;
}
return e;
}
- /*jshint noarg:false */
};
L.DomEvent.on = L.DomEvent.addListener;
includes: L.Mixin.Events,
statics: {
- START: L.Browser.touch ? 'touchstart' : 'mousedown',
- END: L.Browser.touch ? 'touchend' : 'mouseup',
- MOVE: L.Browser.touch ? 'touchmove' : 'mousemove',
+ START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
+ END: {
+ mousedown: 'mouseup',
+ touchstart: 'touchend',
+ MSPointerDown: 'touchend'
+ },
+ MOVE: {
+ mousedown: 'mousemove',
+ touchstart: 'touchmove',
+ MSPointerDown: 'touchmove'
+ },
TAP_TOLERANCE: 15
},
enable: function () {
if (this._enabled) { return; }
- L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this);
+ for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+ L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+ }
this._enabled = true;
},
disable: function () {
if (!this._enabled) { return; }
- L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown);
+ for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+ L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+ }
this._enabled = false;
this._moved = false;
},
}, this), 1000);
}
- L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this);
- L.DomEvent.on(document, L.Draggable.END, this._onUp, this);
+ L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this);
+ L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this);
},
_onMove: function (e) {
this._restoreCursor();
}
- L.DomEvent.off(document, L.Draggable.MOVE, this._onMove);
- L.DomEvent.off(document, L.Draggable.END, this._onUp);
+ for (var i in L.Draggable.MOVE) {
+ if (L.Draggable.MOVE.hasOwnProperty(i)) {
+ L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove);
+ L.DomEvent.off(document, L.Draggable.END[i], this._onUp);
+ }
+ }
if (this._moved) {
// ensure drag is not fired after dragend
/*
- * L.Handler classes are used internally to inject interaction features to classes like Map and Marker.
- */
+ L.Handler is a base class for handler classes that are used internally to inject
+ interaction features like dragging to classes like Map and Marker.
+*/
L.Handler = L.Class.extend({
initialize: function (map) {
/*
- * L.Handler.MapDrag is used internally by L.Map to make the map draggable.
+ * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
*/
L.Map.mergeOptions({
longPress: true,
// TODO refactor, move to CRS
- worldCopyJump: true
+ worldCopyJump: false
});
L.Map.Drag = L.Handler.extend({
},
_onViewReset: function () {
+ // TODO fix hardcoded Earth values
var pxCenter = this._map.getSize()._divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0));
_onPreDrag: function () {
// TODO refactor to be able to adjust map pane position after zoom
- var map = this._map,
- worldWidth = this._worldWidth,
+ var worldWidth = this._worldWidth,
halfWidth = Math.round(worldWidth / 2),
dx = this._initialWorldOffset,
x = this._draggable._newPos.x,
options = map.options,
delay = +new Date() - this._lastTime,
- noInertia = !options.inertia ||
- delay > options.inertiaThreshold ||
- !this._positions[0];
+ noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
if (noInertia) {
map.fire('moveend');
var direction = this._lastPos.subtract(this._positions[0]),
duration = (this._lastTime + delay - this._times[0]) / 1000,
+ ease = options.easeLinearity,
- speedVector = direction.multiplyBy(options.easeLinearity / duration),
+ speedVector = direction.multiplyBy(ease / duration),
speed = speedVector.distanceTo(new L.Point(0, 0)),
limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
- decelerationDuration = limitedSpeed / (options.inertiaDeceleration * options.easeLinearity),
+ decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
L.Util.requestAnimFrame(function () {
- map.panBy(offset, decelerationDuration, options.easeLinearity);
+ map.panBy(offset, decelerationDuration, ease);
});
}
/*
- * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
+ * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
*/
L.Map.mergeOptions({
L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
+
/*
* L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
*/
L.Map.mergeOptions({
- scrollWheelZoom: !L.Browser.touch || L.Browser.msTouch
+ scrollWheelZoom: true
});
L.Map.ScrollWheelZoom = L.Handler.extend({
_performZoom: function () {
var map = this._map,
- delta = Math.round(this._delta),
+ delta = this._delta,
zoom = map.getZoom();
+ delta = delta > 0 ? Math.ceil(delta) : Math.round(delta);
delta = Math.max(Math.min(delta, 4), -4);
delta = map._limitZoom(zoom + delta) - zoom;
L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
+
L.extend(L.DomEvent, {
_touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart',
doubleTap = (delta > 0 && delta <= delay);
last = now;
}
+
function onTouchEnd(e) {
+ /*jshint forin:false */
if (L.Browser.msTouch) {
var idx = trackedTouches.indexOf(e.pointerId);
if (idx === -1) {
//Work around .type being readonly with MSPointer* events
var newTouch = { },
prop;
+
for (var i in touch) {
- if (true) { //Make JSHint happy, we want to copy all properties
- prop = touch[i];
- if (typeof prop === 'function') {
- newTouch[i] = prop.bind(touch);
- } else {
- newTouch[i] = prop;
- }
+ prop = touch[i];
+ if (typeof prop === 'function') {
+ newTouch[i] = prop.bind(touch);
+ } else {
+ newTouch[i] = prop;
}
}
touch = newTouch;
});
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
L.extend(L.DomEvent, {
_msTouches: [],
L.DomUtil.getScaleString(this._scale, this._startCenter);
},
- _onTouchEnd: function (e) {
+ _onTouchEnd: function () {
if (!this._moved || !this._zooming) { return; }
var map = this._map;
/*
- * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
+ * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
+ * (zoom to a selected bounding box), enabled by default.
*/
L.Map.mergeOptions({
.off(document, 'mouseup', this._onMouseUp);
var map = this._map,
- layerPoint = map.mouseEventToLayerPoint(e),
+ layerPoint = map.mouseEventToLayerPoint(e);
+
+ if (this._startLayerPoint.equals(layerPoint)) { return; }
- bounds = new L.LatLngBounds(
+ var bounds = new L.LatLngBounds(
map.layerPointToLatLng(this._startLayerPoint),
map.layerPointToLatLng(layerPoint));
L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
+/*
+ * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
L.Map.mergeOptions({
keyboard: true,
keyboardPanOffset: 80,
L.Map.Keyboard = L.Handler.extend({
- // list of e.keyCode values for particular actions
keyCodes: {
left: [37],
right: [39],
down: [40],
up: [38],
zoomIn: [187, 107, 61],
- zoomOut: [189, 109]
+ zoomOut: [189, 109, 173]
},
initialize: function (map) {
}
L.DomEvent
- .addListener(container, 'focus', this._onFocus, this)
- .addListener(container, 'blur', this._onBlur, this)
- .addListener(container, 'mousedown', this._onMouseDown, this);
+ .on(container, 'focus', this._onFocus, this)
+ .on(container, 'blur', this._onBlur, this)
+ .on(container, 'mousedown', this._onMouseDown, this);
this._map
.on('focus', this._addHooks, this)
var container = this._map._container;
L.DomEvent
- .removeListener(container, 'focus', this._onFocus, this)
- .removeListener(container, 'blur', this._onBlur, this)
- .removeListener(container, 'mousedown', this._onMouseDown, this);
+ .off(container, 'focus', this._onFocus, this)
+ .off(container, 'blur', this._onBlur, this)
+ .off(container, 'mousedown', this._onMouseDown, this);
this._map
.off('focus', this._addHooks, this)
},
_addHooks: function () {
- L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this);
+ L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
},
_removeHooks: function () {
- L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this);
+ L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
},
_onKeyDown: function (e) {
- var key = e.keyCode;
+ var key = e.keyCode,
+ map = this._map;
if (this._panKeys.hasOwnProperty(key)) {
- this._map.panBy(this._panKeys[key]);
+ map.panBy(this._panKeys[key]);
+
+ if (map.options.maxBounds) {
+ map.panInsideBounds(map.options.maxBounds);
+ }
} else if (this._zoomKeys.hasOwnProperty(key)) {
- this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]);
+ map.setZoom(map.getZoom() + this._zoomKeys[key]);
} else {
return;
return this._draggable && this._draggable._moved;
},
- _onDragStart: function (e) {
+ _onDragStart: function () {
this._marker
.closePopup()
.fire('movestart')
.fire('dragstart');
},
- _onDrag: function (e) {
+ _onDrag: function () {
var marker = this._marker,
shadow = marker._shadow,
iconPos = L.DomUtil.getPosition(marker._icon),
});
+/*
+ * L.Handler.PolyEdit is an editing handler for polylines and polygons.
+ */
+
L.Handler.PolyEdit = L.Handler.extend({
options: {
icon: new L.DivIcon({
}
});
+L.Polyline.addInitHook(function () {
+
+ if (L.Handler.PolyEdit) {
+ this.editing = new L.Handler.PolyEdit(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+ this.on('add', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.addHooks();
+ }
+ });
+
+ this.on('remove', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.removeHooks();
+ }
+ });
+});
+
+
+/*
+ * L.Control is a base class for implementing map controls. Handles positioning.
+ * All other controls extend from this class.
+ */
L.Control = L.Class.extend({
options: {
};
+/*
+ * Adds control-related methods to L.Map.
+ */
+
L.Map.include({
addControl: function (control) {
control.addTo(this);
});
+/*
+ * L.Control.Zoom is used for the default zoom buttons on the map.
+ */
+
L.Control.Zoom = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
- var className = 'leaflet-control-zoom',
- container = L.DomUtil.create('div', className);
+ var zoomName = 'leaflet-control-zoom',
+ barName = 'leaflet-bar',
+ partName = barName + '-part',
+ container = L.DomUtil.create('div', zoomName + ' ' + barName);
this._map = map;
- this._createButton('Zoom in', className + '-in', container, this._zoomIn, this);
- this._createButton('Zoom out', className + '-out', container, this._zoomOut, this);
+ this._zoomInButton = this._createButton('+', 'Zoom in',
+ zoomName + '-in ' +
+ partName + ' ' +
+ partName + '-top',
+ container, this._zoomIn, this);
+
+ this._zoomOutButton = this._createButton('-', 'Zoom out',
+ zoomName + '-out ' +
+ partName + ' ' +
+ partName + '-bottom',
+ container, this._zoomOut, this);
+
+ map.on('zoomend', this._updateDisabled, this);
return container;
},
+ onRemove: function (map) {
+ map.off('zoomend', this._updateDisabled, this);
+ },
+
_zoomIn: function (e) {
this._map.zoomIn(e.shiftKey ? 3 : 1);
},
this._map.zoomOut(e.shiftKey ? 3 : 1);
},
- _createButton: function (title, className, container, fn, context) {
+ _createButton: function (html, title, className, container, fn, context) {
var link = L.DomUtil.create('a', className, container);
+ link.innerHTML = html;
link.href = '#';
link.title = title;
+ var stop = L.DomEvent.stopPropagation;
+
L.DomEvent
- .on(link, 'click', L.DomEvent.stopPropagation)
- .on(link, 'mousedown', L.DomEvent.stopPropagation)
- .on(link, 'dblclick', L.DomEvent.stopPropagation)
+ .on(link, 'click', stop)
+ .on(link, 'mousedown', stop)
+ .on(link, 'dblclick', stop)
.on(link, 'click', L.DomEvent.preventDefault)
.on(link, 'click', fn, context);
return link;
+ },
+
+ _updateDisabled: function () {
+ var map = this._map,
+ className = 'leaflet-control-zoom-disabled';
+
+ L.DomUtil.removeClass(this._zoomInButton, className);
+ L.DomUtil.removeClass(this._zoomOutButton, className);
+
+ if (map._zoom === map.getMinZoom()) {
+ L.DomUtil.addClass(this._zoomOutButton, className);
+ }
+ if (map._zoom === map.getMaxZoom()) {
+ L.DomUtil.addClass(this._zoomInButton, className);
+ }
}
});
+/*
+ * L.Control.Attribution is used for displaying attribution on the map (added by default).
+ */
+
L.Control.Attribution = L.Control.extend({
options: {
position: 'bottomright',
};
+/*
+ * L.Control.Scale is used for displaying metric/imperial scale on the map.
+ */
+
L.Control.Scale = L.Control.extend({
options: {
position: 'bottomleft',
};
+/*
+ * L.Control.Layers is a control to allow users to switch between different layers on the map.
+ */
+
L.Control.Layers = L.Control.extend({
options: {
collapsed: true,
this._update();
map
- .on('layeradd', this._update, this)
- .on('layerremove', this._update, this);
+ .on('layeradd', this._onLayerChange, this)
+ .on('layerremove', this._onLayerChange, this);
return this._container;
},
onRemove: function (map) {
map
- .off('layeradd', this._update)
- .off('layerremove', this._update);
+ .off('layeradd', this._onLayerChange)
+ .off('layerremove', this._onLayerChange);
},
addBaseLayer: function (layer, name) {
if (!L.Browser.touch) {
L.DomEvent.disableClickPropagation(container);
+ L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation);
} else {
L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
}
},
_update: function () {
- if (!this._container || this._handlingClick) {
+ if (!this._container) {
return;
}
this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none');
},
+ _onLayerChange: function (e) {
+ var id = L.stamp(e.layer);
+
+ if (this._layers[id] && !this._handlingClick) {
+ this._update();
+ }
+ },
+
// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
_createRadioElement: function (name, checked) {
var container = obj.overlay ? this._overlaysList : this._baseLayersList;
container.appendChild(label);
+
+ return label;
},
_onInputClick: function () {
}
if (baseLayer) {
+ this._map.setZoom(this._map.getZoom());
this._map.fire('baselayerchange', {layer: baseLayer});
}
// toggle reflow, Chrome flickers for some reason if you don't do this
L.Util.falseFn(el.offsetWidth);
- // there's no native way to track value updates of tranisitioned properties, so we imitate this
+ // there's no native way to track value updates of transitioned properties, so we imitate this
this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50);
},
});
+/*
+ * Extends L.Map to handle panning animations.
+ */
L.Map.include({
L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
- var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset);
+ var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round();
this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity);
return this;
});
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
L.Map.mergeOptions({
zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera
});
return true;
},
- _catchTransitionEnd: function (e) {
+ _catchTransitionEnd: function () {
if (this._animatingZoom) {
this._onZoomTransitionEnd();
}
_onZoomTransitionEnd: function () {
this._restoreTileFront();
- L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
- this._resetView(this._animateToCenter, this._animateToZoom, true, true);
L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+ L.Util.falseFn(this._tileBg.offsetWidth); // force reflow
this._animatingZoom = false;
+ this._resetView(this._animateToCenter, this._animateToZoom, true, true);
if (L.Draggable) {
L.Draggable._disabled = false;
/*
- * Provides L.Map with convenient shortcuts for W3C geolocation.
+ * Provides L.Map with convenient shortcuts for using browser geolocation features.
*/
L.Map.include({
});
-
-
-}(this));
+}(this, document));
\ No newline at end of file