/* @preserve
- * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com
+ * Leaflet 1.3.0, a JS library for interactive maps. http://leafletjs.com
* (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
+
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.L = {})));
}(this, (function (exports) { 'use strict';
-var version = "1.2.0";
+var version = "1.3.0";
/*
* @namespace Util
var lastId = 0;
// @function stamp(obj: Object): Number
-// Returns the unique ID of an object, assiging it one if it doesn't have it.
+// Returns the unique ID of an object, assigning it one if it doesn't have it.
function stamp(obj) {
/*eslint-disable */
obj._leaflet_id = obj._leaflet_id || ++lastId;
return obj._leaflet_id;
- /*eslint-enable */
+ /* eslint-enable */
}
// @function throttle(fn: Function, time: Number, context: Object): Function
function falseFn() { return false; }
// @function formatNum(num: Number, digits?: Number): Number
-// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
+// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
function formatNum(num, digits) {
- var pow = Math.pow(10, digits || 5);
+ var pow = Math.pow(10, (digits === undefined ? 6 : digits));
return Math.round(num * pow) / pow;
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
}
-var templateRe = /\{ *([\w_\-]+) *\}/g;
+var templateRe = /\{ *([\w_-]+) *\}/g;
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
};
function checkDeprecatedMixinEvents(includes) {
- if (!L || !L.Mixin) { return; }
+ if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
includes = isArray(includes) ? includes : [includes];
fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; }
- var event = extend({}, data, {type: type, target: this});
+ var event = extend({}, data, {
+ type: type,
+ target: this,
+ sourceTarget: data && data.sourceTarget || this
+ });
if (this._events) {
var listeners = this._events[type];
_propagateEvent: function (e) {
for (var id in this._eventParents) {
- this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
+ this._eventParents[id].fire(e.type, extend({
+ layer: e.target,
+ propagatedFrom: e.target
+ }, e), true);
}
}
};
* map.panBy([200, 300]);
* map.panBy(L.point(200, 300));
* ```
+ *
+ * Note that `Point` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function Point(x, y, round) {
this.y = (round ? Math.round(y) : y);
}
+var trunc = Math.trunc || function (v) {
+ return v > 0 ? Math.floor(v) : Math.ceil(v);
+};
+
Point.prototype = {
// @method clone(): Point
return this;
},
+ // @method trunc(): Point
+ // Returns a copy of the current point with truncated coordinates (rounded towards zero).
+ trunc: function () {
+ return this.clone()._trunc();
+ },
+
+ _trunc: function () {
+ this.x = trunc(this.x);
+ this.y = trunc(this.y);
+ return this;
+ },
+
// @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points.
distanceTo: function (point) {
* ```js
* otherBounds.intersects([[10, 10], [40, 60]]);
* ```
+ *
+ * Note that `Bounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function Bounds(a, b) {
* ```
*
* Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
+ *
+ * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
},
// @method pad(bufferRatio: Number): LatLngBounds
- // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
+ // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
+ // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
+ // Negative values will retract the bounds.
pad: function (bufferRatio) {
var sw = this._southWest,
ne = this._northEast,
},
// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
- // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
+ // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
equals: function (bounds, maxMargin) {
if (!bounds) { return false; }
* map.panTo({lat: 50, lng: 30});
* map.panTo(L.latLng(50, 30));
* ```
+ *
+ * Note that `LatLng` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
*/
function LatLng(lat, lng, alt) {
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
- // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
+ // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
equals: function (obj, maxMargin) {
if (!obj) { return false; }
},
// @method distanceTo(otherLatLng: LatLng): Number
- // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
+ // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
distanceTo: function (other) {
return Earth.distance(this, toLatLng(other));
},
* Leaflet defines the most usual CRSs by default. If you want to use a
* CRS not defined by default, take a look at the
* [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
+ *
+ * Note that the CRS instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
*/
var CRS = {
var rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
- a = Math.sin(lat1) * Math.sin(lat2) +
- Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
-
- return this.R * Math.acos(Math.min(a, 1));
+ sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
+ sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
+ a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ return this.R * c;
}
});
sin = Math.sin(lat * d);
return new Point(
- this.R * latlng.lng * d,
- this.R * Math.log((1 + sin) / (1 - sin)) / 2);
+ this.R * latlng.lng * d,
+ this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject: function (point) {
// @alternative
// @factory L.transformation(coefficients: Array): Transformation
-// Expects an coeficients array of the form
+// Expects an coefficients array of the form
// `[a: Number, b: Number, c: Number, d: Number]`.
function toTransformation(a, b, c, d) {
// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
var android23 = userAgentContains('android 2') || userAgentContains('android 3');
+/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
+var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
+// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
+var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
+
// @property opera: Boolean; `true` for the Opera browser
var opera = !!window.opera;
webkit: webkit,
android: android,
android23: android23,
+ androidStock: androidStock,
opera: opera,
chrome: chrome,
gecko: gecko,
var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
+
var _pointers = {};
var _pointerDocListener = false;
function _addPointerStart(obj, handler, id) {
var onDown = bind(function (e) {
- if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+ if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
// In IE11, some touch events needs to fire for form controls, or
// the controls will stop working. We keep a whitelist of tag names that
// need these events. For other target tags, we prevent default on the event.
var eventsKey = '_leaflet_events';
// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Removes a previously added listener function. If no function is specified,
-// it will remove all the listeners of that particular DOM event from the element.
+// Removes a previously added listener function.
// Note that if you passed a custom context to on, you must pass the same
// context to `off` in order to remove the listener.
// @alternative
// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-
-// @alternative
-// @function off(el: HTMLElement): this
-// Removes all known event listeners
function off(obj, types, fn, context) {
if (typeof types === 'object') {
if (pointer && type.indexOf('touch') === 0) {
removePointerListener(obj, type, id);
- } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
+ } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
+ !(pointer && chrome)) {
removeDoubleTapListener(obj, id);
} else if ('removeEventListener' in obj) {
return this;
}
-// @function stop(ev): this
+// @function stop(ev: DOMEvent): this
// Does `stopPropagation` and `preventDefault` at the same time.
function stop(e) {
preventDefault(e);
var rect = container.getBoundingClientRect();
+ var scaleX = rect.width / container.offsetWidth || 1;
+ var scaleY = rect.height / container.offsetHeight || 1;
return new Point(
- e.clientX - rect.left - container.clientLeft,
- e.clientY - rect.top - container.clientTop);
+ e.clientX / scaleX - rect.left - container.clientLeft,
+ e.clientY / scaleY - rect.top - container.clientTop);
}
// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
/*eslint-disable */
el._leaflet_pos = point;
- /*eslint-enable */
+ /* eslint-enable */
if (any3d) {
setTransform(el, point);
}
}
- this._moveStart(true);
+ this._moveStart(true, options.noMoveStart);
frame.call(this);
return this;
// @method setMinZoom(zoom: Number): this
// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
setMinZoom: function (zoom) {
+ var oldZoom = this.options.minZoom;
this.options.minZoom = zoom;
- if (this._loaded && this.getZoom() < this.options.minZoom) {
- return this.setZoom(zoom);
+ if (this._loaded && oldZoom !== zoom) {
+ this.fire('zoomlevelschange');
+
+ if (this.getZoom() < this.options.minZoom) {
+ return this.setZoom(zoom);
+ }
}
return this;
// @method setMaxZoom(zoom: Number): this
// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
setMaxZoom: function (zoom) {
+ var oldZoom = this.options.maxZoom;
this.options.maxZoom = zoom;
- if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
- return this.setZoom(zoom);
+ if (this._loaded && oldZoom !== zoom) {
+ this.fire('zoomlevelschange');
+
+ if (this.getZoom() > this.options.maxZoom) {
+ return this.setZoom(zoom);
+ }
}
return this;
return this;
},
- // @method invalidateSize(options: Zoom/Pan options): this
+ // @method invalidateSize(options: Zoom/pan options): this
// Checks if the map container size changed and updates the map if so —
// call it after you've changed the map size dynamically, also animating
// pan by default. If `options.pan` is `false`, panning will not occur.
this.fire('locationfound', data);
},
- // TODO handler.addTo
- // TODO Appropiate docs section?
+ // TODO Appropriate docs section?
// @section Other Methods
// @method addHandler(name: String, HandlerClass: Function): this
// Adds a new `Handler` to the map, given its name and constructor function.
} catch (e) {
/*eslint-disable */
this._container._leaflet_id = undefined;
- /*eslint-enable */
+ /* eslint-enable */
this._containerId = undefined;
}
+ if (this._locationWatchId !== undefined) {
+ this.stopLocate();
+ }
+
+ this._stop();
+
remove(this._mapPane);
if (this._clearControlPos) {
// Pane for `GridLayer`s and `TileLayer`s
this.createPane('tilePane');
// @pane overlayPane: HTMLElement = 400
- // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
+ // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
this.createPane('shadowPane');
// @pane shadowPane: HTMLElement = 500
// Pane for overlay shadows (e.g. `Marker` shadows)
// Pane for `Icon`s of `Marker`s
this.createPane('markerPane');
// @pane tooltipPane: HTMLElement = 650
- // Pane for tooltip.
+ // Pane for `Tooltip`s.
this.createPane('tooltipPane');
// @pane popupPane: HTMLElement = 700
// Pane for `Popup`s.
var zoomChanged = this._zoom !== zoom;
this
- ._moveStart(zoomChanged)
+ ._moveStart(zoomChanged, false)
._move(center, zoom)
._moveEnd(zoomChanged);
}
},
- _moveStart: function (zoomChanged) {
+ _moveStart: function (zoomChanged, noMoveStart) {
// @event zoomstart: Event
// Fired when the map zoom is about to change (e.g. before zoom animation).
// @event movestart: Event
if (zoomChanged) {
this.fire('zoomstart');
}
- return this.fire('movestart');
+ if (!noMoveStart) {
+ this.fire('movestart');
+ }
+ return this;
},
_move: function (center, zoom, data) {
};
if (e.type !== 'keypress') {
- var isMarker = (target.options && 'icon' in target.options);
+ var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
data.containerPoint = isMarker ?
- this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
+ this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
}
_tryAnimatedPan: function (center, options) {
// difference between the new and current centers in pixels
- var offset = this._getCenterOffset(center)._floor();
+ var offset = this._getCenterOffset(center)._trunc();
// don't animate too far unless animate: true specified in options
if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
requestAnimFrame(function () {
this
- ._moveStart(true)
+ ._moveStart(true, false)
._animateZoom(center, zoom, true);
}, this);
},
_animateZoom: function (center, zoom, startAnim, noUpdate) {
+ if (!this._mapPane) { return; }
+
if (startAnim) {
this._animatingZoom = true;
_onZoomTransitionEnd: function () {
if (!this._animatingZoom) { return; }
- removeClass(this._mapPane, 'leaflet-zoom-anim');
+ if (this._mapPane) {
+ removeClass(this._mapPane, 'leaflet-zoom-anim');
+ }
this._animatingZoom = false;
y = map.getSize().y / 2;
var maxMeters = map.distance(
- map.containerPointToLatLng([0, y]),
- map.containerPointToLatLng([this.options.maxWidth, y]));
+ map.containerPointToLatLng([0, y]),
+ map.containerPointToLatLng([this.options.maxWidth, y]));
this._updateScales(maxMeters);
},
// Called when the handler is disabled, should remove the event hooks added previously.
});
+// @section There is static function which can be called without instantiating L.Handler:
+// @function addTo(map: Map, name: String): this
+// Adds a new Handler to the given map with the given name.
+Handler.addTo = function (map, name) {
+ map.addHandler(name, this);
+ return this;
+};
+
var Mixin = {Events: Events};
/*
/*
* @namespace LineUtil
*
- * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
+ * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
*/
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
*/
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
- * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
+ * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
* Used by Leaflet to only show polygon points that are on the screen or near, increasing
* performance. Note that polygon points needs different algorithm for clipping
- * than polyline, so there's a seperate method for it.
+ * than polyline, so there's a separate method for it.
*/
function clipPolygon(points, bounds, round) {
var clippedPoints,
* The inverse of `project`. Projects a 2D point into a geographical location.
* Only accepts actual `L.Point` instances, not arrays.
+ * Note that the projection instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
+
*/
var LayerGroup = Layer.extend({
- initialize: function (layers) {
+ initialize: function (layers, options) {
+ setOptions(this, options);
+
this._layers = {};
var i, len;
// @method clearLayers(): this
// Removes all the layers from the group.
clearLayers: function () {
- for (var i in this._layers) {
- this.removeLayer(this._layers[i]);
- }
- return this;
+ return this.eachLayer(this.removeLayer, this);
},
// @method invoke(methodName: String, …): this
},
onAdd: function (map) {
- for (var i in this._layers) {
- map.addLayer(this._layers[i]);
- }
+ this.eachLayer(map.addLayer, map);
},
onRemove: function (map) {
- for (var i in this._layers) {
- map.removeLayer(this._layers[i]);
- }
+ this.eachLayer(map.removeLayer, map);
},
// @method eachLayer(fn: Function, context?: Object): this
// Returns an array of all the layers added to the group.
getLayers: function () {
var layers = [];
-
- for (var i in this._layers) {
- layers.push(this._layers[i]);
- }
+ this.eachLayer(layers.push, layers);
return layers;
},
});
-// @factory L.layerGroup(layers?: Layer[])
-// Create a layer group, optionally given an initial set of layers.
-var layerGroup = function (layers) {
- return new LayerGroup(layers);
+// @factory L.layerGroup(layers?: Layer[], options?: Object)
+// Create a layer group, optionally given an initial set of layers and an `options` object.
+var layerGroup = function (layers, options) {
+ return new LayerGroup(layers, options);
};
/*
},
// @method bringToBack(): this
- // Brings the layer group to the top of all other layers
+ // Brings the layer group to the back of all other layers
bringToBack: function () {
return this.invoke('bringToBack');
},
* will be aligned so that this point is at the marker's geographical location. Centered
* by default if size is specified, also can be set in CSS with negative margins.
*
- * @option popupAnchor: Point = null
+ * @option popupAnchor: Point = [0, 0]
* The coordinates of the point from which popups will "open", relative to the icon anchor.
*
+ * @option tooltipAnchor: Point = [0, 0]
+ * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
+ *
* @option shadowUrl: String = null
* The URL to the icon shadow image. If not specified, no shadow image will be created.
*
* A custom class name to assign to both icon and shadow images. Empty by default.
*/
+ options: {
+ popupAnchor: [0, 0],
+ tooltipAnchor: [0, 0],
+ },
+
initialize: function (options) {
setOptions(this, options);
},
}
// @option imagePath: String
- // `Icon.Default` will try to auto-detect the absolute location of the
+ // `Icon.Default` will try to auto-detect the location of the
// blue icon images. If you are placing these images in a non-standard
- // way, set this option to point to the right absolute path.
+ // way, set this option to point to the right path.
return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
},
if (path === null || path.indexOf('url') !== 0) {
path = '';
} else {
- path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
+ path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
}
return path;
this._draggable.on({
dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).enable();
removeHooks: function () {
this._draggable.off({
dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).disable();
return this._draggable && this._draggable._moved;
},
+ _adjustPan: function (e) {
+ var marker = this._marker,
+ map = marker._map,
+ speed = this._marker.options.autoPanSpeed,
+ padding = this._marker.options.autoPanPadding,
+ iconPos = L.DomUtil.getPosition(marker._icon),
+ bounds = map.getPixelBounds(),
+ origin = map.getPixelOrigin();
+
+ var panBounds = toBounds(
+ bounds.min._subtract(origin).add(padding),
+ bounds.max._subtract(origin).subtract(padding)
+ );
+
+ if (!panBounds.contains(iconPos)) {
+ // Compute incremental movement
+ var movement = toPoint(
+ (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
+ (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
+
+ (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
+ (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
+ ).multiplyBy(speed);
+
+ map.panBy(movement, {animate: false});
+
+ this._draggable._newPos._add(movement);
+ this._draggable._startPos._add(movement);
+
+ L.DomUtil.setPosition(marker._icon, this._draggable._newPos);
+ this._onDrag(e);
+
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
_onDragStart: function () {
// @section Dragging events
// @event dragstart: Event
.fire('dragstart');
},
+ _onPreDrag: function (e) {
+ if (this._marker.options.autoPan) {
+ cancelAnimFrame(this._panRequest);
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
_onDrag: function (e) {
var marker = this._marker,
shadow = marker._shadow,
// @event dragend: DragEndEvent
// Fired when the user stops dragging the marker.
+ cancelAnimFrame(this._panRequest);
+
// @event moveend: Event
// Fired when the marker stops moving (because of dragging).
delete this._oldLatLng;
// Whether the marker is draggable with mouse/touch or not.
draggable: false,
+ // @option autoPan: Boolean = false
+ // Set it to `true` if you want the map to do panning animation when marker hits the edges.
+ autoPan: false,
+
+ // @option autoPanPadding: Point = Point(50, 50)
+ // Equivalent of setting both top left and bottom right autopan padding to the same value.
+ autoPanPadding: [50, 50],
+
+ // @option autoPanSpeed: Number = 10
+ // Number of pixels the map should move by.
+ autoPanSpeed: 10,
+
// @option keyboard: Boolean = true
// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
keyboard: true,
update: function () {
- if (this._icon) {
+ if (this._icon && this._map) {
var pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos);
}
if (options.title) {
icon.title = options.title;
}
- if (options.alt) {
- icon.alt = options.alt;
+
+ if (icon.tagName === 'IMG') {
+ icon.alt = options.alt || '';
}
}
},
_getPopupAnchor: function () {
- return this.options.icon.options.popupAnchor || [0, 0];
+ return this.options.icon.options.popupAnchor;
},
_getTooltipAnchor: function () {
- return this.options.icon.options.tooltipAnchor || [0, 0];
+ return this.options.icon.options.tooltipAnchor;
}
});
_clickTolerance: function () {
// used when doing hit detection for Canvas layers
- return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
+ return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
}
});
}
this._point = p.subtract(map.getPixelOrigin());
- this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
- this._radiusY = Math.max(Math.round(p.y - top.y), 1);
+ this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
+ this._radiusY = p.y - top.y;
} else {
var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
return !this._latlngs.length;
},
+ // @method closestLayerPoint: Point
+ // Returns the point closest to `p` on the Polyline.
closestLayerPoint: function (p) {
var minDistance = Infinity,
minPoint = null,
for (var i = 0, len = coords.length, latlng; i < len; i++) {
latlng = levelsDeep ?
- coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
- (_coordsToLatLng || coordsToLatLng)(coords[i]);
+ coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
+ (_coordsToLatLng || coordsToLatLng)(coords[i]);
latlngs.push(latlng);
}
function latLngToCoords(latlng, precision) {
precision = typeof precision === 'number' ? precision : 6;
return latlng.alt !== undefined ?
- [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
- [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
}
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
function getFeature(layer, newGeometry) {
return layer.feature ?
- extend({}, layer.feature, {geometry: newGeometry}) :
- asFeature(newGeometry);
+ extend({}, layer.feature, {geometry: newGeometry}) :
+ asFeature(newGeometry);
}
// @function asFeature(geojson: Object): Object
},
_initImage: function () {
- var img = this._image = create$1('img',
- 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +
- (this.options.className || ''));
+ var wasElementSupplied = this._url.tagName === 'IMG';
+ var img = this._image = wasElementSupplied ? this._url : create$1('img');
+
+ addClass(img, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(img, this.options.className); }
img.onselectstart = falseFn;
img.onmousemove = falseFn;
this._updateZIndex();
}
+ if (wasElementSupplied) {
+ this._url = img.src;
+ return;
+ }
+
img.src = this._url;
img.alt = this.options.alt;
},
var wasElementSupplied = this._url.tagName === 'VIDEO';
var vid = this._image = wasElementSupplied ? this._url : create$1('video');
- vid.class = vid.class || '';
- vid.class += 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '');
+ addClass(vid, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
vid.onselectstart = falseFn;
vid.onmousemove = falseFn;
// Fired when the video has finished loading the first frame
vid.onloadeddata = bind(this.fire, this, 'load');
- if (wasElementSupplied) { return; }
+ if (wasElementSupplied) {
+ var sourceElements = vid.getElementsByTagName('source');
+ var sources = [];
+ for (var j = 0; j < sourceElements.length; j++) {
+ sources.push(sourceElements[j].src);
+ }
+
+ this._url = (sourceElements.length > 0) ? sources : [vid.src];
+ return;
+ }
if (!isArray(this._url)) { this._url = [this._url]; }
// the popup closing when another popup is opened.
autoClose: true,
+ // @option closeOnEscapeKey: Boolean = true
+ // Set it to `false` if you want to override the default behavior of
+ // the ESC key for closing of the popup.
+ closeOnEscapeKey: true,
+
// @option closeOnClick: Boolean = *
// Set it if you want to override the default behavior of the popup closing when user clicks
// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
},
// @method openPopup(latlng?: LatLng): this
- // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
+ // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
openPopup: function (layer, latlng) {
if (!(layer instanceof Layer)) {
latlng = layer;
* marker.bindTooltip("my tooltip text").openTooltip();
* ```
* Note about tooltip offset. Leaflet takes two options in consideration
- * for computing tooltip offseting:
+ * for computing tooltip offsetting:
* - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
* Add a positive x offset to move the tooltip to the right, and a positive y offset to
* move it to the bottom. Negatives will move to the left and top.
// @option direction: String = 'auto'
// Direction where to open the tooltip. Possible values are: `right`, `left`,
// `top`, `bottom`, `center`, `auto`.
- // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
+ // `auto` will dynamically switch between `right` and `left` according to the tooltip
// position on the map.
direction: 'auto',
},
// @method openTooltip(latlng?: LatLng): this
- // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
+ // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
openTooltip: function (layer, latlng) {
if (!(layer instanceof Layer)) {
latlng = layer;
remove(this._container);
map._removeZoomLimit(this);
this._container = null;
- this._tileZoom = null;
+ this._tileZoom = undefined;
},
// @method bringToFront: this
// @section Extension methods
// Layers extending `GridLayer` shall reimplement the following method.
// @method createTile(coords: Object, done?: Function): HTMLElement
- // Called only internally, must be overriden by classes extending `GridLayer`.
+ // Called only internally, must be overridden by classes extending `GridLayer`.
// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
// is specified, it must be called when the tile has finished loading and drawing.
createTile: function () {
}
this._removeAllTiles();
- this._tileZoom = null;
+ this._tileZoom = undefined;
},
_retainParent: function (x, y, z, minZoom) {
if (!this._isValidTile(coords)) { continue; }
- if (!this._tiles[this._tileCoordsToKey(coords)]) {
+ var tile = this._tiles[this._tileCoordsToKey(coords)];
+ if (tile) {
+ tile.current = true;
+ } else {
queue.push(coords);
}
}
return this._tileCoordsToBounds(this._keyToTileCoords(key));
},
- // converts tile coordinates to its geographical bounds
- _tileCoordsToBounds: function (coords) {
-
+ _tileCoordsToNwSe: function (coords) {
var map = this._map,
tileSize = this.getTileSize(),
-
nwPoint = coords.scaleBy(tileSize),
sePoint = nwPoint.add(tileSize),
-
nw = map.unproject(nwPoint, coords.z),
- se = map.unproject(sePoint, coords.z),
- bounds = new LatLngBounds(nw, se);
+ se = map.unproject(sePoint, coords.z);
+ return [nw, se];
+ },
+
+ // converts tile coordinates to its geographical bounds
+ _tileCoordsToBounds: function (coords) {
+ var bp = this._tileCoordsToNwSe(coords),
+ bounds = new LatLngBounds(bp[0], bp[1]);
if (!this.options.noWrap) {
- map.wrapLatLngBounds(bounds);
+ bounds = this._map.wrapLatLngBounds(bounds);
}
-
return bounds;
},
-
// converts tile coordinates to key for the tile cache
_tileCoordsToKey: function (coords) {
return coords.x + ':' + coords.y + ':' + coords.z;
var tile = this._tiles[key];
if (!tile) { return; }
+ // Cancels any pending http requests associated with the tile
+ // unless we're on Android's stock browser,
+ // see https://github.com/Leaflet/Leaflet/issues/137
+ if (!androidStock) {
+ tile.el.setAttribute('src', emptyImageUrl);
+ }
remove(tile.el);
delete this._tiles[key];
// @method createTile(coords: Object, done?: Function): HTMLElement
// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
- // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
+ // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
// callback is called when the tile has been loaded.
createTile: function (coords, done) {
var tile = document.createElement('img');
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
- z: this._getZoomForUrl()
+ z: coords.z ? coords.z : this._getZoomForUrl()
};
if (this._map && !this._map.options.crs.infinite) {
var invertedY = this._globalTileRange.max.y - coords.y;
_tileOnError: function (done, tile, e) {
var errorUrl = this.options.errorTileUrl;
- if (errorUrl && tile.src !== errorUrl) {
+ if (errorUrl && tile.getAttribute('src') !== errorUrl) {
tile.src = errorUrl;
}
done(e, tile);
if (!tile.complete) {
tile.src = emptyImageUrl;
remove(tile);
+ delete this._tiles[i];
}
}
}
options = setOptions(this, options);
- wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
+ var realRetina = options.detectRetina && retina ? 2 : 1;
+ var tileSize = this.getTileSize();
+ wmsParams.width = tileSize.x * realRetina;
+ wmsParams.height = tileSize.y * realRetina;
this.wmsParams = wmsParams;
},
getTileUrl: function (coords) {
- var tileBounds = this._tileCoordsToBounds(coords),
- nw = this._crs.project(tileBounds.getNorthWest()),
- se = this._crs.project(tileBounds.getSouthEast()),
-
+ var tileBounds = this._tileCoordsToNwSe(coords),
+ crs = this._crs,
+ bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
+ min = bounds.min,
+ max = bounds.max,
bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
- [se.y, nw.x, nw.y, se.x] :
- [nw.x, se.y, se.x, nw.y]).join(','),
-
- url = TileLayer.prototype.getTileUrl.call(this, coords);
-
+ [min.y, min.x, max.y, max.x] :
+ [min.x, min.y, max.x, max.y]).join(','),
+ url = L.TileLayer.prototype.getTileUrl.call(this, coords);
return url +
getParamString(this.wmsParams, url, this.options.uppercase) +
(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
// @option padding: Number = 0.1
// How much to extend the clip area around the map view (relative to its size)
// e.g. 0.1 would be 10% of map view in each direction
- padding: 0.1
+ padding: 0.1,
+
+ // @option tolerance: Number = 0
+ // How much to extend click tolerance round a path/object on the map
+ tolerance : 0
},
initialize: function (options) {
var p = layer._point,
ctx = this._ctx,
- r = layer._radius,
- s = (layer._radiusY || r) / r;
+ r = Math.max(Math.round(layer._radius), 1),
+ s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
this._drawnLayers[layer._leaflet_id] = layer;
prev.next = next;
} else if (next) {
// Update first entry unless this is the
- // signle entry
+ // single entry
this._drawFirst = next;
}
next.prev = prev;
} else if (prev) {
// Update last entry unless this is the
- // signle entry
+ // single entry
this._drawLast = prev;
}
r2 = Math.round(layer._radiusY || r);
this._setPath(layer, layer._empty() ? 'M0 0' :
- 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
+ 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
},
_setPath: function (layer, path) {
off(this._container);
delete this._container;
delete this._rootGroup;
+ delete this._svgSize;
},
_onZoomStart: function () {
_updateCircle: function (layer) {
var p = layer._point,
- r = layer._radius,
- r2 = layer._radiusY || r,
+ r = Math.max(Math.round(layer._radius), 1),
+ r2 = Math.max(Math.round(layer._radiusY), 1) || r,
arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
// drawing a circle with two half-arcs
var d = layer._empty() ? 'M0 0' :
- 'M' + (p.x - r) + ',' + p.y +
- arc + (r * 2) + ',0 ' +
- arc + (-r * 2) + ',0 ';
+ 'M' + (p.x - r) + ',' + p.y +
+ arc + (r * 2) + ',0 ' +
+ arc + (-r * 2) + ',0 ';
this._setPath(layer, d);
},
SVG.include(vmlMixin);
}
+// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
function svg$1(options) {
/*
* @class Rectangle
- * @aka L.Retangle
+ * @aka L.Rectangle
* @inherits Polygon
*
* A class for drawing rectangle overlays on a map. Extends `Polygon`.
this._positions.push(pos);
this._times.push(time);
- if (time - this._times[0] > 50) {
- this._positions.shift();
- this._times.shift();
- }
+ this._prunePositions(time);
}
this._map
.fire('drag', e);
},
+ _prunePositions: function (time) {
+ while (this._positions.length > 1 && time - this._times[0] > 50) {
+ this._positions.shift();
+ this._times.shift();
+ }
+ },
+
_onZoomEnd: function () {
var pxCenter = this._map.getSize().divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
map.fire('moveend');
} else {
+ this._prunePositions(+new Date());
var direction = this._lastPos.subtract(this._positions[0]),
duration = (this._lastTime - this._times[0]) / 1000,
} else if (key in this._zoomKeys) {
map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
- } else if (key === 27 && map._popup) {
+ } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
map.closePopup();
} else {
}
if (!this._moved) {
- map._moveStart(true);
+ map._moveStart(true, false);
this._moved = true;
}